
上个月我在做一件无聊的事:翻 GitHub 上的 MCP server 仓库。
MCP 发布一年多,GitHub 上已经长出上千个 server 实现。我花了一个下午,按星标从高到低翻了大约三十个。
翻到第二十个的时候,我停下来。
不对劲。
这三十个仓库里,二十多个是 TypeScript 写的。剩下的零星几个 Python,一两个 Go,偶尔一个 Rust。当然,“所有"是夸张了——但比例悬殊到让人想问一个基本问题:为什么?
TypeScript 不是一门"前端语言"吗?AI 的主流生态不是 Python 吗?PyTorch、TensorFlow、LangChain、HuggingFace——AI 世界的基础设施几乎全是 Python 写的。为什么在 AI 工具这个新兴领域,TS 的占有率能碾压式地超过 Python?
我后来花了一些时间研究这件事。结论让我意外——或者说,让我意识到我一直在用错误的框架理解这个问题。我以为这是一个"开发者偏好"问题。实际上,这关乎 AI 怎么理解工具接口。

一、表面答案:所有人都在说的三件事
在你搜索"为什么 AI 工具用 TypeScript"时,大概率会看到三个答案。
类型安全。TS 的静态类型系统能在编译期捕获错误。对于需要处理复杂数据流的 AI 工具来说,一个 Agent 可能同时编排十几个工具调用,每个调用的参数和返回值格式都不同,类型检查是基础设施。没有类型,你在运行时才知道某个工具返回了 null 而你在对它取属性。有了类型,编辑器直接给你画红线。
全栈统一。前后端都用同一种语言,团队不需要在 Python 后端和 React 前端之间切换。一个人能从数据库查询写到 UI 渲染,中间不用换脑子。YC 最近两批(2025-2026)大量 AI 创业公司选了 TypeScript 做全栈。对于 3-5 人的早期团队来说,少一门语言就是少一个招人岗位。
npm 生态。npm 是世界上最大的包管理器,超过两百万个包。AI 工具需要的各种集成(HTTP 客户端、JSON 解析、OAuth 认证、数据库 ORM、WebSocket、队列)在 npm 上都有现成的轮子。更重要的是 npx,一行命令就能运行一个包,不需要全局安装。这让 AI 工具的分发摩擦接近零。
这三个答案都对。但它们有一个共同的问题:它们解释的是"人"为什么喜欢 TypeScript。
人喜欢类型安全,因为能少写 bug。人喜欢全栈统一,因为能少学一门语言。人喜欢 npm,因为能少造轮子。这些都是开发者体验的视角,DX 视角。从 DX 视角看,TypeScript 确实好。但 DX 好的语言不只 TypeScript 一个。Kotlin 的 DX 也很好,Swift 的 DX 也很好,为什么它们没有在 AI 工具领域占一席之地?
如果我们换一个视角呢?如果 AI 自身也在"选择"这门语言呢?

二、第一性原理:LLM 眼中的世界
让我们从 AI 怎么理解代码说起。
一个大语言模型本质上在做一件事:接收一段 token 序列,预测下一段 token 序列。它不"理解"代码的语义,至少不像人类那样理解。它不会"执行"代码去看结果,至少在理解工具接口定义这件事上不会。它做的是:从输入的结构化信息中,推断出统计学意义上最可能的正确输出。
这意味着一个核心推论:
你给 AI 的信息越结构化、约束越明确,它输出正确结果的概率就越高。
这不是猜测——大量 function calling 的工程实践验证了这一点。当你在 prompt 中写"请输出一个 JSON"时,模型能给出正确 JSON 的概率远高于你写"请输出一些数据”。因为"JSON"这个约束大幅缩小了可能的输出空间。
同样的逻辑适用于 AI 如何"理解"一个函数接口。
想象两个场景。
场景一,你给 AI 看一个 Python 函数:
def get_weather(city, unit="celsius", days=None):
"""获取天气信息"""
...
AI 看到这段代码,需要做多少"猜测"工作?
city的类型是什么?从参数名推断大概是字符串。但万一是城市 ID(整数)呢?AI 不确定。unit只能是 “celsius” 或 “fahrenheit” 吗?还是也接受 “kelvin”?从默认值看只有 celsius,但没有约束说明。AI 不确定。days是什么?天数?日期列表?如果是天数,范围是多少?AI 不确定。- docstring 只写了"获取天气信息"——和没说一样。
AI 面对的是一个充满歧义的接口。它可以猜,而且大概率猜对——但"大概率"和"确定"之间的差距,在工程中就是 bug 率。
场景二,你给 AI 看同一个函数的 TypeScript 版本:
function getWeather(input: {
city: string;
unit: "celsius" | "fahrenheit";
forecast_days?: number; // 1-7
}): Promise<WeatherResult> { ... }
现在 AI 需要猜什么?什么都不用猜。
city是string,确定。unit只能是两个值之一,确定。forecast_days是可选的number,确定。
对 LLM 来说,第二种写法的价值在于"我能确定这段代码要什么"。类型声明消除了歧义,把 AI 的"猜测空间"从一个开放集合压缩到一个封闭集合。
换个角度想:类型声明本质上是一种机器可解析的约束。人类可以通过阅读注释、查看文档、运行代码来理解一个接口。但在理解工具接口这个具体场景里,AI 处理的是纯文本形式的 schema 定义。在这个范围内,结构化的类型声明比自然语言注释高效得多。
结构化表示天然比自由文本紧凑——一个 z.enum(["celsius", "fahrenheit"]) 用远少于自然语言的 token 数就传递了完整的约束信息。而同样的约束写在 docstring 里,“unit 参数只接受 celsius 或 fahrenheit”,不仅更冗长,还需要 AI 从自然语言中额外提取结构化信息。
这就是我说的"AI 选了 TypeScript"的意思。不是有人投票选了它——而是 LLM 的认知架构,天然更擅长处理结构化的类型约束。偏好还是效率?对 AI 来说没区别。
用一个类比:类型声明之于 AI,就像图纸之于施工队。没有图纸你也能盖房子,但有图纸时所有人都知道该做什么。类型声明就是 AI 的工程图纸。
如果你写过 AI Agent 应用,你可能已经有过类似的体感——当你把一个工具的接口定义写得越精确(参数类型、返回值结构、可选性),AI 调用这个工具的成功率就越高。类型约束减少歧义,减少歧义提升成功率。

