@actiondock/mcp 2.0.11 → 2.2.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/dist/http.js ADDED
@@ -0,0 +1,221 @@
1
+ import { ACTIONDOCK_VERSION, DEFAULT_MAX_BODY_BYTES, formatHostForUrl, isLoopbackHost, launchHttpServer, resolveCorsHeaders, verifyBearerToken, UNAUTHORIZED, } from "@actiondock/core";
2
+ import { createMcpHandler } from "@modelcontextprotocol/server";
3
+ import { createActionDockMcpServer, resolveTarget } from "./adapter.js";
4
+ /**
5
+ * Starts an ActionDock MCP server over HTTP transport.
6
+ */
7
+ export function startMcpHttpServer(options = {}) {
8
+ const hostInstance = typeof options.host === "object" && options.host !== null
9
+ ? options.host
10
+ : undefined;
11
+ const hostString = typeof options.host === "string" ? options.host : "127.0.0.1";
12
+ const port = options.port ?? 5178;
13
+ const host = hostString;
14
+ const token = options.token;
15
+ // Non-loopback address requires token authentication by default
16
+ if (!isLoopbackHost(host) && !token && !options.allowInsecureNoAuth) {
17
+ throw new Error("Authentication token is required when binding to a non-loopback address. Use --allow-insecure-no-auth to override.");
18
+ }
19
+ return (async () => {
20
+ // 先一次性解析 target,工厂闭包直接捕获已解析产物,消除选项重复展开
21
+ const { target } = await resolveTarget({
22
+ ...options,
23
+ host: hostInstance ?? (typeof options.host === "object" ? options.host : undefined),
24
+ });
25
+ const handler = createMcpHandler(() => createActionDockMcpServer({ target }), {
26
+ onerror: (err) => {
27
+ process.stderr.write(`[MCP HTTP Error] ${err?.message || String(err)}\n`);
28
+ },
29
+ });
30
+ const server = await launchHttpServer(port, host, async (req) => {
31
+ const origin = req.headers.get("origin");
32
+ const corsHeaders = resolveCorsHeaders(origin, options.corsOrigins);
33
+ if (req.method === "OPTIONS") {
34
+ return new Response(null, {
35
+ status: 204,
36
+ headers: corsHeaders,
37
+ });
38
+ }
39
+ const url = new URL(req.url);
40
+ const pathname = url.pathname;
41
+ const verifyOptions = { allowQueryToken: options.allowQueryToken };
42
+ // 1. Health check
43
+ if (pathname === "/health" || pathname === "/api/v1/health") {
44
+ if (!verifyBearerToken(req, token, verifyOptions)) {
45
+ return new Response(JSON.stringify({
46
+ ok: false,
47
+ error: {
48
+ code: UNAUTHORIZED,
49
+ message: "Invalid or missing Bearer token",
50
+ },
51
+ }), {
52
+ status: 401,
53
+ headers: {
54
+ "Content-Type": "application/json",
55
+ ...corsHeaders,
56
+ },
57
+ });
58
+ }
59
+ return new Response(JSON.stringify({
60
+ status: "ok",
61
+ protocol: "mcp",
62
+ version: ACTIONDOCK_VERSION,
63
+ timestamp: new Date().toISOString(),
64
+ }), {
65
+ status: 200,
66
+ headers: {
67
+ "Content-Type": "application/json",
68
+ ...corsHeaders,
69
+ },
70
+ });
71
+ }
72
+ // 2. Authentication check
73
+ if (!verifyBearerToken(req, token, verifyOptions)) {
74
+ return new Response(JSON.stringify({
75
+ jsonrpc: "2.0",
76
+ error: {
77
+ code: -32000,
78
+ message: "Unauthorized: Invalid or missing Bearer token",
79
+ },
80
+ id: null,
81
+ }), {
82
+ status: 401,
83
+ headers: {
84
+ "Content-Type": "application/json",
85
+ ...corsHeaders,
86
+ },
87
+ });
88
+ }
89
+ // 3. Delegate MCP endpoint
90
+ if (pathname === "/mcp" || pathname === "/") {
91
+ const maxBytes = options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
92
+ const contentLengthHeader = req.headers.get("content-length");
93
+ if (contentLengthHeader) {
94
+ const parsedLength = parseInt(contentLengthHeader, 10);
95
+ if (!isNaN(parsedLength) && parsedLength > maxBytes) {
96
+ return new Response(JSON.stringify({
97
+ ok: false,
98
+ error: {
99
+ code: "REQUEST_TOO_LARGE",
100
+ message: "Request body exceeds maximum allowed size",
101
+ },
102
+ }), {
103
+ status: 413,
104
+ headers: {
105
+ "Content-Type": "application/json",
106
+ ...corsHeaders,
107
+ },
108
+ });
109
+ }
110
+ }
111
+ let currentReq = req;
112
+ if (req.body && req.method !== "GET" && req.method !== "HEAD") {
113
+ const reader = req.body.getReader();
114
+ const chunks = [];
115
+ let totalBytes = 0;
116
+ let tooLarge = false;
117
+ try {
118
+ while (true) {
119
+ const { done, value } = await reader.read();
120
+ if (done)
121
+ break;
122
+ if (value) {
123
+ totalBytes += value.byteLength;
124
+ if (totalBytes > maxBytes) {
125
+ tooLarge = true;
126
+ await reader.cancel();
127
+ break;
128
+ }
129
+ chunks.push(value);
130
+ }
131
+ }
132
+ }
133
+ finally {
134
+ reader.releaseLock();
135
+ }
136
+ if (tooLarge) {
137
+ return new Response(JSON.stringify({
138
+ ok: false,
139
+ error: {
140
+ code: "REQUEST_TOO_LARGE",
141
+ message: "Request body exceeds maximum allowed size",
142
+ },
143
+ }), {
144
+ status: 413,
145
+ headers: {
146
+ "Content-Type": "application/json",
147
+ ...corsHeaders,
148
+ },
149
+ });
150
+ }
151
+ const merged = new Uint8Array(totalBytes);
152
+ let offset = 0;
153
+ for (const chunk of chunks) {
154
+ merged.set(chunk, offset);
155
+ offset += chunk.byteLength;
156
+ }
157
+ currentReq = new Request(req.url, {
158
+ method: req.method,
159
+ headers: req.headers,
160
+ body: merged,
161
+ signal: req.signal,
162
+ });
163
+ }
164
+ const mcpResponse = await handler.fetch(currentReq);
165
+ if (Object.keys(corsHeaders).length > 0) {
166
+ const newHeaders = new Headers(mcpResponse.headers);
167
+ for (const [k, v] of Object.entries(corsHeaders)) {
168
+ newHeaders.set(k, String(v));
169
+ }
170
+ return new Response(mcpResponse.body, {
171
+ status: mcpResponse.status,
172
+ statusText: mcpResponse.statusText,
173
+ headers: newHeaders,
174
+ });
175
+ }
176
+ return mcpResponse;
177
+ }
178
+ return new Response(JSON.stringify({
179
+ error: "Not Found",
180
+ }), {
181
+ status: 404,
182
+ headers: {
183
+ "Content-Type": "application/json",
184
+ ...corsHeaders,
185
+ },
186
+ });
187
+ });
188
+ if (server.ready) {
189
+ await server.ready;
190
+ }
191
+ const actualHost = host === "0.0.0.0" ? "127.0.0.1" : host;
192
+ const url = `http://${formatHostForUrl(actualHost)}:${server.port}`;
193
+ return {
194
+ port: server.port ?? port,
195
+ host,
196
+ url,
197
+ target,
198
+ stop: async () => {
199
+ let targetError;
200
+ try {
201
+ await target.close();
202
+ }
203
+ catch (err) {
204
+ targetError = err;
205
+ }
206
+ try {
207
+ await server.stop(true);
208
+ }
209
+ catch (serverErr) {
210
+ if (targetError) {
211
+ throw new AggregateError([targetError, serverErr], "Failed to stop MCP HTTP server and target");
212
+ }
213
+ throw serverErr;
214
+ }
215
+ if (targetError) {
216
+ throw targetError;
217
+ }
218
+ },
219
+ };
220
+ })();
221
+ }
@@ -0,0 +1,6 @@
1
+ export * from "./types.js";
2
+ export * from "./schemas.js";
3
+ export * from "./execution-mode.js";
4
+ export * from "./adapter.js";
5
+ export * from "./stdio.js";
6
+ export * from "./http.js";
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export * from "./types.js";
2
+ export * from "./schemas.js";
3
+ export * from "./execution-mode.js";
4
+ export * from "./adapter.js";
5
+ export * from "./stdio.js";
6
+ export * from "./http.js";
@@ -0,0 +1,9 @@
1
+ import type { ActionDockTarget } from "@actiondock/core";
2
+ import { McpServer } from "@modelcontextprotocol/server";
3
+ /**
4
+ * 在低层 Server 实例上注册 tasks 能力声明与三个请求处理器。
5
+ *
6
+ * @param server MCP 高层服务实例
7
+ * @param target ActionDockTarget 公共契约门面
8
+ */
9
+ export declare function registerTasksExtension(server: McpServer, target: ActionDockTarget): void;
@@ -0,0 +1,69 @@
1
+ import { fromJsonSchema } from "@modelcontextprotocol/server";
2
+ import { toMcpTaskPayload, toMcpTaskStatus } from "./types.js";
3
+ const TASK_ID_PARAMS = fromJsonSchema({
4
+ type: "object",
5
+ properties: {
6
+ taskId: { type: "string" },
7
+ reason: { type: "string" },
8
+ },
9
+ required: ["taskId"],
10
+ });
11
+ const TASK_LIST_PARAMS = fromJsonSchema({
12
+ type: "object",
13
+ properties: {
14
+ limit: { type: "number" },
15
+ actionId: { type: "string" },
16
+ },
17
+ });
18
+ /**
19
+ * 将内部 RunRecord 的 ISO 时间字符串转为毫秒时间戳,用于任务排序比较。
20
+ *
21
+ * @param value 待解析的时间字符串
22
+ */
23
+ function toTimeMillis(value) {
24
+ const parsed = Date.parse(value);
25
+ return Number.isNaN(parsed) ? 0 : parsed;
26
+ }
27
+ /**
28
+ * 在低层 Server 实例上注册 tasks 能力声明与三个请求处理器。
29
+ *
30
+ * @param server MCP 高层服务实例
31
+ * @param target ActionDockTarget 公共契约门面
32
+ */
33
+ export function registerTasksExtension(server, target) {
34
+ server.server.registerCapabilities({
35
+ tasks: {
36
+ list: {},
37
+ cancel: {},
38
+ },
39
+ });
40
+ server.server.setRequestHandler("tasks/get", { params: TASK_ID_PARAMS }, async (params) => {
41
+ const run = await target.getRun(params.taskId);
42
+ if (!run) {
43
+ throw new Error(`Task '${params.taskId}' not found`);
44
+ }
45
+ return { task: toMcpTaskPayload(run) };
46
+ });
47
+ server.server.setRequestHandler("tasks/cancel", { params: TASK_ID_PARAMS }, async (params) => {
48
+ const reason = params.reason || "Cancelled via MCP tasks/cancel";
49
+ const cancelRes = await target.cancelRun(params.taskId, reason);
50
+ if (cancelRes.outcome === "requested") {
51
+ return { taskId: params.taskId, status: "cancelled" };
52
+ }
53
+ if (cancelRes.outcome === "already_terminal") {
54
+ return { taskId: params.taskId, status: toMcpTaskStatus(cancelRes.status) };
55
+ }
56
+ const run = await target.getRun(params.taskId);
57
+ if (!run) {
58
+ throw new Error(`Task '${params.taskId}' not found`);
59
+ }
60
+ return { taskId: params.taskId, status: toMcpTaskStatus(run.status) };
61
+ });
62
+ server.server.setRequestHandler("tasks/list", { params: TASK_LIST_PARAMS }, async (params) => {
63
+ const limit = typeof params.limit === "number" ? params.limit : 50;
64
+ const actionId = params.actionId;
65
+ const runs = await target.listRuns({ limit, actionId });
66
+ const ordered = [...runs].sort((a, b) => toTimeMillis(b.startedAt) - toTimeMillis(a.startedAt));
67
+ return { tasks: ordered.slice(0, limit).map(toMcpTaskPayload) };
68
+ });
69
+ }
@@ -0,0 +1,6 @@
1
+ import type { JsonSchema } from "@actiondock/sdk";
2
+ /**
3
+ * 将 ActionDock 标准 JSON Schema 归一化为 MCP 标准 Tool Schema。
4
+ * 布尔分支已穷尽(true / false / 对象),无其余可达路径。
5
+ */
6
+ export declare function toMcpSchema(schema?: JsonSchema): import("@modelcontextprotocol/server").StandardSchemaWithJSON<unknown, unknown>;
@@ -0,0 +1,30 @@
1
+ import { fromJsonSchema } from "@modelcontextprotocol/server";
2
+ /**
3
+ * 将 ActionDock 标准 JSON Schema 归一化为 MCP 标准 Tool Schema。
4
+ * 布尔分支已穷尽(true / false / 对象),无其余可达路径。
5
+ */
6
+ export function toMcpSchema(schema) {
7
+ if (schema === undefined || schema === true) {
8
+ return fromJsonSchema({});
9
+ }
10
+ if (schema === false) {
11
+ return fromJsonSchema({
12
+ not: {},
13
+ });
14
+ }
15
+ // JSON Schema 为纯数据结构,structuredClone 深拷贝避免污染调用方原始定义;布尔分支已穷尽,收窄为对象模式
16
+ const cloned = structuredClone(schema);
17
+ const properties = (cloned.properties ?? {});
18
+ if (cloned.type === "object" || cloned.properties) {
19
+ cloned.properties = properties;
20
+ properties.execution = {
21
+ type: "object",
22
+ description: "Execution options",
23
+ properties: {
24
+ mode: { type: "string", enum: ["sync", "async"] },
25
+ timeoutMs: { type: "number" },
26
+ },
27
+ };
28
+ }
29
+ return fromJsonSchema(cloned);
30
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ import { serveParentIpc } from "@actiondock/core";
3
+ import { resolveTarget } from "./adapter.js";
4
+ try {
5
+ const rawOptions = process.env.ACTIONDOCK_MCP_OPTIONS;
6
+ const options = rawOptions ? JSON.parse(rawOptions) : {};
7
+ const { target } = await resolveTarget(options);
8
+ await serveParentIpc(target);
9
+ }
10
+ catch (err) {
11
+ process.stderr.write(`[MCP Host Error] ${err?.stack || err?.message || String(err)}\n`);
12
+ process.exit(1);
13
+ }
@@ -0,0 +1,10 @@
1
+ import type { ActionDockMcpOptions } from "./types.js";
2
+ /**
3
+ * 启动 ActionDock MCP STDIO 协议服务。
4
+ * 建立监督进程物理边界:
5
+ * 1. 监督父进程独占标准输入/输出通道,专用于承载 JSON-RPC 协议;
6
+ * 2. 子进程承载 Action 运行时,通过 Node IPC 通信;
7
+ * 3. 子进程输出被重定向并进行限流排空(转入受控诊断流 stderr),彻底杜绝业务 console.log 破坏 MCP STDIO 流;
8
+ * 4. 子进程异常退出时,转换为结构化 JSON-RPC 错误,保障协议稳定性。
9
+ */
10
+ export declare function startMcpStdio(options?: ActionDockMcpOptions): Promise<void>;
package/dist/stdio.js ADDED
@@ -0,0 +1,114 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { IpcActionDockTarget } from "@actiondock/core";
6
+ import { serveStdio } from "@modelcontextprotocol/server/stdio";
7
+ import { createActionDockMcpServer, resolveTarget } from "./adapter.js";
8
+ /**
9
+ * 启动 ActionDock MCP STDIO 协议服务。
10
+ * 建立监督进程物理边界:
11
+ * 1. 监督父进程独占标准输入/输出通道,专用于承载 JSON-RPC 协议;
12
+ * 2. 子进程承载 Action 运行时,通过 Node IPC 通信;
13
+ * 3. 子进程输出被重定向并进行限流排空(转入受控诊断流 stderr),彻底杜绝业务 console.log 破坏 MCP STDIO 流;
14
+ * 4. 子进程异常退出时,转换为结构化 JSON-RPC 错误,保障协议稳定性。
15
+ */
16
+ export async function startMcpStdio(options = {}) {
17
+ let activeServer;
18
+ let targetToUse;
19
+ let childProcess;
20
+ // 若显式传入了已构造的 target 或不可序列化的内存对象,直接复用或解析目标实例
21
+ if (options.target || options.actions || options.storage || options.app || options.host) {
22
+ targetToUse = options.target ?? (await resolveTarget(options)).target;
23
+ }
24
+ else {
25
+ // 建立隔离的监督子进程
26
+ const currentDir = dirname(fileURLToPath(import.meta.url));
27
+ const hostScript = resolve(currentDir, existsSync(join(currentDir, "stdio-host.ts")) ? "stdio-host.ts" : "stdio-host.js");
28
+ childProcess = spawn(process.execPath, [hostScript], {
29
+ cwd: options.projectRoot || process.cwd(),
30
+ env: {
31
+ ...process.env,
32
+ ACTIONDOCK_MCP_OPTIONS: JSON.stringify({
33
+ projectRoot: options.projectRoot,
34
+ projectRoots: options.projectRoots,
35
+ packageId: options.packageId,
36
+ packageIds: options.packageIds,
37
+ all: options.all,
38
+ customHome: options.customHome,
39
+ configOverrides: options.configOverrides,
40
+ timeoutMs: options.timeoutMs,
41
+ }),
42
+ },
43
+ stdio: ["pipe", "pipe", "pipe", "ipc"],
44
+ });
45
+ // 标准输出物理隔离与受控限流排空转入 stderr 诊断流
46
+ targetToUse = new IpcActionDockTarget({
47
+ childProcess,
48
+ maxDiagnosticBytes: 512 * 1024,
49
+ maxDiagnosticRate: 64 * 1024,
50
+ diagnosticTarget: process.stderr,
51
+ });
52
+ }
53
+ const stdioHandler = serveStdio(async () => {
54
+ const server = await createActionDockMcpServer({
55
+ ...options,
56
+ target: targetToUse,
57
+ });
58
+ activeServer = server;
59
+ targetToUse = targetToUse || server.target;
60
+ return server;
61
+ }, {
62
+ onerror: (err) => {
63
+ process.stderr.write(`[MCP Error] ${err?.message || String(err)}\n`);
64
+ },
65
+ });
66
+ const cleanup = async () => {
67
+ // 清理链路逐层释放,错误不吞没:收集首个异常向上透传,后续层继续尽力释放
68
+ let firstError;
69
+ const swallow = (err) => {
70
+ if (firstError === undefined)
71
+ firstError = err;
72
+ process.stderr.write(`[MCP Cleanup Error] ${err instanceof Error ? err.message : String(err)}\n`);
73
+ };
74
+ if (activeServer) {
75
+ try {
76
+ await activeServer.close();
77
+ }
78
+ catch (err) {
79
+ swallow(err);
80
+ }
81
+ }
82
+ if (targetToUse) {
83
+ try {
84
+ await targetToUse.close();
85
+ }
86
+ catch (err) {
87
+ swallow(err);
88
+ }
89
+ }
90
+ try {
91
+ await stdioHandler.close();
92
+ }
93
+ catch (err) {
94
+ swallow(err);
95
+ }
96
+ if (firstError !== undefined) {
97
+ throw firstError;
98
+ }
99
+ };
100
+ const handleSignal = (signalName) => {
101
+ cleanup()
102
+ .then(() => {
103
+ // 正常关闭退出码为 0
104
+ process.exit(0);
105
+ })
106
+ .catch((err) => {
107
+ // 清理失败退出非零退出码,异常详情已写入 stderr 诊断流
108
+ process.stderr.write(`[MCP ${signalName} Cleanup Failed] ${err instanceof Error ? err.message : String(err)}\n`);
109
+ process.exit(1);
110
+ });
111
+ };
112
+ process.once("SIGINT", () => handleSignal("SIGINT"));
113
+ process.once("SIGTERM", () => handleSignal("SIGTERM"));
114
+ }
@@ -0,0 +1,99 @@
1
+ import type { ActionDockApp, ActionDockHost, ActionDockTarget, RuntimeStorage } from "@actiondock/core";
2
+ import type { ActionDefinition, RunRecord, RunStatus } from "@actiondock/sdk";
3
+ /**
4
+ * MCP 任务状态枚举(兼容 Model Context Protocol Task 规范)。
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;
27
+ /**
28
+ * ActionDock MCP 适配层初始化选项。
29
+ * 统一以 target(及可选 host, app)为核心。
30
+ */
31
+ export interface ActionDockMcpOptions {
32
+ /** 目标 ActionDockTarget 门面实例(最高优先级) */
33
+ target?: ActionDockTarget;
34
+ /** 目标 ActionDockHost 宿主实例 */
35
+ host?: ActionDockHost;
36
+ /** 目标 ActionDockApp 应用实例 */
37
+ app?: ActionDockApp;
38
+ /** 单个目标项目根目录 */
39
+ projectRoot?: string;
40
+ /** 多个项目根目录(用于多包聚合提供) */
41
+ projectRoots?: string[];
42
+ /** 目标 Package ID */
43
+ packageId?: string;
44
+ /** 多个 Package ID 列表 */
45
+ packageIds?: string[];
46
+ /** 是否聚合暴露全局 Registry 中的所有 Package */
47
+ all?: boolean;
48
+ /** 自定义家目录路径 */
49
+ customHome?: string;
50
+ /** 配置动态覆盖项 */
51
+ configOverrides?: Record<string, unknown>;
52
+ /** 单个 Tool 执行超时时间(毫秒) */
53
+ timeoutMs?: number;
54
+ /** 预加载的 Action 集合(单元测试或内存模式使用) */
55
+ actions?: Map<string, ActionDefinition> | Array<{
56
+ id: string;
57
+ action: ActionDefinition;
58
+ } | (ActionDefinition & {
59
+ id: string;
60
+ })> | Record<string, ActionDefinition>;
61
+ /** 底层存储实例(单元测试或特定场景注入) */
62
+ storage?: RuntimeStorage;
63
+ /**
64
+ * 是否由适配层接管外部注入 storage 的生命周期。
65
+ * 默认 false:外部注入的 storage 由注入方自行管理,适配层不具各 close 语义;
66
+ * 置为 true 时透传原始实例,随 target.close() 级联关闭。
67
+ */
68
+ ownStorageLifecycle?: boolean;
69
+ /**
70
+ * 实例 close 是否级联关闭 target 门面。
71
+ * 默认 false:适配层产物可能被 SDK 传输层按请求(或按连接)创建与销毁,
72
+ * 级联语义会误杀共享 target,生命周期统一由外层入口的 stop/cleanup 收敛;
73
+ * 仅在调用方自行持有实例并需要一次 close 同时释放 target 时置 true。
74
+ */
75
+ cascadeTargetClose?: boolean;
76
+ }
77
+ /**
78
+ * HTTP 传输协议安全配置项。
79
+ */
80
+ export interface HttpSecurityOptions {
81
+ host?: string;
82
+ port?: number;
83
+ token?: string;
84
+ allowInsecureNoAuth?: boolean;
85
+ allowQueryToken?: boolean;
86
+ allowInsecureHttp?: boolean;
87
+ corsOrigins?: string[];
88
+ maxBodyBytes?: number;
89
+ }
90
+ export interface ActionDockMcpHttpOptions extends Omit<ActionDockMcpOptions, "host">, Omit<HttpSecurityOptions, "host"> {
91
+ host?: string | ActionDockHost;
92
+ }
93
+ export interface ActionDockMcpHttpServerInstance {
94
+ port: number;
95
+ host: string;
96
+ url: string;
97
+ target?: ActionDockTarget;
98
+ stop: () => Promise<void>;
99
+ }
package/dist/types.js ADDED
@@ -0,0 +1,33 @@
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
+ }