MCP协议 入门 MCP Server开发 TypeScript Node.js

快速搭建MCP Server:从零开始的实战指南

AIEng Hub
阅读约 15 分钟

引言

本文将从零开始,带你搭建一个完整的MCP Server。不需要任何MCP开发经验,只需要基本的编程基础。

环境准备

前置条件

# Node.js >= 18.x
node --version
npm --version

# 推荐使用 TypeScript
npm install -g typescript ts-node

创建项目

# 1. 创建项目目录
mkdir my-first-mcp-server
cd my-first-mcp-server

# 2. 初始化 npm 项目
npm init -y

# 3. 安装核心依赖
npm install @modelcontextprotocol/sdk zod

# 4. 安装开发依赖
npm install -D typescript @types/node

# 5. 配置 TypeScript
npx tsc --init --target es2022 --module nodenext --moduleResolution nodenext \
  --outDir dist --rootDir src --declaration true --strict true

项目结构

my-first-mcp-server/
├── src/
│   ├── index.ts          # 入口文件
│   ├── tools/            # 工具实现
│   │   ├── calculator.ts
│   │   └── weather.ts
│   └── utils/            # 工具函数
│       └── validation.ts
├── package.json
├── tsconfig.json
└── README.md

基础Server实现

最简单的Server

// src/index.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";

// 1. 创建Server实例
const server = new Server(
  {
    name: "hello-world-server",
    version: "1.0.0",
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

// 2. 注册工具列表处理器
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: "greet",
        description: "向用户打招呼",
        inputSchema: {
          type: "object",
          properties: {
            name: {
              type: "string",
              description: "你的名字",
            },
          },
          required: ["name"],
        },
      },
    ],
  };
});

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

  if (name === "greet") {
    return {
      content: [
        {
          type: "text",
          text: `你好,${args.name}!欢迎来到MCP的世界!`,
        },
      ],
      isError: false,
    };
  }

  throw new Error(`未知工具: ${name}`);
});

// 4. 启动Server
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("Hello World MCP Server running on stdio");
}

main().catch(console.error);

构建与运行

编译

# 编译TypeScript
npm run build

# 或者直接运行(开发模式)
npx ts-node src/index.ts

测试

使用MCP Inspector进行测试:

# 启动Inspector
npx @modelcontextprotocol/inspector \
  node dist/index.js

# 浏览器访问
open http://localhost:5173

MCP Inspector界面提供:

  • 连接测试
  • 工具列表查看
  • 工具调用测试
  • 消息日志查看

配置文件集成

Claude Desktop配置

将MCP Server添加到Claude Desktop的配置中:

{
  "mcpServers": {
    "hello-world": {
      "command": "node",
      "args": ["/path/to/my-first-mcp-server/dist/index.js"]
    }
  }
}

配置文件位置:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%/Claude/claude_desktop_config.json
  • Linux: ~/.config/Claude/claude_desktop_config.json

独立Client测试

也可以使用Python Client进行测试:

import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

async def test_server():
    server_params = StdioServerParameters(
        command="node",
        args=["dist/index.js"],
    )

    async with stdio_client(server_params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()

            # 列出工具
            tools = await session.list_tools()
            print(f"可用工具: {[t.name for t in tools.tools]}")

            # 调用工具
            result = await session.call_tool(
                "greet",
                arguments={"name": "小明"}
            )
            print(f"结果: {result}")

asyncio.run(test_server())

进阶:实现计算器Server

多工具Server

// src/tools/calculator.ts
import { z } from "zod";
import { Server } from "@modelcontextprotocol/sdk/server/index.js";

const CalculatorArgs = z.object({
  operation: z.enum(["add", "subtract", "multiply", "divide"]),
  a: z.number(),
  b: z.number(),
});

export function registerCalculatorTools(server: Server) {
  // 工具已在 ListToolsRequestSchema 中注册
  // 这里仅实现计算逻辑
}

export function executeCalculator(args: unknown) {
  const parsed = CalculatorArgs.parse(args);
  const { operation, a, b } = parsed;

  switch (operation) {
    case "add":
      return { result: a + b };
    case "subtract":
      return { result: a - b };
    case "multiply":
      return { result: a * b };
    case "divide":
      if (b === 0) throw new Error("除数不能为0");
      return { result: a / b };
  }
}

// src/index.ts (更新版)
const toolsRegistry = [
  {
    name: "calculate",
    description: "执行数学运算。支持加(add)、减(subtract)、乘(multiply)、除(divide)",
    inputSchema: {
      type: "object",
      properties: {
        operation: {
          type: "string",
          enum: ["add", "subtract", "multiply", "divide"],
          description: "运算类型",
        },
        a: { type: "number", description: "第一个操作数" },
        b: { type: "number", description: "第二个操作数" },
      },
      required: ["operation", "a", "b"],
    },
  },
  {
    name: "greet",
    description: "向用户打招呼",
    inputSchema: {
      type: "object",
      properties: {
        name: { type: "string", description: "你的名字" },
      },
      required: ["name"],
    },
  },
];

server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: toolsRegistry,
}));

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  switch (name) {
    case "greet":
      return {
        content: [{ type: "text", text: `你好,${args.name}!` }],
        isError: false,
      };

    case "calculate":
      try {
        const result = executeCalculator(args);
        return {
          content: [{ type: "text", text: `结果: ${result.result}` }],
          isError: false,
        };
      } catch (error) {
        return {
          content: [{ type: "text", text: `计算错误: ${error.message}` }],
          isError: true,
        };
      }

    default:
      throw new Error(`未知工具: ${name}`);
  }
});

生产部署

Docker化部署

# Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json ./
CMD ["node", "dist/index.js"]
# 构建Docker镜像
docker build -t my-mcp-server .

# 运行
docker run -i my-mcp-server

PM2进程管理

# 安装PM2
npm install -g pm2

# 启动
pm2 start dist/index.js --name my-mcp-server

# 查看日志
pm2 logs my-mcp-server

# 开机自启
pm2 startup
pm2 save

常见问题

Q: MCP Server如何接收输入?

A: MCP通过 stdio(标准输入输出)通信。Transport会自动处理JSON-RPC消息的读写。

Q: 如何调试MCP Server?

A: 推荐使用 @modelcontextprotocol/inspector 进行交互式调试,或通过 console.error 输出日志到stderr。

Q: MCP Server支持HTTP吗?

A: 是的!除了stdio,还支持SSE(Server-Sent Events)传输方式,适用于远程场景。

总结

搭建MCP Server的流程:

步骤操作工具/命令
1初始化项目npm init, 安装SDK
2创建Server使用 Server
3注册处理器setRequestHandler
4连接TransportStdioServerTransport
5测试MCP Inspector
6部署Docker/PM2

下一步学习建议:


本文最后更新于 2024-07-05。