UNPKG

firebase-tools

Version:
123 lines (122 loc) 5.11 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.OneMcpServer = void 0; const types_js_1 = require("@modelcontextprotocol/sdk/types.js"); const apiv2_1 = require("../../apiv2"); const error_1 = require("../../error"); const ensureApiEnabled_1 = require("../../ensureApiEnabled"); class OneMcpServer { constructor(feature, serverUrl, meta, options = {}) { this.feature = feature; this.serverUrl = serverUrl; this.meta = meta; this.options = options; this.listClient = new apiv2_1.Client({ urlPrefix: this.serverUrl, auth: false, }); this.callClient = new apiv2_1.Client({ urlPrefix: this.serverUrl, auth: true, }); } async listTools() { try { const res = await this.listClient.post("/mcp", { method: "tools/list", jsonrpc: "2.0", id: 1, }, { headers: { "MCP-Protocol-Version": types_js_1.LATEST_PROTOCOL_VERSION, "Mcp-Method": "tools/list", }, }); const parsed = types_js_1.ListToolsResultSchema.parse(res.body.result); const tools = parsed.tools.filter((mcpTool) => { if (!this.options.allowedTools?.length) { return true; } return (this.options.allowedTools.includes(mcpTool.name) || this.options.allowedTools.includes(`${this.feature}_${mcpTool.name}`)); }); const optOutSet = new Set(this.options.toolsToOptOutProjectRequirement || []); return tools.map((mcpTool) => { const isOptedOut = this.meta.requiresProject === false || optOutSet.has(mcpTool.name) || optOutSet.has(`${this.feature}_${mcpTool.name}`); const toolRequiresProject = !isOptedOut; return { mcp: { ...mcpTool, name: `${this.feature}_${mcpTool.name}`, _meta: { ...this.meta, requiresProject: toolRequiresProject, feature: this.feature, }, }, fn: (args, ctx) => this.callTool(mcpTool.name, mcpTool.inputSchema, args, ctx), isAvailable: () => Promise.resolve(true), }; }); } catch (error) { throw new error_1.FirebaseError("Failed to fetch remote tools for " + this.serverUrl + ": " + JSON.stringify(error)); } } async callTool(toolName, inputSchema, args, ctx) { if (this.options.allowedTools?.length && !this.options.allowedTools.includes(toolName) && !this.options.allowedTools.includes(`${this.feature}_${toolName}`)) { throw new error_1.FirebaseError(`Tool '${toolName}' is not allowed on remote server for feature '${this.feature}'.`); } const paramHeaders = {}; const props = inputSchema?.properties; if (props && typeof props === "object" && args) { for (const [paramName, propSchema] of Object.entries(props)) { if (propSchema && typeof propSchema === "object" && "x-mcp-header" in propSchema) { const headerName = propSchema["x-mcp-header"]; const argValue = args[paramName]; if (argValue !== undefined && argValue !== null) { paramHeaders[`Mcp-Param-${headerName}`] = String(argValue); } } } } if (ctx.projectId) { await (0, ensureApiEnabled_1.ensure)(ctx.projectId, this.serverUrl, this.feature, true); } try { const res = await this.callClient.post("/mcp", { method: "tools/call", params: { name: toolName, arguments: args, }, jsonrpc: "2.0", id: 1, }, { headers: { "MCP-Protocol-Version": types_js_1.LATEST_PROTOCOL_VERSION, "Mcp-Method": "tools/call", "Mcp-Name": toolName, ...(ctx.projectId ? { "x-goog-user-project": ctx.projectId } : {}), ...paramHeaders, }, }); return types_js_1.CallToolResultSchema.parse(res.body.result); } catch (error) { if (error instanceof error_1.FirebaseError) { const firebaseError = error; const body = firebaseError.context?.body; if (body?.result?.isError) { return types_js_1.CallToolResultSchema.parse(body.result); } } throw error; } } } exports.OneMcpServer = OneMcpServer;