技术分析

为什么现有的 MCP SDK 不够用?开发者的视角

官方 MCP SDK 很强大,但它不是为快速开发设计的。让我们探讨它的局限性,以及为什么像 @zhama/mcp-server 这样的框架对于高效的 AI 工具开发至关重要。

Zhama AI 团队
20 min read
为什么现有的 MCP SDK 不够用?开发者的视角

让我先声明一点:官方的模型上下文协议 SDK 非常优秀。它设计良好,精确遵循规范,并让你完全控制协议。Anthropic 团队在创建一个坚实的 MCP 开发基础方面做得非常出色。

但问题是:技术上正确并不意味着对开发者友好。

Zhama 为生产环境构建了数十个 MCP 服务器之后,我发现了当前 SDK 的六个根本问题,这些问题使它不适合真实世界的快速开发。这不是为了批评而批评 - 这是对我们为什么构建 @zhama/mcp-server 的诚实分析,以及为什么你可能也需要它。

核心问题:协议优先 vs 开发者优先的设计

官方 MCP SDK 是协议优先的。它旨在让你直接访问模型上下文协议的原语:请求、响应、处理器、传输层。如果你要:

  • 为 MCP 构建基础设施
  • 创建自己的抽象层
  • 实现自定义协议扩展
  • 调试协议级问题

这很好。

但如果你要:

  • 快速构建业务工具
  • 专注于应用逻辑
  • 维护多个工具
  • 让新开发者快速上手
  • 在紧迫的期限下工作

这就很糟糕

让我准确展示我的意思。

问题 #1:学习曲线太陡峭

你需要学习什么(官方 SDK)

在你可以构建一个简单的计算器工具之前,你需要理解:

  1. 服务器初始化和配置

    const server = new Server({
      name: 'my-server',
      version: '1.0.0'
    }, {
      capabilities: {
        tools: {},
        resources: {},
        prompts: {}
      }
    });
    
  2. 请求模式类型

    import {
      CallToolRequestSchema,
      ListToolsRequestSchema,
      ListResourcesRequestSchema,
      ReadResourceRequestSchema
    } from '@modelcontextprotocol/sdk/types.js';
    
  3. 请求处理器注册

    server.setRequestHandler(ListToolsRequestSchema, async () => {
      // 返回工具定义
    });
    
    server.setRequestHandler(CallToolRequestSchema, async (request) => {
      // 执行工具逻辑
    });
    
  4. 用于输入验证的 JSON Schema

    inputSchema: {
      type: 'object',
      properties: {
        param1: { type: 'string' },
        param2: { type: 'number' }
      },
      required: ['param1', 'param2']
    }
    
  5. 响应格式化

    return {
      content: [{
        type: 'text',
        text: JSON.stringify(result)
      }]
    };
    
  6. 传输层设置

    const transport = new StdioServerTransport();
    await server.connect(transport);
    

这至少是 6 个不同的概念,仅仅是为了构建一个 "Hello World" 工具。

你需要学习什么(@zhama/mcp-server)

import { createMCPServer, BaseTool, Tool } from '@zhama/mcp-server';

@Tool({
  name: 'hello',
  description: '打招呼',
  parameters: [
    { name: 'name', type: 'string', required: true }
  ]
})
class HelloTool extends BaseTool {
  protected toolDefinition = {
    name: 'hello',
    description: '打招呼',
    parameters: []
  };

  protected async executeInternal(params: Record<string, unknown>): Promise<unknown> {
    const { name } = params as { name: string };
    return { greeting: `你好,${name}!` };
  }
}

createMCPServer('hello-server', '1.0.0')
  .enableTools()
  .addTool(new HelloTool())
  .runStdio();

两个概念:装饰器和执行方法。其他一切都自动处理。

影响

使用官方 SDK,你的第一个工具需要 2-4 小时(包括学习时间)。使用 @zhama/mcp-server,只需要 5-10 分钟

这不是一个小差异 - 这是第一天生产力提高 24 倍

问题 #2:样板代码爆炸

让我们用两种方法构建相同的功能,并计算代码行数。

任务:创建天气工具

官方 SDK 实现:156 行

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
  Tool as MCPTool
} from '@modelcontextprotocol/sdk/types.js';

