diff --git a/CHANGELOG.md b/CHANGELOG.md index c79a6c643f6..0752cf767f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ - Added `appcheck:providers:list`, `appcheck:providers:get` and `appcheck:providers:set` to configure App Check attestation providers for an app. - Added `appcheck:apps:list` to show every app with its configured App Check providers. -- Added web app support for Crashlytics MCP tools and prompts.>>>>>>> main +- Added web app support for Crashlytics MCP tools and prompts. +- Added support for forwarding custom HTTP headers (`Mcp-Param-*`) to remote MCP tools when defined in tool parameter input schemas (`x-mcp-header`), per [SEP-2243](https://modelcontextprotocol.io/seps/2243-http-standardization). diff --git a/src/mcp/onemcp/onemcp_server.spec.ts b/src/mcp/onemcp/onemcp_server.spec.ts index 07dd0e79a10..5f82b1d9145 100644 --- a/src/mcp/onemcp/onemcp_server.spec.ts +++ b/src/mcp/onemcp/onemcp_server.spec.ts @@ -151,6 +151,44 @@ describe("OneMcpServer", () => { }); }); + it("should include Mcp-Param-X HTTP headers when tool inputSchema defines x-mcp-header", async () => { + const mockMcpTool = { + name: "execute_sql", + inputSchema: { + type: "object", + properties: { + region: { + type: "string", + "x-mcp-header": "Region", + }, + query: { + type: "string", + }, + }, + }, + }; + clientRequestStub.onFirstCall().resolves({ + body: { result: { tools: [mockMcpTool] } }, + }); + + const tools = await server.listTools(); + const tool = tools[0]; + + clientRequestStub.onSecondCall().resolves({ + body: { result: { content: [] } }, + }); + + await tool.fn({ region: "us-west1", query: "SELECT 1" }, mockContext); + + expect(clientRequestStub.secondCall.args[0].headers).to.deep.include({ + "MCP-Protocol-Version": LATEST_PROTOCOL_VERSION, + "Mcp-Method": "tools/call", + "Mcp-Name": "execute_sql", + "x-goog-user-project": "test-project", + "Mcp-Param-Region": "us-west1", + }); + }); + it("should proxy tool call without x-goog-user-project header if projectId is missing", async () => { const mockMcpTool = { name: "test_tool", inputSchema: { type: "object", properties: {} } }; clientRequestStub.onFirstCall().resolves({ @@ -240,7 +278,7 @@ describe("OneMcpServer", () => { const fn = (serverWithFilter as any).callTool.bind(serverWithFilter); - await expect(fn("disallowed_tool", {}, mockContext)).to.be.rejectedWith( + await expect(fn("disallowed_tool", undefined, {}, mockContext)).to.be.rejectedWith( FirebaseError, /is not allowed on remote server/, ); diff --git a/src/mcp/onemcp/onemcp_server.ts b/src/mcp/onemcp/onemcp_server.ts index ea68a81ce36..22100762210 100644 --- a/src/mcp/onemcp/onemcp_server.ts +++ b/src/mcp/onemcp/onemcp_server.ts @@ -7,6 +7,7 @@ import { ListToolsRequest, CallToolRequest, LATEST_PROTOCOL_VERSION, + Tool, } from "@modelcontextprotocol/sdk/types.js"; import { Client } from "../../apiv2"; import { ServerTool, ServerToolMeta } from "../tool"; @@ -96,7 +97,7 @@ export class OneMcpServer { [x: string]: unknown; }, ctx: McpContext, - ) => this.callTool(mcpTool.name, args, ctx), + ) => this.callTool(mcpTool.name, mcpTool.inputSchema, args, ctx), isAvailable: () => Promise.resolve(true), })); } catch (error) { @@ -111,6 +112,7 @@ export class OneMcpServer { */ private async callTool( toolName: string, + inputSchema: Tool["inputSchema"] | undefined, args: { [x: string]: unknown; }, @@ -126,6 +128,20 @@ export class OneMcpServer { ); } + const paramHeaders: Record = {}; + 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 as Record)["x-mcp-header"]; + const argValue = args[paramName]; + if (argValue !== undefined && argValue !== null) { + paramHeaders[`Mcp-Param-${headerName}`] = String(argValue); + } + } + } + } + // TODO: Optimize this to not call ensure on every tool call. await ensure(ctx.projectId, this.serverUrl, this.feature, /* silent=*/ true); try { @@ -149,6 +165,7 @@ export class OneMcpServer { "Mcp-Method": "tools/call", "Mcp-Name": toolName, ...(ctx.projectId ? { "x-goog-user-project": ctx.projectId } : {}), + ...paramHeaders, }, }, );