@axiom-lattice/gateway 8.3.0 → 9.0.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 (126) hide show
  1. package/dist/{A2AAuthService-7UGQ7GMW.mjs → A2AAuthService-UKN53ZGP.mjs} +2 -2
  2. package/dist/{a2a-BHBLACJV.mjs → a2a-YLKZE5JG.mjs} +7 -5
  3. package/dist/{a2a-standard-VL5I7B4W.mjs → a2a-standard-MD7WNDTP.mjs} +79 -127
  4. package/dist/a2a-standard-MD7WNDTP.mjs.map +1 -0
  5. package/dist/chunk-A7YNAU5L.mjs +377 -0
  6. package/dist/chunk-A7YNAU5L.mjs.map +1 -0
  7. package/dist/{chunk-3XIIQ6EK.mjs → chunk-BOPNXTMO.mjs} +66 -5
  8. package/dist/chunk-BOPNXTMO.mjs.map +1 -0
  9. package/dist/{chunk-L6MBEEII.mjs → chunk-JGQURU44.mjs} +24 -6
  10. package/dist/chunk-JGQURU44.mjs.map +1 -0
  11. package/dist/{chunk-GPHOYR3X.mjs → chunk-LCM4OQTI.mjs} +30 -1
  12. package/dist/chunk-LCM4OQTI.mjs.map +1 -0
  13. package/dist/{chunk-HVIKCUOF.mjs → chunk-MFWTGNQR.mjs} +74 -5
  14. package/dist/chunk-MFWTGNQR.mjs.map +1 -0
  15. package/dist/chunk-NYYSBNDG.mjs +575 -0
  16. package/dist/chunk-NYYSBNDG.mjs.map +1 -0
  17. package/dist/{chunk-W3YH4S6G.mjs → chunk-SIMEG2JT.mjs} +19 -2
  18. package/dist/chunk-SIMEG2JT.mjs.map +1 -0
  19. package/dist/chunk-T2HIWZGP.mjs +580 -0
  20. package/dist/chunk-T2HIWZGP.mjs.map +1 -0
  21. package/dist/chunk-Y7OVVPVU.mjs +13 -0
  22. package/dist/chunk-Y7OVVPVU.mjs.map +1 -0
  23. package/dist/{chunk-GL7RV5L3.mjs → chunk-YR32MWEJ.mjs} +36 -73
  24. package/dist/chunk-YR32MWEJ.mjs.map +1 -0
  25. package/dist/embed/{embed-page-AgentDetailSideApp-7YTMN643.js → embed-page-AgentDetailSideApp-6RYNHS5F.js} +1 -1
  26. package/dist/embed/{embed-page-AgentDetailSideApp-O6G4NJ7I.css → embed-page-AgentDetailSideApp-JMJOOC4V.css} +1 -1
  27. package/dist/embed/{embed-page-AgentGovernanceWorkbench-SFZNVMUH.css → embed-page-AgentGovernanceWorkbench-3T3ZFJI5.css} +1 -1
  28. package/dist/embed/embed-page-AgentGovernanceWorkbench-UAPXF3SZ.js +1 -0
  29. package/dist/embed/{embed-page-LangfuseDashboard-BEOJDMWF.js → embed-page-LangfuseDashboard-ZYGZNW7P.js} +1 -1
  30. package/dist/embed/{embed-page-MessageGraph-N2DXVMEK.js → embed-page-MessageGraph-S2P3WACC.js} +1 -1
  31. package/dist/embed/{embed-page-TopologyRuntimeView-CWOXLVFW.css → embed-page-MessageGraph-UDC6GQA5.css} +1 -1
  32. package/dist/embed/{embed-page-ReactChart-3QDZX2WG.js → embed-page-ReactChart-I3UZVE4Z.js} +1 -1
  33. package/dist/embed/{embed-page-ReactChart-MXGF35OQ.css → embed-page-ReactChart-URBKYQYE.css} +1 -1
  34. package/dist/embed/{embed-page-SkillFlow-3X24ILKU.js → embed-page-SkillFlow-JMIJ46FX.js} +1 -1
  35. package/dist/embed/{embed-page-TasksSideApp-MYIRJWFM.js → embed-page-TasksSideApp-IVFEFLSH.js} +1 -1
  36. package/dist/embed/{embed-page-TasksSideApp-HV6DKQ26.css → embed-page-TasksSideApp-TTCP4ENP.css} +1 -1
  37. package/dist/embed/{embed-page-ThreadMaintenancePanel-V327PV5R.js → embed-page-ThreadMaintenancePanel-KQPPAEWJ.js} +1 -1
  38. package/dist/embed/{embed-page-WorkflowAutomationView-YQFB3CTV.css → embed-page-TopologyRuntimeView-S4AMB4RI.css} +1 -1
  39. package/dist/embed/{embed-page-TopologyRuntimeView-3O6VJUZR.js → embed-page-TopologyRuntimeView-XCFQ23HF.js} +1 -1
  40. package/dist/embed/{embed-page-MessageGraph-UT2TCPSO.css → embed-page-WorkflowAutomationView-FWVCQ3YM.css} +1 -1
  41. package/dist/embed/embed-page-WorkflowAutomationView-UTKCKP75.js +3 -0
  42. package/dist/embed/embed-page-chunk-5X4MNQAW.js +242 -0
  43. package/dist/embed/{embed-page-chunk-PMOGSY6W.js → embed-page-chunk-77CQOFKU.js} +1 -1
  44. package/dist/embed/{embed-page-chunk-PROZXFWS.js → embed-page-chunk-7VDAOX3D.js} +1 -1
  45. package/dist/embed/{embed-page-chunk-BSRASK7V.js → embed-page-chunk-MD4U4DHC.js} +5 -5
  46. package/dist/embed/embed-page-chunk-RANB2CUO.js +3 -0
  47. package/dist/embed/{embed-page-chunk-IELKCYQX.js → embed-page-chunk-RVIS6CKE.js} +1 -1
  48. package/dist/embed/embed-page-chunk-SEOTZ4Q7.js +6829 -0
  49. package/dist/embed/{embed-page-chunk-ASEIOK7X.js → embed-page-chunk-TCVPYXPL.js} +2 -2
  50. package/dist/embed/embed-page-chunk-WHGU76CU.js +211 -0
  51. package/dist/embed/{embed-page-chunk-WGEXK773.js → embed-page-chunk-ZZFTCBM6.js} +1 -1
  52. package/dist/embed/embed-page-en-4HSTKY4Q.js +1 -0
  53. package/dist/embed/{embed-page-en-XJYWUJEL.js → embed-page-en-DNDGCPMA.js} +1 -1
  54. package/dist/embed/embed-page-en-H5NOZEKE.js +1 -0
  55. package/dist/embed/embed-page-en-MIVBTEEL.js +1 -0
  56. package/dist/embed/{embed-page-en-HDSVQ4O6.js → embed-page-en-QJRX2NQE.js} +1 -1
  57. package/dist/embed/{embed-page-zh-6EBUEFKP.js → embed-page-zh-2H4EB3F3.js} +1 -1
  58. package/dist/embed/embed-page-zh-4C2K3RH2.js +1 -0
  59. package/dist/embed/embed-page-zh-7PJNJJZY.js +1 -0
  60. package/dist/embed/{embed-page-zh-IR652A6J.js → embed-page-zh-HTEHFCSL.js} +1 -1
  61. package/dist/embed/embed-page-zh-UZ4GPIU7.js +1 -0
  62. package/dist/embed/embed-page.css +1 -1
  63. package/dist/embed/embed-page.js +1 -1
  64. package/dist/embed/mcp-app-page.js +61 -0
  65. package/dist/index.d.mts +29 -0
  66. package/dist/index.d.ts +29 -0
  67. package/dist/index.js +3574 -2349
  68. package/dist/index.js.map +1 -1
  69. package/dist/index.mjs +327 -1049
  70. package/dist/index.mjs.map +1 -1
  71. package/dist/mcp-app-shares-SHPBJQXS.mjs +300 -0
  72. package/dist/mcp-app-shares-SHPBJQXS.mjs.map +1 -0
  73. package/dist/mcp-apps-QVUK6UBA.mjs +26 -0
  74. package/dist/{open-7ED27I5B.mjs → open-27ZPLBIW.mjs} +101 -18
  75. package/dist/open-27ZPLBIW.mjs.map +1 -0
  76. package/dist/{open-catalog-SZQ435AY.mjs → open-catalog-NVGP6SQP.mjs} +38 -7
  77. package/dist/open-catalog-NVGP6SQP.mjs.map +1 -0
  78. package/dist/{open-exposure-I3DN2X4E.mjs → open-exposure-HGYT5VFC.mjs} +8 -9
  79. package/dist/open-exposure-HGYT5VFC.mjs.map +1 -0
  80. package/dist/{open-files-V3R2BFTV.mjs → open-files-JPB2NBL6.mjs} +4 -4
  81. package/dist/{open-session-2Y7XJJW2.mjs → open-session-CLZT3HVG.mjs} +5 -5
  82. package/dist/open-session-CLZT3HVG.mjs.map +1 -0
  83. package/dist/openLattice-D5YTSZBY.mjs +28 -0
  84. package/dist/{registerExposedPlugins-6GCOE64C.mjs → registerExposedPlugins-2JGXSMAL.mjs} +4 -3
  85. package/dist/{resources-DAU5ZIPB.mjs → resources-YD5DOPGD.mjs} +83 -3
  86. package/dist/resources-YD5DOPGD.mjs.map +1 -0
  87. package/dist/{sandboxOpenCredentialProvider-AT227D4N.mjs → sandboxOpenCredentialProvider-RTYOYRCT.mjs} +19 -14
  88. package/dist/sandboxOpenCredentialProvider-RTYOYRCT.mjs.map +1 -0
  89. package/package.json +10 -8
  90. package/dist/a2a-standard-VL5I7B4W.mjs.map +0 -1
  91. package/dist/chunk-3XIIQ6EK.mjs.map +0 -1
  92. package/dist/chunk-GL7RV5L3.mjs.map +0 -1
  93. package/dist/chunk-GPHOYR3X.mjs.map +0 -1
  94. package/dist/chunk-GXZFO3VU.mjs +0 -42
  95. package/dist/chunk-GXZFO3VU.mjs.map +0 -1
  96. package/dist/chunk-HVIKCUOF.mjs.map +0 -1
  97. package/dist/chunk-HYYLYIMH.mjs +0 -230
  98. package/dist/chunk-HYYLYIMH.mjs.map +0 -1
  99. package/dist/chunk-L6MBEEII.mjs.map +0 -1
  100. package/dist/chunk-W3YH4S6G.mjs.map +0 -1
  101. package/dist/embed/embed-page-AgentGovernanceWorkbench-YWJ4ZLEV.js +0 -1
  102. package/dist/embed/embed-page-WorkflowAutomationView-YHOS4WQR.js +0 -3
  103. package/dist/embed/embed-page-chunk-4XKCMM5C.js +0 -211
  104. package/dist/embed/embed-page-chunk-IBRMWDHE.js +0 -6835
  105. package/dist/embed/embed-page-chunk-JTL73EBV.js +0 -3
  106. package/dist/embed/embed-page-chunk-YMVKQQXJ.js +0 -242
  107. package/dist/embed/embed-page-en-E35GE7TA.js +0 -1
  108. package/dist/embed/embed-page-en-GLT2HEZZ.js +0 -1
  109. package/dist/embed/embed-page-en-JOTELYAL.js +0 -1
  110. package/dist/embed/embed-page-zh-BPO6ZSJM.js +0 -1
  111. package/dist/embed/embed-page-zh-XMEJPDMF.js +0 -1
  112. package/dist/embed/embed-page-zh-ZLK3GLM3.js +0 -1
  113. package/dist/open-7ED27I5B.mjs.map +0 -1
  114. package/dist/open-catalog-SZQ435AY.mjs.map +0 -1
  115. package/dist/open-exposure-I3DN2X4E.mjs.map +0 -1
  116. package/dist/open-session-2Y7XJJW2.mjs.map +0 -1
  117. package/dist/openLattice-Q6M5S6CY.mjs +0 -18
  118. package/dist/openRateLimiter-XEOL3A5G.mjs +0 -8
  119. package/dist/resources-DAU5ZIPB.mjs.map +0 -1
  120. package/dist/sandboxOpenCredentialProvider-AT227D4N.mjs.map +0 -1
  121. /package/dist/{A2AAuthService-7UGQ7GMW.mjs.map → A2AAuthService-UKN53ZGP.mjs.map} +0 -0
  122. /package/dist/{a2a-BHBLACJV.mjs.map → a2a-YLKZE5JG.mjs.map} +0 -0
  123. /package/dist/{openLattice-Q6M5S6CY.mjs.map → mcp-apps-QVUK6UBA.mjs.map} +0 -0
  124. /package/dist/{open-files-V3R2BFTV.mjs.map → open-files-JPB2NBL6.mjs.map} +0 -0
  125. /package/dist/{openRateLimiter-XEOL3A5G.mjs.map → openLattice-D5YTSZBY.mjs.map} +0 -0
  126. /package/dist/{registerExposedPlugins-6GCOE64C.mjs.map → registerExposedPlugins-2JGXSMAL.mjs.map} +0 -0
