Skip to content

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));
  }
}

下一步

🚀 探索模型上下文协议的无限可能 | 连接 AI 与世界的桥梁 | 让智能更智能