@zhin.js/mcp-feature 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 凉菜
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # @zhin.js/mcp-feature
2
+
3
+ 下一代 MCP Client Feature。它从 `mcp/<name>.ts` 发现 provider-neutral client definition,并将连接实例的创建、启动、调用和销毁绑定到 Plugin owner 与 generation lease。
4
+
5
+ ## 目录约定
6
+
7
+ ```text
8
+ mcp/
9
+ ├── filesystem.ts
10
+ └── memory.ts
11
+ ```
12
+
13
+ 只发现一级 `.ts` 文件。连接参数、URL、command 和 secret 应从 owner config/resource 读取,不写入另一份全局 MCP 配置 registry。
14
+
15
+ ## 定义 MCP Client
16
+
17
+ ```ts
18
+ import { defineMcp } from '@zhin.js/mcp-feature';
19
+ import { mcpTransportFactoryToken } from '../plugin.js';
20
+
21
+ export default defineMcp({
22
+ description: 'Workspace filesystem tools',
23
+ create(context) {
24
+ const client = context.use(mcpTransportFactoryToken).create(context.config);
25
+ return {
26
+ start: () => client.connect(),
27
+ stop: () => client.close(),
28
+ listTools: () => client.listTools(),
29
+ callTool: (name, input) => client.callTool(name, input),
30
+ };
31
+ },
32
+ });
33
+ ```
34
+
35
+ `create()` 在 projection prepare 中执行,只能构造 inert client;网络连接必须放在 `start()`。具体 stdio/HTTP transport 和 MCP SDK 由 Plugin Resource 或可选 adapter 提供,本包没有相关第三方依赖。
36
+
37
+ ## 生命周期
38
+
39
+ ```text
40
+ prepare candidate: create()
41
+ before commit: candidate.start()
42
+ commit: publish new McpIndex
43
+ old lease = 0: previous.stop()
44
+ rollback: candidate.stop()
45
+ ```
46
+
47
+ MCP 没有入站 admission,因此候选连接不 quiesce 旧 client。旧 turn 可继续通过旧 projection 完成在途调用;新 turn 只看到已启动并已提交的新 client。要求排他连接时,应把连接资源提升到 Plugin Resource handoff。
48
+
49
+ `McpIndex` 对连接使用 owner inheritance。动态工具名由 client 返回;orchestrator 可投影为 `{connection}__{tool}`,但该模型命名不改变 Capability identity。
50
+
51
+ ## 验证
52
+
53
+ ```bash
54
+ pnpm --filter @zhin.js/mcp-feature test
55
+ pnpm --filter @zhin.js/mcp-feature build
56
+ ```
@@ -0,0 +1,22 @@
1
+ import type { CapabilityContext } from '@zhin.js/feature-kit';
2
+ declare const mcpBrand: "zhin.mcp/1";
3
+ export interface McpToolDescriptor {
4
+ readonly name: string;
5
+ readonly description?: string;
6
+ readonly inputSchema?: unknown;
7
+ }
8
+ export interface McpClientInstance {
9
+ start?(): void | Promise<void>;
10
+ stop?(): void | Promise<void>;
11
+ listTools(): readonly McpToolDescriptor[] | Promise<readonly McpToolDescriptor[]>;
12
+ callTool(name: string, input: unknown): unknown | Promise<unknown>;
13
+ }
14
+ export interface McpDefinition<TConfig = unknown> {
15
+ readonly $feature: typeof mcpBrand;
16
+ readonly description?: string;
17
+ create(context: CapabilityContext<TConfig>): McpClientInstance | Promise<McpClientInstance>;
18
+ }
19
+ export declare function defineMcp<TConfig = unknown>(definition: Omit<McpDefinition<TConfig>, '$feature'>): Readonly<McpDefinition<TConfig>>;
20
+ export declare function parseMcpDefinition(value: unknown): McpDefinition;
21
+ export declare function assertMcpClient(value: unknown, source: string): asserts value is McpClientInstance;
22
+ export {};
@@ -0,0 +1,30 @@
1
+ const mcpBrand = 'zhin.mcp/1';
2
+ export function defineMcp(definition) {
3
+ if (typeof definition.create !== 'function') {
4
+ throw new TypeError('MCP create must be a function');
5
+ }
6
+ return Object.freeze({ ...definition, $feature: mcpBrand });
7
+ }
8
+ export function parseMcpDefinition(value) {
9
+ if (!value || typeof value !== 'object')
10
+ throw invalidMcp();
11
+ const definition = value;
12
+ if (definition.$feature !== mcpBrand || typeof definition.create !== 'function') {
13
+ throw invalidMcp();
14
+ }
15
+ return definition;
16
+ }
17
+ export function assertMcpClient(value, source) {
18
+ if (!value || typeof value !== 'object')
19
+ throw invalidClient(source);
20
+ const client = value;
21
+ if (typeof client.listTools !== 'function' || typeof client.callTool !== 'function') {
22
+ throw invalidClient(source);
23
+ }
24
+ }
25
+ function invalidMcp() {
26
+ return new TypeError('MCP module must default-export defineMcp(...)');
27
+ }
28
+ function invalidClient(source) {
29
+ return new TypeError(`MCP ${source} create() must return a client instance`);
30
+ }
package/lib/index.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from './definition.js';
2
+ export * from './mcp-index.js';
3
+ export * from './provider.js';
4
+ export { default } from './provider.js';
package/lib/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from './definition.js';
2
+ export * from './mcp-index.js';
3
+ export * from './provider.js';
4
+ export { default } from './provider.js';
@@ -0,0 +1,20 @@
1
+ import { type CapabilitySlot, type PluginId, type RuntimeSnapshot } from '@zhin.js/plugin-runtime';
2
+ import { type McpDefinition, type McpToolDescriptor } from './definition.js';
3
+ export interface McpDescriptor {
4
+ readonly owner: PluginId;
5
+ readonly name: string;
6
+ readonly qualifiedName: string;
7
+ readonly description?: string;
8
+ readonly source: string;
9
+ }
10
+ export declare class McpIndex {
11
+ #private;
12
+ private constructor();
13
+ static create(slots: readonly Readonly<CapabilitySlot<McpDefinition>>[], snapshot: RuntimeSnapshot): Promise<McpIndex>;
14
+ list(): readonly McpDescriptor[];
15
+ visible(requester: PluginId): readonly McpDescriptor[];
16
+ start(): Promise<void>;
17
+ stop(): Promise<void>;
18
+ listTools(requester: PluginId, name: string): Promise<readonly McpToolDescriptor[]>;
19
+ callTool<TResult = unknown>(requester: PluginId, connection: string, tool: string, input: unknown): Promise<TResult>;
20
+ }
@@ -0,0 +1,106 @@
1
+ import { DisposeStack, } from '@zhin.js/plugin-runtime';
2
+ import { OwnerCapabilityIndex, createCapabilityContext, } from '@zhin.js/feature-kit';
3
+ import { assertMcpClient, } from './definition.js';
4
+ export class McpIndex {
5
+ #index;
6
+ #records = new Map();
7
+ #order;
8
+ constructor(index, records) {
9
+ this.#index = index;
10
+ this.#order = Object.freeze([...records]);
11
+ for (const record of records)
12
+ this.#records.set(record.entry.slot.id, record);
13
+ }
14
+ static async create(slots, snapshot) {
15
+ const index = new OwnerCapabilityIndex(slots, snapshot);
16
+ const records = [];
17
+ try {
18
+ for (const entry of index.entries()) {
19
+ const client = await entry.slot.definition.create(createCapabilityContext(snapshot, entry.owner));
20
+ assertMcpClient(client, entry.source);
21
+ records.push({ entry, client, started: false, stopped: false });
22
+ }
23
+ return new McpIndex(index, records);
24
+ }
25
+ catch (error) {
26
+ await stopRecords(records, error);
27
+ throw error;
28
+ }
29
+ }
30
+ list() {
31
+ return this.#index.entries().map(toDescriptor);
32
+ }
33
+ visible(requester) {
34
+ return this.#index.visible(requester).map(toDescriptor);
35
+ }
36
+ async start() {
37
+ const started = [];
38
+ try {
39
+ for (const record of this.#order) {
40
+ if (record.started || record.stopped)
41
+ continue;
42
+ started.push(record);
43
+ await record.client.start?.();
44
+ record.started = true;
45
+ }
46
+ }
47
+ catch (error) {
48
+ await stopRecords(started, error);
49
+ throw error;
50
+ }
51
+ }
52
+ async stop() {
53
+ await stopRecords(this.#order);
54
+ }
55
+ async listTools(requester, name) {
56
+ const record = this.#resolve(requester, name);
57
+ return Object.freeze((await record.client.listTools()).map((tool) => {
58
+ if (!tool.name.trim())
59
+ throw new TypeError(`MCP ${name} returned a Tool without a name`);
60
+ return Object.freeze({ ...tool });
61
+ }));
62
+ }
63
+ async callTool(requester, connection, tool, input) {
64
+ const record = this.#resolve(requester, connection);
65
+ return record.client.callTool(tool, input);
66
+ }
67
+ #resolve(requester, name) {
68
+ const entry = this.#index.resolve(requester, name);
69
+ if (!entry)
70
+ throw new Error(`Unknown MCP connection ${name} for ${requester}`);
71
+ const record = this.#records.get(entry.slot.id);
72
+ if (!record || !record.started || record.stopped) {
73
+ throw new Error(`MCP connection is not active: ${entry.qualifiedName}`);
74
+ }
75
+ return record;
76
+ }
77
+ }
78
+ function toDescriptor(entry) {
79
+ return Object.freeze({
80
+ owner: entry.owner,
81
+ name: entry.name,
82
+ qualifiedName: entry.qualifiedName,
83
+ description: entry.slot.definition.description,
84
+ source: entry.source,
85
+ });
86
+ }
87
+ async function stopRecords(records, primary) {
88
+ const stack = new DisposeStack();
89
+ for (const record of records) {
90
+ if (record.stopped)
91
+ continue;
92
+ stack.add(async () => {
93
+ record.stopped = true;
94
+ await record.client.stop?.();
95
+ });
96
+ }
97
+ try {
98
+ await stack.dispose();
99
+ }
100
+ catch (stopError) {
101
+ if (primary !== undefined) {
102
+ throw new AggregateError([primary, stopError], 'MCP prepare and client cleanup both failed', { cause: stopError });
103
+ }
104
+ throw stopError;
105
+ }
106
+ }
@@ -0,0 +1,5 @@
1
+ import { McpIndex } from './mcp-index.js';
2
+ export declare const mcpFeatureId: import("@zhin.js/plugin-runtime").FeatureId;
3
+ declare const mcpFeature: Readonly<import("@zhin.js/feature-kit").FeatureProvider<import("./definition.js").McpDefinition<unknown>, McpIndex>>;
4
+ export { mcpFeature };
5
+ export default mcpFeature;
@@ -0,0 +1,32 @@
1
+ import { featureId } from '@zhin.js/plugin-runtime';
2
+ import { defineFeatureProvider, typeScriptModules } from '@zhin.js/feature-kit';
3
+ import { parseMcpDefinition } from './definition.js';
4
+ import { McpIndex } from './mcp-index.js';
5
+ export const mcpFeatureId = featureId('zhin.mcp');
6
+ const mcpFeature = defineFeatureProvider({
7
+ protocol: 1,
8
+ id: mcpFeatureId,
9
+ authoring: {
10
+ conventions: [typeScriptModules({
11
+ id: 'mcp-ts',
12
+ directory: 'mcp',
13
+ recursive: false,
14
+ })],
15
+ validate: parseMcpDefinition,
16
+ },
17
+ runtime: {
18
+ async project(slots, context) {
19
+ const index = await McpIndex.create(slots, context.snapshot);
20
+ return {
21
+ value: index,
22
+ dispose: () => index.stop(),
23
+ handoff: {
24
+ activateNext: () => index.start(),
25
+ deactivateNext: () => index.stop(),
26
+ },
27
+ };
28
+ },
29
+ },
30
+ });
31
+ export { mcpFeature };
32
+ export default mcpFeature;
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@zhin.js/mcp-feature",
3
+ "version": "1.0.0",
4
+ "description": "Convention-based MCP client Feature for Zhin Plugin Runtime",
5
+ "type": "module",
6
+ "main": "./lib/index.js",
7
+ "types": "./lib/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./lib/index.d.ts",
11
+ "development": "./src/index.ts",
12
+ "import": "./lib/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "lib",
17
+ "src"
18
+ ],
19
+ "dependencies": {
20
+ "@zhin.js/feature-kit": "1.0.0",
21
+ "@zhin.js/plugin-runtime": "1.0.0"
22
+ },
23
+ "devDependencies": {
24
+ "@types/node": "^26.1.0",
25
+ "typescript": "^6.0.3"
26
+ },
27
+ "zhin": {
28
+ "protocol": 1,
29
+ "type": "feature",
30
+ "entry": "./lib/provider.js",
31
+ "engine": "^1.0.0",
32
+ "featureApi": "1.0.0"
33
+ },
34
+ "engines": {
35
+ "node": "^20.19.0 || >=22.12.0"
36
+ },
37
+ "license": "MIT",
38
+ "private": false,
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/zhinjs/zhin.git",
42
+ "directory": "packages/im/mcp-feature"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public",
46
+ "registry": "https://registry.npmjs.org"
47
+ },
48
+ "scripts": {
49
+ "build": "tsc",
50
+ "clean": "rimraf lib",
51
+ "test": "vitest run --root ../../.. packages/im/mcp-feature/tests"
52
+ }
53
+ }
@@ -0,0 +1,56 @@
1
+ import type { CapabilityContext } from '@zhin.js/feature-kit';
2
+
3
+ const mcpBrand = 'zhin.mcp/1' as const;
4
+
5
+ export interface McpToolDescriptor {
6
+ readonly name: string;
7
+ readonly description?: string;
8
+ readonly inputSchema?: unknown;
9
+ }
10
+
11
+ export interface McpClientInstance {
12
+ start?(): void | Promise<void>;
13
+ stop?(): void | Promise<void>;
14
+ listTools(): readonly McpToolDescriptor[] | Promise<readonly McpToolDescriptor[]>;
15
+ callTool(name: string, input: unknown): unknown | Promise<unknown>;
16
+ }
17
+
18
+ export interface McpDefinition<TConfig = unknown> {
19
+ readonly $feature: typeof mcpBrand;
20
+ readonly description?: string;
21
+ create(context: CapabilityContext<TConfig>): McpClientInstance | Promise<McpClientInstance>;
22
+ }
23
+
24
+ export function defineMcp<TConfig = unknown>(
25
+ definition: Omit<McpDefinition<TConfig>, '$feature'>,
26
+ ): Readonly<McpDefinition<TConfig>> {
27
+ if (typeof definition.create !== 'function') {
28
+ throw new TypeError('MCP create must be a function');
29
+ }
30
+ return Object.freeze({ ...definition, $feature: mcpBrand });
31
+ }
32
+
33
+ export function parseMcpDefinition(value: unknown): McpDefinition {
34
+ if (!value || typeof value !== 'object') throw invalidMcp();
35
+ const definition = value as Partial<McpDefinition>;
36
+ if (definition.$feature !== mcpBrand || typeof definition.create !== 'function') {
37
+ throw invalidMcp();
38
+ }
39
+ return definition as McpDefinition;
40
+ }
41
+
42
+ export function assertMcpClient(value: unknown, source: string): asserts value is McpClientInstance {
43
+ if (!value || typeof value !== 'object') throw invalidClient(source);
44
+ const client = value as Partial<McpClientInstance>;
45
+ if (typeof client.listTools !== 'function' || typeof client.callTool !== 'function') {
46
+ throw invalidClient(source);
47
+ }
48
+ }
49
+
50
+ function invalidMcp(): TypeError {
51
+ return new TypeError('MCP module must default-export defineMcp(...)');
52
+ }
53
+
54
+ function invalidClient(source: string): TypeError {
55
+ return new TypeError(`MCP ${source} create() must return a client instance`);
56
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from './definition.js';
2
+ export * from './mcp-index.js';
3
+ export * from './provider.js';
4
+ export { default } from './provider.js';
@@ -0,0 +1,156 @@
1
+ import {
2
+ DisposeStack,
3
+ type CapabilitySlot,
4
+ type PluginId,
5
+ type RuntimeSnapshot,
6
+ } from '@zhin.js/plugin-runtime';
7
+ import {
8
+ OwnerCapabilityIndex,
9
+ createCapabilityContext,
10
+ type OwnerCapabilityEntry,
11
+ } from '@zhin.js/feature-kit';
12
+ import {
13
+ assertMcpClient,
14
+ type McpClientInstance,
15
+ type McpDefinition,
16
+ type McpToolDescriptor,
17
+ } from './definition.js';
18
+
19
+ export interface McpDescriptor {
20
+ readonly owner: PluginId;
21
+ readonly name: string;
22
+ readonly qualifiedName: string;
23
+ readonly description?: string;
24
+ readonly source: string;
25
+ }
26
+
27
+ interface McpRecord {
28
+ readonly entry: OwnerCapabilityEntry<McpDefinition>;
29
+ readonly client: McpClientInstance;
30
+ started: boolean;
31
+ stopped: boolean;
32
+ }
33
+
34
+ export class McpIndex {
35
+ readonly #index: OwnerCapabilityIndex<McpDefinition>;
36
+ readonly #records = new Map<string, McpRecord>();
37
+ readonly #order: readonly McpRecord[];
38
+
39
+ private constructor(
40
+ index: OwnerCapabilityIndex<McpDefinition>,
41
+ records: readonly McpRecord[],
42
+ ) {
43
+ this.#index = index;
44
+ this.#order = Object.freeze([...records]);
45
+ for (const record of records) this.#records.set(record.entry.slot.id, record);
46
+ }
47
+
48
+ static async create(
49
+ slots: readonly Readonly<CapabilitySlot<McpDefinition>>[],
50
+ snapshot: RuntimeSnapshot,
51
+ ): Promise<McpIndex> {
52
+ const index = new OwnerCapabilityIndex(slots, snapshot);
53
+ const records: McpRecord[] = [];
54
+ try {
55
+ for (const entry of index.entries()) {
56
+ const client = await entry.slot.definition.create(
57
+ createCapabilityContext(snapshot, entry.owner),
58
+ );
59
+ assertMcpClient(client, entry.source);
60
+ records.push({ entry, client, started: false, stopped: false });
61
+ }
62
+ return new McpIndex(index, records);
63
+ } catch (error) {
64
+ await stopRecords(records, error);
65
+ throw error;
66
+ }
67
+ }
68
+
69
+ list(): readonly McpDescriptor[] {
70
+ return this.#index.entries().map(toDescriptor);
71
+ }
72
+
73
+ visible(requester: PluginId): readonly McpDescriptor[] {
74
+ return this.#index.visible(requester).map(toDescriptor);
75
+ }
76
+
77
+ async start(): Promise<void> {
78
+ const started: McpRecord[] = [];
79
+ try {
80
+ for (const record of this.#order) {
81
+ if (record.started || record.stopped) continue;
82
+ started.push(record);
83
+ await record.client.start?.();
84
+ record.started = true;
85
+ }
86
+ } catch (error) {
87
+ await stopRecords(started, error);
88
+ throw error;
89
+ }
90
+ }
91
+
92
+ async stop(): Promise<void> {
93
+ await stopRecords(this.#order);
94
+ }
95
+
96
+ async listTools(requester: PluginId, name: string): Promise<readonly McpToolDescriptor[]> {
97
+ const record = this.#resolve(requester, name);
98
+ return Object.freeze((await record.client.listTools()).map((tool) => {
99
+ if (!tool.name.trim()) throw new TypeError(`MCP ${name} returned a Tool without a name`);
100
+ return Object.freeze({ ...tool });
101
+ }));
102
+ }
103
+
104
+ async callTool<TResult = unknown>(
105
+ requester: PluginId,
106
+ connection: string,
107
+ tool: string,
108
+ input: unknown,
109
+ ): Promise<TResult> {
110
+ const record = this.#resolve(requester, connection);
111
+ return record.client.callTool(tool, input) as TResult | Promise<TResult>;
112
+ }
113
+
114
+ #resolve(requester: PluginId, name: string): McpRecord {
115
+ const entry = this.#index.resolve(requester, name);
116
+ if (!entry) throw new Error(`Unknown MCP connection ${name} for ${requester}`);
117
+ const record = this.#records.get(entry.slot.id);
118
+ if (!record || !record.started || record.stopped) {
119
+ throw new Error(`MCP connection is not active: ${entry.qualifiedName}`);
120
+ }
121
+ return record;
122
+ }
123
+ }
124
+
125
+ function toDescriptor(entry: OwnerCapabilityEntry<McpDefinition>): McpDescriptor {
126
+ return Object.freeze({
127
+ owner: entry.owner,
128
+ name: entry.name,
129
+ qualifiedName: entry.qualifiedName,
130
+ description: entry.slot.definition.description,
131
+ source: entry.source,
132
+ });
133
+ }
134
+
135
+ async function stopRecords(records: readonly McpRecord[], primary?: unknown): Promise<void> {
136
+ const stack = new DisposeStack();
137
+ for (const record of records) {
138
+ if (record.stopped) continue;
139
+ stack.add(async () => {
140
+ record.stopped = true;
141
+ await record.client.stop?.();
142
+ });
143
+ }
144
+ try {
145
+ await stack.dispose();
146
+ } catch (stopError) {
147
+ if (primary !== undefined) {
148
+ throw new AggregateError(
149
+ [primary, stopError],
150
+ 'MCP prepare and client cleanup both failed',
151
+ { cause: stopError },
152
+ );
153
+ }
154
+ throw stopError;
155
+ }
156
+ }
@@ -0,0 +1,35 @@
1
+ import { featureId } from '@zhin.js/plugin-runtime';
2
+ import { defineFeatureProvider, typeScriptModules } from '@zhin.js/feature-kit';
3
+ import { parseMcpDefinition } from './definition.js';
4
+ import { McpIndex } from './mcp-index.js';
5
+
6
+ export const mcpFeatureId = featureId('zhin.mcp');
7
+
8
+ const mcpFeature = defineFeatureProvider({
9
+ protocol: 1,
10
+ id: mcpFeatureId,
11
+ authoring: {
12
+ conventions: [typeScriptModules({
13
+ id: 'mcp-ts',
14
+ directory: 'mcp',
15
+ recursive: false,
16
+ })],
17
+ validate: parseMcpDefinition,
18
+ },
19
+ runtime: {
20
+ async project(slots, context) {
21
+ const index = await McpIndex.create(slots, context.snapshot);
22
+ return {
23
+ value: index,
24
+ dispose: () => index.stop(),
25
+ handoff: {
26
+ activateNext: () => index.start(),
27
+ deactivateNext: () => index.stop(),
28
+ },
29
+ };
30
+ },
31
+ },
32
+ });
33
+
34
+ export { mcpFeature };
35
+ export default mcpFeature;