inital upload
This commit is contained in:
840
node_modules/@modelcontextprotocol/sdk/README.md
generated
vendored
Normal file
840
node_modules/@modelcontextprotocol/sdk/README.md
generated
vendored
Normal file
@@ -0,0 +1,840 @@
|
||||
# MCP TypeScript SDK  
|
||||
|
||||
## Table of Contents
|
||||
- [Overview](#overview)
|
||||
- [Installation](#installation)
|
||||
- [Quickstart](#quickstart)
|
||||
- [What is MCP?](#what-is-mcp)
|
||||
- [Core Concepts](#core-concepts)
|
||||
- [Server](#server)
|
||||
- [Resources](#resources)
|
||||
- [Tools](#tools)
|
||||
- [Prompts](#prompts)
|
||||
- [Running Your Server](#running-your-server)
|
||||
- [stdio](#stdio)
|
||||
- [Streamable HTTP](#streamable-http)
|
||||
- [Testing and Debugging](#testing-and-debugging)
|
||||
- [Examples](#examples)
|
||||
- [Echo Server](#echo-server)
|
||||
- [SQLite Explorer](#sqlite-explorer)
|
||||
- [Advanced Usage](#advanced-usage)
|
||||
- [Low-Level Server](#low-level-server)
|
||||
- [Writing MCP Clients](#writing-mcp-clients)
|
||||
- [Server Capabilities](#server-capabilities)
|
||||
- [Proxy OAuth Server](#proxy-authorization-requests-upstream)
|
||||
- [Backwards Compatibility](#backwards-compatibility)
|
||||
|
||||
## Overview
|
||||
|
||||
The Model Context Protocol allows applications to provide context for LLMs in a standardized way, separating the concerns of providing context from the actual LLM interaction. This TypeScript SDK implements the full MCP specification, making it easy to:
|
||||
|
||||
- Build MCP clients that can connect to any MCP server
|
||||
- Create MCP servers that expose resources, prompts and tools
|
||||
- Use standard transports like stdio and Streamable HTTP
|
||||
- Handle all MCP protocol messages and lifecycle events
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @modelcontextprotocol/sdk
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
Let's create a simple MCP server that exposes a calculator tool and some data:
|
||||
|
||||
```typescript
|
||||
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import { z } from "zod";
|
||||
|
||||
// Create an MCP server
|
||||
const server = new McpServer({
|
||||
name: "Demo",
|
||||
version: "1.0.0"
|
||||
});
|
||||
|
||||
// Add an addition tool
|
||||
server.tool("add",
|
||||
{ a: z.number(), b: z.number() },
|
||||
async ({ a, b }) => ({
|
||||
content: [{ type: "text", text: String(a + b) }]
|
||||
})
|
||||
);
|
||||
|
||||
// Add a dynamic greeting resource
|
||||
server.resource(
|
||||
"greeting",
|
||||
new ResourceTemplate("greeting://{name}", { list: undefined }),
|
||||
async (uri, { name }) => ({
|
||||
contents: [{
|
||||
uri: uri.href,
|
||||
text: `Hello, ${name}!`
|
||||
}]
|
||||
})
|
||||
);
|
||||
|
||||
// Start receiving messages on stdin and sending messages on stdout
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
```
|
||||
|
||||
## What is MCP?
|
||||
|
||||
The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) lets you build servers that expose data and functionality to LLM applications in a secure, standardized way. Think of it like a web API, but specifically designed for LLM interactions. MCP servers can:
|
||||
|
||||
- Expose data through **Resources** (think of these sort of like GET endpoints; they are used to load information into the LLM's context)
|
||||
- Provide functionality through **Tools** (sort of like POST endpoints; they are used to execute code or otherwise produce a side effect)
|
||||
- Define interaction patterns through **Prompts** (reusable templates for LLM interactions)
|
||||
- And more!
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Server
|
||||
|
||||
The McpServer is your core interface to the MCP protocol. It handles connection management, protocol compliance, and message routing:
|
||||
|
||||
```typescript
|
||||
const server = new McpServer({
|
||||
name: "My App",
|
||||
version: "1.0.0"
|
||||
});
|
||||
```
|
||||
|
||||
### Resources
|
||||
|
||||
Resources are how you expose data to LLMs. They're similar to GET endpoints in a REST API - they provide data but shouldn't perform significant computation or have side effects:
|
||||
|
||||
```typescript
|
||||
// Static resource
|
||||
server.resource(
|
||||
"config",
|
||||
"config://app",
|
||||
async (uri) => ({
|
||||
contents: [{
|
||||
uri: uri.href,
|
||||
text: "App configuration here"
|
||||
}]
|
||||
})
|
||||
);
|
||||
|
||||
// Dynamic resource with parameters
|
||||
server.resource(
|
||||
"user-profile",
|
||||
new ResourceTemplate("users://{userId}/profile", { list: undefined }),
|
||||
async (uri, { userId }) => ({
|
||||
contents: [{
|
||||
uri: uri.href,
|
||||
text: `Profile data for user ${userId}`
|
||||
}]
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
### Tools
|
||||
|
||||
Tools let LLMs take actions through your server. Unlike resources, tools are expected to perform computation and have side effects:
|
||||
|
||||
```typescript
|
||||
// Simple tool with parameters
|
||||
server.tool(
|
||||
"calculate-bmi",
|
||||
{
|
||||
weightKg: z.number(),
|
||||
heightM: z.number()
|
||||
},
|
||||
async ({ weightKg, heightM }) => ({
|
||||
content: [{
|
||||
type: "text",
|
||||
text: String(weightKg / (heightM * heightM))
|
||||
}]
|
||||
})
|
||||
);
|
||||
|
||||
// Async tool with external API call
|
||||
server.tool(
|
||||
"fetch-weather",
|
||||
{ city: z.string() },
|
||||
async ({ city }) => {
|
||||
const response = await fetch(`https://api.weather.com/${city}`);
|
||||
const data = await response.text();
|
||||
return {
|
||||
content: [{ type: "text", text: data }]
|
||||
};
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
### Prompts
|
||||
|
||||
Prompts are reusable templates that help LLMs interact with your server effectively:
|
||||
|
||||
```typescript
|
||||
server.prompt(
|
||||
"review-code",
|
||||
{ code: z.string() },
|
||||
({ code }) => ({
|
||||
messages: [{
|
||||
role: "user",
|
||||
content: {
|
||||
type: "text",
|
||||
text: `Please review this code:\n\n${code}`
|
||||
}
|
||||
}]
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
## Running Your Server
|
||||
|
||||
MCP servers in TypeScript need to be connected to a transport to communicate with clients. How you start the server depends on the choice of transport:
|
||||
|
||||
### stdio
|
||||
|
||||
For command-line tools and direct integrations:
|
||||
|
||||
```typescript
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
|
||||
const server = new McpServer({
|
||||
name: "example-server",
|
||||
version: "1.0.0"
|
||||
});
|
||||
|
||||
// ... set up server resources, tools, and prompts ...
|
||||
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
```
|
||||
|
||||
### Streamable HTTP
|
||||
|
||||
For remote servers, set up a Streamable HTTP transport that handles both client requests and server-to-client notifications.
|
||||
|
||||
#### With Session Management
|
||||
|
||||
In some cases, servers need to be stateful. This is achieved by [session management](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#session-management).
|
||||
|
||||
```typescript
|
||||
import express from "express";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
||||
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js"
|
||||
|
||||
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
// Map to store transports by session ID
|
||||
const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};
|
||||
|
||||
// Handle POST requests for client-to-server communication
|
||||
app.post('/mcp', async (req, res) => {
|
||||
// Check for existing session ID
|
||||
const sessionId = req.headers['mcp-session-id'] as string | undefined;
|
||||
let transport: StreamableHTTPServerTransport;
|
||||
|
||||
if (sessionId && transports[sessionId]) {
|
||||
// Reuse existing transport
|
||||
transport = transports[sessionId];
|
||||
} else if (!sessionId && isInitializeRequest(req.body)) {
|
||||
// New initialization request
|
||||
transport = new StreamableHTTPServerTransport({
|
||||
sessionIdGenerator: () => randomUUID(),
|
||||
onsessioninitialized: (sessionId) => {
|
||||
// Store the transport by session ID
|
||||
transports[sessionId] = transport;
|
||||
}
|
||||
});
|
||||
|
||||
// Clean up transport when closed
|
||||
transport.onclose = () => {
|
||||
if (transport.sessionId) {
|
||||
delete transports[transport.sessionId];
|
||||
}
|
||||
};
|
||||
const server = new McpServer({
|
||||
name: "example-server",
|
||||
version: "1.0.0"
|
||||
});
|
||||
|
||||
// ... set up server resources, tools, and prompts ...
|
||||
|
||||
// Connect to the MCP server
|
||||
await server.connect(transport);
|
||||
} else {
|
||||
// Invalid request
|
||||
res.status(400).json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32000,
|
||||
message: 'Bad Request: No valid session ID provided',
|
||||
},
|
||||
id: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle the request
|
||||
await transport.handleRequest(req, res, req.body);
|
||||
});
|
||||
|
||||
// Reusable handler for GET and DELETE requests
|
||||
const handleSessionRequest = async (req: express.Request, res: express.Response) => {
|
||||
const sessionId = req.headers['mcp-session-id'] as string | undefined;
|
||||
if (!sessionId || !transports[sessionId]) {
|
||||
res.status(400).send('Invalid or missing session ID');
|
||||
return;
|
||||
}
|
||||
|
||||
const transport = transports[sessionId];
|
||||
await transport.handleRequest(req, res);
|
||||
};
|
||||
|
||||
// Handle GET requests for server-to-client notifications via SSE
|
||||
app.get('/mcp', handleSessionRequest);
|
||||
|
||||
// Handle DELETE requests for session termination
|
||||
app.delete('/mcp', handleSessionRequest);
|
||||
|
||||
app.listen(3000);
|
||||
```
|
||||
|
||||
#### Without Session Management (Stateless)
|
||||
|
||||
For simpler use cases where session management isn't needed:
|
||||
|
||||
```typescript
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
app.post('/mcp', async (req: Request, res: Response) => {
|
||||
// In stateless mode, create a new instance of transport and server for each request
|
||||
// to ensure complete isolation. A single instance would cause request ID collisions
|
||||
// when multiple clients connect concurrently.
|
||||
|
||||
try {
|
||||
const server = getServer();
|
||||
const transport: StreamableHTTPServerTransport = new StreamableHTTPServerTransport({
|
||||
sessionIdGenerator: undefined,
|
||||
});
|
||||
res.on('close', () => {
|
||||
console.log('Request closed');
|
||||
transport.close();
|
||||
server.close();
|
||||
});
|
||||
await server.connect(transport);
|
||||
await transport.handleRequest(req, res, req.body);
|
||||
} catch (error) {
|
||||
console.error('Error handling MCP request:', error);
|
||||
if (!res.headersSent) {
|
||||
res.status(500).json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32603,
|
||||
message: 'Internal server error',
|
||||
},
|
||||
id: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/mcp', async (req: Request, res: Response) => {
|
||||
console.log('Received GET MCP request');
|
||||
res.writeHead(405).end(JSON.stringify({
|
||||
jsonrpc: "2.0",
|
||||
error: {
|
||||
code: -32000,
|
||||
message: "Method not allowed."
|
||||
},
|
||||
id: null
|
||||
}));
|
||||
});
|
||||
|
||||
app.delete('/mcp', async (req: Request, res: Response) => {
|
||||
console.log('Received DELETE MCP request');
|
||||
res.writeHead(405).end(JSON.stringify({
|
||||
jsonrpc: "2.0",
|
||||
error: {
|
||||
code: -32000,
|
||||
message: "Method not allowed."
|
||||
},
|
||||
id: null
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
// Start the server
|
||||
const PORT = 3000;
|
||||
app.listen(PORT, () => {
|
||||
console.log(`MCP Stateless Streamable HTTP Server listening on port ${PORT}`);
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
This stateless approach is useful for:
|
||||
- Simple API wrappers
|
||||
- RESTful scenarios where each request is independent
|
||||
- Horizontally scaled deployments without shared session state
|
||||
|
||||
### Testing and Debugging
|
||||
|
||||
To test your server, you can use the [MCP Inspector](https://github.com/modelcontextprotocol/inspector). See its README for more information.
|
||||
|
||||
## Examples
|
||||
|
||||
### Echo Server
|
||||
|
||||
A simple server demonstrating resources, tools, and prompts:
|
||||
|
||||
```typescript
|
||||
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { z } from "zod";
|
||||
|
||||
const server = new McpServer({
|
||||
name: "Echo",
|
||||
version: "1.0.0"
|
||||
});
|
||||
|
||||
server.resource(
|
||||
"echo",
|
||||
new ResourceTemplate("echo://{message}", { list: undefined }),
|
||||
async (uri, { message }) => ({
|
||||
contents: [{
|
||||
uri: uri.href,
|
||||
text: `Resource echo: ${message}`
|
||||
}]
|
||||
})
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"echo",
|
||||
{ message: z.string() },
|
||||
async ({ message }) => ({
|
||||
content: [{ type: "text", text: `Tool echo: ${message}` }]
|
||||
})
|
||||
);
|
||||
|
||||
server.prompt(
|
||||
"echo",
|
||||
{ message: z.string() },
|
||||
({ message }) => ({
|
||||
messages: [{
|
||||
role: "user",
|
||||
content: {
|
||||
type: "text",
|
||||
text: `Please process this message: ${message}`
|
||||
}
|
||||
}]
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
### SQLite Explorer
|
||||
|
||||
A more complex example showing database integration:
|
||||
|
||||
```typescript
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import sqlite3 from "sqlite3";
|
||||
import { promisify } from "util";
|
||||
import { z } from "zod";
|
||||
|
||||
const server = new McpServer({
|
||||
name: "SQLite Explorer",
|
||||
version: "1.0.0"
|
||||
});
|
||||
|
||||
// Helper to create DB connection
|
||||
const getDb = () => {
|
||||
const db = new sqlite3.Database("database.db");
|
||||
return {
|
||||
all: promisify<string, any[]>(db.all.bind(db)),
|
||||
close: promisify(db.close.bind(db))
|
||||
};
|
||||
};
|
||||
|
||||
server.resource(
|
||||
"schema",
|
||||
"schema://main",
|
||||
async (uri) => {
|
||||
const db = getDb();
|
||||
try {
|
||||
const tables = await db.all(
|
||||
"SELECT sql FROM sqlite_master WHERE type='table'"
|
||||
);
|
||||
return {
|
||||
contents: [{
|
||||
uri: uri.href,
|
||||
text: tables.map((t: {sql: string}) => t.sql).join("\n")
|
||||
}]
|
||||
};
|
||||
} finally {
|
||||
await db.close();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"query",
|
||||
{ sql: z.string() },
|
||||
async ({ sql }) => {
|
||||
const db = getDb();
|
||||
try {
|
||||
const results = await db.all(sql);
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(results, null, 2)
|
||||
}]
|
||||
};
|
||||
} catch (err: unknown) {
|
||||
const error = err as Error;
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Error: ${error.message}`
|
||||
}],
|
||||
isError: true
|
||||
};
|
||||
} finally {
|
||||
await db.close();
|
||||
}
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Dynamic Servers
|
||||
|
||||
If you want to offer an initial set of tools/prompts/resources, but later add additional ones based on user action or external state change, you can add/update/remove them _after_ the Server is connected. This will automatically emit the corresponding `listChanged` notificaions:
|
||||
|
||||
```ts
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { z } from "zod";
|
||||
|
||||
const server = new McpServer({
|
||||
name: "Dynamic Example",
|
||||
version: "1.0.0"
|
||||
});
|
||||
|
||||
const listMessageTool = server.tool(
|
||||
"listMessages",
|
||||
{ channel: z.string() },
|
||||
async ({ channel }) => ({
|
||||
content: [{ type: "text", text: await listMessages(channel) }]
|
||||
})
|
||||
);
|
||||
|
||||
const putMessageTool = server.tool(
|
||||
"putMessage",
|
||||
{ channel: z.string(), message: z.string() },
|
||||
async ({ channel, message }) => ({
|
||||
content: [{ type: "text", text: await putMessage(channel, string) }]
|
||||
})
|
||||
);
|
||||
// Until we upgrade auth, `putMessage` is disabled (won't show up in listTools)
|
||||
putMessageTool.disable()
|
||||
|
||||
const upgradeAuthTool = server.tool(
|
||||
"upgradeAuth",
|
||||
{ permission: z.enum(["write', vadmin"])},
|
||||
// Any mutations here will automatically emit `listChanged` notifications
|
||||
async ({ permission }) => {
|
||||
const { ok, err, previous } = await upgradeAuthAndStoreToken(permission)
|
||||
if (!ok) return {content: [{ type: "text", text: `Error: ${err}` }]}
|
||||
|
||||
// If we previously had read-only access, 'putMessage' is now available
|
||||
if (previous === "read") {
|
||||
putMessageTool.enable()
|
||||
}
|
||||
|
||||
if (permission === 'write') {
|
||||
// If we've just upgraded to 'write' permissions, we can still call 'upgradeAuth'
|
||||
// but can only upgrade to 'admin'.
|
||||
upgradeAuthTool.update({
|
||||
paramSchema: { permission: z.enum(["admin"]) }, // change validation rules
|
||||
})
|
||||
} else {
|
||||
// If we're now an admin, we no longer have anywhere to upgrade to, so fully remove that tool
|
||||
upgradeAuthTool.remove()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Connect as normal
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
```
|
||||
|
||||
### Low-Level Server
|
||||
|
||||
For more control, you can use the low-level Server class directly:
|
||||
|
||||
```typescript
|
||||
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import {
|
||||
ListPromptsRequestSchema,
|
||||
GetPromptRequestSchema
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
|
||||
const server = new Server(
|
||||
{
|
||||
name: "example-server",
|
||||
version: "1.0.0"
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
prompts: {}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
||||
return {
|
||||
prompts: [{
|
||||
name: "example-prompt",
|
||||
description: "An example prompt template",
|
||||
arguments: [{
|
||||
name: "arg1",
|
||||
description: "Example argument",
|
||||
required: true
|
||||
}]
|
||||
}]
|
||||
};
|
||||
});
|
||||
|
||||
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
||||
if (request.params.name !== "example-prompt") {
|
||||
throw new Error("Unknown prompt");
|
||||
}
|
||||
return {
|
||||
description: "Example prompt",
|
||||
messages: [{
|
||||
role: "user",
|
||||
content: {
|
||||
type: "text",
|
||||
text: "Example prompt text"
|
||||
}
|
||||
}]
|
||||
};
|
||||
});
|
||||
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
```
|
||||
|
||||
### Writing MCP Clients
|
||||
|
||||
The SDK provides a high-level client interface:
|
||||
|
||||
```typescript
|
||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
||||
|
||||
const transport = new StdioClientTransport({
|
||||
command: "node",
|
||||
args: ["server.js"]
|
||||
});
|
||||
|
||||
const client = new Client(
|
||||
{
|
||||
name: "example-client",
|
||||
version: "1.0.0"
|
||||
}
|
||||
);
|
||||
|
||||
await client.connect(transport);
|
||||
|
||||
// List prompts
|
||||
const prompts = await client.listPrompts();
|
||||
|
||||
// Get a prompt
|
||||
const prompt = await client.getPrompt({
|
||||
name: "example-prompt",
|
||||
arguments: {
|
||||
arg1: "value"
|
||||
}
|
||||
});
|
||||
|
||||
// List resources
|
||||
const resources = await client.listResources();
|
||||
|
||||
// Read a resource
|
||||
const resource = await client.readResource({
|
||||
uri: "file:///example.txt"
|
||||
});
|
||||
|
||||
// Call a tool
|
||||
const result = await client.callTool({
|
||||
name: "example-tool",
|
||||
arguments: {
|
||||
arg1: "value"
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Proxy Authorization Requests Upstream
|
||||
|
||||
You can proxy OAuth requests to an external authorization provider:
|
||||
|
||||
```typescript
|
||||
import express from 'express';
|
||||
import { ProxyOAuthServerProvider, mcpAuthRouter } from '@modelcontextprotocol/sdk';
|
||||
|
||||
const app = express();
|
||||
|
||||
const proxyProvider = new ProxyOAuthServerProvider({
|
||||
endpoints: {
|
||||
authorizationUrl: "https://auth.external.com/oauth2/v1/authorize",
|
||||
tokenUrl: "https://auth.external.com/oauth2/v1/token",
|
||||
revocationUrl: "https://auth.external.com/oauth2/v1/revoke",
|
||||
},
|
||||
verifyAccessToken: async (token) => {
|
||||
return {
|
||||
token,
|
||||
clientId: "123",
|
||||
scopes: ["openid", "email", "profile"],
|
||||
}
|
||||
},
|
||||
getClient: async (client_id) => {
|
||||
return {
|
||||
client_id,
|
||||
redirect_uris: ["http://localhost:3000/callback"],
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
app.use(mcpAuthRouter({
|
||||
provider: proxyProvider,
|
||||
issuerUrl: new URL("http://auth.external.com"),
|
||||
baseUrl: new URL("http://mcp.example.com"),
|
||||
serviceDocumentationUrl: new URL("https://docs.example.com/"),
|
||||
}))
|
||||
```
|
||||
|
||||
This setup allows you to:
|
||||
- Forward OAuth requests to an external provider
|
||||
- Add custom token validation logic
|
||||
- Manage client registrations
|
||||
- Provide custom documentation URLs
|
||||
- Maintain control over the OAuth flow while delegating to an external provider
|
||||
|
||||
### Backwards Compatibility
|
||||
|
||||
Clients and servers with StreamableHttp tranport can maintain [backwards compatibility](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#backwards-compatibility) with the deprecated HTTP+SSE transport (from protocol version 2024-11-05) as follows
|
||||
|
||||
#### Client-Side Compatibility
|
||||
|
||||
For clients that need to work with both Streamable HTTP and older SSE servers:
|
||||
|
||||
```typescript
|
||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
||||
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
||||
let client: Client|undefined = undefined
|
||||
const baseUrl = new URL(url);
|
||||
try {
|
||||
client = new Client({
|
||||
name: 'streamable-http-client',
|
||||
version: '1.0.0'
|
||||
});
|
||||
const transport = new StreamableHTTPClientTransport(
|
||||
new URL(baseUrl)
|
||||
);
|
||||
await client.connect(transport);
|
||||
console.log("Connected using Streamable HTTP transport");
|
||||
} catch (error) {
|
||||
// If that fails with a 4xx error, try the older SSE transport
|
||||
console.log("Streamable HTTP connection failed, falling back to SSE transport");
|
||||
client = new Client({
|
||||
name: 'sse-client',
|
||||
version: '1.0.0'
|
||||
});
|
||||
const sseTransport = new SSEClientTransport(baseUrl);
|
||||
await client.connect(sseTransport);
|
||||
console.log("Connected using SSE transport");
|
||||
}
|
||||
```
|
||||
|
||||
#### Server-Side Compatibility
|
||||
|
||||
For servers that need to support both Streamable HTTP and older clients:
|
||||
|
||||
```typescript
|
||||
import express from "express";
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
||||
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
||||
|
||||
const server = new McpServer({
|
||||
name: "backwards-compatible-server",
|
||||
version: "1.0.0"
|
||||
});
|
||||
|
||||
// ... set up server resources, tools, and prompts ...
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
// Store transports for each session type
|
||||
const transports = {
|
||||
streamable: {} as Record<string, StreamableHTTPServerTransport>,
|
||||
sse: {} as Record<string, SSEServerTransport>
|
||||
};
|
||||
|
||||
// Modern Streamable HTTP endpoint
|
||||
app.all('/mcp', async (req, res) => {
|
||||
// Handle Streamable HTTP transport for modern clients
|
||||
// Implementation as shown in the "With Session Management" example
|
||||
// ...
|
||||
});
|
||||
|
||||
// Legacy SSE endpoint for older clients
|
||||
app.get('/sse', async (req, res) => {
|
||||
// Create SSE transport for legacy clients
|
||||
const transport = new SSEServerTransport('/messages', res);
|
||||
transports.sse[transport.sessionId] = transport;
|
||||
|
||||
res.on("close", () => {
|
||||
delete transports.sse[transport.sessionId];
|
||||
});
|
||||
|
||||
await server.connect(transport);
|
||||
});
|
||||
|
||||
// Legacy message endpoint for older clients
|
||||
app.post('/messages', async (req, res) => {
|
||||
const sessionId = req.query.sessionId as string;
|
||||
const transport = transports.sse[sessionId];
|
||||
if (transport) {
|
||||
await transport.handlePostMessage(req, res, req.body);
|
||||
} else {
|
||||
res.status(400).send('No transport found for sessionId');
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
```
|
||||
|
||||
**Note**: The SSE transport is now deprecated in favor of Streamable HTTP. New implementations should use Streamable HTTP, and existing SSE implementations should plan to migrate.
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Model Context Protocol documentation](https://modelcontextprotocol.io)
|
||||
- [MCP Specification](https://spec.modelcontextprotocol.io)
|
||||
- [Example Servers](https://github.com/modelcontextprotocol/servers)
|
||||
|
||||
## Contributing
|
||||
|
||||
Issues and pull requests are welcome on GitHub at https://github.com/modelcontextprotocol/typescript-sdk.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License—see the [LICENSE](LICENSE) file for details.
|
||||
Reference in New Issue
Block a user