/**
 * This file is part of the NocoBase (R) project.
 * Copyright (c) 2020-2024 NocoBase Co., Ltd.
 * Authors: NocoBase Team.
 *
 * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
 * For more information, please refer to: https://www.nocobase.com/agreement.
 */

var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var plugin_exports = {};
__export(plugin_exports, {
  PluginMcpServerServer: () => PluginMcpServerServer,
  default: () => plugin_default
});
module.exports = __toCommonJS(plugin_exports);
var import_server = require("@nocobase/server");
var import_mcp_tools = require("./mcp-tools");
var import_mcp_server = require("./mcp-server");
var import_crud_tool = require("./crud-tool");
function normalizeBasePath(path = "") {
  const normalized = path.replace(/\/+/g, "/").replace(/\/$/, "");
  return normalized || "/";
}
const MCP_PACKAGES_HEADER = "x-mcp-packages";
class PluginMcpServerServer extends import_server.Plugin {
  mcpServer;
  mcpScopes = ["mcp", "offline_access"];
  mcpToolsCache = /* @__PURE__ */ new Map();
  getApiBasePath() {
    return normalizeBasePath(process.env.API_BASE_PATH || "/api");
  }
  getMcpPath() {
    return `${this.getApiBasePath()}/mcp`;
  }
  getIdpOauthPlugin() {
    return this.app.pm.get("idp-oauth");
  }
  getOauthMetadata(ctx) {
    var _a, _b;
    const issuer = (_b = (_a = this.getIdpOauthPlugin()) == null ? void 0 : _a.service) == null ? void 0 : _b.getProviderContext(ctx).issuer;
    if (!issuer) {
      return void 0;
    }
    return {
      issuer,
      resource: `${issuer}/mcp`,
      resourceMetadataUrl: `${issuer}/.well-known/oauth-protected-resource`
    };
  }
  rewriteUnauthorizedResponse(ctx) {
    const metadata = this.getOauthMetadata(ctx);
    if (metadata) {
      ctx.set(
        "WWW-Authenticate",
        `Bearer resource_metadata="${metadata.resourceMetadataUrl}", scope="${this.mcpScopes.join(" ")}"`
      );
    }
    ctx.withoutDataWrapping = true;
    ctx.type = "application/json";
    ctx.status = 401;
    ctx.body = {
      errors: [{ message: "Authentication required" }]
    };
  }
  registerProtectedResourceMetadataRoute() {
    const metadataPath = `${this.getApiBasePath()}/.well-known/oauth-protected-resource`;
    this.app.use(
      async (ctx, next) => {
        if (ctx.path !== metadataPath || ctx.method !== "GET") {
          await next();
          return;
        }
        const metadata = this.getOauthMetadata(ctx);
        if (!metadata) {
          await next();
          return;
        }
        ctx.withoutDataWrapping = true;
        ctx.type = "application/json";
        ctx.body = {
          resource: metadata.resource,
          authorization_servers: [metadata.issuer],
          scopes_supported: [...this.mcpScopes]
        };
      },
      {
        tag: "mcp-oauth-protected-resource",
        before: "dataWrapping"
      }
    );
  }
  registerIdpResource() {
    var _a, _b;
    (_b = (_a = this.getIdpOauthPlugin()) == null ? void 0 : _a.service) == null ? void 0 : _b.registerResourceServer("mcp", {
      path: "/mcp",
      scope: this.mcpScopes.join(" "),
      accessTokenFormat: "jwt",
      jwt: {
        sign: { alg: "RS256" }
      }
    });
  }
  unregisterIdpResource() {
    var _a, _b, _c;
    (_c = (_b = (_a = this.getIdpOauthPlugin()) == null ? void 0 : _a.service) == null ? void 0 : _b.unregisterResourceServer) == null ? void 0 : _c.call(_b, "mcp");
  }
  parsePackagePatterns(ctx) {
    var _a, _b, _c;
    const value = ((_a = ctx == null ? void 0 : ctx.headers) == null ? void 0 : _a[MCP_PACKAGES_HEADER]) ?? ((_c = (_b = ctx == null ? void 0 : ctx.request) == null ? void 0 : _b.headers) == null ? void 0 : _c[MCP_PACKAGES_HEADER]);
    if (typeof value === "undefined") {
      return void 0;
    }
    const rawValues = (Array.isArray(value) ? value : [value]).flatMap((item) => String(item).split(","));
    return (0, import_mcp_tools.normalizePackagePatterns)(rawValues);
  }
  getMcpToolsCacheKey(packagePatterns) {
    if (typeof packagePatterns === "undefined") {
      return "__default__";
    }
    return packagePatterns.slice().sort().join(",");
  }
  async getMcpTools(ctx) {
    const packagePatterns = this.parsePackagePatterns(ctx);
    const cacheKey = this.getMcpToolsCacheKey(packagePatterns);
    let toolsPromise = this.mcpToolsCache.get(cacheKey);
    if (!toolsPromise) {
      toolsPromise = (0, import_mcp_tools.collectMcpToolsFromSwagger)({
        app: this.app,
        packagePatterns
      }).then((apiTools) => [
        ...apiTools,
        ...(0, import_crud_tool.createCrudTools)({ app: this.app, mcpToolsManager: this.ai.mcpToolsManager })
      ]).catch((error) => {
        this.mcpToolsCache.delete(cacheKey);
        throw error;
      });
      this.mcpToolsCache.set(cacheKey, toolsPromise);
    }
    return toolsPromise;
  }
  async afterAdd() {
  }
  async beforeLoad() {
  }
  async load() {
    var _a;
    if ((_a = this.getIdpOauthPlugin()) == null ? void 0 : _a.service) {
      this.registerIdpResource();
      this.registerProtectedResourceMetadataRoute();
    }
    this.mcpServer = new import_mcp_server.McpServer({
      name: "nocobase-mcp-server",
      version: this.options.version || "0.0.0",
      toolsManager: this.ai.mcpToolsManager,
      getTools: async (ctx) => this.getMcpTools(ctx),
      logger: this.log
    });
    this.app.use(
      async (ctx, next) => {
        if (ctx.path !== this.getMcpPath()) {
          return next();
        }
        try {
          await next();
        } catch (error) {
          if (ctx.status === 401 || (error == null ? void 0 : error.status) === 401 || (error == null ? void 0 : error.statusCode) === 401) {
            this.rewriteUnauthorizedResponse(ctx);
            return;
          }
          throw error;
        }
        if (ctx.status === 401) {
          this.rewriteUnauthorizedResponse(ctx);
        }
      },
      {
        tag: "mcp-unauthorized-response",
        before: "dataSource"
      }
    );
    const mcpHandler = async (ctx) => {
      await this.mcpServer.handlePost(ctx);
    };
    this.app.resourceManager.define({
      name: "mcp",
      actions: {
        // list and create are default methods for 'GET' and 'POST' requests
        // so that the mcp server can be called via '/mcp' endpoint directly
        list: mcpHandler,
        create: mcpHandler
      },
      only: ["list", "create"]
    });
    this.app.acl.allow("mcp", "*", "loggedIn");
  }
  async install() {
  }
  async afterEnable() {
  }
  async afterDisable() {
    this.unregisterIdpResource();
  }
  async remove() {
    this.unregisterIdpResource();
  }
}
var plugin_default = PluginMcpServerServer;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  PluginMcpServerServer
});
