Token导航 LogoToken导航TokenDH.com
MCP Sd Kjavascript logo
AI代理未说明官方级别未说明来源级核验

MCP Sd Kjavascript

MCP Server

一个用于标准化LLM上下文交互的TypeScript SDK,支持创建MCP服务器、构建客户端及使用标准传输协议。

工具数

6

提示词数

0

GitHub Stars

0

资源数

0
上下文管理协议实现TypeScriptClaudeAI开发工具ClaudeCursorVS Code

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

作者 / 组织

yun59000

提供方

yun59000

最后核验

2026/5/17 20:20

快速接入

先看主来源和安装命令,再打开仓库或文档;下面只保留这个条目的关键接入事实。

详细介绍

MCP TypeScript SDK MIT licensed

Table of Contents

- 服务器 - 工具 - 资源 - 提示 - 完井作业 - 显示名称和元数据 - 抽样

- 可流式传输的HTTP - stdio(在编程中通常指“标准输入输出”库,全称为“Standard Input Output”) - 测试与调试

- 回声服务器 - SQLite 探索器

- 动态服务器 - 通过防抖技术提高网络效率 - 低级服务器 - 获取用户输入 - 编写MCP客户端 - 代理上游授权请求 - 向后兼容性

概述

模型上下文协议允许应用程序以标准化的方式为大型语言模型(LLM)提供上下文,将提供上下文的功能与实际的LLM交互分离开来。这个TypeScript软件开发工具包实现了这一功能 完整的MCP规范,使其易于:

  • 创建MCP服务器以暴露资源、提示和工具
  • 构建能够连接到任何MCP服务器的MCP客户端
  • 使用标准传输方式,如stdio和可流式HTTP

安装

npm install @modelcontextprotocol/sdk

快速入门

让我们创建一个简单的MCP服务器,它提供一个计算器工具和一些数据。将以下内容保存为 server.ts

import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import express from 'express';
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() },
        outputSchema: { result: z.number() }
    },
    async ({ a, b }) => {
        const output = { result: a + b };
        return {
            content: [{ type: 'text', text: JSON.stringify(output) }],
            structuredContent: output
        };
    }
);

// 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}!`
            }
        ]
    })
);

// Set up Express and HTTP transport
const app = express();
app.use(express.json());

app.post('/mcp', async (req, res) => {
    // Create a new transport for each request to prevent request ID collisions
    const transport = new StreamableHTTPServerTransport({
        sessionIdGenerator: undefined,
        enableJsonResponse: true
    });

    res.on('close', () => {
        transport.close();
    });

    await server.connect(transport);
    await transport.handleRequest(req, res, req.body);
});

const port = parseInt(process.env.PORT || '3000');
app.listen(port, () => {
    console.log(`Demo MCP Server running on http://localhost:${port}/mcp`);
}).on('error', error => {
    console.error('Server error:', error);
    process.exit(1);
});

使用以下命令安装依赖项: npm install @modelcontextprotocol/sdk express zod@3,并与(某物/某人)一起运行 npx -y tsx server.ts.

您可以使用任何支持可流式传输HTTP的MCP客户端来连接它,例如:

然后试着让你的代理使用它的新工具来相加两个数字!

核心概念

服务器

McpServer是您与MCP协议交互的核心接口。它负责连接管理、协议合规性以及消息路由:

const server = new McpServer({
    name: 'my-app',
    version: '1.0.0'
});

工具

工具 让大型语言模型(LLMs)通过您的服务器执行操作。工具可以执行计算、获取数据并产生副作用。工具应设计为由模型控制——即,人工智能模型将决定调用哪些工具, 以及论点。

// Simple tool with parameters
server.registerTool(
    'calculate-bmi',
    {
        title: 'BMI Calculator',
        description: 'Calculate Body Mass Index',
        inputSchema: {
            weightKg: z.number(),
            heightM: z.number()
        },
        outputSchema: { bmi: z.number() }
    },
    async ({ weightKg, heightM }) => {
        const output = { bmi: weightKg / (heightM * heightM) };
        return {
            content: [
                {
                    type: 'text',
                    text: JSON.stringify(output)
                }
            ],
            structuredContent: output
        };
    }
);

