跳转到内容

asm-mcp

@asm/asm-mcp 提供业务 MCP 服务与适配器。AsmDataProvider 是宿主的数据边界,InMemoryAsmDataProvider 是不访问外部 API 的确定性模拟实现。

import { createRegistry } from "@earendil-works/pi-durable";
import {
AsmMcpServer,
createAsmMcpExtension,
generateAsmMultitenantSeed,
InMemoryAsmDataProvider,
isRoutineUnattended,
} from "@asm/asm-mcp";
import { createExtraToolsExtension } from "@asm/extra-tools";
const seed = generateAsmMultitenantSeed();
const server = new AsmMcpServer({
provider: new InMemoryAsmDataProvider(seed),
});
const registry = createRegistry();
registry.install(createAsmMcpExtension(server));
registry.install(createExtraToolsExtension({
multiTenant: true,
skills: seed.skills,
isUnattended: isRoutineUnattended,
}));

宿主用该 Registry 打开 Harness,并为每次执行传递已认证的租户 Context。最小直接调用见开始组装。

MCP 服务公开八个工具,Durable 桥接名称加 asm__ 前缀:

工具 关键参数
pos_query_report period、dateOrMonth、可选 endDate
knowledge_search query、可选 topK(1–20)
competitor_search mode: "nearby" | "details",可选 queryOrName、radiusMeters;details 要求名称
macro_stats_query category: "market_cpi" | "labor_wages"、可选 months
law_regulation_search query、可选 keywords
web_search query、可选 extractUrls(最多两个)
report_get_facts period、targetDate、可选 sectionIds
report_review draft、period、targetDate

period 为 daily、weekly 或 monthly。工具 schema 拒绝额外参数,租户身份不放在业务参数内。

调用元数据是 { tenantContext, execution }。前者要求 tenantId、scopeId 和非空 roles;后者要求 runId、callId、mode: "direct" | "codemode",可带 profile。在 MCP 传输中它位于 tools/call 的 _meta,由可信宿主注入;收到未认证客户端提供的同名字段不能直接转发。

接入方式 API 与宿主责任
MCP transport await server.connect(transport) 返回异步关闭函数;宿主实现传输认证
Durable createAsmMcpExtension(serverOrClient) 或 createTenantScopedMcpTools(serverOrClient)
独立 QuickJS createAsmCodemodeTools(client, { runId }, context);独立沙箱不提供 Durable 恢复
pi CLI createAsmCliExtension({ client, resolveIngress, ... }) 返回可加载的扩展函数

本地服务的 Durable 桥接把事实缓存、审核记录和网页预算存入会话所属的 AsmRunDoc。远程 MCP 服务的持久状态仍需服务端提供 AsmStateStore;默认 MemoryAsmStateStore 只在进程内保存。

CLI 的 resolveIngress(flags, signal?) 必须校验认证授权,不能直接信任 --tenant、--scope 或 --roles。需要 PostgreSQL 历史时配置 pool、provider 和会话锁;恢复时校验原会话。公共入口 resolveAndInjectTenantContext(input, provider, context?) 处理身份和范围检查,bootstrapTenantCliSession(input, pool, provider, context?) 还验证持久会话归属。

接续前面的 registry 和 server,增加原生 Durable 报告扩展:

import { InlineCodemodeSandbox, loadQuickJSWasm } from "@earendil-works/pi-codemode";
import {
createReportPipelineExtension,
ReportPipelineTask,
} from "@asm/asm-mcp";
const sandbox = new InlineCodemodeSandbox({ wasm: loadQuickJSWasm() });
registry.install(createReportPipelineExtension({
sandbox,
registry: server.sections,
}));
// RoutineRunner.run 的请求中使用:
// task: ReportPipelineTask,
// input: { period: "daily", targetDate: seed.targetDate }

ReportPipelineTask 直接启动报告工具,不通过模型规划步骤。宿主配置会话模型或传入 writer,并在所有使用者关闭后关闭 sandbox。

流程先采集事实,再写一次初稿并执行代码校验。初稿未通过时只修订一次;再次失败则输出确定性事实模板,状态为 degraded_facts_only。事实和已完成草稿都在推进前提交,恢复时复用;未提交的模型调用可能再次执行。

内置章节为 sales_summary、channel_breakdown、customer_traffic、issues_and_actions。可通过 ReportSectionRegistry.register(plugin) 扩展。插件的 fetch、compute、severity 在 QuickJS 中执行,函数不得捕获宿主变量或调用宿主 import;template 在宿主渲染事实。

财务查询拒绝只读员工角色;报告入口需要报告权限和适当执行模式。readOnly: true 会在报告查询或模型调用前被拒绝,冻结租户不能开始报告工作。只读归档访问仍由认证入口控制。

web_search 每次运行最多调用两次。需要抓取页面的 Provider 使用 fetchPublicText() 或遵守其固定解析地址和重定向复查约定。不同并发会话和报告应使用不同的 sessionKey。

源码依据:服务与元数据、工具 schema、桥接、CLI、报告任务。