@actiondock/mcp 2.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/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # @actiondock/mcp
2
+
3
+ Model Context Protocol (MCP) adapter for ActionDock 2.0.
4
+
5
+ [![Bun](https://img.shields.io/badge/Bun-%3E%3D1.2-black?logo=bun)](https://bun.sh/)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue?logo=typescript)](https://www.typescriptlang.org/)
7
+ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
8
+
9
+ > **Runtime requirement**: [Bun](https://bun.sh/) >= 1.2.0 is required.
10
+
11
+ `@actiondock/mcp` connects ActionDock Actions directly to the Model Context Protocol (MCP), exposing Actions as fully-typed MCP Tools over STDIO and HTTP transports.
12
+
13
+ ---
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ bun add @actiondock/mcp
19
+ # or
20
+ npm install @actiondock/mcp
21
+ ```
22
+
23
+ ---
24
+
25
+ ## Features
26
+
27
+ - **STDIO & HTTP Transport**: Run locally via STDIO (for Claude Code, Cursor, Windsurf) or over HTTP with authentication and CORS.
28
+ - **Dynamic Tool Mapping**: Automatically converts Action `inputSchema` / `outputSchema` into standard MCP Tool contracts.
29
+ - **Cancellation Propagation**: Propagates MCP client cancellations directly to `ctx.signal` (`AbortSignal`).
30
+ - **Tasks Extension**: Supports asynchronous background tool calls via MCP Tasks extension (`tasks/get`, `tasks/cancel`, `tasks/list`).
31
+
32
+ ---
33
+
34
+ ## 📖 Documentation
35
+
36
+ - [MCP Integration Guide](https://github.com/team4u/actiondock/blob/main/docs/guides/mcp.md)
37
+ - [Action API Reference](https://github.com/team4u/actiondock/blob/main/docs/reference/action-api.md)
38
+
39
+ ---
40
+
41
+ ## License
42
+
43
+ [Apache-2.0](LICENSE) © team4u
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@actiondock/mcp",
3
+ "version": "2.0.0",
4
+ "description": "ActionDock MCP Adapter for exposing Actions as Model Context Protocol (MCP) Tools",
5
+ "type": "module",
6
+ "main": "./src/index.ts",
7
+ "module": "./src/index.ts",
8
+ "types": "./src/index.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./src/index.ts",
12
+ "types": "./src/index.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "src",
17
+ "README.md"
18
+ ],
19
+ "engines": {
20
+ "bun": ">=1.2.0"
21
+ },
22
+ "publishConfig": {
23
+ "access": "public",
24
+ "registry": "https://registry.npmjs.org/"
25
+ },
26
+ "scripts": {
27
+ "test": "bun test"
28
+ },
29
+ "dependencies": {
30
+ "@actiondock/core": "^2.0.0",
31
+ "@actiondock/sdk": "^2.0.0",
32
+ "@modelcontextprotocol/server": "^2.0.0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/bun": "latest",
36
+ "typescript": "^5.7.0"
37
+ },
38
+ "keywords": ["actiondock", "mcp", "model-context-protocol", "tools", "agent"],
39
+ "author": "team4u",
40
+ "license": "Apache-2.0",
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/team4u/actiondock.git",
44
+ "directory": "packages/mcp"
45
+ },
46
+ "homepage": "https://github.com/team4u/actiondock#readme",
47
+ "bugs": {
48
+ "url": "https://github.com/team4u/actiondock/issues"
49
+ }
50
+ }
package/src/adapter.ts ADDED
@@ -0,0 +1,363 @@
1
+ import { existsSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import {
4
+ ActionRunner,
5
+ createStorage,
6
+ ExecutionManager,
7
+ findProjectRoot,
8
+ listLinkedPackages,
9
+ loadActions,
10
+ loadProjectConfig,
11
+ resolvePackageRoot,
12
+ ServerRuntimeRegistry,
13
+ } from "@actiondock/core";
14
+ import type { ProjectConfig, RuntimeStorage } from "@actiondock/core";
15
+ import type { ActionDefinition, ExecutionResult, RunRecord } from "@actiondock/sdk";
16
+ import { McpServer } from "@modelcontextprotocol/server";
17
+ import { toMcpSchema } from "./schemas";
18
+ import { toMcpTaskPayload, type ActionDockMcpOptions } from "./types";
19
+
20
+ /**
21
+ * 将 ActionDock 标准的 ExecutionResult 信封结构转换为 MCP 协议规范的 Tool Call 返回结果。
22
+ *
23
+ * @param result ExecutionResult 结果对象
24
+ */
25
+ export function toMcpResult(result: ExecutionResult) {
26
+ if (result.ok) {
27
+ return {
28
+ content: [
29
+ {
30
+ type: "text" as const,
31
+ text: JSON.stringify(result),
32
+ },
33
+ ],
34
+ structuredContent: result.data as Record<string, unknown>,
35
+ };
36
+ }
37
+
38
+ return {
39
+ isError: true,
40
+ content: [
41
+ {
42
+ type: "text" as const,
43
+ text: JSON.stringify(result),
44
+ },
45
+ ],
46
+ };
47
+ }
48
+
49
+ interface ResolvedTarget {
50
+ projectRoot: string;
51
+ config: ProjectConfig;
52
+ actions: Map<string, ActionDefinition>;
53
+ storage: RuntimeStorage;
54
+ runner: ActionRunner;
55
+ }
56
+
57
+ function createMcpToolCallback(
58
+ runner: ActionRunner,
59
+ actionId: string,
60
+ executionManager: ExecutionManager,
61
+ timeoutMs?: number
62
+ ) {
63
+ return async (input: any, ctx: any) => {
64
+ const isAsync = Boolean(
65
+ input &&
66
+ typeof input === "object" &&
67
+ (input.execution?.mode === "async" ||
68
+ input.__async === true ||
69
+ input.async === true)
70
+ );
71
+ const signal = ctx.mcpReq?.signal;
72
+
73
+ if (isAsync) {
74
+ const handle = runner.start(actionId, input, {
75
+ signal,
76
+ timeoutMs,
77
+ });
78
+ executionManager.register(handle);
79
+
80
+ return {
81
+ content: [
82
+ {
83
+ type: "text" as const,
84
+ text: JSON.stringify({
85
+ ok: true,
86
+ runId: handle.runId,
87
+ taskId: handle.runId,
88
+ status: "running",
89
+ }),
90
+ },
91
+ ],
92
+ };
93
+ }
94
+
95
+ const handle = runner.start(actionId, input, {
96
+ signal,
97
+ timeoutMs,
98
+ });
99
+ executionManager.register(handle);
100
+ const result = await handle.result;
101
+ return toMcpResult(result);
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Creates and configures an McpServer instance bound to one or more ActionDock packages with Tasks extension support.
107
+ */
108
+ export async function createActionDockMcpServer(
109
+ options: ActionDockMcpOptions = {}
110
+ ): Promise<McpServer> {
111
+ const targetRoots: string[] = [];
112
+ if (options.projectRoot) {
113
+ targetRoots.push(options.projectRoot);
114
+ }
115
+ if (options.projectRoots) {
116
+ targetRoots.push(...options.projectRoots);
117
+ }
118
+
119
+ const targetPackages: string[] = [];
120
+ if (options.packageId) {
121
+ targetPackages.push(options.packageId);
122
+ }
123
+ if (options.packageIds) {
124
+ targetPackages.push(...options.packageIds);
125
+ }
126
+
127
+ const resolvedRoots = new Set<string>();
128
+
129
+ // 1. Handle --all: discover all linked packages from registry
130
+ if (options.all) {
131
+ const linked = listLinkedPackages(options.customHome);
132
+ for (const pkg of linked) {
133
+ if (existsSync(pkg.path)) {
134
+ resolvedRoots.add(resolve(pkg.path));
135
+ }
136
+ }
137
+ const currentRoot = findProjectRoot(process.cwd());
138
+ if (currentRoot) {
139
+ resolvedRoots.add(resolve(currentRoot));
140
+ }
141
+ }
142
+
143
+ // 2. Handle specific package IDs
144
+ for (const pkgId of targetPackages) {
145
+ const root = resolvePackageRoot(pkgId, undefined, options.customHome);
146
+ if (!root || !existsSync(root)) {
147
+ throw new Error(`Package '${pkgId}' not found in registry`);
148
+ }
149
+ resolvedRoots.add(resolve(root));
150
+ }
151
+
152
+ // 3. Handle specific directory paths
153
+ for (const dir of targetRoots) {
154
+ const absPath = resolve(dir);
155
+ const root = findProjectRoot(absPath);
156
+ if (!root) {
157
+ throw new Error(
158
+ `Project root '${dir}' is not a valid ActionDock package (actiondock.json not found)`
159
+ );
160
+ }
161
+ resolvedRoots.add(resolve(root));
162
+ }
163
+
164
+ // 4. Default fallback: current working directory
165
+ if (resolvedRoots.size === 0 && !options.all) {
166
+ const currentRoot = findProjectRoot(process.cwd());
167
+ if (currentRoot) {
168
+ resolvedRoots.add(resolve(currentRoot));
169
+ }
170
+ }
171
+
172
+ const runtimeRegistry = options.runtimeRegistry ?? new ServerRuntimeRegistry();
173
+ const executionManager = options.executionManager ?? runtimeRegistry.executionManager;
174
+ const targets: ResolvedTarget[] = [];
175
+
176
+ // Handle case where options.actions is provided directly (e.g. unit tests or virtual packages)
177
+ if (resolvedRoots.size === 0 && options.actions) {
178
+ const dummyConfig: ProjectConfig = {
179
+ id: "virtual",
180
+ name: "Virtual Package",
181
+ version: "2.0.0",
182
+ description: "In-memory virtual package",
183
+ actionsDir: "actions",
184
+ playbooksDir: "playbooks",
185
+ };
186
+ const storage = options.storage ?? runtimeRegistry.getStorage("virtual");
187
+ const runner = new ActionRunner({
188
+ packageId: dummyConfig.id,
189
+ storage,
190
+ projectConfig: dummyConfig,
191
+ configOverrides: options.configOverrides,
192
+ actions: options.actions,
193
+ });
194
+ targets.push({
195
+ projectRoot: "virtual",
196
+ config: dummyConfig,
197
+ actions: options.actions,
198
+ storage,
199
+ runner,
200
+ });
201
+ } else {
202
+ if (resolvedRoots.size === 0) {
203
+ throw new Error(
204
+ "No ActionDock project root found. Run inside an ActionDock package or specify --dir / --package / --all."
205
+ );
206
+ }
207
+
208
+ for (const root of resolvedRoots) {
209
+ const projectConfig = loadProjectConfig(root);
210
+ const actions =
211
+ options.actions && resolvedRoots.size === 1
212
+ ? options.actions
213
+ : await loadActions(root, projectConfig.actionsDir);
214
+
215
+ const storage =
216
+ options.storage && resolvedRoots.size === 1
217
+ ? options.storage
218
+ : runtimeRegistry.getStorage(projectConfig.id, root);
219
+
220
+ const runner = new ActionRunner({
221
+ packageId: projectConfig.id,
222
+ storage,
223
+ projectConfig,
224
+ configOverrides: options.configOverrides,
225
+ actions,
226
+ });
227
+
228
+ targets.push({
229
+ projectRoot: root,
230
+ config: projectConfig,
231
+ actions,
232
+ storage,
233
+ runner,
234
+ });
235
+ }
236
+ }
237
+
238
+ const isMultiPackage = targets.length > 1;
239
+
240
+ // Check action ID collision across packages
241
+ const actionIdCounts = new Map<string, number>();
242
+ for (const target of targets) {
243
+ for (const actionId of target.actions.keys()) {
244
+ actionIdCounts.set(actionId, (actionIdCounts.get(actionId) || 0) + 1);
245
+ }
246
+ }
247
+
248
+ const serverName =
249
+ targets.length === 1
250
+ ? targets[0].config.id || targets[0].config.name || "actiondock"
251
+ : "actiondock";
252
+ const serverVersion =
253
+ targets.length === 1 ? targets[0].config.version || "2.0.0" : "2.0.0";
254
+
255
+ const server = new McpServer({
256
+ name: serverName,
257
+ version: serverVersion,
258
+ });
259
+
260
+ (server.server as any).registerCapabilities({
261
+ tasks: {
262
+ listChanged: true,
263
+ cancel: {},
264
+ },
265
+ });
266
+
267
+ // 1. Register Action Tools across all targets
268
+ for (const target of targets) {
269
+ for (const action of target.actions.values()) {
270
+ const count = actionIdCounts.get(action.id) || 1;
271
+ const toolName = count > 1 ? `${target.config.id}_${action.id}` : action.id;
272
+ const description = isMultiPackage
273
+ ? `[${target.config.id}] ${action.description || ""}`
274
+ : action.description;
275
+
276
+ server.registerTool(
277
+ toolName,
278
+ {
279
+ description,
280
+ inputSchema: toMcpSchema(action.inputSchema),
281
+ outputSchema: action.outputSchema ? toMcpSchema(action.outputSchema) : undefined,
282
+ },
283
+ createMcpToolCallback(
284
+ target.runner,
285
+ action.id,
286
+ executionManager,
287
+ options.timeoutMs
288
+ )
289
+ );
290
+ }
291
+ }
292
+
293
+ const storages = targets.map((t) => t.storage);
294
+
295
+ // 2. Register Tasks extension endpoints: tasks/get
296
+ (server.server as any).setRequestHandler("tasks/get", async (req: any) => {
297
+ const taskId = req.params?.taskId;
298
+ if (!taskId) {
299
+ throw new Error("taskId parameter is required for tasks/get");
300
+ }
301
+ for (const storage of storages) {
302
+ const run = storage.getRun(taskId);
303
+ if (run) {
304
+ return {
305
+ task: toMcpTaskPayload(run),
306
+ };
307
+ }
308
+ }
309
+ throw new Error(`Task '${taskId}' not found`);
310
+ });
311
+
312
+ // 3. Register Tasks extension endpoints: tasks/cancel
313
+ (server.server as any).setRequestHandler("tasks/cancel", async (req: any) => {
314
+ const taskId = req.params?.taskId;
315
+ if (!taskId) {
316
+ throw new Error("taskId parameter is required for tasks/cancel");
317
+ }
318
+ const activeHandle = executionManager.get(taskId);
319
+ if (activeHandle) {
320
+ executionManager.cancel(taskId, req.params?.reason || "Cancelled via MCP tasks/cancel");
321
+ return {
322
+ taskId,
323
+ status: "cancelled",
324
+ };
325
+ }
326
+ for (const storage of storages) {
327
+ const run = storage.getRun(taskId);
328
+ if (run) {
329
+ if (run.status === "running") {
330
+ storage.updateRun(taskId, "cancelled", undefined, {
331
+ code: "ACTION_CANCELLED",
332
+ message: req.params?.reason || "Cancelled via MCP tasks/cancel",
333
+ });
334
+ }
335
+ return {
336
+ taskId,
337
+ status: "cancelled",
338
+ };
339
+ }
340
+ }
341
+ throw new Error(`Task '${taskId}' not found`);
342
+ });
343
+
344
+ // 4. Register Tasks extension endpoints: tasks/list
345
+ (server.server as any).setRequestHandler("tasks/list", async (req: any) => {
346
+ const limit = typeof req.params?.limit === "number" ? req.params.limit : 50;
347
+ const actionId = req.params?.actionId;
348
+ const allRuns: RunRecord[] = [];
349
+ for (const storage of storages) {
350
+ const runs = storage.listRuns({ limit, actionId });
351
+ allRuns.push(...runs);
352
+ }
353
+ allRuns.sort(
354
+ (a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime()
355
+ );
356
+ const trimmed = allRuns.slice(0, limit);
357
+ return {
358
+ tasks: trimmed.map(toMcpTaskPayload),
359
+ };
360
+ });
361
+
362
+ return server;
363
+ }
package/src/http.ts ADDED
@@ -0,0 +1,166 @@
1
+ import {
2
+ isLoopbackHost,
3
+ resolveCorsHeaders,
4
+ ServerRuntimeRegistry,
5
+ verifyBearerToken,
6
+ } from "@actiondock/core";
7
+ import { createMcpHandler } from "@modelcontextprotocol/server";
8
+ import { createActionDockMcpServer } from "./adapter";
9
+ import type { ActionDockMcpHttpOptions, ActionDockMcpHttpServerInstance } from "./types";
10
+
11
+ /**
12
+ * Starts an ActionDock MCP server over HTTP transport.
13
+ */
14
+ export function startMcpHttpServer(
15
+ options: ActionDockMcpHttpOptions = {}
16
+ ): ActionDockMcpHttpServerInstance {
17
+ const port = options.port ?? 5178;
18
+ const host = options.host ?? "127.0.0.1";
19
+ const token = options.token;
20
+
21
+ // Non-loopback address requires token authentication by default
22
+ if (!isLoopbackHost(host) && !token && !options.allowInsecureNoAuth) {
23
+ throw new Error(
24
+ "Authentication token is required when binding to a non-loopback address. Use --allow-insecure-no-auth to override."
25
+ );
26
+ }
27
+
28
+ const runtimeRegistry = new ServerRuntimeRegistry();
29
+
30
+ const handler = createMcpHandler(
31
+ () => {
32
+ return createActionDockMcpServer({
33
+ ...options,
34
+ runtimeRegistry,
35
+ executionManager: runtimeRegistry.executionManager,
36
+ });
37
+ },
38
+ {
39
+ onerror: (err) => {
40
+ process.stderr.write(`[MCP HTTP Error] ${err?.message || String(err)}\n`);
41
+ },
42
+ }
43
+ );
44
+
45
+
46
+ const server = Bun.serve({
47
+ port,
48
+ hostname: host,
49
+ async fetch(req) {
50
+ const origin = req.headers.get("origin");
51
+ const corsHeaders = resolveCorsHeaders(origin, options.corsOrigins);
52
+
53
+ if (req.method === "OPTIONS") {
54
+ return new Response(null, {
55
+ status: 204,
56
+ headers: corsHeaders,
57
+ });
58
+ }
59
+
60
+ const url = new URL(req.url);
61
+ const pathname = url.pathname;
62
+
63
+ // 1. Health check
64
+ if (pathname === "/health" || pathname === "/api/v1/health") {
65
+ if (!verifyBearerToken(req, token)) {
66
+ return new Response(
67
+ JSON.stringify({
68
+ ok: false,
69
+ error: {
70
+ code: "UNAUTHORIZED",
71
+ message: "Invalid or missing Bearer token",
72
+ },
73
+ }),
74
+ {
75
+ status: 401,
76
+ headers: {
77
+ "Content-Type": "application/json",
78
+ ...corsHeaders,
79
+ },
80
+ }
81
+ );
82
+ }
83
+ return new Response(
84
+ JSON.stringify({
85
+ status: "ok",
86
+ protocol: "mcp",
87
+ version: "2.0.0",
88
+ timestamp: new Date().toISOString(),
89
+ }),
90
+ {
91
+ status: 200,
92
+ headers: {
93
+ "Content-Type": "application/json",
94
+ ...corsHeaders,
95
+ },
96
+ }
97
+ );
98
+ }
99
+
100
+ // 2. Authentication check
101
+ if (!verifyBearerToken(req, token)) {
102
+ return new Response(
103
+ JSON.stringify({
104
+ jsonrpc: "2.0",
105
+ error: {
106
+ code: -32000,
107
+ message: "Unauthorized: Invalid or missing Bearer token",
108
+ },
109
+ id: null,
110
+ }),
111
+ {
112
+ status: 401,
113
+ headers: {
114
+ "Content-Type": "application/json",
115
+ ...corsHeaders,
116
+ },
117
+ }
118
+ );
119
+ }
120
+
121
+ // 3. Delegate MCP endpoint
122
+ if (pathname === "/mcp" || pathname === "/") {
123
+ const mcpResponse = await handler.fetch(req);
124
+ if (Object.keys(corsHeaders).length > 0) {
125
+ const newHeaders = new Headers(mcpResponse.headers);
126
+ for (const [k, v] of Object.entries(corsHeaders)) {
127
+ newHeaders.set(k, String(v));
128
+ }
129
+ return new Response(mcpResponse.body, {
130
+ status: mcpResponse.status,
131
+ statusText: mcpResponse.statusText,
132
+ headers: newHeaders,
133
+ });
134
+ }
135
+ return mcpResponse;
136
+ }
137
+
138
+ return new Response(
139
+ JSON.stringify({
140
+ error: "Not Found",
141
+ }),
142
+ {
143
+ status: 404,
144
+ headers: {
145
+ "Content-Type": "application/json",
146
+ ...corsHeaders,
147
+ },
148
+ }
149
+ );
150
+ },
151
+ });
152
+
153
+ const actualHost = host === "0.0.0.0" ? "127.0.0.1" : host;
154
+ const url = `http://${actualHost}:${server.port}`;
155
+
156
+ return {
157
+ port: server.port ?? port,
158
+ host,
159
+ url,
160
+ stop: () => {
161
+ runtimeRegistry.close();
162
+ server.stop(true);
163
+ },
164
+ };
165
+ }
166
+
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from "./types";
2
+ export * from "./schemas";
3
+ export * from "./adapter";
4
+ export * from "./stdio";
5
+ export * from "./http";
package/src/schemas.ts ADDED
@@ -0,0 +1,19 @@
1
+ import type { JsonSchema } from "@actiondock/sdk";
2
+ import { fromJsonSchema } from "@modelcontextprotocol/server";
3
+
4
+ /**
5
+ * Normalizes an ActionDock JSON Schema into a standard MCP Tool Schema.
6
+ */
7
+ export function toMcpSchema(schema?: JsonSchema) {
8
+ if (schema === undefined || schema === true) {
9
+ return fromJsonSchema({});
10
+ }
11
+
12
+ if (schema === false) {
13
+ return fromJsonSchema({
14
+ not: {},
15
+ });
16
+ }
17
+
18
+ return fromJsonSchema(schema as Record<string, unknown>);
19
+ }
package/src/stdio.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { serveStdio } from "@modelcontextprotocol/server/stdio";
2
+ import { createActionDockMcpServer } from "./adapter";
3
+ import type { ActionDockMcpOptions } from "./types";
4
+
5
+ /**
6
+ * Starts an ActionDock MCP server over STDIO transport.
7
+ */
8
+ export async function startMcpStdio(
9
+ options: ActionDockMcpOptions = {}
10
+ ): Promise<void> {
11
+ serveStdio(() => createActionDockMcpServer(options), {
12
+ onerror: (err) => {
13
+ process.stderr.write(`[MCP Error] ${err?.message || String(err)}\n`);
14
+ },
15
+ });
16
+ }
package/src/types.ts ADDED
@@ -0,0 +1,105 @@
1
+ import type { ExecutionManager, RuntimeStorage, ServerRuntimeRegistry } from "@actiondock/core";
2
+ import type { ActionDefinition, ExecutionResult, RunRecord, RunStatus } from "@actiondock/sdk";
3
+
4
+ /**
5
+ * MCP 任务状态枚举(兼容 Model Context Protocol Task 规范)。
6
+ */
7
+ export type McpTaskStatus = "working" | "completed" | "failed" | "cancelled";
8
+
9
+ /**
10
+ * MCP 任务状态数据载荷结构体。
11
+ */
12
+ export interface McpTaskPayload {
13
+ taskId: string;
14
+ status: McpTaskStatus;
15
+ createdAt: string;
16
+ finishedAt?: string;
17
+ input?: unknown;
18
+ output?: unknown;
19
+ error?: unknown;
20
+ }
21
+
22
+ /**
23
+ * 将 ActionDock 内部的 RunStatus 转换为 MCP 标准的 TaskStatus。
24
+ */
25
+ export function toMcpTaskStatus(status: RunStatus): McpTaskStatus {
26
+ switch (status) {
27
+ case "running":
28
+ return "working";
29
+ case "success":
30
+ return "completed";
31
+ case "failed":
32
+ return "failed";
33
+ case "cancelled":
34
+ return "cancelled";
35
+ default:
36
+ return "working";
37
+ }
38
+ }
39
+
40
+ /**
41
+ * 将内部 RunRecord 实体转换为向 MCP 客户端暴露的 McpTaskPayload。
42
+ */
43
+ export function toMcpTaskPayload(run: RunRecord): McpTaskPayload {
44
+ return {
45
+ taskId: run.id,
46
+ status: toMcpTaskStatus(run.status),
47
+ createdAt: run.startedAt,
48
+ finishedAt: run.finishedAt,
49
+ input: run.input,
50
+ output: run.output,
51
+ error: run.error,
52
+ };
53
+ }
54
+
55
+ /**
56
+ * ActionDock MCP 适配层初始化选项。
57
+ */
58
+ export interface ActionDockMcpOptions {
59
+ /** 单个目标项目根目录 */
60
+ projectRoot?: string;
61
+ /** 多个项目根目录(用于多包聚合提供) */
62
+ projectRoots?: string[];
63
+ /** 目标 Package ID */
64
+ packageId?: string;
65
+ /** 多个 Package ID 列表 */
66
+ packageIds?: string[];
67
+ /** 是否聚合暴露全局 Registry 中的所有 Package */
68
+ all?: boolean;
69
+ /** 自定义家目录路径 */
70
+ customHome?: string;
71
+ /** 配置动态覆盖项 */
72
+ configOverrides?: Record<string, unknown>;
73
+ /** 单个 Tool 执行超时时间(毫秒) */
74
+ timeoutMs?: number;
75
+ /** 预加载的 Action 映射表 */
76
+ actions?: Map<string, ActionDefinition>;
77
+ /** 底层存储实例 */
78
+ storage?: RuntimeStorage;
79
+ /** 服务端运行时注册表 */
80
+ runtimeRegistry?: ServerRuntimeRegistry;
81
+ /** 活跃执行任务管理器 */
82
+ executionManager?: ExecutionManager;
83
+ }
84
+
85
+ /**
86
+ * HTTP 传输协议安全配置项。
87
+ */
88
+ export interface HttpSecurityOptions {
89
+ host?: string;
90
+ port?: number;
91
+ token?: string;
92
+ allowInsecureNoAuth?: boolean;
93
+ corsOrigins?: string[];
94
+ maxBodyBytes?: number;
95
+ }
96
+
97
+ export interface ActionDockMcpHttpOptions extends ActionDockMcpOptions, HttpSecurityOptions {}
98
+
99
+ export interface ActionDockMcpHttpServerInstance {
100
+ port: number;
101
+ host: string;
102
+ url: string;
103
+ stop: () => void;
104
+ }
105
+