NestJs 创建MCP服务的总结

3 阅读8分钟

各位大佬们,这篇文章AI味很浓,我是把codex当成技术交流者和实践者,再让它总结成NestJs文档。如果有介意的话轻喷。为啥要用NestJs,因为我发现codex的skill同时支持python和typescript,我又是前端开发转NodeJs开发,所以用了NestJs框架

基于 @nestjs-mcp/server 与 TypeORM 的 stdio 只读查询服务 (可以形成增删改服务,本文只有只读服务,保证数据正确及来源干净)

本文汇总实现和一次官方 MCP Client 冒烟测试为基础;后续若调整传输层、鉴权或接入方式,以最新代码和实际环境为准。

一、当前实现基线

1.1 服务形态

·  不新建独立服务或新 package:代码位于 servers 工程下的 aiSkill/ai-mcp 目录,新依赖安装于 servers 根目录。

·使用 TypeORM,并直接复用主工程已有实体,不引入 Sequelize,也不额外创建实体。

·只提供只读、语义明确的查询工具;默认只排除逻辑删除记录(is_delete=1),step_status 不限任何取值。

·以 stdio 模式运行,仅当 MCP_STDIO_ENABLED=true 时进入 MCP 分支;普通 HTTP 启动流程不受影响。

·启动脚本 start:mcp 已加入 AI_QUEUE_ENABLED=false,避免 MCP 进程额外连接 Redis/BullMQ。

1.2 对外暴露的工具

工具作用典型参数
query_workflow_steps分页查询 ai_workflow_step,支持工作流 id、状态、类型、输出格式、模型、provider、耗时、创建时间与关键词等过滤。page、pageSize、stepStatus、stepType、workflowId、keyword 等
get_workflow_step_detail按 id 获取单条完整记录,含完整 prompt、输出内容、result JSON 等字段。id
get_workflow_step_stats统计总数、success/failed/pending/running 数量、成功率、平均/最大耗时,并按状态、输出格式、provider、模型做分布汇总。与 query 相同的可选过滤条件

1.3 测试结论

官方 MCP Client 冒烟测试通过:成功列出 3 个工具,真实库统计结果为 total=37、success=28、failed=3、pending=6、successRate=75.68%;分页查询与 id=121 详情查询均正常。

验证命令: node src\aiSkill\ai-mcp\scripts\mcp-smoke-test.mjs(在 servers 根目录运行)

二、Codex 如何调用这个 MCP 服务

2.1 注册方式

Codex 本身不会凭空知道该服务。需要把 stdio MCP 注册到 Codex 配置中,例如写入全局 config.toml 的 [mcp_servers] 段,或使用 codex mcp add。建议 command 使用 Node 绝对路径,并注入三个环境变量。

[mcp_servers.ai-workflow-step-mcp]

command = "C:/Program Files/nodejs/node.exe"

args = ["E:/otherProject/ai-video/service/servers/dist/main.js"]

env = { NODE_ENV = "development", MCP_STDIO_ENABLED = "true", AI_QUEUE_ENABLED = "false" }

命令行方式等价于:codex mcp add ai-workflow-step-mcp,并用 --env 注入上述环境变量,再以 -- 指定 node 与 dist/main.js。注册后需重启 Codex 或新建任务才会生效。

2.2 完整调用链路

1.       Codex 作为 MCP Client,在会话启动时按配置 spawn 子进程:node ...\dist\main.js。

2.       dist/main.js 检测到 MCP_STDIO_ENABLED=true,不启动 HTTP,而是创建 Nest 应用上下文并由 McpStdioTransportService 启动 stdio MCP Server。

3.       双方通过 stdin/stdout 走 JSON-RPC:先 initialize,再 tools/list,Codex 因此看到 3 个可用工具。

4.       用户用自然语言提问后,模型自行判断应调用哪个工具并生成参数。

5.       MCP Client 发送 tools/call,Nest 侧由 TypeORM Repository 查询 MySQL ai_workflow_step 表。

6.       服务把结果封装为 CallToolResult(JSON 文本)返回,Codex 再整理成自然语言答案。

7.       会话关闭时传输随之关闭,进程退出;因此无需常驻端口,每次由 Codex 重新拉起。

2.3 使用示例

用户在CodeX提问:“ 最近有哪些视频生成步骤失败了?分别是什么错误?”

Codex 会调用 query_workflow_steps,参数大致为 { stepType: "video", stepStatus: "failed", page: 1, pageSize: 20 },服务按 create_date 倒序返回结果。

需要说明:如果 MCP 尚未注册进当前会话,Codex 不会主动拉起该服务。此前演示是用独立的官方 MCP Client 脚本发起相同协议调用,效果与 Codex 注册后一致。

三、业务查询示例:最近失败的视频生成步骤

按 step_type=video、step_status=failed、is_delete=0 查询并按创建时间倒序,当前共有 3 条失败记录。

