Skip to content

Commit 5063f4a

Browse files
authored
feat: Transport setHeaders (#44)
Signed-off-by: John McBride <john@zuplo.com>
1 parent aaff617 commit 5063f4a

File tree

5 files changed

+69
-50
lines changed

5 files changed

+69
-50
lines changed

src/client/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import type {
1818
Tool,
1919
} from "../mcp/20250618/types.js";
2020
import { LATEST_PROTOCOL_VERSION } from "../mcp/versions.js";
21-
import type { Transport } from "../transport/types.js";
21+
import type { Transport, TransportOptions } from "../transport/types.js";
2222
import type { MCPClientOptions } from "./types.js";
2323

2424
export const DEFAULT_MCP_CLIENT_NAME = "MCP Client";
@@ -34,11 +34,13 @@ export class MCPClient {
3434
private protocolVersion?: string;
3535
private logger: Logger;
3636
private requestId = 1;
37+
private transportOptions: TransportOptions;
3738

3839
constructor(options: MCPClientOptions = {}) {
3940
this.name = options.name || DEFAULT_MCP_CLIENT_NAME;
4041
this.version = options.version || DEFAULT_MCP_CLIENT_VERSION;
4142
this.logger = options.logger || createDefaultLogger();
43+
this.transportOptions = options.transportOptions || {};
4244
this.capabilities = {
4345
experimental: {},
4446
sampling: {},
@@ -50,6 +52,9 @@ export class MCPClient {
5052
* Connect to an MCP server using the provided transport
5153
*/
5254
public async connect(transport: Transport): Promise<void> {
55+
if (this.transportOptions.headers) {
56+
transport.setHeaders(this.transportOptions.headers);
57+
}
5358
this.transport = transport;
5459
await transport.connect();
5560
}

src/client/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import type { Logger } from "../logger/types.js";
22
import type { ClientCapabilities } from "../mcp/20250618/types.js";
3+
import type { TransportOptions } from "../transport/types.js";
34

45
export interface MCPClientOptions {
56
name?: string;
67
version?: string;
78
logger?: Logger;
89
capabilities?: ClientCapabilities;
10+
transportOptions?: TransportOptions;
911
}

src/transport/httpclient.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ export class HTTPClientTransport implements Transport {
4545
}
4646
}
4747

48+
setHeaders(headers: Record<string, string>): void {
49+
this.headers = { ...this.headers, ...headers };
50+
}
51+
4852
async connect(): Promise<void> {
4953
try {
5054
new URL(this.url);
@@ -79,7 +83,10 @@ export class HTTPClientTransport implements Transport {
7983

8084
const response = await this.fetch(this.url, {
8185
method: "POST",
82-
headers: requestHeaders,
86+
headers: {
87+
...this.headers,
88+
...(this.sessionId && { "Mcp-Session-Id": this.sessionId }),
89+
},
8390
body: JSON.stringify(message),
8491
signal: controller.signal,
8592
});

src/transport/httpstreamable.ts

Lines changed: 48 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -48,20 +48,31 @@ export class HTTPStreamableTransport implements Transport {
4848
messageHandler: MessageHandler | null = null;
4949
closeCallback: (() => void) | null = null;
5050

51+
private headers: Record<string, string>;
5152
private options: TransportOptions;
5253
private connected = false;
5354
private enableStreaming = false;
5455
private sessions: Map<string, Session> = new Map();
5556
private streams: Map<string, StreamInfo> = new Map();
5657
private logger: Logger;
5758

59+
setHeaders(headers: Record<string, string>): void {
60+
this.headers = { ...this.headers, ...headers };
61+
}
62+
5863
constructor(options: TransportOptions = {}, streamable = false) {
5964
// Set defaults
6065
this.options = {
6166
timeout: 30 * 60 * 1000, // 30 minutes
6267
enableSessions: false,
6368
...options,
6469
};
70+
71+
this.headers = {
72+
"Content-Type": "application/json",
73+
...options.headers,
74+
};
75+
6576
this.logger = options.logger || createDefaultLogger();
6677

6778
// Start session cleanup timer if sessions are enabled
@@ -189,7 +200,7 @@ export class HTTPStreamableTransport implements Transport {
189200
),
190201
{
191202
status: 503,
192-
headers: { "Content-Type": "application/json" },
203+
headers: this.headers,
193204
}
194205
);
195206
}
@@ -205,7 +216,7 @@ export class HTTPStreamableTransport implements Transport {
205216
),
206217
{
207218
status: 500,
208-
headers: { "Content-Type": "application/json" },
219+
headers: this.headers,
209220
}
210221
);
211222
}
@@ -260,7 +271,7 @@ export class HTTPStreamableTransport implements Transport {
260271
),
261272
{
262273
status: 400,
263-
headers: { "Content-Type": "application/json" },
274+
headers: this.headers,
264275
}
265276
);
266277
}
@@ -295,7 +306,7 @@ export class HTTPStreamableTransport implements Transport {
295306
),
296307
{
297308
status: 406,
298-
headers: { "Content-Type": "application/json" },
309+
headers: this.headers,
299310
}
300311
);
301312
}
@@ -313,7 +324,7 @@ export class HTTPStreamableTransport implements Transport {
313324
),
314325
{
315326
status: 400,
316-
headers: { "Content-Type": "application/json" },
327+
headers: this.headers,
317328
}
318329
);
319330
}
@@ -347,9 +358,10 @@ export class HTTPStreamableTransport implements Transport {
347358
}
348359
return new Response(null, {
349360
status: 202,
350-
headers: currentSession
351-
? { "Mcp-Session-Id": currentSession.id }
352-
: undefined,
361+
headers: {
362+
...this.headers,
363+
...(currentSession && { "Mcp-Session-Id": currentSession.id }),
364+
},
353365
});
354366
}
355367