// Async tool with external API call
server.registerTool(
    'fetch-weather',
    {
        title: 'Weather Fetcher',
        description: 'Get weather data for a city',
        inputSchema: { city: z.string() },
        outputSchema: { temperature: z.number(), conditions: z.string() }
    },
    async ({ city }) => {
        const response = await fetch(`https://api.weather.com/${city}`);
        const data = await response.json();
        const output = { temperature: data.temp, conditions: data.conditions };
        return {
            content: [{ type: 'text', text: JSON.stringify(output) }],
            structuredContent: output
        };
    }
);

// Tool that returns ResourceLinks
server.registerTool(
    'list-files',
    {
        title: 'List Files',
        description: 'List project files',
        inputSchema: { pattern: z.string() },
        outputSchema: {
            count: z.number(),
            files: z.array(z.object({ name: z.string(), uri: z.string() }))
        }
    },
    async ({ pattern }) => {
        const output = {
            count: 2,
            files: [
                { name: 'README.md', uri: 'file:///project/README.md' },
                { name: 'index.ts', uri: 'file:///project/src/index.ts' }
            ]
        };
        return {
            content: [
                { type: 'text', text: JSON.stringify(output) },
                // 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'
                }
            ],
            structuredContent: output
        };
    }
);

资源链接

工具可以返回 ResourceLink 反对在引用资源时不嵌入其全部内容。在处理大文件或众多资源时,这有助于提高性能——客户端可以使用提供的URI有选择地仅读取其所需的资源。

资源

资源 也可以将数据暴露给大型语言模型(LLMs),但与工具不同的是,不应进行大量计算或产生副作用。

资源被设计为以应用驱动的方式使用,这意味着MCP客户端应用程序可以决定如何展示这些资源。例如,客户端可以向用户展示一个资源选择器,或者直接将资源暴露给模型。

// 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}`
            }
        ]
    })
);

提示

提示 这些是可重用的模板,帮助人类提示模型与您的服务器进行交互。它们旨在由用户驱动,并可能在聊天界面中以斜杠命令的形式出现。

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 为了更好的用户界面展示而设计的字段。该 title 用作显示名称(例如“创建一个新问题”),而 name 仍然是唯一标识符(例如。 create_issue)。

注: 这个(或“该”) register* 方法(registerToolregisterPromptregisterResource) 是编写新代码时推荐的方法。旧的方法(toolpromptresource)为保持向后兼容性而继续可用。

工具标题优先级

对于工具而言,具体有两种方式来指定标题:

  • title 工具配置中的字段
  • annotations.title 字段(在使用较旧的 tool() (带有注释的方法)

优先级顺序为: titleannotations.titlename

// 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 { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import express from 'express';
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',
    {
        title: 'Text Summarizer',
        description: 'Summarize any text using an LLM',
        inputSchema: {
            text: z.string().describe('Text to summarize')
        },
        outputSchema: { summary: z.string() }
    },
    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
        });

        const summary = response.content.type === 'text' ? response.content.text : 'Unable to generate summary';
        const output = { summary };
        return {
            content: [{ type: 'text', text: JSON.stringify(output) }],
            structuredContent: output
        };
    }
);

const app = express();
app.use(express.json());

app.post('/mcp', async (req, res) => {
    const transport = new StreamableHTTPServerTransport({
        sessionIdGenerator: undefined,
        enableJsonResponse: true
    });

    res.on('close', () => {
        transport.close();
    });

    await mcpServer.connect(transport);
    await transport.handleRequest(req, res, req.body);
});

const port = parseInt(process.env.PORT || '3000');
app.listen(port, () => {
    console.log(`MCP Server running on http://localhost:${port}/mcp`);
}).on('error', error => {
    console.error('Server error:', error);
    process.exit(1);
});

运行您的服务器

在TypeScript中,MCP服务器需要连接到一个传输层以与客户端进行通信。服务器的启动方式取决于所选的传输层:

可流式传输的HTTP

对于远程服务器,请使用可流式传输的HTTP传输方式。

无会话管理(推荐)

对于大多数不需要会话管理的使用场景:

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import express from 'express';
import { z } from 'zod';

const app = express();
app.use(express.json());

// Create the MCP server once (can be reused across requests)
const server = new McpServer({
    name: 'example-server',
    version: '1.0.0'
});

