Skip to content

Commit bfd4be0

Browse files
Merge branch 'main' into patch-1
2 parents 797078a + fcde488 commit bfd4be0

19 files changed

Lines changed: 316 additions & 88 deletions

.changeset/drop-zod-peer-dep.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@modelcontextprotocol/client': patch
3+
'@modelcontextprotocol/server': patch
4+
---
5+
6+
Drop `zod` from `peerDependencies` (kept as direct dependency)
7+
8+
Since Standard Schema support landed, `zod` is purely an internal runtime dependency used for protocol message parsing. User-facing schemas (`registerTool`, `registerPrompt`) accept any Standard Schema library. `zod` remains in `dependencies` and auto-installs; users no longer
9+
need to install it alongside the SDK.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@modelcontextprotocol/core': patch
3+
---
4+
5+
Consolidate per-request cleanup in `_requestWithSchema` into a single `.finally()` block. This fixes an abort signal listener leak (listeners accumulated when a caller reused one `AbortSignal` across requests) and two cases where `_responseHandlers` entries leaked on send-failure paths.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@modelcontextprotocol/examples-server': patch
3+
---
4+
5+
Example servers now return HTTP 404 (not 400) when a request includes an unknown session ID, so clients can correctly detect they need to start a new session. Requests missing a session ID entirely still return 400.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@modelcontextprotocol/core': patch
3+
---
4+
5+
Ensure `standardSchemaToJsonSchema` emits `type: "object"` at the root, fixing discriminated-union tool/prompt schemas that previously produced `{oneOf: [...]}` without the MCP-required top-level type. Also throws a clear error when given an explicitly non-object schema (e.g. `z.string()`). Fixes #1643.

README.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ This monorepo publishes split packages:
3939
- **`@modelcontextprotocol/server`**: build MCP servers
4040
- **`@modelcontextprotocol/client`**: build MCP clients
4141

