remove custom headers

This commit is contained in:
ihrpr 2025-04-07 16:03:31 +01:00
commit af64a0e18c
No known key found for this signature in database
3 changed files with 11 additions and 160 deletions

View file

@ -379,7 +379,7 @@ describe("tool()", () => {
expect(receivedSessionId).toBe("test-session-123");
});
test("should provide sendNotification withing tool call", async () => {
test("should provide sendNotification within tool call", async () => {
const mcpServer = new McpServer(
{
name: "test server",
@ -401,12 +401,10 @@ describe("tool()", () => {
);
let receivedLogMessage: string | undefined;
const loggingMessage = "hello here is log message 1"
const loggingMessage = "hello here is log message 1";
client.setNotificationHandler(LoggingMessageNotificationSchema, (notification) => {
receivedLogMessage = notification.params.data as string;
});
mcpServer.tool("test-tool", async ({ sendNotification }) => {
@ -422,15 +420,10 @@ describe("tool()", () => {
});
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
// Set a test sessionId on the server transport
serverTransport.sessionId = "test-session-123";
await Promise.all([
client.connect(clientTransport),
mcpServer.server.connect(serverTransport),
]);
await client.request(
{
method: "tools/call",

View file

@ -99,7 +99,7 @@ describe("StreamableHTTPServerTransport", () => {
await transport.handleRequest(req, mockResponse);
expect(mockResponse.writeHead).toHaveBeenCalledWith(404, {});
expect(mockResponse.writeHead).toHaveBeenCalledWith(404);
// check if the error response is a valid JSON-RPC error format
expect(mockResponse.end).toHaveBeenCalledWith(expect.stringContaining('"jsonrpc":"2.0"'));
expect(mockResponse.end).toHaveBeenCalledWith(expect.stringContaining('"error"'));
@ -116,7 +116,7 @@ describe("StreamableHTTPServerTransport", () => {
await transport.handleRequest(req, mockResponse);
expect(mockResponse.writeHead).toHaveBeenCalledWith(400, {});
expect(mockResponse.writeHead).toHaveBeenCalledWith(400);
expect(mockResponse.end).toHaveBeenCalledWith(expect.stringContaining('"jsonrpc":"2.0"'));
expect(mockResponse.end).toHaveBeenCalledWith(expect.stringContaining('"message":"Bad Request: Mcp-Session-Id header is required"'));
});
@ -842,135 +842,4 @@ describe("StreamableHTTPServerTransport", () => {
expect(onMessageMock).not.toHaveBeenCalledWith(requestBodyMessage);
});
});
describe("Custom Headers", () => {
const customHeaders = {
"X-Custom-Header": "custom-value",
"X-API-Version": "1.0",
"Access-Control-Allow-Origin": "*"
};
let transportWithHeaders: StreamableHTTPServerTransport;
let mockResponse: jest.Mocked<ServerResponse>;
beforeEach(() => {
transportWithHeaders = new StreamableHTTPServerTransport({ sessionId: randomUUID(), customHeaders });
mockResponse = createMockResponse();
});
it("should include custom headers in SSE response", async () => {
const req = createMockRequest({
method: "POST",
headers: {
"content-type": "application/json",
accept: "application/json, text/event-stream",
"mcp-session-id": transportWithHeaders.sessionId
},
body: JSON.stringify({
jsonrpc: "2.0",
method: "test",
params: {},
id: "test-headers-id"
})
});
await transportWithHeaders.handleRequest(req, mockResponse);
expect(mockResponse.writeHead).toHaveBeenCalledWith(
200,
expect.objectContaining({
...customHeaders,
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"mcp-session-id": transportWithHeaders.sessionId
})
);
});
it("should include custom headers in error responses", async () => {
const req = createMockRequest({
method: "GET",
headers: {
accept: "application/json, text/event-stream",
"mcp-session-id": "invalid-session-id"
},
});
await transportWithHeaders.handleRequest(req, mockResponse);
expect(mockResponse.writeHead).toHaveBeenCalledWith(
404,
expect.objectContaining(customHeaders)
);
});
it("should not override essential headers with custom headers", async () => {
const transportWithConflictingHeaders = new StreamableHTTPServerTransport({
sessionId: randomUUID(),
customHeaders: {
"Content-Type": "text/plain",
"X-Custom-Header": "custom-value"
}
});
const req = createMockRequest({
method: "POST",
headers: {
"content-type": "application/json",
accept: "application/json, text/event-stream",
"mcp-session-id": transportWithConflictingHeaders.sessionId
},
body: JSON.stringify({
jsonrpc: "2.0",
method: "test",
params: {},
id: "test-conflict-id"
})
});
await transportWithConflictingHeaders.handleRequest(req, mockResponse);
expect(mockResponse.writeHead).toHaveBeenCalledWith(
200,
expect.objectContaining({
"Content-Type": "text/event-stream",
"X-Custom-Header": "custom-value"
})
);
});
it("should work with empty custom headers", async () => {
const transportWithoutHeaders = new StreamableHTTPServerTransport({
sessionId: randomUUID(),
});
const req = createMockRequest({
method: "POST",
headers: {
"content-type": "application/json",
accept: "application/json, text/event-stream",
"mcp-session-id": transportWithoutHeaders.sessionId
},
body: JSON.stringify({
jsonrpc: "2.0",
method: "test",
params: {},
id: "test-empty-headers-id"
})
});
await transportWithoutHeaders.handleRequest(req, mockResponse);
expect(mockResponse.writeHead).toHaveBeenCalledWith(
200,
expect.objectContaining({
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"mcp-session-id": transportWithoutHeaders.sessionId
})
);
});
});
});

View file

@ -13,15 +13,11 @@ export interface StreamableHTTPServerTransportOptions {
/**
* The session ID SHOULD be globally unique and cryptographically secure (e.g., a securely generated UUID, a JWT, or a cryptographic hash)
*
* When sessionId is not set, the transport will be in stateless mode.
* When there is no sessionId, the transport will not perform session management.
*/
sessionId: string | undefined;
/**
* Custom headers to be included in all responses
* These headers will be added to both SSE and regular HTTP responses
*/
customHeaders?: Record<string, string>;
}
@ -63,7 +59,6 @@ export class StreamableHTTPServerTransport implements Transport {
// when sessionId is not set (undefined), it means the transport is in stateless mode
private _sessionId: string | undefined;
private _started: boolean = false;
private _customHeaders: Record<string, string>;
private _sseResponseMapping: Map<RequestId, ServerResponse> = new Map();
onclose?: () => void;
@ -72,7 +67,6 @@ export class StreamableHTTPServerTransport implements Transport {
constructor(options: StreamableHTTPServerTransportOptions) {
this._sessionId = options.sessionId;
this._customHeaders = options.customHeaders || {};
}
/**
@ -97,7 +91,7 @@ export class StreamableHTTPServerTransport implements Transport {
} else if (req.method === "DELETE") {
await this.handleDeleteRequest(req, res);
} else {
res.writeHead(405, this._customHeaders).end(JSON.stringify({
res.writeHead(405).end(JSON.stringify({
jsonrpc: "2.0",
error: {
code: -32000,
@ -120,7 +114,6 @@ export class StreamableHTTPServerTransport implements Transport {
}
res.writeHead(405, {
...this._customHeaders,
"Allow": "POST, DELETE"
}).end(JSON.stringify({
jsonrpc: "2.0",
@ -193,9 +186,7 @@ export class StreamableHTTPServerTransport implements Transport {
msg => 'method' in msg && msg.method === 'initialize'
);
if (isInitializationRequest) {
const headers: Record<string, string> = {
...this._customHeaders
};
const headers: Record<string, string> = {};
if (this._sessionId !== undefined) {
headers["mcp-session-id"] = this._sessionId;
@ -232,14 +223,12 @@ export class StreamableHTTPServerTransport implements Transport {
}
} else if (hasRequests) {
const headers: Record<string, string> = {
...this._customHeaders,
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
};
// For initialization requests, always include the session ID if we have one
// even if we're in stateless mode
// After initialization, always include the session ID if we have one
if (this._sessionId !== undefined) {
headers["mcp-session-id"] = this._sessionId;
}
@ -296,7 +285,7 @@ export class StreamableHTTPServerTransport implements Transport {
if (!sessionId) {
// Non-initialization requests without a session ID should return 400 Bad Request
res.writeHead(400, this._customHeaders).end(JSON.stringify({
res.writeHead(400).end(JSON.stringify({
jsonrpc: "2.0",
error: {
code: -32000,
@ -307,7 +296,7 @@ export class StreamableHTTPServerTransport implements Transport {
return false;
} else if ((Array.isArray(sessionId) ? sessionId[0] : sessionId) !== this._sessionId) {
// Reject requests with invalid session ID with 404 Not Found
res.writeHead(404, this._customHeaders).end(JSON.stringify({
res.writeHead(404).end(JSON.stringify({
jsonrpc: "2.0",
error: {
code: -32001,