attention-manifest-protocol 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,18 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this
6
6
  project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.6.1] - 2026-11-03
9
+
10
+ ### Added
11
+
12
+ - `ProtocolRegistry` can bind a runtime implementation to a registered protocol entry: `bind`, `implementation`, `resolveImplementation` (type-guarded, optional family check) and `bound`. Implementations are stored by reference and never appear in discovery metadata, and `remove`/`clear` drop the bound implementation together with the entry.
13
+ - `ProtocolEntry.family` names the protocol family, so entries registered by `ProtocolRegistry` can be filtered by protocol (`mcp`, `a2a`, ...).
14
+ - MCP is registered as a concrete protocol rather than a separate registry: `mcpProtocolEntry` builds a `family: "mcp"` entry with its protocol version, endpoint and capabilities, and `mcpListTools`, `mcpSyncTools` and `mcpWithdrawTools` read and mirror an endpoint's tools through the bound implementation.
15
+
16
+ ### Validation
17
+
18
+ - Joint regression: 189/189 and 50/50 across protocol, registry, discovery, MCP executor, workspace and database suites; source and tools typechecks passed.
19
+
8
20
  ## [0.6.0] - 2026-11-03
9
21
 
10
22
  ### Added
@@ -5,6 +5,18 @@
5
5
  格式遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),版本号遵循
