AI摘要

本文介绍了如何从零开始构建一个简单的MCP(Model Context Protocol)服务,以“田小檬时间服务器”为例,讲解了项目结构、核心代码实现、配置部署及常见问题解决方法,强调通过动手实践掌握MCP原理,提倡从简单功能入手,逐步迭代,实现个性化AI工具集成。

前言

在这个AI飞速发展的时代,MCP(Model Context Protocol)作为连接AI模型与外部工具的桥梁,正在成为开发者们的新宠。今天,我想和大家分享一下如何从零开始,打造一个属于自己的MCP服务。

就像我在之前的文章中提到的,完成比完美更重要。与其纠结于复杂的架构设计,不如先动手实现一个简单但实用的MCP服务。

什么是MCP服务?

MCP(Model Context Protocol)是一个开放标准,它允许AI模型与各种外部工具和数据源进行交互。简单来说,它就像是AI的"工具箱",让AI能够调用各种功能来完成更复杂的任务。

想象一下,如果AI只能聊天,那它就像是一个只会说话的人。但有了MCP,AI就能"动手"了——它可以查询数据库、调用API、处理文件,甚至控制硬件设备。

为什么要自己写MCP服务?

你可能会问:"市面上已经有那么多现成的工具,为什么还要自己写?"

这就像问为什么要写博客一样。正如我在《置顶个人Blog,意义何在?》中提到的,自己动手的意义在于:

  1. 深度理解:只有自己实现过,才能真正理解MCP的工作原理
  2. 定制化需求:现成的工具可能无法完全满足你的特定需求
  3. 学习成长:编写MCP服务是提升编程技能的绝佳机会
  4. 技术积累:这些经验会成为你技术栈中的宝贵财富

实战:田小檬时间服务器

今天,我们就以一个简单的时间服务为例,来看看如何实现一个完整的MCP服务。

项目结构



xiaomeng-time-servers-mcp/
├── package.json          # 项目配置
├── index.js              # 主服务文件
├── mcp-config.json       # MCP配置示例
├── start.bat             # Windows启动脚本
└── README.md             # 项目说明

核心实现

1. 项目初始化

首先,我们需要创建一个Node.js项目:

{
  "name": "xiaomeng-time-server-mcp",
  "version": "1.0.0",
  "description": "一个简单的 MCP 服务,用于获取当前系统时间",
  "type": "module",
  "main": "index.js",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.17.4"
  }
}

注意这里的 "type": "module",这让我们可以使用ES6的模块语法。

2. 时间格式化工具类

/**
 * 时间格式化工具类
 */
class TimeFormatter {
  /**
   * 获取当前时间的详细信息
   */
  static getCurrentTime(format = 'iso') {
    const now = new Date();
    const timestamp = now.getTime();
    const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
    
    let formattedTime;
    switch (format.toLowerCase()) {
      case 'timestamp':
        formattedTime = timestamp;
        break;
      case 'local':
        formattedTime = now.toLocaleString();
        break;
      case 'utc':
        formattedTime = now.toUTCString();
        break;
      case 'iso':
      default:
        formattedTime = now.toISOString();
        break;
    }

    return {
      current_time: formattedTime,
      timestamp: timestamp,
      timezone: timezone,
      timezone_offset: this.getTimezoneOffset(now)
    };
  }
}

3. MCP服务器实现

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';

class TimeServer {
  constructor() {
    this.server = new Server(
      {
        name: 'xiaomeng-time-server',
        version: '1.0.0',
      },
      {
        capabilities: {
          tools: {},
        },
      }
    );

    this.setupToolHandlers();
  }

  setupToolHandlers() {
    // 注册工具列表
    this.server.setRequestHandler(ListToolsRequestSchema, async () => {
      return {
        tools: [
          {
            name: 'get_current_time',
            description: '获取当前系统时间的详细信息',
            inputSchema: {
              type: 'object',
              properties: {
                format: {
                  type: 'string',
                  description: '时间格式:iso(默认)、timestamp、local、utc',
                  enum: ['iso', 'timestamp', 'local', 'utc'],
                  default: 'iso'
                }
              }
            }
          }
        ]
      };
    });

    // 处理工具调用
    this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
      const { name, arguments: args } = request.params;

      switch (name) {
        case 'get_current_time':
          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify(TimeFormatter.getCurrentTime(args?.format), null, 2)
              }
            ]
          };
        default:
          throw new McpError(ErrorCode.MethodNotFound, `未知工具:${name}`);
      }
    });
  }

  async start() {
    const transport = new StdioServerTransport();
    await this.server.connect(transport);
    console.error('田小檬时间服务器 MCP 已启动');
  }
}

配置与部署

MCP客户端配置

{
  "mcpServers": {
    "xiaomeng-time-server": {
      "command": "node",
      "args": [
        "d:\\path\\to\\your\\project\\index.js"
      ]
    }
  }
}

Windows启动脚本

为了方便Windows用户,我们还可以创建一个批处理文件:

@echo off
chcp 65001 >nul
cd /d "%~dp0"
echo Starting Xiaomeng Time Server MCP...
node index.js
pause

开发过程中的"坑"

在开发过程中,我遇到了几个典型问题,分享给大家避坑:

1. 模块启动条件判断

最初的代码中,服务启动后立即退出。问题出在这里:

// 有问题的代码
if (import.meta.url === `file://${process.argv[1]}`) {
  main().catch(console.error);
}

// 修复后的代码
if (process.argv[1] && (import.meta.url === `file://${process.argv[1]}` || import.meta.url.includes('index.js'))) {
  main().catch(console.error);
}

2. 中文编码问题

在Windows环境下,批处理文件可能出现中文乱码。解决方案是在脚本开头添加:

chcp 65001 >nul

3. MCP连接错误

如果遇到 "MCP error -32000: Connection closed" 错误,通常是因为:

  • 依赖未正确安装
  • 文件路径不正确
  • Node.js版本过低

测试与验证

为了确保服务正常工作,我们可以创建一个简单的测试脚本:

// 测试MCP服务功能
const tester = new MCPTester();
await tester.runAllTests();

当看到 "所有测试通过!MCP 服务工作正常。" 这样的输出时,就说明我们的服务已经可以正常工作了。

扩展思路

基于这个简单的时间服务,你可以继续扩展:

  1. 天气服务:集成天气API,提供天气查询功能
  2. 文件操作:实现文件读写、目录遍历等功能
  3. 数据库连接:连接MySQL、MongoDB等数据库
  4. API代理:作为各种第三方API的代理服务
  5. 系统监控:监控系统资源使用情况

总结

正如我在《25年即将过半,完成比完美更重要》中提到的,行动胜过完美的计划

创建MCP服务并不复杂,关键是要:

  1. 从简单开始:不要一开始就想着做一个复杂的系统
  2. 逐步迭代:先实现基本功能,再慢慢完善
  3. 注重实用:解决实际问题比炫技更重要
  4. 持续学习:在实践中不断积累经验

希望这篇文章能够帮助你开启MCP开发之旅。


相关链接:

如果觉得我的文章对你有用,可以赞助本站,使本站更好的发展