主题
TypeScript SDK
MCP TypeScript SDK 提供了构建 MCP 服务器和客户端的完整工具集,支持现代 TypeScript/JavaScript 开发模式。
安装
使用 npm
bash
npm install @modelcontextprotocol/sdk
使用 yarn
bash
yarn add @modelcontextprotocol/sdk
使用 pnpm
bash
pnpm add @modelcontextprotocol/sdk
核心组件
1. 服务器 SDK
基础服务器
typescript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListResourcesRequestSchema,
ListToolsRequestSchema,
ReadResourceRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
// 创建服务器实例
const server = new Server(
{
name: 'my-typescript-server',
version: '1.0.0',
},
{
capabilities: {
resources: {},
tools: {},
prompts: {},
},
}
);
// 资源处理器
server.setRequestHandler(ListResourcesRequestSchema, async () => {
return {
resources: [
{
uri: 'file://config.json',
name: '配置文件',
description: '应用程序配置',
mimeType: 'application/json',
},
{
uri: 'file://data.txt',
name: '数据文件',
description: '应用程序数据',
mimeType: 'text/plain',
},
],
};
});
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const { uri } = request.params;
switch (uri) {
case 'file://config.json':
return {
contents: [
{
uri,
mimeType: 'application/json',
text: JSON.stringify({
app: 'my-app',
version: '1.0.0',
debug: true,
}, null, 2),
},
],
};
case 'file://data.txt':
return {
contents: [
{
uri,
mimeType: 'text/plain',
text: '这是一些示例数据\n包含多行内容',
},
],
};
default:
throw new Error(`未知资源: ${uri}`);
}
});
// 工具处理器
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'calculate',
description: '执行数学计算',
inputSchema: {
type: 'object',
properties: {
expression: {
type: 'string',
description: '要计算的数学表达式',
},
},
required: ['expression'],
},
},
{
name: 'format_text',
description: '格式化文本',
inputSchema: {
type: 'object',
properties: {
text: {
type: 'string',
description: '要格式化的文本',
},
format: {
type: 'string',
enum: ['uppercase', 'lowercase', 'title'],
description: '格式化类型',
},
},
required: ['text', 'format'],
},
},
],
};
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
switch (name) {
case 'calculate': {
const { expression } = args as { expression: string };
try {
// 注意:在生产环境中应使用安全的表达式求值器
const result = eval(expression);
return {
content: [
{
type: 'text',
text: `计算结果: ${result}`,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `计算错误: ${error.message}`,
},
],
isError: true,
};
}
}
case 'format_text': {
const { text, format } = args as { text: string; format: string };
let result: string;
switch (format) {
case 'uppercase':
result = text.toUpperCase();
break;
case 'lowercase':
result = text.toLowerCase();
break;
case 'title':
result = text.replace(/\w\S*/g, (txt) =>
txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()
);
break;
default:
throw new Error(`不支持的格式: ${format}`);
}
return {
content: [
{
type: 'text',
text: `格式化结果: ${result}`,
},
],
};
}
default:
throw new Error(`未知工具: ${name}`);
}
});
// 启动服务器
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('MCP TypeScript 服务器已启动');
}
main().catch((error) => {
console.error('服务器启动失败:', error);
process.exit(1);
});
高级服务器功能
typescript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListResourcesRequestSchema,
ListToolsRequestSchema,
ReadResourceRequestSchema,
ListPromptsRequestSchema,
GetPromptRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import * as fs from 'fs/promises';
import * as path from 'path';
interface ServerConfig {
name: string;
version: string;
dataDir: string;
cacheEnabled: boolean;
logLevel: 'debug' | 'info' | 'warn' | 'error';
}
class AdvancedMCPServer {
private server: Server;
private config: ServerConfig;
private cache = new Map<string, { data: any; timestamp: number }>();
private readonly CACHE_TTL = 5 * 60 * 1000; // 5 分钟
constructor(config: ServerConfig) {
this.config = config;
this.server = new Server(
{
name: config.name,
version: config.version,
},
{
capabilities: {
resources: {},
tools: {},
prompts: {},
logging: {},
},
}
);
this.setupHandlers();
}
private log(level: string, message: string, ...args: any[]) {
const levels = ['debug', 'info', 'warn', 'error'];
const configLevel = levels.indexOf(this.config.logLevel);
const messageLevel = levels.indexOf(level);
if (messageLevel >= configLevel) {
console.error(`[${level.toUpperCase()}] ${message}`, ...args);
}
}
private async getCached<T>(key: string): Promise<T | null> {
if (!this.config.cacheEnabled) return null;
const cached = this.cache.get(key);
if (!cached) return null;
const now = Date.now();
if (now - cached.timestamp > this.CACHE_TTL) {
this.cache.delete(key);
return null;
}
this.log('debug', `缓存命中: ${key}`);
return cached.data;
}
private setCache<T>(key: string, data: T): void {
if (!this.config.cacheEnabled) return;
this.cache.set(key, {
data,
timestamp: Date.now(),
});
this.log('debug', `缓存设置: ${key}`);
}
private setupHandlers() {
// 资源处理器
this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
this.log('info', '列出资源');
const cacheKey = 'resources_list';
const cached = await this.getCached<any[]>(cacheKey);
if (cached) {
return { resources: cached };
}
try {
const resources = [];
const dataDir = this.config.dataDir;
// 扫描数据目录
try {
const files = await fs.readdir(dataDir, { withFileTypes: true });
for (const file of files) {
if (file.isFile()) {
const filePath = path.join(dataDir, file.name);
const stats = await fs.stat(filePath);
resources.push({
uri: `file://${filePath}`,
name: file.name,
description: `文件: ${file.name} (${this.formatFileSize(stats.size)})`,
mimeType: this.getMimeType(file.name),
});
}
}
} catch (error) {
this.log('warn', `无法读取数据目录: ${error.message}`);
}
// 添加 API 资源
resources.push(
{
uri: 'api://system/info',
name: '系统信息',
description: '获取系统信息',
mimeType: 'application/json',
},
{
uri: 'api://weather/current',
name: '当前天气',
description: '获取当前天气信息',
mimeType: 'application/json',
}
);
this.setCache(cacheKey, resources);
return { resources };
} catch (error) {
this.log('error', '列出资源失败:', error);
throw error;
}
});
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const { uri } = request.params;
this.log('info', `读取资源: ${uri}`);
const cacheKey = `resource_${uri}`;
const cached = await this.getCached<any>(cacheKey);
if (cached) {
return cached;
}
try {
let content: any;
if (uri.startsWith('file://')) {
const filePath = uri.slice(7); // 移除 "file://" 前缀
const fileContent = await fs.readFile(filePath, 'utf-8');
content = {
contents: [
{
uri,
mimeType: this.getMimeType(filePath),
text: fileContent,
},
],
};
} else if (uri.startsWith('api://')) {
content = await this.handleApiResource(uri);
} else {
throw new Error(`不支持的 URI 方案: ${uri}`);
}
this.setCache(cacheKey, content);
return content;
} catch (error) {
this.log('error', `读取资源失败 ${uri}:`, error);
throw error;
}
});
// 工具处理器
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
this.log('info', '列出工具');
return {
tools: [
{
name: 'file_search',
description: '在文件中搜索文本',
inputSchema: {
type: 'object',
properties: {
pattern: {
type: 'string',
description: '搜索模式(支持正则表达式)',
},
directory: {
type: 'string',
description: '搜索目录',
default: this.config.dataDir,
},
fileExtensions: {
type: 'array',
items: { type: 'string' },
description: '文件扩展名过滤器',
default: ['.txt', '.md', '.json'],
},
},
required: ['pattern'],
},
},
{
name: 'http_request',
description: '发送 HTTP 请求',
inputSchema: {
type: 'object',
properties: {
url: {
type: 'string',
description: '请求 URL',
},
method: {
type: 'string',
enum: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
default: 'GET',
},
headers: {
type: 'object',
description: '请求头',
},
body: {
type: 'string',
description: '请求体',
},
},
required: ['url'],
},
},
{
name: 'data_transform',
description: '数据转换工具',
inputSchema: {
type: 'object',
properties: {
data: {
type: 'string',
description: '要转换的数据',
},
fromFormat: {
type: 'string',
enum: ['json', 'csv', 'xml', 'yaml'],
description: '源格式',
},
toFormat: {
type: 'string',
enum: ['json', 'csv', 'xml', 'yaml'],
description: '目标格式',
},
},
required: ['data', 'fromFormat', 'toFormat'],
},
},
],
};
});
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
this.log('info', `调用工具: ${name}`, args);
try {
switch (name) {
case 'file_search':
return await this.handleFileSearch(args as any);
case 'http_request':
return await this.handleHttpRequest(args as any);
case 'data_transform':
return await this.handleDataTransform(args as any);
default:
throw new Error(`未知工具: ${name}`);
}
} catch (error) {
this.log('error', `工具调用失败 ${name}:`, error);
return {
content: [
{
type: 'text',
text: `错误: ${error.message}`,
},
],
isError: true,
};
}
});
// 提示处理器
this.server.setRequestHandler(ListPromptsRequestSchema, async () => {
this.log('info', '列出提示');
return {
prompts: [
{
name: 'code_review',
description: '代码审查提示',
arguments: [
{
name: 'code',
description: '要审查的代码',
required: true,
},
{
name: 'language',
description: '编程语言',
required: false,
},
{
name: 'focus',
description: '审查重点',
required: false,
},
],
},
{
name: 'data_analysis',
description: '数据分析提示',
arguments: [
{
name: 'dataset',
description: '数据集描述',
required: true,
},
{
name: 'questions',
description: '要回答的问题',
required: false,
},
],
},
],
};
});
this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
this.log('info', `获取提示: ${name}`, args);
switch (name) {
case 'code_review': {
const { code, language = 'typescript', focus = '最佳实践' } = args as any;
return {
messages: [
{
role: 'user',
content: {
type: 'text',
text: `请审查以下 ${language} 代码,重点关注${focus}:
\`\`\`${language}
${code}
\`\`\`
请提供:
1. 代码质量评估
2. 潜在问题和改进建议
3. 最佳实践建议
4. 性能优化建议(如适用)`,
},
},
],
};
}
case 'data_analysis': {
const { dataset, questions = '基本统计信息和趋势' } = args as any;
return {
messages: [
{
role: 'user',
content: {
type: 'text',
text: `请分析以下数据集:
数据集描述:${dataset}
分析要求:
- ${questions}
- 识别关键模式和趋势
- 提供数据洞察和建议
- 如果发现异常值,请说明
请提供详细的分析报告。`,
},
},
],
};
}
default:
throw new Error(`未知提示: ${name}`);
}
});
}
private async handleApiResource(uri: string): Promise<any> {
if (uri === 'api://system/info') {
return {
contents: [
{
uri,
mimeType: 'application/json',
text: JSON.stringify({
platform: process.platform,
arch: process.arch,
nodeVersion: process.version,
uptime: process.uptime(),
memoryUsage: process.memoryUsage(),
timestamp: new Date().toISOString(),
}, null, 2),
},
],
};
}
if (uri === 'api://weather/current') {
// 模拟天气 API
return {
contents: [
{
uri,
mimeType: 'application/json',
text: JSON.stringify({
location: '北京',
temperature: 22,
humidity: 65,
condition: '晴朗',
windSpeed: 5,
timestamp: new Date().toISOString(),
}, null, 2),
},
],
};
}
throw new Error(`未知 API 资源: ${uri}`);
}
private async handleFileSearch(args: {
pattern: string;
directory?: string;
fileExtensions?: string[];
}) {
const { pattern, directory = this.config.dataDir, fileExtensions = ['.txt', '.md', '.json'] } = args;
const results: string[] = [];
const regex = new RegExp(pattern, 'gi');
try {
const files = await fs.readdir(directory, { withFileTypes: true });
for (const file of files) {
if (file.isFile() && fileExtensions.some(ext => file.name.endsWith(ext))) {
const filePath = path.join(directory, file.name);
try {
const content = await fs.readFile(filePath, 'utf-8');
const matches = content.match(regex);
if (matches) {
results.push(`${file.name}: 找到 ${matches.length} 个匹配`);
// 显示匹配的行
const lines = content.split('\n');
lines.forEach((line, index) => {
if (regex.test(line)) {
results.push(` 第 ${index + 1} 行: ${line.trim()}`);
}
});
}
} catch (error) {
this.log('warn', `无法读取文件 ${filePath}: ${error.message}`);
}
}
}
} catch (error) {
throw new Error(`搜索失败: ${error.message}`);
}
return {
content: [
{
type: 'text',
text: results.length > 0
? `搜索结果:\n${results.join('\n')}`
: '未找到匹配的内容',
},
],
};
}
private async handleHttpRequest(args: {
url: string;
method?: string;
headers?: Record<string, string>;
body?: string;
}) {
const { url, method = 'GET', headers = {}, body } = args;
try {
const response = await fetch(url, {
method,
headers,
body: body ? body : undefined,
});
const responseText = await response.text();
return {
content: [
{
type: 'text',
text: JSON.stringify({
status: response.status,
statusText: response.statusText,
headers: Object.fromEntries(response.headers.entries()),
body: responseText,
}, null, 2),
},
],
};
} catch (error) {
throw new Error(`HTTP 请求失败: ${error.message}`);
}
}
private async handleDataTransform(args: {
data: string;
fromFormat: string;
toFormat: string;
}) {
const { data, fromFormat, toFormat } = args;
try {
// 解析源数据
let parsedData: any;
switch (fromFormat) {
case 'json':
parsedData = JSON.parse(data);
break;
case 'csv':
// 简单的 CSV 解析
const lines = data.trim().split('\n');
const headers = lines[0].split(',');
parsedData = lines.slice(1).map(line => {
const values = line.split(',');
return headers.reduce((obj, header, index) => {
obj[header.trim()] = values[index]?.trim() || '';
return obj;
}, {} as any);
});
break;
default:
throw new Error(`不支持的源格式: ${fromFormat}`);
}
// 转换为目标格式
let result: string;
switch (toFormat) {
case 'json':
result = JSON.stringify(parsedData, null, 2);
break;
case 'csv':
if (Array.isArray(parsedData) && parsedData.length > 0) {
const headers = Object.keys(parsedData[0]);
const csvLines = [
headers.join(','),
...parsedData.map(row => headers.map(header => row[header] || '').join(','))
];
result = csvLines.join('\n');
} else {
result = '';
}
break;
default:
throw new Error(`不支持的目标格式: ${toFormat}`);
}
return {
content: [
{
type: 'text',
text: `转换结果 (${fromFormat} -> ${toFormat}):\n\n${result}`,
},
],
};
} catch (error) {
throw new Error(`数据转换失败: ${error.message}`);
}
}
private getMimeType(filename: string): string {
const ext = path.extname(filename).toLowerCase();
const mimeTypes: Record<string, string> = {
'.json': 'application/json',
'.txt': 'text/plain',
'.md': 'text/markdown',
'.html': 'text/html',
'.css': 'text/css',
'.js': 'text/javascript',
'.ts': 'text/typescript',
'.xml': 'application/xml',
'.csv': 'text/csv',
};
return mimeTypes[ext] || 'application/octet-stream';
}
private formatFileSize(bytes: number): string {
const units = ['B', 'KB', 'MB', 'GB'];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(1)} ${units[unitIndex]}`;
}
async start() {
try {
const transport = new StdioServerTransport();
await this.server.connect(transport);
this.log('info', `${this.config.name} v${this.config.version} 已启动`);
} catch (error) {
this.log('error', '服务器启动失败:', error);
throw error;
}
}
}
// 使用示例
const config: ServerConfig = {
name: 'advanced-typescript-server',
version: '1.0.0',
dataDir: './data',
cacheEnabled: true,
logLevel: 'info',
};
const server = new AdvancedMCPServer(config);
server.start().catch((error) => {
console.error('启动失败:', error);
process.exit(1);
});
2. 客户端 SDK
基础客户端
typescript
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
async function basicClient() {
// 创建客户端
const client = new Client(
{
name: 'my-client',
version: '1.0.0',
},
{
capabilities: {},
}
);
// 创建传输
const transport = new StdioClientTransport({
command: 'node',
args: ['server.js'],
});
try {
// 连接到服务器
await client.connect(transport);
console.log('已连接到服务器');
// 列出资源
const resourcesResult = await client.request(
{ method: 'resources/list' },
{ method: 'resources/list' }
);
console.log('可用资源:', resourcesResult.resources);
// 读取资源
if (resourcesResult.resources.length > 0) {
const resource = resourcesResult.resources[0];
const readResult = await client.request(
{ method: 'resources/read', params: { uri: resource.uri } },
{ method: 'resources/read' }
);
console.log('资源内容:', readResult.contents);
}
// 列出工具
const toolsResult = await client.request(
{ method: 'tools/list' },
{ method: 'tools/list' }
);
console.log('可用工具:', toolsResult.tools);
// 调用工具
if (toolsResult.tools.length > 0) {
const tool = toolsResult.tools[0];
const callResult = await client.request(
{
method: 'tools/call',
params: {
name: tool.name,
arguments: { expression: '5 + 3' },
},
},
{ method: 'tools/call' }
);
console.log('工具结果:', callResult.content);
}
} catch (error) {
console.error('客户端错误:', error);
} finally {
await client.close();
}
}
// 运行客户端
basicClient().catch(console.error);
高级客户端
typescript
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
interface ServerConnection {
name: string;
client: Client;
transport: StdioClientTransport | SSEClientTransport;
capabilities?: {
resources: any[];
tools: any[];
prompts: any[];
};
}
class AdvancedMCPClient {
private connections = new Map<string, ServerConnection>();
private requestTimeout = 30000; // 30 秒超时
async connectStdioServer(
name: string,
command: string,
args: string[] = []
): Promise<void> {
const client = new Client(
{
name: `client-${name}`,
version: '1.0.0',
},
{
capabilities: {
sampling: {},
},
}
);
const transport = new StdioClientTransport({
command,
args,
});
try {
await client.connect(transport);
console.log(`已连接到 STDIO 服务器: ${name}`);
const connection: ServerConnection = {
name,
client,
transport,
};
// 缓存服务器能力
await this.cacheCapabilities(connection);
this.connections.set(name, connection);
} catch (error) {
console.error(`连接 STDIO 服务器失败 ${name}:`, error);
throw error;
}
}
async connectSSEServer(name: string, url: string): Promise<void> {
const client = new Client(
{
name: `client-${name}`,
version: '1.0.0',
},
{
capabilities: {
sampling: {},
},
}
);
const transport = new SSEClientTransport(new URL(url));
try {
await client.connect(transport);
console.log(`已连接到 SSE 服务器: ${name}`);
const connection: ServerConnection = {
name,
client,
transport,
};
// 缓存服务器能力
await this.cacheCapabilities(connection);
this.connections.set(name, connection);
} catch (error) {
console.error(`连接 SSE 服务器失败 ${name}:`, error);
throw error;
}
}
private async cacheCapabilities(connection: ServerConnection): Promise<void> {
try {
const [resourcesResult, toolsResult, promptsResult] = await Promise.all([
this.safeRequest(connection.client, { method: 'resources/list' }),
this.safeRequest(connection.client, { method: 'tools/list' }),
this.safeRequest(connection.client, { method: 'prompts/list' }),
]);
connection.capabilities = {
resources: resourcesResult?.resources || [],
tools: toolsResult?.tools || [],
prompts: promptsResult?.prompts || [],
};
console.log(`已缓存服务器能力 ${connection.name}:`, {
resources: connection.capabilities.resources.length,
tools: connection.capabilities.tools.length,
prompts: connection.capabilities.prompts.length,
});
} catch (error) {
console.warn(`缓存服务器能力失败 ${connection.name}:`, error);
}
}
private async safeRequest(client: Client, request: any): Promise<any> {
try {
return await Promise.race([
client.request(request, request),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('请求超时')), this.requestTimeout)
),
]);
} catch (error) {
console.warn('请求失败:', error);
return null;
}
}
async getServerInfo(serverName: string): Promise<any> {
const connection = this.connections.get(serverName);
if (!connection) {
throw new Error(`服务器 ${serverName} 未连接`);
}
return {
name: connection.name,
connected: true,
capabilities: connection.capabilities,
};
}
async listAllResources(): Promise<Record<string, any[]>> {
const result: Record<string, any[]> = {};
for (const [name, connection] of this.connections) {
try {
const resourcesResult = await this.safeRequest(connection.client, {
method: 'resources/list',
});
result[name] = resourcesResult?.resources || [];
} catch (error) {
console.warn(`获取资源列表失败 ${name}:`, error);
result[name] = [];
}
}
return result;
}
async readResource(serverName: string, uri: string): Promise<any> {
const connection = this.connections.get(serverName);
if (!connection) {
throw new Error(`服务器 ${serverName} 未连接`);
}
try {
const result = await this.safeRequest(connection.client, {
method: 'resources/read',
params: { uri },
});
return result;
} catch (error) {
console.error(`读取资源失败 ${serverName}/${uri}:`, error);
throw error;
}
}
async callTool(
serverName: string,
toolName: string,
arguments_: Record<string, any>
): Promise<any> {
const connection = this.connections.get(serverName);
if (!connection) {
throw new Error(`服务器 ${serverName} 未连接`);
}
try {
const result = await this.safeRequest(connection.client, {
method: 'tools/call',
params: {
name: toolName,
arguments: arguments_,
},
});
return result;
} catch (error) {
console.error(`调用工具失败 ${serverName}/${toolName}:`, error);
throw error;
}
}
async getPrompt(
serverName: string,
promptName: string,
arguments_: Record<string, any> = {}
): Promise<any> {
const connection = this.connections.get(serverName);
if (!connection) {
throw new Error(`服务器 ${serverName} 未连接`);
}
try {
const result = await this.safeRequest(connection.client, {
method: 'prompts/get',
params: {
name: promptName,
arguments: arguments_,
},
});
return result;
} catch (error) {
console.error(`获取提示失败 ${serverName}/${promptName}:`, error);
throw error;
}
}
async executeWorkflow(workflow: Array<{
server: string;
type: 'resource' | 'tool' | 'prompt';
name: string;
params?: Record<string, any>;
}>): Promise<any[]> {
const results = [];
for (const step of workflow) {
try {
let result;
switch (step.type) {
case 'resource':
result = await this.readResource(step.server, step.name);
break;
case 'tool':
result = await this.callTool(step.server, step.name, step.params || {});
break;
case 'prompt':
result = await this.getPrompt(step.server, step.name, step.params || {});
break;
default:
throw new Error(`未知步骤类型: ${step.type}`);
}
results.push({
step,
result,
success: true,
});
} catch (error) {
results.push({
step,
error: error.message,
success: false,
});
}
}
return results;
}
async batchOperations(operations: Array<{
server: string;
type: 'resource' | 'tool';
name: string;
params?: Record<string, any>;
}>): Promise<any[]> {
const promises = operations.map(async (op) => {
try {
switch (op.type) {
case 'resource':
return await this.readResource(op.server, op.name);
case 'tool':
return await this.callTool(op.server, op.name, op.params || {});
default:
throw new Error(`未知操作类型: ${op.type}`);
}
} catch (error) {
return { error: error.message };
}
});
return await Promise.all(promises);
}
async monitorServer(serverName: string, interval = 5000): Promise<void> {
const connection = this.connections.get(serverName);
if (!connection) {
throw new Error(`服务器 ${serverName} 未连接`);
}
console.log(`开始监控服务器: ${serverName}`);
const monitor = async () => {
try {
// 检查连接状态
const resourcesResult = await this.safeRequest(connection.client, {
method: 'resources/list',
});
if (resourcesResult) {
console.log(`服务器 ${serverName} 状态正常, 资源数: ${resourcesResult.resources?.length || 0}`);
} else {
console.warn(`服务器 ${serverName} 响应异常`);
}
} catch (error) {
console.error(`监控服务器失败 ${serverName}:`, error);
}
setTimeout(monitor, interval);
};
monitor();
}
async closeConnection(serverName: string): Promise<void> {
const connection = this.connections.get(serverName);
if (!connection) {
console.warn(`服务器 ${serverName} 未连接`);
return;
}
try {
await connection.client.close();
this.connections.delete(serverName);
console.log(`已关闭连接: ${serverName}`);
} catch (error) {
console.error(`关闭连接失败 ${serverName}:`, error);
}
}
async closeAllConnections(): Promise<void> {
const closePromises = Array.from(this.connections.keys()).map(name =>
this.closeConnection(name)
);
await Promise.all(closePromises);
console.log('已关闭所有连接');
}
}
// 使用示例
async function advancedClientExample() {
const client = new AdvancedMCPClient();
try {
// 连接多个服务器
await client.connectStdioServer('file-server', 'node', ['file-server.js']);
await client.connectStdioServer('calc-server', 'node', ['calc-server.js']);
// 获取服务器信息
const fileServerInfo = await client.getServerInfo('file-server');
console.log('文件服务器信息:', fileServerInfo);
// 列出所有资源
const allResources = await client.listAllResources();
console.log('所有资源:', allResources);
// 执行工作流
const workflow = [
{
server: 'file-server',
type: 'resource' as const,
name: 'file://config.json',
},
{
server: 'calc-server',
type: 'tool' as const,
name: 'calculate',
params: { expression: '10 + 5' },
},
];
const workflowResults = await client.executeWorkflow(workflow);
console.log('工作流结果:', workflowResults);
// 批量操作
const operations = [
{
server: 'calc-server',
type: 'tool' as const,
name: 'calculate',
params: { expression: '2 * 3' },
},
{
server: 'calc-server',
type: 'tool' as const,
name: 'calculate',
params: { expression: '8 / 2' },
},
];
const batchResults = await client.batchOperations(operations);
console.log('批量操作结果:', batchResults);
// 开始监控(在实际应用中,您可能想要在后台运行)
// await client.monitorServer('file-server');
} catch (error) {
console.error('客户端错误:', error);
} finally {
await client.closeAllConnections();
}
}
// 运行示例
advancedClientExample().catch(console.error);
类型定义
核心类型
typescript
// 从 SDK 导入类型
import {
Resource,
Tool,
Prompt,
TextContent,
ImageContent,
EmbeddedResource,
McpError,
CallToolRequest,
CallToolResult,
ListResourcesResult,
ReadResourceResult,
GetPromptResult,
} from '@modelcontextprotocol/sdk/types.js';
// 自定义类型扩展
interface ExtendedResource extends Resource {
tags?: string[];
lastModified?: string;
size?: number;
}
interface ExtendedTool extends Tool {
category?: string;
examples?: Array<{
description: string;
arguments: Record<string, any>;
expectedResult: string;
}>;
}
interface ExtendedPrompt extends Prompt {
category?: string;
tags?: string[];
examples?: Array<{
arguments: Record<string, any>;
description: string;
}>;
}
// 服务器配置类型
interface ServerConfig {
name: string;
version: string;
description?: string;
capabilities: {
resources?: boolean;
tools?: boolean;
prompts?: boolean;
logging?: boolean;
};
limits?: {
maxRequestSize?: number;
maxResponseSize?: number;
requestTimeout?: number;
};
}
// 客户端配置类型
interface ClientConfig {
name: string;
version: string;
timeout?: number;
retryAttempts?: number;
retryDelay?: number;
}
// 错误处理类型
interface ErrorResponse {
error: {
code: number;
message: string;
data?: any;
};
}
// 工作流类型
interface WorkflowStep {
id: string;
type: 'resource' | 'tool' | 'prompt';
server: string;
name: string;
params?: Record<string, any>;
dependsOn?: string[];
}
interface WorkflowResult {
stepId: string;
success: boolean;
result?: any;
error?: string;
duration: number;
}
错误处理
自定义错误类
typescript
import { McpError } from '@modelcontextprotocol/sdk/types.js';
class CustomMcpError extends McpError {
constructor(
message: string,
code: number,
public readonly context?: Record<string, any>
) {
super(code, message);
this.name = 'CustomMcpError';
}
toJSON() {
return {
name: this.name,
message: this.message,
code: this.code,
context: this.context,
};
}
}
// 错误处理中间件
function withErrorHandling<T extends any[], R>(
fn: (...args: T) => Promise<R>
): (...args: T) => Promise<R> {
return async (...args: T): Promise<R> => {
try {
return await fn(...args);
} catch (error) {
if (error instanceof McpError) {
// 重新抛出 MCP 错误
throw error;
}
// 转换为 MCP 错误
if (error instanceof Error) {
throw new CustomMcpError(
error.message,
-32603, // 内部错误
{ originalError: error.name }
);
}
// 未知错误
throw new CustomMcpError(
'未知错误',
-32603,
{ originalError: String(error) }
);
}
};
}
// 使用示例
const safeToolCall = withErrorHandling(async (name: string, args: any) => {
if (name === 'divide') {
const { a, b } = args;
if (typeof a !== 'number' || typeof b !== 'number') {
throw new CustomMcpError(
'参数必须是数字',
-32602, // 无效参数
{ providedTypes: { a: typeof a, b: typeof b } }
);
}
if (b === 0) {
throw new CustomMcpError(
'除数不能为零',
-32602,
{ operation: 'division', divisor: b }
);
}
return a / b;
}
throw new CustomMcpError(
`未知工具: ${name}`,
-32601 // 方法未找到
);
});
测试
Jest 测试配置
json
// package.json
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
},
"devDependencies": {
"@types/jest": "^29.5.0",
"jest": "^29.5.0",
"ts-jest": "^29.1.0"
},
"jest": {
"preset": "ts-jest",
"testEnvironment": "node",
"roots": ["<rootDir>/src", "<rootDir>/tests"],
"testMatch": ["**/__tests__/**/*.ts", "**/?(*.)+(spec|test).ts"],
"collectCoverageFrom": [
"src/**/*.ts",
"!src/**/*.d.ts"
]
}
}
单元测试示例
typescript
// tests/server.test.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
describe('MCP Server', () => {
let server: Server;
beforeEach(() => {
server = new Server(
{ name: 'test-server', version: '1.0.0' },
{ capabilities: { tools: {} } }
);
});
describe('工具处理', () => {
beforeEach(() => {
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'add',
description: '加法运算',
inputSchema: {
type: 'object',
properties: {
a: { type: 'number' },
b: { type: 'number' },
},
required: ['a', 'b'],
},
},
],
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === 'add') {
const { a, b } = args as { a: number; b: number };
return {
content: [
{
type: 'text',
text: String(a + b),
},
],
};
}
throw new Error(`未知工具: ${name}`);
});
});
test('应该列出可用工具', async () => {
const response = await server.request(
{ method: 'tools/list' },
ListToolsRequestSchema
);
expect(response.tools).toHaveLength(1);
expect(response.tools[0].name).toBe('add');
});
test('应该正确调用工具', async () => {
const response = await server.request(
{
method: 'tools/call',
params: {
name: 'add',
arguments: { a: 2, b: 3 },
},
},
CallToolRequestSchema
);
expect(response.content).toHaveLength(1);
expect(response.content[0].text).toBe('5');
});
test('应该处理未知工具错误', async () => {
await expect(
server.request(
{
method: 'tools/call',
params: {
name: 'unknown',
arguments: {},
},
},
CallToolRequestSchema
)
).rejects.toThrow('未知工具: unknown');
});
});
});
集成测试
typescript
// tests/integration.test.ts
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { spawn, ChildProcess } from 'child_process';
describe('MCP 集成测试', () => {
let serverProcess: ChildProcess;
let client: Client;
beforeAll(async () => {
// 启动服务器进程
serverProcess = spawn('node', ['dist/server.js'], {
stdio: ['pipe', 'pipe', 'pipe'],
});
// 等待服务器启动
await new Promise(resolve => setTimeout(resolve, 1000));
// 创建客户端
client = new Client(
{ name: 'test-client', version: '1.0.0' },
{ capabilities: {} }
);
const transport = new StdioClientTransport({
command: 'node',
args: ['dist/server.js'],
});
await client.connect(transport);
});
afterAll(async () => {
if (client) {
await client.close();
}
if (serverProcess) {
serverProcess.kill();
}
});
test('应该能够列出和调用工具', async () => {
// 列出工具
const toolsResponse = await client.request(
{ method: 'tools/list' },
{ method: 'tools/list' }
);
expect(toolsResponse.tools.length).toBeGreaterThan(0);
// 调用工具
const callResponse = await client.request(
{
method: 'tools/call',
params: {
name: toolsResponse.tools[0].name,
arguments: { expression: '1 + 1' },
},
},
{ method: 'tools/call' }
);
expect(callResponse.content).toBeDefined();
});
});
部署
构建配置
json
// tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"allowJs": true,
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "tests"]
}
json
// package.json
{
"name": "my-mcp-server",
"version": "1.0.0",
"type": "module",
"main": "dist/server.js",
"scripts": {
"build": "tsc",
"start": "node dist/server.js",
"dev": "tsc --watch",
"test": "jest",
"lint": "eslint src/**/*.ts",
"format": "prettier --write src/**/*.ts"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"typescript": "^5.0.0",
"eslint": "^8.0.0",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"prettier": "^3.0.0"
}
}
Docker 部署
dockerfile
# Dockerfile
FROM node:20-alpine
WORKDIR /app
# 复制 package 文件
COPY package*.json ./
# 安装依赖
RUN npm ci --only=production
# 复制源代码
COPY . .
# 构建应用
RUN npm run build
# 暴露端口(如果使用 HTTP 传输)
EXPOSE 3000
# 启动命令
CMD ["npm", "start"]
yaml
# docker-compose.yml
version: '3.8'
services:
mcp-server:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- LOG_LEVEL=info
volumes:
- ./data:/app/data
restart: unless-stopped
healthcheck:
test: ["CMD", "node", "healthcheck.js"]
interval: 30s
timeout: 10s
retries: 3
最佳实践
1. 性能优化
typescript
// 连接池管理
class ConnectionPool {
private connections = new Map<string, Client>();
private maxConnections = 10;
async getConnection(serverName: string): Promise<Client> {
if (this.connections.has(serverName)) {
return this.connections.get(serverName)!;
}
if (this.connections.size >= this.maxConnections) {
// 移除最旧的连接
const oldestKey = this.connections.keys().next().value;
const oldestConnection = this.connections.get(oldestKey);
await oldestConnection?.close();
this.connections.delete(oldestKey);
}
// 创建新连接
const client = new Client(
{ name: `client-${serverName}`, version: '1.0.0' },
{ capabilities: {} }
);
// 配置连接...
this.connections.set(serverName, client);
return client;
}
}
// 请求缓存
class RequestCache {
private cache = new Map<string, { data: any; timestamp: number }>();
private ttl = 5 * 60 * 1000; // 5 分钟
get(key: string): any | null {
const cached = this.cache.get(key);
if (!cached) return null;
if (Date.now() - cached.timestamp > this.ttl) {
this.cache.delete(key);
return null;
}
return cached.data;
}
set(key: string, data: any): void {
this.cache.set(key, {
data,
timestamp: Date.now(),
});
}
}
2. 错误恢复
typescript
class ResilientClient {
private maxRetries = 3;
private retryDelay = 1000;
async withRetry<T>(operation: () => Promise<T>): Promise<T> {
let lastError: Error;
for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error as Error;
if (attempt === this.maxRetries) {
break;
}
console.warn(`操作失败,第 ${attempt} 次重试:`, error);
await this.delay(this.retryDelay * attempt);
}
}
throw lastError!;
}
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
下一步
- 查看 Python SDK - 了解 Python 实现
- 构建服务器 - 深入服务器开发
- 构建客户端 - 深入客户端开发
- 查看示例 - 学习实际应用