基于MCP TypeScript SDK 手搓一个 MCP Server!
让你可以构建服务器,以安全且标准化的方式向大语言模型(LLM)应用程序暴露数据和功能。你可以把它想象成一个 Web API,但它是专门为 LLM 交互而设计的。通过 Resources 暴露数据(可以把它们看作是 GET 接口;它们用于将信息加载到 LLM 的上下文中)通过 Tools 提供功能(类似于 POST 接口;它们用于执行代码或产生其他副作用)通过 Prompts 定义交互模式(提供可重
基于MCP TypeScript SDK 手搓一个 MCP Server!
概述
MCP即Model Context Protocol(模型上下文协议)允许应用程序以标准化的方式为大语言模型(LLM)提供上下文,将提供上下文的关注点与实际的LLM交互分离开来。使用MCP的TypeScript SDK 实现了完整的MCP规范,可以轻松实现以下功能:
- 构建能够连接到任何MCP服务器的MCP客户端
- 创建并暴露资源、提示和工具的MCP服务器
- 使用标准传输方式,例如stdio 和 Streamable HTTP
- 处理所有MCP协议消息和生命周期事件
安装
npm install @modelcontextprotocol/sdk
快速开始
我们来创建一个简单的MCP服务器(mcpServer.js),它暴露一个计算BMI的工具和一些数据。
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',
})
// 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)),
},
],
})
)
// Add a dynamic greeting resource
server.resource(
'greeting',
new ResourceTemplate('greeting://{name}', { list: undefined }),
async (uri, { name }) => ({
contents: [
{
uri: uri.href,
text: `Hello, ${name}!`,
},
],
})
)
server.prompt('review-code', { code: z.string() }, ({ code }) => ({
messages: [
{
role: 'user',
content: {
type: 'text',
text: `Please review this code:\n\n${code}`,
},
},
],
}))
// Start receiving messages on stdin and sending messages on stdout
const transport = new StdioServerTransport()
await server.connect(transport)
什么是 MCP?
Model Context Protocol (MCP) 让你可以构建服务器,以安全且标准化的方式向大语言模型(LLM)应用程序暴露数据和功能。你可以把它想象成一个 Web API,但它是专门为 LLM 交互而设计的。
MCP 服务器可以:
-
通过 Resources 暴露数据 (可以把它们看作是 GET 接口;它们用于将信息加载到 LLM 的上下文中)
-
通过 Tools 提供功能 (类似于 POST 接口;它们用于执行代码或产生其他副作用)
-
通过 Prompts 定义交互模式 (提供可重复使用的模板,帮助 LLM 更高效地进行交互)
核心概念
Server
McpServer 是你与 MCP 协议交互的核心接口。它负责处理连接管理、协议合规性以及消息路由
const server = new McpServer({
name: "My App",
version: "1.0.0"
});
Resources
Resources 是你向大语言模型(LLM)暴露数据的方式。它们类似于 REST API 中的 GET 接口——用于提供数据,但不应执行复杂的计算或产生副作用。
// 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允许大语言模型(LLM)通过你的服务器执行操作。与 Resources 不同,Tools 通常会进行计算并产生副作用:
// 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(是可重复使用的模板,有助于大语言模型(LLM)更高效地与你的服务器进行交互:
server.prompt(
"review-code",
{ code: z.string() },
({ code }) => ({
messages: [{
role: "user",
content: {
type: "text",
text: `Please review this code:\n\n${code}`
}
}]
})
);
运行服务
TypeScript 中的 MCP 服务器需要通过(transport)与客户端进行通信。启动服务器的方式取决于你选择的传输协议:
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);
Streamable HTTP
对于远程服务器,可以设置一个 Streamable 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;
}
});
// 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);
实现 MCP Clients
该 SDK 提供了一个高级的客户端接口,基于Client实现一个客户端(mcpClient.ts),用来测试和调试 Server提供的相关功能:
iimport { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
const transport = new StdioClientTransport({
command: 'node',
args: ['mcpServer.js'],
})
const client = new Client({
name: 'example-client',
version: '1.0.0',
})
await client.connect(transport)
// List prompts
const prompts = await client.listPrompts()
console.log(prompts)
const prompt = await client.getPrompt({
name: 'review-code',
arguments: {
code: 'console.log("Hello world")',
},
})
console.log('prompt:', prompt)
// List resources
const resources = await client.listResources()
console.log(resources)
// Read a resource
const resource = await client.readResource({
uri: 'greeting://john',
})
console.log('resource:', resource)
// Call a tool
const result = await client.callTool({
name: 'calculate-bmi',
arguments: {
weightKg: 70,
heightM: 1.7,
},
})
console.log('tool:', result)
运行客户端,调用Server提供的功能
npx tsx mcpClient.ts
运行结果如下
{ prompts: [ { name: 'review-code', arguments: [Array] } ] }
prompt: { messages: [ { role: 'user', content: [Object] } ] }
{ resources: [] }
resource: { contents: [ { uri: 'greeting://john', text: 'Hello, john!' } ] }
tool: { content: [ { type: 'text', text: '24.221453287197235' } ] }
参考文档
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)