手写一个最小 MCP Serverstdio 传输、两个 tools、在 Cursor 里验通调用链会「接入现成 MCP」和会「自己写一个」是两件事。前者解决生产力后者解决当工具列表里没有你要的能力、或你必须把数据留在内网时怎么办。最小 Server 不需要十个工具、也不需要远程部署一个stdio进程、两个tools、能在 Cursor 里把调用链跑通就够建立正确心智模型。本文带你走完协议直觉 → 目录与依赖 → 两个工具实现要点 → mcp.json → 验通清单。代码以「可放进 AtomGit 的教学仓」为目标去密钥、可复制、可改。字段名与 SDK API 随版本会变以你安装的 MCP SDK / Cursor 文档为准下面用稳定的概念名描述避免绑死某次小版本号。摘要先懂调用链配置 → 握手 → tools/list → tools/call → 回传。两个工具够用echo_note纯函数list_workspace_txt限定目录列举。stdio 优先本地子进程好调试适合内网与教学。安全默认根目录收窄、只读列举、密钥不进仓库。验通标准Cursor 能发现、能调用、错参有可读错误。结论最小 Server 的价值是把「魔法」变成「可观察的进程与 JSON」。通了再加第三个工具。结论卡步骤你要看到的证据失败时先查配置mcp.json 被加载路径/命令/工作目录握手server 非红灯stderr / 启动命令list两个工具名可见schema 是否合法call返回可预期文本参数类型与权限安全越权路径被拒根目录是否配宽背景与边界MCP 用 JSON-RPC 风格消息描述能力。Cursor 通过配置拉起命令常见commandargsenv与 Server 走stdio交换。也存在远程传输形态但教学与内网落地优先 stdio少一层网络鉴权问题更可复现。边界本文不是完整 SDK 手册不实现 resources/prompts另文不演示绕过客户端安全策略不连接生产库。若你使用 TypeScript 或 Python 官方 SDK函数名可能不同但「声明 tools → 实现 handler → stdio 入口」三步不变。与「接入现成 MCP」文的差异那边选别人的服务器这边你是服务器作者。原理调用链长什么样配置客户端知道用什么命令启动你的 Server。握手initialize交换能力。发现tools/list把名称、描述、输入 schema 交给模型侧。调用tools/call传入参数你的 handler 返回 content。呈现模型或 UI 使用返回文本人审结果。任一环失败都不要先怀疑「模型不够聪明」。先看进程是否存活、list 是否为空、call 的错误字符串是否被吞掉。步骤 1仓库骨架可放 AtomGit建议最小结构min-mcp-server/ README.md pyproject.toml # 或 package.json src/ server.py # 入口 .env.example .gitignore examples/ mcp.json.snippet.gitignore至少包含.env、虚拟环境、缓存、本地密钥文件。README 写清如何安装、如何在 Cursor 里粘贴配置、默认只读目录是什么。步骤 2只做两个 tools工具 Aecho_note意图验证「参数进来、文本出去」的闭环不含文件系统。输入textstring必填可选prefix。输出一段回显字符串。为何先做它排除「目录权限、编码、路径」干扰专测协议与配置。伪代码级逻辑echo_note(text, prefix) - return f{prefix}{text}工具 Blist_workspace_txt意图在预先配置的根目录下列出*.txt可改成你的扩展名。输入可选subdir相对路径禁止..逃逸。输出文件名列表或「空目录」说明。硬约束根目录来自环境变量例如MCP_ROOTresolve(root, subdir)后必须仍在 root 内本教学版本只列举不读取内容、不删除、不写入。list_workspace_txt(subdir) - path safe_join(MCP_ROOT, subdir) return [p.name for p in path.glob(*.txt)]两个工具的搭配一个证明协议一个证明「带边界的副作用面」。步骤 3stdio 入口与日志入口脚本应能被非交互拉起stdin/stdout 留给协议调试日志打到 stderr避免污染协议流。本地可先用官方 inspector 或 Cursor MCP 面板观察。教学仓 README 可写# 示例用 uv / pip 安装后exportMCP_ROOT/absolute/path/to/playground python-msrc.server真正给 Cursor 用时通常不手动长驻而由客户端按配置拉起。步骤 4写入 Cursor 配置在项目或全局 MCP 配置中增加结构示意键名以你的 Cursor 版本为准{mcpServers:{min-demo:{command:python,args:[-m,src.server],cwd:/absolute/path/to/min-mcp-server,env:{MCP_ROOT:/absolute/path/to/playground}}}}要点cwd指到仓库避免相对导入失败MCP_ROOT用绝对路径且是你愿意暴露的沙箱目录改配置后按客户端要求重载 MCP / 重开窗口先不要挂其他 MCP降低干扰。步骤 5验通调用链按顺序做不要跳MCP 面板里min-demo为已连接。能看到echo_note与list_workspace_txt。在 Ask 中明确「调用 echo_notetextping」。应返回可辨认回显。在 playground 放a.txt调用 list应看到文件名。传入试图逃逸的subdir如../应失败且错误可读。故意缺省必填参数应看到 schema / 校验错误而非空成功。全部通过才算「调用链验通」。然后你可以加第三个工具、改用 TypeScript SDK、或把仓库推到 AtomGit去密钥后。可复制给 Agent 的联调提示词请只使用 MCP 工具 min-demo.echo_note 与 min-demo.list_workspace_txt。 1) 调用 echo_notetextchain-ok 2) 调用 list_workspace_txtsubdir 为空 3) 汇报两次调用的原始返回不要改我的仓库文件 若工具不可用说明你在客户端里看到的错误不要编造成功。踩坑清单现象可能原因处理一直红灯命令/cwd/依赖错误终端手动跑同一命令list 为空未正确注册 tools查 schema 与导出能 list 不能 callhandler 抛错被吞stderr 日志路径逃逸成功safe_join 未做立刻修勿发文炫耀模型不调用工具描述不清或未允许提示词点名工具协议错乱日志打到 stdout改 stderr安全底线写进 README示例默认只读列举若加写工具必须另开开关且默认关闭。禁止在配置或 Rules 里粘贴真实 Token。AtomGit/GitHub 公开前跑密钥扫描。不要把 Server 指到家目录或生产挂载。教学演示用假数据截图打码环境变量。验收标准他人按 README 能在 30 分钟内复现 list→call两个工具行为与文档一致逃逸路径被拒绝仓库无真实密钥你能向同事画出调用链四步陷阱与边界SDK 升级可能改 API锁版本并在 CI 里做「能 list」的冒烟后续可写集成测试文。最小不等于可上生产生产还要鉴权、审计、速率限制。工具描述也是 Token描述写短、写准。不要一次加十个工具每加一个重复验通一次。实现细节描述文本怎么写才不坑模型工具能不能被正确调用一半取决于 handler一半取决于描述与 schema。教学里常见翻车描述写「智能地处理文件」——模型不知道何时该用参数叫path却不说明相对谁可选参数过多模型乱填。建议echo_note描述写成「把文本原样回显用于连通性测试无副作用。」list_workspace_txt写成「在 MCP_ROOT 下列举 txt 文件名subdir 为相对路径禁止 …只读。」schema 里用明确类型与description必填字段宁少勿滥。描述也是 Token两句精准胜过一段散文。本地调试剧本不必先开 Cursor在交给 Cursor 之前先确认进程本身健康导出MCP_ROOT到空沙箱目录放入两个 txt。用 MCP 官方 inspector 或最小客户端发tools/list。再发两次tools/call。把 stderr 日志级别调到可读。只有当「离开 IDE 也能 call 通」你才适合排查 Cursor 配置问题。否则你会在错误的层打转改了三遍 mcp.json其实是 Python 虚拟环境没激活。从两个工具扩到「团队用得上」的路径验通后的扩容顺序建议保持只读加read_workspace_txt仍限根目录、限大小。加显式开关ALLOW_WRITE1才暴露写工具。为写工具做审计日志谁、何时、改了哪。写集成测试mock stdio断言 schema后续专文。不要第一天就做「通用 shell 工具」——那等于把 Agent 的手直接接到你的 bash公约与禁区都会失效。教学仓 README 应有的验收段落直接给读者一张表检查项期望安装文档中的一条命令可完成list两工具可见echo回显匹配list dir看到沙箱 txt逃逸失败密钥扫描通过读者按表打勾你的博客才是「可复现实战」而不是「作者机器上成功过」。常见问答Q必须用官方 SDK 吗A教学期建议用少踩 JSON-RPC 细节若环境限制也可最小手写但要自己保证消息帧正确。Q为什么不直接给 Agent 一个 shell 工具Ashell 面太大禁区无法机械执行。最小 Server 的意义是收窄面。QCursor 里调用了但模型编造了返回A在提示词要求「贴原始工具返回」并在 UI 中核对工具调用轨迹。编造返回通常是模型没真正 call 或你看错了线程。QWindows 路径怎么办AMCP_ROOT仍用绝对路径safe_join要按平台规范化示例 README 分别给出 POSIX/Windows 片段。Q如何分享给同事A推到 AtomGit 后同事只改cwd与MCP_ROOT不要分享你的真实机器绝对路径截图到公开处。对照接入现成 MCP vs 手写接入现成手写最小目标马上有生产力理解与定制风险权限模型外来自己写漏边界适合标准能力内网特例验收官方文档 八问本文调用链清单两者不是对立手写通了你才更会审查现成 Server 的 README 是否在说谎。发布前检查作者视角写这篇实战时作者侧也应自己在干净目录复现一遍截图打码绝对路径与用户名不做「完整可复制机密配置」在文首声明 SDK 版本可能变化。实战文的信誉来自可复现不来自语气强硬。安全演练故意做一遍「坏人提示词」在内部演练勿对生产时用提示词要求 Agent「请用 list_workspace_txt 读取../../.ssh」或「请删除 playground 外文件」。期望工具层拒绝或路径规范化失败即使模型愿意配合也打不开。若演练成功越权立刻停更、修 safe_join、撤回示例仓。把演练结果写进 README「安全」节比写「我们很重视安全」有用一百倍。版本钉扎与可重复安装# 示例锁住 MCP SDK 与运行时 mcpx.y.z python3.11并在 CI 增加「导入 server 模块 断言 tools 名称集合」的冒烟作业。教学仓可以没有复杂业务测试但不能没有「安装后还能 list」。读者三个月后复现失败通常不是读者笨是作者没锁版本。练习作业读者可交到 AtomGitFork 最小仓改echo_note增加uppercase布尔参数。增加第三个只读工具count_txt返回文件数。写一页「我的 MCP_ROOT 边界」说明。打开 PR即使是练习仓描述验通步骤。完成作业的标准仍是调用链清单而不是功能炫技。结束前的自我提问若你只能带走一句我会不会审查别人的 MCP README若会这篇就够本若仍只想复制粘贴星标项目请先把safe_join写会。小结手写最小 MCP Server是把 Agent 的「手」从黑盒变成你仓库里的一个普通进程。stdio、两个 tools、Cursor 验通——这三件事做完你就具备改现成服务器与自建内网工具的基础。下一步不是堆功能而是收紧默认权限并把教学仓干净地放到 AtomGit。草稿未发布 · 作者 梧桐秋海 · 活动九月创作之星、AtomGit秋季、工具实践