// 服务器设置
const server = new Server({
  name: 'weather-server',
  version: '1.0.0'
}, {
  capabilities: {
    tools: {}
  }
});

// 工具定义
const weatherToolDefinition: MCPTool = {
  name: 'get_weather',
  description: '获取城市的当前天气',
  inputSchema: {
    type: 'object',
    properties: {
      city: {
        type: 'string',
        description: '城市名称'
      },
      units: {
        type: 'string',
        description: '温度单位',
        enum: ['celsius', 'fahrenheit'],
        default: 'celsius'
      }
    },
    required: ['city']
  }
};

// 列出工具处理器
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [weatherToolDefinition]
  };
});

// 执行工具处理器
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name !== 'get_weather') {
    throw new Error(`未知工具:${request.params.name}`);
  }

  const { city, units = 'celsius' } = request.params.arguments as {
    city: string;
    units?: string;
  };

  try {
    // 输入验证
    if (!city || typeof city !== 'string') {
      throw new Error('城市必须是非空字符串');
    }

    if (units && !['celsius', 'fahrenheit'].includes(units)) {
      throw new Error('单位必须是 celsius 或 fahrenheit');
    }

    // 获取天气数据
    const apiKey = process.env.WEATHER_API_KEY;
    if (!apiKey) {
      throw new Error('WEATHER_API_KEY 未配置');
    }

    const tempUnit = units === 'fahrenheit' ? 'imperial' : 'metric';
    const response = await fetch(
      `https://api.openweathermap.org/data/2.5/weather?q=${encodeURIComponent(city)}&appid=${apiKey}&units=${tempUnit}`
    );

    if (!response.ok) {
      if (response.status === 404) {
        throw new Error(`未找到城市:${city}`);
      }
      throw new Error(`天气 API 错误:${response.status}`);
    }

    const data = await response.json();

    // 格式化结果
    const result = {
      city: data.name,
      country: data.sys.country,
      temperature: data.main.temp,
      feels_like: data.main.feels_like,
      condition: data.weather[0].main,
      description: data.weather[0].description,
      humidity: data.main.humidity,
      wind_speed: data.wind.speed,
      units: units
    };

    return {
      content: [{
        type: 'text',
        text: JSON.stringify(result, null, 2)
      }]
    };
  } catch (error) {
    // 错误处理
    const errorMessage = error instanceof Error ? error.message : '未知错误';
    console.error('天气工具错误:', errorMessage);
    
    return {
      content: [{
        type: 'text',
        text: JSON.stringify({
          error: errorMessage
        })
      }],
      isError: true
    };
  }
});

// 服务器启动
async function main() {
  try {
    const transport = new StdioServerTransport();
    await server.connect(transport);
    console.error('天气 MCP 服务器运行在 stdio');
  } catch (error) {
    console.error('启动服务器失败:', error);
    process.exit(1);
  }
}

main();

@zhama/mcp-server 实现:47 行

import { createMCPServer, BaseTool, Tool } from '@zhama/mcp-server';
import fetch from 'node-fetch';

@Tool({
  name: 'get_weather',
  description: '获取城市的当前天气',
  parameters: [
    { 
      name: 'city', 
      type: 'string', 
      description: '城市名称', 
      required: true 
    },
    { 
      name: 'units', 
      type: 'string', 
      description: 'celsius 或 fahrenheit', 
      required: false 
    }
  ]
})
class WeatherTool extends BaseTool {
  protected toolDefinition = {
    name: 'get_weather',
    description: '获取天气信息',
    parameters: []
  };

  protected async executeInternal(params: Record<string, unknown>): Promise<unknown> {
    const { city, units = 'celsius' } = params as { 
      city: string; 
      units?: string 
    };

    const apiKey = process.env.WEATHER_API_KEY;
    if (!apiKey) throw new Error('WEATHER_API_KEY 未配置');

    const tempUnit = units === 'fahrenheit' ? 'imperial' : 'metric';
    const response = await fetch(
      `https://api.openweathermap.org/data/2.5/weather?q=${encodeURIComponent(city)}&appid=${apiKey}&units=${tempUnit}`
    );

    if (!response.ok) {
      if (response.status === 404) throw new Error(`未找到城市:${city}`);
      throw new Error(`天气 API 错误:${response.status}`);
    }

    const data = await response.json();

    return {
      city: data.name,
      country: data.sys.country,
      temperature: data.main.temp,
      feels_like: data.main.feels_like,
      condition: data.weather[0].main,
      description: data.weather[0].description,
      humidity: data.main.humidity,
      wind_speed: data.wind.speed,
      units
    };
  }
}

