From 49b575cc714966b6074b0271c5cdc01f06cc1d51 Mon Sep 17 00:00:00 2001 From: zh-ffmc Date: Wed, 28 Oct 2026 17:57:06 +0800 Subject: [PATCH] fix(mcp): drop X-Glean-Internal-Service header from gateway requests buildTransport() sends X-Glean-Internal-Service: true on every request to the public MCP gateway (/mcp/gateway/proxy). Glean's public edge refuses any external request carrying that internal-service marker with a bare HTTP 403 before auth, so plugin setup fails before the OAuth sign-in prompt is ever reached - on every instance. Reproduced live (plugin 3.4.2 and 3.5.1, affected since the header's introduction): POST /mcp/gateway/proxy (MCP initialize, NO header) -> 401 + Www-Authenticate: Bearer resource_metadata=... (correct) same POST with X-Glean-Internal-Service: true -> 403 Forbidden (GCP LB HTML page; no X-Glean-Trace-Id, request never reaches the Glean app) same POST with browser User-Agent + the header -> 403 (User-Agent is irrelevant; the header is the trigger) Removing only the header restores the full flow against a live instance: 401 -> OAuth discovery (PRM/ASM 200) -> dynamic registration 201 -> token 200 -> gateway initialize 200 -> tools enabled. The X-Glean-Gateway-Request-Metadata attribution header is untouched. Adds a regression test that captures the transport's request headers and asserts the internal-service marker is absent while gateway metadata remains (fails before this change, passes after). Full bundle suite: 435/435 green; typecheck clean. --- shared/glean/mcp/src/remote-client.ts | 6 ++++- shared/glean/mcp/tests/remote-client.test.ts | 28 ++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/shared/glean/mcp/src/remote-client.ts b/shared/glean/mcp/src/remote-client.ts index cb19530..99339b5 100644 --- a/shared/glean/mcp/src/remote-client.ts +++ b/shared/glean/mcp/src/remote-client.ts @@ -154,7 +154,11 @@ function buildTransport( ): StreamableHTTPClientTransport { const parsedUrl = new URL(serverUrl); const headers: Record = { - "X-Glean-Internal-Service": "true", + // Note: do NOT add an "X-Glean-Internal-Service" header here. Glean's + // public edge (GCP LB) refuses any external request carrying that + // internal-service marker with HTTP 403 before auth, which breaks the + // plugin's OAuth flow entirely (gleanwork/claude-plugins installs, 3.4.x + // and 3.5.x, were blocked on every instance). "X-Glean-Gateway-Request-Metadata": buildGatewayMetadataHeader(chatSessionId), }; diff --git a/shared/glean/mcp/tests/remote-client.test.ts b/shared/glean/mcp/tests/remote-client.test.ts index c500bc2..4d13516 100644 --- a/shared/glean/mcp/tests/remote-client.test.ts +++ b/shared/glean/mcp/tests/remote-client.test.ts @@ -70,6 +70,34 @@ describe("buildGatewayMetadataHeader", () => { }); }); +describe("transport request headers", () => { + // Regression: plugin builds up to 3.5.1 sent X-Glean-Internal-Service: true + // on every gateway request. Glean's public edge 403s any external request + // carrying that internal marker, before auth, breaking setup on every + // instance (401 OAuth challenge without it, 403 with it). + it("omits the internal-service marker and keeps gateway metadata", async () => { + let captured: Headers | undefined; + const handler = createMcpHandler(() => { + return new McpServer({ name: "test", version: "1.0.0" }); + }); + const client = await createRemoteClient("http://test.local/mcp", { + fetch: (url, init) => { + const request = new Request(url, init); + captured ??= request.headers; + return handler.fetch(request); + }, + }); + try { + expect(captured).toBeDefined(); + expect(captured!.has("x-glean-internal-service")).toBe(false); + expect(captured!.get("x-glean-gateway-request-metadata")).not.toBeNull(); + } finally { + await client.close(); + await handler.close(); + } + }); +}); + describe("remoteToolTimeoutMs", () => { const KEY = "GLEAN_REMOTE_TOOL_TIMEOUT_MS"; const original = process.env[KEY];