@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,196 @@
1
+ import { MoxxyError } from '@moxxy/sdk';
2
+ import { defaultClientFactory } from '../client.js';
3
+ import { MCP_CONNECT_TIMEOUT_MS, withTimeout } from '../timeout.js';
4
+ import { wrapMcpServerToolsLazy } from '../wrap.js';
5
+ import { mutateMcpConfig } from './config-io.js';
6
+ import { resolveServerSecrets } from './secrets.js';
7
+ export function createMcpRuntime(registry, options = {}) {
8
+ const secretResolver = options.secretResolver ?? null;
9
+ /** Connect with secrets resolved; `server` itself stays placeholder-form. */
10
+ const connect = async (server) => defaultClientFactory(await resolveServerSecrets(server, secretResolver));
11
+ // Track hot-attached runtimes keyed by server name. We need to know
12
+ // which tools each server contributed so `mcp_remove_server` can
13
+ // unregister them cleanly, and which client to close on shutdown.
14
+ const runtimes = new Map();
15
+ /**
16
+ * Eager attach used by `mcp_add_server`: connect, list tools, register
17
+ * them. Returns the discovered descriptors so the caller can cache
18
+ * them into mcp.json for lazy boots next time.
19
+ */
20
+ const attachServer = async (server) => {
21
+ // Bound the connect + listTools handshake. `mcp_add_server` and
22
+ // `enableAndAttach` drive this path at the model's request; a wedged
23
+ // server spawn (or a connected-but-mute endpoint that never answers
24
+ // listTools) would otherwise hang the tool call indefinitely — a
25
+ // permanent pending dot with no recovery short of killing moxxy. On
26
+ // timeout we reject AND close any client that opened, so a slow-to-spawn
27
+ // stdio child / socket can't leak after we've given up on it.
28
+ const client = await withTimeout(connect(server), MCP_CONNECT_TIMEOUT_MS, `MCP connect "${server.name}"`);
29
+ let list;
30
+ try {
31
+ list = await withTimeout(client.listTools(), MCP_CONNECT_TIMEOUT_MS, `MCP listTools "${server.name}"`);
32
+ }
33
+ catch (err) {
34
+ await client.close().catch(() => { });
35
+ throw err;
36
+ }
37
+ const descriptors = list.tools;
38
+ // List ONCE and feed the descriptors into the lazy wrapper over the
39
+ // already-open client — wrapMcpServerTools would call listTools a second
40
+ // time, paying a redundant round-trip and risking descriptor/tool drift
41
+ // for servers whose tool list is non-deterministic between calls.
42
+ const wrapped = wrapMcpServerToolsLazy({
43
+ server,
44
+ descriptors,
45
+ getClient: () => Promise.resolve(client),
46
+ });
47
+ if (!registry) {
48
+ await client.close();
49
+ return { toolNames: wrapped.map((t) => t.name), descriptors };
50
+ }
51
+ const collisions = wrapped.filter((t) => registry.has(t.name)).map((t) => t.name);
52
+ if (collisions.length > 0) {
53
+ await client.close();
54
+ throw new MoxxyError({
55
+ code: 'CONFIG_INVALID',
56
+ message: `mcp_add_server: tool name collision — already registered: ${collisions.join(', ')}. ` +
57
+ 'Pick a different server name (the server name becomes a prefix on each tool).',
58
+ });
59
+ }
60
+ for (const tool of wrapped)
61
+ registry.register(tool);
62
+ runtimes.set(server.name, { client, toolNames: wrapped.map((t) => t.name) });
63
+ return { toolNames: wrapped.map((t) => t.name), descriptors };
64
+ };
65
+ /**
66
+ * Lazy attach used at boot: register stub tools using cached
67
+ * descriptors WITHOUT connecting. The first call to any of these
68
+ * tools triggers a single shared connection via `getOrConnect`;
69
+ * subsequent calls reuse it. Failed connections reset so the next
70
+ * call can retry.
71
+ *
72
+ * When `cachedTools` is missing (catalog entry predates the cache
73
+ * feature or was edited by hand), the caller is responsible for
74
+ * refreshing the cache first via `refreshServerCache`.
75
+ */
76
+ const attachServerLazy = (server) => {
77
+ if (!registry)
78
+ return { toolNames: [] };
79
+ if (runtimes.has(server.name))
80
+ return { toolNames: runtimes.get(server.name).toolNames };
81
+ const descriptors = server.cachedTools ?? [];
82
+ if (descriptors.length === 0) {
83
+ return { toolNames: [] };
84
+ }
85
+ let connectPromise = null;
86
+ const getOrConnect = async () => {
87
+ if (!connectPromise) {
88
+ connectPromise = (async () => {
89
+ // Bound the first-call connect so a wedged transport can't hang the
90
+ // triggering tool call forever (the per-call timeout in wrap.ts only
91
+ // covers callTool, not this connect handshake).
92
+ const client = await withTimeout(connect(server), MCP_CONNECT_TIMEOUT_MS, `MCP connect "${server.name}"`);
93
+ // Stash the live client on the runtime entry so shutdown can
94
+ // close it. The entry was created with a sentinel; replace it.
95
+ const runtime = runtimes.get(server.name);
96
+ if (runtime) {
97
+ runtimes.set(server.name, { client, toolNames: runtime.toolNames });
98
+ return client;
99
+ }
100
+ // detachServer ran while this connect was in flight: the runtime
101
+ // entry (and its shutdown close path) is gone. Close the freshly
102
+ // opened client here so its stdio child / socket isn't orphaned,
103
+ // and fail the call rather than handing back a leaked handle.
104
+ await client.close().catch(() => { });
105
+ throw new Error(`MCP server "${server.name}" detached during connect`);
106
+ })().catch((err) => {
107
+ // Reset so a future call can retry instead of being stuck on
108
+ // a rejected promise.
109
+ connectPromise = null;
110
+ throw err;
111
+ });
112
+ }
113
+ return connectPromise;
114
+ };
115
+ const wrapped = wrapMcpServerToolsLazy({ server, descriptors, getClient: getOrConnect });
116
+ const collisions = wrapped.filter((t) => registry.has(t.name)).map((t) => t.name);
117
+ if (collisions.length > 0) {
118
+ throw new MoxxyError({
119
+ code: 'CONFIG_INVALID',
120
+ message: `lazy attach: tool name collision for "${server.name}": ${collisions.join(', ')}. ` +
121
+ 'A different server (or a previously-attached version) already owns these names.',
122
+ });
123
+ }
124
+ for (const tool of wrapped)
125
+ registry.register(tool);
126
+ // Sentinel client gets swapped for the real one inside getOrConnect.
127
+ // Until first call, close() is a no-op via the LazyClient sentinel.
128
+ const lazyClient = {
129
+ listTools: async () => ({ tools: descriptors }),
130
+ callTool: async (args) => (await getOrConnect()).callTool(args),
131
+ close: async () => {
132
+ if (connectPromise) {
133
+ const client = await connectPromise.catch(() => null);
134
+ if (client)
135
+ await client.close();
136
+ }
137
+ },
138
+ };
139
+ runtimes.set(server.name, { client: lazyClient, toolNames: wrapped.map((t) => t.name) });
140
+ return { toolNames: wrapped.map((t) => t.name) };
141
+ };
142
+ /**
143
+ * Connect to a server, list its tools, and write the descriptors back
144
+ * to mcp.json. Used to refresh stale or missing caches at boot. The
145
+ * connection is closed immediately — registration happens via the
146
+ * caller's subsequent `attachServerLazy` call.
147
+ */
148
+ const refreshServerCache = async (server) => {
149
+ // Bound the connect handshake: a dead endpoint or a stdio child that
150
+ // spawns but never completes the MCP handshake would otherwise hang here,
151
+ // and at boot core awaits onInit serially — one stale entry would make
152
+ // moxxy unstartable. On timeout we reject (the onInit catch logs+skips it).
153
+ const client = await withTimeout(connect(server), MCP_CONNECT_TIMEOUT_MS, `MCP connect "${server.name}"`);
154
+ try {
155
+ // Bound listTools too — a connected-but-unresponsive server can stall
156
+ // discovery just as badly as a dead connect.
157
+ const list = await withTimeout(client.listTools(), MCP_CONNECT_TIMEOUT_MS, `MCP listTools "${server.name}"`);
158
+ const refreshed = { ...server, cachedTools: list.tools };
159
+ // Persist the refreshed cache so subsequent boots can lazy-attach
160
+ // without reconnecting. Read-modify-write under the shared config
161
+ // mutex so a concurrent add/remove can't clobber the file.
162
+ await mutateMcpConfig((cfg) => ({
163
+ next: { servers: cfg.servers.map((s) => (s.name === server.name ? refreshed : s)) },
164
+ result: undefined,
165
+ }));
166
+ return refreshed;
167
+ }
168
+ finally {
169
+ try {
170
+ await client.close();
171
+ }
172
+ catch {
173
+ /* ignore */
174
+ }
175
+ }
176
+ };
177
+ const detachServer = async (name) => {
178
+ const runtime = runtimes.get(name);
179
+ if (!runtime)
180
+ return false;
181
+ runtimes.delete(name);
182
+ if (registry) {
183
+ for (const toolName of runtime.toolNames)
184
+ registry.unregister(toolName);
185
+ }
186
+ try {
187
+ await runtime.client.close();
188
+ }
189
+ catch {
190
+ // ignore — best-effort close
191
+ }
192
+ return true;
193
+ };
194
+ return { runtimes, attachServer, attachServerLazy, refreshServerCache, detachServer };
195
+ }
196
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/admin/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,sBAAsB,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACpE,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,oBAAoB,EAA0B,MAAM,cAAc,CAAC;AA2B5E,MAAM,UAAU,gBAAgB,CAC9B,QAAsC,EACtC,UAA6B,EAAE;IAE/B,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,IAAI,CAAC;IACtD,6EAA6E;IAC7E,MAAM,OAAO,GAAG,KAAK,EAAE,MAAuB,EAA0B,EAAE,CACxE,oBAAoB,CAAC,MAAM,oBAAoB,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC;IAC3E,oEAAoE;IACpE,iEAAiE;IACjE,kEAAkE;IAClE,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA4B,CAAC;IAErD;;;;OAIG;IACH,MAAM,YAAY,GAA+B,KAAK,EAAE,MAAM,EAAE,EAAE;QAChE,gEAAgE;QAChE,qEAAqE;QACrE,oEAAoE;QACpE,iEAAiE;QACjE,oEAAoE;QACpE,yEAAyE;QACzE,8DAA8D;QAC9D,MAAM,MAAM,GAAG,MAAM,WAAW,CAC9B,OAAO,CAAC,MAAM,CAAC,EACf,sBAAsB,EACtB,gBAAgB,MAAM,CAAC,IAAI,GAAG,CAC/B,CAAC;QACF,IAAI,IAAiD,CAAC;QACtD,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,WAAW,CACtB,MAAM,CAAC,SAAS,EAAE,EAClB,sBAAsB,EACtB,kBAAkB,MAAM,CAAC,IAAI,GAAG,CACjC,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YACrC,MAAM,GAAG,CAAC;QACZ,CAAC;QACD,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC;QAC/B,oEAAoE;QACpE,yEAAyE;QACzE,wEAAwE;QACxE,kEAAkE;QAClE,MAAM,OAAO,GAAG,sBAAsB,CAAC;YACrC,MAAM;YACN,WAAW;YACX,SAAS,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC;SACzC,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;YACrB,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,CAAC;QAChE,CAAC;QACD,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAClF,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1B,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;YACrB,MAAM,IAAI,UAAU,CAAC;gBACnB,IAAI,EAAE,gBAAgB;gBACtB,OAAO,EACL,6DAA6D,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;oBACtF,+EAA+E;aAClF,CAAC,CAAC;QACL,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,OAAO;YAAE,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACpD,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC7E,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,CAAC;IAChE,CAAC,CAAC;IAEF;;;;;;;;;;OAUG;IACH,MAAM,gBAAgB,GAAmC,CAAC,MAAM,EAAE,EAAE;QAClE,IAAI,CAAC,QAAQ;YAAE,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;QACxC,IAAI,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAE,CAAC,SAAS,EAAE,CAAC;QAC1F,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,IAAI,EAAE,CAAC;QAC7C,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC7B,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;QAC3B,CAAC;QAED,IAAI,cAAc,GAAkC,IAAI,CAAC;QACzD,MAAM,YAAY,GAAG,KAAK,IAA4B,EAAE;YACtD,IAAI,CAAC,cAAc,EAAE,CAAC;gBACpB,cAAc,GAAG,CAAC,KAAK,IAAI,EAAE;oBAC3B,oEAAoE;oBACpE,qEAAqE;oBACrE,gDAAgD;oBAChD,MAAM,MAAM,GAAG,MAAM,WAAW,CAC9B,OAAO,CAAC,MAAM,CAAC,EACf,sBAAsB,EACtB,gBAAgB,MAAM,CAAC,IAAI,GAAG,CAC/B,CAAC;oBACF,6DAA6D;oBAC7D,+DAA+D;oBAC/D,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;oBAC1C,IAAI,OAAO,EAAE,CAAC;wBACZ,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;wBACpE,OAAO,MAAM,CAAC;oBAChB,CAAC;oBACD,iEAAiE;oBACjE,iEAAiE;oBACjE,iEAAiE;oBACjE,8DAA8D;oBAC9D,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;oBACrC,MAAM,IAAI,KAAK,CAAC,eAAe,MAAM,CAAC,IAAI,2BAA2B,CAAC,CAAC;gBACzE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;oBACjB,6DAA6D;oBAC7D,sBAAsB;oBACtB,cAAc,GAAG,IAAI,CAAC;oBACtB,MAAM,GAAG,CAAC;gBACZ,CAAC,CAAC,CAAC;YACL,CAAC;YACD,OAAO,cAAc,CAAC;QACxB,CAAC,CAAC;QAEF,MAAM,OAAO,GAAG,sBAAsB,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC,CAAC;QACzF,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAClF,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,UAAU,CAAC;gBACnB,IAAI,EAAE,gBAAgB;gBACtB,OAAO,EACL,yCAAyC,MAAM,CAAC,IAAI,MAAM,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;oBACnF,iFAAiF;aACpF,CAAC,CAAC;QACL,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,OAAO;YAAE,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACpD,qEAAqE;QACrE,oEAAoE;QACpE,MAAM,UAAU,GAAkB;YAChC,SAAS,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;YAC/C,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,MAAM,YAAY,EAAE,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;YAC/D,KAAK,EAAE,KAAK,IAAI,EAAE;gBAChB,IAAI,cAAc,EAAE,CAAC;oBACnB,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;oBACtD,IAAI,MAAM;wBAAE,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;gBACnC,CAAC;YACH,CAAC;SACF,CAAC;QACF,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACzF,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;IACnD,CAAC,CAAC;IAEF;;;;;OAKG;IACH,MAAM,kBAAkB,GAAqC,KAAK,EAAE,MAAM,EAAE,EAAE;QAC5E,qEAAqE;QACrE,0EAA0E;QAC1E,uEAAuE;QACvE,4EAA4E;QAC5E,MAAM,MAAM,GAAG,MAAM,WAAW,CAC9B,OAAO,CAAC,MAAM,CAAC,EACf,sBAAsB,EACtB,gBAAgB,MAAM,CAAC,IAAI,GAAG,CAC/B,CAAC;QACF,IAAI,CAAC;YACH,sEAAsE;YACtE,6CAA6C;YAC7C,MAAM,IAAI,GAAG,MAAM,WAAW,CAC5B,MAAM,CAAC,SAAS,EAAE,EAClB,sBAAsB,EACtB,kBAAkB,MAAM,CAAC,IAAI,GAAG,CACjC,CAAC;YACF,MAAM,SAAS,GAAoB,EAAE,GAAG,MAAM,EAAE,WAAW,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC;YAC1E,kEAAkE;YAClE,kEAAkE;YAClE,2DAA2D;YAC3D,MAAM,eAAe,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;gBAC9B,IAAI,EAAE,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE;gBACnF,MAAM,EAAE,SAAS;aAClB,CAAC,CAAC,CAAC;YACJ,OAAO,SAAS,CAAC;QACnB,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC;gBACH,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;YACvB,CAAC;YAAC,MAAM,CAAC;gBACP,YAAY;YACd,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,YAAY,GAA+B,KAAK,EAAE,IAAI,EAAE,EAAE;QAC9D,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;QAC3B,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACtB,IAAI,QAAQ,EAAE,CAAC;YACb,KAAK,MAAM,QAAQ,IAAI,OAAO,CAAC,SAAS;gBAAE,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;QAC1E,CAAC;QACD,IAAI,CAAC;YACH,MAAM,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACP,6BAA6B;QAC/B,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;IAEF,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,YAAY,EAAE,CAAC;AACxF,CAAC"}
@@ -0,0 +1,37 @@
1
+ import { z } from '@moxxy/sdk';
2
+ import type { McpServerConfig } from '../types.js';
3
+ export declare const serverNameSchema: z.ZodString;
4
+ export declare const addServerInput: z.ZodObject<{
5
+ kind: z.ZodEnum<["stdio", "http", "sse"]>;
6
+ name: z.ZodString;
7
+ command: z.ZodOptional<z.ZodString>;
8
+ args: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
9
+ env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
10
+ cwd: z.ZodOptional<z.ZodString>;
11
+ url: z.ZodOptional<z.ZodString>;
12
+ headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
13
+ autoSkill: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
14
+ }, "strip", z.ZodTypeAny, {
15
+ kind: "stdio" | "sse" | "http";
16
+ name: string;
17
+ autoSkill: boolean;
18
+ headers?: Record<string, string> | undefined;
19
+ command?: string | undefined;
20
+ args?: string[] | undefined;
21
+ env?: Record<string, string> | undefined;
22
+ cwd?: string | undefined;
23
+ url?: string | undefined;
24
+ }, {
25
+ kind: "stdio" | "sse" | "http";
26
+ name: string;
27
+ headers?: Record<string, string> | undefined;
28
+ command?: string | undefined;
29
+ args?: string[] | undefined;
30
+ env?: Record<string, string> | undefined;
31
+ cwd?: string | undefined;
32
+ url?: string | undefined;
33
+ autoSkill?: boolean | undefined;
34
+ }>;
35
+ export type AddServerInput = z.infer<typeof addServerInput>;
36
+ export declare function validateAddServerInput(input: AddServerInput): McpServerConfig;
37
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/admin/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,CAAC,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,eAAO,MAAM,gBAAgB,aAIkE,CAAC;AAQhG,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyDzB,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,cAAc,GAAG,eAAe,CAiC7E"}
@@ -0,0 +1,97 @@
1
+ import { MoxxyError, z } from '@moxxy/sdk';
2
+ export const serverNameSchema = z
3
+ .string()
4
+ .min(1)
5
+ .max(60)
6
+ .regex(/^[a-z0-9][a-z0-9-]*$/, 'name must be slug-like (lowercase letters, digits, hyphens)');
7
+ // Flat schema (no discriminated union) so OpenAI's function-calling
8
+ // validator accepts it. OpenAI rejects top-level oneOf/anyOf with
9
+ // "object schema missing properties"; the model now sees a single
10
+ // object with `kind` + every transport-specific field optional, plus
11
+ // a runtime guard in the handler that enforces the per-kind required
12
+ // set with a readable error.
13
+ export const addServerInput = z.object({
14
+ kind: z.enum(['stdio', 'http', 'sse']).describe('Transport kind. "stdio" runs a local executable; "http" and "sse" connect to a remote URL.'),
15
+ name: serverNameSchema,
16
+ // stdio-only fields
17
+ command: z
18
+ .string()
19
+ .min(1)
20
+ .optional()
21
+ .describe('Required when kind="stdio". Executable to spawn (e.g. "npx", "uv", "python").'),
22
+ args: z
23
+ .array(z.string())
24
+ .optional()
25
+ .describe('Optional when kind="stdio". CLI arguments for the executable.'),
26
+ env: z
27
+ .record(z.string())
28
+ .optional()
29
+ .describe('Optional when kind="stdio". Environment variables for the spawned process. ' +
30
+ 'Secrets MUST be vault references ("${vault:NAME}", stored via vault_set first), ' +
31
+ 'never plaintext — placeholders are resolved at connect time only.'),
32
+ cwd: z
33
+ .string()
34
+ .optional()
35
+ .describe('Optional when kind="stdio". Working directory for the spawned process.'),
36
+ // http/sse-only fields
37
+ url: z
38
+ .string()
39
+ .url()
40
+ // Restrict to http(s): a bare .url() accepts file:, gopher:, ws:, etc.,
41
+ // turning a model-proposed MCP endpoint into an SSRF/file-read surface
42
+ // (e.g. file:///etc/passwd, http://169.254.169.254 metadata). The human
43
+ // permission prompt still gates the add/test, but reject obviously-wrong
44
+ // schemes before any connection is attempted.
45
+ .regex(/^https?:\/\//i, 'url must use the http:// or https:// scheme')
46
+ .optional()
47
+ .describe('Required when kind="http" or "sse". Server URL (http/https only).'),
48
+ headers: z
49
+ .record(z.string())
50
+ .optional()
51
+ .describe('Optional when kind="http" or "sse". HTTP headers (auth, etc). Secrets MUST be ' +
52
+ 'vault references ("${vault:NAME}", stored via vault_set first), never plaintext — ' +
53
+ 'placeholders are resolved at connect time only.'),
54
+ autoSkill: z
55
+ .boolean()
56
+ .optional()
57
+ .default(true)
58
+ .describe('When true (default), auto-write a deterministic usage skill ' +
59
+ '<server-name>-mcp.md into ~/.moxxy/skills/ documenting the ' +
60
+ 'tools the server exposes. Pass false if the user explicitly ' +
61
+ 'asked for no skill.'),
62
+ });
63
+ export function validateAddServerInput(input) {
64
+ // autoSkill is consumed by the handler, not by the connection factory —
65
+ // strip it before constructing the McpServerConfig.
66
+ void input.autoSkill;
67
+ if (input.kind === 'stdio') {
68
+ if (!input.command) {
69
+ throw new MoxxyError({
70
+ code: 'CONFIG_INVALID',
71
+ message: 'mcp_add_server: kind="stdio" requires a `command` field (e.g. "npx", "uv", "python").',
72
+ });
73
+ }
74
+ const out = {
75
+ kind: 'stdio',
76
+ name: input.name,
77
+ command: input.command,
78
+ ...(input.args ? { args: input.args } : {}),
79
+ ...(input.env ? { env: input.env } : {}),
80
+ ...(input.cwd ? { cwd: input.cwd } : {}),
81
+ };
82
+ return out;
83
+ }
84
+ if (!input.url) {
85
+ throw new MoxxyError({
86
+ code: 'CONFIG_INVALID',
87
+ message: `mcp_add_server: kind="${input.kind}" requires a \`url\` field (the remote MCP endpoint).`,
88
+ });
89
+ }
90
+ return {
91
+ kind: input.kind,
92
+ name: input.name,
93
+ url: input.url,
94
+ ...(input.headers ? { headers: input.headers } : {}),
95
+ };
96
+ }
97
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/admin/schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { McpServerConfig } from '../types.js';
2
+ /**
3
+ * Connect-time secret resolution for MCP server credentials.
4
+ *
5
+ * Server entries in ~/.moxxy/mcp.json (and the `mcp_add_server` /
6
+ * `mcp_test_server` tool args) may carry `${vault:NAME}` placeholders in
7
+ * `env` values (stdio) and `headers` values (http/sse). The PLACEHOLDER is
8
+ * what gets persisted; the plaintext secret only ever materializes in the
9
+ * resolved copy handed to the transport at connect time — never in the
10
+ * model's context, never on disk.
11
+ *
12
+ * plugin-mcp deliberately has no vault dependency: setup wires a resolver
13
+ * (the CLI passes the vault's `resolveString`). A resolver MUST pass literal
14
+ * values through unchanged so pre-placeholder configs keep working.
15
+ */
16
+ export type McpSecretResolver = (value: string) => Promise<string>;
17
+ /**
18
+ * Return a copy of `server` with every `env` / `headers` value passed
19
+ * through `resolver`. With no resolver (or nothing to resolve) the original
20
+ * object is returned untouched — callers rely on that to persist the
21
+ * placeholder form, not the resolved one.
22
+ */
23
+ export declare function resolveServerSecrets(server: McpServerConfig, resolver: McpSecretResolver | null | undefined): Promise<McpServerConfig>;
24
+ //# sourceMappingURL=secrets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.d.ts","sourceRoot":"","sources":["../../src/admin/secrets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAEnE;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,eAAe,EACvB,QAAQ,EAAE,iBAAiB,GAAG,IAAI,GAAG,SAAS,GAC7C,OAAO,CAAC,eAAe,CAAC,CAa1B"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Return a copy of `server` with every `env` / `headers` value passed
3
+ * through `resolver`. With no resolver (or nothing to resolve) the original
4
+ * object is returned untouched — callers rely on that to persist the
5
+ * placeholder form, not the resolved one.
6
+ */
7
+ export async function resolveServerSecrets(server, resolver) {
8
+ if (!resolver)
9
+ return server;
10
+ const kind = server.kind ?? 'stdio';
11
+ if (kind === 'http' || kind === 'sse') {
12
+ const httpish = server;
13
+ if (!httpish.headers)
14
+ return server;
15
+ const headers = await resolveRecord(httpish.headers, resolver);
16
+ return headers === httpish.headers ? server : { ...httpish, headers };
17
+ }
18
+ const stdio = server;
19
+ if (!stdio.env)
20
+ return server;
21
+ const env = await resolveRecord(stdio.env, resolver);
22
+ return env === stdio.env ? server : { ...stdio, env };
23
+ }
24
+ async function resolveRecord(rec, resolver) {
25
+ let changed = false;
26
+ const out = {};
27
+ for (const [key, value] of Object.entries(rec)) {
28
+ const resolved = await resolver(value);
29
+ if (resolved !== value)
30
+ changed = true;
31
+ out[key] = resolved;
32
+ }
33
+ return changed ? out : rec;
34
+ }
35
+ //# sourceMappingURL=secrets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/admin/secrets.ts"],"names":[],"mappings":"AAkBA;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,MAAuB,EACvB,QAA8C;IAE9C,IAAI,CAAC,QAAQ;QAAE,OAAO,MAAM,CAAC;IAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,OAAO,CAAC;IACpC,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;QACtC,MAAM,OAAO,GAAG,MAAmD,CAAC;QACpE,IAAI,CAAC,OAAO,CAAC,OAAO;YAAE,OAAO,MAAM,CAAC;QACpC,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QAC/D,OAAO,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,OAAO,EAAE,CAAC;IACxE,CAAC;IACD,MAAM,KAAK,GAAG,MAAuD,CAAC;IACtE,IAAI,CAAC,KAAK,CAAC,GAAG;QAAE,OAAO,MAAM,CAAC;IAC9B,MAAM,GAAG,GAAG,MAAM,aAAa,CAAC,KAAK,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IACrD,OAAO,GAAG,KAAK,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,GAAG,EAAE,CAAC;AACxD,CAAC;AAED,KAAK,UAAU,aAAa,CAC1B,GAA2B,EAC3B,QAA2B;IAE3B,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/C,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,CAAC;QACvC,IAAI,QAAQ,KAAK,KAAK;YAAE,OAAO,GAAG,IAAI,CAAC;QACvC,GAAG,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC;IACtB,CAAC;IACD,OAAO,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { McpServerConfig, McpToolDescriptor } from '../types.js';
2
+ import type { AdminSkillRegistryLike } from './types.js';
3
+ export interface WriteMcpUsageSkillOptions {
4
+ readonly skillRegistry: AdminSkillRegistryLike | null;
5
+ readonly userSkillsDir: string;
6
+ }
7
+ /**
8
+ * Compose a deterministic usage skill from the server's tool list.
9
+ * Cheaper and more reliable than a synthesize_skill model call — the
10
+ * descriptors already carry name + description, so the skill body just
11
+ * lays them out as a bulleted checklist. The user can edit the file
12
+ * later if they want a richer playbook.
13
+ */
14
+ export declare function createMcpUsageSkillWriter(opts: WriteMcpUsageSkillOptions): (server: McpServerConfig, descriptors: ReadonlyArray<McpToolDescriptor>) => Promise<{
15
+ path: string;
16
+ skillName: string;
17
+ } | null>;
18
+ //# sourceMappingURL=skill.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill.d.ts","sourceRoot":"","sources":["../../src/admin/skill.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACtE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEzD,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,aAAa,EAAE,sBAAsB,GAAG,IAAI,CAAC;IACtD,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,yBAAyB,GAC9B,CACD,MAAM,EAAE,eAAe,EACvB,WAAW,EAAE,aAAa,CAAC,iBAAiB,CAAC,KAC1C,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CA2DvD"}
@@ -0,0 +1,70 @@
1
+ import * as path from 'node:path';
2
+ import {} from '@moxxy/sdk';
3
+ import { writeFileAtomic } from '@moxxy/sdk/server';
4
+ /**
5
+ * Compose a deterministic usage skill from the server's tool list.
6
+ * Cheaper and more reliable than a synthesize_skill model call — the
7
+ * descriptors already carry name + description, so the skill body just
8
+ * lays them out as a bulleted checklist. The user can edit the file
9
+ * later if they want a richer playbook.
10
+ */
11
+ export function createMcpUsageSkillWriter(opts) {
12
+ const { skillRegistry, userSkillsDir } = opts;
13
+ return async (server, descriptors) => {
14
+ const skillName = `${server.name}-mcp`;
15
+ if (skillRegistry?.byName(skillName)) {
16
+ // Already exists (likely from a previous attach) — leave it alone
17
+ // so user edits aren't clobbered.
18
+ return null;
19
+ }
20
+ const triggers = [server.name, `${server.name} mcp`, `use ${server.name}`];
21
+ const toolBullets = descriptors
22
+ .map((d) => {
23
+ const wrappedName = `mcp__${server.name}__${d.name}`;
24
+ return `- \`${wrappedName}\` — ${d.description ?? '(no description provided)'}`;
25
+ })
26
+ .join('\n');
27
+ const allowed = descriptors.map((d) => `mcp__${server.name}__${d.name}`);
28
+ const description = `Use the ${server.name} MCP server (${descriptors.length} tools).`.slice(0, 240);
29
+ const frontmatter = `---\n` +
30
+ `name: ${skillName}\n` +
31
+ `description: ${description}\n` +
32
+ `triggers:\n${triggers.map((t) => ` - "${t}"`).join('\n')}\n` +
33
+ `allowed-tools:\n${allowed.map((a) => ` - ${a}`).join('\n')}\n` +
34
+ `---\n`;
35
+ const body = `When the user wants to work with **${server.name}**, use the MCP tools below. Pick the tool that best matches the user's intent; chain multiple if needed.\n\n` +
36
+ `## Available tools\n\n${toolBullets}\n\n` +
37
+ `## Notes\n\n` +
38
+ `- Every tool above is namespaced \`mcp__${server.name}__*\`.\n` +
39
+ `- Auto-generated when the MCP server was registered. Edit this file by hand to refine the playbook.`;
40
+ const raw = `${frontmatter}\n${body}\n`;
41
+ const filePath = path.join(userSkillsDir, `${skillName}.md`);
42
+ // writeFileAtomic creates parent dirs and does a crash-safe tmp+rename.
43
+ await writeFileAtomic(filePath, raw);
44
+ if (skillRegistry) {
45
+ // Build a Skill object that mirrors what discoverSkills would
46
+ // produce so /skills, the system-prompt index, and load_skill all
47
+ // see it immediately.
48
+ const skillObject = {
49
+ id: `user/${skillName}`,
50
+ path: filePath,
51
+ scope: 'user',
52
+ frontmatter: {
53
+ name: skillName,
54
+ description,
55
+ triggers,
56
+ 'allowed-tools': allowed,
57
+ },
58
+ body,
59
+ };
60
+ try {
61
+ skillRegistry.register(skillObject);
62
+ }
63
+ catch {
64
+ // already registered — fine
65
+ }
66
+ }
67
+ return { path: filePath, skillName };
68
+ };
69
+ }
70
+ //# sourceMappingURL=skill.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill.js","sourceRoot":"","sources":["../../src/admin/skill.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { type ToolDef } from '@moxxy/sdk';
2
+ import type { McpServerConfig, McpToolDescriptor } from '../../types.js';
3
+ import type { AdminToolRegistryLike } from '../types.js';
4
+ export interface AddServerToolDeps {
5
+ readonly registry: AdminToolRegistryLike | null;
6
+ attachServer(server: McpServerConfig): Promise<{
7
+ toolNames: ReadonlyArray<string>;
8
+ descriptors: ReadonlyArray<McpToolDescriptor>;
9
+ }>;
10
+ /** Roll back a hot-attach (unregister its tools + close the client) when
11
+ * persisting the new entry fails after `attachServer` already succeeded. */
12
+ detachServer(name: string): Promise<boolean>;
13
+ writeMcpUsageSkill(server: McpServerConfig, descriptors: ReadonlyArray<McpToolDescriptor>): Promise<{
14
+ path: string;
15
+ skillName: string;
16
+ } | null>;
17
+ }
18
+ export declare function buildAddServerTool(deps: AddServerToolDeps): ToolDef;
19
+ //# sourceMappingURL=add.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../../src/admin/tools/add.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,OAAO,EAAE,MAAM,YAAY,CAAC;AAClE,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAGzE,OAAO,KAAK,EACV,qBAAqB,EAEtB,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,QAAQ,EAAE,qBAAqB,GAAG,IAAI,CAAC;IAChD,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC;QAC7C,SAAS,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;QACjC,WAAW,EAAE,aAAa,CAAC,iBAAiB,CAAC,CAAC;KAC/C,CAAC,CAAC;IACH;iFAC6E;IAC7E,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7C,kBAAkB,CAChB,MAAM,EAAE,eAAe,EACvB,WAAW,EAAE,aAAa,CAAC,iBAAiB,CAAC,GAC5C,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;CACxD;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CA6FnE"}