// Set up your tools, resources, and prompts
server.registerTool(
    'echo',
    {
        title: 'Echo Tool',
        description: 'Echoes back the provided message',
        inputSchema: { message: z.string() },
        outputSchema: { echo: z.string() }
    },
    async ({ message }) => {
        const output = { echo: `Tool echo: ${message}` };
        return {
            content: [{ type: 'text', text: JSON.stringify(output) }],
            structuredContent: output
        };
    }
);

app.post('/mcp', async (req, res) => {
    // In stateless mode, create a new transport for each request to prevent
    // request ID collisions. Different clients may use the same JSON-RPC request IDs,
    // which would cause responses to be routed to the wrong HTTP connections if
    // the transport state is shared.

    try {
        const transport = new StreamableHTTPServerTransport({
            sessionIdGenerator: undefined,
            enableJsonResponse: true
        });

        res.on('close', () => {
            transport.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
            });
        }
    }
});

const port = parseInt(process.env.PORT || '3000');
app.listen(port, () => {
    console.log(`MCP Server running on http://localhost:${port}/mcp`);
}).on('error', error => {
    console.error('Server error:', error);
    process.exit(1);
});

具备会话管理功能

在某些情况下,服务器需要有状态的会话。这可以通过 会话管理 在MCP协议中。

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);

基于浏览器的客户端的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

DNS重绑定保护

Streamable HTTP传输协议包含DNS重绑定保护功能,以防止安全漏洞。默认情况下,此保护功能是 残疾的 为了向后兼容。

重要的如果你在本地运行这个服务器,请启用DNS重绑定保护:

const transport = new StreamableHTTPServerTransport({
  sessionIdGenerator: () => randomUUID(),
  enableDnsRebindingProtection: true,

  allowedHosts: ['127.0.0.1', ...],
  allowedOrigins: ['https://yourdomain.com', 'https://www.yourdomain.com']
});

stdio(在中文语境中,通常直接使用原词,不翻译,指标准输入输出库)

对于由另一个进程启动的本地集成,您可以使用stdio传输方式:

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);

测试与调试

要测试您的服务器,您可以使用 MCP 检查器查看其README文件以获取更多信息。

示例

回声服务器

一个展示资源、工具和提示的简单服务器:

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.registerTool(
    'echo',
    {
        title: 'Echo Tool',
        description: 'Echoes back the provided message',
        inputSchema: { message: z.string() },
        outputSchema: { echo: z.string() }
    },
    async ({ message }) => {
        const output = { echo: `Tool echo: ${message}` };
        return {
            content: [{ type: 'text', text: JSON.stringify(output) }],
            structuredContent: output
        };
    }
);

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.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(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() },
        outputSchema: {
            rows: z.array(z.record(z.any())),
            rowCount: z.number()
        }
    },
    async ({ sql }) => {
        const db = getDb();
        try {
            const results = await db.all(sql);
            const output = { rows: results, rowCount: results.length };
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(output, null, 2)
                    }
                ],
                structuredContent: output
            };
        } 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 { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import express from 'express';
import { z } from 'zod';

const server = new McpServer({
    name: 'Dynamic Example',
    version: '1.0.0'
});

const listMessageTool = server.registerTool(
    'listMessages',
    {
        title: 'List Messages',
        description: 'List messages in a channel',
        inputSchema: { channel: z.string() },
        outputSchema: { messages: z.array(z.string()) }
    },
    async ({ channel }) => {
        const messages = await listMessages(channel);
        const output = { messages };
        return {
            content: [{ type: 'text', text: JSON.stringify(output) }],
            structuredContent: output
        };
    }
);

const putMessageTool = server.registerTool(
    'putMessage',
    {
        title: 'Put Message',
        description: 'Send a message to a channel',
        inputSchema: { channel: z.string(), message: z.string() },
        outputSchema: { success: z.boolean() }
    },
    async ({ channel, message }) => {
        await putMessage(channel, message);
        const output = { success: true };
        return {
            content: [{ type: 'text', text: JSON.stringify(output) }],
            structuredContent: output
        };
    }
);
// Until we upgrade auth, `putMessage` is disabled (won't show up in listTools)
putMessageTool.disable();

const upgradeAuthTool = server.registerTool(
    'upgradeAuth',
    {
        title: 'Upgrade Authorization',
        description: 'Upgrade user authorization level',
        inputSchema: { permission: z.enum(['write', 'admin']) },
        outputSchema: {
            success: z.boolean(),
            newPermission: z.string()
        }
    },
    // 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}` }],
                isError: true
            };
        }

        // 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({
                paramsSchema: { 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();
        }

        const output = { success: true, newPermission: permission };
        return {
            content: [{ type: 'text', text: JSON.stringify(output) }],
            structuredContent: output
        };
    }
);

