@ni-c/mcp-hub 0.6.3 → 0.7.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 (45) hide show
  1. package/CHANGELOG.md +154 -0
  2. package/README.md +34 -6
  3. package/dist/auth/headers.js +33 -0
  4. package/dist/auth/headers.js.map +1 -0
  5. package/dist/auth/provider.js +4 -0
  6. package/dist/auth/provider.js.map +1 -1
  7. package/dist/auth/routes.js +4 -1
  8. package/dist/auth/routes.js.map +1 -1
  9. package/dist/auth/store.js +152 -35
  10. package/dist/auth/store.js.map +1 -1
  11. package/dist/config.js +209 -27
  12. package/dist/config.js.map +1 -1
  13. package/dist/docker-proxy/index.js +106 -0
  14. package/dist/docker-proxy/index.js.map +1 -0
  15. package/dist/docker-proxy/policy.js +278 -0
  16. package/dist/docker-proxy/policy.js.map +1 -0
  17. package/dist/docker-proxy/secrets.js +111 -0
  18. package/dist/docker-proxy/secrets.js.map +1 -0
  19. package/dist/docker-proxy/server.js +265 -0
  20. package/dist/docker-proxy/server.js.map +1 -0
  21. package/dist/health.js +11 -1
  22. package/dist/health.js.map +1 -1
  23. package/dist/hub.js +3 -6
  24. package/dist/hub.js.map +1 -1
  25. package/dist/index.js +9 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/mcp-limits.js +69 -0
  28. package/dist/mcp-limits.js.map +1 -0
  29. package/dist/proxy.js +4 -5
  30. package/dist/proxy.js.map +1 -1
  31. package/dist/sandbox/container-spec.js +115 -0
  32. package/dist/sandbox/container-spec.js.map +1 -0
  33. package/dist/sandbox/docker-client.js +291 -0
  34. package/dist/sandbox/docker-client.js.map +1 -0
  35. package/dist/sandbox/policy-protocol.js +11 -0
  36. package/dist/sandbox/policy-protocol.js.map +1 -0
  37. package/dist/supervisor.js +90 -17
  38. package/dist/supervisor.js.map +1 -1
  39. package/dist/transports/docker.js +163 -0
  40. package/dist/transports/docker.js.map +1 -0
  41. package/dist/transports/socket.js +59 -0
  42. package/dist/transports/socket.js.map +1 -0
  43. package/dist/transports/stream.js +102 -0
  44. package/dist/transports/stream.js.map +1 -0
  45. package/package.json +8 -4
@@ -8,6 +8,10 @@ import path from 'node:path';
8
8
  * approved; approved ones are legitimate and never evicted.
9
9
  */
10
10
  export const MAX_UNAPPROVED_CLIENTS = 100;
11
+ const LOCK_WAIT_MS = 10_000;
12
+ const STALE_LOCK_MS = 30_000;
13
+ const LOCK_POLL_MS = 10;
14
+ const lockSleep = new Int32Array(new SharedArrayBuffer(4));
11
15
  /**
12
16
  * All persistent auth state lives in two files under DATA_PATH:
13
17
  * jwt-key.pem (Ed25519 private key) and state.json (clients, refresh tokens,
@@ -28,33 +32,117 @@ export class AuthStore {
28
32
  publicKey;
29
33
  state;
30
34
  statePath;
35
+ lockPath;
31
36
  /** Identity of the file contents this.state was loaded from; see fileSignature(). */
32
37
  signature;
33
38
  tmpCounter = 0;
