@remnic/core 9.3.768 → 9.3.769

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 (37) hide show
  1. package/dist/access-cli.js +5 -5
  2. package/dist/access-http.d.ts +51 -1
  3. package/dist/access-http.js +2 -2
  4. package/dist/access-mcp.d.ts +7 -1
  5. package/dist/access-mcp.js +5 -3
  6. package/dist/access-operations.d.ts +4 -4
  7. package/dist/access-schema.d.ts +68 -68
  8. package/dist/{chunk-TVLN5EZZ.js → chunk-CNXMWYLA.js} +78 -3
  9. package/dist/chunk-CNXMWYLA.js.map +1 -0
  10. package/dist/{chunk-UG274TNV.js → chunk-E2SPGGUI.js} +13 -8
  11. package/dist/chunk-E2SPGGUI.js.map +1 -0
  12. package/dist/{chunk-HBOPSFQQ.js → chunk-EVXI2I6G.js} +3 -3
  13. package/dist/{chunk-4DLFJJOQ.js → chunk-QMN3CIFS.js} +2 -2
  14. package/dist/{chunk-J3UJJZKI.js → chunk-SIPZ5UMK.js} +85 -5
  15. package/dist/chunk-SIPZ5UMK.js.map +1 -0
  16. package/dist/cli.js +3 -3
  17. package/dist/connectors/index.js +2 -2
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.js +11 -5
  20. package/dist/orchestrator.js +5 -5
  21. package/dist/schemas.d.ts +84 -84
  22. package/dist/shared-context/manager.d.ts +8 -8
  23. package/dist/tokens.d.ts +3 -1
  24. package/dist/tokens.js +3 -1
  25. package/dist/transfer/types.d.ts +66 -66
  26. package/package.json +2 -2
  27. package/src/access-http.test.ts +350 -0
  28. package/src/access-http.ts +160 -6
  29. package/src/access-mcp.ts +120 -3
  30. package/src/index.ts +3 -0
  31. package/src/tokens.test.ts +58 -0
  32. package/src/tokens.ts +19 -8
  33. package/dist/chunk-J3UJJZKI.js.map +0 -1
  34. package/dist/chunk-TVLN5EZZ.js.map +0 -1
  35. package/dist/chunk-UG274TNV.js.map +0 -1
  36. /package/dist/{chunk-HBOPSFQQ.js.map → chunk-EVXI2I6G.js.map} +0 -0
  37. /package/dist/{chunk-4DLFJJOQ.js.map → chunk-QMN3CIFS.js.map} +0 -0
@@ -21,7 +21,8 @@ var TOKEN_PREFIXES = {
21
21
  "roo-code": "remnic_rc_",
22
22
  "windsurf": "remnic_ws_",
23
23
  "amp": "remnic_am_",
24
- "generic-mcp": "remnic_gm_"
24
+ "generic-mcp": "remnic_gm_",
25
+ "chatgpt": "remnic_cg_"
25
26
  };
