Skip to content

Commit d841a61

Browse files
committed
fix(client): let auth headers override request init
1 parent 16d13ab commit d841a61

4 files changed

Lines changed: 64 additions & 5 deletions

File tree

packages/client/src/client/sse.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,8 @@ export class SSEClientTransport implements Transport {
120120
const extraHeaders = normalizeHeaders(this._requestInit?.headers);
121121

122122
return new Headers({
123-
...headers,
124-
...extraHeaders
123+
...extraHeaders,
124+
...headers
125125
});
126126
}
127127

packages/client/src/client/streamableHttp.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,8 +226,8 @@ export class StreamableHTTPClientTransport implements Transport {
226226
const extraHeaders = normalizeHeaders(this._requestInit?.headers);
227227

228228
return new Headers({
229-
...headers,
230-
...extraHeaders
229+
...extraHeaders,
230+
...headers
231231
});
232232
}
233233

packages/client/test/client/sse.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,37 @@ describe('SSEClientTransport', () => {
658658
expect(lastServerRequest.headers['x-custom-header']).toBe('custom-value');
659659
});
660660

661+
it('uses auth provider Authorization over requestInit Authorization', async () => {
662+
mockAuthProvider.tokens.mockResolvedValue({
663+
access_token: 'fresh-token',
664+
token_type: 'Bearer'
665+
});
666+
667+
transport = new SSEClientTransport(resourceBaseUrl, {
668+
authProvider: mockAuthProvider,
669+
requestInit: {
670+
headers: {
671+
Authorization: 'Bearer stale-token',
672+
'X-Custom-Header': 'custom-value'
673+
}
674+
}
675+
});
676+
677+
await transport.start();
678+
679+
const message: JSONRPCMessage = {
680+
jsonrpc: '2.0',
681+
id: '1',
682+
method: 'test',
683+
params: {}
684+
};
685+
686+
await transport.send(message);
687+
688+
expect(lastServerRequest.headers.authorization).toBe('Bearer fresh-token');
689+
expect(lastServerRequest.headers['x-custom-header']).toBe('custom-value');
690+
});
691+
661692
it('refreshes expired token during SSE connection', async () => {
662693
// Mock tokens() to return expired token until saveTokens is called
663694
let currentTokens: OAuthTokens = {

packages/client/test/client/streamableHttp.test.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { JSONRPCMessage, JSONRPCRequest } from '@modelcontextprotocol/core'
22
import { OAuthError, OAuthErrorCode, SdkErrorCode, SdkHttpError } from '@modelcontextprotocol/core';
33
import type { Mock, Mocked } from 'vitest';
44

5-
import type { OAuthClientProvider } from '../../src/client/auth.js';
5+
import type { AuthProvider, OAuthClientProvider } from '../../src/client/auth.js';
66
import { UnauthorizedError } from '../../src/client/auth.js';
77
import type { ReconnectionScheduler, StartSSEOptions, StreamableHTTPReconnectionOptions } from '../../src/client/streamableHttp.js';
88
import { StreamableHTTPClientTransport } from '../../src/client/streamableHttp.js';
@@ -629,6 +629,34 @@ describe('StreamableHTTPClientTransport', () => {
629629
expect((actualReqInit.headers as Headers).get('x-custom-header')).toBe('CustomValue');
630630
});
631631

632+
it('uses auth provider Authorization over requestInit Authorization', async () => {
633+
const authProvider: AuthProvider = {
634+
token: vi.fn(async () => 'fresh-token')
635+
};
636+
637+
transport = new StreamableHTTPClientTransport(new URL('http://localhost:1234/mcp'), {
638+
authProvider,
639+
requestInit: {
640+
headers: {
641+
Authorization: 'Bearer stale-token',
642+
'X-Custom-Header': 'CustomValue'
643+
}
644+
}
645+
});
646+
647+
let actualReqInit: RequestInit = {};
648+
(globalThis.fetch as Mock).mockImplementation(async (_url, reqInit) => {
649+
actualReqInit = reqInit;
650+
return new Response(null, { status: 202 });
651+
});
652+
653+
await transport.send({ jsonrpc: '2.0', method: 'test', params: {} } as JSONRPCMessage);
654+
655+
const headers = actualReqInit.headers as Headers;
656+
expect(headers.get('authorization')).toBe('Bearer fresh-token');
657+
expect(headers.get('x-custom-header')).toBe('CustomValue');
658+
});
659+
632660
it('should append custom Accept header to required types on POST requests', async () => {
633661
transport = new StreamableHTTPClientTransport(new URL('http://localhost:1234/mcp'), {
634662
requestInit: {

0 commit comments

Comments
 (0)