34
39
  constructor(dataDir) {
35
40
  fs.mkdirSync(dataDir, { recursive: true });
36
- const keyPath = path.join(dataDir, 'jwt-key.pem');
37
- if (!fs.existsSync(keyPath)) {
38
- const { privateKey } = crypto.generateKeyPairSync('ed25519');
39
- fs.writeFileSync(keyPath, privateKey.export({ type: 'pkcs8', format: 'pem' }), { mode: 0o600 });
40
- }
41
- this.privateKey = crypto.createPrivateKey(fs.readFileSync(keyPath, 'utf8'));
42
- this.publicKey = crypto.createPublicKey(this.privateKey);
43
41
  this.statePath = path.join(dataDir, 'state.json');
44
- const restored = AuthStore.readState(this.statePath);
45
- this.state = restored ?? {
46
- cookieSecret: crypto.randomBytes(32).toString('base64url'),
47
- clients: {},
48
- refreshTokens: {},
49
- approvals: {},
50
- consumedRefreshTokens: {},
51
- revokedBefore: {},
52
- apiTokens: {}
53
- };
54
- if (restored)
55
- this.signature = this.fileSignature();
56
- else
57
- this.persist();
42
+ this.lockPath = path.join(dataDir, '.auth-state.lock');
43
+ this.acquireLock();
44
+ try {
45
+ const keyPath = path.join(dataDir, 'jwt-key.pem');
46
+ if (!fs.existsSync(keyPath)) {
47
+ const { privateKey } = crypto.generateKeyPairSync('ed25519');
48
+ fs.writeFileSync(keyPath, privateKey.export({ type: 'pkcs8', format: 'pem' }), { mode: 0o600 });
49
+ }
50
+ this.privateKey = crypto.createPrivateKey(fs.readFileSync(keyPath, 'utf8'));
51
+ this.publicKey = crypto.createPublicKey(this.privateKey);
52
+ const restored = AuthStore.readState(this.statePath);
53
+ this.state = restored ?? {
54
+ cookieSecret: crypto.randomBytes(32).toString('base64url'),
55
+ clients: {},
56
+ refreshTokens: {},
57
+ approvals: {},
58
+ consumedRefreshTokens: {},
59
+ revokedBefore: {},
60
+ apiTokens: {}
61
+ };
62
+ if (restored)
63
+ this.signature = this.fileSignature();
64
+ else
65
+ this.persistUnlocked();
66
+ }
67
+ finally {
68
+ this.releaseLock();
69
+ }
70
+ }
71
+ /** Atomic directory creation is the portable cross-process mutex. */
72
+ acquireLock() {
73
+ const deadline = Date.now() + LOCK_WAIT_MS;
74
+ for (;;) {
75
+ try {
76
+ fs.mkdirSync(this.lockPath, { mode: 0o700 });
77
+ fs.writeFileSync(path.join(this.lockPath, 'owner'), `${process.pid}\n`, { mode: 0o600 });
78
+ return;
79
+ }
80
+ catch (error) {
81
+ if (error.code !== 'EEXIST')
82
+ throw error;
83
+ }
84
+ this.removeStaleLock();
85
+ if (Date.now() >= deadline)
86
+ throw new Error(`timed out waiting for auth state lock ${this.lockPath}`);
87
+ Atomics.wait(lockSleep, 0, 0, LOCK_POLL_MS);
88
+ }
89
+ }
90
+ /**
91
+ * Break a lock whose owner is gone.
92
+ *
93
+ * The owner's pid is the precise signal and is used whenever it can be read.
94
+ * Age is the fallback, and it has to be one: the directory is created before
95
+ * the owner file is written, so a process killed between the two leaves a
96
+ * lock nobody can attribute. Treating that as "held" would wedge every
97
+ * future AuthStore in the data directory until someone deleted it by hand.
98
+ *
99
+ * The pid also means nothing across a PID namespace — an admin CLI installed
100
+ * from npm on the host, against a /data volume the hub uses from inside a
101
+ * container. The documented invocation is `docker exec` into the hub, which
102
+ * shares the namespace; for the other case the age fallback is what applies.
103
+ */
104
+ removeStaleLock() {
105
+ try {
106
+ const stat = fs.statSync(this.lockPath);
107
+ const pid = this.lockOwnerPid();
108
+ if (pid !== undefined) {
109
+ try {
110
+ process.kill(pid, 0);
111
+ return;
112
+ }
113
+ catch (error) {
114
+ if (error.code !== 'ESRCH')
115
+ return;
116
+ }
117
+ }
118
+ else if (Date.now() - stat.mtimeMs <= STALE_LOCK_MS) {
119
+ return;
120
+ }
121
+ fs.rmSync(this.lockPath, { recursive: true, force: true });
122
+ }
123
+ catch {
124
+ // The owner may have released it between stat and read. Retry mkdir.
125
+ }
126
+ }
127
+ /** undefined when the owner file is missing, unreadable or not a pid. */
128
+ lockOwnerPid() {
129
+ let raw;
130
+ try {
131
+ raw = fs.readFileSync(path.join(this.lockPath, 'owner'), 'utf8');
132
+ }
133
+ catch {
134
+ return undefined;
135
+ }
136
+ const pid = Number(raw.trim());
137
+ return Number.isSafeInteger(pid) && pid > 0 ? pid : undefined;
138
+ }
139
+ releaseLock() {
140
+ try {
141
+ fs.rmSync(this.lockPath, { recursive: true, force: true });
142
+ }
143
+ catch (error) {
144
+ console.error(`mcp-hub: could not release auth state lock: ${error.message}`);
145
+ }
58
146
  }
59
147
  /**
60
148
  * Inode, mtime and size of state.json, or undefined when it is gone. The
@@ -105,10 +193,43 @@ export class AuthStore {
105
193
  * what stops persist() from writing a stale snapshot back over them.
106
194
  */
107
195
  mutate(fn) {
108
- this.reloadIfChanged();
109
- const result = fn();
110
- this.persist();
111
- return result;
196
+ this.acquireLock();
197
+ try {
198
+ this.reloadUnderLock();
199
+ const result = fn();
200
+ this.persistUnlocked();
201
+ return result;
202
+ }
203
+ finally {
204
+ this.releaseLock();
205
+ }
206
+ }
207
+ /**
208
+ * A mutation must never proceed from stale memory, even if stat metadata is
209
+ * unchanged. A file that is *gone* is not stale memory, though: the
210
+ * constructor recreates it, and refusing here instead would turn a deleted
211
+ * state.json into a hub whose every refresh-token rotation fails until it is
212
+ * restarted. Unparseable content stays a hard error — that is a file with
213
+ * contents we cannot merge into, and overwriting it would drop grants.
214
+ */
215
+ reloadUnderLock() {
216
+ let parsed;
217
+ try {
218
+ parsed = JSON.parse(fs.readFileSync(this.statePath, 'utf8'));
219
+ }
220
+ catch (error) {
221
+ if (error.code === 'ENOENT') {
222
+ console.error('mcp-hub: auth state file is gone, writing the state in memory back to disk');
223
+ this.persistUnlocked();
224
+ return;
225
+ }
226
+ throw new Error(`cannot reload auth state while holding the mutation lock: ${error.message}`);
227
+ }
228
+ const next = AuthStore.normalize(parsed);
229
+ if (!next)
230
+ throw new Error('cannot mutate unusable auth state');
231
+ this.state = next;
232
+ this.signature = this.fileSignature();
112
233
  }
