@actiondock/mcp 2.0.8 → 2.0.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@actiondock/mcp",
3
- "version": "2.0.8",
3
+ "version": "2.0.10",
4
4
  "description": "ActionDock MCP Adapter for exposing Actions as Model Context Protocol (MCP) Tools",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -27,15 +27,21 @@
27
27
  "test": "bun test"
28
28
  },
29
29
  "dependencies": {
30
- "@actiondock/core": "^2.0.0",
31
- "@actiondock/sdk": "^2.0.0",
30
+ "@actiondock/core": "^2.0.10",
31
+ "@actiondock/sdk": "^2.0.10",
32
32
  "@modelcontextprotocol/server": "^2.0.0"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@types/bun": "latest",
36
36
  "typescript": "^5.7.0"
37
37
  },
38
- "keywords": ["actiondock", "mcp", "model-context-protocol", "tools", "agent"],
38
+ "keywords": [
39
+ "actiondock",
40
+ "mcp",
41
+ "model-context-protocol",
42
+ "tools",
43
+ "agent"
44
+ ],
39
45
  "author": "team4u",
40
46
  "license": "Apache-2.0",
41
47
  "repository": {
package/src/adapter.ts CHANGED
@@ -17,6 +17,19 @@ import { McpServer } from "@modelcontextprotocol/server";
17
17
  import { toMcpSchema } from "./schemas";
18
18
  import { toMcpTaskPayload, type ActionDockMcpOptions } from "./types";
19
19
 
20
+ /**
21
+ * 判断目标值是否为普通对象(Plain Object)。
22
+ *
23
+ * @param value 待检查的值
24
+ */
25
+ export function isPlainObject(value: unknown): value is Record<string, unknown> {
26
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
27
+ return false;
28
+ }
29
+ const proto = Object.getPrototypeOf(value);
30
+ return proto === Object.prototype || proto === null;
31
+ }
32
+
20
33
  /**
21
34
  * 将 ActionDock 标准的 ExecutionResult 信封结构转换为 MCP 协议规范的 Tool Call 返回结果。
22
35
  *
@@ -24,6 +37,10 @@ import { toMcpTaskPayload, type ActionDockMcpOptions } from "./types";
24
37
  */
25
38
  export function toMcpResult(result: ExecutionResult) {
26
39
  if (result.ok) {
40
+ const structuredContent = isPlainObject(result.data)
41
+ ? (result.data as Record<string, unknown>)
42
+ : { value: result.data };
43
+
27
44
  return {
28
45
  content: [
29
46
  {
@@ -31,7 +48,7 @@ export function toMcpResult(result: ExecutionResult) {
31
48
  text: JSON.stringify(result),
32
49
  },
33
50
  ],
34
- structuredContent: result.data as Record<string, unknown>,
51
+ structuredContent,
35
52
  };
36
53
  }
37
54
 
@@ -102,12 +119,16 @@ function createMcpToolCallback(
102
119
  };
103
120
  }
104
121
 
122
+ export type ActionDockMcpServer = McpServer & {
123
+ close: () => Promise<void>;
124
+ };
125
+
105
126
  /**
106
127
  * Creates and configures an McpServer instance bound to one or more ActionDock packages with Tasks extension support.
107
128
  */
108
129
  export async function createActionDockMcpServer(
109
130
  options: ActionDockMcpOptions = {}
110
- ): Promise<McpServer> {
131
+ ): Promise<ActionDockMcpServer> {
111
132
  const targetRoots: string[] = [];
112
133
  if (options.projectRoot) {
113
134
  targetRoots.push(options.projectRoot);
@@ -359,5 +380,58 @@ export async function createActionDockMcpServer(
359
380
  };
360
381
  });
361
382
 
362
- return server;
383
+ const originalClose = server.close.bind(server);
384
+ let isClosed = false;
385
+
386
+ const closeFn = async (): Promise<void> => {
387
+ if (isClosed) return;
388
+ isClosed = true;
389
+
390
+ process.removeListener("exit", onProcessExit);
391
+
392
+ for (const storage of storages) {
393
+ try {
394
+ storage.close();
395
+ } catch {
396
+ // ignore
397
+ }
398
+ }
399
+
400
+ if (!options.runtimeRegistry) {
401
+ try {
402
+ runtimeRegistry.close();
403
+ } catch {
404
+ // ignore
405
+ }
406
+ }
407
+
408
+ try {
409
+ await originalClose();
410
+ } catch {
411
+ // ignore
412
+ }
413
+ };
414
+
415
+ const onProcessExit = () => {
416
+ for (const storage of storages) {
417
+ try {
418
+ storage.close();
419
+ } catch {
420
+ // ignore
421
+ }
422
+ }
423
+ if (!options.runtimeRegistry) {
424
+ try {
425
+ runtimeRegistry.close();
426
+ } catch {
427
+ // ignore
428
+ }
429
+ }
430
+ };
431
+
432
+ process.once("exit", onProcessExit);
433
+
434
+ (server as any).close = closeFn;
435
+
436
+ return server as ActionDockMcpServer;
363
437
  }
package/src/stdio.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { serveStdio } from "@modelcontextprotocol/server/stdio";
2
- import { createActionDockMcpServer } from "./adapter";
2
+ import { createActionDockMcpServer, type ActionDockMcpServer } from "./adapter";
3
3
  import type { ActionDockMcpOptions } from "./types";
4
4
 
5
5
  /**
@@ -8,9 +8,44 @@ import type { ActionDockMcpOptions } from "./types";
8
8
  export async function startMcpStdio(
9
9
  options: ActionDockMcpOptions = {}
10
10
  ): Promise<void> {
11
- serveStdio(() => createActionDockMcpServer(options), {
12
- onerror: (err) => {
13
- process.stderr.write(`[MCP Error] ${err?.message || String(err)}\n`);
11
+ let activeServer: ActionDockMcpServer | undefined;
12
+
13
+ const stdioHandler = serveStdio(
14
+ async () => {
15
+ const server = await createActionDockMcpServer(options);
16
+ activeServer = server;
17
+ return server;
14
18
  },
19
+ {
20
+ onerror: (err) => {
21
+ process.stderr.write(`[MCP Error] ${err?.message || String(err)}\n`);
22
+ },
23
+ }
24
+ );
25
+
26
+ const cleanup = async () => {
27
+ if (activeServer) {
28
+ try {
29
+ await activeServer.close();
30
+ } catch {
31
+ // ignore
32
+ }
33
+ }
34
+ };
35
+
36
+ process.once("SIGINT", async () => {
37
+ await cleanup();
38
+ try {
39
+ await stdioHandler.close();
40
+ } catch {}
41
+ process.exit(0);
42
+ });
43
+
44
+ process.once("SIGTERM", async () => {
45
+ await cleanup();
46
+ try {
47
+ await stdioHandler.close();
48
+ } catch {}
49
+ process.exit(0);
15
50
  });
16
51
  }