@zhin.js/mcp-feature 1.0.6 → 1.0.9
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/README.md +1 -1
- package/lib/definition.d.ts +1 -1
- package/lib/mcp-index.d.ts +2 -2
- package/lib/mcp-index.js +9 -5
- package/lib/provider.js +4 -14
- package/package.json +3 -3
- package/src/definition.ts +1 -1
- package/src/mcp-index.ts +9 -4
- package/src/provider.ts +5 -16
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ old lease = 0: previous.stop()
|
|
|
47
47
|
rollback: candidate.stop()
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
MCP
|
|
50
|
+
MCP 候选连接不会触碰旧 client。旧 turn 继续通过旧 projection 与 snapshot lease 完成在途调用;新 turn 只看到已启动并已提交的新 client。要求排他连接时,连接必须由 Process Host 持有,并让 generation 只原子切换逻辑路由,不能在 shadow prepare 中抢占旧连接。
|
|
51
51
|
|
|
52
52
|
`McpIndex` 对连接使用 owner inheritance。动态工具名由 client 返回;orchestrator 可投影为 `{connection}__{tool}`,但该模型命名不改变 Capability identity。
|
|
53
53
|
|
package/lib/definition.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export interface McpToolDescriptor {
|
|
|
6
6
|
readonly inputSchema?: unknown;
|
|
7
7
|
}
|
|
8
8
|
export interface McpClientInstance {
|
|
9
|
-
start?(): void | Promise<void>;
|
|
9
|
+
start?(signal: AbortSignal): void | Promise<void>;
|
|
10
10
|
stop?(): void | Promise<void>;
|
|
11
11
|
listTools(): readonly McpToolDescriptor[] | Promise<readonly McpToolDescriptor[]>;
|
|
12
12
|
callTool(name: string, input: unknown): unknown | Promise<unknown>;
|
package/lib/mcp-index.d.ts
CHANGED
|
@@ -10,10 +10,10 @@ export interface McpDescriptor {
|
|
|
10
10
|
export declare class McpIndex {
|
|
11
11
|
#private;
|
|
12
12
|
private constructor();
|
|
13
|
-
static create(slots: readonly Readonly<CapabilitySlot<McpDefinition>>[], snapshot: RuntimeSnapshot): Promise<McpIndex>;
|
|
13
|
+
static create(slots: readonly Readonly<CapabilitySlot<McpDefinition>>[], snapshot: RuntimeSnapshot, signal: AbortSignal): Promise<McpIndex>;
|
|
14
14
|
list(): readonly McpDescriptor[];
|
|
15
15
|
visible(requester: PluginId): readonly McpDescriptor[];
|
|
16
|
-
start(): Promise<void>;
|
|
16
|
+
start(signal: AbortSignal): Promise<void>;
|
|
17
17
|
stop(): Promise<void>;
|
|
18
18
|
listTools(requester: PluginId, name: string): Promise<readonly McpToolDescriptor[]>;
|
|
19
19
|
callTool<TResult = unknown>(requester: PluginId, connection: string, tool: string, input: unknown): Promise<TResult>;
|
package/lib/mcp-index.js
CHANGED
|
@@ -11,12 +11,14 @@ export class McpIndex {
|
|
|
11
11
|
for (const record of records)
|
|
12
12
|
this.#records.set(record.entry.slot.id, record);
|
|
13
13
|
}
|
|
14
|
-
static async create(slots, snapshot) {
|
|
14
|
+
static async create(slots, snapshot, signal) {
|
|
15
15
|
const index = new OwnerCapabilityIndex(slots, snapshot);
|
|
16
16
|
const records = [];
|
|
17
17
|
try {
|
|
18
18
|
for (const entry of index.entries()) {
|
|
19
|
-
|
|
19
|
+
signal.throwIfAborted();
|
|
20
|
+
const client = await entry.slot.definition.create(createCapabilityContext(snapshot, entry.owner, undefined, signal));
|
|
21
|
+
signal.throwIfAborted();
|
|
20
22
|
assertMcpClient(client, entry.source);
|
|
21
23
|
records.push({ entry, client, started: false, stopped: false });
|
|
22
24
|
}
|
|
@@ -33,14 +35,16 @@ export class McpIndex {
|
|
|
33
35
|
visible(requester) {
|
|
34
36
|
return this.#index.visible(requester).map(toDescriptor);
|
|
35
37
|
}
|
|
36
|
-
async start() {
|
|
38
|
+
async start(signal) {
|
|
37
39
|
const started = [];
|
|
38
40
|
try {
|
|
39
41
|
for (const record of this.#order) {
|
|
40
42
|
if (record.started)
|
|
41
43
|
continue;
|
|
44
|
+
signal.throwIfAborted();
|
|
42
45
|
started.push(record);
|
|
43
|
-
await record.client.start?.();
|
|
46
|
+
await record.client.start?.(signal);
|
|
47
|
+
signal.throwIfAborted();
|
|
44
48
|
record.started = true;
|
|
45
49
|
record.stopped = false;
|
|
46
50
|
}
|
|
@@ -92,7 +96,7 @@ async function stopRecords(records, primary) {
|
|
|
92
96
|
continue;
|
|
93
97
|
stack.add(async () => {
|
|
94
98
|
// 先 await stop 成功再标记 stopped:stop 失败时保持未标记,
|
|
95
|
-
// 下次 stop()
|
|
99
|
+
// 下次 stop() 可重试,不会因一次失败永久泄漏。
|
|
96
100
|
await record.client.stop?.();
|
|
97
101
|
record.started = false;
|
|
98
102
|
record.stopped = true;
|
package/lib/provider.js
CHANGED
|
@@ -17,29 +17,19 @@ const mcpFeature = defineFeatureProvider({
|
|
|
17
17
|
},
|
|
18
18
|
runtime: {
|
|
19
19
|
async project(slots, context) {
|
|
20
|
-
const index = await McpIndex.create(slots, context.snapshot);
|
|
21
|
-
let previousIndex;
|
|
20
|
+
const index = await McpIndex.create(slots, context.snapshot, context.signal);
|
|
22
21
|
return {
|
|
23
22
|
value: index,
|
|
24
23
|
dispose: () => index.stop(),
|
|
25
24
|
handoff: {
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return previousIndex?.stop();
|
|
30
|
-
},
|
|
31
|
-
activateNext: () => index.start(),
|
|
25
|
+
// Candidate connections become ready before publication. The old
|
|
26
|
+
// index stays usable until its snapshot's final lease drains.
|
|
27
|
+
activateNext: (signal) => index.start(signal),
|
|
32
28
|
deactivateNext: () => index.stop(),
|
|
33
|
-
resumePrevious() {
|
|
34
|
-
return previousIndex?.start();
|
|
35
|
-
},
|
|
36
29
|
},
|
|
37
30
|
};
|
|
38
31
|
},
|
|
39
32
|
},
|
|
40
33
|
});
|
|
41
|
-
function previousMcpIndex(snapshot) {
|
|
42
|
-
return snapshot.projections.get(mcpFeatureId);
|
|
43
|
-
}
|
|
44
34
|
export { mcpFeature };
|
|
45
35
|
export default mcpFeature;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/mcp-feature",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"description": "Convention-based MCP client Feature for Zhin Plugin Runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"src"
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@zhin.js/feature-kit": "1.0.
|
|
21
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
20
|
+
"@zhin.js/feature-kit": "1.0.9",
|
|
21
|
+
"@zhin.js/plugin-runtime": "1.1.6"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@types/node": "^26.1.2",
|
package/src/definition.ts
CHANGED
|
@@ -9,7 +9,7 @@ export interface McpToolDescriptor {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export interface McpClientInstance {
|
|
12
|
-
start?(): void | Promise<void>;
|
|
12
|
+
start?(signal: AbortSignal): void | Promise<void>;
|
|
13
13
|
stop?(): void | Promise<void>;
|
|
14
14
|
listTools(): readonly McpToolDescriptor[] | Promise<readonly McpToolDescriptor[]>;
|
|
15
15
|
callTool(name: string, input: unknown): unknown | Promise<unknown>;
|
package/src/mcp-index.ts
CHANGED
|
@@ -48,14 +48,17 @@ export class McpIndex {
|
|
|
48
48
|
static async create(
|
|
49
49
|
slots: readonly Readonly<CapabilitySlot<McpDefinition>>[],
|
|
50
50
|
snapshot: RuntimeSnapshot,
|
|
51
|
+
signal: AbortSignal,
|
|
51
52
|
): Promise<McpIndex> {
|
|
52
53
|
const index = new OwnerCapabilityIndex(slots, snapshot);
|
|
53
54
|
const records: McpRecord[] = [];
|
|
54
55
|
try {
|
|
55
56
|
for (const entry of index.entries()) {
|
|
57
|
+
signal.throwIfAborted();
|
|
56
58
|
const client = await entry.slot.definition.create(
|
|
57
|
-
createCapabilityContext(snapshot, entry.owner),
|
|
59
|
+
createCapabilityContext(snapshot, entry.owner, undefined, signal),
|
|
58
60
|
);
|
|
61
|
+
signal.throwIfAborted();
|
|
59
62
|
assertMcpClient(client, entry.source);
|
|
60
63
|
records.push({ entry, client, started: false, stopped: false });
|
|
61
64
|
}
|
|
@@ -74,13 +77,15 @@ export class McpIndex {
|
|
|
74
77
|
return this.#index.visible(requester).map(toDescriptor);
|
|
75
78
|
}
|
|
76
79
|
|
|
77
|
-
async start(): Promise<void> {
|
|
80
|
+
async start(signal: AbortSignal): Promise<void> {
|
|
78
81
|
const started: McpRecord[] = [];
|
|
79
82
|
try {
|
|
80
83
|
for (const record of this.#order) {
|
|
81
84
|
if (record.started) continue;
|
|
85
|
+
signal.throwIfAborted();
|
|
82
86
|
started.push(record);
|
|
83
|
-
await record.client.start?.();
|
|
87
|
+
await record.client.start?.(signal);
|
|
88
|
+
signal.throwIfAborted();
|
|
84
89
|
record.started = true;
|
|
85
90
|
record.stopped = false;
|
|
86
91
|
}
|
|
@@ -139,7 +144,7 @@ async function stopRecords(records: readonly McpRecord[], primary?: unknown): Pr
|
|
|
139
144
|
if (record.stopped) continue;
|
|
140
145
|
stack.add(async () => {
|
|
141
146
|
// 先 await stop 成功再标记 stopped:stop 失败时保持未标记,
|
|
142
|
-
// 下次 stop()
|
|
147
|
+
// 下次 stop() 可重试,不会因一次失败永久泄漏。
|
|
143
148
|
await record.client.stop?.();
|
|
144
149
|
record.started = false;
|
|
145
150
|
record.stopped = true;
|
package/src/provider.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { featureId
|
|
1
|
+
import { featureId } from '@zhin.js/plugin-runtime';
|
|
2
2
|
import { defineFeatureProvider, typeScriptModules } from '@zhin.js/feature-kit';
|
|
3
3
|
import { parseMcpDefinition } from './definition.js';
|
|
4
4
|
import { McpIndex } from './mcp-index.js';
|
|
@@ -19,31 +19,20 @@ const mcpFeature = defineFeatureProvider({
|
|
|
19
19
|
},
|
|
20
20
|
runtime: {
|
|
21
21
|
async project(slots, context) {
|
|
22
|
-
const index = await McpIndex.create(slots, context.snapshot);
|
|
23
|
-
let previousIndex: McpIndex | undefined;
|
|
22
|
+
const index = await McpIndex.create(slots, context.snapshot, context.signal);
|
|
24
23
|
return {
|
|
25
24
|
value: index,
|
|
26
25
|
dispose: () => index.stop(),
|
|
27
26
|
handoff: {
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return previousIndex?.stop();
|
|
32
|
-
},
|
|
33
|
-
activateNext: () => index.start(),
|
|
27
|
+
// Candidate connections become ready before publication. The old
|
|
28
|
+
// index stays usable until its snapshot's final lease drains.
|
|
29
|
+
activateNext: (signal) => index.start(signal),
|
|
34
30
|
deactivateNext: () => index.stop(),
|
|
35
|
-
resumePrevious() {
|
|
36
|
-
return previousIndex?.start();
|
|
37
|
-
},
|
|
38
31
|
},
|
|
39
32
|
};
|
|
40
33
|
},
|
|
41
34
|
},
|
|
42
35
|
});
|
|
43
36
|
|
|
44
|
-
function previousMcpIndex(snapshot: RuntimeSnapshot): McpIndex | undefined {
|
|
45
|
-
return snapshot.projections.get(mcpFeatureId) as McpIndex | undefined;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
37
|
export { mcpFeature };
|
|
49
38
|
export default mcpFeature;
|