@axiom-lattice/gateway 4.3.2 → 4.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.
Files changed (73) hide show
  1. package/dist/{a2a-standard-ALXWP7V6.mjs → a2a-standard-P7JDL6ZA.mjs} +63 -35
  2. package/dist/a2a-standard-P7JDL6ZA.mjs.map +1 -0
  3. package/dist/{registerExposedPlugins-63EUF4VS.mjs → chunk-3XIIQ6EK.mjs} +62 -18
  4. package/dist/chunk-3XIIQ6EK.mjs.map +1 -0
  5. package/dist/chunk-526UYVNQ.mjs +157 -0
  6. package/dist/chunk-526UYVNQ.mjs.map +1 -0
  7. package/dist/chunk-73QZQQOT.mjs +43 -0
  8. package/dist/chunk-73QZQQOT.mjs.map +1 -0
  9. package/dist/{chunk-GF4GGTWJ.mjs → chunk-HVIKCUOF.mjs} +10 -7
  10. package/dist/chunk-HVIKCUOF.mjs.map +1 -0
  11. package/dist/embed/embed-page-AgentDetailSideApp-PSRQVFNR.js +1 -0
  12. package/dist/embed/{embed-page-AgentGovernanceWorkbench-ZNW72EEJ.js → embed-page-AgentGovernanceWorkbench-UPQHJZD7.js} +1 -1
  13. package/dist/embed/embed-page-MessageGraph-PDNQ67T4.js +6 -0
  14. package/dist/embed/{embed-page-TasksSideApp-F4XUHPTG.css → embed-page-MessageGraph-UT2TCPSO.css} +1 -1
  15. package/dist/embed/{embed-page-ReactChart-XS7D26VP.js → embed-page-ReactChart-25KAFLQ3.js} +1 -1
  16. package/dist/embed/embed-page-ReactChart-MXGF35OQ.css +1 -0
  17. package/dist/embed/embed-page-SkillFlow-TKBMB5VV.js +1 -0
  18. package/dist/embed/{embed-page-TasksSideApp-MQTUCJO4.js → embed-page-TasksSideApp-DLH5LRMS.js} +1 -1
  19. package/dist/embed/{embed-page-TopologyRuntimeView-2NQ3OOYI.css → embed-page-TasksSideApp-HV6DKQ26.css} +1 -1
  20. package/dist/embed/{embed-page-TeamWorkspace-7T2HRXLG.js → embed-page-TeamWorkspace-2WJJWF25.js} +1 -1
  21. package/dist/embed/{embed-page-TeamWorkspace-DXKGNQ3C.css → embed-page-TeamWorkspace-YQFWXDLI.css} +1 -1
  22. package/dist/embed/{embed-page-MessageGraph-TYOOATYV.css → embed-page-TopologyRuntimeView-CWOXLVFW.css} +1 -1
  23. package/dist/embed/{embed-page-TopologyRuntimeView-PU2D7VZ7.js → embed-page-TopologyRuntimeView-WDQRHDMO.js} +1 -1
  24. package/dist/embed/{embed-page-WorkflowAutomationView-ST76M33F.js → embed-page-WorkflowAutomationView-BAMJ2WVB.js} +1 -1
  25. package/dist/embed/embed-page-WorkflowAutomationView-YQFB3CTV.css +1 -0
  26. package/dist/embed/{embed-page-chunk-T25T2YE6.js → embed-page-chunk-HMTKPZ5B.js} +7 -5
  27. package/dist/embed/{embed-page-chunk-ZKJ7Z7P3.js → embed-page-chunk-LOXLXZNP.js} +1 -1
  28. package/dist/embed/{embed-page-chunk-Z4S4OYA4.js → embed-page-chunk-MQDYZS3S.js} +1 -1
  29. package/dist/embed/embed-page-chunk-OBM5EUWZ.js +6974 -0
  30. package/dist/embed/{embed-page-chunk-RLP3BC7G.js → embed-page-chunk-SSC7ANWV.js} +2 -2
  31. package/dist/embed/embed-page-chunk-TL2AYM76.js +2 -0
  32. package/dist/embed/{embed-page-chunk-LE6OFJ7R.js → embed-page-chunk-XVUA7UHE.js} +1 -1
  33. package/dist/embed/embed-page.js +1 -1
  34. package/dist/index.js +5103 -3988
  35. package/dist/index.js.map +1 -1
  36. package/dist/index.mjs +1566 -411
  37. package/dist/index.mjs.map +1 -1
  38. package/dist/{open-HH7RKGML.mjs → open-AMEMJOFJ.mjs} +99 -14
  39. package/dist/open-AMEMJOFJ.mjs.map +1 -0
  40. package/dist/{open-catalog-WYS2AENM.mjs → open-catalog-SZQ435AY.mjs} +34 -3
  41. package/dist/open-catalog-SZQ435AY.mjs.map +1 -0
  42. package/dist/{open-exposure-FK4G6O6L.mjs → open-exposure-I44Q4M3T.mjs} +37 -7
  43. package/dist/open-exposure-I44Q4M3T.mjs.map +1 -0
  44. package/dist/{open-files-GVRMK5PL.mjs → open-files-EJ6O2SGX.mjs} +4 -4
  45. package/dist/{open-session-3VN65PRR.mjs → open-session-LBTCU3AE.mjs} +4 -4
  46. package/dist/{openLattice-GAAGQJST.mjs → openLattice-Q6M5S6CY.mjs} +4 -2
  47. package/dist/registerExposedPlugins-6GCOE64C.mjs +11 -0
  48. package/dist/{resources-JFKAPS45.mjs → resources-DXPXAXEZ.mjs} +16 -9
  49. package/dist/{resources-JFKAPS45.mjs.map → resources-DXPXAXEZ.mjs.map} +1 -1
  50. package/package.json +11 -8
  51. package/dist/a2a-standard-ALXWP7V6.mjs.map +0 -1
  52. package/dist/chunk-GF4GGTWJ.mjs.map +0 -1
  53. package/dist/chunk-QKPZWWHH.mjs +0 -433
  54. package/dist/chunk-QKPZWWHH.mjs.map +0 -1
  55. package/dist/embed/embed-page-AgentDetailSideApp-7PQ2JKG6.js +0 -1
  56. package/dist/embed/embed-page-MessageGraph-P7DBTZCL.js +0 -6
  57. package/dist/embed/embed-page-ReactChart-VBM467ER.css +0 -1
  58. package/dist/embed/embed-page-SkillFlow-HPH37PZH.js +0 -1
  59. package/dist/embed/embed-page-WorkflowAutomationView-MAYS27YJ.css +0 -1
  60. package/dist/embed/embed-page-chunk-4ZSGPIBZ.js +0 -1
  61. package/dist/embed/embed-page-chunk-BRETUPSG.js +0 -4094
  62. package/dist/embed/embed-page-chunk-I42T7B7V.js +0 -2415
  63. package/dist/embed/embed-page-chunk-VRV57DFE.js +0 -466
  64. package/dist/embed/embed-page-chunk-ZXFZANZM.js +0 -1
  65. package/dist/mcp-configs-WCY5EXCW.mjs +0 -30
  66. package/dist/open-HH7RKGML.mjs.map +0 -1
  67. package/dist/open-catalog-WYS2AENM.mjs.map +0 -1
  68. package/dist/open-exposure-FK4G6O6L.mjs.map +0 -1
  69. package/dist/registerExposedPlugins-63EUF4VS.mjs.map +0 -1
  70. /package/dist/{open-files-GVRMK5PL.mjs.map → open-files-EJ6O2SGX.mjs.map} +0 -0
  71. /package/dist/{open-session-3VN65PRR.mjs.map → open-session-LBTCU3AE.mjs.map} +0 -0
  72. /package/dist/{mcp-configs-WCY5EXCW.mjs.map → openLattice-Q6M5S6CY.mjs.map} +0 -0
  73. /package/dist/{openLattice-GAAGQJST.mjs.map → registerExposedPlugins-6GCOE64C.mjs.map} +0 -0
@@ -1,19 +1,27 @@
1
+ import {
2
+ buildTenantOpenPlugins,
3
+ mergeOpenCatalog
4
+ } from "./chunk-526UYVNQ.mjs";
1
5
  import {
2
6
  deleteSession,
3
7
  getSession,
4
8
  registerSession
5
9
  } from "./chunk-K32XEQLH.mjs";
