Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions packages/core/src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2384,19 +2384,14 @@ type Flatten<T> = T extends Primitive

type Infer<Schema extends z.ZodTypeAny> = Flatten<z.infer<Schema>>;

/**
* Headers that are compatible with both Node.js and the browser.
*/
export type IsomorphicHeaders = Record<string, string | string[] | undefined>;

/**
* Information about the incoming request.
*/
export interface RequestInfo {
/**
* The headers of the request.
*/
headers: IsomorphicHeaders;
headers: Headers;
}

/**
Expand Down
33 changes: 33 additions & 0 deletions packages/server/src/server/helper/headers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { IncomingHttpHeaders } from 'node:http';

/**
* Converts a Node.js IncomingHttpHeaders object to a Web Headers object.
* @param h - The Node.js IncomingHttpHeaders object.
* @returns The Web Headers object.
*/
export function nodeHeadersToWebHeaders(h: IncomingHttpHeaders): Headers {
const out = new Headers();

for (const [name, value] of Object.entries(h)) {
if (value === undefined) continue;

// Node may surface set-cookie as string[]
if (name.toLowerCase() === 'set-cookie') {
if (Array.isArray(value)) {
for (const v of value) out.append('set-cookie', v);
} else {
out.append('set-cookie', value);
}
continue;
}

if (Array.isArray(value)) {
// Most headers can be joined; append preserves multiple values too.
for (const v of value) out.append(name, v);
} else {
out.set(name, value);
}
}

return out;
}
2 changes: 1 addition & 1 deletion packages/server/src/server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export class Server<
if (this._capabilities.logging) {
this.setRequestHandler(SetLevelRequestSchema, async (request, extra) => {
const transportSessionId: string | undefined =
extra.sessionId || (extra.requestInfo?.headers['mcp-session-id'] as string) || undefined;
extra.sessionId || (extra.requestInfo?.headers.get('mcp-session-id') as string) || undefined;
const { level } = request.params;
const parseResult = LoggingLevelSchema.safeParse(level);
if (parseResult.success) {
Expand Down
4 changes: 3 additions & 1 deletion packages/server/src/server/sse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { JSONRPCMessageSchema } from '@modelcontextprotocol/core';
import contentType from 'content-type';
import getRawBody from 'raw-body';

import { nodeHeadersToWebHeaders } from './helper/headers.js';

const MAXIMUM_MESSAGE_SIZE = '4mb';

/**
Expand Down Expand Up @@ -149,7 +151,7 @@ export class SSEServerTransport implements Transport {
}

const authInfo: AuthInfo | undefined = req.auth;
const requestInfo: RequestInfo = { headers: req.headers };
const requestInfo: RequestInfo = { headers: nodeHeadersToWebHeaders(req.headers) };

let body: string | unknown;
try {
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/server/webStandardStreamableHttp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,7 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {

// Build request info from headers
const requestInfo: RequestInfo = {
headers: Object.fromEntries(req.headers.entries())
headers: req.headers
};

let rawMessage;
Expand Down
8 changes: 4 additions & 4 deletions packages/server/test/server/sse.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
{
type: 'text',
text: JSON.stringify({
headers: {
headers: new Headers({
host: `127.0.0.1:${serverPort}`,
connection: 'keep-alive',
'content-type': 'application/json',
Expand All @@ -313,7 +313,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
'user-agent': 'node',
'accept-encoding': 'gzip, deflate',
'content-length': '124'
}
})
})
}
]
Expand Down Expand Up @@ -416,9 +416,9 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
token: 'test-token'
},
requestInfo: {
headers: {
headers: new Headers({
'content-type': 'application/json'
}
})
}
}
);
Expand Down
4 changes: 2 additions & 2 deletions packages/server/test/server/streamableHttp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {

const requestInfo = JSON.parse(eventData.result.content[1].text);
expect(requestInfo).toMatchObject({
headers: {
headers: new Headers({
'content-type': 'application/json',
accept: 'application/json, text/event-stream',
connection: 'keep-alive',
Expand All @@ -454,7 +454,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
'user-agent': expect.any(String),
'accept-encoding': expect.any(String),
'content-length': expect.any(String)
}
})
});
});

Expand Down
2 changes: 1 addition & 1 deletion test/integration/test/taskResumability.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { randomUUID } from 'node:crypto';
import { createServer, type Server } from 'node:http';

import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
import type { EventStore, JSONRPCMessage } from '@modelcontextprotocol/server';
import {
CallToolResultSchema,
LoggingMessageNotificationSchema,
McpServer,
StreamableHTTPServerTransport
} from '@modelcontextprotocol/server';
import type { EventStore, JSONRPCMessage } from '@modelcontextprotocol/server';
import type { ZodMatrixEntry } from '@modelcontextprotocol/test-helpers';
import { listenOnRandomPort, zodTestMatrix } from '@modelcontextprotocol/test-helpers';

Expand Down
Loading