113
234
  /**
114
235
  * Shapes a parsed state file, or undefined when it is unusable. Fields added
@@ -153,12 +274,11 @@ export class AuthStore {
153
274
  return undefined;
154
275
  }
155
276
  }
156
- persist() {
277
+ persistUnlocked() {
157
278
  this.pruneExpired();
158
- // Per-writer temporary name: a fixed one would let two processes write into
159
- // the same file at once. With a private temporary plus an atomic rename a
160
- // reader can never observe a half-written file the residual risk of
161
- // concurrent writers is a lost update, not a corrupt state.
279
+ // Per-writer temporary name plus atomic rename keeps readers from observing
280
+ // a half-written file. Mutations are serialized by the cross-process lock,
281
+ // so this write also cannot overwrite another process's newer state.
162
282
  const tmp = `${this.statePath}.tmp-${process.pid}-${this.tmpCounter++}`;
163
283
  try {
164
284
  fs.writeFileSync(tmp, JSON.stringify(this.state, null, 2), { mode: 0o600 });
@@ -332,12 +452,9 @@ export class AuthStore {
332
452
  }
333
453
  /** Deleting the record is the revocation: verification refuses unknown ids. */
334
454
  revokeApiToken(id) {
335
- // reloadIfChanged() before the existence check, so revoking a token another
336
- // process minted seconds ago reports true instead of "no such id".
337
- this.reloadIfChanged();
338
- if (!this.state.apiTokens[id])
339
- return false;
340
455
  return this.mutate(() => {
456
+ if (!this.state.apiTokens[id])
457
+ return false;
341
458
  delete this.state.apiTokens[id];
342
459
  return true;
343
460
  });
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/auth/store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/auth/store.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AA8C7B;;;;;GAKG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,CAAC;AAC1C,MAAM,YAAY,GAAG,MAAM,CAAC;AAC5B,MAAM,aAAa,GAAG,MAAM,CAAC;AAC7B,MAAM,YAAY,GAAG,EAAE,CAAC;AACxB,MAAM,SAAS,GAAG,IAAI,UAAU,CAAC,IAAI,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC;AAE3D;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,SAAS;IACX,UAAU,CAAmB;IAC7B,SAAS,CAAmB;IAC7B,KAAK,CAAiB;IACb,SAAS,CAAS;IAClB,QAAQ,CAAS;IAClC,qFAAqF;IAC7E,SAAS,CAAU;IACnB,UAAU,GAAG,CAAC,CAAC;IAEvB,YAAY,OAAe;QACzB,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3C,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;QAClD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,kBAAkB,CAAC,CAAC;QACvD,IAAI,CAAC,WAAW,EAAE,CAAC;QACnB,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;YAClD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC5B,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,CAAC,mBAAmB,CAAC,SAAS,CAAC,CAAC;gBAC7D,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,UAAU,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YAClG,CAAC;YACD,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,gBAAgB,CAAC,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;YAC5E,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAEzD,MAAM,QAAQ,GAAG,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACrD,IAAI,CAAC,KAAK,GAAG,QAAQ,IAAI;gBACvB,YAAY,EAAE,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC;gBAC1D,OAAO,EAAE,EAAE;gBACX,aAAa,EAAE,EAAE;gBACjB,SAAS,EAAE,EAAE;gBACb,qBAAqB,EAAE,EAAE;gBACzB,aAAa,EAAE,EAAE;gBACjB,SAAS,EAAE,EAAE;aACd,CAAC;YACF,IAAI,QAAQ;gBAAE,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;;gBAC/C,IAAI,CAAC,eAAe,EAAE,CAAC;QAC9B,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,WAAW,EAAE,CAAC;QACrB,CAAC;IACH,CAAC;IAED,qEAAqE;IAC7D,WAAW;QACjB,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,YAAY,CAAC;QAC3C,SAAS,CAAC;YACR,IAAI,CAAC;gBACH,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;gBAC7C,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,GAAG,OAAO,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;gBACzF,OAAO;YACT,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;oBAAE,MAAM,KAAK,CAAC;YACtE,CAAC;YACD,IAAI,CAAC,eAAe,EAAE,CAAC;YACvB,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,QAAQ;gBAAE,MAAM,IAAI,KAAK,CAAC,yCAAyC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;YACtG,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,EAAE,CAAC,EAAE,YAAY,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;IAED;;;;;;;;;;;;;OAaG;IACK,eAAe;QACrB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACxC,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;YAChC,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;gBACtB,IAAI,CAAC;oBACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;oBACrB,OAAO;gBACT,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,IAAK,KAA+B,CAAC,IAAI,KAAK,OAAO;wBAAE,OAAO;gBAChE,CAAC;YACH,CAAC;iBAAM,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO,IAAI,aAAa,EAAE,CAAC;gBACtD,OAAO;YACT,CAAC;YACD,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7D,CAAC;QAAC,MAAM,CAAC;YACP,qEAAqE;QACvE,CAAC;IACH,CAAC;IAED,yEAAyE;IACjE,YAAY;QAClB,IAAI,GAAW,CAAC;QAChB,IAAI,CAAC;YACH,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC;QACnE,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QAC/B,OAAO,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;IAChE,CAAC;IAEO,WAAW;QACjB,IAAI,CAAC;YACH,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,+CAAgD,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;QAC3F,CAAC;IACH,CAAC;IAED;;;;;;OAMG;IACK,aAAa;QACnB,IAAI,CAAC;YACH,MAAM,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACtC,OAAO,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;QAC3C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAED;;;;;;;OAOG;IACK,eAAe;QACrB,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QACvC,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,KAAK,IAAI,CAAC,SAAS;YAAE,OAAO;QACpE,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC;QAC/D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,uEAAwE,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;YACjH,OAAO;QACT,CAAC;QACD,MAAM,IAAI,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACzC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAO,CAAC,KAAK,CAAC,kFAAkF,CAAC,CAAC;YAClG,OAAO;QACT,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;IAED;;;OAGG;IACK,MAAM,CAAI,EAAW;QAC3B,IAAI,CAAC,WAAW,EAAE,CAAC;QACnB,IAAI,CAAC;YACH,IAAI,CAAC,eAAe,EAAE,CAAC;YACvB,MAAM,MAAM,GAAG,EAAE,EAAE,CAAC;YACpB,IAAI,CAAC,eAAe,EAAE,CAAC;YACvB,OAAO,MAAM,CAAC;QAChB,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,WAAW,EAAE,CAAC;QACrB,CAAC;IACH,CAAC;IAED;;;;;;;OAOG;IACK,eAAe;QACrB,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC;QAC/D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACvD,OAAO,CAAC,KAAK,CAAC,4EAA4E,CAAC,CAAC;gBAC5F,IAAI,CAAC,eAAe,EAAE,CAAC;gBACvB,OAAO;YACT,CAAC;YACD,MAAM,IAAI,KAAK,CAAC,6DAA8D,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;QAC3G,CAAC;QACD,MAAM,IAAI,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACzC,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;QAChE,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;IACxC,CAAC;IAED;;;;;OAKG;IACK,MAAM,CAAC,SAAS,CAAC,MAAe;QACtC,MAAM,KAAK,GAAG,MAA+B,CAAC;QAC9C,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,CAAC,YAAY,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAC;QACpG,OAAO;YACL,YAAY,EAAE,KAAK,CAAC,YAAY;YAChC,OAAO,EAAE,KAAK,CAAC,OAAO,IAAI,EAAE;YAC5B,aAAa,EAAE,KAAK,CAAC,aAAa,IAAI,EAAE;YACxC,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,EAAE;YAChC,qBAAqB,EAAE,KAAK,CAAC,qBAAqB,IAAI,EAAE;YACxD,aAAa,EAAE,KAAK,CAAC,aAAa,IAAI,EAAE;YACxC,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,EAAE;SACjC,CAAC;IACJ,CAAC;IAED;;;;;;OAMG;IACK,MAAM,CAAC,SAAS,CAAC,SAAiB;QACxC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC;YAAE,OAAO,SAAS,CAAC;QAChD,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;YACvF,IAAI,CAAC,UAAU;gBAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;YAC3D,OAAO,UAAU,CAAC;QACpB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,MAAM,GAAG,GAAG,SAAS,YAAY,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;YACpD,EAAE,CAAC,UAAU,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;YACjC,OAAO,CAAC,KAAK,CACX,iCAAkC,KAAe,CAAC,OAAO,eAAe,MAAM,uCAAuC,CACtH,CAAC;YACF,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAEO,eAAe;QACrB,IAAI,CAAC,YAAY,EAAE,CAAC;QACpB,4EAA4E;QAC5E,2EAA2E;QAC3E,qEAAqE;QACrE,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,SAAS,QAAQ,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;QACxE,IAAI,CAAC;YACH,EAAE,CAAC,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YAC5E,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QACrC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YAChC,MAAM,KAAK,CAAC;QACd,CAAC;QACD,2EAA2E;QAC3E,2EAA2E;QAC3E,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;IACxC,CAAC;IAEO,YAAY;QAClB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAC1C,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC;YACtE,IAAI,MAAM,CAAC,SAAS,GAAG,GAAG;gBAAE,OAAO,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;QACpE,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,qBAAqB,CAAC,EAAE,CAAC;YAC9E,IAAI,MAAM,CAAC,SAAS,GAAG,GAAG;gBAAE,OAAO,IAAI,CAAC,KAAK,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC;QAC5E,CAAC;QACD,KAAK,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;YAChE,IAAI,MAAM,CAAC,SAAS,GAAG,GAAG;gBAAE,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC;IAED,IAAI,YAAY;QACd,IAAI,CAAC,eAAe,EAAE,CAAC;QACvB,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC;IACjC,CAAC;IAED,SAAS,CAAC,QAAgB;QACxB,IAAI,CAAC,eAAe,EAAE,CAAC;QACvB,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACtC,CAAC;IAED,WAAW;QACT,IAAI,CAAC,eAAe,EAAE,CAAC;QACvB,OAAO,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC7C,CAAC;IAED,UAAU,CAAC,MAAkC;QAC3C,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE;YACf,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC;YAC9C,IAAI,CAAC,sBAAsB,EAAE,CAAC;QAChC,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;OAIG;IACK,sBAAsB;QAC5B,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;aACjD,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;aAC/C,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,mBAAmB,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,mBAAmB,IAAI,CAAC,CAAC,CAAC,CAAC;QAC/E,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,MAAM,GAAG,sBAAsB,EAAE,CAAC,EAAE,EAAE,CAAC;YACpE,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QACrD,CAAC;IACH,CAAC;IAED,WAAW,CAAC,QAAgB;QAC1B,IAAI,CAAC,eAAe,EAAE,CAAC;QACvB,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IACxC,CAAC;IAED;4EACwE;IACxE,YAAY,CAAC,QAAgB,EAAE,WAAmB,EAAE,UAAmB;QACrE,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE;YACf,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;YAChD,MAAM,YAAY,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;YACtG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG;gBAC/B,YAAY;gBACZ,UAAU,EAAE,UAAU,IAAI,QAAQ,EAAE,UAAU;gBAC9C,UAAU,EAAE,QAAQ,EAAE,UAAU,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;aAClE,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;IAED,aAAa;QACX,IAAI,CAAC,eAAe,EAAE,CAAC;QACvB,OAAO,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAC/C,CAAC;IAED,cAAc,CAAC,QAAgB;QAC7B,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IACpC,CAAC;IAED,gBAAgB,CAAC,QAAgB;QAC/B,IAAI,CAAC,eAAe,EAAE,CAAC;QACvB,OAAO,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC5C,CAAC;IAED;;;;OAIG;IACH,kBAAkB,CAAC,QAAgB;QACjC,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE;YACtB,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;YACtC,IAAI,aAAa,GAAG,CAAC,CAAC;YACtB,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC;gBACtE,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;oBACjC,OAAO,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;oBACtC,aAAa,EAAE,CAAC;gBAClB,CAAC;YACH,CAAC;YACD,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACjC,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,QAAQ,CAAC,GAAG,aAAa,CAAC;YACnD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,CAAC;QAC1C,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,MAAM,CAAC,IAAI,CAAC,KAAa;QAC/B,OAAO,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACjE,CAAC;IAED,gBAAgB,CAAC,KAAa,EAAE,MAA0B;QACxD,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE;YACf,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,MAAM,CAAC;QAC3D,CAAC,CAAC,CAAC;IACL,CAAC;IAED,eAAe,CAAC,KAAa;QAC3B,IAAI,CAAC,eAAe,EAAE,CAAC;QACvB,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAC/D,IAAI,MAAM,IAAI,MAAM,CAAC,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;YAAE,OAAO,SAAS,CAAC;QACjF,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,kBAAkB,CAAC,KAAa;QAC9B,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE;YACf,OAAO,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QACzD,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;OAGG;IACH,mBAAmB,CAAC,KAAa,EAAE,QAAgB,EAAE,SAAiB;QACpE,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE;YACf,OAAO,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;YACvD,IAAI,CAAC,KAAK,CAAC,qBAAqB,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;QACpF,CAAC,CAAC,CAAC;IACL,CAAC;IAED,WAAW,CAAC,KAAa;QACvB,IAAI,CAAC,eAAe,EAAE,CAAC;QACvB,OAAO,IAAI,CAAC,KAAK,CAAC,qBAAqB,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IACjE,CAAC;IAED,+EAA+E;IAC/E,YAAY,CAAC,QAAgB;QAC3B,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE;YACtB,IAAI,OAAO,GAAG,CAAC,CAAC;YAChB,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC;gBACtE,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;oBACjC,OAAO,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;oBACtC,OAAO,EAAE,CAAC;gBACZ,CAAC;YACH,CAAC;YACD,OAAO,OAAO,CAAC;QACjB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,YAAY,CAAC,EAAU,EAAE,MAAsB;QAC7C,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE;YACf,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC;QACpC,CAAC,CAAC,CAAC;IACL,CAAC;IAED,+EAA+E;IAC/E,WAAW,CAAC,EAAU;QACpB,IAAI,CAAC,eAAe,EAAE,CAAC;QACvB,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QACxC,IAAI,MAAM,IAAI,MAAM,CAAC,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;YAAE,OAAO,SAAS,CAAC;QACjF,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,aAAa;QACX,IAAI,CAAC,eAAe,EAAE,CAAC;QACvB,OAAO,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAC/C,CAAC;IAED,+EAA+E;IAC/E,cAAc,CAAC,EAAU;QACvB,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE;YACtB,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;gBAAE,OAAO,KAAK,CAAC;YAC5C,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;YAChC,OAAO,IAAI,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
package/dist/config.js CHANGED
@@ -4,6 +4,13 @@ import path from 'node:path';
4
4
  const NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
5
5
  const RESERVED_NAMES = new Set(['mcp', 'hub', 'authorize', 'token', 'register', 'login', 'consent', 'health', 'livez', 'revoke', '.well-known']);
6
6
  const REMOTE_TYPES = new Set(['http', 'sse', 'streamable-http', 'streamable_http']);
7
+ const SECRETS_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
8
+ const VOLUME_SOURCE_PATTERN = /^(?:\/[^:]*|[a-zA-Z0-9][a-zA-Z0-9_.-]*)$/;
9
+ const PORT_PATTERN = /^(?:(\d{1,3}(?:\.\d{1,3}){3}):)?(\d{1,5}):(\d{1,5})(?:\/(tcp|udp))?$/;
10
+ const MEMORY_PATTERN = /^(\d+)([bkmg])?$/i;
11
+ export const DEFAULT_DOCKER_MEMORY = 512 * 1024 * 1024;
12
+ export const DEFAULT_DOCKER_PIDS_LIMIT = 256;
13
+ export const DEFAULT_DOCKER_CPUS = 1;
7
14
  export class ConfigError extends Error {
8
15
  }
9
16
  /** Expand ${VAR} references from the process environment, like Claude Code does. */
@@ -16,10 +23,177 @@ export function expandEnvVars(value, env = process.env) {
16
23
  return replacement;
17
24
  });