6
6
  [语义化版本](https://semver.org/lang/zh-CN/)。
7
7
 
8
+ ## [0.6.1] - 2026-11-03
9
+
10
+ ### 新增
11
+
12
+ - `ProtocolRegistry` 可为已登记的协议绑定运行时实现:`bind`、`implementation`、`resolveImplementation`(带类型守卫,可选 family 校验)与 `bound`。实现按引用保存,不会出现在发现元数据中;`remove`/`clear` 会连同条目一起丢弃已绑定的实现。
13
+ - `ProtocolEntry.family` 标注协议族,登记到 `ProtocolRegistry` 的条目可按协议(`mcp`、`a2a` 等)筛选。
14
+ - MCP 作为具体协议登记,而不是单开注册中心:`mcpProtocolEntry` 生成携带协议版本、endpoint 与能力的 `family: "mcp"` 条目,`mcpListTools`、`mcpSyncTools` 与 `mcpWithdrawTools` 通过绑定实现读取并镜像该 endpoint 的工具。
15
+
16
+ ### 验证
17
+
18
+ - 联合回归 189/189 与 50/50 通过(协议、注册中心、发现、MCP 执行器、工作区与数据库套件);源代码与工具两套类型检查通过。
19
+
8
20
  ## [0.6.0] - 2026-11-03
9
21
 
10
22
  ### 新增
package/README.md CHANGED
@@ -208,6 +208,8 @@ await diff.run({ path: "src/new.ts", before: "" });
208
208
 
209
209
  ### MCP stdio
210
210
 
211
+ MCP is a concrete protocol, so it is registered in `ProtocolRegistry` rather than a registry of its own. `mcpProtocolEntry(id, name, options)` builds a `family: "mcp"` entry carrying the protocol version (default `2026-07-28`), endpoint and capabilities; `protocols.bind(id, implementation)` attaches the host-built client and transport, and discovery keeps exposing only the entry's metadata. `protocols.resolveImplementation(id, isMcpProtocol, "mcp")` returns the bound endpoint or `undefined` when the family or the object does not match, `mcpListTools(protocol)` reads the advertised tools, `mcpSyncTools(protocol, toolRegistry)` registers new tools, updates changed ones in place and removes tools the endpoint no longer advertises, and `mcpWithdrawTools` drops one endpoint's tools without touching host tools or other protocols. Risk comes only from annotations (`destructiveHint` → destructive, `readOnlyHint` → read, otherwise write), and nothing here turns a URL string into a connection, so registering cannot make the agent reach a host it was not given. `ProtocolRegistry.remove`/`clear` drop the bound implementation together with the entry.
212
+
211
213
  ```sh
212
214
  npm run workspace-agent-demo
213
215
  npm run workspace-agent -- --config .cache/workspace-agent.local
package/README.zh-CN.md CHANGED
@@ -211,6 +211,8 @@ await diff.run({ path: "src/new.ts", before: "" });
211
211
 
212
212
  ### MCP stdio 接入
213
213
 
214
+ MCP 是一种具体协议,因此登记在 `ProtocolRegistry` 中,而不是单开一个注册中心。`mcpProtocolEntry(id, name, options)` 生成 `family: "mcp"` 的协议条目,携带协议版本(默认 `2026-07-28`)、endpoint 与能力;`protocols.bind(id, implementation)` 绑定宿主自己建好的 client 与 transport,发现接口仍只暴露条目元数据。`protocols.resolveImplementation(id, isMcpProtocol, "mcp")` 在 family 或对象不匹配时返回 `undefined`;`mcpListTools(protocol)` 读取通告的工具,`mcpSyncTools(protocol, toolRegistry)` 注册新工具、原地更新变化的工具、移除 endpoint 不再通告的工具,`mcpWithdrawTools` 撤下一个 endpoint 的工具而不影响宿主工具和其他协议。风险等级只由注解决定(`destructiveHint` → destructive,`readOnlyHint` → read,其余为 write),且不会把 URL 字符串变成连接,因此登记不能让 agent 访问未经宿主的地址。`ProtocolRegistry.remove`/`clear` 会连同条目一起丢弃已绑定的实现。
215
+
214
216
  ```sh
215
217
  npm run workspace-agent-demo
216
218
  npm run workspace-agent -- --config .cache/workspace-agent.local
@@ -9,6 +9,7 @@ export * from "./tool/globalExecutor.js";
9
9
  export * from "./tool/controlExecutor.js";
10
10
  export * from "./tool/filesystemExecutor.js";
11
11
  export * from "./tool/globalFilesystemExecutor.js";
12
+ export * from "./mcp/binding.js";
12
13
  export * from "./mcp/workspaceServer.js";
13
14
  export * from "./mcp/filesystemServer.js";
14
15
  export * from "./sandbox/sandbox.js";
@@ -9,6 +9,7 @@ export * from "./tool/globalExecutor.js";
9
9
  export * from "./tool/controlExecutor.js";
10
10
  export * from "./tool/filesystemExecutor.js";
11
11
  export * from "./tool/globalFilesystemExecutor.js";
12
+ export * from "./mcp/binding.js";
12
13
  export * from "./mcp/workspaceServer.js";
13
14
  export * from "./mcp/filesystemServer.js";
14
15
  export * from "./sandbox/sandbox.js";
@@ -0,0 +1,94 @@
1
+ /**
2
+ * MCP as one concrete protocol registered in the protocol registry.
3
+ *
4
+ * MCP is a specific wire protocol, not an extension of the tool registry: it
5
+ * has its own version, its own capability set and its own transport. Rather
6
+ * than a second registry, an MCP endpoint is an ordinary `ProtocolEntry`
7
+ * (`family: "mcp"`) whose bound implementation is an `McpProtocol`. That keeps
8
+ * "which protocols does this host speak" in one place, next to A2A, the
9
+ * workspace protocol and anything else a host registers.
10
+ *
11
+ * The implementation owns the client the host already built. Nothing here turns
12
+ * a URL string into a connection, so registering cannot widen what the agent
13
+ * can reach.
14
+ */
15
+ import type { McpClient } from "./client.js";
16
+ import type { McpTool } from "./protocol.js";
17
+ import type { Transport } from "./transport.js";
18
+ import type { ProtocolEntry } from "../../protocol/registry.js";
19
+ import type { ToolDescriptor, ToolRegistry } from "../tool/tool.js";
20
+ /** Family tag every MCP entry carries. */
21
+ export declare const McpProtocolFamily: string;
22
+ /**
23
+ * Implementation bound to an `mcp` protocol entry.
24
+ */
25
+ export interface McpProtocol {
26
+ readonly family: typeof McpProtocolFamily;
27
+ /** Client used for `listTools`/`callTool`. */
28
+ readonly client: McpClient;
29
+ /** Optional transport, kept so a host can inspect or close it. */
30
+ readonly transport?: Transport;
31
+ /** Tools last read from this endpoint, in advertised order. */
32
+ readonly tools?: readonly string[];
33
+ }
34
+ /**
35
+ * Type guard for `resolveImplementation`.
36
+ */
37
+ export declare const isMcpProtocol: (value: unknown) => value is McpProtocol;
38
+ /**
39
+ * Risk tier for one MCP tool, derived only from its declared annotations.
40
+ * An unannotated tool is `write`: the safest assumption is that it may act.
41
+ */
42
+ export declare const mcpToolRisk: (tool: McpTool) => ToolDescriptor["risk"];
43
+ /**
44
+ * Convert one advertised MCP tool into a tool descriptor. The executable name
45
+ * is the bare MCP name, so a server cannot rename it into another namespace.
46
+ */
47
+ export declare const mcpToolDescriptor: (tool: McpTool) => ToolDescriptor;
48
+ /**
49
+ * Protocol entry for one MCP endpoint.
50
+ *
51
+ * @param id - Host-chosen protocol id, unique in the protocol registry.
52
+ * @param name - Human label.
53
+ * @param options - Optional endpoint, version override, description and status.
54
+ * @returns A validated `family: "mcp"` entry carrying the MCP capabilities.
55
+ */
56
+ export declare const mcpProtocolEntry: (id: string, name: string, options?: {
57
+ endpoint?: string;
58
+ version?: string;
59
+ description?: string;
60
+ status?: ProtocolEntry["status"];
61
+ capabilities?: readonly string[];
62
+ }) => ProtocolEntry;
63
+ /**
64
+ * Read one endpoint's advertised tool list again.
65
+ *
66
+ * @param protocol - Bound implementation.
67
+ * @returns The advertised tools.
68
+ */
69
+ export declare const mcpListTools: (protocol: McpProtocol) => Promise<McpTool[]>;
70
+ /**
71
+ * Mirror one endpoint's tools into a tool registry.
72
+ *
73
+ * A new tool is registered, a changed schema/description/risk is updated in
74
+ * place, and a tool the endpoint no longer advertises is removed. The reply
75
+ * lists all three sets so a host can audit the change instead of diffing
76
+ * silently.
77
+ *
78
+ * @param protocol - Bound implementation; `tools` is used as the previous set.
79
+ * @param tools - Registry the runtime executes from.
80
+ * @returns Added, updated and removed descriptors.
81
+ */
82
+ export declare const mcpSyncTools: (protocol: McpProtocol, tools: ToolRegistry) => Promise<{
83
+ added: ToolDescriptor[];
84
+ updated: ToolDescriptor[];
85
+ removed: string[];
86
+ }>;
87
+ /**
88
+ * Withdraw one endpoint's tools from a tool registry.
89
+ *
90
+ * @param protocol - Bound implementation; `tools` is the last advertised set.
91
+ * @param tools - Registry to withdraw from.
92
+ * @returns Names actually removed.
93
+ */
94
+ export declare const mcpWithdrawTools: (protocol: McpProtocol, tools: ToolRegistry) => string[];
@@ -0,0 +1,97 @@
1
+ import { ProtocolVersion } from "./protocol.js";
2
+ /** Family tag every MCP entry carries. */
3
+ export const McpProtocolFamily = "mcp";
4
+ /**
5
+ * Type guard for `resolveImplementation`.
6
+ */
7
+ export const isMcpProtocol = (value) => typeof (value) === "object" && value !== null && value.family === McpProtocolFamily
8
+ && typeof (value.client) === "object" && value.client !== null;
9
+ /**
10
+ * Risk tier for one MCP tool, derived only from its declared annotations.
11
+ * An unannotated tool is `write`: the safest assumption is that it may act.
12
+ */
13
+ export const mcpToolRisk = (tool) => {
14
+ if (tool.annotations?.destructiveHint === true) {
15
+ return "destructive";
16
+ }
17
+ return tool.annotations?.readOnlyHint === true ? "read" : "write";
18
+ };
19
+ /**
20
+ * Convert one advertised MCP tool into a tool descriptor. The executable name
21
+ * is the bare MCP name, so a server cannot rename it into another namespace.
22
+ */
23
+ export const mcpToolDescriptor = (tool) => ({
24
+ name: tool.name,
25
+ description: tool.description ?? tool.title ?? tool.name,
26
+ inputSchema: structuredClone(tool.inputSchema),
27
+ risk: mcpToolRisk(tool),
28
+ });
29
+ /**
30
+ * Protocol entry for one MCP endpoint.
31
+ *
32
+ * @param id - Host-chosen protocol id, unique in the protocol registry.
33
+ * @param name - Human label.
34
+ * @param options - Optional endpoint, version override, description and status.
35
+ * @returns A validated `family: "mcp"` entry carrying the MCP capabilities.
36
+ */
37
+ export const mcpProtocolEntry = (id, name, options = {}) => ({
38
+ id,
39
+ name,
40
+ version: options.version ?? ProtocolVersion,
41
+ description: options.description ?? "Model Context Protocol endpoint",
42
+ family: McpProtocolFamily,
43
+ ...(options.status === (void 0) ? {} : { status: options.status }),
44
+ ...(options.endpoint === (void 0) ? {} : { endpoint: options.endpoint }),
45
+ capabilities: [...(options.capabilities ?? ["tools"])],
46
+ });
47
+ /**
48
+ * Read one endpoint's advertised tool list again.
49
+ *
50
+ * @param protocol - Bound implementation.
51
+ * @returns The advertised tools.
52
+ */
53
+ export const mcpListTools = async (protocol) => (await protocol.client.listTools()).tools;
54
+ /**
55
+ * Mirror one endpoint's tools into a tool registry.
56
+ *
57
+ * A new tool is registered, a changed schema/description/risk is updated in
58
+ * place, and a tool the endpoint no longer advertises is removed. The reply
59
+ * lists all three sets so a host can audit the change instead of diffing
60
+ * silently.
61
+ *
62
+ * @param protocol - Bound implementation; `tools` is used as the previous set.
63
+ * @param tools - Registry the runtime executes from.
64
+ * @returns Added, updated and removed descriptors.
65
+ */
66
+ export const mcpSyncTools = async (protocol, tools) => {
67
+ const listed = await mcpListTools(protocol);
68
+ const descriptors = listed.map(mcpToolDescriptor);
69
+ const advertised = descriptors.map((descriptor) => descriptor.name);
70
+ const added = [];
71
+ const updated = [];
72
+ for (const descriptor of descriptors) {
73
+ if (tools.get(descriptor.name) === (void 0)) {
74
+ tools.register(descriptor);
75
+ added.push(descriptor);
76
+ }
77
+ else {
78
+ tools.update(descriptor);
79
+ updated.push(descriptor);
80
+ }
81
+ }
82
+ const removed = [];
83
+ for (const name of protocol.tools ?? []) {
84
+ if (!advertised.includes(name) && tools.remove(name)) {
85
+ removed.push(name);
86
+ }
87
+ }
88
+ return { added, updated, removed };
89
+ };
90
+ /**
91
+ * Withdraw one endpoint's tools from a tool registry.
92
+ *
93
+ * @param protocol - Bound implementation; `tools` is the last advertised set.
94
+ * @param tools - Registry to withdraw from.
95
+ * @returns Names actually removed.
96
+ */
97
+ export const mcpWithdrawTools = (protocol, tools) => (protocol.tools ?? []).filter((name) => tools.remove(name));
@@ -8,15 +8,58 @@ export interface ProtocolEntry {
8
8
  status?: ProtocolStatus;
9
9
  endpoint?: string;
10
10
  capabilities?: string[];
11
+ /**
12
+ * Protocol family this entry implements, for example `mcp` or `a2a`.
13
+ * Discovery projects it as metadata only; it never grants execution.
14
+ */
15
+ family?: string;
11
16
  }
17
+ /**
18
+ * Runtime handler a protocol entry can bind to.
19
+ *
20
+ * The implementation is a host-supplied value: it may carry a client, a
21
+ * transport or any other executor. The registry stores and returns it by
22
+ * reference, so binding a protocol cannot change the entry's discoverable
23
+ * metadata and discovery never exposes it.
24
+ */
25
+ export type ProtocolImplementation = object;
12
26
  export declare class ProtocolRegistry {
13
27
  private readonly entries;
28
+ private readonly implementations;
14
29
  register(protocol: ProtocolEntry): ProtocolEntry;
15
30
  /** Replace an existing protocol. The id must stay the same. */
16
31
  update(protocol: ProtocolEntry): ProtocolEntry;
17
- /** Remove one protocol; the result says whether it existed. */
32
+ /**
33
+ * Bind or replace the runtime implementation of one registered protocol.
34
+ *
35
+ * @param id - Registered protocol id.
36
+ * @param implementation - Host-supplied handler; stored by reference.
37
+ * @returns The implementation.
38
+ * @throws When the protocol id is not registered.
39
+ */
40
+ bind(id: string, implementation: ProtocolImplementation): ProtocolImplementation;
41
+ /**
42
+ * Runtime implementation bound to one protocol.
43
+ *
44
+ * @param id - Protocol id.
45
+ * @returns The implementation, or `undefined` when none is bound.
46
+ */
47
+ implementation(id: string): ProtocolImplementation | undefined;
48
+ /**
49
+ * Bound implementation narrowed to one known protocol type.
50
+ *
51
+ * @param id - Protocol id.
52
+ * @param isProtocol - Type guard for the expected implementation.
53
+ * @param family - Optional family that must match the entry.
54
+ * @returns The implementation, or `undefined` when absent, unbound or of
55
+ * another family.
56
+ */
57
+ resolveImplementation<T extends ProtocolImplementation>(id: string, isProtocol: (value: unknown) => value is T, family?: string): T | undefined;
58
+ /** Protocol ids that currently have a bound implementation. */
59
+ bound(): string[];
60
+ /** Remove one protocol and any bound implementation; says whether it existed. */
18
61
  remove(id: string): boolean;
19
- /** Remove every protocol and report how many were dropped. */
62
+ /** Remove every protocol and bound implementation; reports how many entries were dropped. */
20
63
  clear(): number;
21
64
  get(id: string): ProtocolEntry | undefined;
22
65
  list(options?: {
@@ -20,6 +20,9 @@ const validateProtocol = (protocol) => {
20
20
  if (protocol.endpoint !== (void 0) && protocol.endpoint.trim().length === 0) {
21
21
  throw new Error("protocol endpoint must be a non-empty string");
22
22
  }
23
+ if (protocol.family !== (void 0) && protocol.family.trim().length === 0) {
24
+ throw new Error("protocol family must be a non-empty string");
25
+ }
23
26
  if (protocol.capabilities !== (void 0) &&
24
27
  (!Array.isArray(protocol.capabilities) || protocol.capabilities.some((value) => typeof (value) !== "string" || value.trim().length === 0))) {
25
28
  throw new Error("protocol capabilities must contain non-empty strings");
@@ -27,6 +30,7 @@ const validateProtocol = (protocol) => {
27
30
  };
28
31
  export class ProtocolRegistry {
29
32
  entries = new InMemoryRegistry(cloneProtocol, (id) => new Error(`protocol ${id} is already registered`));
33
+ implementations = new Map();
30
34
  register(protocol) {
31
35
  validateProtocol(protocol);
32
36
  return this.entries.register(protocol);
@@ -36,12 +40,62 @@ export class ProtocolRegistry {
36
40
  validateProtocol(protocol);
37
41
  return this.entries.update(protocol.id, protocol);
38
42
  }
39
- /** Remove one protocol; the result says whether it existed. */
43
+ /**
44
+ * Bind or replace the runtime implementation of one registered protocol.
45
+ *
46
+ * @param id - Registered protocol id.
47
+ * @param implementation - Host-supplied handler; stored by reference.
48
+ * @returns The implementation.
49
+ * @throws When the protocol id is not registered.
50
+ */
51
+ bind(id, implementation) {
52
+ if (!this.entries.has(id)) {
53
+ throw new Error(`protocol ${id} is not registered`);
54
+ }
55
+ if (implementation === null || typeof (implementation) !== "object") {
56
+ throw new TypeError("protocol implementation must be an object");
57
+ }
58
+ this.implementations.set(id, implementation);
59
+ return implementation;
60
+ }
61
+ /**
62
+ * Runtime implementation bound to one protocol.
63
+ *
64
+ * @param id - Protocol id.
65
+ * @returns The implementation, or `undefined` when none is bound.
66
+ */
67
+ implementation(id) {
68
+ return this.implementations.get(id);
69
+ }
70
+ /**
71
+ * Bound implementation narrowed to one known protocol type.
72
+ *
73
+ * @param id - Protocol id.
74
+ * @param isProtocol - Type guard for the expected implementation.
75
+ * @param family - Optional family that must match the entry.
76
+ * @returns The implementation, or `undefined` when absent, unbound or of
77
+ * another family.
78
+ */
79
+ resolveImplementation(id, isProtocol, family) {
80
+ const entry = this.entries.get(id);
81
+ if (entry === (void 0) || (family !== (void 0) && entry.family !== family)) {
82
+ return (void 0);
83
+ }
84
+ const implementation = this.implementations.get(id);
85
+ return implementation !== (void 0) && isProtocol(implementation) ? implementation : (void 0);
86
+ }
87
+ /** Protocol ids that currently have a bound implementation. */
88
+ bound() {
89
+ return [...this.implementations.keys()].sort();
90
+ }
91
+ /** Remove one protocol and any bound implementation; says whether it existed. */
40
92
  remove(id) {
93
+ this.implementations.delete(id);
41
94
  return this.entries.remove(id);
42
95
  }
43
- /** Remove every protocol and report how many were dropped. */
96
+ /** Remove every protocol and bound implementation; reports how many entries were dropped. */
44
97
  clear() {
98
+ this.implementations.clear();
45
99
  return this.entries.clear();
46
100
  }
47
101
  get(id) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "attention-manifest-protocol",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "files": [
5
5
  "lib",
6
6
  "docs/schemas",