10
+ import {
11
+ collectExposedPluginUiResources
12
+ } from "./chunk-3XIIQ6EK.mjs";
6
13
  import {
7
14
  OpenAuditService,
8
15
  OpenCredentialService
9
16
  } from "./chunk-52SUWMOX.mjs";
17
+ import "./chunk-73QZQQOT.mjs";
18
+ import "./chunk-WKNZ7GBC.mjs";
10
19
  import {
11
20
  ActionNotFoundError,
12
21
  GrantDeniedError,
13
22
  ValidationError,
14
23
  openLattice
15
- } from "./chunk-GF4GGTWJ.mjs";
16
- import "./chunk-WKNZ7GBC.mjs";
24
+ } from "./chunk-HVIKCUOF.mjs";
17
25
  import "./chunk-NFEGQTCC.mjs";
18
26
 
19
27
  // src/routes/open.ts
@@ -67,8 +75,14 @@ var OPEN_MCP_SERVER_INFO = {
67
75
  version: "0.1.0"
68
76
  };
69
77
  async function buildSessionServer(options) {
70
- const { credential, grants, catalog, invoke } = options;
71
- const server = new McpServer(OPEN_MCP_SERVER_INFO);
78
+ const { credential, catalog, invoke, uiResources } = options;
79
+ const server = new McpServer(OPEN_MCP_SERVER_INFO, {
80
+ capabilities: {
81
+ // The SDK has no `extensions` field; MCP App hosts discover the UI
82
+ // extension through `experimental`.
83
+ experimental: { "io.modelcontextprotocol/ui": {} }
84
+ }
85
+ });
72
86
  for (const tool of catalog) {
73
87
  server.registerTool(
74
88
  tool.toolName,
@@ -78,7 +92,8 @@ async function buildSessionServer(options) {
78
92
  annotations: {
79
93
  readOnlyHint: tool.annotations.readOnlyHint,
80
94
  destructiveHint: tool.annotations.destructiveHint
81
- }
95
+ },
96
+ ...tool.ui ? { _meta: { ui: { resourceUri: tool.ui.resourceUri } } } : {}
82
97
  },
83
98
  async (args) => {
84
99
  const result = await invoke(tool.toolName, args);
@@ -86,6 +101,30 @@ async function buildSessionServer(options) {
86
101
  }
87
102
  );
88
103
  }
104
+ for (const [uri, res] of uiResources ?? []) {
105
+ const mimeType = res.mimeType ?? "text/html;profile=mcp-app";
106
+ server.registerResource(
107
+ `mcp-app:${uri}`,
108
+ uri,
109
+ {
110
+ mimeType,
111
+ ...res.csp ? { _meta: { ui: { csp: res.csp } } } : {}
112
+ },
113
+ async (u) => {
114
+ const { html, csp } = await res.read();
115
+ return {
116
+ contents: [
117
+ {
118
+ uri: u.href,
119
+ mimeType,
120
+ text: html,
121
+ ...csp ? { _meta: { ui: { csp } } } : {}
122
+ }
123
+ ]
124
+ };
125
+ }
126
+ );
127
+ }
89
128
  const transport = new StreamableHTTPServerTransport({
90
129
  sessionIdGenerator: () => {
91
130
  const sessionId = randomUUID();
@@ -103,7 +142,6 @@ async function buildSessionServer(options) {
103
142
  );
104
143
  }
105
144
  });
106
- await server.connect(transport);
107
145
  return { server, transport, tools: catalog };
108
146
  }
109
147
 
@@ -118,6 +156,20 @@ function readSessionId(request) {
118
156
  const value = Array.isArray(raw) ? raw[0] : raw;
119
157
  return typeof value === "string" && value.length > 0 ? value : void 0;
120
158
  }
159
+ function emptyTenantPlugins() {
160
+ return {
161
+ tools: [],
162
+ uiResources: /* @__PURE__ */ new Map(),
163
+ hasTool: () => false,
164
+ degraded: true,
165
+ invoke: async (toolName) => ({
166
+ content: [
167
+ { type: "text", text: `Tenant plugin catalog unavailable for ${toolName}` }
168
+ ],
169
+ isError: true
170
+ })
171
+ };
172
+ }
121
173
  var RPC_ERROR = {
122
174
  GRANT_DENIED: -32001,
123
175
  ACTION_NOT_FOUND: -32002,
@@ -161,10 +213,34 @@ function registerOpenRoutes(app, deps = {}) {
161
213
  }
162
214
  const grants = grantsSource(context);
163
215
  const catalog = await openLattice.catalogFor(grants, { tenantId: context.tenantId });
216
+ const globalToolNames = new Set(catalog.map((t) => t.toolName));
217
+ const buildTenant = deps.tenantPlugins ?? ((tenantId, tenantGrants, reservedToolNames) => buildTenantOpenPlugins(tenantId, tenantGrants, { reservedToolNames }));
218
+ let tenant;
219
+ try {
220
+ tenant = await buildTenant(context.tenantId, grants, globalToolNames);
221
+ if (tenant.degraded) {
222
+ request.log.warn(
223
+ { tenantId: context.tenantId },
224
+ "open mcp: tenant plugin catalog degraded; serving global tools only"
225
+ );
226
+ }
227
+ } catch (error) {
228
+ request.log.warn(
229
+ { tenantId: context.tenantId, err: error },
230
+ "open mcp: tenant plugin catalog unavailable; serving global tools only"
231
+ );
232
+ tenant = emptyTenantPlugins();
233
+ }
234
+ const mergedCatalog = mergeOpenCatalog(catalog, tenant.tools);
235
+ const uiResources = new Map([
236
+ ...collectExposedPluginUiResources(grants),
237
+ ...tenant.uiResources
238
+ ]);
164
239
  const built = await buildSessionServer({
165
240
  credential: { credentialId: context.credentialId, kind: context.kind },
166
241
  grants,
167
- catalog,
242
+ catalog: mergedCatalog,
243
+ uiResources,
168
244
  invoke: async (toolName, args) => {
169
245
  const startedAt = Date.now();
170
246
  const sep = toolName.indexOf("_");
@@ -192,16 +268,23 @@ function registerOpenRoutes(app, deps = {}) {
192
268
  );
193
269
  }
194
270
  try {
195
- const result = await openLattice.invoke(
271
+ const isTenantRoute = tenant.hasTool(toolName) && !globalToolNames.has(toolName);
272
+ const runConfig = {
273
+ sandboxName: context.sandboxName,
274
+ credentialId: context.credentialId
275
+ };
276
+ const result = isTenantRoute ? await tenant.invoke(toolName, args, {
277
+ tenantId: context.tenantId,
278
+ projectId: context.projectId,
279
+ workspaceId: context.workspaceId,
280
+ runConfig
281
+ }) : await openLattice.invoke(
196
282
  grants,
197
283
  {
198
284
  tenantId: context.tenantId,
199
285
  projectId: context.projectId,
200
286
  workspaceId: context.workspaceId,
201
- runConfig: {
202
- sandboxName: context.sandboxName,
203
- credentialId: context.credentialId
204
- }
287
+ runConfig
205
288
  },
206
289
  toolName,
207
290
  args
@@ -214,7 +297,8 @@ function registerOpenRoutes(app, deps = {}) {
214
297
  domain: auditDomain,
215
298
  action: auditAction,
216
299
  args,
217
- status: "ok",
300
+ status: result.isError ? "error" : "ok",
301
+ ...result.isError ? { errorCode: "EXECUTION_ERROR" } : {},
218
302
  durationMs: Date.now() - startedAt
219
303
  });
220
304
  return result;
@@ -239,6 +323,7 @@ function registerOpenRoutes(app, deps = {}) {
239
323
  }
240
324
  }
241
325
  });
326
+ await built.server.connect(built.transport);
242
327
  reply.hijack();
243
328
  await built.transport.handleRequest(request.raw, reply.raw, body);
244
329
  });
