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 只管调用。
MCP 规范与语言无关;两个参考 SDK 分别是:
@modelcontextprotocol/sdk。依赖极少,运行于 Node,易于以 npx 命令分发。最适合包装 JavaScript 友好的服务(Node API、JSON 服务)。mcp。最适合包装 Python 友好的服务(Django 后台、pandas 流水线、机器学习工具链)。选你们团队更熟悉的那个;协议是完全相同的。以下示例使用 TypeScript。
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 服务器。它暴露了一个工具,校验参数,调用底层函数,并返回结构化结果。
当 Agent 读取工具列表时,它依靠描述来决定何时调用你的工具。模糊的描述("用户查询")会导致错误调用;具体的描述("通过邮箱查询用户;返回 id、名称、套餐和注册日期")则能让调用准确无误。
使用祈使句,明确列出输入,说明返回内容。如果有限制或注意事项,写进描述里——不要等 Agent 真的调用失败后才从运行时错误中得知。例如:"如果用户不存在则返回 404;请先通过 list_users 进行模糊匹配确认。"
description 字段也会展示给 Agent。为每个参数花一句话说清楚:"用户的邮箱,小写形式;请传入规范化的格式。"参数越清晰,浪费的工具调用就越少。
MCP 相比"让 Agent 去 curl 一个 API"的核心优势,就在于凭证始终留在服务器端。当你调用内部数据库时,连接字符串存放在 process.env.INTERNAL_DB_URL 中,设置在 MCP 服务器的环境变量里,而不是出现在任何 Agent 看得到的提示词中。
这也是你控制访问权限的方式:一个持有只读数据库角色的服务器可以暴露 lookup_user,但不能暴露 delete_user。Agent 无法提权;凭证本身就不具备那个权限。
基于 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 流量。
在 .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 就能像使用内置工具一样使用它。
console.log(),否则 Agent 会收到格式错误的 JSON-RPC 并断开连接。这是 TypeScript 服务器中常见的陷阱,知道之后很容易避免。