42-
Both packages have a **required peer dependency** on `zod` for schema validation. The SDK uses Zod v4.
42+
Tool and prompt schemas use [Standard Schema](https://standardschema.dev/) — bring Zod v4, Valibot, ArkType, or any compatible library.
4343

4444
### Middleware packages (optional)
4545

@@ -56,21 +56,21 @@ They are intentionally thin adapters: they should not introduce new MCP function
5656
### Server
5757

5858
```bash
59-
npm install @modelcontextprotocol/server zod
59+
npm install @modelcontextprotocol/server
6060
# or
61-
bun add @modelcontextprotocol/server zod
61+
bun add @modelcontextprotocol/server
6262
# or
63-
deno add npm:@modelcontextprotocol/server npm:zod
63+
deno add npm:@modelcontextprotocol/server
6464
```
6565

6666
### Client
6767

6868
```bash
69-
npm install @modelcontextprotocol/client zod
69+
npm install @modelcontextprotocol/client
7070
# or
71-
bun add @modelcontextprotocol/client zod
71+
bun add @modelcontextprotocol/client
7272
# or
73-
deno add npm:@modelcontextprotocol/client npm:zod
73+
deno add npm:@modelcontextprotocol/client
7474
```
7575

7676
### Optional middleware packages
@@ -157,7 +157,8 @@ The `docs:multi` script checks out both the `v1.x` and `main` branches via git w
157157

158158
## v1 (legacy) documentation and fixes
159159

160-
If you are using the **v1** generation of the SDK, the **v1 API documentation** is available at [`https://ts.sdk.modelcontextprotocol.io/`](https://ts.sdk.modelcontextprotocol.io/). The v1 source code and any v1-specific fixes live on the long-lived [`v1.x` branch](https://github.com/modelcontextprotocol/typescript-sdk/tree/v1.x). V2 API docs are at [`/v2/`](https://ts.sdk.modelcontextprotocol.io/v2/).
160+
If you are using the **v1** generation of the SDK, the **v1 API documentation** is available at [`https://ts.sdk.modelcontextprotocol.io/`](https://ts.sdk.modelcontextprotocol.io/). The v1 source code and any v1-specific fixes live on the long-lived
161+
[`v1.x` branch](https://github.com/modelcontextprotocol/typescript-sdk/tree/v1.x). V2 API docs are at [`/v2/`](https://ts.sdk.modelcontextprotocol.io/v2/).
161162

162163
## Contributing
163164

examples/server/src/elicitationFormExample.ts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -364,14 +364,17 @@ async function main() {
364364

365365
await transport.handleRequest(req, res, req.body);
366366
return;
367+
} else if (sessionId) {
368+
res.status(404).json({
369+
jsonrpc: '2.0',
370+
error: { code: -32_001, message: 'Session not found' },
371+
id: null
372+
});
373+
return;
367374
} else {
368-
// Invalid request - no session ID or not initialization request
369375
res.status(400).json({
370376
jsonrpc: '2.0',
371-
error: {
372-
code: -32_000,
373-
message: 'Bad Request: No valid session ID provided'
374-
},
377+
error: { code: -32_000, message: 'Bad Request: Session ID required' },
375378
id: null
376379
});
377380
return;
@@ -399,8 +402,12 @@ async function main() {
399402
// Handle GET requests for SSE streams
400403
const mcpGetHandler = async (req: Request, res: Response) => {
401404
const sessionId = req.headers['mcp-session-id'] as string | undefined;
402-
if (!sessionId || !transports[sessionId]) {
403-
res.status(400).send('Invalid or missing session ID');
405+
if (!sessionId) {
406+
res.status(400).send('Missing session ID');
407+
return;
408+
}
409+
if (!transports[sessionId]) {
410+
res.status(404).send('Session not found');
404411
return;
405412
}
406413

@@ -414,8 +421,12 @@ async function main() {
414421
// Handle DELETE requests for session termination
415422
const mcpDeleteHandler = async (req: Request, res: Response) => {
416423
const sessionId = req.headers['mcp-session-id'] as string | undefined;
417-
if (!sessionId || !transports[sessionId]) {
418-
res.status(400).send('Invalid or missing session ID');
424+
if (!sessionId) {
425+
res.status(400).send('Missing session ID');
426+
return;
427+
}
428+
if (!transports[sessionId]) {
429+
res.status(404).send('Session not found');
419430
return;
420431
}
421432

examples/server/src/elicitationUrlExample.ts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -606,14 +606,17 @@ const mcpPostHandler = async (req: Request, res: Response) => {
606606

607607
await transport.handleRequest(req, res, req.body);
608608
return; // Already handled
609+
} else if (sessionId) {
610+
res.status(404).json({
611+
jsonrpc: '2.0',
612+
error: { code: -32_001, message: 'Session not found' },
613+
id: null
614+
});
615+
return;
609616
} else {
610-
// Invalid request - no session ID or not initialization request
611617
res.status(400).json({
612618
jsonrpc: '2.0',
613-
error: {
614-
code: -32_000,
615-
message: 'Bad Request: No valid session ID provided'
616-
},
619+
error: { code: -32_000, message: 'Bad Request: Session ID required' },
617620
id: null
618621
});
619622
return;
@@ -643,8 +646,12 @@ app.post('/mcp', authMiddleware, mcpPostHandler);
643646
// Handle GET requests for SSE streams (using built-in support from StreamableHTTP)
644647
const mcpGetHandler = async (req: Request, res: Response) => {
645648
const sessionId = req.headers['mcp-session-id'] as string | undefined;
646-
if (!sessionId || !transports[sessionId]) {
647-
res.status(400).send('Invalid or missing session ID');
649+
if (!sessionId) {
650+
res.status(400).send('Missing session ID');
651+
return;
652+
}
653+
if (!transports[sessionId]) {
654+
res.status(404).send('Session not found');
648655
return;
649656
}
650657

@@ -682,8 +689,12 @@ app.get('/mcp', authMiddleware, mcpGetHandler);
682689
// Handle DELETE requests for session termination (according to MCP spec)
683690
const mcpDeleteHandler = async (req: Request, res: Response) => {
684691
const sessionId = req.headers['mcp-session-id'] as string | undefined;
685-
if (!sessionId || !transports[sessionId]) {
686-
res.status(400).send('Invalid or missing session ID');
692+
if (!sessionId) {
693+
res.status(400).send('Missing session ID');
694+
return;
695+
}
696+
if (!transports[sessionId]) {
697+
res.status(404).send('Session not found');
687698
return;
688699
}
689700

examples/server/src/jsonResponseStreamableHttp.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -110,14 +110,17 @@ app.post('/mcp', async (req: Request, res: Response) => {
110110
await server.connect(transport);
111111
await transport.handleRequest(req, res, req.body);
112112
return; // Already handled
113+
} else if (sessionId) {
114+
res.status(404).json({
115+
jsonrpc: '2.0',
116+
error: { code: -32_001, message: 'Session not found' },
117+
id: null
118+
});
119+
return;
113120
} else {
114-
// Invalid request - no session ID or not initialization request
115121
res.status(400).json({
116122
jsonrpc: '2.0',
117-
error: {
118-
code: -32_000,
119-
message: 'Bad Request: No valid session ID provided'
120-
},
123+
error: { code: -32_000, message: 'Bad Request: Session ID required' },
121124
id: null
122125
});
123126
return;

examples/server/src/simpleStreamableHttp.ts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -689,14 +689,17 @@ const mcpPostHandler = async (req: Request, res: Response) => {
689689

690690
await transport.handleRequest(req, res, req.body);
691691
return; // Already handled
692+
} else if (sessionId) {
693+
res.status(404).json({
694+
jsonrpc: '2.0',
695+
error: { code: -32_001, message: 'Session not found' },
696+
id: null
697+
});
698+
return;
692699
} else {
693-
// Invalid request - no session ID or not initialization request
694700
res.status(400).json({
695701
jsonrpc: '2.0',
696-
error: {
697-
code: -32_000,
698-
message: 'Bad Request: No valid session ID provided'
699-
},
702+
error: { code: -32_000, message: 'Bad Request: Session ID required' },
700703
id: null
701704
});
702705
return;
@@ -730,8 +733,12 @@ if (useOAuth && authMiddleware) {
730733
// Handle GET requests for SSE streams (using built-in support from StreamableHTTP)
731734
const mcpGetHandler = async (req: Request, res: Response) => {
732735
const sessionId = req.headers['mcp-session-id'] as string | undefined;
733-
if (!sessionId || !transports[sessionId]) {
734-
res.status(400).send('Invalid or missing session ID');
736+
if (!sessionId) {
737+
res.status(400).send('Missing session ID');
738+
return;
739+
}
740+
if (!transports[sessionId]) {
741+
res.status(404).send('Session not found');
735742
return;
736743
}
737744

@@ -761,8 +768,12 @@ if (useOAuth && authMiddleware) {
761768
// Handle DELETE requests for session termination (according to MCP spec)
762769
const mcpDeleteHandler = async (req: Request, res: Response) => {
763770
const sessionId = req.headers['mcp-session-id'] as string | undefined;
764-
if (!sessionId || !transports[sessionId]) {
765-
res.status(400).send('Invalid or missing session ID');
771+
if (!sessionId) {
772+
res.status(400).send('Missing session ID');
773+
return;
774+
}
775+
if (!transports[sessionId]) {
776+
res.status(404).send('Session not found');
766777
return;
767778
}
768779

examples/server/src/simpleTaskInteractive.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -670,10 +670,17 @@ app.post('/mcp', async (req: Request, res: Response) => {
670670
await server.connect(transport);
671671
await transport.handleRequest(req, res, req.body);
672672
return;
673+
} else if (sessionId) {
674+
res.status(404).json({
675+
jsonrpc: '2.0',
676+
error: { code: -32_001, message: 'Session not found' },
677+
id: null
678+
});
679+
return;
673680
} else {
674681
res.status(400).json({
675682
jsonrpc: '2.0',
676-
error: { code: -32_000, message: 'Bad Request: No valid session ID' },
683+
error: { code: -32_000, message: 'Bad Request: Session ID required' },
677684
id: null
678685
});
679686
return;
@@ -695,8 +702,12 @@ app.post('/mcp', async (req: Request, res: Response) => {
695702
// Handle GET requests for SSE streams
696703
app.get('/mcp', async (req: Request, res: Response) => {
697704
const sessionId = req.headers['mcp-session-id'] as string | undefined;
698-
if (!sessionId || !transports[sessionId]) {
699-
res.status(400).send('Invalid or missing session ID');
705+
if (!sessionId) {
706+
res.status(400).send('Missing session ID');
707+
return;
708+
}
709+
if (!transports[sessionId]) {
710+
res.status(404).send('Session not found');
700711
return;
701712
}
702713

@@ -707,8 +718,12 @@ app.get('/mcp', async (req: Request, res: Response) => {
707718
// Handle DELETE requests for session termination
708719
app.delete('/mcp', async (req: Request, res: Response) => {
709720
const sessionId = req.headers['mcp-session-id'] as string | undefined;
710-
if (!sessionId || !transports[sessionId]) {
711-
res.status(400).send('Invalid or missing session ID');
721+
if (!sessionId) {
722+
res.status(400).send('Missing session ID');
723+
return;
724+
}
725+
if (!transports[sessionId]) {
726+
res.status(404).send('Session not found');
712727
return;
713728
}
714729

0 commit comments

Comments
 (0)