@@ -257,4 +342,4 @@ function registerOpenRoutes(app, deps = {}) {
257
342
  export {
258
343
  registerOpenRoutes
259
344
  };
260
- //# sourceMappingURL=open-HH7RKGML.mjs.map
345
+ //# sourceMappingURL=open-AMEMJOFJ.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/routes/open.ts","../src/services/open/openRateLimiter.ts","../src/services/open/openMcpServer.ts"],"sourcesContent":["/**\n * Open API surface routes: POST/DELETE /open/mcp.\n *\n * The route is a thin shell — credential resolution (OpenAuthService),\n * catalog filtering and invocation (OpenLattice), and session lifecycle\n * (mcpTransportRegistry) live in services/open/*. The transport writes the\n * response itself, so the reply is hijacked (same precedent as the Project\n * Room SSE route).\n */\n\nimport type { FastifyInstance, FastifyReply, FastifyRequest } from \"fastify\";\nimport { isInitializeRequest } from \"@modelcontextprotocol/sdk/types.js\";\nimport { getStoreLattice } from \"@axiom-lattice/core\";\nimport type { A2AApiKeyStore, OpenAuditStore, OpenGrant, ProjectStore } from \"@axiom-lattice/protocols\";\nimport { OpenCredentialService } from \"../services/open/openCredentialService\";\nimport { OpenAuditService } from \"../services/open/openAuditService\";\nimport { OpenRateLimiter } from \"../services/open/openRateLimiter\";\nimport type { OpenCredential } from \"../services/open/openCredentialService\";\nimport {\n openLattice,\n ActionNotFoundError,\n GrantDeniedError,\n ValidationError,\n} from \"../services/open/openLattice\";\nimport { buildSessionServer } from \"../services/open/openMcpServer\";\nimport { collectExposedPluginUiResources } from \"../services/open/registerExposedPlugins\";\nimport {\n buildTenantOpenPlugins,\n mergeOpenCatalog,\n type TenantOpenPlugins,\n} from \"../services/open/tenantOpenPlugins\";\nimport { getSession, deleteSession } from \"../services/open/mcpTransportRegistry\";\n\nexport interface OpenRouteDeps {\n openAuth?: OpenCredentialService;\n grantsSource?: (ctx: OpenCredential) => OpenGrant[];\n audit?: OpenAuditService;\n rateLimiter?: OpenRateLimiter;\n /**\n * Test seam: build the credential's tenant sandbox plugin bundle. Defaults to\n * {@link buildTenantOpenPlugins} with the global catalog's tool names reserved\n * so a tenant tool can never shadow a global/built-in one.\n */\n tenantPlugins?: (\n tenantId: string,\n grants: OpenGrant[],\n reservedToolNames: ReadonlySet<string>,\n ) => Promise<TenantOpenPlugins>;\n}\n\nfunction extractBearer(request: FastifyRequest): string | undefined {\n const header = request.headers.authorization;\n if (!header?.startsWith(\"Bearer \")) return undefined;\n return header.slice(\"Bearer \".length).trim() || undefined;\n}\n\nfunction readSessionId(request: FastifyRequest): string | undefined {\n const raw = request.headers[\"mcp-session-id\"];\n const value = Array.isArray(raw) ? raw[0] : raw;\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\n/**\n * Degraded tenant plugin bundle used when the sandbox catalog cannot be read.\n *\n * A catalog read failure must not 500 the session: the MCP server still builds\n * and serves the global tools. `invoke` is unreachable through normal routing\n * (`hasTool` is always false, so calls route to OpenLattice) but returns an\n * error result if it is ever reached.\n */\nfunction emptyTenantPlugins(): TenantOpenPlugins {\n return {\n tools: [],\n uiResources: new Map(),\n hasTool: () => false,\n degraded: true,\n invoke: async (toolName) => ({\n content: [\n { type: \"text\", text: `Tenant plugin catalog unavailable for ${toolName}` },\n ],\n isError: true,\n }),\n };\n}\n\n/** JSON-RPC error codes used on the Open surface. */\nconst RPC_ERROR = {\n GRANT_DENIED: -32001,\n ACTION_NOT_FOUND: -32002,\n VALIDATION_FAILED: -32003,\n EXECUTION_ERROR: -32004,\n RATE_LIMITED: -32005,\n} as const;\n\nexport function registerOpenRoutes(\n app: FastifyInstance,\n deps: OpenRouteDeps = {},\n): void {\n const openAuth =\n deps.openAuth ??\n new OpenCredentialService(\n getStoreLattice(\"default\", \"a2aApiKey\").store as A2AApiKeyStore,\n getStoreLattice(\"default\", \"project\").store as ProjectStore,\n );\n const grantsSource =\n deps.grantsSource ?? ((ctx: OpenCredential) => ctx.grants);\n const audit =\n deps.audit ??\n (() => {\n try {\n const store = getStoreLattice(\"default\", \"openAudit\").store as OpenAuditStore;\n return new OpenAuditService(store);\n } catch {\n return undefined;\n }\n })();\n\n const rateLimiter = deps.rateLimiter ?? new OpenRateLimiter();\n\n app.post(\"/open/mcp\", async (request: FastifyRequest, reply: FastifyReply) => {\n const context = await openAuth.resolve(extractBearer(request));\n if (!context) {\n return reply.status(401).send({ error: \"AUTH_FAILED\" });\n }\n\n const body: unknown = request.body;\n const sessionId = readSessionId(request);\n\n // Existing session: reuse its server/transport. Bearer must match the\n // session's credential — a session is not usable by another key.\n if (sessionId) {\n const session = await getSession(sessionId);\n if (\n !session ||\n session.credential.credentialId !== context.credentialId\n ) {\n return reply.status(404).send({ error: \"SESSION_NOT_FOUND\" });\n }\n reply.hijack();\n await session.transport.handleRequest(request.raw, reply.raw, body);\n return;\n }\n\n // New session: only initialize may create one. Registration into the\n // session registry happens inside sessionIdGenerator (see openMcpServer).\n if (!isInitializeRequest(body)) {\n return reply.status(400).send({ error: \"SESSION_REQUIRED\" });\n }\n\n const grants = grantsSource(context);\n const catalog = await openLattice.catalogFor(grants, { tenantId: context.tenantId });\n // Tenant sandbox plugins are volume-resident and outside the OpenLattice\n // registry; merge their grant-filtered tools/UI alongside the globals.\n // Global/built-in tool names are reserved so a tenant plugin of the same\n // type (kb/sandbox/agent) can neither crash `registerTool` with a duplicate\n // nor capture the invoke routing for a global tool.\n const globalToolNames = new Set(catalog.map((t) => t.toolName));\n const buildTenant =\n deps.tenantPlugins ??\n ((tenantId, tenantGrants, reservedToolNames) =>\n buildTenantOpenPlugins(tenantId, tenantGrants, { reservedToolNames }));\n let tenant: TenantOpenPlugins;\n try {\n tenant = await buildTenant(context.tenantId, grants, globalToolNames);\n if (tenant.degraded) {\n // The catalog's common failure mode is a diagnostic, not a throw\n // (`ensureLoaded` returns error-level diagnostics). Surface it and\n // serve global tools only.\n request.log.warn(\n { tenantId: context.tenantId },\n \"open mcp: tenant plugin catalog degraded; serving global tools only\",\n );\n }\n } catch (error) {\n // Sandbox catalogs are volume-resident and can be transiently\n // unavailable; degrade to globals-only rather than failing the whole\n // session so external hosts can still connect. `err` keeps the stack.\n request.log.warn(\n { tenantId: context.tenantId, err: error },\n \"open mcp: tenant plugin catalog unavailable; serving global tools only\",\n );\n tenant = emptyTenantPlugins();\n }\n const mergedCatalog = mergeOpenCatalog(catalog, tenant.tools);\n const uiResources = new Map([\n ...collectExposedPluginUiResources(grants),\n ...tenant.uiResources,\n ]);\n const built = await buildSessionServer({\n credential: { credentialId: context.credentialId, kind: context.kind },\n grants,\n catalog: mergedCatalog,\n uiResources,\n invoke: async (toolName, args) => {\n const startedAt = Date.now();\n const sep = toolName.indexOf(\"_\");\n const auditDomain = sep > 0 ? toolName.slice(0, sep) : toolName;\n const auditAction = sep > 0 ? toolName.slice(sep + 1) : \"\";\n\n const decision = rateLimiter.check(context.credentialId, auditDomain);\n if (!decision.allowed) {\n await audit?.record({\n tenantId: context.tenantId,\n credentialId: context.credentialId,\n credentialKind: context.kind,\n projectId: context.projectId,\n domain: auditDomain,\n action: auditAction,\n args,\n status: \"error\",\n errorCode: \"RATE_LIMITED\",\n durationMs: Date.now() - startedAt,\n });\n throw Object.assign(\n new Error(\n `RATE_LIMITED: retry after ${decision.retryAfterMs ?? 1000}ms`,\n ),\n { rpcCode: RPC_ERROR.RATE_LIMITED },\n );\n }\n\n try {\n const isTenantRoute = tenant.hasTool(toolName) && !globalToolNames.has(toolName);\n const runConfig: Record<string, unknown> = {\n sandboxName: context.sandboxName,\n credentialId: context.credentialId,\n };\n const result = isTenantRoute\n ? await tenant.invoke(toolName, args, {\n tenantId: context.tenantId,\n projectId: context.projectId,\n workspaceId: context.workspaceId,\n runConfig,\n })\n : await openLattice.invoke(\n grants,\n {\n tenantId: context.tenantId,\n projectId: context.projectId,\n workspaceId: context.workspaceId,\n runConfig,\n },\n toolName,\n args,\n );\n await audit?.record({\n tenantId: context.tenantId,\n credentialId: context.credentialId,\n credentialKind: context.kind,\n projectId: context.projectId,\n domain: auditDomain,\n action: auditAction,\n args,\n status: result.isError ? \"error\" : \"ok\",\n ...(result.isError ? { errorCode: \"EXECUTION_ERROR\" } : {}),\n durationMs: Date.now() - startedAt,\n });\n return result;\n } catch (err) {\n const code =\n err instanceof GrantDeniedError\n ? RPC_ERROR.GRANT_DENIED\n : err instanceof ActionNotFoundError\n ? RPC_ERROR.ACTION_NOT_FOUND\n : err instanceof ValidationError\n ? RPC_ERROR.VALIDATION_FAILED\n : RPC_ERROR.EXECUTION_ERROR;\n await audit?.record({\n tenantId: context.tenantId,\n credentialId: context.credentialId,\n credentialKind: context.kind,\n projectId: context.projectId,\n domain: auditDomain,\n action: auditAction,\n args,\n status: \"error\",\n errorCode:\n err instanceof GrantDeniedError\n ? \"GRANT_DENIED\"\n : err instanceof ActionNotFoundError\n ? \"ACTION_NOT_FOUND\"\n : err instanceof ValidationError\n ? \"VALIDATION_FAILED\"\n : \"EXECUTION_ERROR\",\n durationMs: Date.now() - startedAt,\n });\n throw Object.assign(\n new Error(err instanceof Error ? err.message : String(err)),\n { rpcCode: code },\n );\n }\n },\n });\n // buildSessionServer no longer connects internally (so it is testable\n // over InMemoryTransport); the transport still needs an onmessage handler\n // before handleRequest or initialize is swallowed and the request hangs.\n await built.server.connect(built.transport);\n reply.hijack();\n await built.transport.handleRequest(request.raw, reply.raw, body);\n });\n\n app.delete(\n \"/open/mcp\",\n async (request: FastifyRequest, reply: FastifyReply) => {\n const sessionId = readSessionId(request);\n if (!sessionId) {\n return reply.status(400).send({ error: \"SESSION_REQUIRED\" });\n }\n await deleteSession(sessionId);\n return reply.status(204).send();\n },\n );\n}\n","/**\n * In-memory per-credential rate limiter (token bucket). The agent domain gets\n * a stricter bucket because each call consumes LLM budget.\n *\n * Process-local by design (V1 single-gateway); a distributed limiter is V2.\n */\n\nexport interface RateLimitDecision {\n allowed: boolean;\n retryAfterMs?: number;\n}\n\ninterface Bucket {\n tokens: number;\n updatedAt: number;\n}\n\nexport interface OpenRateLimiterOptions {\n /** Default requests per minute per credential. */\n defaultPerMinute?: number;\n /** Stricter limit for the agent domain. */\n agentPerMinute?: number;\n}\n\nexport class OpenRateLimiter {\n private readonly buckets = new Map<string, Bucket>();\n private readonly defaultPerMinute: number;\n private readonly agentPerMinute: number;\n\n constructor(options: OpenRateLimiterOptions = {}) {\n this.defaultPerMinute = options.defaultPerMinute ?? 60;\n this.agentPerMinute = options.agentPerMinute ?? 10;\n }\n\n private bucketClass(domain: string): string {\n return domain === \"agent\" ? \"agent\" : \"default\";\n }\n\n private limitFor(domain: string): number {\n return domain === \"agent\" ? this.agentPerMinute : this.defaultPerMinute;\n }\n\n /** Consume one token; denied calls report how long to wait. */\n check(credentialId: string, domain: string): RateLimitDecision {\n const limit = this.limitFor(domain);\n const key = `${this.bucketClass(domain)}:${credentialId}`;\n const now = Date.now();\n const bucket = this.buckets.get(key) ?? { tokens: limit, updatedAt: now };\n\n const refillPerMs = limit / 60_000;\n const elapsed = now - bucket.updatedAt;\n bucket.tokens = Math.min(limit, bucket.tokens + elapsed * refillPerMs);\n bucket.updatedAt = now;\n\n if (bucket.tokens >= 1) {\n bucket.tokens -= 1;\n this.buckets.set(key, bucket);\n return { allowed: true };\n }\n\n this.buckets.set(key, bucket);\n const deficit = 1 - bucket.tokens;\n return { allowed: false, retryAfterMs: Math.ceil(deficit / refillPerMs) };\n }\n\n /** Test helper. */\n reset(): void {\n this.buckets.clear();\n }\n}\n","/**\n * Builds a per-session MCP server instance whose tool list is the\n * credential-filtered OpenLattice catalog. Per-session instances are what\n * make grant-filtered tools/list possible on the official SDK.\n *\n * Session registration happens inside sessionIdGenerator — the SDK invokes\n * it exactly once while handling initialize, which guarantees the registry\n * key equals the transport-generated id that the response header carries.\n *\n * Every tool executor routes through the OpenLattice invoke kernel, so\n * authorization is re-checked on each invocation (default-deny, live grants).\n */\n\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\nimport { randomUUID } from \"crypto\";\nimport type { McpUiCsp, OpenGrant } from \"@axiom-lattice/protocols\";\nimport type { OpenToolDescriptor } from \"./openLattice\";\nimport { registerSession } from \"./mcpTransportRegistry\";\n\nexport const OPEN_MCP_SERVER_INFO = {\n name: \"axiom-open\",\n version: \"0.1.0\",\n} as const;\n\n/**\n * A `ui://` HTML resource served by the session. `read` is invoked per\n * `resources/read`, so the caller can fetch plugin UI lazily.\n */\nexport interface OpenUiResource {\n /** MIME for the resource; defaults to \"text/html;profile=mcp-app\". */\n mimeType?: string;\n csp?: McpUiCsp;\n read: () => Promise<{ html: string; csp?: McpUiCsp }>;\n}\n\nexport interface BuiltSession {\n server: McpServer;\n transport: StreamableHTTPServerTransport;\n tools: OpenToolDescriptor[];\n}\n\n/**\n * Builds (but does not connect) a per-session MCP server. The caller MUST\n * connect it to a transport (`await built.server.connect(built.transport)`)\n * before handling requests — otherwise the transport has no onmessage\n * handler and initialize is swallowed. Deferring connect is what lets tests\n * attach the server to an `InMemoryTransport`.\n */\nexport async function buildSessionServer(options: {\n credential: { credentialId: string; kind: string };\n grants: OpenGrant[];\n catalog: OpenToolDescriptor[];\n invoke: (\n toolName: string,\n args: unknown,\n ) => Promise<{\n content: Array<{ type: \"text\"; text: string }>;\n isError?: boolean;\n }>;\n uiResources?: Map<string, OpenUiResource>;\n}): Promise<BuiltSession> {\n const { credential, catalog, invoke, uiResources } = options;\n const server = new McpServer(OPEN_MCP_SERVER_INFO, {\n capabilities: {\n // The SDK has no `extensions` field; MCP App hosts discover the UI\n // extension through `experimental`.\n experimental: { \"io.modelcontextprotocol/ui\": {} },\n },\n });\n for (const tool of catalog) {\n server.registerTool(\n tool.toolName,\n {\n description: tool.description,\n inputSchema: tool.schema,\n annotations: {\n readOnlyHint: tool.annotations.readOnlyHint,\n destructiveHint: tool.annotations.destructiveHint,\n },\n ...(tool.ui ? { _meta: { ui: { resourceUri: tool.ui.resourceUri } } } : {}),\n },\n async (args: unknown) => {\n const result = await invoke(tool.toolName, args);\n return { content: result.content, isError: result.isError };\n },\n );\n }\n for (const [uri, res] of uiResources ?? []) {\n const mimeType = res.mimeType ?? \"text/html;profile=mcp-app\";\n server.registerResource(\n `mcp-app:${uri}`,\n uri,\n {\n mimeType,\n ...(res.csp ? { _meta: { ui: { csp: res.csp } } } : {}),\n },\n async (u) => {\n const { html, csp } = await res.read();\n return {\n contents: [\n {\n uri: u.href,\n mimeType,\n text: html,\n ...(csp ? { _meta: { ui: { csp } } } : {}),\n },\n ],\n };\n },\n );\n }\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: () => {\n const sessionId = randomUUID();\n void registerSession(\n sessionId,\n { credentialId: credential.credentialId, kind: credential.kind },\n { server, transport },\n );\n return sessionId;\n },\n enableJsonResponse: true,\n onsessionclosed: (closedId: string) => {\n void import(\"./mcpTransportRegistry\").then(({ deleteSession }) =>\n deleteSession(closedId),\n );\n },\n });\n return { server, transport, tools: catalog };\n}\n"],"mappings":"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","names":["deleteSession"]}
@@ -1,16 +1,21 @@
1
1
  import {
2
2
  openLattice
3
- } from "./chunk-GF4GGTWJ.mjs";
3
+ } from "./chunk-HVIKCUOF.mjs";
4
4
  import "./chunk-NFEGQTCC.mjs";
