@moxxy/plugin-mcp 0.26.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.
Files changed (103) hide show
  1. package/LICENSE +21 -0
  2. package/dist/admin/config-io.d.ts +46 -0
  3. package/dist/admin/config-io.d.ts.map +1 -0
  4. package/dist/admin/config-io.js +108 -0
  5. package/dist/admin/config-io.js.map +1 -0
  6. package/dist/admin/config-schema.d.ts +65 -0
  7. package/dist/admin/config-schema.d.ts.map +1 -0
  8. package/dist/admin/config-schema.js +36 -0
  9. package/dist/admin/config-schema.js.map +1 -0
  10. package/dist/admin/index.d.ts +21 -0
  11. package/dist/admin/index.d.ts.map +1 -0
  12. package/dist/admin/index.js +189 -0
  13. package/dist/admin/index.js.map +1 -0
  14. package/dist/admin/runtime.d.ts +25 -0
  15. package/dist/admin/runtime.d.ts.map +1 -0
  16. package/dist/admin/runtime.js +196 -0
  17. package/dist/admin/runtime.js.map +1 -0
  18. package/dist/admin/schema.d.ts +37 -0
  19. package/dist/admin/schema.d.ts.map +1 -0
  20. package/dist/admin/schema.js +97 -0
  21. package/dist/admin/schema.js.map +1 -0
  22. package/dist/admin/secrets.d.ts +24 -0
  23. package/dist/admin/secrets.d.ts.map +1 -0
  24. package/dist/admin/secrets.js +35 -0
  25. package/dist/admin/secrets.js.map +1 -0
  26. package/dist/admin/skill.d.ts +18 -0
  27. package/dist/admin/skill.d.ts.map +1 -0
  28. package/dist/admin/skill.js +70 -0
  29. package/dist/admin/skill.js.map +1 -0
  30. package/dist/admin/tools/add.d.ts +19 -0
  31. package/dist/admin/tools/add.d.ts.map +1 -0
  32. package/dist/admin/tools/add.js +97 -0
  33. package/dist/admin/tools/add.js.map +1 -0
  34. package/dist/admin/tools/list.d.ts +3 -0
  35. package/dist/admin/tools/list.d.ts.map +1 -0
  36. package/dist/admin/tools/list.js +16 -0
  37. package/dist/admin/tools/list.js.map +1 -0
  38. package/dist/admin/tools/remove.d.ts +6 -0
  39. package/dist/admin/tools/remove.d.ts.map +1 -0
  40. package/dist/admin/tools/remove.js +41 -0
  41. package/dist/admin/tools/remove.js.map +1 -0
  42. package/dist/admin/tools/test.d.ts +8 -0
  43. package/dist/admin/tools/test.d.ts.map +1 -0
  44. package/dist/admin/tools/test.js +73 -0
  45. package/dist/admin/tools/test.js.map +1 -0
  46. package/dist/admin/types.d.ts +107 -0
  47. package/dist/admin/types.d.ts.map +1 -0
  48. package/dist/admin/types.js +2 -0
  49. package/dist/admin/types.js.map +1 -0
  50. package/dist/admin.d.ts +7 -0
  51. package/dist/admin.d.ts.map +1 -0
  52. package/dist/admin.js +7 -0
  53. package/dist/admin.js.map +1 -0
  54. package/dist/client.d.ts +10 -0
  55. package/dist/client.d.ts.map +1 -0
  56. package/dist/client.js +73 -0
  57. package/dist/client.js.map +1 -0
  58. package/dist/index.d.ts +17 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/index.js +50 -0
  61. package/dist/index.js.map +1 -0
  62. package/dist/timeout.d.ts +23 -0
  63. package/dist/timeout.d.ts.map +1 -0
  64. package/dist/timeout.js +35 -0
  65. package/dist/timeout.js.map +1 -0
  66. package/dist/types.d.ts +59 -0
  67. package/dist/types.d.ts.map +1 -0
  68. package/dist/types.js +4 -0
  69. package/dist/types.js.map +1 -0
  70. package/dist/wrap.d.ts +23 -0
  71. package/dist/wrap.d.ts.map +1 -0
  72. package/dist/wrap.js +193 -0
  73. package/dist/wrap.js.map +1 -0
  74. package/package.json +63 -0
  75. package/src/admin/config-io.test.ts +188 -0
  76. package/src/admin/config-io.ts +112 -0
  77. package/src/admin/config-schema.ts +39 -0
  78. package/src/admin/index.ts +225 -0
  79. package/src/admin/runtime.test.ts +470 -0
  80. package/src/admin/runtime.ts +244 -0
  81. package/src/admin/schema.test.ts +70 -0
  82. package/src/admin/schema.ts +110 -0
  83. package/src/admin/secrets.test.ts +60 -0
  84. package/src/admin/secrets.ts +55 -0
  85. package/src/admin/skill.test.ts +143 -0
  86. package/src/admin/skill.ts +83 -0
  87. package/src/admin/tools/add.test.ts +193 -0
  88. package/src/admin/tools/add.ts +118 -0
  89. package/src/admin/tools/list.ts +19 -0
  90. package/src/admin/tools/remove.test.ts +74 -0
  91. package/src/admin/tools/remove.ts +46 -0
  92. package/src/admin/tools/test.test.ts +123 -0
  93. package/src/admin/tools/test.ts +84 -0
  94. package/src/admin/types.ts +112 -0
  95. package/src/admin.ts +25 -0
  96. package/src/client.ts +120 -0
  97. package/src/discovery.test.ts +45 -0
  98. package/src/index.test.ts +95 -0
  99. package/src/index.ts +101 -0
  100. package/src/timeout.ts +37 -0
  101. package/src/types.ts +57 -0
  102. package/src/wrap.test.ts +261 -0
  103. package/src/wrap.ts +236 -0
