feat: add headers support to SSE transport MCP servers (#3902)

This commit is contained in:
Jack Wotherspoon 2025-07-11 15:59:42 -04:00 committed by GitHub
parent 8f12e8a114
commit 2826c7a1c6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 88 additions and 4 deletions

View File

@ -87,7 +87,7 @@ Each server configuration supports the following properties:
#### Optional
- **`args`** (string[]): Command-line arguments for Stdio transport
- **`headers`** (object): Custom HTTP headers when using `httpUrl`
- **`headers`** (object): Custom HTTP headers when using `url` or `httpUrl`
- **`env`** (object): Environment variables for the server process. Values can reference environment variables using `$VAR_NAME` or `${VAR_NAME}` syntax
- **`cwd`** (string): Working directory for Stdio transport
- **`timeout`** (number): Request timeout in milliseconds (default: 600,000ms = 10 minutes)

View File

@ -284,7 +284,10 @@ describe('discoverMcpTools', () => {
mockToolRegistry as any,
);
expect(SSEClientTransport).toHaveBeenCalledWith(new URL(serverConfig.url!));
expect(SSEClientTransport).toHaveBeenCalledWith(
new URL(serverConfig.url!),
{},
);
expect(mockToolRegistry.registerTool).toHaveBeenCalledWith(
expect.any(DiscoveredMCPTool),
);
@ -293,6 +296,75 @@ describe('discoverMcpTools', () => {
expect(registeredTool.name).toBe('tool-sse');
});
describe('SseClientTransport headers', () => {
const setupSseTest = async (headers?: Record<string, string>) => {
const serverConfig: MCPServerConfig = {
url: 'http://localhost:1234/sse',
...(headers && { headers }),
};
const serverName = headers
? 'sse-server-with-headers'
: 'sse-server-no-headers';
const toolName = headers ? 'tool-http-headers' : 'tool-http-no-headers';
mockConfig.getMcpServers.mockReturnValue({ [serverName]: serverConfig });
const mockTool = {
name: toolName,
description: `desc-${toolName}`,
inputSchema: { type: 'object' as const, properties: {} },
};
vi.mocked(Client.prototype.listTools).mockResolvedValue({
tools: [mockTool],
});
mockToolRegistry.getToolsByServer.mockReturnValueOnce([
expect.any(DiscoveredMCPTool),
]);
await discoverMcpTools(
mockConfig.getMcpServers() ?? {},
mockConfig.getMcpServerCommand(),
mockToolRegistry as any,
);
return { serverConfig };
};
it('should pass headers when provided', async () => {
const headers = {
Authorization: 'Bearer test-token',
'X-Custom-Header': 'custom-value',
};
const { serverConfig } = await setupSseTest(headers);
expect(SSEClientTransport).toHaveBeenCalledWith(
new URL(serverConfig.url!),
{ requestInit: { headers } },
);
});
it('should work without headers (backwards compatibility)', async () => {
const { serverConfig } = await setupSseTest();
expect(SSEClientTransport).toHaveBeenCalledWith(
new URL(serverConfig.url!),
{},
);
});
it('should pass oauth token when provided', async () => {
const headers = {
Authorization: 'Bearer test-token',
};
const { serverConfig } = await setupSseTest(headers);
expect(SSEClientTransport).toHaveBeenCalledWith(
new URL(serverConfig.url!),
{ requestInit: { headers } },
);
});
});
it('should discover tools via mcpServers config (streamable http)', async () => {
const serverConfig: MCPServerConfig = {
httpUrl: 'http://localhost:3000/mcp',

View File

@ -6,7 +6,10 @@
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
import {
SSEClientTransport,
SSEClientTransportOptions,
} from '@modelcontextprotocol/sdk/client/sse.js';
import {
StreamableHTTPClientTransport,
StreamableHTTPClientTransportOptions,
@ -190,7 +193,16 @@ async function connectAndDiscover(
transportOptions,
);
} else if (mcpServerConfig.url) {
transport = new SSEClientTransport(new URL(mcpServerConfig.url));
const transportOptions: SSEClientTransportOptions = {};
if (mcpServerConfig.headers) {
transportOptions.requestInit = {
headers: mcpServerConfig.headers,
};
}
transport = new SSEClientTransport(
new URL(mcpServerConfig.url),
transportOptions,
);
} else if (mcpServerConfig.command) {
transport = new StdioClientTransport({
command: mcpServerConfig.command,