ORANGE BOOK · MCP

第十五章:零基础写一个自己的 MCP

写在前面:本章会出现少量代码,但真的很少。 如果你完全不会写代码,照着抄也能跑通;如果你会一点点 Python 或 JS,会读得很轻松。


一、为什么"自己写 MCP"是件值得做的事

很多人觉得"用现成的 MCP 就够了"。 自己写一个的真正价值不在功能本身,在于:

  1. 理解 MCP 的本质——读再多文档,都不如自己 hello world 一次;
  2. 解决"小众但只你需要"的问题——比如读你公司内部的某个奇怪系统;
  3. 把工作流"打包"成 MCP 给团队——别人不用复制提示词,直接调用工具;
  4. 建立"我能创造 AI 工具"的信心——这种感觉可能改变你看世界的方式。

二、最简的 Python MCP:30 行写一个"今日天气"

准备

# 装 uv(已装请跳过)
curl -LsSf https://astral.sh/uv/install.sh | sh

# 建项目
uv init weather-mcp
cd weather-mcp

# 装 MCP 官方 Python SDK
uv add "mcp[cli]" httpx

写代码

新建 weather_mcp.py

import httpx
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("weather")


@mcp.tool()
async def get_weather(city: str) -> str:
    """查询某个城市的当前天气。

    Args:
        city: 城市名,例如 "Beijing"、"Shanghai"
    """
    url = f"https://wttr.in/{city}?format=3"
    async with httpx.AsyncClient() as client:
        resp = await client.get(url, timeout=10)
        return resp.text.strip()


if __name__ == "__main__":
    mcp.run()

就这 16 行。 包括空行、注释、装饰器。 它干的事:暴露一个 get_weather(city) 工具给 AI。

跑起来

uv run weather_mcp.py

看到提示就跑通了,但 stdio 方式下它在等 Claude 来连接。

接进 Claude Desktop

打开 claude_desktop_config.json,加入:

"weather": {
  "command": "uv",
  "args": [
    "--directory",
    "/Users/you/weather-mcp",
    "run",
    "weather_mcp.py"
  ]
}

重启 Claude,对话里输入:

杭州现在天气怎么样?

Claude 会调用 get_weather("Hangzhou"),返回当前天气。

你写的第一个 MCP 上线了。


三、TypeScript 版:读取本地待办

很多人喜欢 JS/TS。我们写一个"读取本地待办"MCP: 读取 ~/todo.txt 的内容,并支持新增。

准备

mkdir todo-mcp && cd todo-mcp
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript tsx @types/node
npx tsc --init

写代码 index.ts

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import * as fs from "node:fs/promises";
import * as path from "node:path";
import * as os from "node:os";

const TODO_PATH = path.join(os.homedir(), "todo.txt");

const server = new McpServer({ name: "todo", version: "0.1.0" });

server.tool(
  "list_todos",
  "列出当前所有待办事项",
  {},
  async () => {
    try {
      const text = await fs.readFile(TODO_PATH, "utf8");
      return { content: [{ type: "text", text }] };
    } catch {
      return { content: [{ type: "text", text: "(待办文件不存在或为空)" }] };
    }
  }
);

server.tool(
  "add_todo",
  "新增一条待办",
  { item: z.string().describe("待办内容") },
  async ({ item }) => {
    await fs.appendFile(TODO_PATH, `- ${item}\n`);
    return { content: [{ type: "text", text: `已添加:${item}` }] };
  }
);

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

跑起来

npx tsx index.ts

接进 Claude

"todo": {
  "command": "npx",
  "args": ["tsx", "/Users/you/todo-mcp/index.ts"]
}

重启 Claude。试:

列一下我的待办; 加一条:"买生日蛋糕"。

看到响应就成功了。


四、写 MCP 的 5 个黄金原则

原则 1:工具粒度要"小而清晰"

不要写一个 do_everything(action: str, params: any) 这种万能工具。 拆成 list_todosadd_todoremove_todo,AI 才能更好地知道什么时候用哪个。

原则 2:参数和返回值都用结构化 schema

像 zod、Pydantic 这种类型校验,AI 才能精准生成调用参数。