@@ -0,0 +1,300 @@
1
+ import {
2
+ getRateLimitService
3
+ } from "./chunk-A7YNAU5L.mjs";
4
+ import "./chunk-NFEGQTCC.mjs";
5
+
6
+ // src/routes/mcp-app-shares.ts
7
+ import bcrypt from "bcryptjs";
8
+
9
+ // src/services/mcp-app-shares/McpAppShareRuntimeService.ts
10
+ var ShareRuntimeError = class extends Error {
11
+ /**
12
+ * @param status - HTTP status.
13
+ * @param code - Stable machine-readable code.
14
+ */
15
+ constructor(status, code) {
16
+ super(code);
17
+ this.status = status;
18
+ this.code = code;
19
+ this.name = "ShareRuntimeError";
20
+ }
21
+ };
22
+ var McpAppShareRuntimeService = class {
23
+ constructor(deps) {
24
+ this.deps = deps;
25
+ }
26
+ /**
27
+ * Return the app identity and a fresh tool result for the hosted page.
28
+ *
29
+ * The share record stores only tool identity/arguments; the tool is executed
30
+ * on every bootstrap so no result is persisted.
31
+ */
32
+ async bootstrap(token, options = {}) {
33
+ const record = await this.resolve(token, { ...options, countAccess: true });
34
+ const app = record.address.app;
35
+ if (!app.toolName) throw new ShareRuntimeError(400, "TOOL_NAME_REQUIRED");
36
+ const result = await this.deps.handlers.rpc(
37
+ record.tenantId,
38
+ this.storedRef(record),
39
+ "tools/call",
40
+ { name: app.toolName, arguments: app.toolInput ?? {} },
41
+ {
42
+ tenantId: record.tenantId,
43
+ workspaceId: record.workspaceId,
44
+ projectId: record.projectId
45
+ }
46
+ );
47
+ if (result.status !== 200) {
48
+ throw new ShareRuntimeError(502, "TOOL_EXECUTION_FAILED");
49
+ }
50
+ return {
51
+ title: record.title,
52
+ visibility: record.visibility,
53
+ app: {
54
+ kind: app.kind,
55
+ toolName: app.toolName,
56
+ ref: this.publicRef(app),
57
+ ...app.toolInput !== void 0 ? { toolInput: app.toolInput } : {},
58
+ toolResult: result.body
59
+ }
60
+ };
61
+ }
62
+ /**
63
+ * Resolve the UI resource using the stored ref.
64
+ */
65
+ async resource(token, _clientRef, options = {}) {
66
+ const record = await this.resolve(token, options);
67
+ return this.deps.handlers.resource(record.tenantId, this.storedRef(record));
68
+ }
69
+ /**
70
+ * Proxy an allow-listed JSON-RPC method using the stored ref.
71
+ */
72
+ async rpc(token, method, params, options = {}) {
73
+ const record = await this.resolve(token, options);
74
+ return this.deps.handlers.rpc(record.tenantId, this.storedRef(record), method, params, {
75
+ tenantId: record.tenantId,
76
+ workspaceId: record.workspaceId,
77
+ projectId: record.projectId
78
+ });
79
+ }
80
+ /**
81
+ * Resolve and authorize a share record.
82
+ *
83
+ * @throws {ShareRuntimeError} when the token is unusable.
84
+ */
85
+ async resolve(token, options) {
86
+ const record = await this.deps.store.findByToken(token);
87
+ if (!record || record.kind !== "mcpApp") {
88
+ throw new ShareRuntimeError(404, "SHARE_NOT_FOUND");
89
+ }
90
+ if (record.revoked) throw new ShareRuntimeError(404, "SHARE_REVOKED");
91
+ if (record.expiresAt && record.expiresAt.getTime() < Date.now()) {
92
+ throw new ShareRuntimeError(410, "SHARE_EXPIRED");
93
+ }
94
+ if (record.visibility === "internal") {
95
+ throw new ShareRuntimeError(403, "INTERNAL_NOT_SUPPORTED");
96
+ }
97
+ if (record.visibility === "password" && options?.unlocked !== true) {
98
+ throw new ShareRuntimeError(401, "SHARE_PASSWORD_REQUIRED");
99
+ }
100
+ if (options?.countAccess) {
101
+ const ok = await this.deps.store.atomicIncrementAccess(token);
102
+ if (!ok) throw new ShareRuntimeError(410, "SHARE_ACCESS_LIMIT");
103
+ }
104
+ return record;
105
+ }
106
+ /** Build the public (scope-less) ref returned to the browser. */
107
+ publicRef(app) {
108
+ if (app.kind === "plugin") {
109
+ return {
110
+ kind: "plugin",
111
+ pluginType: app.pluginType,
112
+ resource: app.resource,
113
+ displayMode: app.displayMode ?? "inline",
114
+ ...app.title ? { title: app.title } : {},
115
+ ...app.toolName ? { toolName: app.toolName } : {}
116
+ };
117
+ }
118
+ return {
119
+ kind: "mcp",
120
+ serverKey: app.serverKey,
121
+ resourceUri: app.resourceUri,
122
+ displayMode: app.displayMode ?? "inline",
123
+ ...app.title ? { title: app.title } : {},
124
+ ...app.toolName ? { toolName: app.toolName } : {}
125
+ };
126
+ }
127
+ /** Build the server-side ref including the signed plugin scope. */
128
+ storedRef(record) {
129
+ const app = record.address.app;
130
+ if (app.kind === "plugin") {
131
+ return {
132
+ kind: "plugin",
133
+ pluginType: app.pluginType,
134
+ resource: app.resource,
135
+ displayMode: app.displayMode ?? "inline",
136
+ ...app.title ? { title: app.title } : {},
137
+ ...app.toolName ? { toolName: app.toolName } : {},
138
+ ...app.scope ? { scope: app.scope } : {}
139
+ };
140
+ }
141
+ return this.publicRef(app);
142
+ }
143
+ };
144
+
145
+ // src/routes/mcp-app-shares.ts
146
+ function tokenOf(request) {
147
+ return request.params.token;
148
+ }
149
+ function unlockedOf(request, token) {
150
+ return (request.headers.cookie ?? "").includes(`mcp_app_unlock_${token}=1`);
151
+ }
152
+ function logDenied(request, token, error) {
153
+ request.log.warn({
154
+ event: "share.denied",
155
+ tokenPrefix: token.slice(0, 8),
156
+ code: error instanceof ShareRuntimeError ? error.code : "INTERNAL_ERROR"
157
+ }, "share denied");
158
+ }
159
+ function sendResult(reply, result) {
160
+ return reply.type("application/json").status(result.status).send(JSON.stringify(result.body));
161
+ }
162
+ function rateLimited(request, reply, decision, token) {
163
+ request.log.warn({
164
+ event: "share.rate_limited",
165
+ policy: decision.policy,
166
+ tokenPrefix: token.slice(0, 8),
167
+ ip: request.ip,
168
+ exceededDimension: decision.exceededDimension,
169
+ limit: decision.limit
170
+ }, "share rate limited");
171
+ return reply.header("Retry-After", String(decision.retryAfterSeconds)).status(429).send({
172
+ error: "RATE_LIMITED",
173
+ policy: decision.policy,
174
+ retryAfterSeconds: decision.retryAfterSeconds
175
+ });
176
+ }
177
+ function registerMcpAppShareRoutes(app, deps) {
178
+ const service = new McpAppShareRuntimeService(deps);
179
+ const rateLimits = deps.rateLimits ?? getRateLimitService();
180
+ const consume = async (request, reply, policy, token, dimensions) => {
181
+ const decision = await rateLimits.consume(policy, dimensions);
182
+ return decision.allowed ? null : rateLimited(request, reply, decision, token);
183
+ };
184
+ app.get("/api/mcp-app-shares/:token/bootstrap", async (request, reply) => {
185
+ const token = tokenOf(request);
186
+ const blocked = await consume(request, reply, "mcp-app-share.bootstrap", token, {
187
+ ip: request.ip
188
+ });
189
+ if (blocked) return blocked;
190
+ try {
191
+ const result = await service.bootstrap(token, {
192
+ unlocked: unlockedOf(request, token)
193
+ });
194
+ request.log.info({
195
+ event: "share.bootstrap",
196
+ tokenPrefix: token.slice(0, 8),
197
+ visibility: result.visibility,
198
+ ip: request.ip
199
+ }, "share bootstrap");
200
+ return result;
201
+ } catch (error) {
202
+ logDenied(request, token, error);
203
+ if (error instanceof ShareRuntimeError) {
204
+ return reply.status(error.status).send({ error: error.code });
205
+ }
206
+ request.log.error(error);
207
+ return reply.status(500).send({ error: "INTERNAL_ERROR" });
208
+ }
209
+ });
210
+ app.post("/api/mcp-app-shares/:token/resource", async (request, reply) => {
211
+ const token = tokenOf(request);
212
+ try {
213
+ const result = await service.resource(
214
+ token,
215
+ request.body?.ref,
216
+ { unlocked: unlockedOf(request, token) }
217
+ );
218
+ return sendResult(reply, result);
219
+ } catch (error) {
220
+ logDenied(request, token, error);
221
+ if (error instanceof ShareRuntimeError) {
222
+ return reply.status(error.status).send({ error: error.code });
223
+ }
224
+ request.log.error(error);
225
+ return reply.status(500).send({ error: "INTERNAL_ERROR" });
226
+ }
227
+ });
228
+ app.post("/api/mcp-app-shares/:token/rpc", async (request, reply) => {
229
+ const token = tokenOf(request);
230
+ const body = request.body ?? {};
231
+ const method = typeof body.method === "string" ? body.method : "";
232
+ const params = typeof body.params === "object" && body.params !== null && !Array.isArray(body.params) ? body.params : {};
233
+ const blocked = await consume(request, reply, "mcp-app-share.rpc", token, {
234
+ token,
235
+ ip: request.ip
236
+ });
237
+ if (blocked) return blocked;
238
+ try {
239
+ const result = await service.rpc(token, method, params, {
240
+ unlocked: unlockedOf(request, token)
241
+ });
242
+ request.log.info({
243
+ event: "share.rpc",
244
+ tokenPrefix: token.slice(0, 8),
245
+ method,
246
+ tool: typeof params.name === "string" ? params.name : void 0,
247
+ ok: result.status === 200
248
+ }, "share rpc");
249
+ return sendResult(reply, result);
250
+ } catch (error) {
251
+ logDenied(request, token, error);
252
+ if (error instanceof ShareRuntimeError) {
253
+ return reply.status(error.status).send({ error: error.code });
254
+ }
255
+ request.log.error(error);
256
+ return reply.status(500).send({ error: "INTERNAL_ERROR" });
257
+ }
258
+ });
259
+ app.post("/api/mcp-app-shares/:token/unlock", async (request, reply) => {
260
+ const token = tokenOf(request);
261
+ const blocked = await consume(request, reply, "mcp-app-share.unlock", token, {
262
+ token,
263
+ ip: request.ip
264
+ });
265
+ if (blocked) return blocked;
266
+ const record = await deps.store.findByToken(token);
267
+ if (!record || record.kind !== "mcpApp" || record.revoked || !record.passwordHash) {
268
+ return reply.status(404).send({ error: "SHARE_NOT_FOUND" });
269
+ }
270
+ if (record.expiresAt && record.expiresAt.getTime() < Date.now()) {
271
+ return reply.status(410).send({ error: "SHARE_EXPIRED" });
272
+ }
273
+ if (record.visibility === "internal") {
274
+ return reply.status(403).send({ error: "INTERNAL_NOT_SUPPORTED" });
275
+ }
276
+ const password = request.body?.password;
277
+ const ok = typeof password === "string" && await bcrypt.compare(password, record.passwordHash);
278
+ if (!ok) {
279
+ request.log.warn({
280
+ event: "share.unlock_failed",
281
+ tokenPrefix: token.slice(0, 8)
282
+ }, "share unlock failed");
283
+ return reply.status(401).send({ error: "SHARE_PASSWORD_INVALID" });
284
+ }
285
+ const secure = request.protocol === "https" ? "; Secure" : "";
286
+ reply.header(
287
+ "set-cookie",
288
+ `mcp_app_unlock_${token}=1; Path=/; HttpOnly; SameSite=Lax; Max-Age=86400${secure}`
289
+ );
290
+ request.log.info({
291
+ event: "share.unlocked",
292
+ tokenPrefix: token.slice(0, 8)
293
+ }, "share unlocked");
294
+ return reply.send({ ok: true });
295
+ });
296
+ }
297
+ export {
298
+ registerMcpAppShareRoutes
299
+ };
300
+ //# sourceMappingURL=mcp-app-shares-SHPBJQXS.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/routes/mcp-app-shares.ts","../src/services/mcp-app-shares/McpAppShareRuntimeService.ts"],"sourcesContent":["/**\n * Public runtime routes for a shared MCP App.\n *\n * These routes are reached with only the share token (public/password). They are\n * exempt from console auth; each handler enforces revocation/expiry/password and\n * always uses the ref persisted in the share record.\n */\n\nimport type { FastifyInstance, FastifyReply, FastifyRequest } from \"fastify\";\nimport type { SharedResourceStore } from \"@axiom-lattice/protocols\";\nimport bcrypt from \"bcryptjs\";\nimport { makeMcpAppHandlers, type McpAppHandlerResult } from \"./mcp-apps\";\nimport {\n McpAppShareRuntimeService,\n ShareRuntimeError,\n} from \"../services/mcp-app-shares/McpAppShareRuntimeService\";\nimport {\n getRateLimitService,\n type RateLimitService,\n} from \"../services/rate-limit/RateLimitService\";\nimport type { RateLimitDecision, RateLimitDimension } from \"../services/rate-limit/types\";\n\n/** Dependencies for the public MCP App share routes. */\nexport interface McpAppShareRouteDeps {\n store: SharedResourceStore;\n handlers: ReturnType<typeof makeMcpAppHandlers>;\n /** Gateway rate limiter; defaults to the gateway policy set. */\n rateLimits?: RateLimitService;\n}\n\n/** Read the token path parameter. */\nfunction tokenOf(request: FastifyRequest): string {\n return (request.params as { token: string }).token;\n}\n\n/** True when the password unlock cookie for this token is present. */\nfunction unlockedOf(request: FastifyRequest, token: string): boolean {\n return (request.headers.cookie ?? \"\").includes(`mcp_app_unlock_${token}=1`);\n}\n\n/** Log a denied request without leaking the token. */\nfunction logDenied(request: FastifyRequest, token: string, error: unknown): void {\n request.log.warn({\n event: \"share.denied\",\n tokenPrefix: token.slice(0, 8),\n code: error instanceof ShareRuntimeError ? error.code : \"INTERNAL_ERROR\",\n }, \"share denied\");\n}\n\n/**\n * Send the result envelope returned by the pure MCP app handlers.\n *\n * Always JSON-encode the body: plugin tools may return a bare string, and the\n * runtime client (like the console client) always parses the response as JSON.\n */\nfunction sendResult(reply: FastifyReply, result: McpAppHandlerResult) {\n return reply\n .type(\"application/json\")\n .status(result.status)\n .send(JSON.stringify(result.body));\n}\n\n/** Build a 429 with the standard retry header. */\nfunction rateLimited(\n request: FastifyRequest,\n reply: FastifyReply,\n decision: RateLimitDecision,\n token: string,\n): FastifyReply {\n request.log.warn({\n event: \"share.rate_limited\",\n policy: decision.policy,\n tokenPrefix: token.slice(0, 8),\n ip: request.ip,\n exceededDimension: decision.exceededDimension,\n limit: decision.limit,\n }, \"share rate limited\");\n return reply\n .header(\"Retry-After\", String(decision.retryAfterSeconds))\n .status(429)\n .send({\n error: \"RATE_LIMITED\",\n policy: decision.policy,\n retryAfterSeconds: decision.retryAfterSeconds,\n });\n}\n\n/**\n * Register the public share runtime routes.\n */\nexport function registerMcpAppShareRoutes(\n app: FastifyInstance,\n deps: McpAppShareRouteDeps,\n): void {\n const service = new McpAppShareRuntimeService(deps);\n const rateLimits = deps.rateLimits ?? getRateLimitService();\n const consume = async (\n request: FastifyRequest,\n reply: FastifyReply,\n policy: string,\n token: string,\n dimensions: Partial<Record<RateLimitDimension, string | undefined>>,\n ): Promise<FastifyReply | null> => {\n const decision = await rateLimits.consume(policy, dimensions);\n return decision.allowed ? null : rateLimited(request, reply, decision, token);\n };\n\n app.get(\"/api/mcp-app-shares/:token/bootstrap\", async (request, reply) => {\n const token = tokenOf(request);\n const blocked = await consume(request, reply, \"mcp-app-share.bootstrap\", token, {\n ip: request.ip,\n });\n if (blocked) return blocked;\n try {\n const result = await service.bootstrap(token, {\n unlocked: unlockedOf(request, token),\n });\n request.log.info({\n event: \"share.bootstrap\",\n tokenPrefix: token.slice(0, 8),\n visibility: result.visibility,\n ip: request.ip,\n }, \"share bootstrap\");\n return result;\n } catch (error) {\n logDenied(request, token, error);\n if (error instanceof ShareRuntimeError) {\n return reply.status(error.status).send({ error: error.code });\n }\n request.log.error(error);\n return reply.status(500).send({ error: \"INTERNAL_ERROR\" });\n }\n });\n\n app.post(\"/api/mcp-app-shares/:token/resource\", async (request, reply) => {\n const token = tokenOf(request);\n try {\n const result = await service.resource(\n token,\n (request.body as { ref?: unknown } | undefined)?.ref,\n { unlocked: unlockedOf(request, token) },\n );\n return sendResult(reply, result);\n } catch (error) {\n logDenied(request, token, error);\n if (error instanceof ShareRuntimeError) {\n return reply.status(error.status).send({ error: error.code });\n }\n request.log.error(error);\n return reply.status(500).send({ error: \"INTERNAL_ERROR\" });\n }\n });\n\n app.post(\"/api/mcp-app-shares/:token/rpc\", async (request, reply) => {\n const token = tokenOf(request);\n const body = (request.body ?? {}) as {\n method?: unknown;\n params?: unknown;\n };\n const method = typeof body.method === \"string\" ? body.method : \"\";\n const params =\n typeof body.params === \"object\" && body.params !== null && !Array.isArray(body.params)\n ? (body.params as Record<string, unknown>)\n : {};\n const blocked = await consume(request, reply, \"mcp-app-share.rpc\", token, {\n token,\n ip: request.ip,\n });\n if (blocked) return blocked;\n try {\n const result = await service.rpc(token, method, params, {\n unlocked: unlockedOf(request, token),\n });\n request.log.info({\n event: \"share.rpc\",\n tokenPrefix: token.slice(0, 8),\n method,\n tool: typeof params.name === \"string\" ? params.name : undefined,\n ok: result.status === 200,\n }, \"share rpc\");\n return sendResult(reply, result);\n } catch (error) {\n logDenied(request, token, error);\n if (error instanceof ShareRuntimeError) {\n return reply.status(error.status).send({ error: error.code });\n }\n request.log.error(error);\n return reply.status(500).send({ error: \"INTERNAL_ERROR\" });\n }\n });\n\n app.post(\"/api/mcp-app-shares/:token/unlock\", async (request, reply) => {\n const token = tokenOf(request);\n const blocked = await consume(request, reply, \"mcp-app-share.unlock\", token, {\n token,\n ip: request.ip,\n });\n if (blocked) return blocked;\n const record = await deps.store.findByToken(token);\n if (!record || record.kind !== \"mcpApp\" || record.revoked || !record.passwordHash) {\n return reply.status(404).send({ error: \"SHARE_NOT_FOUND\" });\n }\n if (record.expiresAt && record.expiresAt.getTime() < Date.now()) {\n return reply.status(410).send({ error: \"SHARE_EXPIRED\" });\n }\n if ((record.visibility as string) === \"internal\") {\n return reply.status(403).send({ error: \"INTERNAL_NOT_SUPPORTED\" });\n }\n const password = (request.body as { password?: unknown } | undefined)?.password;\n const ok =\n typeof password === \"string\" &&\n (await bcrypt.compare(password, record.passwordHash));\n if (!ok) {\n request.log.warn({\n event: \"share.unlock_failed\",\n tokenPrefix: token.slice(0, 8),\n }, \"share unlock failed\");\n return reply.status(401).send({ error: \"SHARE_PASSWORD_INVALID\" });\n }\n const secure = request.protocol === \"https\" ? \"; Secure\" : \"\";\n reply.header(\n \"set-cookie\",\n `mcp_app_unlock_${token}=1; Path=/; HttpOnly; SameSite=Lax; Max-Age=86400${secure}`,\n );\n request.log.info({\n event: \"share.unlocked\",\n tokenPrefix: token.slice(0, 8),\n }, \"share unlocked\");\n return reply.send({ ok: true });\n });\n}\n","/**\n * Runtime access to a shared MCP App.\n *\n * A share token is the only credential for public/password shares. The service\n * always reconstructs the UI ref from the persisted record; client-supplied refs\n * are ignored so a token can never be used to reach another plugin/server.\n */\n\nimport type {\n McpAppShareApp,\n McpAppShareBootstrap,\n McpAppShareRecord,\n McpAppShareTarget,\n McpUiRef,\n ShareRecord,\n SharedResourceStore,\n} from \"@axiom-lattice/protocols\";\nimport type { McpAppHandlerResult, McpAppRunContext } from \"../../routes/mcp-apps\";\nimport { makeMcpAppHandlers } from \"../../routes/mcp-apps\";\n\n/** Stable runtime error surfaced by the public share routes. */\nexport class ShareRuntimeError extends Error {\n /**\n * @param status - HTTP status.\n * @param code - Stable machine-readable code.\n */\n constructor(\n readonly status: number,\n readonly code: string,\n ) {\n super(code);\n this.name = \"ShareRuntimeError\";\n }\n}\n\n/** Dependencies used by {@link McpAppShareRuntimeService}. */\nexport interface McpAppShareRuntimeServiceDeps {\n store: SharedResourceStore;\n handlers: ReturnType<typeof makeMcpAppHandlers>;\n}\n\n/** Options shared by bootstrap/resource/rpc. */\nexport interface McpAppShareAccessOptions {\n /** True when the password unlock cookie is present. */\n unlocked?: boolean;\n /** Count one access (bootstrap only). */\n countAccess?: boolean;\n}\n\n/**\n * Resolve a share token into its app payload and proxy runtime calls.\n */\nexport class McpAppShareRuntimeService {\n constructor(private readonly deps: McpAppShareRuntimeServiceDeps) {}\n\n /**\n * Return the app identity and a fresh tool result for the hosted page.\n *\n * The share record stores only tool identity/arguments; the tool is executed\n * on every bootstrap so no result is persisted.\n */\n async bootstrap(\n token: string,\n options: McpAppShareAccessOptions = {},\n ): Promise<McpAppShareBootstrap> {\n const record = await this.resolve(token, { ...options, countAccess: true });\n const app = (record.address as McpAppShareTarget).app;\n if (!app.toolName) throw new ShareRuntimeError(400, \"TOOL_NAME_REQUIRED\");\n\n const result = await this.deps.handlers.rpc(\n record.tenantId,\n this.storedRef(record),\n \"tools/call\",\n { name: app.toolName, arguments: app.toolInput ?? {} },\n {\n tenantId: record.tenantId,\n workspaceId: record.workspaceId,\n projectId: record.projectId,\n },\n );\n if (result.status !== 200) {\n throw new ShareRuntimeError(502, \"TOOL_EXECUTION_FAILED\");\n }\n\n return {\n title: record.title,\n visibility: record.visibility,\n app: {\n kind: app.kind,\n toolName: app.toolName,\n ref: this.publicRef(app),\n ...(app.toolInput !== undefined ? { toolInput: app.toolInput } : {}),\n toolResult: result.body,\n },\n };\n }\n\n /**\n * Resolve the UI resource using the stored ref.\n */\n async resource(\n token: string,\n _clientRef: unknown,\n options: McpAppShareAccessOptions = {},\n ): Promise<McpAppHandlerResult> {\n const record = await this.resolve(token, options);\n return this.deps.handlers.resource(record.tenantId, this.storedRef(record));\n }\n\n /**\n * Proxy an allow-listed JSON-RPC method using the stored ref.\n */\n async rpc(\n token: string,\n method: string,\n params: Record<string, unknown>,\n options: McpAppShareAccessOptions = {},\n ): Promise<McpAppHandlerResult> {\n const record = await this.resolve(token, options);\n return this.deps.handlers.rpc(record.tenantId, this.storedRef(record), method, params, {\n tenantId: record.tenantId,\n workspaceId: record.workspaceId,\n projectId: record.projectId,\n } satisfies McpAppRunContext);\n }\n\n /**\n * Resolve and authorize a share record.\n *\n * @throws {ShareRuntimeError} when the token is unusable.\n */\n private async resolve(\n token: string,\n options?: McpAppShareAccessOptions,\n ): Promise<McpAppShareRecord> {\n const record = await this.deps.store.findByToken(token);\n if (!record || record.kind !== \"mcpApp\") {\n throw new ShareRuntimeError(404, \"SHARE_NOT_FOUND\");\n }\n if (record.revoked) throw new ShareRuntimeError(404, \"SHARE_REVOKED\");\n if (record.expiresAt && record.expiresAt.getTime() < Date.now()) {\n throw new ShareRuntimeError(410, \"SHARE_EXPIRED\");\n }\n if ((record.visibility as string) === \"internal\") {\n throw new ShareRuntimeError(403, \"INTERNAL_NOT_SUPPORTED\");\n }\n if (record.visibility === \"password\" && options?.unlocked !== true) {\n throw new ShareRuntimeError(401, \"SHARE_PASSWORD_REQUIRED\");\n }\n if (options?.countAccess) {\n const ok = await this.deps.store.atomicIncrementAccess(token);\n if (!ok) throw new ShareRuntimeError(410, \"SHARE_ACCESS_LIMIT\");\n }\n return record;\n }\n\n /** Build the public (scope-less) ref returned to the browser. */\n private publicRef(app: McpAppShareApp): McpUiRef {\n if (app.kind === \"plugin\") {\n return {\n kind: \"plugin\",\n pluginType: app.pluginType!,\n resource: app.resource!,\n displayMode: app.displayMode ?? \"inline\",\n ...(app.title ? { title: app.title } : {}),\n ...(app.toolName ? { toolName: app.toolName } : {}),\n };\n }\n return {\n kind: \"mcp\",\n serverKey: app.serverKey!,\n resourceUri: app.resourceUri!,\n displayMode: app.displayMode ?? \"inline\",\n ...(app.title ? { title: app.title } : {}),\n ...(app.toolName ? { toolName: app.toolName } : {}),\n };\n }\n\n /** Build the server-side ref including the signed plugin scope. */\n private storedRef(record: McpAppShareRecord): McpUiRef {\n const app = (record.address as McpAppShareTarget).app;\n if (app.kind === \"plugin\") {\n return {\n kind: \"plugin\",\n pluginType: app.pluginType!,\n resource: app.resource!,\n displayMode: app.displayMode ?? \"inline\",\n ...(app.title ? { title: app.title } : {}),\n ...(app.toolName ? { toolName: app.toolName } : {}),\n ...(app.scope ? { scope: app.scope } : {}),\n };\n }\n return this.publicRef(app);\n }\n}\n\n/** Type guard used by tests/callers that need to narrow a share record. */\nexport function isMcpAppShareRecord(record: ShareRecord): record is McpAppShareRecord {\n return record.kind === \"mcpApp\";\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,26 @@
1
+ import {
2
+ McpAppScopeError,
3
+ PluginMethodNotSupportedError,
4
+ callPluginTool,
5
+ fetchPluginUiResource,
6
+ getMcpUiResourceResolver,
7
+ makeMcpAppHandlers,
8
+ registerMcpAppRoutes,
9
+ resolveMcpAppDeps
10
+ } from "./chunk-T2HIWZGP.mjs";
11
+ import {
12
+ PluginToolScopeError
13
+ } from "./chunk-XUWD4VKY.mjs";
14
+ import "./chunk-NFEGQTCC.mjs";
15
+ export {
16
+ McpAppScopeError,
17
+ PluginMethodNotSupportedError,
18
+ PluginToolScopeError,
19
+ callPluginTool,
20
+ fetchPluginUiResource,
21
+ getMcpUiResourceResolver,
22
+ makeMcpAppHandlers,
23
+ registerMcpAppRoutes,
24
+ resolveMcpAppDeps
25
+ };
26
+ //# sourceMappingURL=mcp-apps-QVUK6UBA.mjs.map
@@ -5,27 +5,69 @@ import {
5
5
  import {
6
6
  buildTenantOpenPlugins,
7
7
  mergeOpenCatalog
8
- } from "./chunk-L6MBEEII.mjs";
9
- import {
10
- OpenRateLimiter
11
- } from "./chunk-GXZFO3VU.mjs";
8
+ } from "./chunk-JGQURU44.mjs";
12
9
  import {
13
10
  collectExposedPluginUiResources
14
- } from "./chunk-3XIIQ6EK.mjs";
11
+ } from "./chunk-BOPNXTMO.mjs";
12
+ import "./chunk-Y7OVVPVU.mjs";
13
+ import "./chunk-EXPERMZR.mjs";
14
+ import "./chunk-XUWD4VKY.mjs";
15
15
  import {
16
16
  ActionNotFoundError,
17
+ ConnectionNotAllowedError,
17
18
  GrantDeniedError,
18
19
  ValidationError,
19
- openLattice
20
- } from "./chunk-HVIKCUOF.mjs";
21
- import "./chunk-XUWD4VKY.mjs";
22
- import "./chunk-EXPERMZR.mjs";
20
+ openLattice,
21
+ resolveCoveringConnectionKeys
22
+ } from "./chunk-MFWTGNQR.mjs";
23
23
  import "./chunk-NFEGQTCC.mjs";
