@klhapp/skillmux 1.3.3 → 1.4.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/CHANGELOG.md +32 -0
- package/README.md +64 -23
- package/docs/README.md +42 -17
- package/docs/assets/architecture.svg +42 -44
- package/docs/cli.md +49 -7
- package/docs/concepts.md +42 -19
- package/docs/configuration.md +53 -7
- package/docs/deployment.md +76 -20
- package/docs/getting-started.md +62 -18
- package/docs/mcp-routing.md +11 -4
- package/docs/releasing.md +11 -4
- package/docs/schema.json +1 -2
- package/docs/skill-management.md +14 -9
- package/docs/troubleshooting.md +85 -5
- package/package.json +1 -1
- package/src/cli.ts +61 -6
- package/src/commands/config.ts +2 -0
- package/src/config-service.ts +8 -0
- package/src/config-watcher.ts +11 -8
- package/src/deployment.ts +39 -0
- package/src/doctor.ts +47 -18
- package/src/metrics.ts +18 -0
- package/src/output.ts +5 -1
- package/src/router-core.ts +6 -1
- package/src/server.ts +60 -42
package/src/server.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import { createClients } from "./clients";
|
|
7
7
|
import { loadConfig, resolveConfigPath } from "./config";
|
|
8
|
+
import { describeDeployment } from "./deployment";
|
|
8
9
|
import { ConfigWatcher, type ReloadStatus } from "./config-watcher";
|
|
9
10
|
import { RuntimeSnapshotManager } from "./snapshot";
|
|
10
11
|
import {
|
|
@@ -64,46 +65,7 @@ function safeTokenEquals(a: string, b: string): boolean {
|
|
|
64
65
|
return timingSafeEqual(bufA, bufB);
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
export
|
|
68
|
-
transport?: "stdio" | "http";
|
|
69
|
-
port?: number;
|
|
70
|
-
config?: Config;
|
|
71
|
-
clients?: Partial<Clients>;
|
|
72
|
-
configPath?: string;
|
|
73
|
-
}): Promise<ServerHandle> {
|
|
74
|
-
const configPath = resolveConfigPath(opts?.configPath);
|
|
75
|
-
const config = opts?.config ?? (await loadConfig(configPath));
|
|
76
|
-
const initialClients = { ...createClients(config), ...opts?.clients };
|
|
77
|
-
const snapshots = RuntimeSnapshotManager.create(config, initialClients);
|
|
78
|
-
const inactiveReloadStatus: ReloadStatus = {
|
|
79
|
-
last_successful_reload_at: null,
|
|
80
|
-
last_reload_error: null,
|
|
81
|
-
restart_required_keys: [],
|
|
82
|
-
};
|
|
83
|
-
configure({ config, clients: initialClients });
|
|
84
|
-
// An injected config has no guaranteed file source. Watch it only when the
|
|
85
|
-
// caller explicitly supplies that source; normal server startup always watches.
|
|
86
|
-
const watcherPath =
|
|
87
|
-
opts?.configPath ?? (opts?.config ? undefined : configPath);
|
|
88
|
-
const configWatcher = watcherPath
|
|
89
|
-
? await ConfigWatcher.start(watcherPath, {
|
|
90
|
-
onReload: (nextConfig) => {
|
|
91
|
-
const nextClients = {
|
|
92
|
-
...createClients(nextConfig),
|
|
93
|
-
...opts?.clients,
|
|
94
|
-
};
|
|
95
|
-
snapshots.replace(nextConfig, nextClients);
|
|
96
|
-
configure({ config: nextConfig, clients: nextClients });
|
|
97
|
-
},
|
|
98
|
-
onError: (error) =>
|
|
99
|
-
console.error("skillmux config reload error:", error),
|
|
100
|
-
})
|
|
101
|
-
: undefined;
|
|
102
|
-
const stopWatcher = await startVaultWatcher();
|
|
103
|
-
const initPromise = initializeRuntime(readinessState)
|
|
104
|
-
.then(() => metricsRegistry.setReadiness(readinessState.get()))
|
|
105
|
-
.catch((err) => console.error("skillmux runtime init error:", err));
|
|
106
|
-
|
|
68
|
+
export function createMcpServer(): McpServer {
|
|
107
69
|
const server = new McpServer({ name: "skillmux", version: "0.1.0" });
|
|
108
70
|
|
|
109
71
|
// Transport rule from schema.json: the SKILL.md body appears exactly once on
|
|
@@ -166,6 +128,52 @@ export async function startServer(opts?: {
|
|
|
166
128
|
},
|
|
167
129
|
);
|
|
168
130
|
|
|
131
|
+
return server;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function startServer(opts?: {
|
|
135
|
+
transport?: "stdio" | "http";
|
|
136
|
+
port?: number;
|
|
137
|
+
config?: Config;
|
|
138
|
+
clients?: Partial<Clients>;
|
|
139
|
+
configPath?: string;
|
|
140
|
+
}): Promise<ServerHandle> {
|
|
141
|
+
const configPath = resolveConfigPath(opts?.configPath);
|
|
142
|
+
const config = opts?.config ?? (await loadConfig(configPath));
|
|
143
|
+
const initialClients = { ...createClients(config), ...opts?.clients };
|
|
144
|
+
const snapshots = RuntimeSnapshotManager.create(config, initialClients);
|
|
145
|
+
const inactiveReloadStatus: ReloadStatus = {
|
|
146
|
+
last_successful_reload_at: null,
|
|
147
|
+
last_reload_error: null,
|
|
148
|
+
restart_required_keys: [],
|
|
149
|
+
};
|
|
150
|
+
configure({ config, clients: initialClients });
|
|
151
|
+
metricsRegistry.setDeployment(describeDeployment(config));
|
|
152
|
+
// An injected config has no guaranteed file source. Watch it only when the
|
|
153
|
+
// caller explicitly supplies that source; normal server startup always watches.
|
|
154
|
+
const watcherPath =
|
|
155
|
+
opts?.configPath ?? (opts?.config ? undefined : configPath);
|
|
156
|
+
const configWatcher = watcherPath
|
|
157
|
+
? await ConfigWatcher.start(watcherPath, {
|
|
158
|
+
onReload: (nextConfig) => {
|
|
159
|
+
const nextClients = {
|
|
160
|
+
...createClients(nextConfig),
|
|
161
|
+
...opts?.clients,
|
|
162
|
+
};
|
|
163
|
+
snapshots.replace(nextConfig, nextClients);
|
|
164
|
+
configure({ config: nextConfig, clients: nextClients });
|
|
165
|
+
},
|
|
166
|
+
onError: (error) =>
|
|
167
|
+
console.error("skillmux config reload error:", error),
|
|
168
|
+
})
|
|
169
|
+
: undefined;
|
|
170
|
+
const stopWatcher = await startVaultWatcher();
|
|
171
|
+
const initPromise = initializeRuntime(readinessState)
|
|
172
|
+
.then(() => metricsRegistry.setReadiness(readinessState.get()))
|
|
173
|
+
.catch((err) => console.error("skillmux runtime init error:", err));
|
|
174
|
+
|
|
175
|
+
const server = createMcpServer();
|
|
176
|
+
|
|
169
177
|
const transportType = opts?.transport ?? "stdio";
|
|
170
178
|
if (transportType === "http") {
|
|
171
179
|
const { WebStandardStreamableHTTPServerTransport } =
|
|
@@ -270,10 +278,16 @@ export async function startServer(opts?: {
|
|
|
270
278
|
}
|
|
271
279
|
if (url.pathname === "/health/ready") {
|
|
272
280
|
const readiness = readinessState.get();
|
|
281
|
+
const deployment = describeDeployment(config);
|
|
273
282
|
const headers = new Headers({ "Content-Type": "application/json" });
|
|
274
283
|
if (allowOriginHeader)
|
|
275
284
|
headers.set("Access-Control-Allow-Origin", allowOriginHeader);
|
|
276
|
-
return new Response(JSON.stringify(
|
|
285
|
+
return new Response(JSON.stringify({
|
|
286
|
+
...readiness,
|
|
287
|
+
version: deployment.version,
|
|
288
|
+
runtime: deployment.runtime,
|
|
289
|
+
image_variant: deployment.image_variant,
|
|
290
|
+
}), {
|
|
277
291
|
status: readiness.status === "ready" ? 200 : 503,
|
|
278
292
|
headers,
|
|
279
293
|
});
|
|
@@ -389,6 +403,7 @@ export async function startServer(opts?: {
|
|
|
389
403
|
|
|
390
404
|
if (req.method === "GET" && url.pathname === "/admin/v1/config") {
|
|
391
405
|
const { effective, sources } = await getEffectiveConfig(configPath);
|
|
406
|
+
const deployment = describeDeployment(config);
|
|
392
407
|
const desiredHash = computeHash(effective);
|
|
393
408
|
const snapshot = snapshots.acquire();
|
|
394
409
|
const activeRevision = computeHash(snapshot.snapshot.config);
|
|
@@ -411,6 +426,9 @@ export async function startServer(opts?: {
|
|
|
411
426
|
...status,
|
|
412
427
|
readiness: readinessState.get(),
|
|
413
428
|
runtime: "running",
|
|
429
|
+
version: deployment.version,
|
|
430
|
+
deployment_runtime: deployment.runtime,
|
|
431
|
+
image_variant: deployment.image_variant,
|
|
414
432
|
},
|
|
415
433
|
}),
|
|
416
434
|
{ status: 200, headers },
|
|
@@ -430,7 +448,7 @@ export async function startServer(opts?: {
|
|
|
430
448
|
|
|
431
449
|
const ifMatch = req.headers.get("if-match") || "";
|
|
432
450
|
const cleanIfMatch = ifMatch.replace(/^"|"$/g, "");
|
|
433
|
-
const { effective } = await getEffectiveConfig();
|
|
451
|
+
const { effective } = await getEffectiveConfig(configPath);
|
|
434
452
|
const currentHash = computeHash(effective);
|
|
435
453
|
|
|
436
454
|
if (!ifMatch || cleanIfMatch !== currentHash) {
|