5
5
 
6
6
  // src/routes/open-catalog.ts
7
7
  import { getStoreLattice } from "@axiom-lattice/core";
8
8
 
9
9
  // src/services/open/openCatalogService.ts
10
+ import { getSandboxPluginCatalog } from "@axiom-lattice/core";
11
+ async function defaultListTenantPlugins(tenantId) {
12
+ return (await getSandboxPluginCatalog().ensureLoaded(tenantId)).plugins;
13
+ }
10
14
  var OpenCatalogService = class {
11
- constructor(assistantStore, lattice = openLattice) {
15
+ constructor(assistantStore, lattice = openLattice, listTenantPlugins = defaultListTenantPlugins) {
12
16
  this.assistantStore = assistantStore;
13
17
  this.lattice = lattice;
18
+ this.listTenantPlugins = listTenantPlugins;
14
19
  }
15
20
  async buildForTenant(tenantId) {
16
21
  const domains = this.lattice.describeCatalog();
@@ -33,6 +38,32 @@ var OpenCatalogService = class {
33
38
  enriched.push(domain);
34
39
  }
35
40
  }
41
+ const knownDomains = new Set(domains.map((d) => d.domain));
42
+ let plugins = [];
43
+ try {
44
+ plugins = await this.listTenantPlugins(tenantId);
45
+ } catch {
46
+ plugins = [];
47
+ }
48
+ for (const plugin of plugins) {
49
+ const type = plugin.meta.type;
50
+ if (knownDomains.has(type)) continue;
51
+ knownDomains.add(type);
52
+ const items = (plugin.meta.tools ?? []).filter((t) => t.expose).map((t) => ({
53
+ id: t.name,
54
+ toolName: `${type}_${t.name}`,
55
+ label: t.name,
56
+ description: t.description
57
+ }));
58
+ enriched.push({
59
+ domain: type,
60
+ label: plugin.meta.name,
61
+ source: "plugin",
62
+ scopeKind: "tenant",
63
+ itemKind: "action",
64
+ items
65
+ });
66
+ }
36
67
  return { domains: enriched };
37
68
  }
38
69
  };
