mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-13 05:12:55 -07:00
fix(mcp): treat GET 404 as 405 in StreamableHTTPClientTransport (#24847)
Co-authored-by: Coco Sheng <cocosheng@google.com> Co-authored-by: Spencer <spencertang@google.com> Co-authored-by: Tommaso Sciortino <sciortino@gmail.com>
This commit is contained in:
@@ -1816,6 +1816,48 @@ describe('mcp-client', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('wraps fetch to convert GET 404 to 405 for POST-only servers (e.g. n8n)', async () => {
|
||||
const mockFetch = vi
|
||||
.fn()
|
||||
.mockResolvedValue(
|
||||
new Response(null, { status: 404, statusText: 'Not Found' }),
|
||||
);
|
||||
vi.stubGlobal('fetch', mockFetch);
|
||||
|
||||
try {
|
||||
const transport = await createTransport(
|
||||
'test-server',
|
||||
{ httpUrl: 'http://test-server' },
|
||||
false,
|
||||
MOCK_CONTEXT,
|
||||
);
|
||||
|
||||
const wrappedFetch = (
|
||||
transport as unknown as {
|
||||
_fetch: (
|
||||
url: URL | string,
|
||||
init?: RequestInit,
|
||||
) => Promise<Response>;
|
||||
}
|
||||
)._fetch;
|
||||
|
||||
// GET 404 → 405: server doesn't support optional SSE GET stream
|
||||
const getRes = await wrappedFetch('http://test-server', {
|
||||
method: 'GET',
|
||||
});
|
||||
expect(getRes.status).toBe(405);
|
||||
expect(getRes.statusText).toBe('Method Not Allowed');
|
||||
|
||||
// POST 404 → unchanged: real "not found" errors must still propagate
|
||||
const postRes = await wrappedFetch('http://test-server', {
|
||||
method: 'POST',
|
||||
});
|
||||
expect(postRes.status).toBe(404);
|
||||
} finally {
|
||||
vi.unstubAllGlobals();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('should connect via url', () => {
|
||||
|
||||
@@ -2123,6 +2123,22 @@ function createUrlTransport(
|
||||
| StreamableHTTPClientTransportOptions
|
||||
| SSEClientTransportOptions,
|
||||
): StreamableHTTPClientTransport | SSEClientTransport {
|
||||
// Wrap fetch to treat GET 404 as 405 so servers that do not support the
|
||||
// optional SSE GET stream (e.g. n8n native MCP) are handled gracefully.
|
||||
// The SDK already silently ignores 405; 404 is semantically equivalent here.
|
||||
const baseFetch =
|
||||
(transportOptions as StreamableHTTPClientTransportOptions).fetch ??
|
||||
globalThis.fetch;
|
||||
const httpOptions: StreamableHTTPClientTransportOptions = {
|
||||
...transportOptions,
|
||||
fetch: async (url, init) => {
|
||||
const res = await baseFetch(url, init);
|
||||
return init?.method === 'GET' && res.status === 404
|
||||
? new Response(null, { status: 405, statusText: 'Method Not Allowed' })
|
||||
: res;
|
||||
},
|
||||
};
|
||||
|
||||
// Priority 1: httpUrl (deprecated)
|
||||
if (mcpServerConfig.httpUrl) {
|
||||
if (mcpServerConfig.url) {
|
||||
@@ -2133,7 +2149,7 @@ function createUrlTransport(
|
||||
}
|
||||
return new StreamableHTTPClientTransport(
|
||||
new URL(mcpServerConfig.httpUrl),
|
||||
transportOptions,
|
||||
httpOptions,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2142,7 +2158,7 @@ function createUrlTransport(
|
||||
if (mcpServerConfig.type === 'http') {
|
||||
return new StreamableHTTPClientTransport(
|
||||
new URL(mcpServerConfig.url),
|
||||
transportOptions,
|
||||
httpOptions,
|
||||
);
|
||||
} else if (mcpServerConfig.type === 'sse') {
|
||||
return new SSEClientTransport(
|
||||
@@ -2156,7 +2172,7 @@ function createUrlTransport(
|
||||
if (mcpServerConfig.url) {
|
||||
return new StreamableHTTPClientTransport(
|
||||
new URL(mcpServerConfig.url),
|
||||
transportOptions,
|
||||
httpOptions,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user