三、证据:一条推导链的完整拆解
在 MCP 协议、OpenAI 的 function calling、Claude 的 tool use 中,AI 调用外部工具时都需要一个东西:JSON Schema。
JSON Schema 是一种描述 JSON 数据结构的标准格式。当你告诉 Claude “你可以调用一个查天气的工具"时,你实际上发送的是一段 JSON Schema,告诉它:“这个工具接受一个 city 参数(字符串)、一个 unit 参数(只能是 celsius 或 fahrenheit)、一个可选的 days 参数(1-7 的整数)"。
这是 AI 理解"这个工具能干什么、需要什么"的唯一途径。不是 docstring,不是注释,不是 README——是 JSON Schema。
现在,问题变成了:哪门语言能最自然地生产 JSON Schema?
答案是 TypeScript。具体来说,是 TypeScript + Zod。
看看从 TypeScript 代码到 AI 可调用的 tool 之间,整条链路发生了什么:
开发者写 Zod schema → 自动转 JSON Schema → 直接作为 LLM tool_use 的 input_schema
让我拆开给你看。
第一步:开发者用 Zod 声明参数类型。
import { z } from "zod";
const WeatherInput = z.object({
city: z.string().describe("城市名称,如 Beijing"),
unit: z.enum(["celsius", "fahrenheit"]).default("celsius")
.describe("温度单位"),
forecast_days: z.number().min(1).max(7).optional()
.describe("预报天数,1-7"),
});
注意这段代码做了什么:它同时声明了类型(string/enum/number)、约束(min/max/enum 值)、描述(.describe())和可选性(.optional())。所有信息在一个地方,格式统一。
第二步:一行代码,自动变成 JSON Schema。
import { zodToJsonSchema } from "zod-to-json-schema";
const schema = zodToJsonSchema(WeatherInput);
生成的 JSON Schema:
{
"type": "object",
"properties": {
"city": { "type": "string", "description": "城市名称,如 Beijing" },
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"default": "celsius",
"description": "温度单位"
},
"forecast_days": {
"type": "number",
"minimum": 1,
"maximum": 7,
"description": "预报天数,1-7"
}
},
"required": ["city"]
}
第三步:这段 JSON Schema 直接就是 Claude/GPT 接收到的工具定义。
没有翻译,没有额外转换,没有信息丢失。开发者用 TypeScript 写的每一个类型约束——字符串、枚举值、数值范围、可选性——一字不差地传递给了 AI。
你意识到这意味着什么了吗?
意味着当开发者写 z.enum(["celsius", "fahrenheit"]) 的时候,他同时在做两件事:
- 给 TypeScript 编译器一个约束——编译期检查,传错值直接报错(人的工具链)
- 给 LLM 一个约束——AI 知道这个参数只有两个合法值(AI 的工具链)
同一段代码,同时服务人和 AI,核心约束信息几乎无损传递。 这种契合不是有意设计的,但结构上确实合理——Zod 最初是为 TypeScript 的运行时校验设计的,zodToJsonSchema 是后来社区加的转换层。TypeScript 类型系统和 JSON Schema 之间存在结构性的亲缘关系,Zod 只是把它暴露出来了。
我第一次真切体会到这种差异,是在调试一个 Agent 应用的时候。AI 传了一个 “kelvin” 给 unit 参数,运行时才报错。回头看接口定义,果然是 docstring 里漏写了约束——如果用的是 Zod schema,这种情况根本不会发生。
现在让我们看 Python 的等价实现。Python 的 MCP SDK 用 decorator + type hints:
@mcp.tool()
async def get_weather(
city: str,
unit: str = "celsius",
days: int | None = None
) -> dict:
"""获取天气预报。
Args:
city: 城市名称
unit: 温度单位,celsius 或 fahrenheit
days: 预报天数,1-7
"""
...
这段代码的问题在哪?
unit: str告诉 AI 这是一个字符串。但"只能是两个值之一"这个约束,藏在 docstring 的自然语言里。AI 需要从 “温度单位,celsius 或 fahrenheit” 这句话中解析出枚举值。days: int | None没有 min/max。AI 不知道上限是 7——它可能传 30,运行时才报错。- 返回值是
dict——等于没说。什么结构的 dict?有哪些字段?AI 一无所知。
Python 当然能做到和 Zod 一样精确。你可以用 Pydantic:
class WeatherInput(BaseModel):
city: str = Field(description="城市名称")
unit: Literal["celsius", "fahrenheit"] = "celsius"
days: Optional[int] = Field(None, ge=1, le=7, description="预报天数")
这和 Zod 几乎等价。但关键在于:Python SDK 的入门教程以 decorator + docstring 为主要示范方式(虽然也支持 Pydantic 集成)。而在 TypeScript 生态中,Zod 就是 MCP SDK 的标准做法——你用 @modelcontextprotocol/sdk,第一个接触到的就是 Zod schema。
这是哪门语言的默认工程实践,天然生产 AI 可消费的结构化信息的问题。TypeScript 的自然写法产出 JSON Schema。Python 的自然写法产出自然语言注释。前者是机器可解析的;后者需要 AI 做一次自然语言理解。