24
24
 
25
25
  // src/routes/open.ts
26
- import { getReplicaId, getStoreLattice } from "@axiom-lattice/core";
26
+ import {
27
+ getReplicaId,
28
+ getStoreLattice,
29
+ resolvePluginConnections
30
+ } from "@axiom-lattice/core";
27
31
  import { REPLICA_HEADER } from "@axiom-lattice/protocols";
28
32
 
33
+ // src/services/open/openRateLimiter.ts
34
+ var OpenRateLimiter = class {
35
+ constructor(options = {}) {
36
+ this.buckets = /* @__PURE__ */ new Map();
37
+ this.defaultPerMinute = options.defaultPerMinute ?? 6e4;
38
+ this.agentPerMinute = options.agentPerMinute ?? 2e3;
39
+ }
40
+ bucketClass(domain) {
41
+ return domain === "agent" ? "agent" : "default";
42
+ }
43
+ limitFor(domain) {
44
+ return domain === "agent" ? this.agentPerMinute : this.defaultPerMinute;
45
+ }
46
+ /** Consume one token; denied calls report how long to wait. */
47
+ check(credentialId, domain) {
48
+ const limit = this.limitFor(domain);
49
+ const key = `${this.bucketClass(domain)}:${credentialId}`;
50
+ const now = Date.now();
51
+ const bucket = this.buckets.get(key) ?? { tokens: limit, updatedAt: now };
52
+ const refillPerMs = limit / 6e4;
53
+ const elapsed = now - bucket.updatedAt;
54
+ bucket.tokens = Math.min(limit, bucket.tokens + elapsed * refillPerMs);
55
+ bucket.updatedAt = now;
56
+ if (bucket.tokens >= 1) {
57
+ bucket.tokens -= 1;
58
+ this.buckets.set(key, bucket);
59
+ return { allowed: true };
60
+ }
61
+ this.buckets.set(key, bucket);
62
+ const deficit = 1 - bucket.tokens;
63
+ return { allowed: false, retryAfterMs: Math.ceil(deficit / refillPerMs) };
64
+ }
65
+ /** Test helper. */
66
+ reset() {
67
+ this.buckets.clear();
68
+ }
69
+ };
70
+
29
71
  // src/services/open/openMcpServer.ts
