Skip to content

使用 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 包:

Terminal window
# 使用 npm
npm install @modelcontext/mcp-sdk
# 或者使用 yarn
yarn add @modelcontext/mcp-sdk

要开始与 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('连接已关闭。');
}
}

连接后,您的 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: MCPClient
try {
// 示例: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);
}
}

这是一个更完整的示例,演示了如何连接到服务器、发出一些请求,然后干净地断开连接。这假设一个 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();
  • 优雅的错误处理: 始终对连接尝试和所有 MCP 操作实施全面的错误处理。区分 MCPError 和其他错误类型。
  • 资源管理: 当不再需要客户端时,确保调用 client.close()(通常在 finally 块中),以释放资源。
  • 类型安全: 利用 TypeScript 的泛型(例如,client.requestResource<MyPayloadType>(...))为服务器响应定义类型,以提高代码智能和安全性。
  • 异步操作: 正确使用 async/await 来管理 MCP 通信的异步特性,防止回调地狱并提高代码可读性。
  • 大数据流式传输: 对于可能返回大量数据或持续更新(如日志或 LLM 令牌流)的资源或提示,首选 streamResource 和 streamPrompt 方法以提高响应速度并减少内存占用。
  • 客户端能力: 如果您的客户端有特定需求或提供特定功能,请在调用 createClient 时,在 clientDescription 或 capabilities 选项中声明它们。
  • 服务器能力感知: 连接后(如果服务器提供)检查 client.serverCapabilities,以了解服务器在发出请求前提供哪些功能。这有助于构建更动态和健壮的客户端。