@actiondock/mcp 2.7.0 → 2.9.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/README.md +13 -3
- package/dist/adapter.d.ts +10 -8
- package/dist/adapter.js +163 -140
- package/dist/http.js +51 -119
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/register-tasks-extension.d.ts +28 -3
- package/dist/register-tasks-extension.js +75 -8
- package/dist/stdio-host.js +4 -4
- package/dist/stdio.js +21 -11
- package/dist/types.d.ts +23 -44
- package/dist/types.js +1 -33
- package/package.json +3 -11
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# @actiondock/mcp
|
|
2
2
|
|
|
3
|
-
ActionDock 2.
|
|
3
|
+
ActionDock 2.x 模型上下文协议适配器。
|
|
4
4
|
|
|
5
5
|
[](https://nodejs.org/)
|
|
6
6
|
[](https://www.typescriptlang.org/)
|
|
7
7
|
[](https://modelcontextprotocol.io/)
|
|
8
8
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
9
9
|
|
|
10
|
-
`@actiondock/mcp` 将 ActionDock 中的原子 Action 映射为标准的模型上下文协议工具,无缝接入各类主流 AI
|
|
10
|
+
`@actiondock/mcp` 将 ActionDock 中的原子 Action 映射为标准的模型上下文协议工具,无缝接入各类主流 AI 智能体与开发环境。在 ActionDock 2.x 中,MCP 适配层全面对接 `@actiondock/core` 导出的标准服务端口体系(`DiscoveryPort`、`ExecutionPort` 与 `RunsPort`),彻底消除对底层私有实体的直接穿透。
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
@@ -28,7 +28,7 @@ ActionDock 2.0 模型上下文协议适配器。
|
|
|
28
28
|
### 协同取消信号向下传播
|
|
29
29
|
|
|
30
30
|
- 当 MCP 客户端发起取消请求时,适配层自动捕获中断事件。
|
|
31
|
-
-
|
|
31
|
+
- 取消信号通过服务端口直接传递至底层的执行服务,并联动激活当前任务上下文中的 `ctx.signal`。
|
|
32
32
|
- 业务代码可通过监听 AbortSignal 安全释放资源或提前终止执行。
|
|
33
33
|
|
|
34
34
|
### Tasks 异步任务映射扩展
|
|
@@ -39,6 +39,14 @@ ActionDock 2.0 模型上下文协议适配器。
|
|
|
39
39
|
- 任务主动取消:通过 `tasks/cancel` 端点向正在后台执行的长周期任务发出中止指令。
|
|
40
40
|
- 任务清单列举:通过 `tasks/list` 端点批量查询当前会话及宿主下的活跃与历史任务列表。
|
|
41
41
|
|
|
42
|
+
### 细粒度权限白名单与安全过滤
|
|
43
|
+
|
|
44
|
+
MCP 适配层原生支持基于包与动作维度的双层白名单控制,通过 `ActionDockMcpOptions` 进行配置:
|
|
45
|
+
|
|
46
|
+
- 包白名单 `packageAllowlist`(别名 `packageIds`):限制仅暴露指定包内的工具。
|
|
47
|
+
- 动作白名单 `actionAllowlist`:限制仅暴露指定的动作,支持动作短名 `actionId` 与全限定名 `packageId/actionId`。
|
|
48
|
+
- 联动过滤机制:同时配置包白名单与动作白名单时自动计算权限交集。未获授权的动作不会出现在 `tools/list` 工具列表中;在 `tasks/get`、`tasks/cancel` 与 `tasks/list` 任务扩展中,涉及未授权动作的任务同样予以严格隔离与隐藏。
|
|
49
|
+
|
|
42
50
|
---
|
|
43
51
|
|
|
44
52
|
## 快速使用
|
|
@@ -60,6 +68,8 @@ import { startMcpStdioServer } from "@actiondock/mcp";
|
|
|
60
68
|
|
|
61
69
|
await startMcpStdioServer({
|
|
62
70
|
projectRoot: process.cwd(),
|
|
71
|
+
packageAllowlist: ["system-tools"],
|
|
72
|
+
actionAllowlist: ["system-tools/status", "ping"],
|
|
63
73
|
});
|
|
64
74
|
```
|
|
65
75
|
|
package/dist/adapter.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { type ActionDockService } from "@actiondock/core";
|
|
2
|
+
import { type ActionDockHost } from "@actiondock/core/server";
|
|
3
|
+
import type { PackageRuntime } from "@actiondock/core/package";
|
|
2
4
|
import type { ExecutionResult } from "@actiondock/sdk";
|
|
3
5
|
import { McpServer } from "@modelcontextprotocol/server";
|
|
4
6
|
import type { ActionDockMcpOptions } from "./types.js";
|
|
@@ -29,23 +31,23 @@ export declare function toMcpResult(result: ExecutionResult): {
|
|
|
29
31
|
structuredContent?: undefined;
|
|
30
32
|
};
|
|
31
33
|
/**
|
|
32
|
-
* 解析或基于选项创建底层
|
|
34
|
+
* 解析或基于选项创建底层 ActionDockService 统一门面。
|
|
33
35
|
*/
|
|
34
|
-
export declare function
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
export declare function resolveService(options: ActionDockMcpOptions): Promise<{
|
|
37
|
+
service: ActionDockService;
|
|
38
|
+
ownsService: boolean;
|
|
37
39
|
}>;
|
|
38
40
|
export type ActionDockMcpServer = McpServer & {
|
|
39
41
|
close: () => Promise<void>;
|
|
40
|
-
|
|
42
|
+
service: ActionDockService;
|
|
41
43
|
host?: ActionDockHost;
|
|
42
|
-
|
|
44
|
+
runtime?: PackageRuntime;
|
|
43
45
|
events?: (runId: string, options?: {
|
|
44
46
|
after?: number;
|
|
45
47
|
signal?: AbortSignal;
|
|
46
48
|
}) => AsyncIterable<Record<string, unknown>>;
|
|
47
49
|
};
|
|
48
50
|
/**
|
|
49
|
-
* 创建并配置基于
|
|
51
|
+
* 创建并配置基于 ActionDockService 的 McpServer 适配层实例。
|
|
50
52
|
*/
|
|
51
53
|
export declare function createActionDockMcpServer(options?: ActionDockMcpOptions): Promise<ActionDockMcpServer>;
|
package/dist/adapter.js
CHANGED
|
@@ -1,11 +1,46 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
|
-
import { ACTIONDOCK_VERSION,
|
|
4
|
+
import { ACTIONDOCK_VERSION, createActionDock, createNodePlatform, findProjectRoot, MCP_TOOL_NAME_COLLISION, } from "@actiondock/core";
|
|
5
|
+
import { createActionDockHost, isActionAllowed, LocalActionDockService, } from "@actiondock/core/server";
|
|
6
|
+
import { resolvePackageRoot } from "@actiondock/core/registry";
|
|
7
|
+
import { createNonClosingStorageView } from "@actiondock/core/package";
|
|
8
|
+
import { parseActionRef } from "@actiondock/core/graph";
|
|
5
9
|
import { McpServer } from "@modelcontextprotocol/server";
|
|
6
10
|
import { registerTasksExtension } from "./register-tasks-extension.js";
|
|
7
11
|
import { toMcpSchema } from "./schemas.js";
|
|
8
12
|
import { extractExecutionTimeoutMs, isAsyncExecutionRequested, stripExecutionWrapper, } from "./execution-mode.js";
|
|
13
|
+
/** 形状缺失告警是否已输出过(每个进程仅告警一次,避免逐请求噪声) */
|
|
14
|
+
let cancelSignalDegradationWarned = false;
|
|
15
|
+
/**
|
|
16
|
+
* 从 MCP 工具回调上下文提取请求级取消信号。
|
|
17
|
+
*
|
|
18
|
+
* @param ctx MCP SDK 回调传入的工具执行上下文
|
|
19
|
+
* @returns 可用的 AbortSignal;形状缺失时返回 undefined 并输出一次降级警告
|
|
20
|
+
*/
|
|
21
|
+
function extractCancelSignal(ctx) {
|
|
22
|
+
const signal = ctx?.mcpReq?.signal;
|
|
23
|
+
if (!signal && !cancelSignalDegradationWarned) {
|
|
24
|
+
cancelSignalDegradationWarned = true;
|
|
25
|
+
process.stderr.write("[actiondock-mcp] Tool cancel signal unavailable: MCP SDK callback context does not expose mcpReq.signal; per-request cancellation is degraded for this server instance.\n");
|
|
26
|
+
}
|
|
27
|
+
return signal;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 解析 Action 引用字符串为包标识与动作标识(容错语义)。
|
|
31
|
+
*
|
|
32
|
+
* 委派 core 的 parseActionRef 单一事实源完成解析;非法形态(如尾部斜杠、
|
|
33
|
+
* 含冒号等)回退为整体短名处理,与适配层既有的注册容错行为保持一致,
|
|
34
|
+
* 不让目录聚合阶段的脏数据中断服务启动。
|
|
35
|
+
*/
|
|
36
|
+
function splitActionRef(ref) {
|
|
37
|
+
try {
|
|
38
|
+
return parseActionRef(ref);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return { actionId: ref };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
9
44
|
/**
|
|
10
45
|
* 判断目标值是否为普通对象(Plain Object)。
|
|
11
46
|
*
|
|
@@ -48,28 +83,6 @@ export function toMcpResult(result) {
|
|
|
48
83
|
],
|
|
49
84
|
};
|
|
50
85
|
}
|
|
51
|
-
/** 适配层统一结构化错误码:MCP 工具名冲突。 */
|
|
52
|
-
const MCP_TOOL_NAME_COLLISION = "MCP_TOOL_NAME_COLLISION";
|
|
53
|
-
/**
|
|
54
|
-
* 构造外部注入 storage 的非接管视图。
|
|
55
|
-
*
|
|
56
|
-
* 默认(ownStorageLifecycle 为 false)时外部 storage 生命周期由注入方管理,
|
|
57
|
-
* 适配层仅委托读写而不接管关闭:所有成员函数与属性原样转发到原始实例并绑定原 this,
|
|
58
|
-
* 仅 close 收敛为显式声明的无操作边界,确保 target.close() 级联关闭时不会误伤外部实例。
|
|
59
|
-
*
|
|
60
|
-
* @param storage 外部注入的存储实例
|
|
61
|
-
*/
|
|
62
|
-
function createExternalStorageView(storage) {
|
|
63
|
-
return new Proxy(storage, {
|
|
64
|
-
get(target, prop) {
|
|
65
|
-
if (prop === "close") {
|
|
66
|
-
return () => undefined;
|
|
67
|
-
}
|
|
68
|
-
const value = Reflect.get(target, prop, target);
|
|
69
|
-
return typeof value === "function" ? value.bind(target) : value;
|
|
70
|
-
},
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
86
|
/**
|
|
74
87
|
* 构造携带结构化错误码的工具名冲突异常。
|
|
75
88
|
*
|
|
@@ -81,42 +94,51 @@ function toolNameCollisionError(toolName) {
|
|
|
81
94
|
return err;
|
|
82
95
|
}
|
|
83
96
|
/**
|
|
84
|
-
* 解析或基于选项创建底层
|
|
97
|
+
* 解析或基于选项创建底层 ActionDockService 统一门面。
|
|
85
98
|
*/
|
|
86
|
-
export async function
|
|
87
|
-
if (options.
|
|
88
|
-
return {
|
|
99
|
+
export async function resolveService(options) {
|
|
100
|
+
if (options.service) {
|
|
101
|
+
return { service: options.service, ownsService: false };
|
|
89
102
|
}
|
|
90
103
|
if (options.host) {
|
|
91
|
-
const
|
|
92
|
-
return {
|
|
104
|
+
const service = new LocalActionDockService(options.host);
|
|
105
|
+
return { service, ownsService: false };
|
|
93
106
|
}
|
|
94
|
-
if (options.
|
|
95
|
-
const
|
|
96
|
-
|
|
107
|
+
if (options.runtime) {
|
|
108
|
+
const host = await createActionDockHost({
|
|
109
|
+
autoLoadCurrentProject: false,
|
|
110
|
+
scanLinkedPackages: false,
|
|
111
|
+
});
|
|
112
|
+
host.registerRuntime(options.runtime);
|
|
113
|
+
const service = new LocalActionDockService(host);
|
|
114
|
+
return { service, ownsService: false };
|
|
97
115
|
}
|
|
98
116
|
const packages = [];
|
|
99
|
-
// 外部注入的 storage
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
// 显式声明 ownStorageLifecycle 时透传原始实例,随 target.close() 级联关闭
|
|
117
|
+
// 外部注入的 storage 生命周期默认由注入方管理,适配层仅委托读写不接管关闭
|
|
118
|
+
// (非接管视图复用 core 单一事实源);显式声明 ownStorageLifecycle 时透传
|
|
119
|
+
// 原始实例,随 target.close() 级联关闭
|
|
103
120
|
const appStorage = options.storage
|
|
104
121
|
? options.ownStorageLifecycle
|
|
105
122
|
? options.storage
|
|
106
|
-
:
|
|
123
|
+
: createNonClosingStorageView(options.storage)
|
|
107
124
|
: undefined;
|
|
125
|
+
// 多分支共享的包选项基底单点构造:storage 视图、家目录与配置覆写
|
|
126
|
+
// 在此收敛一次,各 push 分支仅叠加自身特有字段
|
|
127
|
+
const basePkgOpts = {
|
|
128
|
+
...(appStorage ? { storage: appStorage } : {}),
|
|
129
|
+
customHome: options.customHome,
|
|
130
|
+
configOverrides: options.configOverrides,
|
|
131
|
+
};
|
|
108
132
|
if (options.actions) {
|
|
109
133
|
packages.push({
|
|
134
|
+
...basePkgOpts,
|
|
110
135
|
projectConfig: {
|
|
111
136
|
id: options.packageId || "default",
|
|
112
137
|
name: options.packageId || "default",
|
|
113
138
|
version: ACTIONDOCK_VERSION,
|
|
114
139
|
},
|
|
115
140
|
actions: options.actions,
|
|
116
|
-
storage: appStorage,
|
|
117
141
|
inMemory: true,
|
|
118
|
-
customHome: options.customHome,
|
|
119
|
-
configOverrides: options.configOverrides,
|
|
120
142
|
});
|
|
121
143
|
}
|
|
122
144
|
if (options.projectRoots && options.projectRoots.length > 0) {
|
|
@@ -127,24 +149,21 @@ export async function resolveTarget(options) {
|
|
|
127
149
|
throw new Error(`Project root '${root}' is not a valid ActionDock package (actiondock.json not found)`);
|
|
128
150
|
}
|
|
129
151
|
packages.push({
|
|
152
|
+
...basePkgOpts,
|
|
130
153
|
packageRoot: detected,
|
|
131
|
-
storage: appStorage,
|
|
132
|
-
customHome: options.customHome,
|
|
133
|
-
configOverrides: options.configOverrides,
|
|
134
154
|
});
|
|
135
155
|
}
|
|
136
156
|
}
|
|
137
|
-
|
|
138
|
-
|
|
157
|
+
const targetPackageIds = options.packageIds || options.packageAllowlist;
|
|
158
|
+
if (targetPackageIds && targetPackageIds.length > 0) {
|
|
159
|
+
for (const pkgId of targetPackageIds) {
|
|
139
160
|
const root = resolvePackageRoot(pkgId, undefined, options.customHome);
|
|
140
161
|
if (!root || !existsSync(root)) {
|
|
141
162
|
throw new Error(`Package '${pkgId}' not found in registry`);
|
|
142
163
|
}
|
|
143
164
|
packages.push({
|
|
165
|
+
...basePkgOpts,
|
|
144
166
|
packageRoot: root,
|
|
145
|
-
storage: appStorage,
|
|
146
|
-
customHome: options.customHome,
|
|
147
|
-
configOverrides: options.configOverrides,
|
|
148
167
|
});
|
|
149
168
|
}
|
|
150
169
|
}
|
|
@@ -173,32 +192,18 @@ export async function resolveTarget(options) {
|
|
|
173
192
|
throw new Error(`Package '${options.packageId}' not found in registry`);
|
|
174
193
|
}
|
|
175
194
|
packages.push({
|
|
195
|
+
...basePkgOpts,
|
|
176
196
|
packageRoot: root,
|
|
177
|
-
storage: appStorage,
|
|
178
|
-
customHome: options.customHome,
|
|
179
|
-
configOverrides: options.configOverrides,
|
|
180
197
|
dataDir: options.dataDir,
|
|
181
198
|
});
|
|
182
199
|
}
|
|
183
200
|
let platform = options.platform;
|
|
184
201
|
if (!platform && typeof process !== "undefined" && process.versions?.node) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
rootDir: projectRoot,
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
catch (err) {
|
|
194
|
-
// 仅「模块不存在」类错误允许降级回退 core 默认平台;
|
|
195
|
-
// 其余异常(加载后初始化失败等)原样上抛,避免吞没真实故障
|
|
196
|
-
const isModuleMissing = err?.code === "ERR_MODULE_NOT_FOUND" ||
|
|
197
|
-
/Cannot find (?:package|module)/i.test(String(err?.message || ""));
|
|
198
|
-
if (!isModuleMissing) {
|
|
199
|
-
throw err;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
+
platform = createNodePlatform({
|
|
203
|
+
customHome: options.customHome,
|
|
204
|
+
dataDir: options.dataDir,
|
|
205
|
+
rootDir: projectRoot,
|
|
206
|
+
});
|
|
202
207
|
}
|
|
203
208
|
for (const pkg of packages) {
|
|
204
209
|
if (typeof pkg === "object" && pkg !== null && !("info" in pkg)) {
|
|
@@ -210,7 +215,7 @@ export async function resolveTarget(options) {
|
|
|
210
215
|
}
|
|
211
216
|
}
|
|
212
217
|
}
|
|
213
|
-
const
|
|
218
|
+
const service = await createActionDock({
|
|
214
219
|
type: "local",
|
|
215
220
|
projectRoot,
|
|
216
221
|
packages: packages.length > 0 ? packages : undefined,
|
|
@@ -225,24 +230,30 @@ export async function resolveTarget(options) {
|
|
|
225
230
|
dataDir: options.dataDir,
|
|
226
231
|
platform,
|
|
227
232
|
});
|
|
228
|
-
return {
|
|
233
|
+
return { service, ownsService: true };
|
|
229
234
|
}
|
|
230
235
|
/**
|
|
231
|
-
* 创建并配置基于
|
|
236
|
+
* 创建并配置基于 ActionDockService 的 McpServer 适配层实例。
|
|
232
237
|
*/
|
|
233
238
|
export async function createActionDockMcpServer(options = {}) {
|
|
234
|
-
const {
|
|
239
|
+
const { service } = await resolveService(options);
|
|
240
|
+
const allowedPackageIds = options.packageAllowlist && options.packageAllowlist.length > 0
|
|
241
|
+
? options.packageAllowlist
|
|
242
|
+
: options.packageIds && options.packageIds.length > 0
|
|
243
|
+
? options.packageIds
|
|
244
|
+
: options.packageId
|
|
245
|
+
? [options.packageId]
|
|
246
|
+
: undefined;
|
|
235
247
|
let serverName = "actiondock";
|
|
236
248
|
let serverVersion = ACTIONDOCK_VERSION;
|
|
237
249
|
try {
|
|
238
|
-
|
|
239
|
-
if (
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
}
|
|
250
|
+
let packages = await service.info();
|
|
251
|
+
if (allowedPackageIds && allowedPackageIds.length > 0) {
|
|
252
|
+
packages = packages.filter((p) => p.id && allowedPackageIds.includes(p.id));
|
|
253
|
+
}
|
|
254
|
+
if (packages && packages.length === 1) {
|
|
255
|
+
serverName = packages[0].name || packages[0].id || serverName;
|
|
256
|
+
serverVersion = packages[0].version || serverVersion;
|
|
246
257
|
}
|
|
247
258
|
}
|
|
248
259
|
catch {
|
|
@@ -253,26 +264,46 @@ export async function createActionDockMcpServer(options = {}) {
|
|
|
253
264
|
version: serverVersion,
|
|
254
265
|
});
|
|
255
266
|
// 任务规范扩展注册集中隔离在独立模块,本层不再直接操作 SDK 内层实例
|
|
256
|
-
registerTasksExtension(server,
|
|
257
|
-
// 工具注册与模式映射:tools/list 纯粹委托
|
|
258
|
-
|
|
259
|
-
|
|
267
|
+
registerTasksExtension(server, service, allowedPackageIds, options.actionAllowlist);
|
|
268
|
+
// 工具注册与模式映射:tools/list 纯粹委托 service.discovery.listActions()
|
|
269
|
+
let rawActions = await service.discovery.listActions();
|
|
270
|
+
// 引用解析单点归一:每项仅解析一次,后续各轮(白名单过滤、去重、频次统计、
|
|
271
|
+
// 工具命名)复用同一结果,避免同一列表对 splitActionRef 的三轮重复调用;
|
|
272
|
+
// 显式 packageId 与解析所得 packageId 分别留存,各轮按既有优先级消费
|
|
273
|
+
const parsedRefs = new Map();
|
|
260
274
|
for (const act of rawActions) {
|
|
261
|
-
let pkgId = act.packageId || "";
|
|
262
|
-
let actId = act.id;
|
|
263
275
|
if (act.id.includes("/")) {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
}
|
|
276
|
+
const parsed = splitActionRef(act.id);
|
|
277
|
+
parsedRefs.set(act, {
|
|
278
|
+
explicitPkgId: act.packageId || "",
|
|
279
|
+
parsedPkgId: parsed.packageId,
|
|
280
|
+
baseId: parsed.actionId,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
parsedRefs.set(act, { explicitPkgId: act.packageId || "", baseId: act.id });
|
|
274
285
|
}
|
|
275
|
-
|
|
286
|
+
}
|
|
287
|
+
if (allowedPackageIds && allowedPackageIds.length > 0) {
|
|
288
|
+
// 白名单轮:显式 packageId 优先,缺失时回退解析所得
|
|
289
|
+
rawActions = rawActions.filter((act) => {
|
|
290
|
+
const ref = parsedRefs.get(act);
|
|
291
|
+
const pkgId = ref.explicitPkgId || ref.parsedPkgId || "";
|
|
292
|
+
return pkgId ? allowedPackageIds.includes(pkgId) : false;
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
if (options.actionAllowlist && options.actionAllowlist.length > 0) {
|
|
296
|
+
rawActions = rawActions.filter((act) => {
|
|
297
|
+
const ref = parsedRefs.get(act);
|
|
298
|
+
const pkgId = ref.explicitPkgId || ref.parsedPkgId || undefined;
|
|
299
|
+
return isActionAllowed({ packageId: pkgId, actionId: ref.baseId }, options.actionAllowlist);
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
const seenActionKeys = new Map();
|
|
303
|
+
for (const act of rawActions) {
|
|
304
|
+
const ref = parsedRefs.get(act);
|
|
305
|
+
const pkgId = ref.explicitPkgId || ref.parsedPkgId || "";
|
|
306
|
+
const key = `${pkgId}:${ref.baseId}`;
|
|
276
307
|
const existing = seenActionKeys.get(key);
|
|
277
308
|
if (existing) {
|
|
278
309
|
// 若已存在的项是全限定名(含 /),而当前项是短名(不含 /),优先保留短名项
|
|
@@ -288,35 +319,24 @@ export async function createActionDockMcpServer(options = {}) {
|
|
|
288
319
|
// 统计 Action 基础 ID 出现频次,用于同名冲突命名空间隔离
|
|
289
320
|
const baseCounts = new Map();
|
|
290
321
|
for (const act of actions) {
|
|
291
|
-
|
|
292
|
-
if (act.id.includes("/")) {
|
|
293
|
-
try {
|
|
294
|
-
const parsed = ActionResolver.parseRef(act.id);
|
|
295
|
-
baseId = parsed.actionId;
|
|
296
|
-
}
|
|
297
|
-
catch {
|
|
298
|
-
const idx = act.id.lastIndexOf("/");
|
|
299
|
-
baseId = act.id.slice(idx + 1);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
322
|
+
const baseId = parsedRefs.get(act).baseId;
|
|
302
323
|
baseCounts.set(baseId, (baseCounts.get(baseId) || 0) + 1);
|
|
303
324
|
}
|
|
304
325
|
const registeredToolNames = new Set();
|
|
326
|
+
// 多包判定(循环外一次算清):不同 packageId 去重计数大于 1,
|
|
327
|
+
// 或任一 id 含斜杠(跨包限定名形态),则工具描述需附全限定 id 锚点;
|
|
328
|
+
// 本轮与工具命名轮一致:解析所得 packageId 优先,缺失时回退显式声明
|
|
329
|
+
const distinctPackageIds = new Set(actions
|
|
330
|
+
.map((a) => {
|
|
331
|
+
const ref = parsedRefs.get(a);
|
|
332
|
+
return ref.parsedPkgId || a.packageId;
|
|
333
|
+
})
|
|
334
|
+
.filter((pkg) => Boolean(pkg)));
|
|
335
|
+
const isMultiPackage = distinctPackageIds.size > 1 || actions.some((a) => a.id.includes("/"));
|
|
305
336
|
for (const action of actions) {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
try {
|
|
310
|
-
const parsed = ActionResolver.parseRef(action.id);
|
|
311
|
-
packageId = parsed.packageId || packageId;
|
|
312
|
-
baseId = parsed.actionId;
|
|
313
|
-
}
|
|
314
|
-
catch {
|
|
315
|
-
const idx = action.id.lastIndexOf("/");
|
|
316
|
-
packageId = packageId || action.id.slice(0, idx);
|
|
317
|
-
baseId = action.id.slice(idx + 1);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
337
|
+
const ref = parsedRefs.get(action);
|
|
338
|
+
const baseId = ref.baseId;
|
|
339
|
+
const packageId = ref.parsedPkgId || action.packageId;
|
|
320
340
|
const count = baseCounts.get(baseId) || 1;
|
|
321
341
|
let toolName = baseId;
|
|
322
342
|
if (count > 1 && packageId) {
|
|
@@ -335,11 +355,10 @@ export async function createActionDockMcpServer(options = {}) {
|
|
|
335
355
|
throw toolNameCollisionError(toolName);
|
|
336
356
|
}
|
|
337
357
|
registeredToolNames.add(toolName);
|
|
338
|
-
const isMultiPackage = actions.some((a) => a.id.includes("/") || (a.packageId && a.packageId !== actions[0].packageId));
|
|
339
358
|
const description = isMultiPackage
|
|
340
359
|
? `[${action.id}] ${action.description || ""}`.trim()
|
|
341
360
|
: action.description;
|
|
342
|
-
// 工具执行:tools/call 委托
|
|
361
|
+
// 工具执行:tools/call 委托 service.execution.run() 或 service.execution.start()
|
|
343
362
|
server.registerTool(toolName, {
|
|
344
363
|
description,
|
|
345
364
|
inputSchema: toMcpSchema(action.inputSchema),
|
|
@@ -350,7 +369,7 @@ export async function createActionDockMcpServer(options = {}) {
|
|
|
350
369
|
}, async (input, ctx) => {
|
|
351
370
|
// 异步执行模式只认显式约定字段 execution.mode,旧版 __async 仅作只读兼容探测
|
|
352
371
|
const isAsync = isAsyncExecutionRequested(input);
|
|
353
|
-
const signal = ctx
|
|
372
|
+
const signal = extractCancelSignal(ctx);
|
|
354
373
|
// 分发前仅剥离适配层注入的包装字段,业务自有字段(含名为 async 的入参)原样透传
|
|
355
374
|
const cleanInput = stripExecutionWrapper(input);
|
|
356
375
|
// 超时组合策略:客户端声明与服务端配置同时存在时取较小值(更防御),
|
|
@@ -360,7 +379,7 @@ export async function createActionDockMcpServer(options = {}) {
|
|
|
360
379
|
? Math.min(options.timeoutMs, clientTimeoutMs)
|
|
361
380
|
: clientTimeoutMs ?? options.timeoutMs;
|
|
362
381
|
if (isAsync) {
|
|
363
|
-
const ticket = await
|
|
382
|
+
const ticket = await service.execution.start(action.id, cleanInput, {
|
|
364
383
|
signal,
|
|
365
384
|
timeoutMs: effectiveTimeoutMs,
|
|
366
385
|
});
|
|
@@ -378,7 +397,7 @@ export async function createActionDockMcpServer(options = {}) {
|
|
|
378
397
|
],
|
|
379
398
|
};
|
|
380
399
|
}
|
|
381
|
-
const result = await
|
|
400
|
+
const result = await service.execution.run(action.id, cleanInput, {
|
|
382
401
|
signal,
|
|
383
402
|
timeoutMs: effectiveTimeoutMs,
|
|
384
403
|
});
|
|
@@ -388,14 +407,17 @@ export async function createActionDockMcpServer(options = {}) {
|
|
|
388
407
|
// 任务规范映射(tasks/get、tasks/cancel、tasks/list)已收敛至 registerTasksExtension 隔离模块
|
|
389
408
|
// 资源与规程映射:规程映射为只读 MCP Resource 与 Prompt
|
|
390
409
|
try {
|
|
391
|
-
|
|
410
|
+
let playbooks = await service.discovery.listPlaybooks();
|
|
411
|
+
if (allowedPackageIds && allowedPackageIds.length > 0) {
|
|
412
|
+
playbooks = playbooks.filter((pb) => pb.packageId && allowedPackageIds.includes(pb.packageId));
|
|
413
|
+
}
|
|
392
414
|
for (const pb of playbooks) {
|
|
393
415
|
server.registerResource(pb.id, `playbook://${pb.id}`, {
|
|
394
416
|
title: pb.id,
|
|
395
417
|
description: pb.description,
|
|
396
418
|
mimeType: "text/markdown",
|
|
397
419
|
}, async (uri) => {
|
|
398
|
-
const spec = await
|
|
420
|
+
const spec = await service.discovery.describePlaybook(pb.id);
|
|
399
421
|
return {
|
|
400
422
|
contents: [
|
|
401
423
|
{
|
|
@@ -410,7 +432,7 @@ export async function createActionDockMcpServer(options = {}) {
|
|
|
410
432
|
title: pb.id,
|
|
411
433
|
description: pb.description,
|
|
412
434
|
}, async () => {
|
|
413
|
-
const spec = await
|
|
435
|
+
const spec = await service.discovery.describePlaybook(pb.id);
|
|
414
436
|
return {
|
|
415
437
|
messages: [
|
|
416
438
|
{
|
|
@@ -429,16 +451,16 @@ export async function createActionDockMcpServer(options = {}) {
|
|
|
429
451
|
// 规程获取或注册失败不中断适配器启动,但输出诊断行保留排障线索
|
|
430
452
|
console.error(`[actiondock-mcp] Failed to register playbook resources/prompts: ${err?.message || String(err)}`);
|
|
431
453
|
}
|
|
432
|
-
// 服务生命周期:默认 close 仅关闭 MCP 服务本身,不级联
|
|
433
|
-
// SDK 传输层(HTTP 每请求 / stdio 探测回落)会销毁工厂产物,若 close 级联会误杀共享
|
|
434
|
-
// 需要「一次 close 同时释放
|
|
454
|
+
// 服务生命周期:默认 close 仅关闭 MCP 服务本身,不级联 service——
|
|
455
|
+
// SDK 传输层(HTTP 每请求 / stdio 探测回落)会销毁工厂产物,若 close 级联会误杀共享 service;
|
|
456
|
+
// 需要「一次 close 同时释放 service」的独立持有方显式传 cascadeServiceClose
|
|
435
457
|
const originalClose = server.close.bind(server);
|
|
436
458
|
let isClosed = false;
|
|
437
459
|
const closeFn = async () => {
|
|
438
460
|
if (isClosed)
|
|
439
461
|
return;
|
|
440
462
|
isClosed = true;
|
|
441
|
-
// 清理异常不吞没:先关服务再级联目标,server 关闭失败也继续释放
|
|
463
|
+
// 清理异常不吞没:先关服务再级联目标,server 关闭失败也继续释放 service 并聚合上抛
|
|
442
464
|
let closeError;
|
|
443
465
|
try {
|
|
444
466
|
await originalClose();
|
|
@@ -446,13 +468,13 @@ export async function createActionDockMcpServer(options = {}) {
|
|
|
446
468
|
catch (err) {
|
|
447
469
|
closeError = err;
|
|
448
470
|
}
|
|
449
|
-
if (options.
|
|
471
|
+
if (options.cascadeServiceClose) {
|
|
450
472
|
try {
|
|
451
|
-
await
|
|
473
|
+
await service.close();
|
|
452
474
|
}
|
|
453
475
|
catch (err) {
|
|
454
476
|
if (closeError !== undefined) {
|
|
455
|
-
throw new AggregateError([closeError, err], "Failed to close MCP server and
|
|
477
|
+
throw new AggregateError([closeError, err], "Failed to close MCP server and service");
|
|
456
478
|
}
|
|
457
479
|
throw err;
|
|
458
480
|
}
|
|
@@ -463,9 +485,10 @@ export async function createActionDockMcpServer(options = {}) {
|
|
|
463
485
|
};
|
|
464
486
|
const decorated = server;
|
|
465
487
|
decorated.close = closeFn;
|
|
466
|
-
decorated.
|
|
488
|
+
decorated.service = service;
|
|
467
489
|
decorated.host = options.host;
|
|
468
|
-
|
|
469
|
-
|
|
490
|
+
if (service.events) {
|
|
491
|
+
decorated.events = (runId, opts) => service.events.events(runId, opts);
|
|
492
|
+
}
|
|
470
493
|
return decorated;
|
|
471
494
|
}
|
package/dist/http.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { ACTIONDOCK_VERSION,
|
|
1
|
+
import { ACTIONDOCK_VERSION, UNAUTHORIZED } from "@actiondock/core";
|
|
2
|
+
import { createMcpEndpointHandler, formatHostForUrl, isLoopbackHost, launchHttpServer, resolveCorsHeaders, verifyBearerToken, } from "@actiondock/core/server";
|
|
2
3
|
import { createMcpHandler } from "@modelcontextprotocol/server";
|
|
3
|
-
import { createActionDockMcpServer,
|
|
4
|
+
import { createActionDockMcpServer, resolveService } from "./adapter.js";
|
|
4
5
|
/**
|
|
5
6
|
* Starts an ActionDock MCP server over HTTP transport.
|
|
6
7
|
*/
|
|
@@ -16,23 +17,52 @@ export function startMcpHttpServer(options = {}) {
|
|
|
16
17
|
// 由 allowInsecureNoAuth 与 token 策略约束,传入该字段不会产生任何效果,
|
|
17
18
|
// 检测到时输出警告避免调用方误以为已生效
|
|
18
19
|
if (options.allowInsecureHttp === true) {
|
|
19
|
-
process.stderr.write("[MCP HTTP Warning] allowInsecureHttp has no effect on the server side and is deprecated; it only applies to remote
|
|
20
|
+
process.stderr.write("[MCP HTTP Warning] allowInsecureHttp has no effect on the server side and is deprecated; it only applies to remote ActionDock clients.\n");
|
|
20
21
|
}
|
|
21
22
|
// Non-loopback address requires token authentication by default
|
|
22
23
|
if (!isLoopbackHost(host) && !token && !options.allowInsecureNoAuth) {
|
|
23
24
|
throw new Error("Authentication token is required when binding to a non-loopback address. Use --allow-insecure-no-auth to override.");
|
|
24
25
|
}
|
|
25
26
|
return (async () => {
|
|
26
|
-
// 先一次性解析
|
|
27
|
-
const {
|
|
27
|
+
// 先一次性解析 service,工厂闭包直接捕获已解析产物,消除选项重复展开
|
|
28
|
+
const { service } = await resolveService({
|
|
28
29
|
...options,
|
|
29
30
|
host: hostInstance ?? (typeof options.host === "object" ? options.host : undefined),
|
|
30
31
|
});
|
|
31
|
-
const
|
|
32
|
+
const resolvedHost = hostInstance ?? (typeof options.host === "object" ? options.host : undefined);
|
|
33
|
+
const handler = createMcpHandler(() => createActionDockMcpServer({ ...options, host: resolvedHost, service }), {
|
|
32
34
|
onerror: (err) => {
|
|
33
35
|
process.stderr.write(`[MCP HTTP Error] ${err?.message || String(err)}\n`);
|
|
34
36
|
},
|
|
35
37
|
});
|
|
38
|
+
// JSON-RPC 形态的 401 响应构造:mcp 客户端按 JSON-RPC 错误信封消费鉴权失败
|
|
39
|
+
// (错误码 -32000),区别于 core 网关的标准 JSON 信封
|
|
40
|
+
const unauthorizedJsonRpcResponse = (corsHeaders) => new Response(JSON.stringify({
|
|
41
|
+
jsonrpc: "2.0",
|
|
42
|
+
error: {
|
|
43
|
+
code: -32000,
|
|
44
|
+
message: "Unauthorized: Invalid or missing Bearer token",
|
|
45
|
+
},
|
|
46
|
+
id: null,
|
|
47
|
+
}), {
|
|
48
|
+
status: 401,
|
|
49
|
+
headers: {
|
|
50
|
+
"Content-Type": "application/json",
|
|
51
|
+
...corsHeaders,
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
// MCP 端点共享处理器:Bearer 鉴权(401 JSON-RPC 形态)→ 请求体有界读取
|
|
55
|
+
// (413 拦截)→ 重建 Request 委托 SDK 处理器 → CORS merge 回写。
|
|
56
|
+
// 序列收敛为 core 单一事实源,此处仅以定制钩子表达 mcp 独立服务的差异
|
|
57
|
+
const mcpEndpoint = createMcpEndpointHandler((req) => handler.fetch(req), {
|
|
58
|
+
token,
|
|
59
|
+
allowQueryToken: options.allowQueryToken,
|
|
60
|
+
corsOrigins: options.corsOrigins,
|
|
61
|
+
maxBodyBytes: options.maxBodyBytes,
|
|
62
|
+
// mcp 独立服务将解析出的 CORS 头合并覆盖到 MCP 响应(core 网关为 inherit 透传)
|
|
63
|
+
corsApplyMode: "merge",
|
|
64
|
+
unauthorizedResponse: unauthorizedJsonRpcResponse,
|
|
65
|
+
});
|
|
36
66
|
const server = await launchHttpServer(port, host, async (req) => {
|
|
37
67
|
const origin = req.headers.get("origin");
|
|
38
68
|
const corsHeaders = resolveCorsHeaders(origin, options.corsOrigins);
|
|
@@ -45,8 +75,8 @@ export function startMcpHttpServer(options = {}) {
|
|
|
45
75
|
const url = new URL(req.url);
|
|
46
76
|
const pathname = url.pathname;
|
|
47
77
|
const verifyOptions = { allowQueryToken: options.allowQueryToken };
|
|
48
|
-
//
|
|
49
|
-
if (pathname === "/health"
|
|
78
|
+
// 健康检查路由
|
|
79
|
+
if (pathname === "/health") {
|
|
50
80
|
if (!verifyBearerToken(req, token, verifyOptions)) {
|
|
51
81
|
return new Response(JSON.stringify({
|
|
52
82
|
ok: false,
|
|
@@ -75,111 +105,13 @@ export function startMcpHttpServer(options = {}) {
|
|
|
75
105
|
},
|
|
76
106
|
});
|
|
77
107
|
}
|
|
78
|
-
//
|
|
79
|
-
if (!verifyBearerToken(req, token, verifyOptions)) {
|
|
80
|
-
return new Response(JSON.stringify({
|
|
81
|
-
jsonrpc: "2.0",
|
|
82
|
-
error: {
|
|
83
|
-
code: -32000,
|
|
84
|
-
message: "Unauthorized: Invalid or missing Bearer token",
|
|
85
|
-
},
|
|
86
|
-
id: null,
|
|
87
|
-
}), {
|
|
88
|
-
status: 401,
|
|
89
|
-
headers: {
|
|
90
|
-
"Content-Type": "application/json",
|
|
91
|
-
...corsHeaders,
|
|
92
|
-
},
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
// 3. Delegate MCP endpoint
|
|
108
|
+
// 委托 MCP 端点(/mcp 与 / 双路径,鉴权与限流由共享端点处理器承担)
|
|
96
109
|
if (pathname === "/mcp" || pathname === "/") {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
return new Response(JSON.stringify({
|
|
103
|
-
ok: false,
|
|
104
|
-
error: {
|
|
105
|
-
code: "REQUEST_TOO_LARGE",
|
|
106
|
-
message: "Request body exceeds maximum allowed size",
|
|
107
|
-
},
|
|
108
|
-
}), {
|
|
109
|
-
status: 413,
|
|
110
|
-
headers: {
|
|
111
|
-
"Content-Type": "application/json",
|
|
112
|
-
...corsHeaders,
|
|
113
|
-
},
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
let currentReq = req;
|
|
118
|
-
if (req.body && req.method !== "GET" && req.method !== "HEAD") {
|
|
119
|
-
const reader = req.body.getReader();
|
|
120
|
-
const chunks = [];
|
|
121
|
-
let totalBytes = 0;
|
|
122
|
-
let tooLarge = false;
|
|
123
|
-
try {
|
|
124
|
-
while (true) {
|
|
125
|
-
const { done, value } = await reader.read();
|
|
126
|
-
if (done)
|
|
127
|
-
break;
|
|
128
|
-
if (value) {
|
|
129
|
-
totalBytes += value.byteLength;
|
|
130
|
-
if (totalBytes > maxBytes) {
|
|
131
|
-
tooLarge = true;
|
|
132
|
-
await reader.cancel();
|
|
133
|
-
break;
|
|
134
|
-
}
|
|
135
|
-
chunks.push(value);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
finally {
|
|
140
|
-
reader.releaseLock();
|
|
141
|
-
}
|
|
142
|
-
if (tooLarge) {
|
|
143
|
-
return new Response(JSON.stringify({
|
|
144
|
-
ok: false,
|
|
145
|
-
error: {
|
|
146
|
-
code: "REQUEST_TOO_LARGE",
|
|
147
|
-
message: "Request body exceeds maximum allowed size",
|
|
148
|
-
},
|
|
149
|
-
}), {
|
|
150
|
-
status: 413,
|
|
151
|
-
headers: {
|
|
152
|
-
"Content-Type": "application/json",
|
|
153
|
-
...corsHeaders,
|
|
154
|
-
},
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
const merged = new Uint8Array(totalBytes);
|
|
158
|
-
let offset = 0;
|
|
159
|
-
for (const chunk of chunks) {
|
|
160
|
-
merged.set(chunk, offset);
|
|
161
|
-
offset += chunk.byteLength;
|
|
162
|
-
}
|
|
163
|
-
currentReq = new Request(req.url, {
|
|
164
|
-
method: req.method,
|
|
165
|
-
headers: req.headers,
|
|
166
|
-
body: merged,
|
|
167
|
-
signal: req.signal,
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
const mcpResponse = await handler.fetch(currentReq);
|
|
171
|
-
if (Object.keys(corsHeaders).length > 0) {
|
|
172
|
-
const newHeaders = new Headers(mcpResponse.headers);
|
|
173
|
-
for (const [k, v] of Object.entries(corsHeaders)) {
|
|
174
|
-
newHeaders.set(k, String(v));
|
|
175
|
-
}
|
|
176
|
-
return new Response(mcpResponse.body, {
|
|
177
|
-
status: mcpResponse.status,
|
|
178
|
-
statusText: mcpResponse.statusText,
|
|
179
|
-
headers: newHeaders,
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
return mcpResponse;
|
|
110
|
+
return (await mcpEndpoint(req));
|
|
111
|
+
}
|
|
112
|
+
// 其余路径统一鉴权拦截:未认证访问任意未知路径返回 401 而非 404
|
|
113
|
+
if (!verifyBearerToken(req, token, verifyOptions)) {
|
|
114
|
+
return unauthorizedJsonRpcResponse(corsHeaders);
|
|
183
115
|
}
|
|
184
116
|
return new Response(JSON.stringify({
|
|
185
117
|
error: "Not Found",
|
|
@@ -201,10 +133,10 @@ export function startMcpHttpServer(options = {}) {
|
|
|
201
133
|
port: server.port ?? port,
|
|
202
134
|
host,
|
|
203
135
|
url,
|
|
204
|
-
|
|
136
|
+
service,
|
|
205
137
|
stop: async () => {
|
|
206
|
-
// 先停 HTTP 服务(等待在途请求收尾)再释放
|
|
207
|
-
// 若先关
|
|
138
|
+
// 先停 HTTP 服务(等待在途请求收尾)再释放 service:
|
|
139
|
+
// 若先关 service,在途请求的后续 Action 调用会全部异常
|
|
208
140
|
let serverError;
|
|
209
141
|
try {
|
|
210
142
|
await server.stop(true);
|
|
@@ -213,13 +145,13 @@ export function startMcpHttpServer(options = {}) {
|
|
|
213
145
|
serverError = err;
|
|
214
146
|
}
|
|
215
147
|
try {
|
|
216
|
-
await
|
|
148
|
+
await service.close();
|
|
217
149
|
}
|
|
218
|
-
catch (
|
|
150
|
+
catch (serviceErr) {
|
|
219
151
|
if (serverError) {
|
|
220
|
-
throw new AggregateError([serverError,
|
|
152
|
+
throw new AggregateError([serverError, serviceErr], "Failed to stop MCP HTTP server and service");
|
|
221
153
|
}
|
|
222
|
-
throw
|
|
154
|
+
throw serviceErr;
|
|
223
155
|
}
|
|
224
156
|
if (serverError) {
|
|
225
157
|
throw serverError;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from "./types.js";
|
|
2
2
|
export * from "./schemas.js";
|
|
3
3
|
export * from "./execution-mode.js";
|
|
4
|
+
export { toMcpTaskStatus, toMcpTaskPayload, type McpTaskStatus, type McpTaskPayload } from "./register-tasks-extension.js";
|
|
4
5
|
export * from "./adapter.js";
|
|
5
6
|
export * from "./stdio.js";
|
|
6
7
|
export * from "./http.js";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export * from "./types.js";
|
|
2
2
|
export * from "./schemas.js";
|
|
3
3
|
export * from "./execution-mode.js";
|
|
4
|
+
// 任务规范纯函数迁入 register-tasks-extension 后,从此处转引保持包根导出面不变
|
|
5
|
+
export { toMcpTaskStatus, toMcpTaskPayload } from "./register-tasks-extension.js";
|
|
4
6
|
export * from "./adapter.js";
|
|
5
7
|
export * from "./stdio.js";
|
|
6
8
|
export * from "./http.js";
|
|
@@ -1,9 +1,34 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ActionDockService } from "@actiondock/core";
|
|
2
|
+
import type { RunRecord, RunStatus } from "@actiondock/sdk";
|
|
2
3
|
import { McpServer } from "@modelcontextprotocol/server";
|
|
4
|
+
/**
|
|
5
|
+
* MCP 任务状态枚举(兼容 Model Context Protocol Task 规范)。
|
|
6
|
+
*/
|
|
7
|
+
export type McpTaskStatus = "working" | "completed" | "failed" | "cancelled";
|
|
8
|
+
/**
|
|
9
|
+
* MCP 任务状态数据载荷结构体。
|
|
10
|
+
*/
|
|
11
|
+
export interface McpTaskPayload {
|
|
12
|
+
taskId: string;
|
|
13
|
+
status: McpTaskStatus;
|
|
14
|
+
createdAt: string;
|
|
15
|
+
finishedAt?: string;
|
|
16
|
+
input?: unknown;
|
|
17
|
+
output?: unknown;
|
|
18
|
+
error?: unknown;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 将 ActionDock 内部的 RunStatus 转换为 MCP 标准的 TaskStatus。
|
|
22
|
+
*/
|
|
23
|
+
export declare function toMcpTaskStatus(status: RunStatus): McpTaskStatus;
|
|
24
|
+
/**
|
|
25
|
+
* 将内部 RunRecord 实体转换为向 MCP 客户端暴露的 McpTaskPayload。
|
|
26
|
+
*/
|
|
27
|
+
export declare function toMcpTaskPayload(run: RunRecord): McpTaskPayload;
|
|
3
28
|
/**
|
|
4
29
|
* 在低层 Server 实例上注册 tasks 能力声明与三个请求处理器。
|
|
5
30
|
*
|
|
6
31
|
* @param server MCP 高层服务实例
|
|
7
|
-
* @param
|
|
32
|
+
* @param service ActionDockService 公共服务实例
|
|
8
33
|
*/
|
|
9
|
-
export declare function registerTasksExtension(server: McpServer,
|
|
34
|
+
export declare function registerTasksExtension(server: McpServer, service: ActionDockService, allowedPackageIds?: string[], actionAllowlist?: string[]): void;
|
|
@@ -1,5 +1,38 @@
|
|
|
1
|
+
import { isActionAllowed } from "@actiondock/core/server";
|
|
1
2
|
import { fromJsonSchema } from "@modelcontextprotocol/server";
|
|
2
|
-
|
|
3
|
+
/**
|
|
4
|
+
* 将 ActionDock 内部的 RunStatus 转换为 MCP 标准的 TaskStatus。
|
|
5
|
+
*/
|
|
6
|
+
export function toMcpTaskStatus(status) {
|
|
7
|
+
switch (status) {
|
|
8
|
+
case "running":
|
|
9
|
+
return "working";
|
|
10
|
+
case "success":
|
|
11
|
+
return "completed";
|
|
12
|
+
case "failed":
|
|
13
|
+
case "timed_out":
|
|
14
|
+
case "interrupted":
|
|
15
|
+
return "failed";
|
|
16
|
+
case "cancelled":
|
|
17
|
+
return "cancelled";
|
|
18
|
+
default:
|
|
19
|
+
return "failed";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 将内部 RunRecord 实体转换为向 MCP 客户端暴露的 McpTaskPayload。
|
|
24
|
+
*/
|
|
25
|
+
export function toMcpTaskPayload(run) {
|
|
26
|
+
return {
|
|
27
|
+
taskId: run.id,
|
|
28
|
+
status: toMcpTaskStatus(run.status),
|
|
29
|
+
createdAt: run.startedAt,
|
|
30
|
+
finishedAt: run.finishedAt,
|
|
31
|
+
input: run.input,
|
|
32
|
+
output: run.output,
|
|
33
|
+
error: run.error,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
3
36
|
/** JSON-RPC 保留码段:协议保留区为 -32000 至 -32099,服务端自定义错误码必须落在该区段内。 */
|
|
4
37
|
const TASK_NOT_FOUND_CODE = -32001;
|
|
5
38
|
/** 无效的 limit 参数错误码,同样落在服务端自定义码段。 */
|
|
@@ -53,9 +86,9 @@ function toTimeMillis(value) {
|
|
|
53
86
|
* 在低层 Server 实例上注册 tasks 能力声明与三个请求处理器。
|
|
54
87
|
*
|
|
55
88
|
* @param server MCP 高层服务实例
|
|
56
|
-
* @param
|
|
89
|
+
* @param service ActionDockService 公共服务实例
|
|
57
90
|
*/
|
|
58
|
-
export function registerTasksExtension(server,
|
|
91
|
+
export function registerTasksExtension(server, service, allowedPackageIds, actionAllowlist) {
|
|
59
92
|
server.server.registerCapabilities({
|
|
60
93
|
tasks: {
|
|
61
94
|
list: {},
|
|
@@ -63,23 +96,51 @@ export function registerTasksExtension(server, target) {
|
|
|
63
96
|
},
|
|
64
97
|
});
|
|
65
98
|
server.server.setRequestHandler("tasks/get", { params: TASK_ID_PARAMS }, async (params) => {
|
|
66
|
-
const run = await
|
|
99
|
+
const run = await service.runs.get(params.taskId);
|
|
67
100
|
if (!run) {
|
|
68
101
|
// 携带语义码透传,避免被 SDK 映射为内部错误
|
|
69
102
|
throw taskNotFoundError(params.taskId);
|
|
70
103
|
}
|
|
104
|
+
if (allowedPackageIds &&
|
|
105
|
+
allowedPackageIds.length > 0 &&
|
|
106
|
+
(!run.packageId || !allowedPackageIds.includes(run.packageId))) {
|
|
107
|
+
throw taskNotFoundError(params.taskId);
|
|
108
|
+
}
|
|
109
|
+
if (actionAllowlist &&
|
|
110
|
+
actionAllowlist.length > 0 &&
|
|
111
|
+
!isActionAllowed({ packageId: run.packageId, actionId: run.actionId }, actionAllowlist)) {
|
|
112
|
+
throw taskNotFoundError(params.taskId);
|
|
113
|
+
}
|
|
71
114
|
return { task: toMcpTaskPayload(run) };
|
|
72
115
|
});
|
|
73
116
|
server.server.setRequestHandler("tasks/cancel", { params: TASK_ID_PARAMS }, async (params) => {
|
|
117
|
+
const hasPackageFilter = Boolean(allowedPackageIds && allowedPackageIds.length > 0);
|
|
118
|
+
const hasActionFilter = Boolean(actionAllowlist && actionAllowlist.length > 0);
|
|
119
|
+
if (hasPackageFilter || hasActionFilter) {
|
|
120
|
+
const existingRun = await service.runs.get(params.taskId);
|
|
121
|
+
if (!existingRun) {
|
|
122
|
+
throw taskNotFoundError(params.taskId);
|
|
123
|
+
}
|
|
124
|
+
if (hasPackageFilter) {
|
|
125
|
+
if (!existingRun.packageId || !allowedPackageIds.includes(existingRun.packageId)) {
|
|
126
|
+
throw taskNotFoundError(params.taskId);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (hasActionFilter) {
|
|
130
|
+
if (!isActionAllowed({ packageId: existingRun.packageId, actionId: existingRun.actionId }, actionAllowlist)) {
|
|
131
|
+
throw taskNotFoundError(params.taskId);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
74
135
|
const reason = params.reason || "Cancelled via MCP tasks/cancel";
|
|
75
|
-
const cancelRes = await
|
|
136
|
+
const cancelRes = await service.runs.cancel(params.taskId, reason);
|
|
76
137
|
if (cancelRes.outcome === "requested") {
|
|
77
138
|
return { taskId: params.taskId, status: "cancelled" };
|
|
78
139
|
}
|
|
79
140
|
if (cancelRes.outcome === "already_terminal") {
|
|
80
141
|
return { taskId: params.taskId, status: toMcpTaskStatus(cancelRes.status) };
|
|
81
142
|
}
|
|
82
|
-
const run = await
|
|
143
|
+
const run = await service.runs.get(params.taskId);
|
|
83
144
|
if (!run) {
|
|
84
145
|
// 携带语义码透传,避免被 SDK 映射为内部错误
|
|
85
146
|
throw taskNotFoundError(params.taskId);
|
|
@@ -96,8 +157,14 @@ export function registerTasksExtension(server, target) {
|
|
|
96
157
|
limit = Math.min(Math.max(params.limit, 1), MAX_TASK_LIST_LIMIT);
|
|
97
158
|
}
|
|
98
159
|
const actionId = params.actionId;
|
|
99
|
-
// limit 单处截断:下推到
|
|
100
|
-
|
|
160
|
+
// limit 单处截断:下推到 service.runs.list 的 limit 参数,此处不再重复 slice
|
|
161
|
+
let runs = await service.runs.list({ limit, actionId });
|
|
162
|
+
if (allowedPackageIds && allowedPackageIds.length > 0) {
|
|
163
|
+
runs = runs.filter((r) => Boolean(r.packageId && allowedPackageIds.includes(r.packageId)));
|
|
164
|
+
}
|
|
165
|
+
if (actionAllowlist && actionAllowlist.length > 0) {
|
|
166
|
+
runs = runs.filter((r) => isActionAllowed({ packageId: r.packageId, actionId: r.actionId }, actionAllowlist));
|
|
167
|
+
}
|
|
101
168
|
const ordered = [...runs].sort((a, b) => toTimeMillis(b.startedAt) - toTimeMillis(a.startedAt));
|
|
102
169
|
return { tasks: ordered.map(toMcpTaskPayload) };
|
|
103
170
|
});
|
package/dist/stdio-host.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { serveParentIpc } from "@actiondock/core";
|
|
3
|
-
import {
|
|
2
|
+
import { serveParentIpc } from "@actiondock/core/server";
|
|
3
|
+
import { resolveService } from "./adapter.js";
|
|
4
4
|
try {
|
|
5
5
|
const rawOptions = process.env.ACTIONDOCK_MCP_OPTIONS;
|
|
6
6
|
const options = rawOptions ? JSON.parse(rawOptions) : {};
|
|
7
|
-
const {
|
|
8
|
-
await serveParentIpc(
|
|
7
|
+
const { service } = await resolveService(options);
|
|
8
|
+
await serveParentIpc(service);
|
|
9
9
|
}
|
|
10
10
|
catch (err) {
|
|
11
11
|
process.stderr.write(`[MCP Host Error] ${err?.stack || err?.message || String(err)}\n`);
|
package/dist/stdio.js
CHANGED
|
@@ -2,9 +2,9 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import {
|
|
5
|
+
import { IpcActionDockService } from "@actiondock/core/server";
|
|
6
6
|
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
7
|
-
import { createActionDockMcpServer,
|
|
7
|
+
import { createActionDockMcpServer, resolveService } from "./adapter.js";
|
|
8
8
|
/**
|
|
9
9
|
* 启动 ActionDock MCP STDIO 协议服务。
|
|
10
10
|
* 建立监督进程物理边界:
|
|
@@ -15,11 +15,17 @@ import { createActionDockMcpServer, resolveTarget } from "./adapter.js";
|
|
|
15
15
|
*/
|
|
16
16
|
export async function startMcpStdio(options = {}) {
|
|
17
17
|
let activeServer;
|
|
18
|
-
let
|
|
18
|
+
let serviceToUse;
|
|
19
|
+
let ownsService = false;
|
|
19
20
|
let childProcess;
|
|
20
|
-
// 若显式传入了已构造的
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
// 若显式传入了已构造的 service 或不可序列化的内存对象,直接复用或解析目标实例;
|
|
22
|
+
// ownsService 标记与 adapter 的所有权契约对齐:仅自建实例由本入口的 cleanup 收敛生命周期
|
|
23
|
+
if (options.service || options.actions || options.storage || options.runtime || options.host || options.platform) {
|
|
24
|
+
const resolved = options.service
|
|
25
|
+
? { service: options.service, ownsService: false }
|
|
26
|
+
: await resolveService(options);
|
|
27
|
+
serviceToUse = resolved.service;
|
|
28
|
+
ownsService = resolved.ownsService;
|
|
23
29
|
}
|
|
24
30
|
else {
|
|
25
31
|
// 建立隔离的监督子进程
|
|
@@ -44,20 +50,22 @@ export async function startMcpStdio(options = {}) {
|
|
|
44
50
|
stdio: ["pipe", "pipe", "pipe", "ipc"],
|
|
45
51
|
});
|
|
46
52
|
// 标准输出物理隔离与受控限流排空转入 stderr 诊断流
|
|
47
|
-
|
|
53
|
+
serviceToUse = new IpcActionDockService({
|
|
48
54
|
childProcess,
|
|
49
55
|
maxDiagnosticBytes: 512 * 1024,
|
|
50
56
|
maxDiagnosticRate: 64 * 1024,
|
|
51
57
|
diagnosticTarget: process.stderr,
|
|
52
58
|
});
|
|
59
|
+
// IPC 代理实例由本入口创建,生命周期归本入口的 cleanup 所有
|
|
60
|
+
ownsService = true;
|
|
53
61
|
}
|
|
54
62
|
const stdioHandler = serveStdio(async () => {
|
|
55
63
|
const server = await createActionDockMcpServer({
|
|
56
64
|
...options,
|
|
57
|
-
|
|
65
|
+
service: serviceToUse,
|
|
58
66
|
});
|
|
59
67
|
activeServer = server;
|
|
60
|
-
|
|
68
|
+
serviceToUse = serviceToUse || server.service;
|
|
61
69
|
return server;
|
|
62
70
|
}, {
|
|
63
71
|
onerror: (err) => {
|
|
@@ -80,9 +88,11 @@ export async function startMcpStdio(options = {}) {
|
|
|
80
88
|
swallow(err);
|
|
81
89
|
}
|
|
82
90
|
}
|
|
83
|
-
if (
|
|
91
|
+
if (serviceToUse && ownsService) {
|
|
92
|
+
// 外部注入的 service(ownsService 为 false)生命周期归调用方,这里不越权关闭;
|
|
93
|
+
// 自建实例(含 IPC 子进程代理)由本入口统一收敛
|
|
84
94
|
try {
|
|
85
|
-
await
|
|
95
|
+
await serviceToUse.close();
|
|
86
96
|
}
|
|
87
97
|
catch (err) {
|
|
88
98
|
swallow(err);
|
package/dist/types.d.ts
CHANGED
|
@@ -1,40 +1,18 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
*/
|
|
6
|
-
export type McpTaskStatus = "working" | "completed" | "failed" | "cancelled";
|
|
7
|
-
/**
|
|
8
|
-
* MCP 任务状态数据载荷结构体。
|
|
9
|
-
*/
|
|
10
|
-
export interface McpTaskPayload {
|
|
11
|
-
taskId: string;
|
|
12
|
-
status: McpTaskStatus;
|
|
13
|
-
createdAt: string;
|
|
14
|
-
finishedAt?: string;
|
|
15
|
-
input?: unknown;
|
|
16
|
-
output?: unknown;
|
|
17
|
-
error?: unknown;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* 将 ActionDock 内部的 RunStatus 转换为 MCP 标准的 TaskStatus。
|
|
21
|
-
*/
|
|
22
|
-
export declare function toMcpTaskStatus(status: RunStatus): McpTaskStatus;
|
|
23
|
-
/**
|
|
24
|
-
* 将内部 RunRecord 实体转换为向 MCP 客户端暴露的 McpTaskPayload。
|
|
25
|
-
*/
|
|
26
|
-
export declare function toMcpTaskPayload(run: RunRecord): McpTaskPayload;
|
|
1
|
+
import type { ActionDockService } from "@actiondock/core";
|
|
2
|
+
import type { ActionDockHost, ServerTlsOptions } from "@actiondock/core/server";
|
|
3
|
+
import type { PackageRuntime, RuntimePlatform, RuntimeStorage } from "@actiondock/core/package";
|
|
4
|
+
import type { ActionDefinition } from "@actiondock/sdk";
|
|
27
5
|
/**
|
|
28
6
|
* ActionDock MCP 适配层初始化选项。
|
|
29
|
-
* 统一以
|
|
7
|
+
* 统一以 service(及可选 host, runtime)为核心。
|
|
30
8
|
*/
|
|
31
9
|
export interface ActionDockMcpOptions {
|
|
32
|
-
/** 目标
|
|
33
|
-
|
|
10
|
+
/** 目标 ActionDockService 标准服务端口实例 */
|
|
11
|
+
service?: ActionDockService;
|
|
34
12
|
/** 目标 ActionDockHost 宿主实例 */
|
|
35
13
|
host?: ActionDockHost;
|
|
36
|
-
/** 目标
|
|
37
|
-
|
|
14
|
+
/** 目标 PackageRuntime 应用运行时实例 */
|
|
15
|
+
runtime?: PackageRuntime;
|
|
38
16
|
/** 单个目标项目根目录 */
|
|
39
17
|
projectRoot?: string;
|
|
40
18
|
/** 多个项目根目录(用于多包聚合提供) */
|
|
@@ -43,6 +21,10 @@ export interface ActionDockMcpOptions {
|
|
|
43
21
|
packageId?: string;
|
|
44
22
|
/** 多个 Package ID 列表 */
|
|
45
23
|
packageIds?: string[];
|
|
24
|
+
/** 允许访问执行的 Package ID 白名单列表(为空允许全部) */
|
|
25
|
+
packageAllowlist?: string[];
|
|
26
|
+
/** 允许暴露执行的 Action 标识白名单列表(为空允许全部,支持短名或 packageId/actionId) */
|
|
27
|
+
actionAllowlist?: string[];
|
|
46
28
|
/** 是否聚合暴露全局 Registry 中的所有 Package */
|
|
47
29
|
all?: boolean;
|
|
48
30
|
/** 自定义家目录路径 */
|
|
@@ -50,7 +32,7 @@ export interface ActionDockMcpOptions {
|
|
|
50
32
|
/** 自定义全局数据存储目录 */
|
|
51
33
|
dataDir?: string;
|
|
52
34
|
/** 运行时底层平台适配(如提供 NodeProcessDriver 的平台实例) */
|
|
53
|
-
platform?:
|
|
35
|
+
platform?: RuntimePlatform;
|
|
54
36
|
/** 配置动态覆盖项 */
|
|
55
37
|
configOverrides?: Record<string, unknown>;
|
|
56
38
|
/** 单个 Tool 执行超时时间(毫秒) */
|
|
@@ -66,18 +48,20 @@ export interface ActionDockMcpOptions {
|
|
|
66
48
|
storage?: RuntimeStorage;
|
|
67
49
|
/**
|
|
68
50
|
* 是否由适配层接管外部注入 storage 的生命周期。
|
|
69
|
-
* 默认 false:外部注入的 storage
|
|
70
|
-
* 置为 true 时透传原始实例,随
|
|
51
|
+
* 默认 false:外部注入的 storage 由注入方自行管理,适配层不具备 close 语义;
|
|
52
|
+
* 置为 true 时透传原始实例,随 service.close() 级联关闭。
|
|
71
53
|
*/
|
|
72
54
|
ownStorageLifecycle?: boolean;
|
|
73
55
|
/**
|
|
74
|
-
* 实例 close 是否级联关闭
|
|
56
|
+
* 实例 close 是否级联关闭 service 门面。
|
|
75
57
|
* 默认 false:适配层产物可能被 SDK 传输层按请求(或按连接)创建与销毁,
|
|
76
|
-
* 级联语义会误杀共享
|
|
77
|
-
* 仅在调用方自行持有实例并需要一次 close 同时释放
|
|
58
|
+
* 级联语义会误杀共享 service,生命周期统一由外层入口的 stop/cleanup 收敛;
|
|
59
|
+
* 仅在调用方自行持有实例并需要一次 close 同时释放 service 时置 true。
|
|
78
60
|
*/
|
|
79
|
-
|
|
61
|
+
cascadeServiceClose?: boolean;
|
|
80
62
|
}
|
|
63
|
+
/** ActionDock MCP 适配层初始化选项别名 */
|
|
64
|
+
export type ActionDockMcpServerOptions = ActionDockMcpOptions;
|
|
81
65
|
/**
|
|
82
66
|
* HTTP 传输协议安全配置项。
|
|
83
67
|
*/
|
|
@@ -87,11 +71,6 @@ export interface HttpSecurityOptions {
|
|
|
87
71
|
token?: string;
|
|
88
72
|
allowInsecureNoAuth?: boolean;
|
|
89
73
|
allowQueryToken?: boolean;
|
|
90
|
-
/**
|
|
91
|
-
* 已废弃:该选项仅适用于远程 ActionDockTarget 客户端侧的明文 HTTP 豁免,
|
|
92
|
-
* 服务端监听安全性由 allowInsecureNoAuth 与 token 策略约束,传入不会产生任何效果;
|
|
93
|
-
* 服务端检测到该字段时仅输出警告日志
|
|
94
|
-
*/
|
|
95
74
|
allowInsecureHttp?: boolean;
|
|
96
75
|
corsOrigins?: string[];
|
|
97
76
|
maxBodyBytes?: number;
|
|
@@ -105,6 +84,6 @@ export interface ActionDockMcpHttpServerInstance {
|
|
|
105
84
|
port: number;
|
|
106
85
|
host: string;
|
|
107
86
|
url: string;
|
|
108
|
-
|
|
87
|
+
service: ActionDockService;
|
|
109
88
|
stop: () => Promise<void>;
|
|
110
89
|
}
|
package/dist/types.js
CHANGED
|
@@ -1,33 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* 将 ActionDock 内部的 RunStatus 转换为 MCP 标准的 TaskStatus。
|
|
3
|
-
*/
|
|
4
|
-
export function toMcpTaskStatus(status) {
|
|
5
|
-
switch (status) {
|
|
6
|
-
case "running":
|
|
7
|
-
return "working";
|
|
8
|
-
case "success":
|
|
9
|
-
return "completed";
|
|
10
|
-
case "failed":
|
|
11
|
-
case "timed_out":
|
|
12
|
-
case "interrupted":
|
|
13
|
-
return "failed";
|
|
14
|
-
case "cancelled":
|
|
15
|
-
return "cancelled";
|
|
16
|
-
default:
|
|
17
|
-
return "failed";
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* 将内部 RunRecord 实体转换为向 MCP 客户端暴露的 McpTaskPayload。
|
|
22
|
-
*/
|
|
23
|
-
export function toMcpTaskPayload(run) {
|
|
24
|
-
return {
|
|
25
|
-
taskId: run.id,
|
|
26
|
-
status: toMcpTaskStatus(run.status),
|
|
27
|
-
createdAt: run.startedAt,
|
|
28
|
-
finishedAt: run.finishedAt,
|
|
29
|
-
input: run.input,
|
|
30
|
-
output: run.output,
|
|
31
|
-
error: run.error,
|
|
32
|
-
};
|
|
33
|
-
}
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@actiondock/mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"description": "ActionDock MCP Adapter for exposing Actions as Model Context Protocol (MCP) Tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -29,18 +29,10 @@
|
|
|
29
29
|
"test": "node ../../scripts/run-tests.ts"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@actiondock/core": "^2.
|
|
33
|
-
"@actiondock/sdk": "^2.
|
|
32
|
+
"@actiondock/core": "^2.9.0",
|
|
33
|
+
"@actiondock/sdk": "^2.9.0",
|
|
34
34
|
"@modelcontextprotocol/server": "^2.0.0"
|
|
35
35
|
},
|
|
36
|
-
"peerDependencies": {
|
|
37
|
-
"@actiondock/runtime-node": "^2.7.0"
|
|
38
|
-
},
|
|
39
|
-
"peerDependenciesMeta": {
|
|
40
|
-
"@actiondock/runtime-node": {
|
|
41
|
-
"optional": true
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
36
|
"devDependencies": {
|
|
45
37
|
"typescript": "^5.7.0"
|
|
46
38
|
},
|