教程 搜索 / 原生 Mac IDE / 编写自己的 MCP 服务器
📝 文字 ● 高级 更新于 2026-05-13

如何编写自己的 MCP 服务器?

TL;DR:编写一个实现 MCP 协议的 Node 或 Python 程序(使用官方 SDK,不过几百行代码),暴露封装了你服务 API 的带类型工具,然后在 mcp.json 中通过 command + args 将 LingCode 指向它。Agent 便能以类型安全的方式访问你的服务所提供的一切功能。

当你希望"Agent 能直接与我们内部的 X 通信"的那一天,就是你该编写 MCP 服务器的时候了。不过几百行代码,作为你已有服务的前端,将"Agent 不了解那个"变成"Agent 有一个带类型的工具"。

接入已有的 MCP 服务器讲的是使用别人构建的服务器。编写则是反过来:你有一个 Agent 不了解的服务——内部管理 API、只有你团队才懂 Schema 的数据库、CI 系统、功能开关平台——你希望 Agent 能够使用它,而不是给它一个 Shell 然后寄望于最好的结果。

工作量其实很小。MCP 服务器是一个通过 stdio 或 HTTP 遵循特定 JSON-RPC 协议通信的进程。它暴露一组工具,每个工具有名称、描述,以及参数和返回值的 JSON Schema。当 Agent 调用某个工具时,你的代码运行,与你想暴露的底层服务通信,并返回结果。就这么简单。Agent 看不到凭证,不需要推断端点,也不会凭空捏造参数——它看到的是命名合理的带类型工具。

你的付出能换来什么:一个 Agent 每次都能正确使用的工具。不会再出现"我让它查一个 Linear 工单,它却试图 curl 错误的 URL"。语义定义在服务器里,你可以对其进行测试;Agent 只管调用。

你将学到什么

第一步:选择运行时

1

TypeScript 或 Python——两者都是一等公民

MCP 规范与语言无关;两个参考 SDK 分别是:

  • TypeScript:@modelcontextprotocol/sdk。依赖极少,运行于 Node,易于以 npx 命令分发。最适合包装 JavaScript 友好的服务(Node API、JSON 服务)。
  • Python:PyPI 上的 mcp。最适合包装 Python 友好的服务(Django 后台、pandas 流水线、机器学习工具链)。

选你们团队更熟悉的那个;协议是完全相同的。以下示例使用 TypeScript。

第二步:初始化项目

2

最简可用的服务器

mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm i @modelcontextprotocol/sdk zod

然后创建 server.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";
import { z } from "zod";

const server = new Server(
  { name: "my-internal-tools", version: "0.1.0" },
  { capabilities: { tools: {} } }
);

// Define your tools' input schemas
const LookupUserArgs = z.object({
  email: z.string().email(),
});

server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: "lookup_user",
      description: "Look up a user by email in the internal database. Returns the user's id, name, plan, and signup date.",
      inputSchema: {
        type: "object",
        properties: {
          email: { type: "string", format: "email", description: "The user's email address" },
        },
        required: ["email"],
      },
    },
  ],
}));

server.setRequestHandler(CallToolRequestSchema, async (req) => {
  if (req.params.name === "lookup_user") {
    const { email } = LookupUserArgs.parse(req.params.arguments);
    const user = await fetchUserFromOurDB(email);
    return { content: [{ type: "text", text: JSON.stringify(user) }] };
  }
  throw new Error(`Unknown tool: ${req.params.name}`);
});

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

这就是一个可以运行的 MCP 服务器。它暴露了一个工具,校验参数,调用底层函数,并返回结构化结果。

第三步:认真撰写工具描述

3

描述就是你给模型的提示

当 Agent 读取工具列表时,它依靠描述来决定何时调用你的工具。模糊的描述("用户查询")会导致错误调用;具体的描述("通过邮箱查询用户;返回 id、名称、套餐和注册日期")则能让调用准确无误。

使用祈使句,明确列出输入,说明返回内容。如果有限制或注意事项,写进描述里——不要等 Agent 真的调用失败后才从运行时错误中得知。例如:"如果用户不存在则返回 404;请先通过 list_users 进行模糊匹配确认。"

每个参数的描述同样重要。JSON Schema 中每个属性的 description 字段也会展示给 Agent。为每个参数花一句话说清楚:"用户的邮箱,小写形式;请传入规范化的格式。"参数越清晰,浪费的工具调用就越少。

第四步:将凭证保存在服务器端

4

Agent 永远不应看到密钥

MCP 相比"让 Agent 去 curl 一个 API"的核心优势,就在于凭证始终留在服务器端。当你调用内部数据库时,连接字符串存放在 process.env.INTERNAL_DB_URL 中,设置在 MCP 服务器的环境变量里,而不是出现在任何 Agent 看得到的提示词中。

这也是你控制访问权限的方式:一个持有只读数据库角色的服务器可以暴露 lookup_user,但不能暴露 delete_user。Agent 无法提权;凭证本身就不具备那个权限。

第五步:接入前先测试服务器

5

stdio 很容易用脚本驱动

基于 stdio 的 MCP 服务器从 stdin 读取 JSON-RPC 消息,并将结果写入 stdout。你可以用任何测试工具来驱动它:

# A tiny smoke test
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | node server.js

如果服务器能正确列出工具,说明 Schema 没有问题。使用官方 MCP Inspector(npx @modelcontextprotocol/inspector)可以获得更完整的开发体验——它提供 UI 界面,让你逐一调用工具、查看响应,并实时监视 JSON-RPC 流量。

第六步:接入 LingCode

6

配置方式与其他 MCP 服务器完全相同

.claude/mcp.json(项目级)或 ~/.claude/mcp.json(用户级)中:

{
  "mcpServers": {
    "internal": {
      "command": "node",
      "args": ["/Users/you/code/my-mcp-server/server.js"],
      "env": {
        "INTERNAL_DB_URL": "postgres://..."
      }
    }
  }
}

工具会以 mcp__internal__lookup_user 的形式出现。每次会话第一次调用时会提示确认权限。之后,Agent 就能像使用内置工具一样使用它。

日志:你的服务器可以自由使用 stderr 输出日志。stdout 专门用于协议消息——不要在那里调用 console.log(),否则 Agent 会收到格式错误的 JSON-RPC 并断开连接。这是 TypeScript 服务器中常见的陷阱,知道之后很容易避免。

下一步