feat: add url property to RequestInfo interface (#1353)

Co-authored-by: Matt <77928207+mattzcarey@users.noreply.github.com>
This commit is contained in:
Valentin Beggi 2026-02-15 13:57:53 +01:00 • committed by GitHub
commit 97ab379e45
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 36 additions and 8 deletions

View file

@ -0,0 +1,5 @@
---
'@modelcontextprotocol/sdk': patch
---
Add `url` property to `RequestInfo` interface as a `URL` type, exposing the full request URL to server handlers. The URL is unified across all HTTP transports (SSE and Streamable HTTP) to always provide the complete URL including protocol, host, and path.

View file

@ -1,5 +1,6 @@
import { randomUUID } from 'node:crypto';
import { IncomingMessage, ServerResponse } from 'node:http';
import { TLSSocket } from 'node:tls';
import { Transport } from '../shared/transport.js';
import { JSONRPCMessage, JSONRPCMessageSchema, MessageExtraInfo, RequestInfo } from '../types.js';
import getRawBody from 'raw-body';
@ -149,7 +150,15 @@ export class SSEServerTransport implements Transport {
}
const authInfo: AuthInfo | undefined = req.auth;
const requestInfo: RequestInfo = { headers: req.headers };
const host = req.headers.host;
const protocol = req.socket instanceof TLSSocket ? 'https' : 'http';
const fullUrl = host && req.url ? new URL(req.url, `${protocol}://${host}`) : undefined;
const requestInfo: RequestInfo = {
headers: req.headers,
url: fullUrl
};
let body: string | unknown;
try {

View file

@ -605,9 +605,10 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
return this.createJsonErrorResponse(415, -32000, 'Unsupported Media Type: Content-Type must be application/json');
}
// Build request info from headers
// Build request info from headers and URL
const requestInfo: RequestInfo = {
headers: Object.fromEntries(req.headers.entries())
headers: Object.fromEntries(req.headers.entries()),
url: new URL(req.url)
};
let rawMessage;

View file

@ -2361,6 +2361,10 @@ export interface RequestInfo {
* The headers of the request.
*/
headers: IsomorphicHeaders;
/**
* The full URL of the request.
*/
url?: URL;
}
/**

View file

@ -19,10 +19,15 @@ const createMockResponse = () => {
return res as unknown as Mocked<http.ServerResponse>;
};
const createMockRequest = ({ headers = {}, body }: { headers?: Record<string, string>; body?: string } = {}) => {
const createMockRequest = ({
headers = {},
body,
url = '/messages'
}: { headers?: Record<string, string>; body?: string; url?: string } = {}) => {
const mockReq = {
headers,
body: body ? body : undefined,
url,
auth: {
token: 'test-token'
},
@ -312,7 +317,8 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
'user-agent': 'node',
'accept-encoding': 'gzip, deflate',
'content-length': '124'
}
},
url: `http://127.0.0.1:${serverPort}/?sessionId=${sessionId}`
})
}
]
@ -387,7 +393,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
id: 1
});
const mockReq = createMockRequest({
headers: { 'content-type': 'application/json' },
headers: { host: 'localhost', 'content-type': 'application/json' },
body: validMessage
});
const mockRes = createMockResponse();
@ -416,8 +422,10 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
},
requestInfo: {
headers: {
host: 'localhost',
'content-type': 'application/json'
}
},
url: new URL('http://localhost/messages')
}
}
);

View file

@ -443,7 +443,8 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
'user-agent': expect.any(String),
'accept-encoding': expect.any(String),
'content-length': expect.any(String)
}
},
url: baseUrl.toString()
});
});