MCP 类型脚本 SDK
重要说明 这是一个非官方的叉子升级到Zod V4. 您可以直接检查 github 仓库上的更改
原订本
原订本
这个叉子
目录
概览
模式上下文协议允许应用程序以标准化的方式为LLMs提供上下文,将提供上下文的关切与实际LLM交互区分开来. 这种TypeScript SDK执行完整的MCP规格,使其易于:
- 构建可以连接到任何 MCP 服务器的 MCP 客户端
- 创建曝光资源、提示和工具的 MCP 服务器
- 使用 Stdio 和可流式 HTTP 等标准运输
- 处理所有 MCP 协议信息和生命周期事件
安装
npm install @modelcontextprotocol/sdk
QQ MCP要求Node.js v18.x或更高值的工作罚款.
快速启动
让我们创建一个简单的MCP服务器,暴露一个计算工具和一些数据:
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-server",
version: "1.0.0",
});
// Add an addition tool
server.registerTool(
"add",
{
title: "Addition Tool",
description: "Add two numbers",
inputSchema: { a: z.number(), b: z.number() },
},
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }],
}),
);
// Add a dynamic greeting resource
server.registerResource(
"greeting",
new ResourceTemplate("greeting://{name}", { list: undefined }),
{
title: "Greeting Resource", // Display name for UI
description: "Dynamic greeting generator",
},
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);
什么是MCP吗?
这个 示范背景协议(MCP) 允许您以安全,标准化的方式建立服务器,使数据和功能暴露于LLM应用程序. 把它想成一个网络API,但专门为LLM交互设计. MCP服务器可以:
- 将数据通过 资源 (想想这些像Get end points;它们用来将信息装入LLM的上下文)
- 通过 工具 (类似 POST 的终点;用于执行代码或以其他方式产生副作用)
- 通过 提示 (LLM互动可重复使用的模板)
- 还有更多!
核心概念
服务器
McpServer是您与MCP协议的核心接口. 它处理连接管理,协议遵守,以及消息路由:
const server = new McpServer({
name: "my-app",
version: "1.0.0",
});
资源
资源是您如何向LLMs披露数据。 它们类似于在 REST API 中获取端点 — 它们提供数据,但不应该进行重大的计算或产生副作用:
// Static resource
server.registerResource(
"config",
"config://app",
{
title: "Application Config",
description: "Application configuration data",
mimeType: "text/plain",
},
async (uri) => ({
contents: [
{
uri: uri.href,
text: "App configuration here",
},
],
}),
);
// Dynamic resource with parameters
server.registerResource(
"user-profile",
new ResourceTemplate("users://{userId}/profile", { list: undefined }),
{
title: "User Profile",
description: "User profile information",
},
async (uri, { userId }) => ({
contents: [
{
uri: uri.href,
text: `Profile data for user ${userId}`,
},
],
}),
);
// Resource with context-aware completion
server.registerResource(
"repository",
new ResourceTemplate("github://repos/{owner}/{repo}", {
list: undefined,
complete: {
// Provide intelligent completions based on previously resolved parameters
repo: (value, context) => {
if (context?.arguments?.["owner"] === "org1") {
return ["project1", "project2", "project3"].filter((r) =>
r.startsWith(value),
);
}
return ["default-repo"].filter((r) => r.startsWith(value));
},
},
}),
{
title: "GitHub Repository",
description: "Repository information",
},
async (uri, { owner, repo }) => ({
contents: [
{
uri: uri.href,
text: `Repository: ${owner}/${repo}`,
},
],
}),
);
工具
工具让 LLMs 通过您的服务器采取行动 。 与资源不同,预计工具会进行计算并具有副作用:
// Simple tool with parameters
server.registerTool(
"calculate-bmi",
{
title: "BMI Calculator",
description: "Calculate Body Mass Index",
inputSchema: {
weightKg: z.number(),
heightM: z.number(),
},
},
async ({ weightKg, heightM }) => ({
content: [
{
type: "text",
text: String(weightKg / (heightM * heightM)),
},
],
}),
);
// Async tool with external API call
server.registerTool(
"fetch-weather",
{
title: "Weather Fetcher",
description: "Get weather data for a city",
inputSchema: { 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 }],
};
},
);
// Tool that returns ResourceLinks
server.registerTool(
"list-files",
{
title: "List Files",
description: "List project files",
inputSchema: { pattern: z.string() },
},
async ({ pattern }) => ({
content: [
{ type: "text", text: `Found files matching "${pattern}":` },
// ResourceLinks let tools return references without file content
{
type: "resource_link",
uri: "file:///project/README.md",
name: "README.md",
mimeType: "text/markdown",
description: "A README file",
},
{
type: "resource_link",
uri: "file:///project/src/index.ts",
name: "index.ts",
mimeType: "text/typescript",
description: "An index file",
},
],
}),
);
资源链接
工具可以返回 ResourceLink 对象为不嵌入全部内容的参考资源。 这对于处理大文件或许多资源时的性能至关重要 - 然后客户端可以有选择地只读取他们使用所提供的URI所需要的资源.
提示
提示是可重复使用的模板, 帮助 LLMS 与您的服务器有效互动 :
import { completable } from "@modelcontextprotocol/sdk/server/completable.js";
server.registerPrompt(
"review-code",
{
title: "Code Review",
description: "Review code for best practices and potential issues",
argsSchema: { code: z.string() },
},
({ code }) => ({
messages: [
{
role: "user",
content: {
type: "text",
text: `Please review this code:\n\n${code}`,
},
},
],
}),
);
// Prompt with context-aware completion
server.registerPrompt(
"team-greeting",
{
title: "Team Greeting",
description: "Generate a greeting for team members",
argsSchema: {
department: completable(z.string(), (value) => {
// Department suggestions
return ["engineering", "sales", "marketing", "support"].filter((d) =>
d.startsWith(value),
);
}),
name: completable(z.string(), (value, context) => {
// Name suggestions based on selected department
const department = context?.arguments?.["department"];
if (department === "engineering") {
return ["Alice", "Bob", "Charlie"].filter((n) => n.startsWith(value));
} else if (department === "sales") {
return ["David", "Eve", "Frank"].filter((n) => n.startsWith(value));
} else if (department === "marketing") {
return ["Grace", "Henry", "Iris"].filter((n) => n.startsWith(value));
}
return ["Guest"].filter((n) => n.startsWith(value));
}),
},
},
({ department, name }) => ({
messages: [
{
role: "assistant",
content: {
type: "text",
text: `Hello ${name}, welcome to the ${department} team!`,
},
},
],
}),
);
完成情况
MCP支持完成参数以帮助用户填写即时参数和资源模板参数. 见上文实例 资源完成情况 财务报告和审定财务报表 及时完成。 。 。 .
客户端使用
// Request completions for any argument
const result = await client.complete({
ref: {
type: "ref/prompt", // or "ref/resource"
name: "example", // or uri: "template://..."
},
argument: {
name: "argumentName",
value: "partial", // What the user has typed so far
},
context: {
// Optional: Include previously resolved arguments
arguments: {
previousArg: "value",
},
},
});
显示名称和元数据
所有资源、工具和提示都支持可选 title 字段,用于更好的 UI 演示。 这个 title 用作显示名称,而 name 仍然是唯一的标识符.
说明: 这个 register* 方法(单位:千美元)registerTool, (中文(简体) ), registerPrompt, (中文(简体) ), registerResource)是新守则的建议方法。 较旧的方法tool, (中文(简体) ), prompt, (中文(简体) ), resource) 仍然可供反向兼容.
工具标题优先
具体地说,对于工具,有两种方式来指定标题:
title字段在工具配置中annotations.title字段( 当使用旧字段时)tool()带有说明的方法)
优先顺序为: title → annotations.title → name
// Using registerTool (recommended)
server.registerTool(
"my_tool",
{
title: "My Tool", // This title takes precedence
annotations: {
title: "Annotation Title", // This is ignored if title is set
},
},
handler,
);
// Using tool with annotations (older API)
server.tool(
"my_tool",
"description",
{
title: "Annotation Title", // This is used as title
},
handler,
);
在构建客户端时, 使用所提供的工具获取适当的显示名 :
import { getDisplayName } from "@modelcontextprotocol/sdk/shared/metadataUtils.js";
// Automatically handles the precedence: title → annotations.title → name
const displayName = getDisplayName(tool);
取样
MCP服务器可以请求支持采样的连接客户端完成LLM.
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const mcpServer = new McpServer({
name: "tools-with-sample-server",
version: "1.0.0",
});
// Tool that uses LLM sampling to summarize any text
mcpServer.registerTool(
"summarize",
{
description: "Summarize any text using an LLM",
inputSchema: {
text: z.string().describe("Text to summarize"),
},
},
async ({ text }) => {
// Call the LLM through MCP sampling
const response = await mcpServer.server.createMessage({
messages: [
{
role: "user",
content: {
type: "text",
text: `Please summarize the following text concisely:\n\n${text}`,
},
},
],
maxTokens: 500,
});
return {
content: [
{
type: "text",
text:
response.content.type === "text"
? response.content.text
: "Unable to generate summary",
},
],
};
},
);
async function main() {
const transport = new StdioServerTransport();
await mcpServer.connect(transport);
console.log("MCP server is running...");
}
main().catch((error) => {
console.error("Server error:", error);
process.exit(1);
});
运行您的服务器
TypeScript中的MCP服务器需要连接到一个传输上才能与客户端通信. 您如何启动服务器取决于运输的选择 :
静态
对于命令行工具和直接集成:
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);
可流取的 HTTP
对于远程服务器,设置可流的HTTP传输,既处理客户端请求,又处理服务器对客户端通知.
有会话管理
在某些情况下,服务器需要状态. 这是通过 会话管理。 。 。 .
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;
},
// DNS rebinding protection is disabled by default for backwards compatibility. If you are running this server
// locally, make sure to set:
// enableDnsRebindingProtection: true,
// allowedHosts: ['127.0.0.1'],
});
// 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);
[TIP] 在远程环境中使用此选项时, 确保允许页眉参数
mcp-session-id在中央统计局。 否则,可能导致:Bad Request: No valid session ID provided错误。 下节为例.
基于浏览器的客户端的 CORS 配置
如果您希望您的服务器能够被基于浏览器的MCP客户端访问,则需要配置 CORS 头. 这个 Mcp-Session-Id 浏览器客户端必须被曝光才能访问:
import cors from "cors";
// Add CORS middleware before your MCP routes
app.use(
cors({
origin: "*", // Configure appropriately for production, for example:
// origin: ['https://your-remote-domain.com', 'https://your-other-remote-domain.com'],
exposedHeaders: ["Mcp-Session-Id"],
allowedHeaders: ["Content-Type", "mcp-session-id"],
}),
);
这种配置是必要的,因为:
- MCP可流式HTTP运输使用
Mcp-Session-Id会话管理头 - 浏览器限制访问响应头, 除非通过 CORS 明确曝光
- 如果没有这种配置,基于浏览器的客户端将无法从初始化回复读取会话ID
无会话管理( 无状态)
简单使用不需要会话管理的案例:
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,
});
}
}
});
// SSE notifications not supported in stateless mode
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,
}),
);
});
// Session termination not needed in stateless mode
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;
setupServer()
.then(() => {
app.listen(PORT, (error) => {
if (error) {
console.error("Failed to start server:", error);
process.exit(1);
}
console.log(
`MCP Stateless Streamable HTTP Server listening on port ${PORT}`,
);
});
})
.catch((error) => {
console.error("Failed to set up the server:", error);
process.exit(1);
});
这种无国籍做法有助于:
- 简单的 API 包装
- 每项请求都是独立的可重复的情景
- 没有共享会话状态的横向部署
DNS 重新绑定的保护
流动式HTTP运输包括DNS重新绑定保护以防止安全漏洞. 默认情况下,这种保护是 已禁用 为后向相容性.
重要: 如果您正在本地运行此服务器, 请启用 DNS 重新绑定保护 :
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
enableDnsRebindingProtection: true,
allowedHosts: ['127.0.0.1', ...],
allowedOrigins: ['https://yourdomain.com', 'https://www.yourdomain.com']
});
测试和调试
为了测试您的服务器, 您可以使用 MCP 检查员。关于更多信息,请参看其研究.
实例
Echo 服务器
一个简单的服务器, 演示资源、 工具和提示 :
import {
McpServer,
ResourceTemplate,
} from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
const server = new McpServer({
name: "echo-server",
version: "1.0.0",
});
server.registerResource(
"echo",
new ResourceTemplate("echo://{message}", { list: undefined }),
{
title: "Echo Resource",
description: "Echoes back messages as resources",
},
async (uri, { message }) => ({
contents: [
{
uri: uri.href,
text: `Resource echo: ${message}`,
},
],
}),
);
server.registerTool(
"echo",
{
title: "Echo Tool",
description: "Echoes back the provided message",
inputSchema: { message: z.string() },
},
async ({ message }) => ({
content: [{ type: "text", text: `Tool echo: ${message}` }],
}),
);
server.registerPrompt(
"echo",
{
title: "Echo Prompt",
description: "Creates a prompt to process a message",
argsSchema: { message: z.string() },
},
({ message }) => ({
messages: [
{
role: "user",
content: {
type: "text",
text: `Please process this message: ${message}`,
},
},
],
}),
);
SQLite 浏览器
一个显示数据库整合的更复杂的例子:
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.registerResource(
"schema",
"schema://main",
{
title: "Database Schema",
description: "SQLite database schema",
mimeType: "text/plain",
},
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.registerTool(
"query",
{
title: "SQL Query",
description: "Execute SQL queries on the database",
inputSchema: { 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();
}
},
);
高级使用
动态服务器
如果您想要提供一组初始工具/ 即时/ 资源, 但稍后根据用户动作或外部状态变化添加更多工具/ 即时/ 资源, 您可以添加/ 更新/ 重新移动 之后 服务器已连接。 这将自动释放相应的 listChanged 通知:
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, message) }],
}),
);
// Until we upgrade auth, `putMessage` is disabled (won't show up in listTools)
putMessageTool.disable();
const upgradeAuthTool = server.tool(
"upgradeAuth",
{ permission: z.enum(["write", "admin"]) },
// 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);
改进网络效率,并发布通知
当执行可触发通知的批量更新(例如,在一个回路中允许或使多个工具失效)时,SDK可以在短时间内发送大量信息. 为了提高性能并减少网络流量,您可以启用通知分解.
这个特性将多个,快速的调用相同的通知类型汇入单个消息. 例如,如果连续禁用5个工具,则只有一个 notifications/tools/list_changed 信件将改为5个.
[大 这个特性是为在其参数中不携带独特数据的"简单"通知而设计的. 为防止无声数据丢失 自动绕过 任何含有下列内容的通知:
params对象或一个relatedRequestId这种通知将始终立即发出.
这是服务器初始化时配置的选入功能 .
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
const server = new McpServer(
{
name: "efficient-server",
version: "1.0.0"
},
{
// Enable notification debouncing for specific methods
debouncedNotificationMethods: [
'notifications/tools/list_changed',
'notifications/resources/list_changed',
'notifications/prompts/list_changed'
]
}
);
// Now, any rapid changes to tools, resources, or prompts will result
// in a single, consolidated notification for each type.
server.registerTool("tool1", ...).disable();
server.registerTool("tool2", ...).disable();
server.registerTool("tool3", ...).disable();
// Only one 'notifications/tools/list_changed' is sent.
低级服务器
为了进行更多的控制,您可以直接使用低级服务器类:
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);
Eliening 用户输入
MCP服务器可以通过引出功能要求用户提供更多信息. 用于服务器需要用户输入或确认的交互式工作流程:
// Server-side: Restaurant booking tool that asks for alternatives
server.tool(
"book-restaurant",
{
restaurant: z.string(),
date: z.string(),
partySize: z.number(),
},
async ({ restaurant, date, partySize }) => {
// Check availability
const available = await checkAvailability(restaurant, date, partySize);
if (!available) {
// Ask user if they want to try alternative dates
const result = await server.server.elicitInput({
message: `No tables available at ${restaurant} on ${date}. Would you like to check alternative dates?`,
requestedSchema: {
type: "object",
properties: {
checkAlternatives: {
type: "boolean",
title: "Check alternative dates",
description: "Would you like me to check other dates?",
},
flexibleDates: {
type: "string",
title: "Date flexibility",
description: "How flexible are your dates?",
enum: ["next_day", "same_week", "next_week"],
enumNames: ["Next day", "Same week", "Next week"],
},
},
required: ["checkAlternatives"],
},
});
if (result.action === "accept" && result.content?.checkAlternatives) {
const alternatives = await findAlternatives(
restaurant,
date,
partySize,
result.content.flexibleDates as string,
);
return {
content: [
{
type: "text",
text: `Found these alternatives: ${alternatives.join(", ")}`,
},
],
};
}
return {
content: [
{
type: "text",
text: "No booking made. Original date not available.",
},
],
};
}
// Book the table
await makeBooking(restaurant, date, partySize);
return {
content: [
{
type: "text",
text: `Booked table for ${partySize} at ${restaurant} on ${date}`,
},
],
};
},
);
客户端: 处理查询请求
// This is a placeholder - implement based on your UI framework
async function getInputFromUser(
message: string,
schema: any,
): Promise<{
action: "accept" | "decline" | "cancel";
data?: Record<string, any>;
}> {
// This should be implemented depending on the app
throw new Error("getInputFromUser must be implemented for your platform");
}
client.setRequestHandler(ElicitRequestSchema, async (request) => {
const userResponse = await getInputFromUser(
request.params.message,
request.params.requestedSchema,
);
return {
action: userResponse.action,
content: userResponse.action === "accept" ? userResponse.data : undefined,
};
});
说明: Election需要客户端支持. 客户必须声明 elicitation 初始化期间的能力.
写入 MCP 客户端
SDK提供了一个高级客户界面:
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",
},
});
代理授权请求上游
您可以代理 OAuth 请求外部授权提供者 :
import express from "express";
import { ProxyOAuthServerProvider } from "@modelcontextprotocol/sdk/server/auth/providers/proxyProvider.js";
import { mcpAuthRouter } from "@modelcontextprotocol/sdk/server/auth/router.js";
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/"),
}),
);
这个设置允许您:
- 将 OAuth 请求转发到外部提供者
- 添加自定义符号验证逻辑
- 管理客户注册
- 提供自定义文档 URL
- 保持对 OAuth 流量的控制,同时将权限下放给外部提供者
后向兼容性
具有可流式Http运输的客户端和服务器可以维护 向后兼容 与被贬值的HTTP+SSE运输(从协议版本2024-11-05)如下:
客户端兼容性
对于需要同时使用可流式HTTP和旧的SSE服务器的客户端:
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");
}
服务器系统兼容性
对于需要同时支持可流式HTTP和旧客户端的服务器:
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);
说明:SSE运输现在被贬值,有利于流式HTTP. 新的实施应当使用可流式HTTP,现有的SSE实施应当计划迁移.
文档
捐款
GitHub网站欢迎提出问题和拉动请求 https://github.com/modelcontextprotocol/typescript-sdk (中文(简体) )。 。 。 .
许可证
这个项目根据麻省理工学院许可证获得许可——见 自由主义 详细文件.