@@ -56,4 +87,4 @@ function registerOpenCatalogRoutes(app) {
56
87
  export {
57
88
  registerOpenCatalogRoutes
58
89
  };
59
- //# sourceMappingURL=open-catalog-WYS2AENM.mjs.map
90
+ //# sourceMappingURL=open-catalog-SZQ435AY.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/routes/open-catalog.ts","../src/services/open/openCatalogService.ts"],"sourcesContent":["/**\n * Open capability catalog endpoint (console-session guarded): returns the\n * grantable capabilities for the caller's tenant, driving the key picker.\n */\n\nimport type { FastifyInstance, FastifyReply, FastifyRequest } from \"fastify\";\nimport { getStoreLattice } from \"@axiom-lattice/core\";\nimport type { AssistantStore } from \"@axiom-lattice/protocols\";\nimport { OpenCatalogService } from \"../services/open/openCatalogService\";\n\ninterface RequestWithUser extends FastifyRequest {\n user?: { id: string; tenantId?: string };\n}\n\nfunction tenantIdOf(request: FastifyRequest): string | undefined {\n return (request as RequestWithUser).user?.tenantId;\n}\n\nexport function registerOpenCatalogRoutes(app: FastifyInstance): void {\n app.get(\"/api/open/catalog\", async (request: FastifyRequest, reply: FastifyReply) => {\n const tenantId = tenantIdOf(request);\n if (!tenantId) {\n return reply.status(401).send({ success: false, error: \"Unauthorized\" });\n }\n const assistantStore = getStoreLattice(\"default\", \"assistant\")\n .store as AssistantStore;\n const service = new OpenCatalogService(assistantStore);\n const catalog = await service.buildForTenant(tenantId);\n return reply.status(200).send({ success: true, data: catalog });\n });\n}\n","/**\n * OpenCatalogService — assembles the tenant-scoped grantable capability\n * catalog for the key-management picker: registry domains (built-in + plugin)\n * plus per-tenant agent instances and tenant sandbox plugin domains.\n */\n\nimport { getSandboxPluginCatalog } from \"@axiom-lattice/core\";\nimport type {\n AssistantStore,\n OpenCatalog,\n OpenCatalogDomain,\n OpenCatalogItem,\n Plugin,\n} from \"@axiom-lattice/protocols\";\nimport { OpenLattice, openLattice } from \"./openLattice\";\n\n/** Reads the tenant's sandbox plugins; injectable for tests. */\nexport type TenantPluginLister = (tenantId: string) => Promise<Plugin[]>;\n\n/**\n * Default tenant plugin source: the process-wide sandbox plugin catalog.\n * A missing/unconfigured sandbox degrades to an empty list.\n */\nasync function defaultListTenantPlugins(tenantId: string): Promise<Plugin[]> {\n return (await getSandboxPluginCatalog().ensureLoaded(tenantId)).plugins;\n}\n\nexport class OpenCatalogService {\n constructor(\n private readonly assistantStore: AssistantStore,\n private readonly lattice: OpenLattice = openLattice,\n private readonly listTenantPlugins: TenantPluginLister = defaultListTenantPlugins,\n ) {}\n\n async buildForTenant(tenantId: string): Promise<OpenCatalog> {\n const domains = this.lattice.describeCatalog();\n const enriched: OpenCatalogDomain[] = [];\n for (const domain of domains) {\n if (domain.domain === \"agent\" && domain.itemKind === \"instance\") {\n let items: OpenCatalogDomain[\"items\"] = [];\n try {\n const assistants = await this.assistantStore.getAllAssistants(tenantId);\n items = assistants.map((a) => ({\n id: a.id,\n toolName: `invoke_${a.id}`,\n label: a.name || a.id,\n }));\n } catch {\n items = [];\n }\n enriched.push({ ...domain, items });\n } else {\n enriched.push(domain);\n }\n }\n\n // Tenant sandbox plugins live in per-tenant volumes and are not part of the\n // OpenLattice registry, so without this the console picker could never grant\n // their domains. Global domains win on collision (consistent with\n // `mergeOpenCatalog`).\n const knownDomains = new Set(domains.map((d) => d.domain));\n let plugins: Plugin[] = [];\n try {\n plugins = await this.listTenantPlugins(tenantId);\n } catch {\n plugins = [];\n }\n for (const plugin of plugins) {\n const type = plugin.meta.type;\n if (knownDomains.has(type)) continue;\n knownDomains.add(type);\n const items: OpenCatalogItem[] = (plugin.meta.tools ?? [])\n .filter((t) => t.expose)\n .map((t) => ({\n id: t.name,\n toolName: `${type}_${t.name}`,\n label: t.name,\n description: t.description,\n }));\n enriched.push({\n domain: type,\n label: plugin.meta.name,\n source: \"plugin\",\n scopeKind: \"tenant\",\n itemKind: \"action\",\n items,\n });\n }\n return { domains: enriched };\n }\n}\n"],"mappings":";;;;;;AAMA,SAAS,uBAAuB;;;ACAhC,SAAS,+BAA+B;AAiBxC,eAAe,yBAAyB,UAAqC;AAC3E,UAAQ,MAAM,wBAAwB,EAAE,aAAa,QAAQ,GAAG;AAClE;AAEO,IAAM,qBAAN,MAAyB;AAAA,EAC9B,YACmB,gBACA,UAAuB,aACvB,oBAAwC,0BACzD;AAHiB;AACA;AACA;AAAA,EAChB;AAAA,EAEH,MAAM,eAAe,UAAwC;AAC3D,UAAM,UAAU,KAAK,QAAQ,gBAAgB;AAC7C,UAAM,WAAgC,CAAC;AACvC,eAAW,UAAU,SAAS;AAC5B,UAAI,OAAO,WAAW,WAAW,OAAO,aAAa,YAAY;AAC/D,YAAI,QAAoC,CAAC;AACzC,YAAI;AACF,gBAAM,aAAa,MAAM,KAAK,eAAe,iBAAiB,QAAQ;AACtE,kBAAQ,WAAW,IAAI,CAAC,OAAO;AAAA,YAC7B,IAAI,EAAE;AAAA,YACN,UAAU,UAAU,EAAE,EAAE;AAAA,YACxB,OAAO,EAAE,QAAQ,EAAE;AAAA,UACrB,EAAE;AAAA,QACJ,QAAQ;AACN,kBAAQ,CAAC;AAAA,QACX;AACA,iBAAS,KAAK,EAAE,GAAG,QAAQ,MAAM,CAAC;AAAA,MACpC,OAAO;AACL,iBAAS,KAAK,MAAM;AAAA,MACtB;AAAA,IACF;AAMA,UAAM,eAAe,IAAI,IAAI,QAAQ,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC;AACzD,QAAI,UAAoB,CAAC;AACzB,QAAI;AACF,gBAAU,MAAM,KAAK,kBAAkB,QAAQ;AAAA,IACjD,QAAQ;AACN,gBAAU,CAAC;AAAA,IACb;AACA,eAAW,UAAU,SAAS;AAC5B,YAAM,OAAO,OAAO,KAAK;AACzB,UAAI,aAAa,IAAI,IAAI,EAAG;AAC5B,mBAAa,IAAI,IAAI;AACrB,YAAM,SAA4B,OAAO,KAAK,SAAS,CAAC,GACrD,OAAO,CAAC,MAAM,EAAE,MAAM,EACtB,IAAI,CAAC,OAAO;AAAA,QACX,IAAI,EAAE;AAAA,QACN,UAAU,GAAG,IAAI,IAAI,EAAE,IAAI;AAAA,QAC3B,OAAO,EAAE;AAAA,QACT,aAAa,EAAE;AAAA,MACjB,EAAE;AACJ,eAAS,KAAK;AAAA,QACZ,QAAQ;AAAA,QACR,OAAO,OAAO,KAAK;AAAA,QACnB,QAAQ;AAAA,QACR,WAAW;AAAA,QACX,UAAU;AAAA,QACV;AAAA,MACF,CAAC;AAAA,IACH;AACA,WAAO,EAAE,SAAS,SAAS;AAAA,EAC7B;AACF;;;AD5EA,SAAS,WAAW,SAA6C;AAC/D,SAAQ,QAA4B,MAAM;AAC5C;AAEO,SAAS,0BAA0B,KAA4B;AACpE,MAAI,IAAI,qBAAqB,OAAO,SAAyB,UAAwB;AACnF,UAAM,WAAW,WAAW,OAAO;AACnC,QAAI,CAAC,UAAU;AACb,aAAO,MAAM,OAAO,GAAG,EAAE,KAAK,EAAE,SAAS,OAAO,OAAO,eAAe,CAAC;AAAA,IACzE;AACA,UAAM,iBAAiB,gBAAgB,WAAW,WAAW,EAC1D;AACH,UAAM,UAAU,IAAI,mBAAmB,cAAc;AACrD,UAAM,UAAU,MAAM,QAAQ,eAAe,QAAQ;AACrD,WAAO,MAAM,OAAO,GAAG,EAAE,KAAK,EAAE,SAAS,MAAM,MAAM,QAAQ,CAAC;AAAA,EAChE,CAAC;AACH;","names":[]}
@@ -1,15 +1,20 @@
1
1
  import {
2
- openLattice
3
- } from "./chunk-GF4GGTWJ.mjs";
2
+ buildTenantOpenPlugins,
3
+ mergeOpenCatalog
4
+ } from "./chunk-526UYVNQ.mjs";
5
+ import "./chunk-73QZQQOT.mjs";
4
6
  import {
5
7
  buildAgentCardForAssistant
6
8
  } from "./chunk-QUT2DS4R.mjs";