四、生态信号:行业发生了什么
Anthropic 的 MCP 官方示例仓库中,TypeScript 实现是主推路径。SDK 发布时 TypeScript 版本的文档完整度和示例丰富度明显更高。官方文档中的入门教程、示例代码,TS 版本更完整。协议设计者在工程层面认定 TS 是最佳匹配。
GitHub 上搜索 “mcp server”(2026 年 4 月),按星标排序,前 30 个仓库中 TypeScript 占比超过 70%。翻到第二页,比例依然悬殊。飞轮开始转了——当官方 SDK 和早期社区惯例都指向 TS 时,生态的聚集效应是自我强化的:更多 TS 实现 → 更多 TS 示例 → 新人更容易用 TS 上手 → 更多 TS 实现。
大量 YC AI 创业公司选择了 TypeScript 做应用层开发。这些公司的技术选型考量很现实:3 人团队、3 个月内把 AI 产品推向市场。答案不约而同指向了 TypeScript。
有意思的是,这些创业公司很多在模型训练侧仍然用 Python(因为 PyTorch/TensorFlow 的地位不可动摇),但在应用侧(Agent 逻辑、工具编排、API 集成)一致选了 TypeScript。
这形成了一个清晰的行业分层:
| 层面 | 主流语言 | 核心原因 |
|---|---|---|
| 模型训练 | Python | PyTorch/TensorFlow 生态不可替代 |
| 应用编排(Agent 逻辑) | TypeScript | 类型系统 + 全栈统一 + npm 生态 |
| 工具接口(MCP/Plugin) | TypeScript | Zod → JSON Schema 的天然契合 |
| 前端展示 | TypeScript | React/Next.js 的 Web 原生优势 |
训练层和应用层的分裂,恰恰验证了核心论点:Python 的优势在"跑模型”,TypeScript 的优势在"让 AI 理解工具接口”。两者是分层互补的关系。

