【MCP Node.js SDK 全栈进阶指南】初级篇(6):MCP传输层配置与使用
前言
在前面的系列文章中,我们已经学习了MCP的基础环境搭建、服务器开发、资源开发、工具开发以及提示模板开发等核心内容。这些都是构建MCP应用的基础组件和功能模块。然而,要让这些组件和模块能够高效、可靠地协同工作,我们还需要深入了解MCP的传输层机制,它是连接客户端与服务器、实现数据通信的关键环节。
MCP SDK提供了多种传输机制来满足不同场景下的应用需求。在本篇文章中,我们将深入探讨MCP传输层的配置与使用,涵盖stdio、HTTP传输设置、流式数据传输等内容,并提供详细的实例代码和故障排除技巧,帮助开发者充分理解和利用MCP的传输层能力。
1. MCP传输层概述
在深入具体的传输方式之前,我们需要先了解MCP传输层的基本概念和架构设计。
1.1 传输层在MCP中的角色
MCP传输层是整个架构中的核心组成部分,它负责客户端与服务器之间的所有通信,包括:
- 请求与响应的传递
- 消息格式化与解析
- 连接管理与会话维护
- 错误处理与重试机制
- 数据流控制
MCP采用了模块化的设计理念,传输层被设计为可插拔的组件,这意味着开发者可以根据不同的应用场景选择或自定义最适合的传输方式。
1.2 MCP支持的传输类型
MCP TypeScript-SDK目前主要支持以下几种传输类型:
- stdio传输:通过标准输入输出流进行通信,适合命令行工具和本地应用
- HTTP传输:基于HTTP/HTTPS协议的通信方式,适合Web应用和分布式系统
- WebSocket传输:提供持久连接的双向通信通道,适合需要实时交互的应用
- 自定义传输:开发者可以根据特定需求实现自定义的传输层
每种传输方式都有其适用场景和优缺点,我们将在接下来的章节中详细讨论。
1.3 传输层的基本工作流程
无论使用哪种传输方式,MCP传输层的基本工作流程都遵循类似的模式:
客户端 --[请求]--> 传输层 --[格式化]--> 服务器
客户端 <--[响应]-- 传输层 <--[处理]---- 服务器
这一流程包括以下关键步骤:
- 客户端发起请求,传递给传输层
- 传输层将请求格式化为MCP协议兼容的格式
- 请求通过网络或进程间通信发送到服务器
- 服务器处理请求并生成响应
- 响应通过传输层返回给客户端
- 传输层解析响应并传递给客户端应用
在这个过程中,传输层负责处理连接建立、消息序列化/反序列化、错误处理等细节,使开发者可以专注于业务逻辑的实现。
2. stdio传输配置与使用场景
2.1 什么是stdio传输
stdio(标准输入/输出)传输是MCP提供的最基本的传输方式之一,它通过进程的标准输入和标准输出流来实现通信。在这种模式下:
- 通过
process.stdin
接收请求数据 - 通过
process.stdout
发送响应数据
stdio传输的特点是简单、直接,不需要网络配置,适合在本地环境中运行的应用,特别是命令行工具和桌面应用。
2.2 stdio传输的配置方法
在MCP TypeScript-SDK中,配置stdio传输非常简单。以下是基本步骤:
服务器端配置
import { McpServer, StdioTransport } from '@mcp/server';// 创建stdio传输实例
const transport = new StdioTransport();// 创建服务器并配置传输层
const server = new McpServer({transport,// 其他服务器配置...
});// 启动服务器
server.start();
客户端配置
import { McpClient, StdioTransport } from '@mcp/client';// 创建stdio传输实例
const transport = new StdioTransport();// 创建客户端并配置传输层
const client = new McpClient({transport,// 其他客户端配置...
});// 连接到服务器
await client.connect();// 使用客户端发送请求
const response = await client.request('some/resource', { param: 'value' });
2.3 高级配置选项
除了基本配置外,stdio传输还支持一些高级选项:
const transport = new StdioTransport({// 自定义输入流input: customInputStream,// 自定义输出流output: customOutputStream,// 是否启用调试日志debug: true,// 消息分隔符(默认为换行符)delimiter: '\n',// 输入编码encoding: 'utf8'
});
这些选项使开发者可以根据具体需求自定义stdio传输的行为。
2.4 stdio传输的适用场景
stdio传输主要适用于以下场景:
-
命令行工具:当构建CLI工具时,stdio传输是理想的选择
-
本地插件系统:主应用与插件之间的通信可以通过stdio实现
-
进程间通信:当需要在同一设备上的不同进程之间通信时
-
开发与测试环境:由于配置简单,适合在开发和测试阶段使用
2.5 实际应用示例:构建MCP命令行助手
以下是一个使用stdio传输构建简单命令行AI助手的示例:
// mcp-cli-assistant.ts
import { McpServer, StdioTransport } from '@mcp/server';
import { z } from 'zod';// 创建stdio传输
const transport = new StdioTransport();// 创建服务器
const server = new McpServer({transport,serviceConfig: {name: 'MCP CLI Assistant',version: '1.0.0',description: '一个简单的MCP命令行助手'}
});// 注册工具
server.registerTool({name: 'calculate',description: '执行基本的数学计算',parameters: z.object({expression: z.string().describe('要计算的数学表达式')}),handler: async ({ expression }) => {try {// 注意:在实际应用中应采用更安全的计算方法return { result: eval(expression) };} catch (error) {return { error: '计算表达式时出错' };}}
});// 注册文本生成资源
server.registerTemplate('respond', `你是一个友好的命令行助手。用户消息: {{message}}请提供简洁明了的回答。如果需要计算,使用calculate工具。`
);// 处理用户输入
process.stdin.on('data', async (data) => {const userMessage = data.toString().trim();if (userMessage.toLowerCase() === 'exit') {console.log('再见!');process.exit(0);}try {const response = await server.generateText('respond', {message: userMessage});console.log(`\n${response}\n`);} catch (error) {console.error('处理请求时出错:', error);}process.stdout.write('> ');
});// 启动服务器
server.start().then(() => {console.log('MCP CLI助手已启动');console.log('输入问题或输入"exit"退出');process.stdout.write('> ');
});
运行这个脚本后,用户可以在命令行中与AI助手进行交互,获取响应或执行计算。
2.6 stdio传输的局限性
虽然stdio传输简单易用,但它也有一些局限性:
- 仅限本地通信:无法直接用于跨网络、跨设备的通信
- 单会话限制:一个进程只能建立一个stdio传输连接
- 生命周期限制:传输的生命周期与进程绑定
- 流控制:在处理大量数据时需要特别注意流控制
在需要更复杂的网络通信或更高级功能时,应考虑使用HTTP或WebSocket等其他传输方式。
3. HTTP传输设置与会话管理
相比于stdio传输的本地化特性,HTTP传输提供了更强大的网络通信能力,使MCP应用能够在分布式环境中运行,为Web应用和API服务提供支持。
3.1 HTTP传输基础
HTTP传输基于HTTP/HTTPS协议,使用RESTful风格的API进行通信。在MCP中,HTTP传输主要有以下特点:
- 通过HTTP请求和响应传递MCP消息
- 支持同步和异步通信模式
- 提供会话管理机制
- 支持身份验证和授权
- 可配置为使用HTTP或HTTPS
3.2 HTTP传输的配置方法
以下是在MCP TypeScript-SDK中配置HTTP传输的基本步骤:
服务器端配置
import { McpServer, HttpTransport } from '@mcp/server';// 创建HTTP传输实例
const transport = new HttpTransport({port: 3000, // 服务器端口host: 'localhost', // 主机地址path: '/mcp', // API路径前缀cors: true, // 是否启用CORShttps: false // 是否使用HTTPS
});// 创建服务器并配置传输层
const server = new McpServer({transport,// 其他服务器配置...
});// 启动服务器
await server.start();
console.log(`MCP服务器已启动,监听端口: ${transport.options.port}`);
客户端配置
import { McpClient, HttpTransport } from '@mcp/client';// 创建HTTP传输实例
const transport = new HttpTransport({baseUrl: 'http://localhost:3000/mcp', // 服务器地址headers: { // 自定义请求头'Authorization': 'Bearer YOUR_TOKEN'},timeout: 30000 // 请求超时时间(毫秒)
});// 创建客户端并配置传输层
const client = new McpClient({transport,// 其他客户端配置...
});// 连接到服务器
await client.connect();// 使用客户端发送请求
const response = await client.request('some/resource', { param: 'value' });
3.3 HTTPS配置
在生产环境中,使用HTTPS对通信进行加密是保障安全的必要措施。以下是配置HTTPS传输的示例:
import fs from 'fs';
import path from 'path';
import { McpServer, HttpTransport } from '@mcp/server';// 加载SSL证书
const sslOptions = {key: fs.readFileSync(path.join(__dirname, 'ssl/private-key.pem')),cert: fs.readFileSync(path.join(__dirname, 'ssl/certificate.pem'))
};// 创建HTTPS传输实例
const transport = new HttpTransport({port: 443,host: 'example.com',path: '/api/mcp',https: true,httpsOptions: sslOptions
});// 创建服务器并配置传输层
const server = new McpServer({transport,// 其他配置...
});// 启动服务器
await server.start();
console.log('MCP HTTPS服务器已启动');
3.4 会话管理
HTTP是无状态协议,但在许多应用场景中我们需要维护会话状态。MCP的HTTP传输提供了多种会话管理选项:
基于令牌的会话管理
import { McpServer, HttpTransport } from '@mcp/server';
import jwt from 'jsonwebtoken';// 会话存储
const sessions = new Map();// 创建HTTP传输实例
const transport = new HttpTransport({port: 3000,// 请求处理中间件middleware: [// 身份验证中间件(req, res, next) => {// 跳过登录路径的身份验证if (req.path === '/mcp/login') {return next();}const authHeader = req.headers.authorization;if (!authHeader || !authHeader.startsWith('Bearer ')) {res.status(401).json({ error: '未授权访问' });return;}const token = authHeader.substring(7);try {// 验证令牌const decoded = jwt.verify(token, 'your-secret-key');req.user = decoded;// 查找会话const sessionId = decoded.sessionId;if (!sessions.has(sessionId)) {res.status(401).json({ error: '会话已过期' });return;}req.session = sessions.get(sessionId);next();} catch (error) {res.status(401).json({ error: '无效的令牌' });}}]
});// 创建服务器
const server = new McpServer({transport,// 其他配置...
});// 注册登录处理程序
transport.expressApp.post('/mcp/login', (req, res) => {const { username, password } = req.body;// 验证凭据(实际应用中应使用安全的认证方法)if (username === 'admin' && password === 'password') {// 创建会话const sessionId = Date.now().toString();const session = {id: sessionId,username,createdAt: new Date(),data: {}};// 存储会话sessions.set(sessionId, session);// 生成令牌const token = jwt.sign({