9
+ import {
10
+ openLattice
11
+ } from "./chunk-HVIKCUOF.mjs";
7
12
  import "./chunk-NFEGQTCC.mjs";
8
13
 
9
14
  // src/routes/open-exposure.ts
10
15
  import { getStoreLattice } from "@axiom-lattice/core";
11
16
  import { effectiveGrants } from "@axiom-lattice/protocols";
12
- function registerOpenExposureRoutes(app) {
17
+ function registerOpenExposureRoutes(app, deps = {}) {
13
18
  app.get(
14
19
  "/api/keys/:id/exposure",
15
20
  async (request, reply) => {
@@ -26,11 +31,29 @@ function registerOpenExposureRoutes(app) {
26
31
  const forwardedHost = request.headers["x-forwarded-host"];
27
32
  const host = (Array.isArray(forwardedHost) ? forwardedHost[0] : forwardedHost) ?? (Array.isArray(request.headers.host) ? request.headers.host[0] : request.headers.host) ?? request.hostname;
28
33
  const baseUrl = `${request.protocol}://${host}`;
29
- const tools = (await openLattice.catalogFor(grants, { tenantId })).map((t) => ({
34
+ const globalCatalog = await openLattice.catalogFor(grants, { tenantId });
35
+ let tenantTools = [];
36
+ let tenantCatalogDegraded = false;
37
+ try {
38
+ const tenant = await buildTenantOpenPlugins(tenantId, grants, {
39
+ ...deps.tenantPluginDeps,
40
+ reservedToolNames: new Set(globalCatalog.map((t) => t.toolName))
41
+ });
42
+ tenantTools = tenant.tools;
43
+ tenantCatalogDegraded = tenant.degraded;
44
+ } catch (error) {
45
+ tenantCatalogDegraded = true;
46
+ request.log.warn(
47
+ { tenantId, err: error },
48
+ "key exposure: tenant plugin catalog unavailable"
49
+ );
50
+ }
51
+ const tools = mergeOpenCatalog(globalCatalog, tenantTools).map((t) => ({
30
52
  domain: t.domain,
31
53
  toolName: t.toolName,
32
54
  description: t.description,
33
- annotations: t.annotations
55
+ annotations: t.annotations,
56
+ hasUi: Boolean(t.ui)
34
57
  }));
35
58
  const agentGrant = grants.find((g) => g.domain === "agent");
36
59
  const assistantStore = getStoreLattice("default", "assistant").store;
@@ -54,7 +77,14 @@ function registerOpenExposureRoutes(app) {
54
77
  tenantId,
55
78
  projectId: record.projectId,
56
79
  grants,
57
- mcp: { endpoint: `${baseUrl}/open/mcp`, tools },
80
+ mcp: {
81
+ endpoint: `${baseUrl}/open/mcp`,
82
+ tools,
83
+ // Surfaced so the console can distinguish "no tenant tools granted"
84
+ // from "tenant catalog unreadable"; the live Open session degrades
85
+ // to globals-only either way.
86
+ tenantCatalogDegraded
87
+ },
58
88
  a2a: { agents }
59
89
  }
60
90
  });
@@ -64,4 +94,4 @@ function registerOpenExposureRoutes(app) {
64
94
  export {
65
95
  registerOpenExposureRoutes
66
96
  };
67
- //# sourceMappingURL=open-exposure-FK4G6O6L.mjs.map
97
+ //# sourceMappingURL=open-exposure-I44Q4M3T.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/routes/open-exposure.ts"],"sourcesContent":["/**\n * Key exposure endpoint: what a given API key exposes to external callers —\n * the MCP tools (per grants) and the A2A agent cards (for granted agents).\n * Console-session guarded and tenant-scoped.\n */\n\nimport type { FastifyInstance, FastifyReply, FastifyRequest } from \"fastify\";\nimport { getStoreLattice } from \"@axiom-lattice/core\";\nimport type {\n A2AApiKeyStore,\n AssistantStore,\n OpenGrant,\n} from \"@axiom-lattice/protocols\";\nimport { effectiveGrants } from \"@axiom-lattice/protocols\";\nimport {\n openLattice,\n type OpenToolDescriptor,\n} from \"../services/open/openLattice\";\nimport {\n buildTenantOpenPlugins,\n mergeOpenCatalog,\n type TenantOpenPluginDeps,\n} from \"../services/open/tenantOpenPlugins\";\nimport { buildAgentCardForAssistant } from \"../services/a2a/A2AAgentCardBuilder\";\n\ninterface RequestWithUser extends FastifyRequest {\n user?: { id: string; tenantId?: string };\n}\n\n/** Overridable seams for the exposure route (tests / embedding). */\nexport interface OpenExposureDeps {\n /** Tenant sandbox plugin catalog seams; defaults to the live sandbox catalog. */\n tenantPluginDeps?: TenantOpenPluginDeps;\n}\n\nexport function registerOpenExposureRoutes(\n app: FastifyInstance,\n deps: OpenExposureDeps = {},\n): void {\n app.get<{ Params: { id: string } }>(\n \"/api/keys/:id/exposure\",\n async (\n request: FastifyRequest<{ Params: { id: string } }>,\n reply: FastifyReply,\n ) => {\n const tenantId = (request as RequestWithUser).user?.tenantId;\n if (!tenantId) {\n return reply.status(401).send({ success: false, error: \"Unauthorized\" });\n }\n\n const keyStore = getStoreLattice(\"default\", \"a2aApiKey\")\n .store as A2AApiKeyStore;\n const record = await keyStore.findById(request.params.id);\n if (!record || record.tenantId !== tenantId) {\n return reply.status(404).send({ success: false, error: \"Key not found\" });\n }\n\n const grants: OpenGrant[] = effectiveGrants(record);\n // Include the port: request.hostname strips it, request.headers.host keeps it.\n const forwardedHost = request.headers[\"x-forwarded-host\"];\n const host =\n (Array.isArray(forwardedHost) ? forwardedHost[0] : forwardedHost) ??\n (Array.isArray(request.headers.host) ? request.headers.host[0] : request.headers.host) ??\n request.hostname;\n const baseUrl = `${request.protocol}://${host}`;\n\n // MCP surface: the credential-filtered tool catalog.\n const globalCatalog = await openLattice.catalogFor(grants, { tenantId });\n\n // Tenant sandbox plugins live in per-tenant volumes, outside the\n // OpenLattice registry, so the globals are merged with the credential's\n // grant-filtered tenant tools (global wins) to mirror the actual Open MCP\n // session. A missing/unavailable sandbox catalog degrades to globals.\n let tenantTools: OpenToolDescriptor[] = [];\n let tenantCatalogDegraded = false;\n try {\n const tenant = await buildTenantOpenPlugins(tenantId, grants, {\n ...deps.tenantPluginDeps,\n reservedToolNames: new Set(globalCatalog.map((t) => t.toolName)),\n });\n tenantTools = tenant.tools;\n tenantCatalogDegraded = tenant.degraded;\n } catch (error) {\n tenantCatalogDegraded = true;\n request.log.warn(\n { tenantId, err: error },\n \"key exposure: tenant plugin catalog unavailable\",\n );\n }\n\n const tools = mergeOpenCatalog(globalCatalog, tenantTools).map((t) => ({\n domain: t.domain,\n toolName: t.toolName,\n description: t.description,\n annotations: t.annotations,\n hasUi: Boolean(t.ui),\n }));\n\n // A2A surface: one card per granted agent.\n const agentGrant = grants.find((g) => g.domain === \"agent\");\n const assistantStore = getStoreLattice(\"default\", \"assistant\")\n .store as AssistantStore;\n const agents: Array<{\n assistantId: string;\n cardUrl: string;\n jsonrpcUrl: string;\n card: unknown;\n }> = [];\n for (const assistantId of agentGrant?.items ?? []) {\n const assistant = await assistantStore.getAssistantById(\n tenantId,\n assistantId,\n );\n if (!assistant) continue;\n agents.push({\n assistantId,\n cardUrl: `${baseUrl}/api/a2a/agents/${assistantId}/.well-known/agent-card.json`,\n jsonrpcUrl: `${baseUrl}/api/a2a/agents/${assistantId}/jsonrpc`,\n card: buildAgentCardForAssistant({ assistant, baseUrl }),\n });\n }\n\n return reply.status(200).send({\n success: true,\n data: {\n tenantId,\n projectId: record.projectId,\n grants,\n mcp: {\n endpoint: `${baseUrl}/open/mcp`,\n tools,\n // Surfaced so the console can distinguish \"no tenant tools granted\"\n // from \"tenant catalog unreadable\"; the live Open session degrades\n // to globals-only either way.\n tenantCatalogDegraded,\n },\n a2a: { agents },\n },\n });\n },\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AAOA,SAAS,uBAAuB;AAMhC,SAAS,uBAAuB;AAsBzB,SAAS,2BACd,KACA,OAAyB,CAAC,GACpB;AACN,MAAI;AAAA,IACF;AAAA,IACA,OACE,SACA,UACG;AACH,YAAM,WAAY,QAA4B,MAAM;AACpD,UAAI,CAAC,UAAU;AACb,eAAO,MAAM,OAAO,GAAG,EAAE,KAAK,EAAE,SAAS,OAAO,OAAO,eAAe,CAAC;AAAA,MACzE;AAEA,YAAM,WAAW,gBAAgB,WAAW,WAAW,EACpD;AACH,YAAM,SAAS,MAAM,SAAS,SAAS,QAAQ,OAAO,EAAE;AACxD,UAAI,CAAC,UAAU,OAAO,aAAa,UAAU;AAC3C,eAAO,MAAM,OAAO,GAAG,EAAE,KAAK,EAAE,SAAS,OAAO,OAAO,gBAAgB,CAAC;AAAA,MAC1E;AAEA,YAAM,SAAsB,gBAAgB,MAAM;AAElD,YAAM,gBAAgB,QAAQ,QAAQ,kBAAkB;AACxD,YAAM,QACH,MAAM,QAAQ,aAAa,IAAI,cAAc,CAAC,IAAI,mBAClD,MAAM,QAAQ,QAAQ,QAAQ,IAAI,IAAI,QAAQ,QAAQ,KAAK,CAAC,IAAI,QAAQ,QAAQ,SACjF,QAAQ;AACV,YAAM,UAAU,GAAG,QAAQ,QAAQ,MAAM,IAAI;AAG7C,YAAM,gBAAgB,MAAM,YAAY,WAAW,QAAQ,EAAE,SAAS,CAAC;AAMvE,UAAI,cAAoC,CAAC;AACzC,UAAI,wBAAwB;AAC5B,UAAI;AACF,cAAM,SAAS,MAAM,uBAAuB,UAAU,QAAQ;AAAA,UAC5D,GAAG,KAAK;AAAA,UACR,mBAAmB,IAAI,IAAI,cAAc,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC;AAAA,QACjE,CAAC;AACD,sBAAc,OAAO;AACrB,gCAAwB,OAAO;AAAA,MACjC,SAAS,OAAO;AACd,gCAAwB;AACxB,gBAAQ,IAAI;AAAA,UACV,EAAE,UAAU,KAAK,MAAM;AAAA,UACvB;AAAA,QACF;AAAA,MACF;AAEA,YAAM,QAAQ,iBAAiB,eAAe,WAAW,EAAE,IAAI,CAAC,OAAO;AAAA,QACrE,QAAQ,EAAE;AAAA,QACV,UAAU,EAAE;AAAA,QACZ,aAAa,EAAE;AAAA,QACf,aAAa,EAAE;AAAA,QACf,OAAO,QAAQ,EAAE,EAAE;AAAA,MACrB,EAAE;AAGF,YAAM,aAAa,OAAO,KAAK,CAAC,MAAM,EAAE,WAAW,OAAO;AAC1D,YAAM,iBAAiB,gBAAgB,WAAW,WAAW,EAC1D;AACH,YAAM,SAKD,CAAC;AACN,iBAAW,eAAe,YAAY,SAAS,CAAC,GAAG;AACjD,cAAM,YAAY,MAAM,eAAe;AAAA,UACrC;AAAA,UACA;AAAA,QACF;AACA,YAAI,CAAC,UAAW;AAChB,eAAO,KAAK;AAAA,UACV;AAAA,UACA,SAAS,GAAG,OAAO,mBAAmB,WAAW;AAAA,UACjD,YAAY,GAAG,OAAO,mBAAmB,WAAW;AAAA,UACpD,MAAM,2BAA2B,EAAE,WAAW,QAAQ,CAAC;AAAA,QACzD,CAAC;AAAA,MACH;AAEA,aAAO,MAAM,OAAO,GAAG,EAAE,KAAK;AAAA,QAC5B,SAAS;AAAA,QACT,MAAM;AAAA,UACJ;AAAA,UACA,WAAW,OAAO;AAAA,UAClB;AAAA,UACA,KAAK;AAAA,YACH,UAAU,GAAG,OAAO;AAAA,YACpB;AAAA;AAAA;AAAA;AAAA,YAIA;AAAA,UACF;AAAA,UACA,KAAK,EAAE,OAAO;AAAA,QAChB;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;","names":[]}
@@ -2,13 +2,13 @@ import {
2
2
  OpenAuditService,
3
3
  OpenCredentialService
4
4
  } from "./chunk-52SUWMOX.mjs";
5
- import {
6
- openLattice
7
- } from "./chunk-GF4GGTWJ.mjs";
8
5
  import {
9
6
  resolveContextSandbox
10
7
  } from "./chunk-TJAUXMYJ.mjs";
11
8
  import "./chunk-WKNZ7GBC.mjs";
9
+ import {
10
+ openLattice
11
+ } from "./chunk-HVIKCUOF.mjs";
12
12
  import "./chunk-NFEGQTCC.mjs";
13
13
 
14
14
  // src/routes/open-files.ts
@@ -143,4 +143,4 @@ function registerOpenFilesRoutes(app, deps = {}) {
143
143
  export {
144
144
  registerOpenFilesRoutes
145
145
  };
146
- //# sourceMappingURL=open-files-GVRMK5PL.mjs.map
146
+ //# sourceMappingURL=open-files-EJ6O2SGX.mjs.map
@@ -1,6 +1,3 @@
1
- import {
2
- openLattice
3
- } from "./chunk-GF4GGTWJ.mjs";
4
1
  import {
5
2
  BROWSER_GRANTS
6
3
  } from "./chunk-2POW27EI.mjs";
@@ -8,6 +5,9 @@ import {
8
5
  init_openToken,
9
6
  signOpenToken
10
7
  } from "./chunk-WKNZ7GBC.mjs";
8
+ import {
9
+ openLattice
10
+ } from "./chunk-HVIKCUOF.mjs";
11
11
  import "./chunk-NFEGQTCC.mjs";
12
12
 
13
13
  // src/routes/open-session.ts
@@ -50,4 +50,4 @@ function registerOpenSessionRoutes(app) {
50
50
  export {
51
51
  registerOpenSessionRoutes
52
52
  };
53
- //# sourceMappingURL=open-session-3VN65PRR.mjs.map
53
+ //# sourceMappingURL=open-session-LBTCU3AE.mjs.map
@@ -3,14 +3,16 @@ import {
3
3
  GrantDeniedError,
4
4
  OpenLattice,
5
5
  ValidationError,
6
+ grantCovers,
6
7
  openLattice
7
- } from "./chunk-GF4GGTWJ.mjs";
8
+ } from "./chunk-HVIKCUOF.mjs";
8
9
  import "./chunk-NFEGQTCC.mjs";
9
10
  export {
10
11
  ActionNotFoundError,
11
12
  GrantDeniedError,
12
13
  OpenLattice,
13
14
  ValidationError,
15
+ grantCovers,
14
16
  openLattice
15
17
  };
16
- //# sourceMappingURL=openLattice-GAAGQJST.mjs.map
18
+ //# sourceMappingURL=openLattice-Q6M5S6CY.mjs.map
@@ -0,0 +1,11 @@
1
+ import {
2
+ collectExposedPluginUiResources,
3
+ registerExposedPluginTools
4
+ } from "./chunk-3XIIQ6EK.mjs";
5
+ import "./chunk-HVIKCUOF.mjs";
6
+ import "./chunk-NFEGQTCC.mjs";
7
+ export {
8
+ collectExposedPluginUiResources,
9
+ registerExposedPluginTools
10
+ };
11
+ //# sourceMappingURL=registerExposedPlugins-6GCOE64C.mjs.map
@@ -261,6 +261,16 @@ var ResourceController = class {
261
261
  _isFilePath(path) {
262
262
  return /\.[a-zA-Z0-9]+$/.test(path);
263
263
  }
264
+ /**
265
+ * Injects a `<base href>` tag into an HTML document when it does not already
266
+ * declare one. Injects right after `<head>` when present, otherwise prefixes
267
+ * the document.
268
+ */
269
+ _injectBaseTag(html, baseHref) {
270
+ if (/<base\b/i.test(html)) return html;
271
+ const baseTag = `<base href="${baseHref}">`;
272
+ return html.includes("<head>") ? html.replace("<head>", `<head>${baseTag}`) : baseTag + html;
273
+ }
264
274
  /**
265
275
  * Shared file resolution: directory → index.html, volume FS → sandbox, base tag, response.
266
276
  */
@@ -291,14 +301,11 @@ var ResourceController = class {
291
301
  const contentType = getContentTypeFromFilename(filename);
292
302
  if (/\.(html|htm)$/i.test(filename)) {
293
303
  const baseDir = cleanPath ? this._isFilePath(cleanPath) ? cleanPath.replace(/[^/]+$/, "") : cleanPath : "";
294
- const html = buf.toString("utf-8");
295
- if (!/<base\b/i.test(html)) {
296
- const baseTag = `<base href="${baseTagPrefix}${baseDir ? "/" + baseDir : ""}/">`;
297
- buf = Buffer.from(
298
- html.includes("<head>") ? html.replace("<head>", `<head>${baseTag}`) : baseTag + html,
299
- "utf-8"
300
- );
301
- }
304
+ const baseHref = `${baseTagPrefix}${baseDir ? "/" + baseDir : ""}/`;
305
+ buf = Buffer.from(
306
+ this._injectBaseTag(buf.toString("utf-8"), baseHref),
307
+ "utf-8"
308
+ );
302
309
  }
303
310
  return sendBufferWithRange(reply, buf, contentType, rangeHeader, {
304
311
  "Content-Disposition": `inline; filename*=UTF-8''${encodeURIComponent(filename)}`,
@@ -407,4 +414,4 @@ var ResourceController = class {
407
414
  export {
408
415
  ResourceController
409
416
  };
410
- //# sourceMappingURL=resources-JFKAPS45.mjs.map
417
+ //# sourceMappingURL=resources-DXPXAXEZ.mjs.map