26
27
  function defaultTokensPath() {
27
28
  return path.join(resolveHomeDir(), ".remnic", "tokens.json");
@@ -209,21 +210,24 @@ function getAllValidTokens(tokensPath) {
209
210
  return loadTokenStore(tokensPath).tokens.map((t) => t.token);
210
211
  }
211
212
  var TOKEN_CACHE_TTL_MS = 5e3;
212
- var _cachedTokens = [];
213
+ var _cachedEntries = [];
213
214
  var _cachedAt = 0;
214
215
  var _cachedPath;
215
216
  function invalidateTokenCache() {
216
- _cachedTokens = [];
217
+ _cachedEntries = [];
217
218
  _cachedAt = 0;
218
219
  _cachedPath = void 0;
219
220
  }
220
- function getAllValidTokensCached(tokensPath) {
221
+ function getAllValidTokenEntriesCached(tokensPath) {
221
222
  const now = Date.now();
222
- if (now - _cachedAt < TOKEN_CACHE_TTL_MS && tokensPath === _cachedPath) return _cachedTokens;
223
- _cachedTokens = getAllValidTokens(tokensPath);
223
+ if (now - _cachedAt < TOKEN_CACHE_TTL_MS && tokensPath === _cachedPath) return _cachedEntries;
224
+ _cachedEntries = loadTokenStore(tokensPath).tokens;
224
225
  _cachedAt = now;
225
226
  _cachedPath = tokensPath;
226
- return _cachedTokens;
227
+ return _cachedEntries;
228
+ }
229
+ function getAllValidTokensCached(tokensPath) {
230
+ return getAllValidTokenEntriesCached(tokensPath).map((entry) => entry.token);
227
231
  }
228
232
  function resolveConnectorFromToken(token, tokensPath) {
229
233
  return loadTokenStore(tokensPath).tokens.find((t) => t.token === token)?.connector;
@@ -238,7 +242,8 @@ export {
238
242
  listTokens,
239
243
  revokeToken,
240
244
  getAllValidTokens,
245
+ getAllValidTokenEntriesCached,
241
246
  getAllValidTokensCached,
242
247
  resolveConnectorFromToken
243
248
  };
244
- //# sourceMappingURL=chunk-UG274TNV.js.map
249
+ //# sourceMappingURL=chunk-E2SPGGUI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tokens.ts"],"sourcesContent":["/**\n * Token management for Remnic multi-connector auth.\n *\n * Manages per-connector tokens in ~/.remnic/tokens.json.\n * Each connector gets a unique token with a recognizable prefix.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { randomBytes, randomUUID } from \"node:crypto\";\nimport { resolveHomeDir } from \"./runtime/env.js\";\n\nexport interface TokenEntry {\n token: string;\n connector: string;\n createdAt: string;\n}\n\nexport interface TokenStore {\n tokens: TokenEntry[];\n}\n\nconst TOKEN_PREFIXES: Record<string, string> = {\n \"openclaw\": \"remnic_oc_\",\n \"claude-code\": \"remnic_cc_\",\n \"codex-cli\": \"remnic_cx_\",\n \"codex\": \"remnic_cx_\",\n \"hermes\": \"remnic_hm_\",\n \"pi\": \"remnic_pi_\",\n \"omp\": \"remnic_op_\",\n \"replit\": \"remnic_rl_\",\n \"cursor\": \"remnic_cu_\",\n \"cline\": \"remnic_cl_\",\n \"github-copilot\": \"remnic_gh_\",\n \"roo-code\": \"remnic_rc_\",\n \"windsurf\": \"remnic_ws_\",\n \"amp\": \"remnic_am_\",\n \"generic-mcp\": \"remnic_gm_\",\n \"chatgpt\": \"remnic_cg_\",\n};\n\nfunction defaultTokensPath(): string {\n return path.join(resolveHomeDir(), \".remnic\", \"tokens.json\");\n}\n\nfunction legacyTokensPath(): string {\n return path.join(resolveHomeDir(), \".engram\", \"tokens.json\");\n}\n\nfunction resolveReadPath(tokensPath?: string): string {\n const primary = tokensPath ?? defaultTokensPath();\n if (tokensPath) return primary;\n if (fs.existsSync(primary)) return primary;\n const legacy = legacyTokensPath();\n return fs.existsSync(legacy) ? legacy : primary;\n}\n\nfunction ensureDir(filePath: string): void {\n const dir = path.dirname(filePath);\n fs.mkdirSync(dir, { recursive: true });\n}\n\nfunction isEnoent(error: unknown): boolean {\n return error instanceof Error && \"code\" in error && (error as NodeJS.ErrnoException).code === \"ENOENT\";\n}\n\nfunction validateTokenEntry(raw: unknown, index: number): TokenEntry {\n if (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) {\n throw new Error(`invalid token entry at index ${index}: expected object`);\n }\n const entry = raw as Record<string, unknown>;\n if (typeof entry.token !== \"string\" || entry.token.length === 0) {\n throw new Error(`invalid token entry at index ${index}: token must be a non-empty string`);\n }\n if (typeof entry.connector !== \"string\" || entry.connector.length === 0) {\n throw new Error(`invalid token entry at index ${index}: connector must be a non-empty string`);\n }\n return {\n token: entry.token,\n connector: entry.connector,\n createdAt:\n typeof entry.createdAt === \"string\" && entry.createdAt.length > 0\n ? entry.createdAt\n : new Date(0).toISOString(),\n };\n}\n\nfunction parseTokenStore(rawText: string, filePath: string): { store: TokenStore; migratedLegacy: boolean } {\n let raw: unknown;\n try {\n raw = JSON.parse(rawText);\n } catch (error) {\n throw new Error(\n `failed to parse token store at ${filePath}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n if (typeof raw === \"object\" && raw !== null && !Array.isArray(raw)) {\n const record = raw as Record<string, unknown>;\n if (record.tokens !== undefined) {\n if (!Array.isArray(record.tokens)) {\n throw new Error(`invalid token store at ${filePath}: tokens must be an array`);\n }\n return {\n store: {\n tokens: record.tokens.map((entry, index) => validateTokenEntry(entry, index)),\n },\n migratedLegacy: false,\n };\n }\n\n // Migrate legacy flat-map format: { \"connector\": \"token_value\", ... }\n const migrated: TokenEntry[] = [];\n for (const [key, value] of Object.entries(record)) {\n if (typeof value === \"string\" && value.length > 0) {\n migrated.push(\n validateTokenEntry(\n { token: value, connector: key, createdAt: new Date().toISOString() },\n migrated.length,\n ),\n );\n }\n }\n if (migrated.length > 0) {\n return { store: { tokens: migrated }, migratedLegacy: true };\n }\n }\n\n throw new Error(`invalid token store at ${filePath}: expected token array or legacy connector map`);\n}\n\nexport function loadTokenStore(tokensPath?: string): TokenStore {\n const p = resolveReadPath(tokensPath);\n try {\n const rawText = fs.readFileSync(p, \"utf8\");\n const { store, migratedLegacy } = parseTokenStore(rawText, p);\n if (migratedLegacy) {\n // Auto-migrate legacy flat-map stores in new format. This is best-effort:\n // a migration write failure must not hide the successfully parsed tokens.\n try {\n saveTokenStore(store, tokensPath);\n } catch {\n // Migration write failed (e.g., read-only fs) — still return parsed tokens.\n }\n }\n return store;\n } catch (error) {\n if (isEnoent(error)) {\n return { tokens: [] };\n }\n throw error;\n }\n}\n\nfunction fsyncDirectoryBestEffort(dirPath: string): void {\n let dirFd: number | null = null;\n try {\n dirFd = fs.openSync(dirPath, \"r\");\n fs.fsyncSync(dirFd);\n } catch {\n // Directory fsync is not supported on every platform/filesystem.\n } finally {\n if (dirFd !== null) {\n try { fs.closeSync(dirFd); } catch { /* ignore */ }\n }\n }\n}\n\nexport function saveTokenStore(store: TokenStore, tokensPath?: string): void {\n const p = tokensPath ?? defaultTokensPath();\n ensureDir(p);\n const validated: TokenStore = {\n tokens: store.tokens.map((entry, index) => validateTokenEntry(entry, index)),\n };\n const dir = path.dirname(p);\n const tmpPath = path.join(dir, `.${path.basename(p)}.${process.pid}.${randomUUID()}.tmp`);\n let fd: number | null = null;\n try {\n fd = fs.openSync(tmpPath, \"w\", 0o600);\n fs.writeFileSync(fd, JSON.stringify(validated, null, 2) + \"\\n\", \"utf8\");\n fs.fsyncSync(fd);\n fs.closeSync(fd);\n fd = null;\n fs.renameSync(tmpPath, p);\n // Tighten permissions on pre-existing files after atomic replacement.\n try { fs.chmodSync(p, 0o600); } catch { /* ignore on platforms without chmod */ }\n fsyncDirectoryBestEffort(dir);\n invalidateTokenCache();\n } catch (error) {\n if (fd !== null) {\n try { fs.closeSync(fd); } catch { /* ignore */ }\n }\n try { fs.rmSync(tmpPath, { force: true }); } catch { /* ignore cleanup failure */ }\n throw error;\n }\n}\n\n/**\n * Build a TokenEntry candidate WITHOUT saving it to the store.\n * Callers use this when they need to defer the save until after a\n * dependent write (e.g. Hermes config.yaml) succeeds — see\n * commitTokenEntry() to persist the candidate.\n */\nexport function buildTokenEntry(connector: string): TokenEntry {\n const prefix = TOKEN_PREFIXES[connector] ?? \"remnic_xx_\";\n const token = prefix + randomBytes(24).toString(\"hex\");\n return {\n token,\n connector,\n createdAt: new Date().toISOString(),\n };\n}\n\n/**\n * Persist a pre-built TokenEntry into the store, replacing any existing\n * entry for the same connector. Used together with buildTokenEntry() when\n * the caller wants to defer the save until after a dependent write succeeds.\n *\n * For transactional rollback, callers should snapshot the full store via\n * loadTokenStore() BEFORE calling commitTokenEntry() and restore it with\n * saveTokenStore() on failure. A full-store snapshot handles partial writes\n * of tokens.json atomically — single-entry restore via the return value is\n * insufficient because if this function throws during saveTokenStore, the\n * return statement never executes (UXJI/UXJT fix).\n */\nexport function commitTokenEntry(entry: TokenEntry, tokensPath?: string): void {\n const store = loadTokenStore(tokensPath);\n store.tokens = store.tokens.filter((t) => t.connector !== entry.connector);\n store.tokens.push(entry);\n saveTokenStore(store, tokensPath);\n}\n\nexport function generateToken(connector: string, tokensPath?: string): TokenEntry {\n const store = loadTokenStore(tokensPath);\n\n // Remove existing token for this connector\n store.tokens = store.tokens.filter((t) => t.connector !== connector);\n\n const entry = buildTokenEntry(connector);\n store.tokens.push(entry);\n saveTokenStore(store, tokensPath);\n return entry;\n}\n\nexport function listTokens(tokensPath?: string): TokenEntry[] {\n return loadTokenStore(tokensPath).tokens;\n}\n\nexport function revokeToken(connector: string, tokensPath?: string): boolean {\n const store = loadTokenStore(tokensPath);\n const before = store.tokens.length;\n store.tokens = store.tokens.filter((t) => t.connector !== connector);\n if (store.tokens.length < before) {\n saveTokenStore(store, tokensPath);\n return true;\n }\n return false;\n}\n\nexport function getAllValidTokens(tokensPath?: string): string[] {\n return loadTokenStore(tokensPath).tokens.map((t) => t.token);\n}\n\n// Cached token-entry snapshot to avoid synchronous disk I/O on every HTTP\n// request. Re-reads tokens.json at most once per TTL interval (default 5s).\n// There is deliberately exactly ONE cache: validation (token strings) and\n// identity (connector ids) are derived from the SAME snapshot, so a token\n// can never validate against a fresher snapshot than the one that resolves\n// its connector. saveTokenStore() invalidates on every mutation, so a\n// freshly minted or revoked token is coherent immediately.\nconst TOKEN_CACHE_TTL_MS = 5_000;\nlet _cachedEntries: TokenEntry[] = [];\nlet _cachedAt = 0;\nlet _cachedPath: string | undefined;\n\nfunction invalidateTokenCache(): void {\n _cachedEntries = [];\n _cachedAt = 0;\n _cachedPath = undefined;\n}\n\n/** Cached token-entry snapshot ({token, connector} pairs). */\nexport function getAllValidTokenEntriesCached(tokensPath?: string): readonly TokenEntry[] {\n const now = Date.now();\n if (now - _cachedAt < TOKEN_CACHE_TTL_MS && tokensPath === _cachedPath) return _cachedEntries;\n _cachedEntries = loadTokenStore(tokensPath).tokens;\n _cachedAt = now;\n _cachedPath = tokensPath;\n return _cachedEntries;\n}\n\nexport function getAllValidTokensCached(tokensPath?: string): string[] {\n return getAllValidTokenEntriesCached(tokensPath).map((entry) => entry.token);\n}\n\nexport function resolveConnectorFromToken(token: string, tokensPath?: string): string | undefined {\n return loadTokenStore(tokensPath).tokens.find((t) => t.token === token)?.connector;\n}\n"],"mappings":"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","names":[]}
@@ -213,7 +213,7 @@ import {
213
213
  } from "./chunk-OADWQ5CR.js";
214
214
  import {
215
215
  EngramAccessHttpServer
216
- } from "./chunk-J3UJJZKI.js";
216
+ } from "./chunk-SIPZ5UMK.js";
217
217
  import {
218
218
  WearablesInputError
219
219
  } from "./chunk-7WV3F5DQ.js";
@@ -226,7 +226,7 @@ import {
226
226
  createProductionChatLlmAdapter,
227
227
  loadChatSession,
228
228
  sessionBelongsToPrincipal
229
- } from "./chunk-TVLN5EZZ.js";
229
+ } from "./chunk-CNXMWYLA.js";
230
230
  import {
231
231
  EngramAccessService
232
232
  } from "./chunk-OAREUC7N.js";
@@ -7520,4 +7520,4 @@ export {
7520
7520
  listMemoryMarkdownFilePaths,
7521
7521
  registerCli
7522
7522
  };
7523
- //# sourceMappingURL=chunk-HBOPSFQQ.js.map
7523
+ //# sourceMappingURL=chunk-EVXI2I6G.js.map
@@ -5,7 +5,7 @@ import {
5
5
  loadTokenStore,
6
6
  revokeToken,
7
7
  saveTokenStore
8
- } from "./chunk-UG274TNV.js";
8
+ } from "./chunk-E2SPGGUI.js";
9
9
  import {
10
10
  getConnectorsDir,
11
11
  getRegistryPath
@@ -2257,4 +2257,4 @@ export {
2257
2257
  resolveWeCloneProxyConfigPath,
2258
2258
  buildWeCloneProxyConfig
2259
2259
  };
2260
- //# sourceMappingURL=chunk-4DLFJJOQ.js.map
2260
+ //# sourceMappingURL=chunk-QMN3CIFS.js.map
@@ -6,12 +6,13 @@ import {
6
6
  } from "./chunk-JBPKEARU.js";
7
7
  import {
8
8
  EngramMcpServer,
9
+ MCP_SUPPORTED_PROTOCOL_VERSIONS,
9
10
  cleanupExpiredChatSessions,
10
11
  loadChatSession,
11
12
  processChatMessage,
12
13
  sessionBelongsToPrincipal,
13
14
  subscribeChatTranscript
14
- } from "./chunk-TVLN5EZZ.js";
15
+ } from "./chunk-CNXMWYLA.js";
15
16
  import {
16
17
  validateRequest
17
18
  } from "./chunk-2MR3MFQB.js";
