@actiondock/mcp 2.8.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 +10 -0
- package/dist/adapter.js +9 -2
- package/dist/register-tasks-extension.d.ts +1 -1
- package/dist/register-tasks-extension.js +24 -5
- package/dist/types.d.ts +4 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -39,6 +39,14 @@ ActionDock 2.x 模型上下文协议适配器。
|
|
|
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.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
import { ACTIONDOCK_VERSION, createActionDock, createNodePlatform, findProjectRoot, MCP_TOOL_NAME_COLLISION, } from "@actiondock/core";
|
|
5
|
-
import { createActionDockHost, LocalActionDockService, } from "@actiondock/core/server";
|
|
5
|
+
import { createActionDockHost, isActionAllowed, LocalActionDockService, } from "@actiondock/core/server";
|
|
6
6
|
import { resolvePackageRoot } from "@actiondock/core/registry";
|
|
7
7
|
import { createNonClosingStorageView } from "@actiondock/core/package";
|
|
8
8
|
import { parseActionRef } from "@actiondock/core/graph";
|
|
@@ -264,7 +264,7 @@ export async function createActionDockMcpServer(options = {}) {
|
|
|
264
264
|
version: serverVersion,
|
|
265
265
|
});
|
|
266
266
|
// 任务规范扩展注册集中隔离在独立模块,本层不再直接操作 SDK 内层实例
|
|
267
|
-
registerTasksExtension(server, service, allowedPackageIds);
|
|
267
|
+
registerTasksExtension(server, service, allowedPackageIds, options.actionAllowlist);
|
|
268
268
|
// 工具注册与模式映射:tools/list 纯粹委托 service.discovery.listActions()
|
|
269
269
|
let rawActions = await service.discovery.listActions();
|
|
270
270
|
// 引用解析单点归一:每项仅解析一次,后续各轮(白名单过滤、去重、频次统计、
|
|
@@ -292,6 +292,13 @@ export async function createActionDockMcpServer(options = {}) {
|
|
|
292
292
|
return pkgId ? allowedPackageIds.includes(pkgId) : false;
|
|
293
293
|
});
|
|
294
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
|
+
}
|
|
295
302
|
const seenActionKeys = new Map();
|
|
296
303
|
for (const act of rawActions) {
|
|
297
304
|
const ref = parsedRefs.get(act);
|
|
@@ -31,4 +31,4 @@ export declare function toMcpTaskPayload(run: RunRecord): McpTaskPayload;
|
|
|
31
31
|
* @param server MCP 高层服务实例
|
|
32
32
|
* @param service ActionDockService 公共服务实例
|
|
33
33
|
*/
|
|
34
|
-
export declare function registerTasksExtension(server: McpServer, service: ActionDockService, allowedPackageIds?: string[]): void;
|
|
34
|
+
export declare function registerTasksExtension(server: McpServer, service: ActionDockService, allowedPackageIds?: string[], actionAllowlist?: string[]): void;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isActionAllowed } from "@actiondock/core/server";
|
|
1
2
|
import { fromJsonSchema } from "@modelcontextprotocol/server";
|
|
2
3
|
/**
|
|
3
4
|
* 将 ActionDock 内部的 RunStatus 转换为 MCP 标准的 TaskStatus。
|
|
@@ -87,7 +88,7 @@ function toTimeMillis(value) {
|
|
|
87
88
|
* @param server MCP 高层服务实例
|
|
88
89
|
* @param service ActionDockService 公共服务实例
|
|
89
90
|
*/
|
|
90
|
-
export function registerTasksExtension(server, service, allowedPackageIds) {
|
|
91
|
+
export function registerTasksExtension(server, service, allowedPackageIds, actionAllowlist) {
|
|
91
92
|
server.server.registerCapabilities({
|
|
92
93
|
tasks: {
|
|
93
94
|
list: {},
|
|
@@ -105,16 +106,31 @@ export function registerTasksExtension(server, service, allowedPackageIds) {
|
|
|
105
106
|
(!run.packageId || !allowedPackageIds.includes(run.packageId))) {
|
|
106
107
|
throw taskNotFoundError(params.taskId);
|
|
107
108
|
}
|
|
109
|
+
if (actionAllowlist &&
|
|
110
|
+
actionAllowlist.length > 0 &&
|
|
111
|
+
!isActionAllowed({ packageId: run.packageId, actionId: run.actionId }, actionAllowlist)) {
|
|
112
|
+
throw taskNotFoundError(params.taskId);
|
|
113
|
+
}
|
|
108
114
|
return { task: toMcpTaskPayload(run) };
|
|
109
115
|
});
|
|
110
116
|
server.server.setRequestHandler("tasks/cancel", { params: TASK_ID_PARAMS }, async (params) => {
|
|
111
|
-
|
|
117
|
+
const hasPackageFilter = Boolean(allowedPackageIds && allowedPackageIds.length > 0);
|
|
118
|
+
const hasActionFilter = Boolean(actionAllowlist && actionAllowlist.length > 0);
|
|
119
|
+
if (hasPackageFilter || hasActionFilter) {
|
|
112
120
|
const existingRun = await service.runs.get(params.taskId);
|
|
113
|
-
if (!existingRun
|
|
114
|
-
!existingRun.packageId ||
|
|
115
|
-
!allowedPackageIds.includes(existingRun.packageId)) {
|
|
121
|
+
if (!existingRun) {
|
|
116
122
|
throw taskNotFoundError(params.taskId);
|
|
117
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
|
+
}
|
|
118
134
|
}
|
|
119
135
|
const reason = params.reason || "Cancelled via MCP tasks/cancel";
|
|
120
136
|
const cancelRes = await service.runs.cancel(params.taskId, reason);
|
|
@@ -146,6 +162,9 @@ export function registerTasksExtension(server, service, allowedPackageIds) {
|
|
|
146
162
|
if (allowedPackageIds && allowedPackageIds.length > 0) {
|
|
147
163
|
runs = runs.filter((r) => Boolean(r.packageId && allowedPackageIds.includes(r.packageId)));
|
|
148
164
|
}
|
|
165
|
+
if (actionAllowlist && actionAllowlist.length > 0) {
|
|
166
|
+
runs = runs.filter((r) => isActionAllowed({ packageId: r.packageId, actionId: r.actionId }, actionAllowlist));
|
|
167
|
+
}
|
|
149
168
|
const ordered = [...runs].sort((a, b) => toTimeMillis(b.startedAt) - toTimeMillis(a.startedAt));
|
|
150
169
|
return { tasks: ordered.map(toMcpTaskPayload) };
|
|
151
170
|
});
|
package/dist/types.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ export interface ActionDockMcpOptions {
|
|
|
23
23
|
packageIds?: string[];
|
|
24
24
|
/** 允许访问执行的 Package ID 白名单列表(为空允许全部) */
|
|
25
25
|
packageAllowlist?: string[];
|
|
26
|
+
/** 允许暴露执行的 Action 标识白名单列表(为空允许全部,支持短名或 packageId/actionId) */
|
|
27
|
+
actionAllowlist?: string[];
|
|
26
28
|
/** 是否聚合暴露全局 Registry 中的所有 Package */
|
|
27
29
|
all?: boolean;
|
|
28
30
|
/** 自定义家目录路径 */
|
|
@@ -58,6 +60,8 @@ export interface ActionDockMcpOptions {
|
|
|
58
60
|
*/
|
|
59
61
|
cascadeServiceClose?: boolean;
|
|
60
62
|
}
|
|
63
|
+
/** ActionDock MCP 适配层初始化选项别名 */
|
|
64
|
+
export type ActionDockMcpServerOptions = ActionDockMcpOptions;
|
|
61
65
|
/**
|
|
62
66
|
* HTTP 传输协议安全配置项。
|
|
63
67
|
*/
|
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,8 +29,8 @@
|
|
|
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
36
|
"devDependencies": {
|