type-atlas
Type Atlas 是我让所有代码代理在我所有 TypeScript 项目中用于所有代码导航需求的工具。这些项目大多是 monorepo。有些项目规模大且复杂,理解一项更改如何融入系统其余部分本身就是工作的一部分。这个工具被设计为代理默认代码导航方法的完整替代品。
我基于自己的编码代理在我的项目中的实际工作方式,花了数月时间迭代这个工具。Type Atlas 所做的大部分工作,都是因为我不断看到同样的问题:
代理从对系统的不完整视角进行推理。 它们可能理解找到的文件,却遗漏了决定其预期使用方式的周围代码。
代理重建已经存在的东西。 能力已经在仓库中,但代理从未找到它,因为它不知道它叫什么。
代理经常读得太少(Claude)或读得太多(Codex)。 读得太少让它们在缺乏足够上下文的情况下做决策。读得太多则用整个文件和与任务无关的实现细节填满上下文窗口。
代理反复停下来运行类型检查,只是为了找到 IDE 早已显示的错误。 这在整个实现过程中增加了延迟,并把反馈推迟到比需要更晚的时候才到达。
代理从根本上是在盲操作。 字符串搜索导航从未给它们一个对代码库的强内部地图。它们被迫从偶然检索到的文件片段和匹配项中做出实现决策,而重要的结构和编译器已知的信息仍然缺失。
随着项目增长,这些问题会累积,并直接体现在代理编写的代码质量上。
语义代码导航
编码代理默认通过文件读取和字符串搜索来导航代码。这给了它们源代码文本,却让模型去重建 TypeScript 语言服务已经知道的关系。
Type Atlas 让代理直接访问这些语义信息:
符号解析到其实际定义和引用,而不是同一文本的每次出现。
调用者和实现通过它们与符号的关系来识别。
推断类型来自语言服务,而不是从附近源码重建。
结果保留其源范围和所属的 TypeScript 项目。
当代理要找的正是文本时,文本匹配仍然有用。但当问题涉及程序本身时,它是语义导航的弱替代品。
找到已经存在的东西
大型代码库包含代理没有理由知道名字的有用代码。字符串搜索在代理已经知道足够词汇来构建搜索时效果最好。
Type Atlas 提供了其他入口:
代理可以用自然语言描述行为,并根据代码的功能找到它。
发现的结果会引导回一个真实符号和精确的源范围。
从该符号出发,代理可以沿着代码库中的实际关系追踪,而不是猜测另一个要搜索的标识符。
这在大型 monorepo 中尤其有用。在代理决定需要创建另一个辅助函数或既有实现之前,更容易发现现有的辅助函数和已建立的实现。
在正常工作中获得诊断
IDE 中的开发者在工作时会看到编译器反馈。编码代理通常通过停止实现、运行类型检查命令然后等待结果来获得这种反馈。
Type Atlas 将大部分反馈移入代理已经在做的工作中:
相关诊断可以随正常的代码智能响应一起到达。
错误在受影响的代码仍在代理当前工作上下文中时就会显示出来。
错误的类型假设可以在变成多个依赖编辑之前被捕获。
我仍然使用完整类型检查进行验证。它们不需要成为代理在工作中了解错误的主要方式。
响应为模型上下文而构建
只有当被移除的信息是不必要的时候,缩小响应才有用。模型也使用剩余内容的组织方式。
Type Atlas 将结构视为信息的一部分:
标签使结果的角色明确。
分组将相关事实保持在一起。
文件边界防止不相关的源码混在一起。
源位置保持附着在它们所描述的事物上。
树保持其层级结构,而不是变成扁平序列。
诊断保留理解它们所需的源码。
同样的原则决定了什么被省略。当签名足够时,函数体可以保持折叠状态。重复的序列化和不相关的源码不需要仅仅因为可用就占据上下文窗口。
目标是有效的信息密度。更少的 token 很重要,但移除帮助模型理解这些 token 的结构会适得其反。
为下一个决策提供信息
每次工具调用都是代理推理过程的一部分。好的响应应该回答当前问题,同时让代理处于更好的位置来决定接下来检查什么。
Type Atlas 将有用的后续信息保持在暴露它的结果附近:
符号可以带着理解它如何参与代码库所需的关系一起到达。
仓库结构可以携带行数和工作树状态以及文件本身。
搜索结果包含可以直接跟随的具体源范围。
项目上下文在代理从一个结果移动到另一个结果时保持附着。
这为代理在调查的每个分支提供了更好的证据。它可以跟随程序中实际存在的关系,而不是把每个文本匹配都当作同样有意义的线索。
好处是更高质量的导航。每一步都保留更多选择下一步所需的信息。
项目和范围感知
TypeScript 问题取决于项目上下文。这在 monorepo 中尤其重要,因为一个答案在一个项目内可能是正确的,但对整个仓库来说仍然不完整。
Type Atlas 保持这些边界可见:
文件通过拥有它们的 TypeScript 项目来解析。
当范围重要时,结果会说明它们来自的项目范围。
计数使结果的大小明确。
当答案覆盖范围小于整个仓库时会说明限制。
源位置可以直接传递到后续调用中。
代理在依赖一个答案之前,会获得足够的信息来理解该答案实际覆盖了什么。
从日常代理使用中构建
我每天在我所有 TypeScript 项目中使用 Type Atlas 和我的编码代理。当前行为来自反复使用。
很多设计可以直接追溯到反复出现的代理行为:
读取被折叠,因为代理在不需要的函数体上花费上下文。
诊断随正常响应一起传输,因为重复的类型检查命令浪费了实现时间。
语义关系被分组,因为代理不断通过单独的调用重建相同的信息。
自然语言代码搜索存在,因为有用的代码往往有一个代理永远无法从任务中推断出的名字。
这就是我仍然在 Type Atlas 上的工作方式。当我不断看到代理在同一个导航问题上浪费时间,或反复错过同一种信息时,我就会改变这个工具。
下面的示例是从运行中的服务器针对一个 fixture monorepo 捕获的,并与实现进行了回归检查。
安装
codex mcp add type-atlas -- npx --yes @type-atlas/mcp@latest
claude mcp add --scope user type-atlas -- npx --yes @type-atlas/mcp@latest
code --add-mcp '{"name":"type-atlas","command":"npx","args":["--yes","@type-atlas/mcp@latest"]}'任何其他客户端都采用标准形式:
{
"mcpServers": {
"type-atlas": {
"command": "npx",
"args": ["--yes", "@type-atlas/mcp@latest"]
}
}
}一个在没有你的 shell PATH 的情况下启动服务器的客户端将无法按名称找到 npx;在这种情况下,给出 which npx 的绝对路径。在 Windows 上,无法启动 npx.cmd shim 的客户端需要 "command": "cmd" 配合 "args": ["/c", "npx", "--yes", "@type-atlas/mcp@latest"]。
客户端在启动时读取 MCP 配置,所以之后要重启。@latest 在每次进程启动时解析;如果你不希望工具行为在你不知情的情况下变化,请固定一个版本。
search_code、related_code、investigate_code 和 search_dependency_code 通过 uvx 运行语义索引,需要 uv。没有它,这四个工具会报告缺少 uv,explore_symbol 会丢弃其相关代码部分,其余功能不受影响。
推荐
安装服务器并不会改变代理会使用什么工具。有些代理(包括 Claude)会组合使用其 shell 允许的任何命令,串联起来,并每次产生一个新的理由,所以列出几个要避免的命令没有用。指令必须排除整个类别并指明例外。将此添加到 AGENTS.md 或 CLAUDE.md:
Type Atlas MCP 是阅读和导航 TypeScript 和 JavaScript 代码的必需工具。这不是偏好。任何 shell 命令都不是可接受的替代品,无论它由什么组成,普通文件读取也不是。唯一有效的回退是服务器宕机、调用出错,或文件既不是 TS 也不是 JS。
--require-intent
这个可选标志要求对广泛的探索工具(如仓库搜索和工作区符号)提供一个决策句。定向读取和语义查找不受影响,意图永远不会回显到工具响应中。
Related MCP server: agent-workspace-mcp
工具调用结果
路径是工作区相对的,坐标是从 1 开始的,所以一个答案中的位置是下一次调用的有效输入。编辑工具返回补丁;不会为你写入任何内容。
以下所有内容都是从运行中的服务器针对 fixtures/ledger 由 scenario suite 捕获的,该套件重放相同的调用并在漂移时失败。这里没有任何手写内容,改变工具的回答会在同一提交中改变此文件。源文件是 README.mdoc。每个工具在 docs/tools 中都有一个包含更多案例的页面。
list_files
在一个树中呈现结构、行数和 git status,使用编辑器已经使用的徽章字母。已删除的文件即使只存在于 git 的答案中也会有一行。折叠的目录说明它们包含什么,而不是消失。
代理的输入
tool: List files
workspace: fixtures/ledger
# working tree arranged: currency.ts edited · rounding.ts created · index.ts deleted
directory: packages/money
depth: 2
# answered in 57ms响应
packages/money/
├ src/ · 3 changed
│ ├ currency.ts · 21 loc · M +2
│ ├ index.ts · D -12
│ ├ money.ts · 58 loc
│ ├ rounding-mode.ts · 15 loc
│ └ rounding.ts · 11 loc · U
├ tests/
│ ├ money.test.ts · 15 loc
│ └ rounding-parity.ts · 15 loc
├ package.json · 19 loc
└ tsconfig.json · 20 locinspect_symbol
在一次调用中呈现悬停、定义、类型定义、实现、调用者、调用和引用。引用是扣除调用者和定义后的剩余部分,所以一个使用只列出一次。与分别调用这些工具相比,字符数减少 4 倍,往返次数减少 7 倍。
代理的输入
tool: Inspect symbol
workspace: fixtures/ledger
file: packages/accounts/src/journal.ts
symbol: Journal
# answered in 49ms响应
Journal [class] · packages/accounts/src/journal.ts:24:14-24:21 · range 24:1-73:2 · packages/accounts/tsconfig.json
```typescript
class Journal<TMeta = undefined>
```
An append-only journal of balanced entries. `TMeta` carries whatever a
consumer attaches to each entry — an import batch id, an approval trail —
without the journal knowing its shape.
## Callers (4)
packages/accounts/tests/journal.test.ts
├ test("posts a balanced transfer through the overload") callback [function] 5:56-14:2 · calls 6:23-6:30
└ test("refuses an unbalanced entry") callback [function] 16:37-29:2 · calls 17:23-17:30
packages/reports/src/balance.ts
└ balancesAsOf [variable] 23:14-23:26 · range 23:14-51:2 · calls 24:12-24:19
packages/importers/src/csv.ts
└ importStatement [variable] 28:14-28:29 · range 28:14-47:2 · calls 29:12-29:19
## Mentions that are not calls (4 of 9 references · 5 relevant projects searched)
packages/accounts/tests/journal.test.ts:3:25-3:32: import { credit, debit, Journal, UnbalancedEntryError } from "../src/index.ts";
packages/accounts/src/index.ts:11:22-11:29: export { type Entry, Journal, UnbalancedEntryError } from "./journal.ts";
packages/reports/src/balance.ts:4:8-4:15: type Journal,
packages/importers/src/csv.ts:1:10-1:17: import { Journal, type Entry, credit, debit, type AccountPath } from "@ledger/accounts";
references lists all 9, with paging.read_file
参数是一个数组,所以多个文件可以在一次调用中到达。函数体默认折叠为签名,头部说明节省了多少行;fold: false 返回它们。
代理的输入
tool: Read files
workspace: fixtures/ledger
file: ["packages/accounts/src/posting.ts","packages/money/src/rounding-mode.ts"]
# answered in 7ms响应
2 files · 42 lines · 6 folded to signatures, pass fold: false for the bodies
=== packages/accounts/src/posting.ts · 32 lines ===
1 | import { type Money, negate } from "@ledger/money";
2 | import type { AccountPath } from "./account.ts";
3 |
4 | /**
5 | * One side of a journal entry. The discriminant is the bookkeeping side, so
6 | * every consumer's switch is checked for exhaustiveness by the compiler.
7 | */
8 | export type Posting =
9 | | { readonly side: "debit"; readonly account: AccountPath; readonly amount: Money }
10 | | { readonly side: "credit"; readonly account: AccountPath; readonly amount: Money };
11 |
12 | export const debit = (account: AccountPath, amount: Money): Posting => ({
13 | side: "debit",
14 | account,
15 | amount,
16 | });
17 |
18 | export const credit = (account: AccountPath, amount: Money): Posting => ({
19 | side: "credit",
20 | account,
21 | amount,
22 | });
23 |
24 | /** A posting's effect on a debit-normal running balance. */
25 | export const signedAmount = (posting: Posting): Money => {
| ... 26-31 folded
32 | };
=== packages/money/src/rounding-mode.ts · 15 lines ===
1 | /** How sub-minor precision resolves when a statement and the books disagree. */
2 | export enum RoundingMode {
3 | HalfUp = "half-up",
4 | HalfEven = "half-even",
5 | Truncate = "truncate",
6 | }
7 |
8 | /** Per-institution conventions, as observed in their exports. */
9 | const bankRounding: Readonly<Record<string, RoundingMode>> = {
10 | "first-national": RoundingMode.HalfEven,
11 | "harbor-credit": RoundingMode.HalfUp,
12 | };
13 |
14 | export const roundingModeOf = (bank: string): RoundingMode =>
15 | bankRounding[bank] ?? RoundingMode.HalfEven;occurrences
按文件分组的字面文本,以及扫描的文件数量。语义工具对已有内容进行排序,这对于确认某个 token 在拆除后已消失毫无用处;这里的零与相同的扫描计数一同出现,因此它是有意义的。
Agent 的输入
tool: Occurrences
workspace: fixtures/ledger
text: signedAmount
# answered in 12ms响应
"signedAmount" occurs 12 times in 7 files · 67 files scanned under the workspace · 1 file of declared build output not scanned.
packages/accounts/src/index.ts:12:39 · export { credit, debit, type Posting, signedAmount } from "./posting.ts";
packages/accounts/src/journal.ts
├ 3:39 · import { credit, debit, type Posting, signedAmount } from "./posting.ts";
└ 52:12 · .map(signedAmount)
packages/accounts/src/posting.ts:25:14 · export const signedAmount = (posting: Posting): Money => {
packages/reconcile/src/drift.ts
├ 4:24 · import { type Posting, signedAmount } from "@ledger/accounts";
└ 20:37 · const journalTotal = postings.map(signedAmount).reduce((total, amount) => total + amount);
packages/reconcile/src/matching.ts
├ 1:55 · // DELIBERATELY BROKEN — the imports for `money` and `signedAmount` are
└ 14:20 · const amount = signedAmount(posting);
packages/reports/src/balance.ts
├ 6:3 · signedAmount,
└ 34:57 · add(own.get(posting.account) ?? zero(currency), signedAmount(posting)),
packages/rules/src/builtin.ts
├ 1:10 · import { signedAmount } from "@ledger/accounts";
└ 26:12 · .map(signedAmount)search_code
按代码的功能查找代码,适用于你无法猜测其名称的情况。命中结果按排序顺序返回,每条结果都带有其来源的文件范围,因此下一次调用有处可去。实时答案还带有每条命中的相关性百分比;下面省略了这一点,因为其背后的嵌入分数在不同机器上有所差异,而这些案例是逐字节比较的。
Agent 的输入
tool: Search code
workspace: fixtures/ledger
query: walking an account up through each of its ancestor accounts
snippetLines: 6
# answered in 20ms响应
Search: walking an account up through each of its ancestor accounts
5 matches · no identifier to anchor on, so these are ranked by meaning alone
=== 1 · packages/accounts/src/account.ts:21-35 ===
Structure: parentPath
Symbol: parentPath [variable] · selection 21:14-21:24 · range 21:14-24:2
21 | export const parentPath = (path: AccountPath): AccountPath | undefined => {
22 | const at = path.lastIndexOf(":");
23 | return at === -1 ? undefined : path.slice(0, at);
24 | };
25 |
26 | /** Every ancestor from root to the account itself: `a`, `a:b`, `a:b:c`. */
=== 2 · packages/reports/src/balance.ts:1-23 ===
Structure: BalanceLine
Symbol: BalanceLine [interface] · selection 11:18-11:29 · range 11:1-16:2
1 | import {
2 | type AccountPath,
3 | type Entry,
4 | type Journal,
5 | lineage,
6 | signedAmount,
=== 3 · packages/accounts/src/journal.ts:59-73 ===
Structure: Journal > history
Symbol: history [method] · selection 60:3-60:10 · range 60:3-64:4
59 | /** Entries touching an account, oldest first. */
60 | history(account: AccountPath): readonly Entry<TMeta>[] {
61 | return this.entries.filter((entry) =>
62 | entry.postings.some((posting) => posting.account === account),
63 | );
64 | }
=== 4 · packages/reports/src/statement.ts:1-11 ===
Structure: statementLine
Symbol: statementLine [variable] · selection 8:14-8:27 · range 8:14-11:2
1 | import { type Account, normalBalance } from "@ledger/accounts";
2 | import { format, type Money, negate } from "@ledger/money";
3 |
4 | /**
5 | * One rendered statement line. The sign follows the account's normal side:
6 | * a liability holding a credit balance reads as positive on its statement.
=== 5 · packages/accounts/src/posting.ts:1-24 ===
Structure: credit
Symbol: credit [variable] · selection 18:14-18:20 · range 18:14-22:3
1 | import { type Money, negate } from "@ledger/money";
2 | import type { AccountPath } from "./account.ts";
3 |
4 | /**
5 | * One side of a journal entry. The discriminant is the bookkeeping side, so
6 | * every consumer's switch is checked for exhaustiveness by the compiler.diagnostics
编译器自身的全项目检查,按项目进行,而非逐文件处理。对一个文件的编辑通常会破坏另一个文件,而此调用正是用来找出那个文件的。
Agent 的输入
tool: Diagnostics
workspace: fixtures/ledger
file: packages/reconcile/src/drift.ts
# answered in 23ms响应
packages/reconcile/src/drift.ts · 4 problems · packages/reconcile/tsconfig.json
=== packages/reconcile/src/drift.ts ===
error ts(2365) 16:33-16:52 — inside lines.reduce() callback
Operator '+' cannot be applied to types 'number' and 'Money'.
14 | /** Statement total, computed by someone who forgot Money is not a number.…
15 | export const statementTotal = (lines: readonly StatementLine[]): number =>
16 | lines.reduce((total, line) => total + line.amount, 0);
| ^^^^^^^^^^^^^^^^^^^
17 |
18 | /** Drift between the journal's view and the bank's view of one day. */
error ts(2365) 20:77-20:91 — inside reduce() callback
Operator '+' cannot be applied to types 'import("packages/money/src/money").Money' and 'import("packages/money/src/money").Money'.
18 | /** Drift between the journal's view and the bank's view of one day. */
19 | export const drift = (postings: readonly Posting[], statement: readonly St…
20 | const journalTotal = postings.map(signedAmount).reduce((total, amount) =…
| ^^^^^^^^^^^^^^
21 | return format(money(journalTotal - statementTotal(statement), "usd"));
22 | };
error ts(2345) 21:65-21:70 — inside drift
Argument of type '"usd"' is not assignable to parameter of type 'Currency'.
19 | export const drift = (postings: readonly Posting[], statement: readonly St…
20 | const journalTotal = postings.map(signedAmount).reduce((total, amount) =…
21 | return format(money(journalTotal - statementTotal(statement), "usd"));
| ^^^^^
22 | };
23 |
error ts(2362) 21:23-21:35 — inside drift
The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
19 | export const drift = (postings: readonly Posting[], statement: readonly St…
20 | const journalTotal = postings.map(signedAmount).reduce((total, amount) =…
21 | return format(money(journalTotal - statementTotal(statement), "usd"));
| ^^^^^^^^^^^^
22 | };
23 |workspace_symbols
当你大致知道某个声明的名称但对它的位置一无所知时,可在会话已加载的所有项目中按名称查找该声明。
Agent 的输入
tool: Workspace symbols
workspace: fixtures/ledger
file: packages/importers/src/statement-parser.ts
query: Parser
# answered in 100ms响应
3 symbols matching Parser · 8 projects loaded · packages/importers/tsconfig.json
CsvStatementParser [class] · packages/importers/src/statement-parser.ts:25:1-35:2
FixedWidthStatementParser [class] · packages/importers/src/statement-parser.ts:41:1-64:2
StatementParser [class] · packages/importers/src/statement-parser.ts:7:1-23:2file_references
谁导入了这个模块。这是模块级别的问题,无需先在其中选取某个符号即可回答。
Agent 的输入
tool: File references
workspace: fixtures/ledger
file: packages/money/src/money.ts
# answered in 134ms响应
packages/money/src/money.ts · referenced from 90 places · 6 relevant projects searched · packages/money/tsconfig.json
1-20 of 90 places · pass offset: 20 for the rest
packages/accounts/src/journal.ts
├ 1:10 — at module level
└ 53:15 — inside post
packages/money/src/index.ts
├ 3:3 — at module level
├ 4:3 — at module level
└ 5:3 — at module level
packages/money/tests/money.test.ts
├ 2:10 — at module level
├ 2:15 — at module level
├ 2:38 — at module level
├ 5:10 — inside test("adds amounts of one currency exactly") callback
├ 9:16 — inside expect() callback
├ 9:67 — inside test("refuses to combine currencies") callback
├ 13:10 — inside test("formats major and minor units per currency") callback
└ 14:10 — inside test("formats major and minor units per currency") callback
packages/reports/src/balance.ts
├ 8:10 — at module level
├ 34:9 — inside balancesAsOf
└ 41:28 — inside balancesAsOf
packages/reports/src/statement.ts
├ 2:10 — at module level
└ 10:40 — inside statementLine
packages/rules/src/builtin.ts
├ 2:10 — at module level
└ 28:58 — inside closedPeriodsBalance包
包 | 角色 |
MCP 服务器 | |
无头代码智能 API | |
由核心包驱动的基于 Volar 的语言服务器 |
开发
vp install
vp run check
vp run check:distributionCONTRIBUTING.md 包含变更与发布流程。
This server cannot be deployed
Maintenance
Related MCP Connectors
Project memory, semantic code search, and grounded agent context.
Coding agents in multi-service codebases routinely rebuild existing helpers, trust stale type definitions, and modify API contracts without knowing who consumes them. Carrick solves this by indexing your entire TypeScript ecosystem across service and repository boundaries. By integrating deeply with the TypeScript compiler, Carrick traces every route, type, and cross-service call while recording function behaviour so agents search by intent rather than name. Delivered via MCP for AI agents and LSP for IDEs, Carrick ensures models see existing endpoints and utilities before generating new code. The scanner is source-available and runs from your CLI or CI pipeline.
A comprehensive Model Context Protocol (MCP) server that enables AI assistants to interact with yo…
Open-source Obsidian for MDX - edit local docs with agent assistance
Related MCP Servers
- AlicenseBqualityDmaintenanceExposes TypeScript Language Server Protocol functionality to AI agents, enabling them to query types at specific positions, find definitions and references, get diagnostics, run type tests, and type-check inline code just like in an IDE.9614 npm3MIT
- AlicenseAqualityDmaintenanceA TypeScript-aware MCP server that provides coding agents with repository discovery, code intelligence, and web project context for local codebases. It enables deep symbol navigation, diagnostic reporting, and structural analysis of monorepos without requiring full IDE integration.715 npm1MIT
- AlicenseNot gradedqualityFmaintenanceBridges the Model Context Protocol with Language Server Protocol to provide AI agents with persistent access to code intelligence features including navigation, diagnostics, refactoring, and completion across 7+ programming languages.1,932 npmMIT
- AlicenseAqualityBmaintenanceEnables AI coding agents to interact with TypeScript projects through compiler-level code intelligence, providing tools for navigation, type information, diagnostics, refactoring, and semantic search.29225 npm3Apache 2.0