@@ -373,17 +385,12 @@ export class HTTPStreamableTransport implements Transport {
373385

374386
// Return a direct JSON response
375387
const responseBody = responses.length === 1 ? responses[0] : responses;
376-
const headers: Record<string, string> = {
377-
"Content-Type": "application/json",
378-
};
379-
380-
if (currentSession) {
381-
headers["Mcp-Session-Id"] = currentSession.id;
382-
}
383-
384388
return new Response(JSON.stringify(responseBody), {
385389
status: 200,
386-
headers,
390+
headers: {
391+
...this.headers,
392+
...(currentSession && { "Mcp-Session-Id": currentSession.id }),
393+
},
387394
});
388395
}
389396

@@ -407,18 +414,15 @@ export class HTTPStreamableTransport implements Transport {
407414
}
408415
}
409416

410-
const headers: Record<string, string> = {
411-
"Content-Type": "text/event-stream",
412-
"Cache-Control": "no-cache",
413-
Connection: "keep-alive",
414-
};
415-
416-
if (currentSession) {
417-
headers["Mcp-Session-Id"] = currentSession.id;
418-
}
419-
420417
// Let responses be handled by the message handler and sent to the stream
421-
return new Response(stream.readable, { headers });
418+
return new Response(stream.readable, {
419+
headers: {
420+
"Cache-Control": "no-cache",
421+
Connection: "keep-alive",
422+
...this.headers,
423+
...(currentSession && { "Mcp-Session-Id": currentSession.id }),
424+
},
425+
});
422426
} catch (error) {
423427
return new Response(
424428
JSON.stringify(
@@ -430,7 +434,7 @@ export class HTTPStreamableTransport implements Transport {
430434
),
431435
{
432436
status: 500,
433-
headers: { "Content-Type": "application/json" },
437+
headers: this.headers,
434438
}
435439
);
436440
}
@@ -441,19 +445,19 @@ export class HTTPStreamableTransport implements Transport {
441445
*/
442446
private async handleGetRequest(
443447
request: Request,
444-
session?: Session
448+
currentSession?: Session
445449
): Promise<Response> {
446450
// Check Accept header as required by the spec
447451
const acceptHeader = request.headers.get("Accept") || "";
448452
if (!acceptHeader.includes("text/event-stream")) {
449453
return new Response(null, {
450454
status: 406, // Not Acceptable
451-
headers: { "Content-Type": "application/json" },
455+
headers: this.headers,
452456
});
453457
}
454458

455459
// If no session and sessions are required, reject
456-
if (this.options.enableSessions && !session) {
460+
if (this.options.enableSessions && !currentSession) {
457461
return new Response(
458462
JSON.stringify(
459463
newJSONRPCError({
@@ -464,33 +468,29 @@ export class HTTPStreamableTransport implements Transport {
464468
),
465469
{
466470
status: 400,
467-
headers: { "Content-Type": "application/json" },
471+
headers: this.headers,
468472
}
469473
);
470474
}
471475

472476
// Create a new stream for server-to-client communication
473-
const { stream, streamId } = this.createStream(session);
477+
const { stream, streamId } = this.createStream(currentSession);
474478

475479
// Check for Last-Event-ID for resumability
476480
const lastEventId = request.headers.get("Last-Event-ID");
477-
if (lastEventId && session) {
481+
if (lastEventId && currentSession) {
478482
// Replay messages if needed
479-
await this.replayMessages(session, streamId, lastEventId);
483+
await this.replayMessages(currentSession, streamId, lastEventId);
480484
}
481485

482-
// Return the stream
483-
const headers: Record<string, string> = {
484-
"Content-Type": "text/event-stream",
485-
"Cache-Control": "no-cache",
486-
Connection: "keep-alive",
487-
};
488-
489-
if (session) {
490-
headers["Mcp-Session-Id"] = session.id;
491-
}
492-
493-
return new Response(stream.readable, { headers });
486+
return new Response(stream.readable, {
487+
headers: {
488+
"Cache-Control": "no-cache",
489+
Connection: "keep-alive",
490+
...this.headers,
491+
...(currentSession && { "Mcp-Session-Id": currentSession.id }),
492+
},
493+
});
494494
}
495495

496496
/**

src/transport/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,9 @@ export interface Transport {
6262
* Set a session ID for subsequent requests
6363
*/
6464
setSessionId(sessionId: string | undefined): void;
65+
66+
/**
67+
* Set custom headers for requests
68+
*/
69+
setHeaders(headers: Record<string, string>): void;
6570
}

0 commit comments

Comments
 (0)