五、边界:为什么不是 Python、Go、Rust?
让我正面回应三个最常见的反对意见。
“Python 在 AI 领域生态更成熟,为什么不用 Python?”
需要精确界定"AI 领域"。如果你说的是模型训练和数据科学,Python 不可替代,我完全同意。PyTorch 的灵活性、NumPy 的性能、Pandas 的便利性,这些在 TypeScript 世界没有对等物。
但本文讨论的是 AI 工具的应用层。在应用层,Python 的动态类型反而成了劣势。def foo(x, y) 不告诉 AI 任何有用的结构信息,除非你额外加 type hints + Pydantic + 完善的 docstring。而 TypeScript 的类型声明是自然做法——你不需要额外做什么,正常写代码就在生产 AI 可消费的结构化信息。
此外还有一个现实问题:Python 的依赖管理。virtualenv、conda、poetry、pip,包管理器比语言本身还分裂。一个用户想运行你的 MCP server,先得搞清楚用 pip 还是 conda,然后处理依赖冲突。TypeScript 的方案?npx your-mcp-server,一行搞定,零安装摩擦。
有人会说:LLM 对代码的理解能力在快速进步,语言间的差异正在缩小。这说得对——但这里讨论的不是"AI 能不能理解 Python 代码"(它当然能),而是"哪门语言的常规工程实践天然产出 AI 可直接消费的结构化接口定义"。LLM 进步消除的是理解层的差异,消除不了工程链路上的结构性优势。
“Go 性能更强,为什么不用 Go?”
Go 确实在高并发场景比 Node.js 强得多。编译成单个二进制文件的部署便利性也是实打实的优势。但 AI 工具的性能瓶颈不在 CPU,在网络 I/O。一个 MCP server 大部分时间在等 LLM API 返回(通常 1-30 秒),Node.js 的事件驱动模型处理这种 I/O 密集场景绰绰有余。
更关键的问题是 Go 的类型系统。Go 有静态类型,但缺少一些对 AI 而言很重要的表达力:
- 没有 union type(
"celsius" | "fahrenheit"在 Go 里得靠常量+手动校验) - 语言原生不支持编译期穷举检查(需依赖第三方 lint 工具)
- 没有内建的 “description” 机制(需要靠 struct tag 或注释)
对 AI 来说,“有类型"不够——需要"表达力足够丰富的类型”。Go 的类型系统偏实用主义,够用但不够细致。TypeScript 的类型系统偏表达主义——union、intersection、conditional type、template literal type——这些在给 AI 描述约束时极其有用。
“Rust 更安全,为什么不用 Rust?”
Rust 的类型系统表达力甚至比 TypeScript 更强。enum 是 sum type,pattern matching 是穷举的。从纯粹的"类型表达力"角度,Rust 理论上可以做得更好。
但现实中有两个硬伤:
- 学习曲线。同样功能的 MCP server,TypeScript 版本的代码量显著少于 Rust 版本。更重要的是认知负担的差异:TypeScript 开发者只需关注业务逻辑,Rust 开发者还需处理所有权和生命周期。AI 工具生态的竞争是摩擦最小化,开发者想在一小时内写完一个 MCP server,Rust 做不到。
- 生态体量。npm 有 200 万+包,crates.io 有 15 万。一个 AI 工具开发者需要 OAuth 集成、数据库连接、JSON 处理,npm 上现成的轮子质量和数量都碾压 crates.io。
你不会用 Rust 写一个"帮我查天气"的 MCP server,就像你不会用 C++ 写一个 todo app。场景不需要那么高的认知成本。
核心论点不变:AI 工具选语言的标准和人的直觉不完全一样。人看性能、安全性、成熟度。AI 看的是一件更具体的事:你的类型系统能不能让我确定你要什么、你能给什么。

结尾:回到那个下午
翻 GitHub 的那个下午,我最初以为自己在看一个"社区偏好"问题——就像十年前 Ruby 社区偏好 Rails、Go 社区偏好微服务、Rust 社区偏好命令行工具。
但这次不一样。
TypeScript 在 AI 工具领域的主导地位,来自一个结构性的契合——当 LLM 需要通过 JSON Schema 理解工具接口时,能自动且几乎无损地生成 JSON Schema 的语言,天然占了先手优势。某个框架带起来的风潮做不到这种深度绑定,某个 KOL 的推荐效应也做不到。
这当然是一种拟人化的说法——更准确地讲,是 LLM 的认知架构天然偏好了 TypeScript 提供的结构化信息。但如果允许我用一句话概括:
不是人选了 TypeScript。
是 AI 选了它。
而当 AI 用得越多、生态越成熟、文档和示例越丰富,人也跟着用——这个飞轮一旦转起来,其他语言要追赶就不只是"写一个更好的 SDK"的事了。而 AI 编码助手在 TS 代码上训练得越多,生成 TS 的质量就越高——这又进一步降低了开发者选择 TS 的门槛。你需要改变整个语言的自然做法——让类型声明变成每个开发者的本能习惯,而不是一个需要额外学习的最佳实践。
当我们讨论 AI 工具的语言选型,真正的问题从来不是"开发者喜欢什么",而是"AI 能从你的代码中读出多少确定性"。这两个问题的答案,恰好指向同一门语言。
如果你正在写 AI 工具,不妨打开你的接口定义看一眼:AI 能从中读出多少确定性?如果答案是"不够",也许是时候让类型替你说话了。
原文发布于 止语Lab