createMCPServer('weather-server', '1.0.0')
  .enableTools()
  .enableLogging('info')
  .addTool(new WeatherTool())
  .runStdio();

结果:代码减少 70% - 而且 @zhama/mcp-server 版本更易读。

样板代码税

使用官方 SDK 构建的每个工具都需要:

  • 服务器配置:约 10 行
  • 模式定义:约 15-30 行
  • 处理器注册:约 5 行
  • 请求路由:约 5-10 行
  • 响应格式化:约 5-10 行
  • 错误处理:约 10-20 行
  • 传输层设置:约 5 行

每个工具有 55-90 行样板代码。

使用 @zhama/mcp-server:**0 行样板代码。**只需编写业务逻辑。

问题 #3:错误处理是你的问题

官方 SDK 完全将错误处理留给你。这意味着:

你必须处理

  1. 输入验证错误

    if (!params.email || typeof params.email !== 'string') {
      throw new Error('无效的邮箱参数');
    }
    
  2. 业务逻辑错误

    try {
      const result = await performOperation();
    } catch (error) {
      // 这里应该返回什么?
    }
    
  3. 响应格式化

    return {
      content: [{
        type: 'text',
        text: JSON.stringify({ error: error.message })
      }],
      isError: true  // 别忘了这个!
    };
    
  4. 日志记录

    console.error('工具失败:', error);  // 希望这就够了
    

真实成本

在生产环境中,我们发现使用官方 SDK 时,错误处理代码经常超过业务逻辑代码。一个典型的工具看起来像:

  • 业务逻辑:30 行
  • 错误处理:50 行
  • 样板代码:40 行

只有 25% 的代码是实际功能。

使用 @zhama/mcp-server

protected async executeInternal(params: Record<string, unknown>): Promise<unknown> {
  // 只需编写你的逻辑
  // 错误自动捕获
  // 日志自动记录
  // 响应自动格式化
  
  const result = await performOperation(params);
  return result;  // 就这样!
}

80-90% 的代码是实际功能。

问题 #4:没有开发者体验功能

官方 SDK 提供零开发者体验优化:

缺失的功能

  1. 没有 TypeScript 装饰器 - 手动模式定义
  2. 没有自动验证 - 你要验证每个参数
  3. 没有日志系统 - 自己实现或使用 console.log
  4. 没有测试工具 - 自己想办法 mock
  5. 没有开发模式 - 没有热重载或自动重启
  6. 没有调试工具 - Console.error 是你的朋友
  7. 没有类型推断 - 手动转换所有内容
  8. 没有文档生成 - 全部手写

示例:添加工具参数

官方 SDK:需要更新 4 个地方

// 1. 更新模式
const toolDef = {
  inputSchema: {
    properties: {
      existingParam: { type: 'string' },
      newParam: { type: 'number' }  // 在这里添加
    },
    required: ['existingParam', 'newParam']  // 还有这里
  }
};

// 2. 更新类型定义
type ToolParams = {
  existingParam: string;
  newParam: number;  // 在这里添加
};

// 3. 更新验证
if (typeof params.newParam !== 'number') {  // 在这里添加
  throw new Error('newParam 必须是数字');
}

// 4. 更新逻辑
const { existingParam, newParam } = params;  // 在这里使用

@zhama/mcp-server:只需更新 1 个地方

@Tool({
  parameters: [
    { name: 'existingParam', type: 'string', required: true },
    { name: 'newParam', type: 'number', required: true }  // 只需在这里添加
  ]
})
class MyTool extends BaseTool {
  protected async executeInternal(params: Record<string, unknown>): Promise<unknown> {
    const { existingParam, newParam } = params as { 
      existingParam: string; 
      newParam: number 
    };
    // 立即使用 - 验证是自动的
  }
}

问题 #5:维护变成噩梦

随着 MCP 服务器的增长,官方 SDK 的问题会复合:

