关于 Angular token saver
Angular token saver 是面向 Angular 项目编码 Agent 的 MCP 服务,借助项目自身的 Angular 编译器和版本信息回答模板与组件问题。它提供组件契约、模板诊断、符号定位和版本规则,避免仅凭文件文本推断。
主要功能
- 01定位模板符号的声明
- 02返回模板编译诊断
- 03提取组件与指令契约
- 04映射工作区和 Angular 版本
- 05查询版本对应的 API 规则
- 06查找组件及输入的用法
适用场景
- 01确认模板属性或方法的声明位置
- 02诊断指定模板或文件批次的错误
- 03查看继承链中的组件公开成员
- 04重命名前定位组件及输入绑定处
MCP 相关
作为 MCP 服务向客户端提供六种 Angular 分析工具;其中模板定位和诊断依赖语言服务器,其余工具无需启动语言服务器。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
npx -y @alex-apps/ng-token-saverNG_TOKEN_SAVER_IDLE_MS密钥Idle milliseconds before a language server session shuts down; 0 disables the shutdown. Default 900000.NG_TOKEN_SAVER_PREWARM密钥1 lets ng_workspace_map with a path inside an app load that app into the language server in the background. Costs ~1 GB of memory until the idle shutdown.NG_TOKEN_SAVER_TRACE密钥Path to a file to append raw LSP traffic to, for debugging.本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
工具
6收录的工具列表 · 更新于 3 周前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| file | string | 是 | 模板路径:.html,或包含内联模板的 .ts |
| line | integer | 是 | |
| symbol | string | 否 | 该行上的名称:userName、app-user-card、date |
| character | integer | 否 | 当名称在该行重复时,使用从 1 开始的列号代替符号 |
详细介绍
ng-token-saver
一个 MCP 服务,让 AI Agent 可以向构建项目所用的同一编译器提问,从而理解 Angular 模板,而不是根据文件文本猜测。节省的 token 数经过了测量,并非空口承诺。
Angular 提供了一流的语言服务,但没有官方的 AI 集成能调用它——Angular CLI MCP(ng mcp)只处理文档和构建,不会触及模板。这个服务正是缺失的那一层,并且它会根据项目实际运行的 Angular 版本作答。
以下标注为实测的数据,均来自在六个真实 Angular 工作区(17.3.12、18.2.14、19.2.25、20.3.26、21.2.18、22.0.8)和两个生产项目中运行代码。所有数据都可以通过复现测量结果中的命令重新获得。
快速开始
语言服务作为常规依赖提供——除了安装这个包之外,无需安装其他内容:
npm install -g @alex-apps/ng-token-saver
Claude Code:
claude mcp add ng-token-saver -- ng-token-saver
Codex CLI:
codex mcp add ng-token-saver -- ng-token-saver
其他任何 MCP 客户端:
{ "mcpServers": { "ng-token-saver": { "command": "ng-token-saver" } } }
也可以跳过安装,让客户端通过 npx 获取:
{ "mcpServers": { "ng-token-saver": { "command": "npx", "args": ["-y", "@alex-apps/ng-token-saver"] } } }
在 Cursor 中,只需单击一下即可使用 npx 方式:
npm 和 npx 两种方式均已验证:将打包后的 tarball(79 kB,仅含 dist)安装到干净的前缀目录后,四种工具类型都能通过真实 MCP 客户端正常响应;使用预热的 npm 缓存时,npx 方式可在 1.2–2.0 s 内连接(机器上的首次运行还会下载依赖树——仅语言服务解包后就有 13.6 MB)。
可以先问它一个问题——询问组件的契约,而其成员分散在 extends 链中。对于 fixtures/v17/src/app/derived-card.component.ts(本仓库中的一个 fixture),ng_component_info 给出的原样答案如下:
{"found":true,"angularVersion":"17.3.12","className":"DerivedCardComponent","kind":"component","selector":"app-derived-card","standalone":true,"inlineTemplate":true,"styleUrls":[],"imports":[],"hostDirectives":[],"extends":"BasePanel","ancestors":[{"name":"BasePanel","file":"<repo>\\fixtures\\v17\\src\\app\\base-panel.ts"},{"name":"BaseWidget","file":"<repo>\\fixtures\\v17\\src\\app\\base-widget.ts"}],"inputs":[{"name":"accent","type":"boolean"},{"name":"heading","type":"string"},{"name":"disabled","type":"boolean"}],"outputs":[{"name":"blurred","type":"void"}],"publicMembers":[{"name":"focus","kind":"method","signature":"focus(): void","noop":true},{"name":"collapse","kind":"method","signature":"collapse(animated: boolean): void","noop":true}]}
使用此检出的绝对路径替换 <repo> 后,输出有 811 个字符;会话首次调用耗时 305 ms(需要加载项目自己的 TypeScript),之后只需个位数毫秒。所询问的文件声明了一个输入和一个 extends 子句;heading、disabled、输出以及两个方法都位于 BasePanel 和 BaseWidget 中,并通过静态解析得出,ancestors 中列出了它们所在的文件,便于进行下一次编辑。focus() 上的 "noop": true 表示子类用空方法体覆盖了它——否则,要了解这一点就得为每个祖先读取整个文件。
Cursor、VS Code、Windsurf、Codex CLI 和 JetBrains 的配置、Node 的最低版本要求以及从源码运行的方法,都在要求和设置中。
它解决的两个问题
1. 不理解模板。 对 .html 文件执行 grep 无法告诉你 {{ user().fullName }} 是在哪里声明的;无论读多少内容,也无法得知 TS2339 Property 'emailAddress' does not exist on type 'UserVm'。这是编译器输出,不是文本。
2. 版本漂移。 angular.dev 上的 AI 上下文文件(llms.txt)只描述最新的主版本,且没有版本标记;版本归档站点则完全不提供这些文件。对于 v17–v21,它们会给 Agent 提供一些指令,导致生成并不存在的 API。实测示例见版本事实。
工具
共有 6 个工具,描述总计 1040 个字符。回答是密集的 JSON,不含 Markdown。
| 工具 | 回答的问题 | 是否需要语言服务器 |
|---|---|---|
ng_template_definition | 模板某一行上的符号在哪里声明——行号按 Read 显示,符号按名称指定 | 是 |
ng_template_diagnostics | 模板或 files 批次的 Angular 编译器错误;锚定在配套 .ts 文件中的条目会带有 file,在多行重复出现的消息只返回一次,并附上对应的 lines | 是 |
ng_component_info | 组件或指令的公开契约 | 否 |
ng_workspace_map | 每个项目的项目、版本、strictTemplates 和 zone.js | 否 |
ng_version_rules | 当前项目的 Angular 版本中有哪些内容,以及没有哪些内容 | 否 |
ng_find_usages | 组件、指令、管道或服务在哪里使用;提供 input 时,返回该输入绑定的位置 | 否 |
6 个工具中有 4 个从不启动语言服务器,因此能在毫秒内给出回答,也能在服务器拒绝加载的工作区中继续工作。
从 0.2.0 开始,所有位置都从 1 开始计数,与 Read 显示一致:ng_template_definition 接收这样的行号,并按名称指定符号(当名称在该行重复时,可用 character 替代)。0.1.x 使用从 0 开始计数的 line 和 character,遵循 LSP 约定——现在相同的数字会指向下一行。
实测:返回契约,而非整个文件
ng_component_info 返回组件的公开契约,而不是其源代码。通过真实 MCP 客户端,在两个生产代码库中进行测量:
| Nx 单体仓库 | CLI 工作区* | |
|---|---|---|
| Angular / TypeScript | 19.2.18 / 5.8.3 | 17.3.8 / 5.3.3 |
| 统计中的组件数 | 1298 | 407 |
| 解析错误 | 0 | 0 |
| 源代码 | 5 404 708 个字符 | 1 735 223 个字符 |
| 契约(包括基类成员) | 1 759 331 个字符 | 463 563 个字符 |
| 节省 | 67% | 73% |
| 节省的 Token 数(o200k_base 代理值) | 67% | 71% |
| 契约比源代码短 | 1201 / 1298(93%) | 386 / 407(95%) |
| 标记为不完整 | 1 / 1298 | 3 / 407 |
| 首次调用(加载项目的 TypeScript) | 306 毫秒 | 564 毫秒 |
* 使用 0.1.2 之前的线格式测量;当前格式更精简,因此这里的节省量是下限。
单体仓库中最大的组件从 177 863 个字符缩减到 10 770 个字符,同时列出 117 个契约成员。契约包含从基类继承的成员——继承链会通过相对导入、tsconfig 路径别名和 barrel 文件解析。在添加该解析器之前,单体仓库中有 1298 个契约中的 91 个被标记为不完整;现在只有 1 个。
这些数据有三个注意事项:
- Token 数通过代理值统计——使用 OpenAI 的
o200k_base,因为 Claude 的分词器并未公开;在这组数据上,cl100k_base的结果相差不超过 1 个百分点。字符数则是精确值。 - 基准是读取整个文件,这也是 Agent 默认的做法。
- JSON 的分词效果略差于 TypeScript,因此 Token 节省量比字符节省量低一到两个百分点。表中同时列出了两者。
对于小型组件,完全没有节省:一个 17 行的组件生成的契约有 578 个字符,而源代码只有 315 个字符。契约长度随成员数量增长,源代码长度随方法体增长——在生产代码中,后者几乎总是更占优势。
实测:重命名与诊断
跨用法重命名输入。 对比 Agent 实际采用的 grep 路径——先读取组件文件以了解选择器,再在整个代码库中 grep 选择器和绑定写法——与 ng_component_info 加上带有 input 筛选条件的 ng_find_usages。后者只返回绑定该名称的标签,每个条目都指向绑定本身。在生产级单体仓库中,共扫描 6243 个文件:
| 组件 | grep 路径 | 桥接方案 | 节省(o200k) |
|---|---|---|---|
577 个用法,mask 绑定在 5 个标签上 | 50 527 个 token | 1 035 个 token | 98% |
466 个用法,icon 绑定在 463 个标签上 | 41 698 个 token | 22 566 个 token | 46% |
1211 个用法,name 绑定在 1134 个标签上 | 129 227 个 token | 30 650 个 token | 76%* |
* 工具每次回答最多返回 500 个条目,并会说明这一点。
节省多少取决于实际绑定该输入的用法数量:mask 只绑定在 568 个标签中的 5 个上,而 grep 仍会打印每一行选择器,以及整个代码库中 67 行形式类似绑定的内容——其中 62 行的 mask 属于其他内容;桥接方案则只回答这五处。
诊断无法编译的模板。 整个项目的编译器诊断列表需要 395 个 token 和 1321 毫秒;调用一次 ng_template_diagnostics,即可用 81 个 token、预热后 1 毫秒返回指定文件的诊断信息。项目规模越大,差距就越明显:列表会随项目增长,回答则不会。编辑后,新的诊断会在 340–400 毫秒内推送到达——无需重新构建。
版本事实
ng_version_rules 中没有任何规则取自文档:数据来自导入每个 fixture 中实际安装的包并运行编译器。
无 Zone 提供器在 v19 和 v20 之间更名。
| API | v17 | v18 | v19 | v20 | v21 | v22 |
|---|---|---|---|---|---|---|
provideExperimentalZonelessChangeDetection | – | 是 | 是 | – | – | – |
provideZonelessChangeDetection | – | – | – | 是 | 是 | 是 |
不注明版本就建议“启用无 Zone”,在六个主要版本中的三个版本上会出错。
存在不等于已就绪。 @experimental 和 @developerPreview 标签只存在于声明 JSDoc 中,运行时不可见:
| API | v17 | v18 | v19 | v20 | v21 | v22 |
|---|---|---|---|---|---|---|
input、output、model、viewChild、contentChild | 预览 | 预览 | 稳定 | 稳定 | 稳定 | 稳定 |
effect、toObservable | 预览 | 预览 | 预览 | 稳定 | 稳定 | 稳定 |
linkedSignal、afterRenderEffect | – | – | 预览 | 稳定 | 稳定 | 稳定 |
resource、rxResource、httpResource | – | – | 实验性 | 实验性 | 实验性 | 稳定 |
因此,在 v17 或 v18 项目中“将 @Input() 改写为 input()”,意味着转而使用非公开 API;而 resource() 一直到 v21 都是实验性的。一批 Signal API 恰好在 v19 出现:linkedSignal、resource、rxResource、httpResource、afterRenderEffect、provideAppInitializer。
两项与实测相矛盾的文档说法: standalone 在 v19 而不是 v20 成为默认值;*ngIf 并未在 22.0.8 中移除——它会报告提示 TS6385(TypeScript 的弃用提示),但仍可正常使用,且 NgIf 仍从 @angular/common 导出。
编译器门槛,从 22.0.8 bundle 中读取并通过运行确认——根据项目的 Angular 版本判断,服务会从 tsconfig 同级的 @angular/core 中检测版本(也会传入 --angularCoreVersion 作为后备值):
| 功能 | 门槛 |
|---|---|
@if / @for / @switch 块 | ≥ 17.0.0 |
| 双向绑定中的信号 | ≥ 17.2.0-0 |
@let | ≥ 18.1.0 |
隐式 standalone | ≥ 19.0.0 |
| DOM 事件类型断言 | ≥ 20.2.0 |
其他实测结果:Signal Forms(@angular/forms/signals)仅从 v21 开始存在,并在 22.0.8 中稳定;AbstractControl.events 从 v18 开始存在;TestBed.tick 从 v20 开始存在。
对于实测范围 v17–v22 之外的版本,ng_version_rules 不会返回任何内容,并会明确说明这一点——推断“v22 中有,所以 v23 中也有”,正是它要防止的错误。
将坦诚作为一项功能
如果回答不完整,就会在回答中明确说明:
- 契约会合并基类成员和宿主指令暴露项,并通过相对导入、tsconfig 别名和 barrel 静态解析。遇到无法继续遍历的情况——例如来自软件包的基类或 mixin 调用——回答会附带
incomplete,指出类名以及接下来应检查的文件。 ng_version_rules会报告notMeasured主题;如果你的次版本号与实测版本不同,还会提供caveat。ng_find_usages会将声明标记为declaration;如果找到类名匹配项但未解析导入,也会明确说明;并会指出选择器同名项——即工作区其他位置存在的同一选择器的第二个声明——而不是悄悄混合它们的用法。ng_template_diagnostics会区分三种看起来都是空列表的状态:模板无问题;服务已静默停止运行(由 canary 探测捕获);或者此项目已关闭模板检查——此时回答会附带checksDisabled,指出对应的 tsconfig。在实测的单体仓库中,七个应用里有两个未启用strictTemplates,因此这种区分并非纸上谈兵。
这不是……
- 在查找用法方面,并不比仔细使用
grep更好。grep -rn "<app-widget"也能找到同样的 62 处用法。ng_find_usages额外提供的功能:自动从文件推导选择器和类名、标注用法类型、覆盖四种属性绑定写法,并通过path限定扫描范围——在测量过的单体仓库中,有 33 个选择器同时在两个应用中声明,不限定范围的搜索会把它们混在一起。 - 不能替代 Angular CLI MCP——它们处于不同层级。
ng mcp负责文档、最佳实践和构建编排;它的九个工具都不涉及模板或语言服务器。两者相辅相成。 - 本身不是类型检查器。 由 LSP 驱动的工具会原样提供项目自身编译器的输出。
已知缺口会如实记录,而非隐瞒:如果基类来自某个包,或藏在 mixin 调用之后,祖先遍历就会停止(在测量过的单体仓库中,1298 个组件里有 1 个);带有推断目标的 Nx 项目没有 tsConfig;也不会检测同名管道。
要求和设置
- Node ≥ 18.20.8——实测最低版本:已发布的包在全新安装的 Node 18.20.8、20.20.2 和 22.x 上均可正常响应。这只涉及 MCP 服务进程——你的项目仍使用自己的 Node 构建。若要仅为服务指定较新的运行时,请将客户端配置指向对应的二进制文件:
"command": "C:\\node22\\node.exe"。
从源码安装(而非使用 npm):
- 项目自身的依赖:运行
npm install,然后运行npm run build。 - 随包提供的语言服务器分支位于
tools/servers/ls22,需要在该目录中运行一次npm ci。 - 在每个客户端配置中,将
ng-token-saver命令替换为node <path>/dist/index.js; 对于 Claude Code:claude mcp add ng-token-saver -- node <path>/dist/index.js。
node_modules 文件夹不会提交,包括 stand 中的 12 个文件夹。若要恢复完整的测量环境:
npm install && npm run build
cd tools/servers/ls22 && npm ci # the branch actually shipped
cd fixtures/v22 && npm ci # repeat per fixture you want to run
客户端
该服务是纯 stdio MCP 服务,不包含任何特定客户端的功能,因此任何 MCP 客户端都能启动它;安装和 Claude Code 注册方法见快速入门。
面向 Agent 的使用说明通过 MCP instructions 字段传递——详见与 Agent 配合使用。
Cursor——使用快速入门中的一键按钮,或者将快速入门中的同一份 mcpServers JSON 放入 ~/.cursor/mcp.json(所有项目)或 .cursor/mcp.json(单个项目)。
Windsurf——将同一份 JSON 放入 ~/.codeium/windsurf/mcp_config.json。
VS Code(Copilot Agent 模式)——使用 .vscode/mcp.json;键名为 servers,条目中需要指定 type:
{ "servers": { "ng-token-saver": { "type": "stdio", "command": "ng-token-saver" } } }
Codex CLI——运行 codex mcp add ng-token-saver -- ng-token-saver,或编辑 ~/.codex/config.toml:
[mcp_servers.ng-token-saver]
command = "ng-token-saver"
JetBrains AI Assistant / Junie——Settings → Tools → AI Assistant → Model Context Protocol 接受与快速入门中相同的 JSON;对于 Junie,还需启用“Pass custom MCP servers”。
配置项,均为可选:
NG_TOKEN_SAVER_IDLE_MS——语言服务器会话在此时长内未使用时,其 ngserver 会关闭;下次调用需要重新冷启动。默认值为 900000(15 分钟);设为0可让会话保持运行,直到服务退出。正在处理调用的会话不会关闭。NG_TOKEN_SAVER_PREWARM=1——若ng_workspace_map的path位于某个应用内,就会在后台将该应用加载到语言服务器中,让首次诊断或定义调用时应用已就绪:在生产单体仓库中从 21 秒缩短至 1.4 秒。代价是空闲关闭前约占用 1 GB 内存,无论后续是否调用 LSP 工具;如果某个文件夹包含多个项目(例如工作区根目录、Nx 的apps/),则不会预热,因为无法确定要加载哪个应用。NG_TOKEN_SAVER_SERVERS_DIR——语言服务器分支所在目录;若不在构建目录旁的tools/servers中,可通过此项指定。
与 Agent 配合使用
服务会在 MCP instructions 字段中发送使用指南(924 个字符)。即使工具描述要等到搜索工具时才会提供,Claude Code 也会将这些内容保留在 Agent 的上下文中(在 Claude Code 2.1.282 上检查过:工具仅显示名称时,完整文本就已传入)。对于忽略 instructions 的客户端,同样的文本会写入 CLAUDE.md 或 AGENTS.md:
Angular 工具直接从编译器和源码中获取答案,而不是读取整个文件。优先使用这些工具,而不是读取和搜索:使用 ng_component_info 查看组件或指令接受的内容(包括 inputs、outputs、成员和继承的成员),而不是读取其 .ts 文件;使用 ng_find_usages 查找组件、指令、管道或服务的使用位置,并通过 input 查找某个 input 的绑定位置,而不是使用 grep;编辑模板后使用 ng_template_diagnostics,而不是运行 ng build;使用 ng_template_definition 查看模板符号的定义;在建议 Angular API 前使用 ng_version_rules;使用一次 ng_workspace_map 查看项目、版本和 strictTemplates。工作区中首次调用诊断或定义工具时,会将项目加载到 Angular 语言服务器中;大型工作区最多需要一分钟。之后的调用只需几毫秒,因此首次响应较慢并不表示卡住。行和字符编号在所有地方都从 1 开始,与 Read 显示的一致。
重现测量结果
npm test build plus 182 unit tests (node:test, no dependencies)
npm run smoke end-to-end check with a real MCP client over stdio
npm run bench:settle whether a pause after didOpen is needed (it is not)
npm run bench:standalone where standalone becomes the default (v17..v22)
npm run bench:api which Angular APIs exist in which majors, and their stability
npm run bench:contract <project root> contract size against reading whole files (--tokens adds token counts)
npm run bench:rename <component> <input> the grep path against the bridge for an input rename
npm run bench:diagnose a compiler listing against one diagnostics call
npm run bench:matrix resolution probes across a fixture
npm run bench:negative what the server returns when things break
npm run bench:didchange diagnostics timing after an edit
测试目录为 fixtures/v17..v22——六个真实的 Angular 工作区,每个工作区都有自己的 node_modules 和固定版本的 TypeScript;此外还有用于失败案例的 fixtures/negative/*。
状态
六个工具均已在六个测试夹具和两个生产代码库中通过验证,这两个代码库分别包含 1298 和 407 个组件,且解析错误为零;此外还在一个 Angular 16 项目中进行了检查,以确认超出范围时会返回结构化拒绝,而非静默处理。182 项单元测试全部通过。
测得的延迟:两个由 LSP 支持的工具首次调用时需要 8–28 秒冷启动,之后响应时间为 2–9 毫秒;四个静态工具首次调用时需要 250–600 毫秒,项目的 TypeScript 缓存后则只需几毫秒。会话空闲 15 分钟后,语言服务器会关闭;下次调用时需要再次冷启动,参见上文的 NG_TOKEN_SAVER_IDLE_MS。
许可证
GPL-3.0-or-later——参见 LICENSE。版权所有 (C) 2026 Alex Ryabov。 可自由使用和修改;衍生作品必须继续以相同许可证开源。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。