@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/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 async function startServer(opts?: {
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(readiness), {
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) {