@suveren/gateway 0.8.6 → 0.8.7

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 (59) hide show
  1. package/dist/control-plane/index.mjs +262 -46
  2. package/dist/mcp-server/http.mjs +73 -9
  3. package/dist/ui/assets/index-tzStUGuj.js +110 -0
  4. package/dist/ui/icons/apple-touch-icon.png +0 -0
  5. package/dist/ui/icons/icon-192.png +0 -0
  6. package/dist/ui/icons/icon-512.png +0 -0
  7. package/dist/ui/icons/icon-maskable-512.png +0 -0
  8. package/dist/ui/index.html +7 -1
  9. package/dist/ui/manifest.webmanifest +15 -0
  10. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/auth.d.ts +15 -4
  11. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/auth.d.ts.map +1 -1
  12. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/auth.js +22 -3
  13. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/auth.js.map +1 -1
  14. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/express.d.ts.map +1 -1
  15. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/express.js +3 -1
  16. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/express.js.map +1 -1
  17. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/requestBody.d.ts +25 -0
  18. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/requestBody.d.ts.map +1 -0
  19. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/requestBody.js +64 -0
  20. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/requestBody.js.map +1 -0
  21. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/webStandardStreamableHttp.d.ts +10 -1
  22. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/webStandardStreamableHttp.d.ts.map +1 -1
  23. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/webStandardStreamableHttp.js +13 -1
  24. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/webStandardStreamableHttp.js.map +1 -1
  25. package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/auth.d.ts +15 -4
  26. package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/auth.d.ts.map +1 -1
  27. package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/auth.js +22 -3
  28. package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/auth.js.map +1 -1
  29. package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/express.d.ts.map +1 -1
  30. package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/express.js +3 -1
  31. package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/express.js.map +1 -1
  32. package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/requestBody.d.ts +25 -0
  33. package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/requestBody.d.ts.map +1 -0
  34. package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/requestBody.js +58 -0
  35. package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/requestBody.js.map +1 -0
  36. package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/webStandardStreamableHttp.d.ts +10 -1
  37. package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/webStandardStreamableHttp.d.ts.map +1 -1
  38. package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/webStandardStreamableHttp.js +13 -1
  39. package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/webStandardStreamableHttp.js.map +1 -1
  40. package/node_modules/@modelcontextprotocol/sdk/package.json +1 -1
  41. package/node_modules/hono/dist/adapter/aws-lambda/handler.js +6 -3
  42. package/node_modules/hono/dist/adapter/lambda-edge/handler.js +26 -12
  43. package/node_modules/hono/dist/cjs/adapter/aws-lambda/handler.js +6 -3
  44. package/node_modules/hono/dist/cjs/adapter/lambda-edge/handler.js +26 -12
  45. package/node_modules/hono/dist/cjs/helper/accepts/accepts.js +7 -5
  46. package/node_modules/hono/dist/cjs/jsx/components.js +1 -1
  47. package/node_modules/hono/dist/cjs/jsx/streaming.js +1 -1
  48. package/node_modules/hono/dist/cjs/middleware/pretty-json/index.js +7 -1
  49. package/node_modules/hono/dist/cjs/router/linear-router/router.js +3 -3
  50. package/node_modules/hono/dist/cjs/utils/jwt/jwt.js +7 -6
  51. package/node_modules/hono/dist/helper/accepts/accepts.js +7 -5
  52. package/node_modules/hono/dist/jsx/components.js +1 -1
  53. package/node_modules/hono/dist/jsx/streaming.js +1 -1
  54. package/node_modules/hono/dist/middleware/pretty-json/index.js +7 -1
  55. package/node_modules/hono/dist/router/linear-router/router.js +3 -3
  56. package/node_modules/hono/dist/utils/jwt/jwt.js +7 -6
  57. package/node_modules/hono/package.json +1 -1
  58. package/package.json +1 -1
  59. package/dist/ui/assets/index-DWpjjPIf.js +0 -110
