Skip to content
Draft
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
4 changes: 3 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,6 @@ jobs:
run: pnpm run build:all

- name: Publish preview packages
run: pnpm dlx pkg-pr-new publish --packageManager=npm --pnpm './packages/server' './packages/client'
run:
pnpm dlx pkg-pr-new publish --packageManager=npm --pnpm './packages/server' './packages/client'
'./packages/server-express' './packages/server-hono'
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ mcpServer.tool('tool-name', { param: z.string() }, async ({ param }, extra) => {

```typescript
// Server
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID() });
const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID() });
await server.connect(transport);

// Client
Expand Down
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# MCP TypeScript SDK

> [!IMPORTANT]
> **This is the `main` branch which contains v2 of the SDK (currently in development, pre-alpha).**
> [!IMPORTANT] **This is the `main` branch which contains v2 of the SDK (currently in development, pre-alpha).**
>
> We anticipate a stable v2 release in Q1 2026. Until then, **v1.x remains the recommended version** for production use. v1.x will continue to receive bug fixes and security updates for at least 6 months after v2 ships to give people time to upgrade.
>
Expand Down
1 change: 1 addition & 0 deletions common/eslint-config/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export default defineConfig(
'@typescript-eslint/consistent-type-imports': ['error', { disallowTypeAnnotations: false }],
'simple-import-sort/imports': 'warn',
'simple-import-sort/exports': 'warn',
'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
'import/no-extraneous-dependencies': [
'error',
{
Expand Down
4 changes: 2 additions & 2 deletions docs/server.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ For more detailed patterns (stateless vs stateful, JSON response mode, CORS, DNS
MCP servers running on localhost are vulnerable to DNS rebinding attacks. Use `createMcpExpressApp()` to create an Express app with DNS rebinding protection enabled by default:

```typescript
import { createMcpExpressApp } from '@modelcontextprotocol/server';
import { createMcpExpressApp } from '@modelcontextprotocol/server-express';

// Protection auto-enabled (default host is 127.0.0.1)
const app = createMcpExpressApp();
Expand All @@ -85,7 +85,7 @@ const app = createMcpExpressApp({ host: '0.0.0.0' });
When binding to `0.0.0.0` / `::`, provide an allow-list of hosts:

```typescript
import { createMcpExpressApp } from '@modelcontextprotocol/server';
import { createMcpExpressApp } from '@modelcontextprotocol/server-express';

const app = createMcpExpressApp({
host: '0.0.0.0',
Expand Down
7 changes: 5 additions & 2 deletions examples/server/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# MCP TypeScript SDK Examples (Server)

This directory contains runnable MCP **server** examples built with `@modelcontextprotocol/server`.
This directory contains runnable MCP **server** examples built with `@modelcontextprotocol/server` plus framework adapters:

- `@modelcontextprotocol/server-express`
- `@modelcontextprotocol/server-hono`

For client examples, see [`../client/README.md`](../client/README.md). For guided docs, see [`../../docs/server.md`](../../docs/server.md).

Expand Down Expand Up @@ -68,7 +71,7 @@ When deploying MCP servers in a horizontally scaled environment (multiple server

### Stateless mode

To enable stateless mode, configure the `StreamableHTTPServerTransport` with:
To enable stateless mode, configure the `NodeStreamableHTTPServerTransport` with:

```typescript
sessionIdGenerator: undefined;
Expand Down
2 changes: 2 additions & 0 deletions examples/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
"hono": "catalog:runtimeServerOnly",
"@modelcontextprotocol/examples-shared": "workspace:^",
"@modelcontextprotocol/server": "workspace:^",
"@modelcontextprotocol/server-express": "workspace:^",
"@modelcontextprotocol/server-hono": "workspace:^",
"cors": "catalog:runtimeServerOnly",
"express": "catalog:runtimeServerOnly",
"zod": "catalog:runtimeShared"
Expand Down
11 changes: 6 additions & 5 deletions examples/server/src/elicitationFormExample.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@

import { randomUUID } from 'node:crypto';

import { createMcpExpressApp, isInitializeRequest, McpServer, StreamableHTTPServerTransport } from '@modelcontextprotocol/server';
import { type Request, type Response } from 'express';
import { isInitializeRequest, McpServer, NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/server';
import { createMcpExpressApp } from '@modelcontextprotocol/server-express';
import type { Request, Response } from 'express';

// Create MCP server - it will automatically use AjvJsonSchemaValidator with sensible defaults
// The validator supports format validation (email, date, etc.) if ajv-formats is installed
Expand Down Expand Up @@ -321,7 +322,7 @@ async function main() {
const app = createMcpExpressApp();

// Map to store transports by session ID
const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};
const transports: { [sessionId: string]: NodeStreamableHTTPServerTransport } = {};

// MCP POST endpoint
const mcpPostHandler = async (req: Request, res: Response) => {
Expand All @@ -331,13 +332,13 @@ async function main() {
}

try {
let transport: StreamableHTTPServerTransport;
let transport: NodeStreamableHTTPServerTransport;
if (sessionId && transports[sessionId]) {
// Reuse existing transport for this session
transport = transports[sessionId];
} else if (!sessionId && isInitializeRequest(req.body)) {
// New initialization request - create new transport
transport = new StreamableHTTPServerTransport({
transport = new NodeStreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
onsessioninitialized: sessionId => {
// Store the transport by session ID when session is initialized
Expand Down
12 changes: 5 additions & 7 deletions examples/server/src/elicitationUrlExample.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,13 @@ import { setupAuthServer } from '@modelcontextprotocol/examples-shared';
import type { CallToolResult, ElicitRequestURLParams, ElicitResult, OAuthMetadata } from '@modelcontextprotocol/server';
import {
checkResourceAllowed,
createMcpExpressApp,
getOAuthProtectedResourceMetadataUrl,
isInitializeRequest,
mcpAuthMetadataRouter,
McpServer,
requireBearerAuth,
StreamableHTTPServerTransport,
NodeStreamableHTTPServerTransport,
UrlElicitationRequiredError
} from '@modelcontextprotocol/server';
import { createMcpExpressApp, mcpAuthMetadataRouter, requireBearerAuth } from '@modelcontextprotocol/server-express';
import cors from 'cors';
import type { Request, Response } from 'express';
import express from 'express';
Expand Down Expand Up @@ -594,7 +592,7 @@ app.post('/confirm-payment', express.urlencoded(), (req: Request, res: Response)
});

// Map to store transports by session ID
const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};
const transports: { [sessionId: string]: NodeStreamableHTTPServerTransport } = {};

// Interface for a function that can send an elicitation request
type ElicitationSender = (params: ElicitRequestURLParams) => Promise<ElicitResult>;
Expand All @@ -613,15 +611,15 @@ const mcpPostHandler = async (req: Request, res: Response) => {
console.debug(`Received MCP POST for session: ${sessionId || 'unknown'}`);

try {
let transport: StreamableHTTPServerTransport;
let transport: NodeStreamableHTTPServerTransport;
if (sessionId && transports[sessionId]) {
// Reuse existing transport
transport = transports[sessionId];
} else if (!sessionId && isInitializeRequest(req.body)) {
const server = getServer();
// New initialization request
const eventStore = new InMemoryEventStore();
transport = new StreamableHTTPServerTransport({
transport = new NodeStreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
eventStore, // Enable resumability
onsessioninitialized: sessionId => {
Expand Down
3 changes: 2 additions & 1 deletion examples/server/src/honoWebStandardStreamableHttp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import { serve } from '@hono/node-server';
import type { CallToolResult } from '@modelcontextprotocol/server';
import { McpServer, WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/server';
import { mcpStreamableHttpHandler } from '@modelcontextprotocol/server-hono';
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import * as z from 'zod/v4';
Expand Down Expand Up @@ -56,7 +57,7 @@ app.use(
app.get('/health', c => c.json({ status: 'ok' }));

// MCP endpoint
app.all('/mcp', c => transport.handleRequest(c.req.raw));
app.all('/mcp', mcpStreamableHttpHandler(transport));

// Start the server
const PORT = process.env.MCP_PORT ? parseInt(process.env.MCP_PORT, 10) : 3000;
Expand Down
9 changes: 5 additions & 4 deletions examples/server/src/jsonResponseStreamableHttp.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { randomUUID } from 'node:crypto';

import type { CallToolResult } from '@modelcontextprotocol/server';
import { createMcpExpressApp, isInitializeRequest, McpServer, StreamableHTTPServerTransport } from '@modelcontextprotocol/server';
import { isInitializeRequest, McpServer, NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/server';
import { createMcpExpressApp } from '@modelcontextprotocol/server-express';
import type { Request, Response } from 'express';
import * as z from 'zod/v4';

Expand Down Expand Up @@ -96,21 +97,21 @@ const getServer = () => {
const app = createMcpExpressApp();

// Map to store transports by session ID
const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};
const transports: { [sessionId: string]: NodeStreamableHTTPServerTransport } = {};

app.post('/mcp', async (req: Request, res: Response) => {
console.log('Received MCP request:', req.body);
try {
// Check for existing session ID
const sessionId = req.headers['mcp-session-id'] as string | undefined;
let transport: StreamableHTTPServerTransport;
let transport: NodeStreamableHTTPServerTransport;

if (sessionId && transports[sessionId]) {
// Reuse existing transport
transport = transports[sessionId];
} else if (!sessionId && isInitializeRequest(req.body)) {
// New initialization request - use JSON response mode
transport = new StreamableHTTPServerTransport({
transport = new NodeStreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
enableJsonResponse: true, // Enable JSON response mode
onsessioninitialized: sessionId => {
Expand Down
3 changes: 2 additions & 1 deletion examples/server/src/simpleSseServer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { CallToolResult } from '@modelcontextprotocol/server';
import { createMcpExpressApp, McpServer, SSEServerTransport } from '@modelcontextprotocol/server';
import { McpServer, SSEServerTransport } from '@modelcontextprotocol/server';
import { createMcpExpressApp } from '@modelcontextprotocol/server-express';
import type { Request, Response } from 'express';
import * as z from 'zod/v4';

Expand Down
5 changes: 3 additions & 2 deletions examples/server/src/simpleStatelessStreamableHttp.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { CallToolResult, GetPromptResult, ReadResourceResult } from '@modelcontextprotocol/server';
import { createMcpExpressApp, McpServer, StreamableHTTPServerTransport } from '@modelcontextprotocol/server';
import { McpServer, NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/server';
import { createMcpExpressApp } from '@modelcontextprotocol/server-express';
import type { Request, Response } from 'express';
import * as z from 'zod/v4';

Expand Down Expand Up @@ -103,7 +104,7 @@ const app = createMcpExpressApp();
app.post('/mcp', async (req: Request, res: Response) => {
const server = getServer();
try {
const transport: StreamableHTTPServerTransport = new StreamableHTTPServerTransport({
const transport: NodeStreamableHTTPServerTransport = new NodeStreamableHTTPServerTransport({
sessionIdGenerator: undefined
});
await server.connect(transport);
Expand Down
12 changes: 5 additions & 7 deletions examples/server/src/simpleStreamableHttp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,15 @@ import type {
} from '@modelcontextprotocol/server';
import {
checkResourceAllowed,
createMcpExpressApp,
ElicitResultSchema,
getOAuthProtectedResourceMetadataUrl,
InMemoryTaskMessageQueue,
InMemoryTaskStore,
isInitializeRequest,
mcpAuthMetadataRouter,
McpServer,
requireBearerAuth,
StreamableHTTPServerTransport
NodeStreamableHTTPServerTransport
} from '@modelcontextprotocol/server';
import { createMcpExpressApp, mcpAuthMetadataRouter, requireBearerAuth } from '@modelcontextprotocol/server-express';
import type { Request, Response } from 'express';
import * as z from 'zod/v4';

Expand Down Expand Up @@ -590,7 +588,7 @@ if (useOAuth) {
}

// Map to store transports by session ID
const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};
const transports: { [sessionId: string]: NodeStreamableHTTPServerTransport } = {};

// MCP POST endpoint with optional auth
const mcpPostHandler = async (req: Request, res: Response) => {
Expand All @@ -605,14 +603,14 @@ const mcpPostHandler = async (req: Request, res: Response) => {
console.log('Authenticated user:', req.auth);
}
try {
let transport: StreamableHTTPServerTransport;
let transport: NodeStreamableHTTPServerTransport;
if (sessionId && transports[sessionId]) {
// Reuse existing transport
transport = transports[sessionId];
} else if (!sessionId && isInitializeRequest(req.body)) {
// New initialization request
const eventStore = new InMemoryEventStore();
transport = new StreamableHTTPServerTransport({
transport = new NodeStreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
eventStore, // Enable resumability
onsessioninitialized: sessionId => {
Expand Down
12 changes: 6 additions & 6 deletions examples/server/src/simpleTaskInteractive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,16 @@ import type {
} from '@modelcontextprotocol/server';
import {
CallToolRequestSchema,
createMcpExpressApp,
GetTaskPayloadRequestSchema,
GetTaskRequestSchema,
InMemoryTaskStore,
isTerminal,
ListToolsRequestSchema,
NodeStreamableHTTPServerTransport,
RELATED_TASK_META_KEY,
Server,
StreamableHTTPServerTransport
Server
} from '@modelcontextprotocol/server';
import { createMcpExpressApp } from '@modelcontextprotocol/server-express';
import type { Request, Response } from 'express';

// ============================================================================
Expand Down Expand Up @@ -642,7 +642,7 @@ const createServer = (): Server => {
const app = createMcpExpressApp();

// Map to store transports by session ID
const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};
const transports: { [sessionId: string]: NodeStreamableHTTPServerTransport } = {};

// Helper to check if request is initialize
const isInitializeRequest = (body: unknown): boolean => {
Expand All @@ -654,12 +654,12 @@ app.post('/mcp', async (req: Request, res: Response) => {
const sessionId = req.headers['mcp-session-id'] as string | undefined;

try {
let transport: StreamableHTTPServerTransport;
let transport: NodeStreamableHTTPServerTransport;

if (sessionId && transports[sessionId]) {
transport = transports[sessionId];
} else if (!sessionId && isInitializeRequest(req.body)) {
transport = new StreamableHTTPServerTransport({
transport = new NodeStreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
onsessioninitialized: sid => {
console.log(`Session initialized: ${sid}`);
Expand Down
21 changes: 8 additions & 13 deletions examples/server/src/sseAndStreamableHttpCompatibleServer.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import { randomUUID } from 'node:crypto';

import type { CallToolResult } from '@modelcontextprotocol/server';
import {
createMcpExpressApp,
isInitializeRequest,
McpServer,
SSEServerTransport,
StreamableHTTPServerTransport
} from '@modelcontextprotocol/server';
import { isInitializeRequest, McpServer, NodeStreamableHTTPServerTransport, SSEServerTransport } from '@modelcontextprotocol/server';
import { createMcpExpressApp } from '@modelcontextprotocol/server-express';
import type { Request, Response } from 'express';
import * as z from 'zod/v4';

Expand Down Expand Up @@ -81,7 +76,7 @@ const getServer = () => {
const app = createMcpExpressApp();

// Store transports by session ID
const transports: Record<string, StreamableHTTPServerTransport | SSEServerTransport> = {};
const transports: Record<string, NodeStreamableHTTPServerTransport | SSEServerTransport> = {};

//=============================================================================
// STREAMABLE HTTP TRANSPORT (PROTOCOL VERSION 2025-11-25)
Expand All @@ -94,16 +89,16 @@ app.all('/mcp', async (req: Request, res: Response) => {
try {
// Check for existing session ID
const sessionId = req.headers['mcp-session-id'] as string | undefined;
let transport: StreamableHTTPServerTransport;
let transport: NodeStreamableHTTPServerTransport;

if (sessionId && transports[sessionId]) {
// Check if the transport is of the correct type
const existingTransport = transports[sessionId];
if (existingTransport instanceof StreamableHTTPServerTransport) {
if (existingTransport instanceof NodeStreamableHTTPServerTransport) {
// Reuse existing transport
transport = existingTransport;
} else {
// Transport exists but is not a StreamableHTTPServerTransport (could be SSEServerTransport)
// Transport exists but is not a NodeStreamableHTTPServerTransport (could be SSEServerTransport)
res.status(400).json({
jsonrpc: '2.0',
error: {
Expand All @@ -116,7 +111,7 @@ app.all('/mcp', async (req: Request, res: Response) => {
}
} else if (!sessionId && req.method === 'POST' && isInitializeRequest(req.body)) {
const eventStore = new InMemoryEventStore();
transport = new StreamableHTTPServerTransport({
transport = new NodeStreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
eventStore, // Enable resumability
onsessioninitialized: sessionId => {
Expand Down Expand Up @@ -191,7 +186,7 @@ app.post('/messages', async (req: Request, res: Response) => {
// Reuse existing transport
transport = existingTransport;
} else {
// Transport exists but is not a SSEServerTransport (could be StreamableHTTPServerTransport)
// Transport exists but is not a SSEServerTransport (could be NodeStreamableHTTPServerTransport)
res.status(400).json({
jsonrpc: '2.0',
error: {
Expand Down
Loading
Loading