场景:一个服务器中有 10 个工具

官方 SDK 结构

// server.ts - 800+ 行

// 工具定义(150 行)
const tool1Def = { /* ... */ };
const tool2Def = { /* ... */ };
// ... 还有 8 个

// 列表处理器(50 行)
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [tool1Def, tool2Def, /* ... */]
  };
});

// 执行处理器(600+ 行)
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  switch (request.params.name) {
    case 'tool1':
      // 60 行逻辑 + 错误处理
      break;
    case 'tool2':
      // 60 行逻辑 + 错误处理
      break;
    // ... 还有 8 个 case
  }
});

问题:

  • 单个 800 行的文件
  • 工具无法独立测试
  • 共享状态导致 bug
  • 每个 PR 都有合并冲突
  • 无法跨服务器重用工具

@zhama/mcp-server 结构

// tools/tool1.ts (30 行)
@Tool({ /* ... */ })
export class Tool1 extends BaseTool { /* ... */ }

// tools/tool2.ts (30 行)
@Tool({ /* ... */ })
export class Tool2 extends BaseTool { /* ... */ }

// ... 还有 8 个工具文件

// server.ts (15 行)
import { Tool1, Tool2, /* ... */ } from './tools';

createMCPServer('my-server', '1.0.0')
  .enableTools()
  .addTool(new Tool1())
  .addTool(new Tool2())
  // ... 还有 8 个
  .runStdio();

好处:

  • 模块化、有组织的代码库
  • 每个工具可独立测试
  • 没有共享状态问题
  • 干净的 git 历史
  • 工具是可重用的包

问题 #6:忽略生产环境考虑

官方 SDK 设计上是最小化的。它不包括生产必需品:

缺少什么

  1. 结构化日志

    • 没有日志级别
    • 没有日志格式化
    • 没有日志聚合支持
    • 只有 console.error()
  2. 指标和监控

    • 没有性能跟踪
    • 没有错误率监控
    • 没有使用统计
    • 没有健康检查
  3. 配置管理

    • 没有基于环境的配置
    • 没有密钥管理
    • 没有配置验证
    • 所有东西都要手动设置
  4. 优雅关闭

    • 没有清理钩子
    • 没有连接排空
    • 手动信号处理
  5. 速率限制

    • 没有内置速率限制
    • 没有请求节流
    • 没有配额管理
  6. 缓存

    • 没有缓存层
    • 没有响应记忆化
    • 自己构建

生产环境现实检查

Zhama,我们运行每天处理数千个请求的 MCP 服务器。这是我们需要的:

// 我们想要的
createMCPServer('production-server', '1.0.0')
  .enableLogging('info')  // 结构化日志
  .enableMetrics()  // 性能跟踪
  .enableRateLimit({ maxRequests: 100, windowMs: 60000 })
  .enableCaching({ ttl: 300 })
  .enableHealthCheck('/health')
  .onShutdown(async () => {
    // 清理
  })
  .addTool(new MyTool())
  .runStdio();

官方 SDK 提供:以上都没有。

@zhama/mcp-server 提供:内置日志,其余可扩展。

真实世界影响:案例研究

让我分享我们在 Zhama 的实际数据:

项目:客户支持自动化

**目标:**为 Claude 构建 15 个工具来帮助客户支持

使用官方 SDK(我们的第一次尝试)

  • **开发时间:**6 周
  • **编写代码:**约 4,500 行
  • **编写测试:**约 1,200 行
  • **第一个月的 bug:**23 个
  • **新开发者入职时间:**3 天
  • **业务逻辑行数:**约 1,200 行(27%)
  • **样板代码行数:**约 3,300 行(73%)

使用 @zhama/mcp-server(迁移后)

  • **迁移时间:**1 周
  • **迁移后代码:**约 1,800 行(减少 60%)
  • **编写测试:**约 600 行
  • **第一个月的 bug:**4 个
  • **新开发者入职时间:**4 小时
  • **业务逻辑行数:**约 1,400 行(78%)
  • **样板代码行数:**约 400 行(22%)

ROI

  • 入职速度提高 5 倍
  • bug 减少 6 倍
  • 代码维护量减少 60%
  • 添加新工具速度提高 3 倍