原则 3:给每个工具写"清晰的 description"

AI 是看 description 决定"什么时候用我"的。 不要写 "do something",要写 "列出当前所有未完成的待办事项"

原则 4:错误要友好

报错时返回人话,比如 "待办文件不存在", 不要直接抛 stack trace——AI 看不懂、用户更看不懂。

原则 5:默认只读,写操作要显式

写 / 删 / 发 / 付,要明确命名,AI 才会更慎重; 也方便用户在客户端那边设"该工具需要二次确认"。


五、把 MCP 升级成"远程版"

stdio MCP 只能本地用。要给团队 / 别的设备用,要做成 HTTPS。

Python 版

# 改 main:
if __name__ == "__main__":
    mcp.run(transport="streamable-http", host="0.0.0.0", port=3000)

跑:

uv run weather_mcp.py

然后通过反向代理(nginx / Cloudflare)暴露到公网,加 HTTPS + token 鉴权。

TypeScript 版

StdioServerTransport 换成 StreamableHttpServerTransport,并配 Express。 SDK 文档里有详细模板。

偷懒:用 mcp-remote 包一层

不想改代码?最简单:用 mcp-remote 把本地 stdio 包成 HTTPS(见第十四章)。


六、把 MCP 发布到社区

1. npm 上发布

如果是 TypeScript MCP,可以发布到 npm:

npm version 0.1.0
npm publish --access public

包名建议带 mcp-server- 前缀,比如 mcp-server-todo,方便别人搜。

2. PyPI 上发布

Python MCP 用 uv

uv build
uv publish

3. 上社区目录

社区会迅速给你反馈和星标—— 你不仅做了一个 MCP,还在搭建自己的开发者影响力。


七、5 个适合"小而美"的 MCP 创意

如果你想从写 MCP 入手,下面这些点子简单、有用、也容易爆款:

  1. mcp-server-applescript:用 AppleScript 操作 Mac 上一切(Finder、邮件、备忘录);
  2. mcp-server-rss:把指定 RSS Feed 的最新内容拉给 AI;
  3. mcp-server-bookmarks:读你浏览器书签 / Pocket / Instapaper;
  4. mcp-server-pomodoro:番茄钟开始 / 暂停 / 统计;
  5. mcp-server-recipe:自己家的"菜谱本",AI 能查能加新食谱。

任何一个,半天写完,发出去就有人用。


八、关于"开源"和"商业化"

如果你的 MCP 真的火了,会面临一个选择:

  • 纯开源(MIT / Apache):人人能用,但商业化空间小;
  • 开源核心 + 付费高阶(Open Core):免费版基础够用,付费版加高阶功能(如团队、SSO、SLA);
  • 服务化(SaaS MCP):直接做成 OAuth 远程服务,按使用量计费。

2026 年开始已经有一些 MCP 创业公司走通了 SaaS 路线,月营收过万美元。 这个生态机会还很大,普通开发者也能上桌


九、本章小结

  1. 写 MCP 不可怕,Python 30 行就能跑
  2. 5 个原则:小工具 / 结构化 / 清晰描述 / 友好错误 / 写操作显式;
  3. 升级远程:直接换 transport,或用 mcp-remote 包;
  4. 发布渠道:npm / PyPI / awesome-mcp-servers / getmcp / mcp.run;
  5. 写 MCP 是 2026 年最低成本的"开发者影响力建设"。

十、动手任务(120 分钟)

按下面顺序做:

  1. 30 分钟:跑通本章的 Python "今日天气"或 TypeScript "待办" MCP;
  2. 30 分钟:在它基础上加一个工具(比如天气 MCP 加一个"未来 3 天预报",待办 MCP 加一个"标记完成");
  3. 30 分钟:写一个真正属于你自己生活 / 工作的小 MCP(比如读你 Apple Notes、读公司某个 Excel);
  4. 30 分钟:发到 GitHub,提 PR 到 awesome-mcp-servers。

完成 → 你就从"用户"升级成了"贡献者"。 这是这本橙皮书想送你的最远的一段路。

第四篇至此结束。下一篇我们回到普通用户视角, 讲安全、讲 FAQ、讲未来。