记录 ID工作流 ID创建时间 (UTC)模型 / Provider错误结论
113872026-08-31 11:45:14doubao-seedance-2-5-260628 / doubao-videoDoubao 404:账号未激活 doubao-seedance-2-5-260628,需在 Ark 控制台开通模型
112862026-08-31 11:43:54MiniMax-H3 / minimax-videoMiniMax 请求失败:fetch failed,疑似网络或服务端不可达
108822026-08-29 14:20:27doubao-seedance-2-5-260628 / doubao-videoDoubao 404:账号未激活 doubao-seedance-2-5-260628,需在 Ark 控制台开通模型

两类根因:

·         记录 113 与 108:豆包视频模型 doubao-seedance-2-5-260628 未在当前账号开通,需在火山方舟 Ark 控制台激活对应模型服务。

·         记录 112:MiniMax 请求网络失败(fetch failed),需要检查 MiniMax 侧服务状态、网络连通性及超时配置。

四、多系统接入与线上部署

4.1 能否给其它网页端 AI 聊天框调用

能,但不是网页前端直接连,而是聊天系统的后端作为 MCP Client 去调用。前提是服务需提供可被远程访问的 Streamable HTTP / SSE 端点;当前 stdio 模式只能被本机进程拉起。

·         传输层:stdio 只能本机使用;线上服务需增加 Streamable HTTP/SSE 端点,例如 https://域名/mcp。

·         调用方:对方聊天后端需接入 MCP Client SDK,负责 tools/list 与 tools/call;浏览器前端本身不直接通信。

·         安全:公开端点需鉴权、CORS 白名单、限流与审计;由于工具只读,风险相对可控。

·         替代方案:若只服务自家系统,直接提供只读 HTTP API 也可以,MCP 并非唯一通道。

4.2 tools/list 与 tools/call 的分层关系

可以按“AI 工具类调用”来理解,但要分清两层:模型负责决定调用哪个工具,MCP Client(对方后端)负责真正发送 tools/call。链路示意如下。

用户网页 → 对方 AI 后端(MCP Client) → 你的 MCP Server → MySQL → 结果返回 → LLM 汇总回答

4.3 对方使用 NestJS 与 nestjs-llm-tools 能否调用

不能仅靠 nestjs-llm-tools 直接调用 MCP

·         nestjs-llm-tools(registry 当前版本 0.0.3)的作用是把 NestJS 本地方法定义成 LLM function calling 工具,适合做“本地工具注册”。

·         其依赖中没有 @modelcontextprotocol/sdk,因此它本身不是 MCP Client,无法连接并调用你的 MCP 服务。

·         正确接法:对方 NestJS 后端引入官方 MCP Client,用 client.listTools() 获取工具清单,将 schema 转给自家 LLM;模型选定后由后端执行 client.callTool(),再把结果回给模型。

五、鉴权方案:给每个接入系统发放密钥

可以,这也是私有部署最常见的方案:为每个接入方发放一把机器密钥(API Key/Bearer Token),请求时放入 Authorization 头。

·         一把密钥对应一个系统:便于单独吊销、限流与审计,避免所有系统共用同一密钥。

·         服务端只存哈希(bcrypt/scrypt 等),不存明文;密钥只在 HTTPS 通道传输。

·         MCP 端点在执行任何请求前校验密钥,失败返回 401;成功后放行 tools/list 与 tools/call。

·         Codex 注册远程 HTTP MCP 时可用 --bearer-token-env-var 指向环境变量,避免把明文密钥写进配置文件。

·         边界:机器密钥只能标识“哪个系统”,不能区分最终用户;若后续要做用户级数据隔离,需再叠加 JWT/OAuth 用户令牌。

六、Skill、角色与 MCP 的关系

6.1 Skill 能否替代 MCP

不能。 Skill 是给模型的“方法论/操作指南”,不具备进程管理或协议能力,不会自己拉起 MCP 服务,也不能替你完成 tools/list、tools/call。它可以指导模型“遇到这类问题优先用哪个 MCP 工具”,但底层仍需 MCP 已注册并连接。Codex Plugin 可以把 Skill 与 MCP 配置打包成可分发的插件,但运行时仍走 MCP。

6.2 Skill 是否代表“角色”

不准确。角色(Role)定义“以什么身份做、语气与边界”;Skill 定义“这类任务按什么流程做”;MCP 定义“能真正访问哪些外部能力”。三者互补而不是替代关系。

维度角色( Role/Persona SkillMCP Server
本质身份与沟通方式操作指南与领域流程外部工具与数据通道
作用决定以什么身份做决定按什么方法做决定能否真正调用外部系统
例子资深 DBA / 审阅者查询步骤与返回规范query_workflow_steps 等工具

七、结论与后续建议

·         当前 stdio 版 MCP 已能通过官方 MCP Client 调用,适合本机 Codex / 本地客户端接入;冒烟测试已验证工具列表与真实查询。

·         Codex 接入需完成 config.toml 注册并重启/新开任务;注册后自然语言提问即可触发对应工具。

·         要服务多个线上系统,下一步应增加 Streamable HTTP/SSE 端点、每系统独立密钥鉴权、HTTPS、限流与审计。

·         对方 NestJS 后端接入需使用真正的 MCP Client;nestjs-llm-tools 只能用于本地 LLM 工具定义,不能替代 MCP 客户端。

·         Skill、角色与 MCP 分属不同层次:Skill 负责方法,角色负责身份,MCP 负责外部能力连接,互相不能替代。