diff --git a/packages/core/src/tools/mcp-client.test.ts b/packages/core/src/tools/mcp-client.test.ts index 4d682c52c2b..409f7b47275 100644 --- a/packages/core/src/tools/mcp-client.test.ts +++ b/packages/core/src/tools/mcp-client.test.ts @@ -2225,6 +2225,26 @@ describe('mcp-client', () => { vi.unstubAllGlobals(); } }); + + it('encodes non-ByteString header values for httpUrl transports', async () => { + const transport = await createTransport( + 'test-server', + { + httpUrl: 'http://test-server', + headers: { 'X-Custom': 'mąka' }, + }, + false, + MOCK_CONTEXT, + ); + + const headers = (unwrap(transport) as TestableTransport)._requestInit + ?.headers; + + expect(() => new Headers(headers)).not.toThrow(); + expect(headers?.['X-Custom']).toBe( + Buffer.from('mąka', 'utf8').toString('latin1'), + ); + }); }); describe('should connect via url', () => { diff --git a/packages/core/src/tools/mcp-client.ts b/packages/core/src/tools/mcp-client.ts index 47048526d22..5e512c5655e 100644 --- a/packages/core/src/tools/mcp-client.ts +++ b/packages/core/src/tools/mcp-client.ts @@ -1002,13 +1002,35 @@ function createTransportRequestInit( } return { - headers: { + headers: encodeHeaderValuesForFetch({ ...expandedHeaders, ...headers, - }, + }), }; } +function encodeHeaderValuesForFetch( + headers: Record, +): Record { + return Object.fromEntries( + Object.entries(headers).map(([key, value]) => [ + key, + isByteString(value) + ? value + : Buffer.from(value, 'utf8').toString('latin1'), + ]), + ); +} + +function isByteString(value: string): boolean { + for (const char of value) { + if (char.codePointAt(0)! > 255) { + return false; + } + } + return true; +} + /** * Create an AuthProvider for the MCP Transport. * @@ -1673,10 +1695,10 @@ function createSSETransportWithAuth( config: MCPServerConfig, accessToken?: string | null, ): SSEClientTransport { - const headers = { + const headers = encodeHeaderValuesForFetch({ ...config.headers, ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}), - }; + }); const options: SSEClientTransportOptions = {}; if (Object.keys(headers).length > 0) {