创建第一个 Provider
可运行
最快跑通
已经完成安装时,先使用仓库中的完整受检示例目录。它包含本页全部定义、知识文件和 discover.mjs,不需要把下方片段重新拼装。
把该目录保存到项目后运行:
node first-provider/discover.mjs
成功输出包含 catalog: ["route", "route.http"]、selected: ["route.http"]、document: "routing-guide" 和 specification: "SPEC-01"。下文逐项解释定义、显式选择与读取,再说明如何放进常见的 providers/acme-http 项目布局。
一个文件权威 Provider 至少需要 provider.json 和一个 *.capability.json。Core 会递归收集能力文件,但会按精确目录名跳过 .git、node_modules、dist、dist-test、coverage、.cache 和 .tmp。
前置条件与目标
完成安装,在接入项目中创建下面的文件。本页从零定义两个静态能力,最后读取一份知识文档;无需启动 HTTP 服务、MCP 或数据库。
capability-graph-demo/
├── package.json
├── discover.mjs
└── providers/acme-http/
├── provider.json
├── PROVIDER.md
├── route.capability.json
├── route-http.capability.json
└── knowledge/routing.md
前四个定义/规范文件都放在 providers/acme-http/;知识放在其 knowledge/ 子目录。
1. 定义 Provider
provider.json
{
"providerId": "acme.http",
"name": "Acme HTTP",
"version": "0.1.0",
"specification": {
"specificationId": "acme.http.conventions",
"version": "1",
"appliesTo": {
"software": "acme-http",
"versionRange": ">=0.1.0 <1.0.0"
},
"documents": [{ "kind": "document", "knowledgeId": "SPEC-01", "role": "specification", "locale": "en",
"locator": { "type": "relative-file", "path": "PROVIDER.md" } }]
}
}
documents 是 Provider Specification 的声明映射。Core 可在 readSpecification() 被显式调用时读取正文,但不会在目录、详情或 Selection 中自动读取,也不会把正文当作指令执行。
创建引用的 PROVIDER.md:
PROVIDER.md
# Acme HTTP Provider
- 修改已有路由前,通过已配置的 Runtime 查询确认项目中的实例。
- 路由注册与请求校验是不同能力,先按任务选择。
- 只读取已选 Capability 关联的 Knowledge。
Core 校验文档引用与 Provider 范围。接入层负责判断适用版本、决定是否读取正文并如何向 Agent 投递。
Specification 写跨能力的使用规则;Capability 描述可以独立发现和选择的能力;Knowledge 写执行某个已选能力时才需要的详细知识。把所有规则塞进 description 会让每次目录查询重复传输长正文。
2. 定义主能力
route.capability.json
{
"capabilityId": "route",
"name": "Routing",
"description": "Define how requests reach application handlers.",
"whenToUse": "Choose a routing capability before changing an entrypoint.",
"distinction": "A static capability family, not registered runtime routes."
}
再添加一个细分能力:
route-http.capability.json
{
"capabilityId": "route.http",
"name": "HTTP routing",
"description": "Expose a request handler through an HTTP route.",
"whenToUse": "The application accepts HTTP requests.",
"distinction": "Describes an HTTP routing capability, not a live route instance.",
"parents": ["route"],
"knowledge": [
{
"kind": "document",
"knowledgeId": "routing-guide",
"role": "guide",
"locale": "en",
"locator": { "type": "relative-file", "path": "knowledge/routing.md" }
}
]
}
关系端点必须位于同一 Provider。parents、specializes、requires 分别无环;Core 不从命名层级推导关系。
这里用 parents: ["route"] 表达 HTTP 路由属于路由能力族。route.http 中的点号只是命名约定,省略 parents 就没有父子关系。只有“保留另一能力核心语义的更具体变体”才使用 specializes;related 只提示显式关联;只有确实需要另一能力作为必要上下文时才写 requires。本例没有依赖边,因此 Selection 不会把父能力自动加入闭包。
同时创建 knowledge/routing.md。本地知识正文不进入 Static Revision;读取结果通过独立 contentId 标识。
knowledge/routing.md
# HTTP Routing Guide
Register the route in the host framework, then expose its static capability through the Provider-owned discovery API or MCP integration. Runtime route instances remain an Adapter concern.
修改绑定或 locator 会改变静态定义;只修改正文则用新的 contentId 区分内容,避免把整篇文档混进静态图哈希。
3. 打开 Provider
以下配置片段展示各项含义;第 4 步给出包含初始化和关闭的完整 discover.mjs。
import { CapabilityGraph } from '@devcodex/capability-graph';
const graph = await CapabilityGraph.open({
hostAllowedProviders: ['acme.http'],
integrationEnabledProviders: ['acme.http'],
providers: [{
providerId: 'acme.http',
authority: { kind: 'file', rootDir: '/absolute/path/to/acme-provider' }
}]
});
有效启用范围中的每个 Provider 必须恰有一个 Authority。缺少来源会得到 CG_CONFIG_INCOMPLETE,不会被解释为空目录。
有效范围为 hostAllowedProviders ∩ integrationEnabledProviders ∩ requestProviderScope,未提供请求范围时使用前两者的交集。这两个数组分别服务宿主策略与当前集成开关,不能用它们代替 providers 的加载配置。
4. 执行渐进查询
在项目根创建 discover.mjs。目录返回两个能力的摘要;本例的调用方随后明确选择 route.http,Core 不会自行判断用户意图。Provider 想首轮只展示主能力时,需在接入层定义入口策略,见下一页。
discover.mjs
import { fileURLToPath } from 'node:url';
import { CapabilityGraph } from '@devcodex/capability-graph';
const graph = await CapabilityGraph.open({
hostAllowedProviders: ['acme.http'],
integrationEnabledProviders: ['acme.http'],
providers: [{
providerId: 'acme.http',
authority: {
kind: 'file',
rootDir: fileURLToPath(new URL('./providers/acme-http/', import.meta.url))
}
}]
});
try {
const provider = graph.forProvider('acme.http');
const catalog = await provider.listCatalog({ limit: 20 });
const requiredStaticRevision = catalog.meta.staticRevision;
const detail = await provider.getCapabilities(['route.http'], { requiredStaticRevision });
const neighbors = await provider.getNeighbors('route.http', { requiredStaticRevision });
const selection = await provider.resolveSelection({ selected: ['route.http'], requiredStaticRevision });
const documents = await provider.readDocuments({
selected: selection.resolved.map(({ capabilityId }) => capabilityId),
knowledgeIds: ['routing-guide'],
roles: ['guide'],
locales: ['en'],
requiredStaticRevision
});
const specification = await provider.readSpecification({ knowledgeIds: ['SPEC-01'], requiredStaticRevision });
if (!detail.results[0]?.ok || !documents.results[0]?.ok || !specification.results[0]?.ok) {
throw new Error(JSON.stringify({ detail, documents, specification }));
}
console.log(JSON.stringify({
catalog: catalog.items.map(({ id }) => id.capabilityId),
detail: detail.results[0].value.id.capabilityId,
parents: neighbors.groups.parents.items.map(({ id }) => id.capabilityId),
selected: selection.resolved.map(({ capabilityId }) => capabilityId),
document: documents.results[0].value.knowledgeId,
specification: specification.results[0].value.knowledgeId,
text: documents.results[0].value.text,
completeness: catalog.meta.completeness
}, null, 2));
} finally {
await graph.close();
}
读取顺序是:
listCatalog() 返回用途、使用时机和关键区别,用于第一轮选择。
getCapabilities() 返回已选能力详情,关系摘要和知识引用有各自预算,不返回知识正文。
getNeighbors() 只展开请求的关系组,不递归遍历整张图。
resolveSelection() 从显式能力沿 requires 计算完整必要上下文闭包;不会沿 parents 或 related 扩张。
readDocuments() 按知识 ID、用途和语言筛选后读取;readSpecification() 是独立的显式规范读取。
始终检查 meta.completeness、warnings 和 nextCursor。部分结果不是全集;已知 ID 也可以直接查询详情,无需强制先列目录。
close() 释放 Core 持有的权威视图句柄,不取消已经交给 Adapter 的底层任务。
预期结果与排错
终端输出的精简投影如下,text 字段还会包含上面知识文件的完整正文:
{
"catalog": ["route", "route.http"],
"detail": "route.http",
"parents": ["route"],
"selected": ["route.http"],
"document": "routing-guide",
"specification": "SPEC-01",
"completeness": "complete"
}
这证明定义可加载、显式父关系可查询、Selection 不误把父节点当依赖、文档及规范可按需读取。requiredStaticRevision 来自本次 Catalog,后续查询沿用它,不能填写 Provider 的 version。
批量详情和知识返回 results,每项检查 ok;目录返回 items。其他情况见故障排查。
下一步
把这组查询原语接入 Provider 自有 API,或直接查看 Provider 自有 MCP 的渐进发现流程。