30
72
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
31
73
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
@@ -116,7 +158,8 @@ var RPC_ERROR = {
116
158
  ACTION_NOT_FOUND: -32002,
117
159
  VALIDATION_FAILED: -32003,
118
160
  EXECUTION_ERROR: -32004,
119
- RATE_LIMITED: -32005
161
+ RATE_LIMITED: -32005,
162
+ CONNECTION_NOT_ALLOWED: -32006
120
163
  };
121
164
  function registerOpenRoutes(app, deps = {}) {
122
165
  const openAuth = deps.openAuth ?? new OpenCredentialService(
@@ -133,6 +176,26 @@ function registerOpenRoutes(app, deps = {}) {
133
176
  }
134
177
  })();
135
178
  const rateLimiter = deps.rateLimiter ?? new OpenRateLimiter();
179
+ const rateLimits = deps.rateLimits;
180
+ const resolveConnections = deps.resolveConnections ?? resolvePluginConnections;
181
+ const selectedConnectionKeys = async (grants, domain, action, tenantId) => {
182
+ const keys = resolveCoveringConnectionKeys(grants, domain, action);
183
+ if (!keys) return void 0;
184
+ let resolved;
185
+ try {
186
+ resolved = await resolveConnections(domain, { connections: keys }, { tenantId });
187
+ } catch {
188
+ throw new ConnectionNotAllowedError(`connection store unavailable for ${domain}`);
189
+ }
190
+ const found = new Set(resolved.map((entry) => entry.key));
191
+ const missing = keys.filter((key) => !found.has(key));
192
+ if (missing.length > 0) {
193
+ throw new ConnectionNotAllowedError(
194
+ `unknown connection(s) for ${domain}: ${missing.join(", ")}`
195
+ );
196
+ }
197
+ return keys;
198
+ };
136
199
  const markReplica = (reply) => {
137
200
  const replica = getReplicaId();
138
201
  reply.header(REPLICA_HEADER, replica);
@@ -174,8 +237,21 @@ function registerOpenRoutes(app, deps = {}) {
174
237
  const sep = toolName.indexOf("_");
175
238
  const auditDomain = sep > 0 ? toolName.slice(0, sep) : toolName;
176
239
  const auditAction = sep > 0 ? toolName.slice(sep + 1) : "";
177
- const decision = rateLimiter.check(context.credentialId, auditDomain);
178
- if (!decision.allowed) {
240
+ let allowed;
241
+ let retryAfterMs;
242
+ if (rateLimits) {
243
+ const decision = await rateLimits.consume(
244
+ auditDomain === "agent" ? "open.agent" : "open.api",
245
+ { credential: context.credentialId }
246
+ );
247
+ allowed = decision.allowed;
248
+ retryAfterMs = decision.allowed ? void 0 : decision.retryAfterSeconds * 1e3;
249
+ } else {
250
+ const decision = rateLimiter.check(context.credentialId, auditDomain);
251
+ allowed = decision.allowed;
252
+ retryAfterMs = decision.retryAfterMs;
253
+ }
254
+ if (!allowed) {
179
255
  await audit?.record({
180
256
  tenantId: context.tenantId,
181
257
  credentialId: context.credentialId,
@@ -190,12 +266,18 @@ function registerOpenRoutes(app, deps = {}) {
190
266
  });
191
267
  throw Object.assign(
192
268
  new Error(
193
- `RATE_LIMITED: retry after ${decision.retryAfterMs ?? 1e3}ms`
269
+ `RATE_LIMITED: retry after ${retryAfterMs ?? 1e3}ms`
194
270
  ),
195
271
  { rpcCode: RPC_ERROR.RATE_LIMITED }
196
272
  );
197
273
  }
198
274
  try {
275
+ const connectionKeys = await selectedConnectionKeys(
276
+ grants,
277
+ auditDomain,
278
+ auditAction,
279
+ context.tenantId
280
+ );
199
281
  const isTenantRoute = tenant.hasTool(toolName) && !globalToolNames.has(toolName);
200
282
  const runConfig = {
201
283
  sandboxName: context.sandboxName,
@@ -206,7 +288,8 @@ function registerOpenRoutes(app, deps = {}) {
206
288
  tenantId: context.tenantId,
207
289
  projectId: context.projectId,
208
290
  workspaceId: context.workspaceId,
209
- runConfig
291
+ runConfig,
292
+ ...connectionKeys ? { connectionKeys } : {}
210
293
  }) : await openLattice.invoke(
211
294
  grants,
212
295
  {
@@ -232,7 +315,7 @@ function registerOpenRoutes(app, deps = {}) {
232
315
  });
233
316
  return result;
234
317
  } catch (err) {
235
- const code = err instanceof GrantDeniedError ? RPC_ERROR.GRANT_DENIED : err instanceof ActionNotFoundError ? RPC_ERROR.ACTION_NOT_FOUND : err instanceof ValidationError ? RPC_ERROR.VALIDATION_FAILED : RPC_ERROR.EXECUTION_ERROR;
318
+ const code = err instanceof ConnectionNotAllowedError ? RPC_ERROR.CONNECTION_NOT_ALLOWED : err instanceof GrantDeniedError ? RPC_ERROR.GRANT_DENIED : err instanceof ActionNotFoundError ? RPC_ERROR.ACTION_NOT_FOUND : err instanceof ValidationError ? RPC_ERROR.VALIDATION_FAILED : RPC_ERROR.EXECUTION_ERROR;
236
319
  await audit?.record({
237
320
  tenantId: context.tenantId,
238
321
  credentialId: context.credentialId,
@@ -242,7 +325,7 @@ function registerOpenRoutes(app, deps = {}) {
242
325
  action: auditAction,
243
326
  args,
244
327
  status: "error",
245
- errorCode: err instanceof GrantDeniedError ? "GRANT_DENIED" : err instanceof ActionNotFoundError ? "ACTION_NOT_FOUND" : err instanceof ValidationError ? "VALIDATION_FAILED" : "EXECUTION_ERROR",
328
+ errorCode: err instanceof ConnectionNotAllowedError ? "CONNECTION_NOT_ALLOWED" : err instanceof GrantDeniedError ? "GRANT_DENIED" : err instanceof ActionNotFoundError ? "ACTION_NOT_FOUND" : err instanceof ValidationError ? "VALIDATION_FAILED" : "EXECUTION_ERROR",
246
329
  durationMs: Date.now() - startedAt
247
330
  });
248
331
  throw Object.assign(
@@ -288,4 +371,4 @@ function registerOpenRoutes(app, deps = {}) {
288
371
  export {
289
372
  registerOpenRoutes
290
373
  };
291
- //# sourceMappingURL=open-7ED27I5B.mjs.map
374
+ //# sourceMappingURL=open-27ZPLBIW.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: GET/POST/DELETE /open/mcp.\n *\n * The route is a thin shell — credential resolution (OpenAuthService),\n * catalog filtering and invocation (OpenLattice), and stateless MCP server\n * construction live in services/open/*. The transport writes the response\n * itself, so the reply is hijacked (same precedent as the Project Room SSE\n * route).\n */\n\nimport type { FastifyInstance, FastifyReply, FastifyRequest } from \"fastify\";\nimport {\n getReplicaId,\n getStoreLattice,\n resolvePluginConnections,\n} from \"@axiom-lattice/core\";\nimport { REPLICA_HEADER, type A2AApiKeyStore, type OpenAuditStore, type OpenGrant, type 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 { RateLimitService } from \"../services/rate-limit/RateLimitService\";\nimport type { OpenCredential } from \"../services/open/openCredentialService\";\nimport {\n openLattice,\n ActionNotFoundError,\n ConnectionNotAllowedError,\n GrantDeniedError,\n ValidationError,\n resolveCoveringConnectionKeys,\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\";\n\nexport interface OpenRouteDeps {\n openAuth?: OpenCredentialService;\n grantsSource?: (ctx: OpenCredential) => OpenGrant[];\n audit?: OpenAuditService;\n /** Legacy in-memory limiter; used only when `rateLimits` is absent. */\n rateLimiter?: OpenRateLimiter;\n /** Gateway rate limiter; preferred when provided. */\n rateLimits?: RateLimitService;\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 /** Test seam for grant-pinned connection validation. */\n resolveConnections?: typeof resolvePluginConnections;\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\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 CONNECTION_NOT_ALLOWED: -32006,\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 const rateLimits = deps.rateLimits;\n\n const resolveConnections = deps.resolveConnections ?? resolvePluginConnections;\n\n /**\n * Resolve and validate the connection keys pinned by the covering grant.\n *\n * The credential tenant is authoritative; a selected key that is missing or\n * belongs to another tenant/type fails closed.\n */\n const selectedConnectionKeys = async (\n grants: OpenGrant[],\n domain: string,\n action: string,\n tenantId: string,\n ): Promise<string[] | undefined> => {\n const keys = resolveCoveringConnectionKeys(grants, domain, action);\n if (!keys) return undefined;\n let resolved: Awaited<ReturnType<typeof resolvePluginConnections>>;\n try {\n resolved = await resolveConnections(domain, { connections: keys }, { tenantId });\n } catch {\n throw new ConnectionNotAllowedError(`connection store unavailable for ${domain}`);\n }\n const found = new Set(resolved.map((entry) => entry.key));\n const missing = keys.filter((key) => !found.has(key));\n if (missing.length > 0) {\n throw new ConnectionNotAllowedError(\n `unknown connection(s) for ${domain}: ${missing.join(\", \")}`,\n );\n }\n return keys;\n };\n\n const markReplica = (reply: FastifyReply): void => {\n const replica = getReplicaId();\n reply.header(REPLICA_HEADER, replica);\n reply.raw.setHeader(REPLICA_HEADER, replica);\n };\n\n const buildServerFor = async (\n context: OpenCredential,\n log: FastifyRequest[\"log\"],\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 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 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 let allowed: boolean;\n let retryAfterMs: number | undefined;\n if (rateLimits) {\n const decision = await rateLimits.consume(\n auditDomain === \"agent\" ? \"open.agent\" : \"open.api\",\n { credential: context.credentialId },\n );\n allowed = decision.allowed;\n retryAfterMs = decision.allowed\n ? undefined\n : decision.retryAfterSeconds * 1000;\n } else {\n const decision = rateLimiter.check(context.credentialId, auditDomain);\n allowed = decision.allowed;\n retryAfterMs = decision.retryAfterMs;\n }\n if (!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 ${retryAfterMs ?? 1000}ms`,\n ),\n { rpcCode: RPC_ERROR.RATE_LIMITED },\n );\n }\n\n try {\n const connectionKeys = await selectedConnectionKeys(\n grants,\n auditDomain,\n auditAction,\n context.tenantId,\n );\n const isTenantRoute = tenant.hasTool(toolName) && !globalToolNames.has(toolName);\n const runConfig: Record<string, unknown> = {\n sandboxName: context.sandboxName,\n credentialId: context.credentialId,\n depth: context.depth,\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 ...(connectionKeys ? { connectionKeys } : {}),\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 ConnectionNotAllowedError\n ? RPC_ERROR.CONNECTION_NOT_ALLOWED\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 ConnectionNotAllowedError\n ? \"CONNECTION_NOT_ALLOWED\"\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 return built;\n };\n\n const handleOpenMcpPost = async (\n request: FastifyRequest,\n reply: FastifyReply,\n ) => {\n markReplica(reply);\n const context = await openAuth.resolve(extractBearer(request));\n if (!context) {\n return reply.status(401).send({ error: \"AUTH_FAILED\" });\n }\n\n const built = await buildServerFor(context, request.log);\n await built.server.connect(built.transport);\n reply.hijack();\n const close = (): void => {\n void built.transport.close().catch(() => undefined);\n void built.server.close().catch(() => undefined);\n };\n reply.raw.once(\"close\", close);\n await built.transport.handleRequest(request.raw, reply.raw, request.body);\n };\n\n const methodNotAllowed = async (\n _request: FastifyRequest,\n reply: FastifyReply,\n ) => {\n reply.header(\"Allow\", \"POST\");\n return reply.status(405).send({ error: \"METHOD_NOT_ALLOWED\" });\n };\n\n app.post(\"/open/mcp\", handleOpenMcpPost);\n // Canonical MCP surface aliases the unified Open MCP server.\n app.post(\"/mcp\", handleOpenMcpPost);\n app.post(\"/mcp/\", handleOpenMcpPost);\n app.get(\"/mcp\", methodNotAllowed);\n app.get(\"/mcp/\", methodNotAllowed);\n\n // Stateless Streamable HTTP has no server→client GET stream or session\n // DELETE. A 404 is not a recognized \"not supported\" signal: the official\n // client transport tolerates 405 (`Allow` set) and continues.\n app.get(\"/open/mcp\", methodNotAllowed);\n app.get(\"/open/mcp/\", methodNotAllowed);\n app.delete(\"/open/mcp\", methodNotAllowed);\n app.delete(\"/mcp\", methodNotAllowed);\n app.delete(\"/mcp/\", methodNotAllowed);\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 ?? 60000;\n this.agentPerMinute = options.agentPerMinute ?? 2000;\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 stateless MCP server instance whose tool list is the\n * credential-filtered OpenLattice catalog. A fresh instance is built per HTTP\n * request so no server/session state is shared between replicas.\n *\n * The transport uses `sessionIdGenerator: undefined`: it never issues\n * `Mcp-Session-Id`, and every request is self-contained. Credential-scoped\n * tool lists therefore stay per-request without a session registry.\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 type { McpUiCsp, OpenGrant } from \"@axiom-lattice/protocols\";\nimport type { OpenToolDescriptor } from \"./openLattice\";\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 stateless MCP server. The caller MUST\n * connect it to a transport (`await built.server.connect(built.transport)`)\n * before handling a request — otherwise the transport has no onmessage\n * handler and the request 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 { 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: undefined,\n enableJsonResponse: true,\n });\n return { server, transport, tools: catalog };\n}\n"],"mappings":"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","names":[]}