@@ -272,6 +273,23 @@ function parseHttpServerPort(port) {
272
273
  }
273
274
  return port;
274
275
  }
276
+ function assertResourceMetadataUrl(value) {
277
+ if (value === void 0) return void 0;
278
+ let parsed;
279
+ try {
280
+ parsed = new URL(value);
281
+ } catch {
282
+ throw new Error(
283
+ `access HTTP resourceMetadataUrl must be an absolute http(s) URL, got: ${value}`
284
+ );
285
+ }
286
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
287
+ throw new Error(
288
+ `access HTTP resourceMetadataUrl must use http or https, got: ${parsed.protocol}`
289
+ );
290
+ }
291
+ return value;
292
+ }
275
293
  function parseTrustZoneKindFilter(raw) {
276
294
  if (raw === null) return void 0;
277
295
  if (TRUST_ZONE_RECORD_KINDS.includes(raw)) {
@@ -343,6 +361,8 @@ var EngramAccessHttpServer = class {
343
361
  authToken;
344
362
  authTokens;
345
363
  authTokensGetter;
364
+ authTokenEntriesGetter;
365
+ tokenPathPolicy;
346
366
  authenticatedPrincipal;
347
367
  maxBodyBytes;
348
368
  adminConsoleEnabled;
@@ -352,6 +372,8 @@ var EngramAccessHttpServer = class {
352
372
  trustPrincipalHeader;
353
373
  adapterRegistry;
354
374
  readiness;
375
+ resourceMetadataUrl;
376
+ externalRequestHandler;
355
377
  writeRequestTimestamps = [];
356
378
  mcpServer;
357
379
  server = null;
@@ -381,6 +403,8 @@ var EngramAccessHttpServer = class {
381
403
  this.authToken = options.authToken?.trim() || void 0;
382
404
  this.authTokens = (options.authTokens ?? []).map((t) => t.trim()).filter(Boolean);
383
405
  this.authTokensGetter = options.authTokensGetter;
406
+ this.authTokenEntriesGetter = options.authTokenEntriesGetter;
407
+ this.tokenPathPolicy = options.tokenPathPolicy;
384
408
  this.authenticatedPrincipal = options.principal?.trim() || void 0;
385
409
  this.maxBodyBytes = Number.isFinite(options.maxBodyBytes) ? Math.max(1, Math.floor(options.maxBodyBytes ?? 131072)) : 131072;
386
410
  this.adminConsoleEnabled = options.adminConsoleEnabled !== false;
@@ -389,6 +413,8 @@ var EngramAccessHttpServer = class {
389
413
  this.adminControls = options.adminControls;
390
414
  this.trustPrincipalHeader = options.trustPrincipalHeader === true;
391
415
  this.readiness = options.readiness ?? (() => ({ ready: true, warmupAttempts: 0 }));
416
+ this.resourceMetadataUrl = assertResourceMetadataUrl(options.resourceMetadataUrl);
417
+ this.externalRequestHandler = options.externalRequestHandler;
392
418
  this.adapterRegistry = options.enableAdapters !== false ? options.adapterRegistry ?? new AdapterRegistry() : null;
393
419
  this.mcpServer = new EngramMcpServer(this.service, {
394
420
  principal: options.principal,
@@ -404,7 +430,7 @@ var EngramAccessHttpServer = class {
404
430
  });
405
431
  }
406
432
  async start() {
407
- if (!this.authToken && this.authTokens.length === 0 && !this.authTokensGetter) {
433
+ if (!this.authToken && this.authTokens.length === 0 && !this.authTokensGetter && !this.authTokenEntriesGetter) {
408
434
  throw new Error("engram access HTTP requires authToken or authTokens");
409
435
  }
410
436
  if (this.server) return this.status();
@@ -647,11 +673,27 @@ var EngramAccessHttpServer = class {
647
673
  return;
648
674
  }
649
675
  }
676
+ if (this.externalRequestHandler) {
677
+ const authorized = this.isAuthorized(req, pathname);
678
+ if (await this.externalRequestHandler(req, res, { authorized })) {
679
+ return;
680
+ }
681
+ }
650
682
  if (!this.isAuthorized(req, pathname)) {
651
683
  const body = JSON.stringify({ error: "unauthorized", code: "unauthorized" });
652
684
  res.writeHead(401, {
653
685
  "content-type": "application/json; charset=utf-8",
654
- "www-authenticate": "Bearer",
686
+ "www-authenticate": this.bearerChallenge(),
687
+ "x-request-id": correlationId
688
+ });
689
+ res.end(body);
690
+ return;
691
+ }
692
+ if (pathname === "/mcp" && (req.method === "GET" || req.method === "DELETE")) {
693
+ const body = JSON.stringify({ error: "method_not_allowed", code: "method_not_allowed" });
694
+ res.writeHead(405, {
695
+ "content-type": "application/json; charset=utf-8",
696
+ allow: "POST",
655
697
  "x-request-id": correlationId
656
698
  });
657
699
  res.end(body);
@@ -2253,6 +2295,20 @@ var EngramAccessHttpServer = class {
2253
2295
  req.once("error", cleanup);
2254
2296
  }
2255
2297
  async handleMcpRequest(req, res) {
2298
+ const headerVersion = req.headers["mcp-protocol-version"];
2299
+ if (typeof headerVersion === "string" && headerVersion.length > 0) {
2300
+ if (!MCP_SUPPORTED_PROTOCOL_VERSIONS.includes(headerVersion)) {
2301
+ this.respondJson(res, 400, {
2302
+ jsonrpc: "2.0",
2303
+ id: null,
2304
+ error: {
2305
+ code: -32e3,
2306
+ message: `unsupported MCP-Protocol-Version: ${headerVersion}; supported: ${MCP_SUPPORTED_PROTOCOL_VERSIONS.join(", ")}`
2307
+ }
2308
+ });
2309
+ return;
2310
+ }
2311
+ }
2256
2312
  const body = await this.readJsonBody(req);
2257
2313
  const request = body;
2258
2314
  const toolName = typeof request.params?.name === "string" ? request.params.name : "";
@@ -2552,8 +2608,23 @@ var EngramAccessHttpServer = class {
2552
2608
  }
2553
2609
  return result.data;
2554
2610
  }
2611
+ /**
2612
+ * Build the WWW-Authenticate challenge string for 401 responses.
2613
+ * When `resourceMetadataUrl` is configured, includes the RFC 9728
2614
+ * `resource_metadata` parameter so MCP clients (e.g. ChatGPT) can
2615
+ * discover the OAuth 2.0 protected-resource metadata document.
2616
+ * Otherwise the bare `Bearer` challenge is returned (unchanged).
2617
+ */
2618
+ bearerChallenge() {
2619
+ if (this.resourceMetadataUrl) {
2620
+ return `Bearer resource_metadata="${this.resourceMetadataUrl}"`;
2621
+ }
2622
+ return "Bearer";
2623
+ }
2555
2624
  isAuthorized(req, pathname) {
2556
- if (!this.authToken && this.authTokens.length === 0 && !this.authTokensGetter) return false;
2625
+ if (!this.authToken && this.authTokens.length === 0 && !this.authTokensGetter && !this.authTokenEntriesGetter) {
2626
+ return false;
2627
+ }
2557
2628
  const raw = req.headers.authorization;
2558
2629
  let candidate = null;
2559
2630
  if (raw) {
@@ -2581,6 +2652,15 @@ var EngramAccessHttpServer = class {
2581
2652
  for (const valid of this.authTokens) {
2582
2653
  if (this.timingSafeStringEqual(token, valid)) return true;
2583
2654
  }
2655
+ if (this.authTokenEntriesGetter) {
2656
+ for (const entry of this.authTokenEntriesGetter()) {
2657
+ if (!this.timingSafeStringEqual(token, entry.token)) continue;
2658
+ if (!this.tokenPathPolicy) return true;
2659
+ if (typeof entry.connector !== "string" || entry.connector.length === 0) return false;
2660
+ return this.tokenPathPolicy(entry.connector, pathname);
2661
+ }
2662
+ return false;
2663
+ }
2584
2664
  if (this.authTokensGetter) {
2585
2665
  for (const valid of this.authTokensGetter()) {
2586
2666
  if (this.timingSafeStringEqual(token, valid)) return true;
@@ -2664,4 +2744,4 @@ function positiveIntQueryParam(value, label) {
2664
2744
  export {
2665
2745
  EngramAccessHttpServer
2666
2746
  };
2667
- //# sourceMappingURL=chunk-J3UJJZKI.js.map
2747
+ //# sourceMappingURL=chunk-SIPZ5UMK.js.map