@@ -134,6 +134,13 @@ var SPClient = class {
134
134
  sessionCookie = "";
135
135
  apiKey = "";
136
136
  receiptRetry;
137
+ /** Set when a call comes back 401 while we held a session — distinct from
138
+ * never having logged in. Cleared the moment a fresh cookie is configured. */
139
+ lockReason = null;
140
+ /** Single-flight per client instance: only the FIRST 401 after a session
141
+ * was active tells the control plane. Concurrent in-flight calls that were
142
+ * all sent under the same now-dead cookie would otherwise all report it. */
143
+ sessionExpiredNotified = false;
137
144
  /** The AS base URL this client talks to (for provenance records). */
138
145
  get url() {
139
146
  return this.baseUrl;
@@ -143,6 +150,22 @@ var SPClient = class {
143
150
  }
144
151
  setSessionCookie(cookie) {
145
152
  this.sessionCookie = cookie;
153
+ this.lockReason = null;
154
+ this.sessionExpiredNotified = false;
155
+ }
156
+ /**
157
+ * Drop the session because the control plane learned (by whatever path —
158
+ * one of OUR calls 401ing, or its own AS proxy 401ing) that it ended.
159
+ * Idempotent: safe to call redundantly from the control-plane's push.
160
+ */
161
+ clearSession() {
162
+ this.sessionCookie = "";
163
+ this.lockReason = "expired";
164
+ }
165
+ /** Why we are currently locked, if known. `null` means the default
166
+ * boot-locked case (nobody has signed in yet this run). */
167
+ getLockReason() {
168
+ return this.sessionCookie === "" ? this.lockReason : null;
146
169
  }
147
170
  /**
148
171
  * Has anyone signed in yet this run?
@@ -174,10 +197,20 @@ var SPClient = class {
174
197
  if (this.apiKey) {
175
198
  headers["X-API-Key"] = this.apiKey;
176
199
  }
177
- return globalThis.fetch(url, {
200
+ const hadSession = this.sessionCookie !== "";
201
+ const res = await globalThis.fetch(url, {
178
202
  ...init,
179
203
  headers
180
204
  });
205
+ if (res.status === 401 && hadSession) {
206
+ this.sessionCookie = "";
207
+ this.lockReason = "expired";
208
+ if (!this.sessionExpiredNotified) {
209
+ this.sessionExpiredNotified = true;
210
+ void notifyControlPlane("session-expired");
211
+ }
212
+ }
213
+ return res;
181
214
  }
182
215
  /**
183
216
  * Get SP public key.
@@ -1578,14 +1611,22 @@ function uiUrl() {
1578
1611
  const port2 = process.env.SUVEREN_CP_PORT ?? "3400";
1579
1612
  return `http://localhost:${port2}`;
1580
1613
  }
1581
- function lockedNotice(action) {
1582
- const what = action ? `Cannot ${action}: the` : "The";
1583
- return `${what} Suveren gateway is running but LOCKED, so it cannot read any authorizations or credentials.
1614
+ function bootLockedNotice(what) {
1615
+ return `${what} Suveren gateway is running but LOCKED, so it cannot read any mandates or credentials.
1584
1616
 
1585
- This is not the same as having no authorizations \u2014 yours are on disk, encrypted, and unreadable until someone unlocks them.
1617
+ This is not the same as having no mandates \u2014 yours are on disk, encrypted, and unreadable until someone unlocks them.
1586
1618
 
1587
1619
  TELL THE USER: open ${uiUrl()} and enter your Suveren API key to unlock the gateway. It boots locked by design after every restart; the key is never stored.`;
1588
1620
  }
1621
+ function expiredSessionNotice(what) {
1622
+ return `${what} Suveren gateway's sign-in has ended (sign-ins last 30 days, or it was revoked), so it is now LOCKED.
1623
+
1624
+ TELL THE USER: open ${uiUrl()} and enter your Suveren API key to sign in again. Your mandates are safe; nothing ran.`;
1625
+ }
1626
+ function lockedNotice(action, reason = "restart") {
1627
+ const what = action ? `Cannot ${action}: the` : "The";
1628
+ return reason === "expired" ? expiredSessionNotice(what) : bootLockedNotice(what);
1629
+ }
1589
1630
 
1590
1631
  // src/tools/authorizations.ts
1591
1632
  import { getProfile as getProfile4 } from "@hap/core";
@@ -2109,7 +2150,7 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
2109
2150
  const lockedGuard = (inner) => async (args) => {
2110
2151
  if (!state2.spClient.isUnlocked()) {
2111
2152
  return {
2112
- content: [{ type: "text", text: lockedNotice(`use ${tool.namespacedName}`) }],
2153
+ content: [{ type: "text", text: lockedNotice(`use ${tool.namespacedName}`, state2.spClient.getLockReason() ?? "restart") }],
2113
2154
  isError: true
2114
2155
  };
2115
2156
  }
@@ -2421,6 +2462,12 @@ Proposal ID: ${proposal.id}. Check status with check-pending-commitments(proposa
2421
2462
  }]
2422
2463
  };
2423
2464
  } catch (err) {
2465
+ if (!state2.spClient.isUnlocked()) {
2466
+ return {
2467
+ content: [{ type: "text", text: lockedNotice(`use ${tool.namespacedName}`, "expired") }],
2468
+ isError: true
2469
+ };
2470
+ }
2424
2471
  return {
2425
2472
  content: [{ type: "text", text: `Failed to submit proposal: ${err instanceof Error ? err.message : String(err)}` }],
2426
2473
  isError: true
@@ -2516,6 +2563,12 @@ Proposal ID: ${proposal.id}. Use check-pending-commitments to track status.`
2516
2563
  }]
2517
2564
  };
2518
2565
  } catch (proposalErr) {
2566
+ if (!state2.spClient.isUnlocked()) {
2567
+ return {
2568
+ content: [{ type: "text", text: lockedNotice(`use ${tool.namespacedName}`, "expired") }],
2569
+ isError: true
2570
+ };
2571
+ }
2519
2572
  return {
2520
2573
  content: [{ type: "text", text: `Failed to submit approval proposal: ${proposalErr instanceof Error ? proposalErr.message : String(proposalErr)}` }],
2521
2574
  isError: true
@@ -2537,6 +2590,12 @@ Proposal ID: ${proposal.id}. Use check-pending-commitments to track status.`
2537
2590
  isError: true
2538
2591
  };
2539
2592
  }
2593
+ if (!state2.spClient.isUnlocked()) {
2594
+ return {
2595
+ content: [{ type: "text", text: lockedNotice(`use ${tool.namespacedName}`, "expired") }],
2596
+ isError: true
2597
+ };
2598
+ }
2540
2599
  return {
2541
2600
  content: [{ type: "text", text: `SP unavailable \u2014 tool call blocked. ${err instanceof Error ? err.message : ""}` }],
2542
2601
  isError: true
@@ -2667,7 +2726,7 @@ function buildCapabilityMap(profileId, toolGating, integrationManager2) {
2667
2726
  function listAuthorizationsHandler(state2, integrationManager2, contextDir) {
2668
2727
  return async (args) => {
2669
2728
  if (!state2.spClient.isUnlocked()) {
2670
- return { content: [{ type: "text", text: lockedNotice("list authorizations") }] };
2729
+ return { content: [{ type: "text", text: lockedNotice("list authorizations", state2.spClient.getLockReason() ?? "restart") }] };
2671
2730
  }
2672
2731
  const authorizations = state2.getEnrichedAuthorizations();
2673
2732
  const now = Math.floor(Date.now() / 1e3);
@@ -2861,7 +2920,7 @@ function shortProfileName3(profileId) {
2861
2920
  function listIntegrationsHandler(state2, integrationManager2) {
2862
2921
  return async () => {
2863
2922
  if (!state2.spClient.isUnlocked()) {
2864
- return { content: [{ type: "text", text: lockedNotice("list integrations") }] };
2923
+ return { content: [{ type: "text", text: lockedNotice("list integrations", state2.spClient.getLockReason() ?? "restart") }] };
2865
2924
  }
2866
2925
  if (!integrationManager2) {
2867
2926
  return { content: [{ type: "text", text: "No integration manager available." }] };
@@ -3067,7 +3126,7 @@ Result: ${resultText}` };
3067
3126
  function checkPendingCommitmentsHandler(state2, integrationManager2) {
3068
3127
  return async (args) => {
3069
3128
  if (!state2.spClient.isUnlocked()) {
3070
- return { content: [{ type: "text", text: lockedNotice("check commitments") }] };
3129
+ return { content: [{ type: "text", text: lockedNotice("check commitments", state2.spClient.getLockReason() ?? "restart") }] };
3071
3130
  }
3072
3131
  try {
3073
3132
  if (args.proposal_id) {
@@ -4312,6 +4371,11 @@ app.post("/internal/configure", internalOnly, (req, res) => {
4312
4371
  }
4313
4372
  res.json({ ok: true });
4314
4373
  });
4374
+ app.post("/internal/clear-session", internalOnly, (_req, res) => {
4375
+ state.spClient.clearSession();
4376
+ console.error("[Suveren MCP] Session cleared by control-plane (AS session ended)");
4377
+ res.json({ ok: true });
4378
+ });
4315
4379
  app.post("/internal/gate-content", internalOnly, async (req, res) => {
4316
4380
  try {
4317
4381
  const { authorizationId, boundsHash, contextHash, context, contextLabels, path: rawPath, gateContent } = req.body;