为什么所有 AI 工具都在用 TypeScript

TypeScript 在 AI 工具领域的主导地位不是开发者偏好驱动的——是 LLM 的认知架构天然偏好了类型系统提供的结构化信息。从 Zod 到 JSON Schema 再到 tool_use,一条无损推导链揭示了 AI 选择 TypeScript 的结构性原因。

封面

上个月我在做一件无聊的事:翻 GitHub 上的 MCP server 仓库。

MCP 发布一年多,GitHub 上已经长出上千个 server 实现。我花了一个下午,按星标从高到低翻了大约三十个。

翻到第二十个的时候,我停下来。

不对劲。

这三十个仓库里,二十多个是 TypeScript 写的。剩下的零星几个 Python,一两个 Go,偶尔一个 Rust。当然,“所有"是夸张了——但比例悬殊到让人想问一个基本问题:为什么?

TypeScript 不是一门"前端语言"吗?AI 的主流生态不是 Python 吗?PyTorch、TensorFlow、LangChain、HuggingFace——AI 世界的基础设施几乎全是 Python 写的。为什么在 AI 工具这个新兴领域,TS 的占有率能碾压式地超过 Python?

我后来花了一些时间研究这件事。结论让我意外——或者说,让我意识到我一直在用错误的框架理解这个问题。我以为这是一个"开发者偏好"问题。实际上,这关乎 AI 怎么理解工具接口。

翻 GitHub 仓库的发现

一、表面答案:所有人都在说的三件事

在你搜索"为什么 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 自身也在"选择"这门语言呢?

Python雾团 vs TypeScript清晰卡片: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 需要猜什么?什么都不用猜。

  • citystring,确定。
  • 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 调用这个工具的成功率就越高。类型约束减少歧义,减少歧义提升成功率。

Zod → JSON Schema → LLM tool_use 推导链

三、证据:一条推导链的完整拆解

在 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"]) 的时候,他同时在做两件事:

  1. 给 TypeScript 编译器一个约束——编译期检查,传错值直接报错(人的工具链)
  2. 给 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 做一次自然语言理解。

MCP 协议中 Zod 到 tool definition 的工程路径

四、生态信号:行业发生了什么

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 理解工具接口”。两者是分层互补的关系。

模型训练 vs 应用编排 vs 工具接口的语言分层

五、边界:为什么不是 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 理论上可以做得更好。

但现实中有两个硬伤:

  1. 学习曲线。同样功能的 MCP server,TypeScript 版本的代码量显著少于 Rust 版本。更重要的是认知负担的差异:TypeScript 开发者只需关注业务逻辑,Rust 开发者还需处理所有权和生命周期。AI 工具生态的竞争是摩擦最小化,开发者想在一小时内写完一个 MCP server,Rust 做不到。
  2. 生态体量。npm 有 200 万+包,crates.io 有 15 万。一个 AI 工具开发者需要 OAuth 集成、数据库连接、JSON 处理,npm 上现成的轮子质量和数量都碾压 crates.io。

你不会用 Rust 写一个"帮我查天气"的 MCP server,就像你不会用 C++ 写一个 todo app。场景不需要那么高的认知成本。

核心论点不变:AI 工具选语言的标准和人的直觉不完全一样。人看性能、安全性、成熟度。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


关于止语Lab

一个工程师的深度技术笔记。

不写入门教程,不追热点。只写那些真正折腾过、想通了的东西。

了解更多 →