18
25
  }
19
- function expandRecord(record, env) {
20
- return Object.fromEntries(Object.entries(record).map(([k, v]) => [k, expandEnvVars(v, env)]));
26
+ function expanderFor(env, options) {
27
+ if (options?.expand === false)
28
+ return value => value;
29
+ return value => expandEnvVars(value, env);
21
30
  }
22
- function parseServer(name, entry, env) {
31
+ function expandRecord(record, expand) {
32
+ return Object.fromEntries(Object.entries(record).map(([k, v]) => [k, expand(v)]));
33
+ }
34
+ function requireStringArray(name, field, value) {
35
+ if (!Array.isArray(value) || !value.every(v => typeof v === 'string')) {
36
+ throw new ConfigError(`Server "${name}": "${field}" must be an array of strings`);
37
+ }
38
+ return value;
39
+ }
40
+ function requireStringRecord(name, field, value) {
41
+ if (typeof value !== 'object' || value === null || Array.isArray(value) || !Object.values(value).every(v => typeof v === 'string')) {
42
+ throw new ConfigError(`Server "${name}": "${field}" must be an object of string values`);
43
+ }
44
+ return value;
45
+ }
46
+ /** "512m" / "1g" / "1048576" -> bytes. */
47
+ export function parseMemory(name, value) {
48
+ if (typeof value === 'number') {
49
+ if (!Number.isSafeInteger(value) || value <= 0)
50
+ throw new ConfigError(`Server "${name}": "memory" must be a positive integer`);
51
+ return value;
52
+ }
53
+ if (typeof value !== 'string')
54
+ throw new ConfigError(`Server "${name}": "memory" must be a string like "512m" or a byte count`);
55
+ const match = MEMORY_PATTERN.exec(value.trim());
56
+ if (!match)
57
+ throw new ConfigError(`Server "${name}": "memory" must look like "512m", "1g" or a byte count`);
58
+ const factor = { b: 1, k: 1024, m: 1024 ** 2, g: 1024 ** 3 }[(match[2] ?? 'b').toLowerCase()];
59
+ const bytes = Number(match[1]) * factor;
60
+ if (bytes <= 0 || !Number.isSafeInteger(bytes))
61
+ throw new ConfigError(`Server "${name}": "memory" is out of range`);
62
+ return bytes;
63
+ }
64
+ /**
65
+ * Everything the docker proxy validates has to be spelled out literally.
66
+ *
67
+ * The proxy derives the container it will allow from this very config, but it
68
+ * deliberately does not hold the hub's environment — a `${VAR}` in the image
69
+ * name or a bind mount would be a field it could not check, which is exactly
70
+ * the field an attacker would choose. Only `env` values, which the proxy
71
+ * compares by key and never by value, may be substituted.
72
+ */
73
+ function requireLiteral(name, field, value) {
74
+ if (value.includes('${')) {
75
+ throw new ConfigError(`Server "${name}": "${field}" must not use \${VAR} — only "env" values are substituted for docker servers`);
76
+ }
77
+ return value;
78
+ }
79
+ function parseDockerServer(name, entry, expand, hub) {
80
+ if (entry.command !== undefined && !Array.isArray(entry.command)) {
81
+ throw new ConfigError(`Server "${name}": for docker servers "command" is an array of strings (the container's Cmd)`);
82
+ }
83
+ if (typeof entry.image !== 'string' || entry.image.length === 0) {
84
+ throw new ConfigError(`Server "${name}": docker servers need an "image" string`);
85
+ }
86
+ const pull = entry.pull ?? 'never';
87
+ if (pull !== 'never' && pull !== 'missing') {
88
+ throw new ConfigError(`Server "${name}": "pull" must be "never" or "missing"`);
89
+ }
90
+ const secretsFrom = entry.secretsFrom;
91
+ if (secretsFrom !== undefined && (typeof secretsFrom !== 'string' || !SECRETS_NAME_PATTERN.test(secretsFrom))) {
92
+ throw new ConfigError(`Server "${name}": "secretsFrom" must be a name matching ${SECRETS_NAME_PATTERN}`);
93
+ }
94
+ const volumes = requireStringArray(name, 'volumes', entry.volumes ?? []).map(v => requireLiteral(name, 'volumes', v));
95
+ for (const volume of volumes) {
96
+ const parts = volume.split(':');
97
+ if (parts.length < 2 || parts.length > 3) {
98
+ throw new ConfigError(`Server "${name}": volume "${volume}" must look like "source:/target" or "source:/target:ro"`);
99
+ }
100
+ if (!VOLUME_SOURCE_PATTERN.test(parts[0]) || parts[0].includes('..')) {
101
+ throw new ConfigError(`Server "${name}": volume source "${parts[0]}" must be an absolute path without ".." or a named volume`);
102
+ }
103
+ if (!parts[1].startsWith('/'))
104
+ throw new ConfigError(`Server "${name}": volume target "${parts[1]}" must be an absolute path`);
105
+ if (parts[2] !== undefined && parts[2] !== 'ro' && parts[2] !== 'rw') {
106
+ throw new ConfigError(`Server "${name}": volume mode "${parts[2]}" must be "ro" or "rw"`);
107
+ }
108
+ }
109
+ const ports = requireStringArray(name, 'ports', entry.ports ?? []).map(v => requireLiteral(name, 'ports', v));
110
+ for (const port of ports) {
111
+ const match = PORT_PATTERN.exec(port);
112
+ if (!match)
113
+ throw new ConfigError(`Server "${name}": port "${port}" must look like "127.0.0.1:8686:8000" or "8686:8000"`);
114
+ for (const value of [match[2], match[3]]) {
115
+ const number = Number(value);
116
+ if (number < 1 || number > 65535)
117
+ throw new ConfigError(`Server "${name}": port "${port}" is out of range`);
118
+ }
119
+ }
120
+ const network = entry.network === undefined ? 'none' : entry.network;
121
+ if (typeof network !== 'string' || network.length === 0)
122
+ throw new ConfigError(`Server "${name}": "network" must be a string`);
123
+ if (ports.length > 0 && network === 'none') {
124
+ // Published ports on a container without a network silently do nothing;
125
+ // saying so here beats a sandbox that looks reachable and is not.
126
+ throw new ConfigError(`Server "${name}": "ports" need a network, but "network" is "none"`);
127
+ }
128
+ const tmpfs = requireStringArray(name, 'tmpfs', entry.tmpfs ?? ['/tmp']).map(v => requireLiteral(name, 'tmpfs', v));
129
+ for (const mount of tmpfs) {
130
+ if (!mount.startsWith('/'))
131
+ throw new ConfigError(`Server "${name}": tmpfs "${mount}" must be an absolute path`);
132
+ }
133
+ if (entry.readOnly !== undefined && typeof entry.readOnly !== 'boolean') {
134
+ throw new ConfigError(`Server "${name}": "readOnly" must be a boolean`);
135
+ }
136
+ if (entry.user !== undefined && typeof entry.user !== 'string') {
137
+ throw new ConfigError(`Server "${name}": "user" must be a string`);
138
+ }
139
+ if (entry.pidsLimit !== undefined && (!Number.isSafeInteger(entry.pidsLimit) || entry.pidsLimit < 1)) {
140
+ throw new ConfigError(`Server "${name}": "pidsLimit" must be a positive integer`);
141
+ }
142
+ if (entry.cpus !== undefined &&
143
+ (typeof entry.cpus !== 'number' ||
144
+ !Number.isFinite(entry.cpus) ||
145
+ entry.cpus <= 0 ||
146
+ !Number.isSafeInteger(Math.round(entry.cpus * 1_000_000_000)))) {
147
+ throw new ConfigError(`Server "${name}": "cpus" must be a positive number`);
148
+ }
149
+ for (const forbidden of ['privileged', 'capAdd', 'securityOpt', 'devices', 'restart']) {
150
+ if (entry[forbidden] !== undefined) {
151
+ throw new ConfigError(`Server "${name}": "${forbidden}" is not supported — sandboxes always run with all capabilities dropped, no-new-privileges and no restart policy`);
152
+ }
153
+ }
154
+ return {
155
+ kind: 'docker',
156
+ image: requireLiteral(name, 'image', entry.image),
157
+ pull,
158
+ ...(entry.command !== undefined ? { command: requireStringArray(name, 'command', entry.command).map(v => requireLiteral(name, 'command', v)) } : {}),
159
+ ...(entry.entrypoint !== undefined
160
+ ? { entrypoint: requireStringArray(name, 'entrypoint', entry.entrypoint).map(v => requireLiteral(name, 'entrypoint', v)) }
161
+ : {}),
162
+ env: expandRecord(requireStringRecord(name, 'env', entry.env ?? {}), expand),
163
+ ...(secretsFrom !== undefined ? { secretsFrom } : {}),
164
+ volumes,
165
+ ports,
166
+ network: requireLiteral(name, 'network', network),
167
+ memory: entry.memory !== undefined ? parseMemory(name, entry.memory) : DEFAULT_DOCKER_MEMORY,
168
+ pidsLimit: entry.pidsLimit !== undefined ? entry.pidsLimit : DEFAULT_DOCKER_PIDS_LIMIT,
169
+ cpus: entry.cpus !== undefined ? entry.cpus : DEFAULT_DOCKER_CPUS,
170
+ readOnly: entry.readOnly !== false,
171
+ tmpfs,
172
+ ...(entry.user !== undefined ? { user: requireLiteral(name, 'user', entry.user) } : {}),
173
+ hub
174
+ };
175
+ }
176
+ function parseSocketServer(name, entry, type, expand, hub) {
177
+ if (entry.command !== undefined || entry.url !== undefined) {
178
+ throw new ConfigError(`Server "${name}": "${type}" servers take a socket address, not "command" or "url"`);
179
+ }
180
+ if (type === 'unix') {
181
+ if (typeof entry.socket !== 'string' || !entry.socket.startsWith('/')) {
182
+ throw new ConfigError(`Server "${name}": unix servers need a "socket" path starting with "/"`);
183
+ }
184
+ return { kind: 'socket', transport: 'unix', socketPath: expand(entry.socket), hub };
185
+ }
186
+ if (typeof entry.host !== 'string' || entry.host.length === 0) {
187
+ throw new ConfigError(`Server "${name}": tcp servers need a "host" string`);
188
+ }
189
+ const port = entry.port;
190
+ if (!Number.isSafeInteger(port) || port < 1 || port > 65535) {
191
+ throw new ConfigError(`Server "${name}": tcp servers need a "port" between 1 and 65535`);
192
+ }
193
+ return { kind: 'socket', transport: 'tcp', host: expand(entry.host), port: port, hub };
194
+ }
195
+ function parseServer(name, entry, env, options) {
196
+ const expand = expanderFor(env, options);
23
197
  const hub = entry.hub !== false;
24
198
  if (entry.hub !== undefined && typeof entry.hub !== 'boolean') {
25
199
  throw new ConfigError(`Server "${name}": "hub" must be a boolean`);
@@ -28,10 +202,14 @@ function parseServer(name, entry, env) {
28
202
  if (type !== undefined && typeof type !== 'string') {
29
203
  throw new ConfigError(`Server "${name}": "type" must be a string`);
30
204
  }
205
+ if (type === 'docker')
206
+ return parseDockerServer(name, entry, expand, hub);
207
+ if (type === 'unix' || type === 'tcp')
208
+ return parseSocketServer(name, entry, type, expand, hub);
31
209
  const isRemote = (typeof type === 'string' && type !== 'stdio') || entry.url !== undefined;
32
210
  if (isRemote) {
33
211
  if (typeof type === 'string' && type !== 'stdio' && !REMOTE_TYPES.has(type)) {
34
- throw new ConfigError(`Server "${name}": unknown type "${type}" (supported: stdio, http, sse)`);
212
+ throw new ConfigError(`Server "${name}": unknown type "${type}" (supported: stdio, http, sse, docker, unix, tcp)`);
35
213
  }
36
214
  if (typeof entry.url !== 'string' || entry.url.length === 0) {
37
215
  throw new ConfigError(`Server "${name}": remote servers need a "url" string`);
@@ -39,11 +217,8 @@ function parseServer(name, entry, env) {
39
217
  if (entry.command !== undefined) {
40
218
  throw new ConfigError(`Server "${name}": "command" and "url" are mutually exclusive`);
41
219
  }
42
- const headers = entry.headers ?? {};
43
- if (typeof headers !== 'object' || headers === null || Array.isArray(headers) || !Object.values(headers).every(v => typeof v === 'string')) {
44
- throw new ConfigError(`Server "${name}": "headers" must be an object of string values`);
45
- }
46
- const url = expandEnvVars(entry.url, env);
220
+ const headers = requireStringRecord(name, 'headers', entry.headers ?? {});
221
+ const url = expand(entry.url);
47
222
  try {
48
223
  new URL(url);
49
224
  }
@@ -54,30 +229,24 @@ function parseServer(name, entry, env) {
54
229
  kind: 'remote',
55
230
  transport: type === 'sse' ? 'sse' : 'http',
56
231
  url,
57
- headers: expandRecord(headers, env),
232
+ headers: expandRecord(headers, expand),
58
233
  hub
59
234
  };
60
235
  }
61
236
  if (typeof entry.command !== 'string' || entry.command.length === 0) {
62
237
  throw new ConfigError(`Server "${name}" is missing a "command" string`);
63
238
  }
64
- const args = entry.args ?? [];
65
- if (!Array.isArray(args) || !args.every(a => typeof a === 'string')) {
66
- throw new ConfigError(`Server "${name}": "args" must be an array of strings`);
67
- }
68
- const envEntry = entry.env ?? {};
69
- if (typeof envEntry !== 'object' || envEntry === null || Array.isArray(envEntry) || !Object.values(envEntry).every(v => typeof v === 'string')) {
70
- throw new ConfigError(`Server "${name}": "env" must be an object of string values`);
71
- }
239
+ const args = requireStringArray(name, 'args', entry.args ?? []);
240
+ const envEntry = requireStringRecord(name, 'env', entry.env ?? {});
72
241
  return {
73
242
  kind: 'stdio',
74
- command: expandEnvVars(entry.command, env),
75
- args: args.map(a => expandEnvVars(a, env)),
76
- env: expandRecord(envEntry, env),
243
+ command: expand(entry.command),
244
+ args: args.map(expand),
245
+ env: expandRecord(envEntry, expand),
77
246
  hub
78
247
  };
79
248
  }
80
- export function parseConfig(json, env = process.env) {
249
+ export function parseConfig(json, env = process.env, options) {
81
250
  let raw;
82
251
  try {
83
252
  raw = JSON.parse(json);
@@ -100,12 +269,20 @@ export function parseConfig(json, env = process.env) {
100
269
  if (typeof entry !== 'object' || entry === null) {
101
270
  throw new ConfigError(`Server "${name}" must be an object`);
102
271
  }
103
- result.set(name, parseServer(name, entry, env));
272
+ result.set(name, parseServer(name, entry, env, options));
104
273
  }
105
274
  return result;
106
275
  }
107
- export function loadConfig(filePath, env = process.env) {
108
- return parseConfig(fs.readFileSync(filePath, 'utf8'), env);
276
+ export function loadConfig(filePath, env = process.env, options) {
277
+ return parseConfig(fs.readFileSync(filePath, 'utf8'), env, options);
278
+ }
279
+ export function warnMutableDockerImages(config, component = 'mcp-hub') {
280
+ for (const [name, entry] of config) {
281
+ if (entry.kind !== 'docker' || entry.image.includes('@sha256:'))
282
+ continue;
283
+ console.warn(`${component}: docker server "${name}" uses mutable image reference "${entry.image}"; ` +
284
+ 'this is supported for compatibility, but a sha256 digest is strongly recommended');
285
+ }
109
286
  }
110
287
  export function diffConfigs(before, after) {
111
288
  const added = [...after.keys()].filter(name => !before.has(name));
@@ -126,14 +303,18 @@ export class ConfigWatcher extends EventEmitter {
126
303
  current;
127
304
  env;
128
305
  pollIntervalMs;
306
+ parseOptions;
307
+ validate;
129
308
  watcher;
130
309
  debounce;
131
- constructor(filePath, current, env = process.env, pollIntervalMs = 3_000) {
310
+ constructor(filePath, current, env = process.env, pollIntervalMs = 3_000, parseOptions, validate) {
132
311
  super();
133
312
  this.filePath = filePath;
134
313
  this.current = current;
135
314
  this.env = env;
136
315
  this.pollIntervalMs = pollIntervalMs;
316
+ this.parseOptions = parseOptions;
317
+ this.validate = validate;
137
318
  }
138
319
  start() {
139
320
  const dir = path.dirname(this.filePath);
@@ -159,7 +340,8 @@ export class ConfigWatcher extends EventEmitter {
159
340
  reload() {
160
341
  let next;
161
342
  try {
162
- next = loadConfig(this.filePath, this.env);
343
+ next = loadConfig(this.filePath, this.env, this.parseOptions);
344
+ this.validate?.(next);
163
345
  }
164
346
  catch (error) {
165
347
  // Keep running with the previous config; a broken edit must not take the hub down.