关于 Ajisai
Ajisai 是面向需要精确有理数与代数计算及机器可读 Word 合约的 Agent 的 MCP 服务。它通过受限执行、静态检查和合约注册表区分计算与验证。
主要功能
- 01精确有理数与代数计算
- 02受限源码执行
- 03合约声明检查
- 04Word 合约推断与查询
- 05结构化诊断结果
适用场景
- 01对精确数值进行计算
- 02执行前检查 Word 声明
- 03查看推断或规范合约
MCP 相关
提供 compute、check、infer_contracts 和 word_contract 四个 MCP 工具。结果采用共用 schema,也可通过 ajisai://schema/result 资源获取。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
5收录的工具列表 · 更新于 2 周前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| source | string | 是 | Ajisai 源文本(不接受文件路径)。有效上限为 65536 个 UTF-8 字节,因此非 ASCII 文本会比 maxLength 所暗示的更早达到上限。语法为后缀形式:先写操作数,再写 Word — `1 2 ADD`、`[ 1 2 3 ] LENGTH`。字符串使用单引号(`'hi'`,不能使用 "hi")。传递给 MAP/FILTER/FOLD/SCAN 的代码块与其他内容一样是 Vector,使用 `[ ]` 表示——没有单独的代码块括号(`[ 1 2 3 4 ] [ 2 GT ] FILTER`)。Word 的操作数形式是其契约的一部分;不确定时应使用 word_contract 检查——有些 Word 接受向量,而不是看起来更自然的单个数字,例如 `0 4 RANGE` 和 `[ [ 1 2 ] [ 3 4 ] ] ZIP`。 |
详细介绍
Ajisai MCP 服务
Ajisai 的 MCP 接口让这门语言成为 AI Agent 可用的有界、确定性且可诊断的计算内核。服务仍是一个轻量适配器:语言语义由 Rust CLI 承载,词汇由生成的产物提供。
Ajisai 承诺在其支持的数值域内精确计算,而非笼统地承诺“没有舍入误差”。显式舍入等操作,以及超出该数值域的函数,仍遵循其文档所述的语义。
本 README 记录了此服务的功能及其验证方式;当退出标准达成后,曾与之一起存放在 docs/dev/ 中的就绪度跟踪表和交接备忘录已退役。
各主机的资源上限对比见 docs/dev/mcp-host-profiles.md。
安装和连接
要求:Node 20 或更高版本。除此之外无需任何东西——不需要构建步骤、cargo 或本机二进制文件:该软件包自带 WASM 后端。
此服务以 ajisai-mcp-server 为名发布在 npm 上,并列入官方 MCP Registry,名称为 io.github.masamoto1982/ajisai。大多数 MCP 客户端使用 JSON 服务条目。Claude Desktop(claude_desktop_config.json)、Claude Code(.mcp.json)和 Cursor(.cursor/mcp.json)都采用以下格式:
{
"mcpServers": {
"ajisai": {
"command": "npx",
"args": ["-y", "ajisai-mcp-server"]
}
}
}
如果已安装的副本能够实际执行计算,npx -y ajisai-mcp-server --doctor 会以 0 退出。
从代码检出目录运行
克隆的仓库无需 npm 即可运行相同的服务,这也是访问仓库自身测试和未发布引擎的方式。WASM 后端已提交到 wasm/generated/,因此全新克隆后即可立即执行计算。
git clone https://github.com/masamoto1982/Ajisai.git
cd Ajisai/tools/mcp-server
npm install
node index.js --doctor # exits 0 when this copy can actually compute
然后,服务条目指向代码检出目录中的 index.js:
{
"mcpServers": {
"ajisai": {
"command": "node",
"args": ["/path/to/Ajisai/tools/mcp-server/index.js"]
}
}
}
配置到此为止。无需 env 块;将 AJISAI_BIN 指向本机二进制文件(早期版本的此文件仅有的可用示例曾这样配置)是可选覆盖项,并非前置条件:
AJISAI_BIN用于选择本机ajisai二进制文件,而非打包的 WASM 后端;如果 Docker 镜像在构建时包含该二进制文件,就应这样配置。AJISAI_REPO是仅用于开发的回退选项,可在不指定名称的情况下查找本地构建的二进制文件。
如果两者都未设置,只有在从 Ajisai 代码检出目录(tools/mcp-server 与 rust/Cargo.toml 同级)运行此软件包时,才会查找本机二进制文件;此时会使用 rust/target/release 和 rust/target/debug 中较新构建的文件。安装在 node_modules 下的副本不会在自身目录之外查找:它会运行打包的 WASM 后端。
发布的软件包仅包含服务运行所需的内容——适配器、两个后端、打包的资源和 WASM 模块、README.md、CHANGELOG.md 及 LICENSE。评测工具、语料库和测试均保留在仓库中(如果其中任何一项被打包发布,npm run test:pack 就会失败)。
两个后端的响应完全相同(见后端与来源);覆盖项影响的是部署方式,而非结果。
检查安装
服务运行正常时不会输出任何内容,因此无法与卡住的服务区分。此可执行文件也能自检:
node index.js --version # adapter, engine and specification versions, registry digest
node index.js --doctor # Node, assets, backend and two real computations
node index.js --help
所有检查均通过时,--doctor 以 0 退出;任意检查失败时则以 1 退出,因此可用它来控制容器启动,或用于支持请求。它会通过所选后端计算 2 3 DIV 1 3 DIV ADD 和 2 SQRT:能够启动并加载资源、却给出错误响应的服务依然有问题;只有实际运行测试才能验证一切正常。不带参数时,进程会通过 stdin/stdout 使用 MCP 协议,且不会向其输出其他内容。
Agent 接口
| 工具 | 用途 |
|---|---|
compute | 在时间、源代码、输出和步数限制内执行源代码 |
check | 解析、解析名称并保守地验证 #:contract 声明,不执行 |
infer_contracts | 推断用户定义的 Word 的契约,不执行 |
word_contract | 查询完整的规范 spec/words.json 契约注册表 |
执行工具只接受源代码文本。刻意不提供文件路径输入, 可防止 AI 工具调用变成任意本地文件读取器。
check 回答程序的形式问题,绝不涉及运行程序:它会进行词法分析、解析、
解析名称并验证 #:contract 声明
(#:contract DOUBLE inputs=1 outputs=1 field=closed,每个 Word 一行,在
键 CONTRACT 中回答 inputs、outputs 和 field,且不接受
其他键:任何其他词项都会使指令格式错误),所有操作都不执行程序。
compute 会在执行任何内容之前执行相同的声明检查;如果程序主体与声明矛盾,
或声明格式错误,就会停止运行:两个工具都会将其作为普通错误返回——
error.category: contractViolation,并在 message 中引用检查结果——
每条检查结果都会出现在 contractDecls.findings({ severity, message })中,
并在 contractDecls.declarations 中为每个 Word 给出判定,而 compute 会将其命名为
outcome: error:contractViolation。没有指令的源代码不会包含
contractDecls。因此,check 返回 status: ok 表示程序
格式正确且名称解析成功——并不表示程序会成功运行、不会超出限制,
或会产生一个值而非 NIL。这里不会判定任何只能通过运行才能确定的内容。
应将其理解为“程序可以执行到运行阶段”,而 compute 则会告诉你运行它会发生什么。
三个源代码工具对同一个程序给出一致的回答,而
tool-consistency.test.js(npm run selftest 的一部分)会通过所有 golden 和语料库中的源代码来检验这一点:
- 错误的
outcome和error.category会指明同一类别。 - 无法解析的源代码对三个工具来说都是
malformedSource—infer_contracts也不例外。 check返回 ok,表示运行不会因程序的形式或名称而失败。infer_contracts返回的每一行suggested,粘贴到源代码上方后, 都能通过check验证。check比运行更严格:即使在从未调用的 Word 中,它也会拒绝未定义的名称, 而运行永远不会执行到那里。
区分三种结果
| Ajisai 结果 | status | outcome(compute) | isError |
|---|---|---|---|
| 一个值 | ok | value | — |
NIL(reason) | ok,并附带缺失原因 | nil:<reason> | — |
语言 ERROR | error,并附带 message 和 error | error:<category> | — |
| 宿主故障 | hostError | — | 是 |
outcome 使用 spec/outcomes.json 中的 ID,因此无需读取栈顶节点,
就能区分有原因的缺失与一个值。
所有四个工具都使用相同的封装格式回答(result.schema.json,也可通过
ajisai://schema/result 获取),因此一个 schema 就能描述调用方
可能收到的所有结果,无需维护第二份契约并确保其同步。
结果的成本
每个结果都会以两种形式返回:structuredContent,供调用方据此分支处理;以及序列化为文本内容块的形式,因为 MCP 要求带有输出架构的工具也以这种方式返回序列化 JSON——纯文本客户端没有其他途径获取这些内容。这份镜像会保留。用散文摘要替代它,是唯一一种确实会丢失信息的精简方式;自测会确保纯文本客户端仅凭文本,就能区分一个值、带有原因的 NIL、语言错误和主机故障。
移除的是所有固定内容。1 2 ADD 返回了 3,732 字节,其中答案——stack 和 stackDisplay——占 85 字节;其余部分是每个结果都重复包含的十二项限制表(mcp.limits),以及主机未经验证的执行回执和观测摘要。一个回合中的五次调用会为此耗费 5,000 个 token。现在:
mcp.limitProfile标明配置(mcp-local-stdio);各项上限位于ajisai://limits资源中,只需读取一次。触发上限时,error.resource仍会标明对应资源,message会给出其值;与上限相关的主机故障则会在error.limit中标明上限,因此结果无需附带这张表也能采取相应操作。- 会发送
resourceUsage——本次运行在各项上限下的用量;引擎不再报告任何优化器计数器。 - 引擎不再发送回执和观测摘要(信封架构 5)。
- 没有值的可选字段会直接省略,而不是设为
null——普通成功结果中的message和error,以及未声明任何内容的源代码中的contractDecls都是如此。请检查字段是否存在,而不是检查它是否为null。
1 2 ADD 返回时约为 1 KB;保留下来的固定内容是其来源信息。npm run eval:performance 会测量七个基准用例的完整响应和单独的文本块,并依据已提交的 medianResponseBytesBudget 进行检查,超出预算就会失败,因此任何回归都不会悄然发生。响应确实缩小时应降低预算上限,绝不能为了让回归通过而提高它。
主机故障可供机器读取:error.code 是稳定标识符(invalidRequest、unknownTool、sourceTooLarge、backendUnavailable、capacityExhausted、timeout、responseTooLarge、malformedBackendResponse、backendFailure、registryUnavailable);error.retryable 表示重试是否可能有帮助;如果故障与某项上限有关,error.limit 会标明该上限。error.message 是为模型编写的,不包含主机路径、环境变量名称或进程启动诊断信息;这些细节会写入服务的 stderr,供运维人员查看。
当 concurrentExecutions 达到上限时,会将调用方排队等待最多一秒,然后才返回 capacityExhausted;这样,普通的突发请求会受到背压,而不是变成需要调用方编写重试循环的情况。
读取代数值
2 SQRT 返回该值、它的表示形式以及一个近似值。在栈节点中,各字段含义如下:
| 字段 | 含义 |
|---|---|
stackDisplay | 将该值写成一个 token:"sqrt(2)"、"2/1*sqrt(2)"、"sqrt(2)-sqrt(3)";精确且永不截断 |
semantics.exactTerms | 值本身:Σ (numerator/denominator)·√radicand;任意精度整数以字符串表示 |
显示文本会准确呈现旁边的各项。请使用 exactTerms 进行计算;显示文本供阅读,不用于解析。
容易误导的是节点自身的 value:它是一个被标记为 semantics.approximate 的有理数近似值,看起来精确,实际并非如此。
显示文本采用规范的标准形式,因此相同的值会以相同方式书写:8 SQRT 和 2 SQRT 2 SQRT ADD 都会给出 2/1*sqrt(2),而 EQ 会判断它们是否为同一个数。这里应通过比较来判断是否相等,而不是比较字符串——字符串只是显示文本,并不是值。对于有理数或有理数向量,不会出现 exactTerms,因为它们的 stackDisplay 已经是完整的值。
错误
语言错误包含一行文本 message 和一个对象
error: { category, resource?, word?, line?, column? }。category 是
spec/outcomes.json 中的错误类别,与 outcome 中的 ID 相同,也是分支判断依据;word 是被归因于此次失败的 Word,line 和
column 是其编写位置;对于 resourceLimitExceeded,resource 是触发限制的
ajisai://limits 条目。未知 Word 的错误为
category: unknownWord,并在 word 中保留其原名;word_contract 会用最接近的已知 Word 填入 suggestions,以回应该名称。
responseBytes 限制实际发送的响应大小:包括结构化结果、其序列化文本镜像和来源信息。引擎会确保成功结果不超出此限制,而不是直接拒绝:在 Agent 配置下,栈会完整发送,最大为 440 KiB(包含一个由 5,000 个小整数构成的向量);超过此大小后,放不下的槽位会像失败栈中的槽位一样被省略——value: null,并附带包含 elements 和 approxBytes 的 elided 记录,在 stackDisplay 中放入匹配的 <elided …> 标记,并在顶层设置 stackElided.reason: valueStackBudget;其余每个值都会完整送达。0 7000 RANGE 1 2 ADD 过去会返回 responseTooLarge,让调用方知道答案太大,却得不到其他信息;现在则会返回 3/1,并附带一条记录,说明有一个含 7,001 个元素的向量被省略,调用方据此可以将其丢弃。responseTooLarge 仍是硬性门槛,适用于无法通过省略内容而将结果压到上限以内的情况。
资源限制错误会在 error.resource 中指明 ajisai://limits 里触发限制的具体条目,并在 message 中说明限制和超出情况(materializedElements of 100002 exceeds the limit of 100000)。
触及上限不会导致调用被拒绝却不报错。格式正确、会生成超出限制结果的 Word——例如,0 100001 RANGE 超过 materializedElements、FILL 或 RESHAPE 的形状超过该限制或 nestingDepth、对过深文本执行 JSON-DECODE、NUM 的词法项超过 numericLiteralDigits,或 POW 超过 bigintBits——都会返回 status: error 和 resourceLimitExceeded;error.resource 会指明上限,message 会说明上限值和请求的大小,并且在构建任何内容之前就进行测量;该 Word 的操作数会保留在栈上。限制由宿主设定,而非程序设定,因此超限绝不能变成一个值:此处的 NIL 会像其他值一样继续流转,让同一个程序在两个宿主上返回不同的值,却都回答 ok(在 engine 1.0.0-beta.1 之前确实会如此,返回 NIL(spaceExhausted))。宿主之间的差异始终会作为一条指明具体上限的失败显现出来,绝不会表现为不同的答案。
后端与来源信息
所有执行工具都会通过两个可互换的后端之一(tools/mcp-server/backend/),调用同一个与宿主无关的 Rust Agent 边界(rust/src/agent):每次调用启动一个原生 ajisai 子进程,或将同一份 Agent 代码编译为 WASM,并在复用的 worker_threads Worker 中,每次调用都运行于一个全新的 WebAssembly 实例。两者返回完全相同的结果封装格式——backend/parity-test.js 中逐项验证了这一点——因此 Node 不会重新解释特定命令的结果。
后端在启动时选择一次,并记录在 mcp.backend.kind 中(nativeCli 或 wasmWorker)。如果按请求选择,那么 cargo build 在会话中途完成后,后续调用就会悄然切换到另一条执行路径,而响应中对此毫无说明。结果一致性确保两个答案相同;来源信息则能让不一致的答案可供调查。
每个结果还包含 mcp.serverVersion、mcp.engineVersion、mcp.assetDigest 以及所应用配置的名称 mcp.limitProfile。两个版本号对应的是分别发布的两个组件:serverVersion 是这个 Node 适配器的版本,engineVersion 则是它所使用的 Ajisai 语言的版本。以前,保存的结果只记录第二个版本号,因此无法分辨归档封装中缺少某字段,是因为适配器版本从未发送该字段,还是因为字段后来丢失。
每个引擎结果顶层的 specificationVersion 字段,记录引擎所实现的语言规范版本。该引擎目前为 beta 版:在 1.0.0 之前,程序的答案可能会随着此版本变化;Ajisai 仓库的 spec/CHANGELOG.md 会在每项此类变更中列出一个程序及其变更前后的答案。保存结果时,请保留此字段。
打包的注册表会在启动时根据记录的摘要进行验证,因此资产损坏时,服务器会直接停止,而不是等到某个请求首次触及它时才以通用故障的形式暴露。
限制
此配置是 ajisai://limits 资源,并在每个结果中以 mcp.limitProfile 命名。每个条目都在 golden/limits.json 中有对应条目;如果两组条目不一致,自测就会失败——声明上限时,必须说明如何对其进行测试。其中五项由真实的边界源固定,并在每次自测时针对运行中的服务器执行,再对照两个后端进行比较;concurrentExecutions、wallTimeMs 和 responseBytes 通过适配器自身的准入与交付路径固定;numericWork、bigintBits 和 algebraicTerms 则在 Rust 中通过注入上限进行固定,因为在其声明值下,它们无法在 wallTimeMs 范围内触发。golden/limits.json 和 docs/dev/mcp-host-profiles.md 都明确说明了这一点,不会让人自行发现其中的空缺。
Playground 使用了不同且更宽松的配置——0 100001 RANGE 在 Playground 中可以成功,在此处则会被拒绝(resourceLimitExceeded,并标明 materializedElements);它的步数预算是此处的 120 倍,因此,在 Playground 中遍历 100,000 个元素的块迭代,在此处会被拒绝(executionSteps;快速入门说明了应改为编写什么)。一个程序如果能在两个宿主上运行,就会在两者上得到相同答案:上限只会将答案转为带名称的失败,不会产生不同答案。两个宿主都会公布各自应用的配置——Playground 会在启动页上列出两种配置的名称,并在 Playground 徽章的工具提示中,将此配置的上限与其自身的上限并列列出;展示这一差异的 golden 用例则会通过明确的 hostDivergence 区块记录该差异。
资源
ajisai://guide/quickstart、ajisai://vocabulary、ajisai://contracts、
ajisai://schema/result、ajisai://limits 和 ajisai://phrasebook。ajisai://contracts 一次读取即可查看每个
Word 的完整契约;ajisai://vocabulary 仅列出清单——每个名称及其 kind、family 和 vocabularyTier。
ajisai://words/{name} 模板无需调用工具,即可公开与 word_contract 相同的完整 Word 契约。Word 名称不区分大小写,无论查询还是在程序中使用都一样(add 可写作 ADD);注册表摘要根据规范原文计算,而非根据精简后的文档清单计算。
ajisai://guide/quickstart 是一份 MCP 前言(mcp-quickstart.md),后接生成的写作协议(SKILL.md),两者由
sync-assets.js 合并。
ajisai://phrasebook 对应仓库中的 docs/phrasebook/phrasebook.ajisai:
其中包含程序反复写入的短语用 User Words,每个都带有经过验证的
#:contract、示例,以及标明其内容身份的 #:digest 行。
compute、check 和 infer_contracts 可选接受一个包含这些名称的 words 数组,并在运行源代码前定义这些 Word 及其调用的 phrasebook Word;结果的 prelude 会列出每个添加的 Word 及其摘要,因此可以按内容而非名称核验实际运行的内容。每次调用仍会运行一份全新的源代码:调用之间不会保留任何状态。将 AJISAI_PHRASEBOOK 设为另一份同格式文件的路径——每个 Word 占一行 DEF,后接其 #:digest 行——即可改用该文件来处理 words 和 ajisai://phrasebook;词汇涌现实验会通过这种方式为每一代提供继承的 Word。
使用无效的百分号编码字符串作为名称读取 ajisai://words/{name} 会被拒绝,并作为无效请求处理;工具调用若携带其架构未声明的参数,也会被拒绝为 invalidRequest,并指出该参数——服务端会强制执行 additionalProperties: false,而不只是声明它。该指南过去只有 SKILL.md,开篇就是 CLI 运行循环——ajisai run file --json 是已连接的客户端无法执行的命令,而且指南从未说明该调用哪些工具,因此模型先读它时,会先学到语言,再学到接口。前言用一屏内容说明工具选择、结果分支处理和代数值陷阱,然后再交接。自检会针对实时后端执行前言中的示例,这与生成器为其后半部分提供的保证相同。
全部四个工具都声明了只读、非破坏性且幂等的 MCP 注解。
开发
cd tools/mcp-server
npm install
npm run selftest # uses the packaged WASM backend unless AJISAI_BIN is set
npm run test:pack
npm run eval:validate
npm run eval:performance
打包后的 WASM bundle(wasm/generated/)由仓库根目录下的
npm run build:mcp-wasm 重新生成。无论后端为何,词汇表、契约、指南、
版本和注册表来源信息都会打包到 assets/ 下。
在仓库根目录运行 npm run test:mcp-backends(构建原生二进制文件,然后
运行 backend/parity-test.js),会针对两个后端运行每个黄金用例和每个已声明的限制
边界,并断言两者结果一致。
服务的评估方式——双语 Agent 语料库、轨迹和修复评分器、捕获的模型基线、性能和
响应大小预算——另有文档说明:docs/dev/mcp-evaluation.md。
本 README 介绍如何连接到服务,以及如何查看它的回答。
npm run test:pack 会创建一个经过允许列表筛选的 tarball,并将其安装到一个空的临时前缀目录中,随后通过四种方式测试该安装副本:在未设置 AJISAI_REPO 和 AJISAI_BIN 的情况下导入
createServer,证明它能通过打包的、自包含的 WASM 后端完成计算,且无需仓库
或可用的原生二进制文件;通过 node_modules/.bin 启动 ajisai-mcp-server 可执行文件,这正是所有文档所述客户端入口启动它的方式;
在已安装的包上运行 --doctor;最后再次启动该可执行文件,并显式指定 AJISAI_BIN,断言 mcp.backend.kind 为
nativeCli。
特意将最后两项作为独立进程启动。每个进程只解析一次后端,因此在同一进程中设置 AJISAI_BIN 并构造第二个服务时,会继续使用第一个场景已确定的 WASM 后端——即使机器上完全没有原生二进制文件,原生后端断言也会通过。现在这项检查会实际运行原生二进制文件,因此 npm run test:pack 需要先构建它;从仓库根目录运行 npm run test:mcp-pack 会先构建该文件。
浏览器 Playground 独立于此包,并且仍可使用。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。