@@ -0,0 +1,97 @@
1
+ import { defineTool, MoxxyError } from '@moxxy/sdk';
2
+ import { mcpConfigPath, mutateMcpConfig, readMcpConfig } from '../config-io.js';
3
+ import { addServerInput, validateAddServerInput } from '../schema.js';
4
+ export function buildAddServerTool(deps) {
5
+ const { registry, attachServer, detachServer, writeMcpUsageSkill } = deps;
6
+ return defineTool({
7
+ name: 'mcp_add_server',
8
+ description: 'Register a new MCP server in ~/.moxxy/mcp.json. Pick "stdio" for local commands ' +
9
+ '(npm/uv packages, scripts); pick "http" or "sse" for remote HTTP servers. The new ' +
10
+ 'server\'s tools become available after the next moxxy restart. Call mcp_test_server ' +
11
+ 'first if you want to verify connectivity before persisting. NEVER pass API keys or ' +
12
+ 'tokens in plaintext: store the secret in the vault first (vault_set), then reference ' +
13
+ 'it as "${vault:NAME}" in the env/header value — the placeholder is what gets ' +
14
+ 'persisted, and it is resolved only at connect time.',
15
+ inputSchema: addServerInput,
16
+ permission: { action: 'prompt' },
17
+ handler: async (input) => {
18
+ const server = validateAddServerInput(input);
19
+ const cfg = await readMcpConfig();
20
+ if (cfg.servers.some((s) => s.name === server.name)) {
21
+ throw new MoxxyError({
22
+ code: 'CONFIG_INVALID',
23
+ message: `mcp_add_server: an MCP server named "${server.name}" already exists. ` +
24
+ `Use mcp_remove_server first, or pick a different name.`,
25
+ });
26
+ }
27
+ // Hot-attach: connect + register tools BEFORE persisting. If
28
+ // attach fails (bad URL, missing command, schema mismatch),
29
+ // we never write a broken entry to disk.
30
+ const { toolNames, descriptors } = await attachServer(server);
31
+ // Cache descriptors so next boot can register lazy stubs
32
+ // without paying the connection cost up-front.
33
+ const stored = { ...server, cachedTools: descriptors };
34
+ // Persist under the shared config mutex, re-reading the latest
35
+ // catalog so a concurrent add/remove can't clobber the file. The
36
+ // duplicate check is repeated against the fresh read to catch a race
37
+ // where two adds for the same name both passed the initial check.
38
+ try {
39
+ await mutateMcpConfig((current) => {
40
+ if (current.servers.some((s) => s.name === server.name)) {
41
+ throw new MoxxyError({
42
+ code: 'CONFIG_INVALID',
43
+ message: `mcp_add_server: an MCP server named "${server.name}" already exists. ` +
44
+ `Use mcp_remove_server first, or pick a different name.`,
45
+ });
46
+ }
47
+ return { next: { servers: [...current.servers, stored] }, result: undefined };
48
+ });
49
+ }
50
+ catch (err) {
51
+ // attachServer already registered the live tools + opened a client, but
52
+ // persistence failed (e.g. a concurrent add for the same name won the
53
+ // race, or the file write threw). Roll the live registry back so we
54
+ // don't strand callable tools with no config entry + a leaked client.
55
+ await detachServer(server.name).catch(() => undefined);
56
+ throw err;
57
+ }
58
+ // Auto-create the usage skill so /skills surfaces the new
59
+ // server alongside hand-authored skills. Best-effort — if
60
+ // skill writing fails, the MCP attach still succeeded.
61
+ let skillResult = null;
62
+ if (input.autoSkill !== false) {
63
+ try {
64
+ skillResult = await writeMcpUsageSkill(server, descriptors);
65
+ }
66
+ catch (err) {
67
+ skillResult = null;
68
+ // surface but don't fail the whole tool call
69
+ return {
70
+ ok: true,
71
+ name: server.name,
72
+ path: mcpConfigPath(),
73
+ attached: registry !== null,
74
+ tools: toolNames,
75
+ skill: null,
76
+ skillError: err instanceof Error ? err.message : String(err),
77
+ note: 'Server attached + persisted; skill creation failed (see skillError).',
78
+ };
79
+ }
80
+ }
81
+ return {
82
+ ok: true,
83
+ name: server.name,
84
+ path: mcpConfigPath(),
85
+ attached: registry !== null,
86
+ tools: toolNames,
87
+ skill: skillResult,
88
+ note: registry
89
+ ? `Live in this session — ${toolNames.length} tool${toolNames.length === 1 ? '' : 's'} now callable.` +
90
+ (skillResult ? ` Usage skill written to ${skillResult.path}.` : '') +
91
+ ' Persisted; survives restart.'
92
+ : 'Saved to config. Restart moxxy to load the tools (no live registry was wired into the admin plugin).',
93
+ };
94
+ },
95
+ });
96
+ }
97
+ //# sourceMappingURL=add.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add.js","sourceRoot":"","sources":["../../../src/admin/tools/add.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAgB,MAAM,YAAY,CAAC;AAElE,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChF,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAqBtE,MAAM,UAAU,kBAAkB,CAAC,IAAuB;IACxD,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,GAAG,IAAI,CAAC;IAC1E,OAAO,UAAU,CAAC;QAChB,IAAI,EAAE,gBAAgB;QACtB,WAAW,EACT,kFAAkF;YAClF,oFAAoF;YACpF,sFAAsF;YACtF,qFAAqF;YACrF,uFAAuF;YACvF,+EAA+E;YAC/E,qDAAqD;QACvD,WAAW,EAAE,cAAc;QAC3B,UAAU,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE;QAChC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;YACvB,MAAM,MAAM,GAAG,sBAAsB,CAAC,KAAK,CAAC,CAAC;YAC7C,MAAM,GAAG,GAAG,MAAM,aAAa,EAAE,CAAC;YAClC,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;gBACpD,MAAM,IAAI,UAAU,CAAC;oBACnB,IAAI,EAAE,gBAAgB;oBACtB,OAAO,EACL,wCAAwC,MAAM,CAAC,IAAI,oBAAoB;wBACvE,wDAAwD;iBAC3D,CAAC,CAAC;YACL,CAAC;YACD,6DAA6D;YAC7D,4DAA4D;YAC5D,yCAAyC;YACzC,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,GAAG,MAAM,YAAY,CAAC,MAAM,CAAC,CAAC;YAC9D,yDAAyD;YACzD,+CAA+C;YAC/C,MAAM,MAAM,GAAoB,EAAE,GAAG,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC;YACxE,+DAA+D;YAC/D,iEAAiE;YACjE,qEAAqE;YACrE,kEAAkE;YAClE,IAAI,CAAC;gBACH,MAAM,eAAe,CAAC,CAAC,OAAO,EAAE,EAAE;oBAChC,IAAI,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;wBACxD,MAAM,IAAI,UAAU,CAAC;4BACnB,IAAI,EAAE,gBAAgB;4BACtB,OAAO,EACL,wCAAwC,MAAM,CAAC,IAAI,oBAAoB;gCACvE,wDAAwD;yBAC3D,CAAC,CAAC;oBACL,CAAC;oBACD,OAAO,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;gBAChF,CAAC,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,wEAAwE;gBACxE,sEAAsE;gBACtE,oEAAoE;gBACpE,sEAAsE;gBACtE,MAAM,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;gBACvD,MAAM,GAAG,CAAC;YACZ,CAAC;YACD,0DAA0D;YAC1D,0DAA0D;YAC1D,uDAAuD;YACvD,IAAI,WAAW,GAA+C,IAAI,CAAC;YACnE,IAAI,KAAK,CAAC,SAAS,KAAK,KAAK,EAAE,CAAC;gBAC9B,IAAI,CAAC;oBACH,WAAW,GAAG,MAAM,kBAAkB,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;gBAC9D,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,WAAW,GAAG,IAAI,CAAC;oBACnB,6CAA6C;oBAC7C,OAAO;wBACL,EAAE,EAAE,IAAI;wBACR,IAAI,EAAE,MAAM,CAAC,IAAI;wBACjB,IAAI,EAAE,aAAa,EAAE;wBACrB,QAAQ,EAAE,QAAQ,KAAK,IAAI;wBAC3B,KAAK,EAAE,SAAS;wBAChB,KAAK,EAAE,IAAI;wBACX,UAAU,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;wBAC5D,IAAI,EAAE,sEAAsE;qBAC7E,CAAC;gBACJ,CAAC;YACH,CAAC;YACD,OAAO;gBACL,EAAE,EAAE,IAAI;gBACR,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,IAAI,EAAE,aAAa,EAAE;gBACrB,QAAQ,EAAE,QAAQ,KAAK,IAAI;gBAC3B,KAAK,EAAE,SAAS;gBAChB,KAAK,EAAE,WAAW;gBAClB,IAAI,EAAE,QAAQ;oBACZ,CAAC,CAAC,0BAA0B,SAAS,CAAC,MAAM,QAAQ,SAAS,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,gBAAgB;wBACnG,CAAC,WAAW,CAAC,CAAC,CAAC,2BAA2B,WAAW,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;wBACnE,+BAA+B;oBACjC,CAAC,CAAC,sGAAsG;aAC3G,CAAC;QACJ,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { type ToolDef } from '@moxxy/sdk';
2
+ export declare function buildListServersTool(): ToolDef;
3
+ //# sourceMappingURL=list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../src/admin/tools/list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,OAAO,EAAE,MAAM,YAAY,CAAC;AAGzD,wBAAgB,oBAAoB,IAAI,OAAO,CAe9C"}
@@ -0,0 +1,16 @@
1
+ import { defineTool, z } from '@moxxy/sdk';
2
+ import { readMcpConfig } from '../config-io.js';
3
+ export function buildListServersTool() {
4
+ return defineTool({
5
+ name: 'mcp_list_servers',
6
+ description: 'List every MCP server currently registered in ~/.moxxy/mcp.json. Returns name + transport kind + connection details (command/url) for each.',
7
+ inputSchema: z.object({}),
8
+ handler: async () => {
9
+ const cfg = await readMcpConfig();
10
+ return cfg.servers.map((s) => s.kind === undefined || s.kind === 'stdio'
11
+ ? { name: s.name, kind: 'stdio', command: s.command }
12
+ : { name: s.name, kind: s.kind, url: s.url });
13
+ },
14
+ });
15
+ }
16
+ //# sourceMappingURL=list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.js","sourceRoot":"","sources":["../../../src/admin/tools/list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,CAAC,EAAgB,MAAM,YAAY,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,MAAM,UAAU,oBAAoB;IAClC,OAAO,UAAU,CAAC;QAChB,IAAI,EAAE,kBAAkB;QACxB,WAAW,EACT,6IAA6I;QAC/I,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,MAAM,GAAG,GAAG,MAAM,aAAa,EAAE,CAAC;YAClC,OAAO,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAC3B,CAAC,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO;gBACxC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,OAAgB,EAAE,OAAO,EAAG,CAAyB,CAAC,OAAO,EAAE;gBACvF,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,GAAG,EAAG,CAAqB,CAAC,GAAG,EAAE,CACpE,CAAC;QACJ,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { type ToolDef } from '@moxxy/sdk';
2
+ export interface RemoveServerToolDeps {
3
+ detachServer(name: string): Promise<boolean>;
4
+ }
5
+ export declare function buildRemoveServerTool(deps: RemoveServerToolDeps): ToolDef;
6
+ //# sourceMappingURL=remove.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remove.d.ts","sourceRoot":"","sources":["../../../src/admin/tools/remove.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,OAAO,EAAE,MAAM,YAAY,CAAC;AAIzD,MAAM,WAAW,oBAAoB;IACnC,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9C;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAqCzE"}
@@ -0,0 +1,41 @@
1
+ import { defineTool, z } from '@moxxy/sdk';
2
+ import { mutateMcpConfig } from '../config-io.js';
3
+ import { serverNameSchema } from '../schema.js';
4
+ export function buildRemoveServerTool(deps) {
5
+ const { detachServer } = deps;
6
+ return defineTool({
7
+ name: 'mcp_remove_server',
8
+ description: 'Remove an MCP server from ~/.moxxy/mcp.json and detach its tools from the live session. ' +
9
+ 'The tools become uncallable immediately and the entry is gone on next restart.',
10
+ inputSchema: z.object({ name: serverNameSchema }),
11
+ permission: { action: 'prompt' },
12
+ handler: async ({ name }) => {
13
+ // Read-modify-write under the shared config mutex so a concurrent
14
+ // add/remove can't clobber the file.
15
+ const persisted = await mutateMcpConfig((cfg) => {
16
+ const filtered = cfg.servers.filter((s) => s.name !== name);
17
+ if (filtered.length === cfg.servers.length) {
18
+ // Nothing matched — return the same reference to skip the write.
19
+ return { next: cfg, result: false };
20
+ }
21
+ return { next: { servers: filtered }, result: true };
22
+ });
23
+ // Runtime detach runs OUTSIDE the config mutex (the live tool registry
24
+ // isn't guarded by it), so a concurrent same-name add could interleave
25
+ // between this persist and detach. Detach is best-effort and loosely
26
+ // coupled to the config — the entry is authoritative, the runtime map
27
+ // self-heals on the next attach/restart.
28
+ const detached = await detachServer(name);
29
+ if (!persisted && !detached) {
30
+ return { removed: false, name, note: `No MCP server named "${name}" was registered.` };
31
+ }
32
+ return {
33
+ removed: true,
34
+ name,
35
+ persistedChange: persisted,
36
+ detachedFromSession: detached,
37
+ };
38
+ },
39
+ });
40
+ }
41
+ //# sourceMappingURL=remove.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remove.js","sourceRoot":"","sources":["../../../src/admin/tools/remove.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,CAAC,EAAgB,MAAM,YAAY,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAMhD,MAAM,UAAU,qBAAqB,CAAC,IAA0B;IAC9D,MAAM,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC;IAC9B,OAAO,UAAU,CAAC;QAChB,IAAI,EAAE,mBAAmB;QACzB,WAAW,EACT,0FAA0F;YAC1F,gFAAgF;QAClF,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC;QACjD,UAAU,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE;QAChC,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YAC1B,kEAAkE;YAClE,qCAAqC;YACrC,MAAM,SAAS,GAAG,MAAM,eAAe,CAAC,CAAC,GAAG,EAAE,EAAE;gBAC9C,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;gBAC5D,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;oBAC3C,iEAAiE;oBACjE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;gBACtC,CAAC;gBACD,OAAO,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;YACvD,CAAC,CAAC,CAAC;YACH,uEAAuE;YACvE,uEAAuE;YACvE,qEAAqE;YACrE,sEAAsE;YACtE,yCAAyC;YACzC,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,CAAC;YAC1C,IAAI,CAAC,SAAS,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAC5B,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,wBAAwB,IAAI,mBAAmB,EAAE,CAAC;YACzF,CAAC;YACD,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,IAAI;gBACJ,eAAe,EAAE,SAAS;gBAC1B,mBAAmB,EAAE,QAAQ;aAC9B,CAAC;QACJ,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { type ToolDef } from '@moxxy/sdk';
2
+ import { type McpSecretResolver } from '../secrets.js';
3
+ export interface TestServerToolDeps {
4
+ /** Resolves `${vault:NAME}` placeholders in env/header values at connect time. */
5
+ readonly secretResolver?: McpSecretResolver | null;
6
+ }
7
+ export declare function buildTestServerTool(deps?: TestServerToolDeps): ToolDef;
8
+ //# sourceMappingURL=test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../../../src/admin/tools/test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,OAAO,EAAE,MAAM,YAAY,CAAC;AAKtD,OAAO,EAAwB,KAAK,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAE7E,MAAM,WAAW,kBAAkB;IACjC,kFAAkF;IAClF,QAAQ,CAAC,cAAc,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;CACpD;AAED,wBAAgB,mBAAmB,CAAC,IAAI,GAAE,kBAAuB,GAAG,OAAO,CAuE1E"}
@@ -0,0 +1,73 @@
1
+ import { defineTool } from '@moxxy/sdk';
2
+ import { defaultClientFactory } from '../../client.js';
3
+ import { MCP_CONNECT_TIMEOUT_MS, withTimeout } from '../../timeout.js';
4
+ import { wrapMcpServerTools } from '../../wrap.js';
5
+ import { addServerInput, validateAddServerInput } from '../schema.js';
6
+ import { resolveServerSecrets } from '../secrets.js';
7
+ export function buildTestServerTool(deps = {}) {
8
+ return defineTool({
9
+ name: 'mcp_test_server',
10
+ description: 'Connect to an MCP server WITHOUT saving it to config. Returns the list of tools the ' +
11
+ 'server exposes if the connection succeeds, or a connection-error message. Useful for ' +
12
+ 'sanity-checking before calling mcp_add_server. Credentials (env/header values) must be ' +
13
+ 'vault references, never plaintext: store the secret first via vault_set, then pass ' +
14
+ '"${vault:NAME}" — it is resolved at connect time.',
15
+ inputSchema: addServerInput,
16
+ // Mirrors mcp_add_server: a stdio server is an arbitrary local
17
+ // executable we spawn, so gate behind a prompt rather than running
18
+ // unknown commands silently.
19
+ permission: { action: 'prompt' },
20
+ // Honest capability surface modeled on the Bash tool: for kind="stdio"
21
+ // this spawns a child process the user named (subprocess + broad fs,
22
+ // since the command can touch anything it likes); for http/sse it
23
+ // makes an outbound connection to an arbitrary URL (net: any). Advisory
24
+ // until @moxxy/plugin-security is enabled, then enforced at call time.
25
+ isolation: {
26
+ required: 'inproc',
27
+ capabilities: {
28
+ subprocess: true,
29
+ fs: { read: ['$cwd/**', '/tmp/**'], write: ['$cwd/**', '/tmp/**'] },
30
+ net: { mode: 'any' },
31
+ env: ['PATH', 'HOME', 'USER', 'SHELL', 'LANG', 'LC_ALL', 'TERM'],
32
+ timeMs: 600_000,
33
+ },
34
+ },
35
+ handler: async (input) => {
36
+ const server = validateAddServerInput(input);
37
+ let client = null;
38
+ try {
39
+ // Bound the connect + listTools handshake so a wedged / unreachable
40
+ // server surfaces as a readable connection error instead of hanging
41
+ // this tool call forever (defaultClientFactory + listTools take no
42
+ // AbortSignal). On timeout the inner `connect` may still resolve a
43
+ // client after we've rejected; the finally{} below closes whatever
44
+ // `client` we captured, so a late-opening handle isn't leaked.
45
+ client = await withTimeout(defaultClientFactory(await resolveServerSecrets(server, deps.secretResolver ?? null)), MCP_CONNECT_TIMEOUT_MS, `MCP connect "${server.name}"`);
46
+ const wrapped = await withTimeout(wrapMcpServerTools({ server, client }), MCP_CONNECT_TIMEOUT_MS, `MCP listTools "${server.name}"`);
47
+ return {
48
+ ok: true,
49
+ name: server.name,
50
+ tools: wrapped.map((t) => ({ name: t.name, description: t.description })),
51
+ };
52
+ }
53
+ catch (err) {
54
+ return {
55
+ ok: false,
56
+ name: server.name,
57
+ error: err instanceof Error ? err.message : String(err),
58
+ };
59
+ }
60
+ finally {
61
+ if (client) {
62
+ try {
63
+ await client.close();
64
+ }
65
+ catch {
66
+ /* ignore */
67
+ }
68
+ }
69
+ }
70
+ },
71
+ });
72
+ }
73
+ //# sourceMappingURL=test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test.js","sourceRoot":"","sources":["../../../src/admin/tools/test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,107 @@
1
+ import type { Skill, ToolDef } from '@moxxy/sdk';
2
+ import type { McpClientLike, McpServerConfig, McpToolDescriptor } from '../types.js';
3
+ import type { McpSecretResolver } from './secrets.js';
4
+ /**
5
+ * Minimal skill-registry shape the admin plugin needs to auto-register
6
+ * a usage skill after `mcp_add_server`. Loose typing to keep this plugin
7
+ * free of an explicit @moxxy/core import.
8
+ */
9
+ export interface AdminSkillRegistryLike {
10
+ register(skill: Skill): void;
11
+ byName(name: string): Skill | undefined;
12
+ }
13
+ /**
14
+ * Live runtime: live MCP clients keyed by server name plus the set of
15
+ * tool names each one registered into the session. Lets us close +
16
+ * unregister on `mcp_remove_server` and on shutdown without
17
+ * rediscovering anything. Module-scoped so the admin plugin and the
18
+ * shutdown hook share the same state; each Session that loads the
19
+ * plugin gets its own map via the closure in `buildMcpAdminPlugin`.
20
+ */
21
+ export interface McpRuntimeHandle {
22
+ readonly client: McpClientLike;
23
+ readonly toolNames: ReadonlyArray<string>;
24
+ }
25
+ /**
26
+ * Tool-registry surface the admin plugin uses to hot-attach / detach
27
+ * MCP tools. Matches the `ToolRegistry` in @moxxy/core but typed loosely
28
+ * so we don't add an internal-dep on core from this plugin.
29
+ */
30
+ export interface AdminToolRegistryLike {
31
+ has(name: string): boolean;
32
+ register(tool: ToolDef): void;
33
+ unregister(name: string): void;
34
+ }
35
+ /**
36
+ * On-disk catalog entry: connection config PLUS a cache of the tool
37
+ * descriptors the server last advertised, plus an enable/disable flag.
38
+ *
39
+ * Defined as an intersection (not `extends`) so the McpServerConfig
40
+ * discriminated union is preserved — `extends` would collapse it.
41
+ */
42
+ export type McpStoredServer = McpServerConfig & {
43
+ readonly cachedTools?: ReadonlyArray<McpToolDescriptor>;
44
+ /** When true, the boot loader skips this entry — no lazy stubs are
45
+ * registered and tools stay invisible. Lets the user keep the
46
+ * connection config for later without paying for tool registration. */
47
+ readonly disabled?: boolean;
48
+ };
49
+ export interface McpStoredConfig {
50
+ readonly servers: ReadonlyArray<McpStoredServer>;
51
+ }
52
+ export interface BuildMcpAdminPluginOptions {
53
+ /**
54
+ * Live tool registry. When provided, `mcp_add_server` connects + wraps
55
+ * the server immediately and registers its tools into this registry —
56
+ * no restart needed. `mcp_remove_server` closes the client and
57
+ * unregisters. Pass `null` for pure-config behavior (write-only).
58
+ */
59
+ readonly toolRegistry: AdminToolRegistryLike | null;
60
+ /**
61
+ * Skill registry + skills dir. When provided, `mcp_add_server`
62
+ * auto-writes a deterministic usage skill (server-name + tool catalog)
63
+ * to disk and registers it so `/skills` and the system-prompt index
64
+ * surface the MCP server alongside hand-authored skills. The skill is
65
+ * generated from descriptors directly — no model call. Pass `null` to
66
+ * disable auto-skill creation.
67
+ */
68
+ readonly skillRegistry?: AdminSkillRegistryLike | null;
69
+ readonly userSkillsDir?: string;
70
+ /**
71
+ * Resolves `${vault:NAME}` placeholders in server env/header values at
72
+ * CONNECT time (every connect path: hot-attach, lazy attach, cache
73
+ * refresh, mcp_test_server). Wired from setup with the vault's
74
+ * `resolveString`; literal values pass through unchanged. The persisted
75
+ * catalog always keeps the placeholder form, never the plaintext.
76
+ */
77
+ readonly secretResolver?: McpSecretResolver | null;
78
+ }
79
+ /**
80
+ * Runtime control surface exposed alongside the admin Plugin. The TUI's
81
+ * /mcp slash command and the CLI's `moxxy mcp` subcommand use this to
82
+ * detach a server's live tools when disabling, or re-attach when
83
+ * enabling, without going through the model.
84
+ */
85
+ export interface McpAdminApi {
86
+ /** Refresh + lazy-attach a server (used after enabling). */
87
+ enableAndAttach(name: string): Promise<{
88
+ toolNames: ReadonlyArray<string>;
89
+ } | null>;
90
+ /** Detach a server's live tools and close its client. */
91
+ detach(name: string): Promise<boolean>;
92
+ /**
93
+ * One-shot snapshot of every server configured in ~/.moxxy/mcp.json,
94
+ * joined with the runtime attach state. `enabled` reflects the persisted
95
+ * config (`disabled !== true`); `connected` is true when the server's
96
+ * tools are attached to the session (whether eagerly or via a lazy stub
97
+ * — both states make tools callable from a user's perspective). Used
98
+ * by the TUI status bar to show "mcp 2/3" style summaries.
99
+ */
100
+ listServers(): Promise<ReadonlyArray<McpServerStatus>>;
101
+ }
102
+ export interface McpServerStatus {
103
+ readonly name: string;
104
+ readonly enabled: boolean;
105
+ readonly connected: boolean;
106
+ }
107
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/admin/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACrF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEtD;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAC7B,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,CAAC;CACzC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAC3C;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IAC9B,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,eAAe,GAAG;IAC9C,QAAQ,CAAC,WAAW,CAAC,EAAE,aAAa,CAAC,iBAAiB,CAAC,CAAC;IACxD;;4EAEwE;IACxE,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B,CAAC;AAEF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;CAClD;AAED,MAAM,WAAW,0BAA0B;IACzC;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACpD;;;;;;;OAOG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,sBAAsB,GAAG,IAAI,CAAC;IACvD,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;CACpD;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,4DAA4D;IAC5D,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IACpF,yDAAyD;IACzD,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACvC;;;;;;;OAOG;IACH,WAAW,IAAI,OAAO,CAAC,aAAa,CAAC,eAAe,CAAC,CAAC,CAAC;CACxD;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/admin/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Backward-compatibility façade. The real implementation lives in
3
+ * `./admin/` as cohesive sub-modules; this file just re-exports the
4
+ * symbols that consumers (and the package's own `src/index.ts`) import.
5
+ */
6
+ export { buildMcpAdminPlugin, buildMcpAdminPluginWithApi, mcpAdminPlugin, mcpConfigPath, readMcpConfig, removeServerFromConfig, setServerDisabled, writeMcpConfig, resolveServerSecrets, type McpSecretResolver, type AdminSkillRegistryLike, type AdminToolRegistryLike, type BuildMcpAdminPluginOptions, type McpAdminApi, type McpRuntimeHandle, type McpServerStatus, type McpStoredConfig, type McpStoredServer, } from './admin/index.js';
7
+ //# sourceMappingURL=admin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../src/admin.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,mBAAmB,EACnB,0BAA0B,EAC1B,cAAc,EACd,aAAa,EACb,aAAa,EACb,sBAAsB,EACtB,iBAAiB,EACjB,cAAc,EACd,oBAAoB,EACpB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,0BAA0B,EAC/B,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,eAAe,GACrB,MAAM,kBAAkB,CAAC"}
package/dist/admin.js ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Backward-compatibility façade. The real implementation lives in
3
+ * `./admin/` as cohesive sub-modules; this file just re-exports the
4
+ * symbols that consumers (and the package's own `src/index.ts`) import.
5
+ */
6
+ export { buildMcpAdminPlugin, buildMcpAdminPluginWithApi, mcpAdminPlugin, mcpConfigPath, readMcpConfig, removeServerFromConfig, setServerDisabled, writeMcpConfig, resolveServerSecrets, } from './admin/index.js';
7
+ //# sourceMappingURL=admin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin.js","sourceRoot":"","sources":["../src/admin.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,mBAAmB,EACnB,0BAA0B,EAC1B,cAAc,EACd,aAAa,EACb,aAAa,EACb,sBAAsB,EACtB,iBAAiB,EACjB,cAAc,EACd,oBAAoB,GAUrB,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Default MCP client factory + transport setup. Lives in its own module so
3
+ * both `index.ts` (the public surface) and `admin.ts` (the runtime
4
+ * admin/hot-attach tools) can pull `defaultClientFactory` without the two
5
+ * referencing each other — which is what would otherwise create the
6
+ * `admin.ts → index.ts → admin.ts` import cycle.
7
+ */
8
+ import type { McpClientLike, McpPluginOptions, McpServerConfig } from './types.js';
9
+ export declare function defaultClientFactory(server: McpServerConfig, options?: McpPluginOptions): Promise<McpClientLike>;
10
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAEV,aAAa,EAEb,gBAAgB,EAChB,eAAe,EAEhB,MAAM,YAAY,CAAC;AAEpB,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,eAAe,EACvB,OAAO,GAAE,gBAAkC,GAC1C,OAAO,CAAC,aAAa,CAAC,CAgCxB"}
package/dist/client.js ADDED
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Default MCP client factory + transport setup. Lives in its own module so
3
+ * both `index.ts` (the public surface) and `admin.ts` (the runtime
4
+ * admin/hot-attach tools) can pull `defaultClientFactory` without the two
5
+ * referencing each other — which is what would otherwise create the
6
+ * `admin.ts → index.ts → admin.ts` import cycle.
7
+ */
8
+ export async function defaultClientFactory(server, options = { servers: [] }) {
9
+ const { Client } = (await import('@modelcontextprotocol/sdk/client/index.js'));
10
+ // Type the SDK boundary ONCE here; the rest of the factory works against the
11
+ // typed `McpClientUntyped` so a signature change in the SDK surfaces as a
12
+ // compile error rather than being papered over by per-call casts.
13
+ const client = new Client({ name: options.clientName ?? 'moxxy', version: options.clientVersion ?? '0.0.0' }, { capabilities: {} });
14
+ const transport = await createTransport(server);
15
+ await client.connect(transport);
16
+ return {
17
+ async listTools() {
18
+ const result = await client.listTools();
19
+ return { tools: (result.tools ?? []).map(toToolDescriptor) };
20
+ },
21
+ async callTool(args) {
22
+ const result = await client.callTool(args);
23
+ const out = {
24
+ content: result.content?.map(toContentBlock),
25
+ isError: result.isError,
26
+ };
27
+ return out;
28
+ },
29
+ async close() {
30
+ await client.close();
31
+ },
32
+ };
33
+ }
34
+ /** Narrow one SDK tool entry to our internal descriptor at the single boundary. */
35
+ function toToolDescriptor(t) {
36
+ return t;
37
+ }
38
+ /** Narrow one SDK content block to our internal union at the single boundary. */
39
+ function toContentBlock(block) {
40
+ return block;
41
+ }
42
+ async function createTransport(server) {
43
+ const kind = server.kind ?? 'stdio';
44
+ if (kind === 'stdio') {
45
+ const stdioServer = server;
46
+ const { StdioClientTransport } = (await import('@modelcontextprotocol/sdk/client/stdio.js'));
47
+ // Set stderr to 'ignore' so spawned subprocesses (mcp-remote, etc.)
48
+ // don't dump their boot logs into the moxxy TUI. The SDK defaults
49
+ // to 'inherit' which clobbers the chat with proxy-status lines on
50
+ // every boot. Set MOXXY_MCP_STDERR=inherit to opt back in for
51
+ // debugging.
52
+ const stderrMode = process.env.MOXXY_MCP_STDERR === 'inherit' ? 'inherit' : 'ignore';
53
+ return new StdioClientTransport({
54
+ command: stdioServer.command,
55
+ args: stdioServer.args ? [...stdioServer.args] : undefined,
56
+ env: stdioServer.env,
57
+ cwd: stdioServer.cwd,
58
+ stderr: stderrMode,
59
+ });
60
+ }
61
+ const httpish = server;
62
+ if (kind === 'sse') {
63
+ const { SSEClientTransport } = (await import('@modelcontextprotocol/sdk/client/sse.js'));
64
+ return new SSEClientTransport(new URL(httpish.url), {
65
+ requestInit: httpish.headers ? { headers: httpish.headers } : undefined,
66
+ });
67
+ }
68
+ const { StreamableHTTPClientTransport } = (await import('@modelcontextprotocol/sdk/client/streamableHttp.js'));
69
+ return new StreamableHTTPClientTransport(new URL(httpish.url), {
70
+ requestInit: httpish.headers ? { headers: httpish.headers } : undefined,
71
+ });
72
+ }
73
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import { type Plugin } from '@moxxy/sdk';
2
+ import type { McpClientLike, McpPluginOptions, McpServerConfig } from './types.js';
3
+ export type { McpClientLike, McpContentBlock, McpPluginOptions, McpServerConfig, McpToolDescriptor, SseServerConfig, StdioServerConfig, StreamableHttpServerConfig, } from './types.js';
4
+ export { wrapMcpServerTools } from './wrap.js';
5
+ export { defaultToolNamePrefix } from './types.js';
6
+ export { defaultClientFactory } from './client.js';
7
+ export { buildMcpAdminPlugin, buildMcpAdminPluginWithApi, mcpAdminPlugin, mcpConfigPath, readMcpConfig, removeServerFromConfig, setServerDisabled, writeMcpConfig, resolveServerSecrets, type McpSecretResolver, type McpAdminApi, type McpServerStatus, type McpStoredConfig, type McpStoredServer, } from './admin.js';
8
+ export interface CreateMcpPluginOptions extends McpPluginOptions {
9
+ /**
10
+ * Inject a custom client factory. Used by tests; production code uses the
11
+ * default factory that imports `@modelcontextprotocol/sdk`.
12
+ */
13
+ readonly clientFactory?: (server: McpServerConfig, options: McpPluginOptions) => Promise<McpClientLike>;
14
+ }
15
+ export declare function createMcpPlugin(opts: CreateMcpPluginOptions): Promise<Plugin>;
16
+ export { mcpAdminPlugin as default } from './admin.js';
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAKnF,YAAY,EACV,aAAa,EACb,eAAe,EACf,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,0BAA0B,GAC3B,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EACL,mBAAmB,EACnB,0BAA0B,EAC1B,cAAc,EACd,aAAa,EACb,aAAa,EACb,sBAAsB,EACtB,iBAAiB,EACjB,cAAc,EACd,oBAAoB,EACpB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,eAAe,GACrB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,sBAAuB,SAAQ,gBAAgB;IAC9D;;;OAGG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,gBAAgB,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;CACzG;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,CAmDnF;AAKD,OAAO,EAAE,cAAc,IAAI,OAAO,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,50 @@
1
+ import { definePlugin } from '@moxxy/sdk';
2
+ import { defaultClientFactory } from './client.js';
3
+ import { MCP_CONNECT_TIMEOUT_MS, withTimeout } from './timeout.js';
4
+ import { wrapMcpServerTools } from './wrap.js';
5
+ export { wrapMcpServerTools } from './wrap.js';
6
+ export { defaultToolNamePrefix } from './types.js';
7
+ export { defaultClientFactory } from './client.js';
8
+ export { buildMcpAdminPlugin, buildMcpAdminPluginWithApi, mcpAdminPlugin, mcpConfigPath, readMcpConfig, removeServerFromConfig, setServerDisabled, writeMcpConfig, resolveServerSecrets, } from './admin.js';
9
+ export async function createMcpPlugin(opts) {
10
+ const factory = opts.clientFactory ?? defaultClientFactory;
11
+ // Connect + list each server in PARALLEL: serial boot paid the sum of every
12
+ // server's spawn/handshake/listTools round-trip, so N servers serialized N
13
+ // latencies. allSettled bounds boot at the slowest server, not the sum,
14
+ // while still letting us close every successfully-opened client if any leg
15
+ // fails (the onShutdown hook is only wired once we RETURN the plugin).
16
+ const opened = [];
17
+ const results = await Promise.allSettled(opts.servers.map(async (server) => {
18
+ // Bound the connect handshake so a single wedged server spawn surfaces
19
+ // as a rejected leg rather than stalling boot until the transport's own
20
+ // (possibly absent) timeout fires. The leg's client, if it opened before
21
+ // the timeout, is still tracked in `opened` and closed in the failure
22
+ // cleanup below.
23
+ const client = await withTimeout(factory(server, opts), MCP_CONNECT_TIMEOUT_MS, `MCP connect "${server.name}"`);
24
+ // Record the client the instant it opens, before listTools — so even a
25
+ // listTools failure can't leak its child process / socket.
26
+ opened.push(client);
27
+ return withTimeout(wrapMcpServerTools({ server, client, toolNamePrefix: opts.toolNamePrefix }), MCP_CONNECT_TIMEOUT_MS, `MCP listTools "${server.name}"`);
28
+ }));
29
+ const failure = results.find((r) => r.status === 'rejected');
30
+ if (failure) {
31
+ await Promise.allSettled(opened.map((c) => c.close()));
32
+ throw failure.reason;
33
+ }
34
+ // Flatten in server order (map preserves index → deterministic tool order).
35
+ const tools = results.flatMap((r) => r.value);
36
+ const clients = opened;
37
+ return definePlugin({
38
+ name: '@moxxy/plugin-mcp',
39
+ version: '0.0.0',
40
+ tools,
41
+ hooks: {
42
+ onShutdown: async () => {
43
+ await Promise.allSettled(clients.map((c) => c.close()));
44
+ },
45
+ },
46
+ });
47
+ }
48
+ // Discovery entry: `createPluginLoader` requires a default Plugin export.
49
+ export { mcpAdminPlugin as default } from './admin.js';
50
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAe,MAAM,YAAY,CAAC;AAEvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,sBAAsB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAY/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EACL,mBAAmB,EACnB,0BAA0B,EAC1B,cAAc,EACd,aAAa,EACb,aAAa,EACb,sBAAsB,EACtB,iBAAiB,EACjB,cAAc,EACd,oBAAoB,GAMrB,MAAM,YAAY,CAAC;AAUpB,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAA4B;IAChE,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,IAAI,oBAAoB,CAAC;IAC3D,4EAA4E;IAC5E,2EAA2E;IAC3E,wEAAwE;IACxE,2EAA2E;IAC3E,uEAAuE;IACvE,MAAM,MAAM,GAAoB,EAAE,CAAC;IACnC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CACtC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;QAChC,uEAAuE;QACvE,wEAAwE;QACxE,yEAAyE;QACzE,sEAAsE;QACtE,iBAAiB;QACjB,MAAM,MAAM,GAAG,MAAM,WAAW,CAC9B,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,EACrB,sBAAsB,EACtB,gBAAgB,MAAM,CAAC,IAAI,GAAG,CAC/B,CAAC;QACF,uEAAuE;QACvE,2DAA2D;QAC3D,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACpB,OAAO,WAAW,CAChB,kBAAkB,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,CAAC,EAC3E,sBAAsB,EACtB,kBAAkB,MAAM,CAAC,IAAI,GAAG,CACjC,CAAC;IACJ,CAAC,CAAC,CACH,CAAC;IAEF,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC;IAC7D,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QACvD,MAAO,OAAiC,CAAC,MAAM,CAAC;IAClD,CAAC;IAED,4EAA4E;IAC5E,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAE,CAA4E,CAAC,KAAK,CAAC,CAAC;IAC1H,MAAM,OAAO,GAAG,MAAM,CAAC;IAEvB,OAAO,YAAY,CAAC;QAClB,IAAI,EAAE,mBAAmB;QACzB,OAAO,EAAE,OAAO;QAChB,KAAK;QACL,KAAK,EAAE;YACL,UAAU,EAAE,KAAK,IAAI,EAAE;gBACrB,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;YAC1D,CAAC;SACF;KACF,CAAC,CAAC;AACL,CAAC;AAID,0EAA0E;AAC1E,OAAO,EAAE,cAAc,IAAI,OAAO,EAAE,MAAM,YAAY,CAAC"}