使用 TypeScript SDK 开发 MCP 客户端
MCP 客户端开发 - 使用 TypeScript SDK
Section titled “MCP 客户端开发 - 使用 TypeScript SDK”本指南提供了详细的步骤和代码示例,旨在帮助您使用官方 TypeScript SDK 开发 MCP(模型上下文协议)客户端。MCP 客户端与 MCP 服务器交互,以利用其公开的资源(Resources)、提示(Prompts)和工具(Tools),从而支持丰富、上下文感知的应用程序。
TypeScript SDK 提供了类型安全、现代 JavaScript 特性(包括 async/await),非常适合 Node.js 环境,并可能适用于基于浏览器的 MCP 客户端。
先决条件:
- Node.js(推荐 18.x 或更高版本)。
- 包管理器,例如 npm(Node.js 自带)或 yarn。
安装:
使用 npm 或 yarn 安装 MCP TypeScript SDK 包:
# 使用 npmnpm install @modelcontext/mcp-sdk
# 或者使用 yarnyarn add @modelcontext/mcp-sdk连接到 MCP 服务器
Section titled “连接到 MCP 服务器”要开始与 MCP 服务器交互,您首先需要建立连接。SDK 的 createClient 函数会初始化一个 MCPClient 实例,然后您可以使用该实例进行连接。
import { createClient, MCPClient } from '@modelcontext/mcp-sdk';
async function connectToServer() { let client: MCPClient | undefined; try { client = createClient({ uri: 'ws://localhost:8080/mcp', // 替换为您的 MCP 服务器的 WebSocket URI clientId: 'my-typescript-client-01', clientDescription: 'An example MCP client using the TypeScript SDK', // 如果需要,您还可以在此处声明客户端能力 });
console.log('正在连接到 MCP 服务器...'); await client.connect(); console.log('成功连接到 MCP 服务器!');
// 此时,您可以与服务器交互。 // 例如,如果连接时提供了服务器能力,则可以记录它们: // console.log('Server capabilities:', client.serverCapabilities);
} catch (error) { console.error('连接 MCP 服务器失败:', error); } finally { // 我们将在执行操作后关闭客户端。 // if (client && client.isConnected()) { // await client.close(); // console.log('Connection closed.'); // } } return client; // 返回以便后续使用}
// 示例用法(客户端将由其他函数处理):// connectToServer();在与服务器交互完成后,务必正确关闭连接,以释放客户端和服务器两端的资源。请使用 client.close() 方法。
// 假设 'client' 是从 connectToServer() 获取的活跃 MCPClient 实例async function closeConnection(client: MCPClient) { if (client && client.isConnected()) { console.log('正在关闭连接...'); await client.close(); console.log('连接已关闭。'); }}与服务器能力交互
Section titled “与服务器能力交互”连接后,您的 MCP 客户端可以请求服务器公开的资源(Resources)、利用提示(Prompts)和调用工具(Tools)。TypeScript SDK 为这些交互提供了直观的方法。
请求资源(Resources): 资源(Resources)提供对数据的访问。使用 client.requestResource<PayloadType>(resourceId, params) 获取单个数据响应,或使用 client.streamResource<PayloadType>(resourceId, params) 接收数据流。
// 假设 'client' 是一个活跃且已连接的 MCPClient 实例
// 请求资源(例如,获取文件内容)async function getResource(client: MCPClient) { try { // 如果已知,定义预期的负载结构 interface FileContentPayload { content: string; encoding?: string; }
const resourceResponse = await client.requestResource<FileContentPayload>( 'fileAccess', { path: '/example/document.txt' } ); console.log('资源 (fileAccess) 响应:', resourceResponse.payload.content); } catch (error) { console.error('请求资源时出错:', error); }}
// 流式传输资源(例如,跟踪日志)async function streamLogResource(client: MCPClient) { try { interface LogChunkPayload { chunk: string; timestamp: string; }
const stream = client.streamResource<LogChunkPayload>( 'logStream', { source: '/var/log/app.log', follow: true } ); console.log('正在启动日志流...'); for await (const update of stream) { console.log(`日志 [${update.payload.timestamp}]: ${update.payload.chunk}`); // 如果您的用例需要,添加一个条件来中断循环 if (update.payload.chunk.includes('STREAM_END_MARKER')) { console.log('收到结束标记,正在关闭流。'); break; } } console.log('日志流已完成或已关闭。'); } catch (error) { console.error('流式传输资源时出错:', error); }}利用提示(Prompts): 提示(Prompts)通常用于与大型语言模型(LLM)或其他生成式系统交互。使用 client.requestPrompt<PayloadType>(promptId, params) 获取单个完成响应,或使用 client.streamPrompt<PayloadType>(promptId, params) 进行逐令牌流式传输。
// 假设 'client' 是一个活跃且已连接的 MCPClient 实例
// 请求提示(例如,总结文本)async function summarizeText(client: MCPClient, textToSummarize: string) { try { interface SummaryPayload { summary: string; tokensUsed?: number; }
const promptResponse = await client.requestPrompt<SummaryPayload>( 'textSummarizer', { text: textToSummarize, maxLength: 100 } ); console.log('摘要:', promptResponse.payload.summary); } catch (error) { console.error('请求提示时出错:', error); }}
// 流式传输提示(例如,生成故事)async function streamStory(client: MCPClient, theme: string) { try { interface StoryTokenPayload { token: string; sequence: number; } let fullStory = ''; const stream = client.streamPrompt<StoryTokenPayload>( 'storyGenerator', { theme: theme, chapters: 1 } ); console.log(`正在为主题生成故事:${theme}...`); for await (const update of stream) { fullStory += update.payload.token; process.stdout.write(update.payload.token); // 实时输出 } console.log('\n故事生成完成。'); // console.log('完整故事:', fullStory); } catch (error) { console.error('流式传输提示时出错:', error); }}调用工具(Tools): 工具(Tools)是服务器公开的函数,客户端可以执行它们。使用 client.requestTool<PayloadType>(toolId, params)。
// 假设 'client' 是一个活跃且已连接的 MCPClient 实例
// 调用工具(例如,计算器)async function useCalculatorTool(client: MCPClient, operation: string, a: number, b: number) { try { interface CalcResultPayload { result: number; details?: string; }
const toolResponse = await client.requestTool<CalcResultPayload>( 'calculator', { operation: operation, operand1: a, operand2: b } ); console.log(`计算器结果 (${operation}):`, toolResponse.payload.result); } catch (error) { console.error('调用工具时出错:', error); }}网络问题或服务器端问题可能导致错误。将 MCP 操作包装在 try...catch 块中至关重要。SDK 可能会抛出 MCPError 实例,其中包含来自服务器的更具体的错误代码和消息,或者针对连接问题的标准 JavaScript 错误。
import { MCPError } from '@modelcontext/mcp-sdk'; // 确保已导入 MCPError
// 在使用 'client' 的异步函数内部// client: MCPClienttry { // 示例:const data = await client.requestResource('someResource', {}); // ... 执行其他操作 ...} catch (error) { if (error instanceof MCPError) { // 处理特定的 MCP 协议错误 console.error(`MCP 错误(代码:${error.code}):${error.message}`, error.data); } else if (error instanceof Error) { // 处理其他 JavaScript 错误(例如,MCP 握手前的网络错误) console.error('通用客户端错误:', error.message); } else { // 处理任何其他意外错误 console.error('发生未知错误:', error); }}完整客户端示例
Section titled “完整客户端示例”这是一个更完整的示例,演示了如何连接到服务器、发出一些请求,然后干净地断开连接。这假设一个 MCP 服务器正在 ws://localhost:8080/mcp 运行,并公开了一个名为 status 的资源和一个名为 echo 的提示。
import { createClient, MCPClient, MCPError } from '@modelcontext/mcp-sdk';
async function runFullClient() { let client: MCPClient | undefined; try { client = createClient({ uri: 'ws://localhost:8080/mcp', // 标准 MCP 路径 clientId: 'ts-full-client-example', });
console.log('正在尝试连接...'); await client.connect(); console.log('已连接!服务器能力:', client.serverCapabilities || '未指定');
// 1. 请求 'status' 资源(假设它不带参数并返回 {payload: {message: string}}) console.log("\n正在请求 'status' 资源..."); const statusRes = await client.requestResource<{ message: string }>('status', {}); console.log('服务器状态:', statusRes.payload.message);
// 2. 请求 'echo' 提示(假设它带 {text: string} 参数并返回 {payload: {echoed: string}}) const textToEcho = '来自 TypeScript 客户端的问候!'; console.log(`\n正在请求 'echo' 提示,文本为:"${textToEcho}"`); const echoRes = await client.requestPrompt<{ echoed: string }>('echo', { text: textToEcho }); console.log('服务器回显:', echoRes.payload.echoed);
// 3. 调用 'calculator' 工具(假设它带 {op: string, a: number, b: number} 参数并返回 {payload: {result: number}}) console.log("\n正在调用 'calculator' 工具进行 10 加 5 的运算..."); const calcRes = await client.requestTool<{result: number}>('calculator', {op: 'add', a: 10, b: 5}); console.log('计算结果:', calcRes.payload.result);
} catch (error) { if (error instanceof MCPError) { console.error(`MCP 通信错误(代码:${error.code}):${error.message}`, error.data || ''); } else if (error instanceof Error) { console.error('应用程序错误:', error.message, error.stack); } else { console.error('发生未知错误:', error); } } finally { if (client && client.isConnected()) { console.log('\n正在关闭客户端连接...'); await client.close(); console.log('客户端连接已关闭。'); } }}
runFullClient();TypeScript MCP 客户端的最佳实践
Section titled “TypeScript MCP 客户端的最佳实践”- 优雅的错误处理: 始终对连接尝试和所有 MCP 操作实施全面的错误处理。区分
MCPError和其他错误类型。 - 资源管理: 当不再需要客户端时,确保调用
client.close()(通常在finally块中),以释放资源。 - 类型安全: 利用 TypeScript 的泛型(例如,
client.requestResource<MyPayloadType>(...))为服务器响应定义类型,以提高代码智能和安全性。 - 异步操作: 正确使用
async/await来管理 MCP 通信的异步特性,防止回调地狱并提高代码可读性。 - 大数据流式传输: 对于可能返回大量数据或持续更新(如日志或 LLM 令牌流)的资源或提示,首选
streamResource和streamPrompt方法以提高响应速度并减少内存占用。 - 客户端能力: 如果您的客户端有特定需求或提供特定功能,请在调用
createClient时,在clientDescription或capabilities选项中声明它们。 - 服务器能力感知: 连接后(如果服务器提供)检查
client.serverCapabilities,以了解服务器在发出请求前提供哪些功能。这有助于构建更动态和健壮的客户端。