TypeSpec 打造 API-First MCP 服务器:@tool 装饰器、JS 发射器与 Emitter 框架实践
【免费下载链接】typespec项目地址: https://gitcode.com/GitHub_Trending/ty/typespec
MCP(Model Context Protocol)服务器正在快速进入各类智能体工作流。本文基于 TypeSpec 官方博客《API-first MCP servers with TypeSpec》(2025-05-30 发布,作者 Brian Terlson),讲解如何用 TypeSpec 以"API 优先"的方式定义 MCP 服务器与工具:通过@tool、@mcpServer等装饰器描述服务端点,再用tsp compile配合typespec-mcp-server-js发射器生成 JavaScript 服务器骨架,最终只需实现工具业务逻辑即可得到可运行的 MCP 服务器。读完本文,你将理解"规范 + 确定性代码生成 + AI 编写业务逻辑"这一组合为何比纯手写更可靠,并能结合本仓库源码理解其背后的新 Emitter Framework 组合式架构。
背景:为什么选择 API-First 的 MCP 服务器
官方 SDK 和社区框架都能构建 MCP 服务器,但 TypeSpec 团队更关注"从 API 描述生成 MCP 服务器"这条路线。其动机在于:MCP 服务器的协议处理(工具请求的分发、输入输出校验、JSON 编组)属于高度模式化、可确定性生成的部分,而真正需要人工(或智能体)思考的只有工具自身的业务逻辑。TypeSpec 作为一门简洁、人类可读的 API 描述语言,天然适合承担这份"契约"的角色。
TypeSpec 团队当时正在预发布一组 MCP 项目以收集反馈,包括四个包(发布在独立的 typespec-mcp 项目中):
| 包名 | 角色 |
|---|---|
typespec-mcp | 用 TypeSpec 描述 MCP 服务器的词汇库(decorators + 内置类型 + typekit) |
typespec-mcp-server-js | TypeSpec 发射器:从 TypeSpec 生成 JavaScript MCP 服务器 |
typespec-http-mcp-server-js | TypeSpec 发射器:为用 TypeSpec 编写的 REST API 生成 MCP 服务器 |
mcp-server-typespec | 帮助智能体构建 TypeSpec 项目的 MCP 服务器 |
快速上手:从 TypeSpec 到可运行的 MCP 服务器
官方示例定义了一个向量运算服务器。给定如下 TypeSpec:
import "typespec-mcp"; using MCP; @mcpServer(#{ name: "VectorMCP" }) namespace VectorMCP; model Vec3 { x: int32; y: int32; z: int32; } @tool op addVector(v1: Vec3, v2: Vec3): Vec3; @tool op subVector(v1: Vec3, v2: Vec3): Vec3;要点说明:
@mcpServer装饰器(属性绑定语法#{ name: "VectorMCP" })把命名空间标记为 MCP 服务器,name即服务器名称;- 每个需要对外暴露为 MCP 工具的操作前加
@tool; - 参数与返回值使用普通 TypeSpec 模型(如
Vec3),其结构即构成工具的 JSON Schema 契约。
运行tsp compile并使用typespec-mcp-server-js发射器后,生成的服务器骨架已处理完所有 MCP 协议细节,你只需要实现工具处理器:
import { setToolHandler } from "#mcp-server"; setToolHandler({ async addVector(v1, v2) { return { x: v1.x + v2.x, y: v1.y + v2.y, z: v1.z + v2.z, }; }, subVector(v1, v2) { return { x: v1.x - v2.x, y: v1.y - v2.y, z: v1.z - v2.z, }; }, });注意setToolHandler接收的对象形状与 TypeSpec 中操作签名一一对应——参数与返回类型已由生成的 TypeScript 接口和 Zod schema 约束,业务代码完全不需要关心 MCP 协议本身。
核心理念:AI 时代,代码生成与智能体是"好朋友"
博客专门用一节回应了"AI 时代为什么要写规范再走代码生成"的疑问,其论证链条值得完整保留:
- 代码生成为智能体提供了护栏(guiderails)。TypeSpec 规范以简洁、人类可读的形式描述"到底要实现什么",先迭代规范再让智能体写代码,意图表达会更精确,vibe coding 的生产率反而更高。
- 生成的代码是确定性的。从规范到协议处理代码是完全确定的映射:正确响应每个工具请求、按严格 schema 校验输入输出、完成 JSON 等格式的编组。这些代码智能体也能写,但容易写错,且复杂度越高越需要仔细验证;而确定性生成天然规避了这类风险。
- 业务逻辑实现成本更低。如上面的示例,所有 MCP 协议细节都被抽象掉了——智能体不需要了解 MCP 协议、不需要研究 MCP SDK,只需在完全有 TypeScript 文档约束的 API 契约上实现逻辑,这是它非常擅长的事。简单的提示词往往能一次成功,且上下文与 LLM 输出量更小,token 消耗也更低。
结论是:TypeSpec 与代码生成在"智能体写代码"场景下组合起来特别有用——显式声明你要构建什么,再生成让智能体更容易写对的护栏。
预发布包详解
typespec-mcp:MCP 服务器词汇库
该库提供定义 MCP 服务器与工具的装饰器:
@tool:把某个操作声明为 MCP 工具。如果给工具写了文档注释(doc comment),它会直接用作该工具的描述;@summary:提供简短描述(TypeSpec 内置装饰器);@readonly、@nondestructive、@idempotent、@closedWorld:一组工具注解,用于向调用方(智能体)传达工具的副作用特征,帮助智能体安全地决定何时调用工具;- 内置常用类型:覆盖 MCP 请求与结果的各类标准类型,包括用于工具调用响应的
TextResult、ImageResult、AudioResult、ResourceResult,以及用于描述服务器可能抛出的错误的MCPError; - typekit 支持:库为发射器作者提供了 typekit,让第三方可以基于该词汇库构建自己的代码生成器。
typespec-mcp-server-js:MCP 服务器发射器
这是核心发射器:输入使用typespec-mcp词汇库编写的 TypeSpec,输出的 JavaScript 项目包含四个部分:
- MCP 工具定义——每个
@tool操作对应的工具元数据; - MCP 工具处理器——负责请求/响应编组(marshalling),并把你调用的请求分发到你的实现;
- 所有数据类型的 Zod schema——覆盖请求与响应;
- 所有数据类型的 TypeScript 接口。
使用上你只需要导入setToolHandler和server,Zod schema 与 TS 接口也一并导出,按需取用。
该发射器还是可扩展的:它开放了调度(dispatch)代码生成的钩子,允许你以完全自定义的方式处理 MCP 工具调用。官方博客提到的typespec-http-mcp-server-js正是利用了这一能力,把默认调度器替换为直接调用你的 REST 服务器 HTTP 端点的调度器。
typespec-http-mcp-server-js:REST API 直通 MCP
这个发射器生成一个完全可用的 MCP 服务器,其职责是把工具调用代理(proxy)到 REST 服务器的端点上。使用方式极简:在一个 HTTP 操作上加上@tool,其余工作全部由发射器完成——这意味着任何用 TypeSpec 描述的 REST API 都能"顺带"变成一个 MCP 服务器,而不必重写任何业务端点。
mcp-server-typespec:为构建 TypeSpec 项目而生的 MCP 服务器
团队用上述能力反过来构建了一个服务于 TypeSpec 自身的 MCP 服务器,供智能体在开发 TypeSpec 项目时使用。它提供四个工具:
learnTypeSpec——向模型注入理解与编写 TypeSpec 的入门信息;init——在当前工作目录脚手架出一个带示例工具实现的新项目;compile——运行tsp compile生成发射器产物;build——在当前项目中执行npm run build。
这形成了一个有趣的闭环:用 TypeSpec 生成 MCP 服务器,再用这个 MCP 服务器加速 TypeSpec 项目的构建。
纵深:新 Emitter Framework 的组合式架构
博客最后专门说明:TypeSpec 1.0 之后团队一直在构建新的发射器框架,而这次预发布的所有 MCP 发射器全部基于新框架实现。对本仓库的读者来说,这正是可以落到本地源码验证的部分。
框架核心位于 emitter-framework 包(@typespec/emitter-framework),采用基于组件的构建方式:
- 入口结构:核心模块由 src/core/index.ts 导出,包括组件(
components)、上下文(context)、SCC 集(scc-set)、传输名策略(transport-name-policy)、类型连接器(type-connector)与输出写入(write-output); - 上下文与 Typekit 的绑定:在 src/core/context/tsp-context.ts 中,
TspContext通过createNamedContext建立,useTsp()钩子从上下文中取出编译产物Program,并在需要时惰性创建 Typekit 实例($(context.program))供下游组件查询类型信息。Typekit 是编译器侧的类型查询工具包,也是typespec-mcp词汇库向第三方发射器开放的能力基础; - 组件与覆盖机制:src/core/components/index.tsx 导出
output组件与overrides覆盖机制(含component-overrides、config),这正是博客所说的"组合"(composition)能力的落点——一个发射器可以暴露可复用组件,让另一个发射器在自己的项目里复用其代码生成逻辑。比如typespec-mcp-server-js允许替换默认调度器的钩子、Zod 发射器既可以独立把类型转换为 Zod schema 也可以作为库嵌入其他发射器; - 多语言模块:框架按目标语言划分模块(
typescript、python、csharp等,见 package.json 的exports字段),MCP 的 JS 发射器即构建在 TypeScript 模块之上。
从源码结构看,这套"上下文 + 组件 + 可覆盖(override)"的设计与 React 类框架的组织方式一致:TspContext.Provider提供全局编译上下文,OverridesContext允许子发射器对父发射器的组件行为做定向替换——typespec-http-mcp-server-js替换调度器、typespec-mcp-server-js复用 Zod 组件,都是这一机制的实际用例。
路线图与限制
博客明确交代了当时的状态与后续计划,引用时应注意这些是预发布(preview)阶段的规划:
typespec-mcp词汇库与typespec-mcp-server-js发射器计划新增对 resources 与 prompts 的支持(MCP 协议除 tools 外的另外两类原语);- 编组(marshalling)工作仍在进行,例如把日期时间字符串转换为 Temporal 对象;
typespec-http-mcp-server-js会继续迭代,目标是让任何用 TypeSpec 定义的 REST API 都能顺带生成一个功能完整的 MCP 服务器;- 博客原文发布于 2025-05-30,文中所述 API 形态对应当时的预发布版本,具体装饰器与包能力请以 typespec-mcp 项目当时的最新文档为准。
小结
这篇文章给出的核心方法是:把 MCP 服务器当作一份 API 规范来写——@mcpServer声明服务器、@tool声明工具、模型与注解描述契约;tsp compile确定性地生成工具定义、调度器、Zod schema 与 TS 接口;智能体(或人)只在setToolHandler里实现纯业务逻辑。再叠加"REST 操作加@tool即代理成 MCP 工具"的路线,任何存量 API 规范都能低成本获得 MCP 形态。而支撑这一切可组合、可扩展发射器的,是本仓库 emitter-framework 包 所实现的组件化新框架——这也是 TypeSpec 生态在 1.0 之后的重要演进方向。
【免费下载链接】typespec项目地址: https://gitcode.com/GitHub_Trending/ty/typespec
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考