Agent 集成指南
AI 编码代理——Claude Code、Cursor、Copilot、Zed 等——通过 Nudo 的语言服务器 @nudojs/lsp 访问推断能力。驱动 VS Code 扩展的同一个服务器,同时通过标准的 workspace/executeCommand 调用暴露五个 agent 命令,外加拉取式诊断。不需要安装或维持独立的 MCP 服务器进程:一个服务器同时服务编辑器和 agent。
面向 agent 的 Abs-first 工具:
| 命令 | 返回 |
|---|---|
nudo.check | CheckJson v1——签名 + actual ⊭ expected |
nudo.infer | InferJson v1——带无损 intension.abs 的 case |
nudo.hover | 指定位置的无损 Abs(+ 可选 inlay) |
nudo.whatIf / suggestCase / trace | 类型探索与 case 覆盖 |
完整的命令参考(参数、返回形状、类型表达式语法)见 Agent API 页面。面向 agent 的现成 skill 文件发布在 packages/lsp/agent-skill/SKILL.md。
安装
npm i -g @nudojs/lsp
包内发布编译后的 dist/server.js 与带 shebang 的 nudo-lsp bin。服务器通过 stdio 讲 LSP:
nudo-lsp
# 或没有全局 bin 时:
node node_modules/@nudojs/lsp/dist/server.js
三种接入方式
方式一:LSP→MCP 桥
如果你的 agent 只讲 MCP,运行一个通用桥,把 Nudo 注册为 .js 文件的语言服务器。三个桥均可直接使用:
cclsp —— 在项目旁配置 cclsp.json:
{
"extensions": ["js", "mjs", "ts"],
"command": ["node", "node_modules/@nudojs/lsp/dist/server.js"],
"rootDir": "."
}
然后把桥加入 MCP 客户端:
claude mcp add cclsp -- npx cclsp@latest --env CCLSP_CONFIG_PATH=/abs/path/to/cclsp.json
mcpls —— 配置 mcpls.toml:
[[lsp_servers]]
language_id = "javascript"
command = "node"
args = ["node_modules/@nudojs/lsp/dist/server.js"]
file_patterns = ["**/*.js", "**/*.mjs", "**/*.ts"]
agent-lsp —— 运行 agent-lsp init;它会自动探测 PATH 上的语言服务器并替你写好 MCP 客户端配置,把多个服务器编排成 agent 原生的工作流。
各桥转发的能力不同。标准 LSP 功能(hover、诊断、定义跳转)总会透传;如果某个桥没有把 workspace/executeCommand 转发到 Nudo 的五个命令,请改用方式二。
方式二:原生 LSP 客户端
任何 LSP 客户端库(vscode-languageserver-protocol、各类语言的原生 LSP 客户端、手写 stdio 客户端)都可以。启动服务器、initialize,然后调用 workspace/executeCommand——线上消息就是普通 JSON-RPC,可以逐字发送:
// 1. 握手
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"processId":null,"rootUri":"file:///home/you/project","capabilities":{}}}
{"jsonrpc":"2.0","method":"initialized","params":{}}
// 2. 提一个 what-if 问题
{"jsonrpc":"2.0","id":2,"method":"workspace/executeCommand","params":{
"command": "nudo.whatIf",
"arguments": [{ "file": "src/app.js", "bindings": [{ "name": "x", "type": "string" }], "target": "y" }]
}}
file 参数接受 file:// URI 或裸路径。未在编辑器中打开的文件直接从磁盘读取——不需要 didOpen。
方式三:VS Code / Cursor 扩展
安装 nudo-vscode 扩展后,本指南的一切都已接好:扩展会启动 @nudojs/lsp,编辑器内的 agent 通过同一服务器获得悬停类型、诊断和用例切换 CodeLens。
五个命令
每个示例都是完整的 workspace/executeCommand 载荷——复制、改路径、直接发送。以下命令共用示例文件 src/app.js(what-if 示例自带 src/config.js):
Abs-first(推荐给 agent)
nudo.check —— 精化门禁,CheckJson v1:
{ "command": "nudo.check", "arguments": [{ "file": "src/validators.js", "format": "json" }] }
nudo.infer —— InferJson v1(可选 functions 过滤):
{ "command": "nudo.infer", "arguments": [{ "file": "src/app.js", "functions": ["normalize"], "format": "json" }] }
nudo.hover —— 指定位置的无损 Abs:
{ "command": "nudo.hover", "arguments": [{ "file": "src/app.js", "line": 1, "column": 9, "includeInlays": true }] }
探索与 case
function normalize(x) {
const trimmed = x.trim();
return Number(trimmed);
}
nudo.whatIf ——为某个顶层绑定假设一个类型,观察另一个绑定变成什么。bindings 会以 @nudo:as 假设的形式在分析前注入,因此回答会反映它们。示例文件 src/config.js:
const raw = loadRaw();
const size = raw.length;
{ "command": "nudo.whatIf", "arguments": [{
"file": "src/config.js",
"bindings": [{ "name": "raw", "type": "string" }],
"target": "size"
}] }
Type of "size": number
Bindings applied: raw: string
不做假设时,raw 是 unknown,size 也随之是 unknown。bindings 只匹配顶层声明——找不到对应声明(函数参数、局部变量)的绑定会以 Bindings not applied (no top-level declaration found): … 回报,回答使用文件自身的类型。
nudo.trace —— 列出每个用例的输入和输出:
{ "command": "nudo.trace", "arguments": [{ "file": "src/app.js", "functionName": "normalize" }] }
nudo.suggestCase —— 检查 @nudo:case 覆盖情况:
{ "command": "nudo.suggestCase", "arguments": [{ "file": "src/app.js", "functionName": "normalize" }] }
当函数的用例全部由调用点合成时,返回可直接粘贴到函数声明上方的指令文本—— 例如以 (1, 2) 和 ("x", "y") 调用过的 add(a, b):
Function "add" has 2 synthesized case(s); suggested directives:
/**
* @nudo:case "call@L2" (1, 2)
* @nudo:case "call@L3" ("x", "y")
*/
已有手写用例的函数则返回 Function "<name>" already has N case(s);其余返回情形见 Agent API 页面。
nudo.selectCase —— 把函数固定到一个用例(悬停与诊断随之切换,直到再次切换):
{ "command": "nudo.selectCase", "arguments": [{ "file": "src/app.js", "functionName": "normalize", "caseIndex": 0 }] }
nudo.getActiveCases —— 读取每个函数的活动用例:
{ "command": "nudo.getActiveCases", "arguments": [{ "file": "src/app.js" }] }
whatIf、trace、suggestCase 返回 { content: [{ type: "text", text }] }——MCP 风格文本内容;selectCase 返回 { success: true };getActiveCases 返回 Record<string, number>。