server-and-tool-architecture

服务端入口与工具注册架构

这是 MCP 服务端的骨架——它让 AI 客户端(Claude Code、Claude Desktop 之类)通过 MCP 的 stdio 传输调用进 YouTeacher。它是一个刻意扁平的小程序:一个入口把服务器启动起来,加载凭据,把一组固定的工具挂到同一个共享 HTTP 客户端上,再接上传输层。这里没有插件加载器,也没有动态发现——工具集就是一份手写的清单。

它以私有 npm 包(@youteacher/mcp)的形式发布,是一个面向 Node 20 及以上的 ES-module TypeScript 项目。它对外暴露单个可执行文件 youteacher-mcp,运行期只依赖两样东西:官方的 @modelcontextprotocol/sdk,以及做 schema 校验的 zod。构建就是一句 tsc;开发时用 tsx 直接跑入口。

启动流程

入口是一个可执行脚本(#!/usr/bin/env node),它的 main 按顺序做四件事:

  1. 加载凭据。 管理员 API-key 凭据从一个文件读入,文件位置由一个环境变量给出。这一步包在 try/catch 里:文件缺失或无效时,服务器把原因写到 stderr 并以非零码退出。
  2. 建一个 auth 客户端。 用这些凭据构造单个 AuthClient,供每一组工具共享——工具自己从不持有凭据、也不自开连接。
  3. 注册各工具组。 先创建一个 McpServer(命名为 youteacher-mcp,带它自己的版本号),再用 (server, auth) 逐个调用各 register*Tool(s) 函数:me 工具、列出 API-key 的工具,以及 post、category、export、update-self 这几组工具。
  4. 接上传输层。 挂上 StdioServerTransport 并 await server.connect——从这一刻起,进程就通过 stdin/stdout 讲 MCP。

main 外层的 .catch 兜住启动阶段逃逸出来的任何失败:把栈(或消息)打到 stderr,然后非零退出。

快速失败,而不是沉默失败

凭据检查是这里最吃重的一个设计决定,原因写在代码注释里:一个没有有效凭据就起来的服务器,会在每一次调用上都静默地返回 401。那是一种令人困惑的失败——客户端看着是连上了,可什么都不工作。所以服务器在凭据缺失或格式不对时干脆拒绝启动,把一个散布在每次请求上的认证错误,收敛成运维者立刻能看见的一次明确的启动失败。「这凭据不对」这个事实只有一个家——启动那一步——而不是在每个请求里被反复重新发现。

一个共享客户端,多个轻工具组

接线是一颗以 AuthClient 为中心的星。每个工具组都是一个 register* 函数,收下 server 和那唯一的 auth 客户端;这一组的活,就是把 MCP 的工具调用翻译成经由这个客户端发出的 HTTP 请求,再把响应递回去。这样认证和传输的事都归在一处,各工具组就能安心当一个后端端点之上的薄包装。加一组工具,就是多写一个 register* 函数、在 main 里多加一行——套路不变。

把后端响应标准化

内容服务的各工具共用两个小 helper,好让每个包装都以同样的方式,把后端回复变成一个规整的 MCP 结果。

toToolResult 把 auth 客户端的 ApiResponse 转成 MCP 的 CallToolResult 形状。2xx 状态变成单个文本块,装着漂亮打印的 JSON 正文(若正文本身已是字符串,就直接用它)。任何非 2xx 状态变成一个带 HTTP 状态行前缀的文本块,并且带上 isError: true——正是这个标记让 MCP 客户端把这次调用渲染成失败,而不是一次内容奇怪的成功。这个 helper 的返回类型照抄了 SDK 那个开放的索引签名,所以一个更窄的本地接口仍能通过 CallToolResult 的类型检查。

withQuery 从一个 params 对象拼出 path?k=v&… 字符串,跳过任何值为 nullundefined 的键。这样每个工具就能把一个部分填写的 filter 对象直接透传——调用方不必手写 if (x != null) params.set(...) 那套仪式,没给的 filter 就干脆不出现在查询串里。要是跳完什么都没剩,就返回不带尾随 ? 的裸 path。

为什么长这样

整个服务器是故意做小的。凭据加载一次、失败就大声报出来;一个客户端替所有人带着认证;工具组是统一的、注册完就不管的包装;两个 helper 保证「成功」和「失败」在客户端看到的每一个工具上长得一样。这个服务器没有的问题,它就没有对应的机械装置。

about this entry

One of sijie's wiki entries. The AI on this site is grounded in the same corpus and answers in sijie's voice, with citations back to entries like this one — answering costs sijie money, so it waits behind a code: enter an access code →

server-and-tool-architecture