这就是我们为什么构建 @zhama/mcp-server

解决方案:框架,而非库

根本问题是 MCP SDK 是一个(低级原语),而大多数开发者需要的是一个框架(高级抽象)。

有什么区别?

库(官方 SDK):

  • 你调用它
  • 你控制流程
  • 你编写所有粘合代码
  • 最大灵活性
  • 最小生产力

框架(@zhama/mcp-server):

  • 它调用你
  • 它控制流程
  • 它提供粘合代码
  • 优化生产力
  • 在重要的地方仍然灵活

框架优势

// 库方法:你做所有事情
const server = createServer();
server.on('request', (req) => {
  if (req.type === 'listTools') {
    return handleListTools();
  } else if (req.type === 'callTool') {
    return handleCallTool(req);
  }
});

// 框架方法:你提供业务逻辑
@Tool({ name: 'my-tool', /* ... */ })
class MyTool extends BaseTool {
  async executeInternal(params) {
    // 只需你的逻辑
  }
}

从官方 SDK 迁移

如果你已经在使用官方 SDK,迁移很简单:

步骤 1:安装框架

npm install @zhama/mcp-server

步骤 2:转换工具

之前:

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name === 'my-tool') {
    const result = await doSomething(request.params.arguments);
    return {
      content: [{ type: 'text', text: JSON.stringify(result) }]
    };
  }
});

之后:

@Tool({ name: 'my-tool', /* ... */ })
class MyTool extends BaseTool {
  protected async executeInternal(params: Record<string, unknown>) {
    return await doSomething(params);
  }
}

步骤 3:更新服务器

之前:

const server = new Server(/* ... */);
server.setRequestHandler(/* ... */);
const transport = new StdioServerTransport();
await server.connect(transport);

之后:

createMCPServer('my-server', '1.0.0')
  .enableTools()
  .addTool(new MyTool())
  .runStdio();

迁移时间

  • **1-3 个工具:**30 分钟
  • **4-10 个工具:**2-3 小时
  • **11-20 个工具:**1 天
  • **20+ 个工具:**2-3 天

加上:新工具的生产力立即提升。

何时使用哪个

使用官方 SDK 当:

✅ 你在构建 MCP 基础设施
✅ 你需要协议级控制
✅ 你在实现自定义扩展
✅ 你有非常特定的需求
✅ 你在构建自己的框架

使用 @zhama/mcp-server 当:

✅ 你在构建业务工具(99% 的情况)
✅ 你想快速交付
✅ 你重视开发者体验
✅ 你在构建生产服务
✅ 你有一个开发团队
✅ 你想要可维护的代码

未来:为每个人提供更好的工具

我们在 Zhama 的目标不是取代官方 SDK - 而是在它之上构建。@zhama/mcp-server 在底层使用官方 SDK,添加了缺失的开发者体验层。

接下来是什么

我们正在积极开发:

  • 测试工具 - 简单的单元和集成测试
  • 开发模式 - 热重载和更好的调试
  • 指标集成 - Prometheus、DataDog 等
  • 速率限制 - 内置请求节流
  • 缓存层 - 智能响应缓存
  • 工具市场 - 分享和发现工具
  • 文档生成器 - 自动生成工具文档

结论:选择你自己的冒险

模型上下文协议是革命性的。官方 SDK 是可靠的。但它是为协议实现者设计的,而不是应用程序开发者。

如果你在构建:

底线

官方 SDK:

  • 天气工具 156 行
  • 第一个工具 6 小时
  • 73% 样板代码
  • 手动错误处理
  • 没有 DX 功能
  • 难以维护

@zhama/mcp-server:

  • 天气工具 47 行(减少 70%)
  • 第一个工具 15 分钟(快 24 倍)
  • 22% 样板代码
  • 自动错误处理
  • 内置 DX 功能
  • 易于维护

选择是显而易见的。

今天就试试

准备好摆脱样板地狱了吗?5 分钟开始:

npm install @zhama/mcp-server

访问:


Zhama 用 ❤️ 打造 - 因为开发者应该拥有更好的工具。

对这个分析有想法?加入 GitHub 的讨论或通过 team@zhama.com 联系我们

标签

MCPSDK 分析开发体验TypeScript框架设计最佳实践