// Connect with HTTP transport
const app = express();
app.use(express.json());

app.post('/mcp', async (req, res) => {
    const transport = new StreamableHTTPServerTransport({
        sessionIdGenerator: undefined,
        enableJsonResponse: true
    });

    res.on('close', () => {
        transport.close();
    });

    await server.connect(transport);
    await transport.handleRequest(req, res, req.body);
});

const port = parseInt(process.env.PORT || '3000');
app.listen(port, () => {
    console.log(`MCP Server running on http://localhost:${port}/mcp`);
});

通过防抖技术提高网络效率

在执行会触发通知的批量更新操作时(例如,在循环中启用或禁用多个工具),SDK 可能在短时间内发送大量消息。为了提高性能并减少网络流量,您可以启用通知防抖功能。

这个功能将多次快速发出的相同通知类型的调用合并为一条消息。例如,如果你连续禁用了五个工具,那么只会收到一条 notifications/tools/list_changed 将发送一条信息,而非五条。

\[!重要\] 此功能旨在用于“简单”通知,这些通知的参数中不包含唯一数据。为防止数据静默丢失,已采用防抖处理 自动绕过 对于任何包含(某内容)的通知 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.

低级别服务器

为了获得更精细的控制,您可以直接使用低级别的 Server 类:

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);

获取用户输入

MCP服务器可以通过提示功能向用户请求额外信息。这对于需要服务器获取用户输入或确认的交互式工作流非常有用:

// Server-side: Restaurant booking tool that asks for alternatives
server.registerTool(
    'book-restaurant',
    {
        title: 'Book Restaurant',
        description: 'Book a table at a restaurant',
        inputSchema: {
            restaurant: z.string(),
            date: z.string(),
            partySize: z.number()
        },
        outputSchema: {
            success: z.boolean(),
            booking: z
                .object({
                    restaurant: z.string(),
                    date: z.string(),
                    partySize: z.number()
                })
                .optional(),
            alternatives: z.array(z.string()).optional()
        }
    },
    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);
                const output = { success: false, alternatives };
                return {
                    content: [
                        {
                            type: 'text',
                            text: JSON.stringify(output)
                        }
                    ],
                    structuredContent: output
                };
            }

            const output = { success: false };
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(output)
                    }
                ],
                structuredContent: output
            };
        }

        // Book the table
        await makeBooking(restaurant, date, partySize);
        const output = {
            success: true,
            booking: { restaurant, date, partySize }
        };
        return {
            content: [
                {
                    type: 'text',
                    text: JSON.stringify(output)
                }
            ],
            structuredContent: output
        };
    }
);

客户端:处理引出请求

// This is a placeholder - implement based on your UI framework
async function getInputFromUser(
    message: string,
    schema: any
): Promise;
}> {
    // 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
    };
});

引出(或获取)信息需要客户的支持。客户必须声明 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流程的控制

向后兼容性

使用StreamableHttp传输的客户端和服务器可以保持 向后兼容性 随着以下已弃用的HTTP+SSE传输方式(自协议版本2024-11-05起)

客户端兼容性

对于需要同时与Streamable 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,
    sse: {} as Record
};

// 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 上,欢迎提交问题和拉取请求,网址为 。

许可证

这个项目采用MIT许可证授权——详见 许可证 详情请见文件。

目录标签

目录标签

上下文管理协议实现TypeScriptClaudeAI开发工具LLM集成本地部署TypeScript工具

支持客户端

ClaudeCursorVS Code

接入字段

传输方式(transport,传输协议)

未说明

鉴权方式(authType,认证方式)

oauth

工具数量(toolCount,工具数)

6

资源数量(resourceCount,资源数)

0

提示词数量(promptCount,提示词数)

0

权限和风险

未说明oauth部署方式未说明

接入前请确认传输方式、认证方式和部署位置,并根据实际工具能力限制访问范围。

安装前确认

不要直接授予不必要的文件、网络或账号权限;先核对安装命令和配置内容。

仍需确认:installCommand

来源信息

继续浏览同类 MCP