firebase-tools
Version:
Command-Line Interface for Firebase
123 lines (122 loc) • 5.11 kB
JavaScript
;
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;