@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.
- package/LICENSE +21 -0
- package/dist/admin/config-io.d.ts +46 -0
- package/dist/admin/config-io.d.ts.map +1 -0
- package/dist/admin/config-io.js +108 -0
- package/dist/admin/config-io.js.map +1 -0
- package/dist/admin/config-schema.d.ts +65 -0
- package/dist/admin/config-schema.d.ts.map +1 -0
- package/dist/admin/config-schema.js +36 -0
- package/dist/admin/config-schema.js.map +1 -0
- package/dist/admin/index.d.ts +21 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +189 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/runtime.d.ts +25 -0
- package/dist/admin/runtime.d.ts.map +1 -0
- package/dist/admin/runtime.js +196 -0
- package/dist/admin/runtime.js.map +1 -0
- package/dist/admin/schema.d.ts +37 -0
- package/dist/admin/schema.d.ts.map +1 -0
- package/dist/admin/schema.js +97 -0
- package/dist/admin/schema.js.map +1 -0
- package/dist/admin/secrets.d.ts +24 -0
- package/dist/admin/secrets.d.ts.map +1 -0
- package/dist/admin/secrets.js +35 -0
- package/dist/admin/secrets.js.map +1 -0
- package/dist/admin/skill.d.ts +18 -0
- package/dist/admin/skill.d.ts.map +1 -0
- package/dist/admin/skill.js +70 -0
- package/dist/admin/skill.js.map +1 -0
- package/dist/admin/tools/add.d.ts +19 -0
- package/dist/admin/tools/add.d.ts.map +1 -0
- package/dist/admin/tools/add.js +97 -0
- package/dist/admin/tools/add.js.map +1 -0
- package/dist/admin/tools/list.d.ts +3 -0
- package/dist/admin/tools/list.d.ts.map +1 -0
- package/dist/admin/tools/list.js +16 -0
- package/dist/admin/tools/list.js.map +1 -0
- package/dist/admin/tools/remove.d.ts +6 -0
- package/dist/admin/tools/remove.d.ts.map +1 -0
- package/dist/admin/tools/remove.js +41 -0
- package/dist/admin/tools/remove.js.map +1 -0
- package/dist/admin/tools/test.d.ts +8 -0
- package/dist/admin/tools/test.d.ts.map +1 -0
- package/dist/admin/tools/test.js +73 -0
- package/dist/admin/tools/test.js.map +1 -0
- package/dist/admin/types.d.ts +107 -0
- package/dist/admin/types.d.ts.map +1 -0
- package/dist/admin/types.js +2 -0
- package/dist/admin/types.js.map +1 -0
- package/dist/admin.d.ts +7 -0
- package/dist/admin.d.ts.map +1 -0
- package/dist/admin.js +7 -0
- package/dist/admin.js.map +1 -0
- package/dist/client.d.ts +10 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +73 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +50 -0
- package/dist/index.js.map +1 -0
- package/dist/timeout.d.ts +23 -0
- package/dist/timeout.d.ts.map +1 -0
- package/dist/timeout.js +35 -0
- package/dist/timeout.js.map +1 -0
- package/dist/types.d.ts +59 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -0
- package/dist/wrap.d.ts +23 -0
- package/dist/wrap.d.ts.map +1 -0
- package/dist/wrap.js +193 -0
- package/dist/wrap.js.map +1 -0
- package/package.json +63 -0
- package/src/admin/config-io.test.ts +188 -0
- package/src/admin/config-io.ts +112 -0
- package/src/admin/config-schema.ts +39 -0
- package/src/admin/index.ts +225 -0
- package/src/admin/runtime.test.ts +470 -0
- package/src/admin/runtime.ts +244 -0
- package/src/admin/schema.test.ts +70 -0
- package/src/admin/schema.ts +110 -0
- package/src/admin/secrets.test.ts +60 -0
- package/src/admin/secrets.ts +55 -0
- package/src/admin/skill.test.ts +143 -0
- package/src/admin/skill.ts +83 -0
- package/src/admin/tools/add.test.ts +193 -0
- package/src/admin/tools/add.ts +118 -0
- package/src/admin/tools/list.ts +19 -0
- package/src/admin/tools/remove.test.ts +74 -0
- package/src/admin/tools/remove.ts +46 -0
- package/src/admin/tools/test.test.ts +123 -0
- package/src/admin/tools/test.ts +84 -0
- package/src/admin/types.ts +112 -0
- package/src/admin.ts +25 -0
- package/src/client.ts +120 -0
- package/src/discovery.test.ts +45 -0
- package/src/index.test.ts +95 -0
- package/src/index.ts +101 -0
- package/src/timeout.ts +37 -0
- package/src/types.ts +57 -0
- package/src/wrap.test.ts +261 -0
- 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":"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"}
|
|
@@ -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"}
|