@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
@@ -57,8 +57,25 @@ function buildAgentCardForAssistant(params) {
57
57
  };
58
58
  }
59
59
 
60
+ // src/utils/request-base-url.ts
61
+ function firstHeader(value) {
62
+ const raw = Array.isArray(value) ? value[0] : value;
63
+ if (typeof raw !== "string") return void 0;
64
+ const first = raw.split(",")[0]?.trim();
65
+ return first ? first : void 0;
66
+ }
67
+ function resolveRequestBaseUrl(request) {
68
+ const forwardedProto = firstHeader(
69
+ request.headers["x-forwarded-proto"]
70
+ )?.toLowerCase();
71
+ const protocol = forwardedProto === "http" || forwardedProto === "https" ? forwardedProto : request.protocol;
72
+ const host = firstHeader(request.headers["x-forwarded-host"]) ?? request.host;
73
+ return `${protocol}://${host}`;
74
+ }
75
+
60
76
  export {
61
77
  deriveAgentSkills,
62
- buildAgentCardForAssistant
78
+ buildAgentCardForAssistant,
79
+ resolveRequestBaseUrl
63
80
  };
64
- //# sourceMappingURL=chunk-W3YH4S6G.mjs.map
81
+ //# sourceMappingURL=chunk-SIMEG2JT.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/services/a2a/A2AAgentCardBuilder.ts","../src/utils/request-base-url.ts"],"sourcesContent":["import type { AgentCard, A2ASkill, AgentMiddlewareConfig } from \"@axiom-lattice/protocols\";\n\n/** Minimal assistant shape needed to derive a card from its own definition. */\nexport interface CardAssistant {\n id: string;\n name: string;\n description?: string;\n graphDefinition?: {\n middleware?: AgentMiddlewareConfig[];\n } | null;\n}\n\n/**\n * Parameters for building a per-assistant A2A AgentCard.\n */\nexport interface BuildAgentCardParams {\n assistant: CardAssistant;\n /** Gateway origin, e.g. \"https://gw.example.com\". */\n baseUrl: string;\n /** Optional JSON-RPC endpoint path override (official `/a2a/:id` alias). */\n jsonRpcPath?: string;\n /** Advertise push notification support; defaults to `true`. */\n pushNotifications?: boolean;\n}\n\n/**\n * Derive the card's advertised skills from the agent's own definition: every\n * enabled middleware contributes a skill (id = middleware type, name/description\n * from its config). No manual `a2aExposure` declaration — the card reflects\n * what the agent is actually built from.\n */\nexport function deriveAgentSkills(assistant: CardAssistant): A2ASkill[] {\n const enabled = assistant.graphDefinition?.middleware?.filter((m) => m.enabled) ?? [];\n return enabled.map((m) => ({\n id: m.type || m.id,\n name: m.name || m.type || m.id,\n description: m.description || \"\",\n tags: [],\n examples: [],\n }));\n}\n\n/**\n * Build an A2A 0.3 AgentCard (discovery document) for a single assistant.\n *\n * `url` points at the assistant's JSON-RPC endpoint (also advertised via\n * `additionalInterfaces`). Skills are derived from the agent definition;\n * input/output modes default to text.\n */\nexport function buildAgentCardForAssistant(params: BuildAgentCardParams): AgentCard {\n const { assistant, baseUrl } = params;\n const jsonRpcUrl = `${baseUrl}${\n params.jsonRpcPath ?? `/api/a2a/agents/${assistant.id}/jsonrpc`\n }`;\n\n return {\n name: assistant.name,\n description: assistant.description ?? \"\",\n url: jsonRpcUrl,\n protocolVersion: \"0.3.0\",\n version: \"1.0.0\",\n supportsAuthenticatedExtendedCard: true,\n defaultInputModes: [\"text\"],\n defaultOutputModes: [\"text\"],\n capabilities: {\n streaming: true,\n pushNotifications: params.pushNotifications ?? true,\n stateTransitionHistory: true,\n extensions: [\n {\n uri: \"https://axiom-lattice.dev/a2a/extensions/push-notifications-experimental\",\n description:\n \"Push notifications are experimental and best-effort. Delivery is at-most-once; clients should poll tasks/get or use tasks/resubscribe for reliability.\",\n params: {\n experimental: true,\n reliable: false,\n delivery: \"at-most-once\",\n },\n required: false,\n },\n ],\n },\n skills: deriveAgentSkills(assistant),\n additionalInterfaces: [{ transport: \"JSONRPC\", url: jsonRpcUrl }],\n securitySchemes: {\n bearer: {\n type: \"http\",\n scheme: \"Bearer\",\n description: \"API key sent as an Authorization Bearer token\",\n },\n apiKeyHeader: {\n type: \"apiKey\",\n in: \"header\",\n name: \"x-api-key\",\n description: \"API key sent via the x-api-key header\",\n },\n },\n security: [{ bearer: [] }, { apiKeyHeader: [] }],\n };\n}\n","import type { FastifyRequest } from \"fastify\";\n\n/** First non-empty value from a possibly comma-separated / array header. */\nfunction firstHeader(value: string | string[] | undefined): string | undefined {\n const raw = Array.isArray(value) ? value[0] : value;\n if (typeof raw !== \"string\") return undefined;\n const first = raw.split(\",\")[0]?.trim();\n return first ? first : undefined;\n}\n\n/**\n * Resolve the externally visible base URL for a request.\n *\n * @param request - Incoming Fastify request.\n * @returns `scheme://host[:port]` using the first forwarded value when present.\n *\n * @remarks\n * TLS is normally terminated by a reverse proxy, so the gateway itself sees\n * `http`. Prefer `X-Forwarded-Proto`/`X-Forwarded-Host` (first value) and fall\n * back to the direct request. Only `http` and `https` schemes are accepted, so\n * a malformed forwarded header cannot produce an invalid or unsafe public URL.\n * Deployments that do not send these headers should configure their proxy or\n * provide an explicit public base URL.\n */\nexport function resolveRequestBaseUrl(request: FastifyRequest): string {\n const forwardedProto = firstHeader(\n request.headers[\"x-forwarded-proto\"],\n )?.toLowerCase();\n const protocol =\n forwardedProto === \"http\" || forwardedProto === \"https\"\n ? forwardedProto\n : request.protocol;\n const host = firstHeader(request.headers[\"x-forwarded-host\"]) ?? request.host;\n return `${protocol}://${host}`;\n}\n"],"mappings":";AA+BO,SAAS,kBAAkB,WAAsC;AACtE,QAAM,UAAU,UAAU,iBAAiB,YAAY,OAAO,CAAC,MAAM,EAAE,OAAO,KAAK,CAAC;AACpF,SAAO,QAAQ,IAAI,CAAC,OAAO;AAAA,IACzB,IAAI,EAAE,QAAQ,EAAE;AAAA,IAChB,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE;AAAA,IAC5B,aAAa,EAAE,eAAe;AAAA,IAC9B,MAAM,CAAC;AAAA,IACP,UAAU,CAAC;AAAA,EACb,EAAE;AACJ;AASO,SAAS,2BAA2B,QAAyC;AAClF,QAAM,EAAE,WAAW,QAAQ,IAAI;AAC/B,QAAM,aAAa,GAAG,OAAO,GAC3B,OAAO,eAAe,mBAAmB,UAAU,EAAE,UACvD;AAEA,SAAO;AAAA,IACL,MAAM,UAAU;AAAA,IAChB,aAAa,UAAU,eAAe;AAAA,IACtC,KAAK;AAAA,IACL,iBAAiB;AAAA,IACjB,SAAS;AAAA,IACT,mCAAmC;AAAA,IACnC,mBAAmB,CAAC,MAAM;AAAA,IAC1B,oBAAoB,CAAC,MAAM;AAAA,IAC3B,cAAc;AAAA,MACZ,WAAW;AAAA,MACX,mBAAmB,OAAO,qBAAqB;AAAA,MAC/C,wBAAwB;AAAA,MACxB,YAAY;AAAA,QACV;AAAA,UACE,KAAK;AAAA,UACL,aACE;AAAA,UACF,QAAQ;AAAA,YACN,cAAc;AAAA,YACd,UAAU;AAAA,YACV,UAAU;AAAA,UACZ;AAAA,UACA,UAAU;AAAA,QACZ;AAAA,MACF;AAAA,IACF;AAAA,IACA,QAAQ,kBAAkB,SAAS;AAAA,IACnC,sBAAsB,CAAC,EAAE,WAAW,WAAW,KAAK,WAAW,CAAC;AAAA,IAChE,iBAAiB;AAAA,MACf,QAAQ;AAAA,QACN,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,aAAa;AAAA,MACf;AAAA,MACA,cAAc;AAAA,QACZ,MAAM;AAAA,QACN,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,aAAa;AAAA,MACf;AAAA,IACF;AAAA,IACA,UAAU,CAAC,EAAE,QAAQ,CAAC,EAAE,GAAG,EAAE,cAAc,CAAC,EAAE,CAAC;AAAA,EACjD;AACF;;;AChGA,SAAS,YAAY,OAA0D;AAC7E,QAAM,MAAM,MAAM,QAAQ,KAAK,IAAI,MAAM,CAAC,IAAI;AAC9C,MAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,QAAM,QAAQ,IAAI,MAAM,GAAG,EAAE,CAAC,GAAG,KAAK;AACtC,SAAO,QAAQ,QAAQ;AACzB;AAgBO,SAAS,sBAAsB,SAAiC;AACrE,QAAM,iBAAiB;AAAA,IACrB,QAAQ,QAAQ,mBAAmB;AAAA,EACrC,GAAG,YAAY;AACf,QAAM,WACJ,mBAAmB,UAAU,mBAAmB,UAC5C,iBACA,QAAQ;AACd,QAAM,OAAO,YAAY,QAAQ,QAAQ,kBAAkB,CAAC,KAAK,QAAQ;AACzE,SAAO,GAAG,QAAQ,MAAM,IAAI;AAC9B;","names":[]}
@@ -0,0 +1,580 @@
1
+ import {
2
+ PluginToolScopeError,
3
+ invokePluginTool
4
+ } from "./chunk-XUWD4VKY.mjs";
5
+
6
+ // src/routes/mcp-apps.ts
7
+ import {
8
+ parseMcpUiRef
9
+ } from "@axiom-lattice/protocols";
10
+ import {
11
+ getSandboxPluginCatalog,
12
+ mcpManager,
13
+ resolvePluginConnections,
14
+ verifyMcpUiScope
15
+ } from "@axiom-lattice/core";
16
+
17
+ // src/services/mcp-ui/UiResourceResolver.ts
18
+ import { createHash } from "crypto";
19
+ var DEFAULT_TTL_MS = 5 * 60 * 1e3;
20
+ function publicFieldsOf(payload) {
21
+ return {
22
+ mimeType: payload.mimeType,
23
+ html: payload.html,
24
+ ...payload.csp !== void 0 ? { csp: payload.csp } : {},
25
+ ...payload.permissions !== void 0 ? { permissions: payload.permissions } : {},
26
+ ...payload.prefersBorder !== void 0 ? { prefersBorder: payload.prefersBorder } : {}
27
+ };
28
+ }
29
+ var UiResourceResolver = class {
30
+ constructor(deps) {
31
+ this.deps = deps;
32
+ this.inFlight = /* @__PURE__ */ new Map();
33
+ }
34
+ keyOf(ref) {
35
+ return ref.kind === "mcp" ? `mcp\0${ref.serverKey}\0${ref.resourceUri}` : `plugin\0${ref.pluginType}\0${ref.resource}`;
36
+ }
37
+ fullKey(tenantId, ref) {
38
+ return `${tenantId}\0${this.keyOf(ref)}`;
39
+ }
40
+ /**
41
+ * Resolve a UI resource for a tenant.
42
+ *
43
+ * @param tenantId Tenant scope.
44
+ * @param ref The fence ref.
45
+ * @returns Public UI resource payload (no cache metadata).
46
+ */
47
+ async resolve(tenantId, ref) {
48
+ const key = this.fullKey(tenantId, ref);
49
+ const ttl = this.deps.ttlMs ?? DEFAULT_TTL_MS;
50
+ const cached = await this.deps.store.get(key);
51
+ if (cached && Date.now() - cached.fetchedAt < ttl) return publicFieldsOf(cached);
52
+ const existing = this.inFlight.get(key);
53
+ if (existing) return existing;
54
+ const promise = this.fetchAndCache(tenantId, ref, key, cached);
55
+ this.inFlight.set(key, promise);
56
+ try {
57
+ return await promise;
58
+ } finally {
59
+ this.inFlight.delete(key);
60
+ }
61
+ }
62
+ /**
63
+ * Evict the cached entry for a tenant/ref.
64
+ *
65
+ * @param tenantId Tenant scope.
66
+ * @param ref The fence ref.
67
+ */
68
+ async invalidate(tenantId, ref) {
69
+ await this.deps.store.delete(this.fullKey(tenantId, ref));
70
+ }
71
+ /**
72
+ * Evict every cached plugin UI resource for a tenant.
73
+ *
74
+ * @param tenantId Tenant whose plugin UI resources to drop.
75
+ * @remarks Called when a tenant's plugin catalog changes so an edited
76
+ * `ui/*.html` is re-fetched instead of being served from the TTL cache
77
+ * (which is keyed by ref identity, not content).
78
+ */
79
+ async invalidateTenantPlugins(tenantId) {
80
+ await this.deps.store.deleteByPrefix(`${tenantId}\0plugin\0`);
81
+ }
82
+ async fetchAndCache(tenantId, ref, key, cached) {
83
+ try {
84
+ const fresh = ref.kind === "mcp" ? await this.deps.fetchMcp(tenantId, ref.serverKey, ref.resourceUri) : await this.deps.fetchPlugin(tenantId, ref.pluginType, ref.resource);
85
+ const entry = {
86
+ ...publicFieldsOf(fresh),
87
+ hash: createHash("sha256").update(fresh.html).digest("hex"),
88
+ fetchedAt: Date.now()
89
+ };
90
+ await this.deps.store.set(key, entry);
91
+ return publicFieldsOf(fresh);
92
+ } catch (error) {
93
+ if (cached) return { ...publicFieldsOf(cached), stale: true };
94
+ throw error;
95
+ }
96
+ }
97
+ };
98
+
99
+ // src/services/mcp-ui/McpUiResourceCacheStore.ts
100
+ var InMemoryMcpUiResourceCacheStore = class {
101
+ constructor() {
102
+ this.map = /* @__PURE__ */ new Map();
103
+ }
104
+ async get(key) {
105
+ return this.map.get(key);
106
+ }
107
+ async set(key, entry) {
108
+ this.map.set(key, entry);
109
+ }
110
+ async delete(key) {
111
+ this.map.delete(key);
112
+ }
113
+ async deleteByPrefix(prefix) {
114
+ for (const key of this.map.keys()) {
115
+ if (key.startsWith(prefix)) this.map.delete(key);
116
+ }
117
+ }
118
+ };
119
+
120
+ // src/services/mcp-ui/buildCsp.ts
121
+ var DEFAULT_CSP = [
122
+ "default-src 'none'",
123
+ "script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval'",
124
+ "style-src 'self' 'unsafe-inline'",
125
+ "img-src 'self' data:",
126
+ "media-src 'self' data:",
127
+ // Fonts are restricted to the document origin; declared `resourceDomains`
128
+ // are the only origins ever added (no `data:` — a base64-embedded font must
129
+ // be served from a declared origin).
130
+ "font-src 'self'",
131
+ "object-src 'none'",
132
+ "connect-src 'none'",
133
+ "frame-src 'none'",
134
+ "base-uri 'self'"
135
+ ].join("; ");
136
+ var ORIGIN_PATTERN = /^(https?|wss?):\/\/(\*\.)?[^\s;,'"*]+$/;
137
+ function declaredOrigins(domains) {
138
+ if (!Array.isArray(domains)) return [];
139
+ return domains.filter((domain) => typeof domain === "string" && ORIGIN_PATTERN.test(domain));
140
+ }
141
+ function buildCsp(csp) {
142
+ if (!csp) return DEFAULT_CSP;
143
+ const resourceOrigins = declaredOrigins(csp.resourceDomains);
144
+ const resource = resourceOrigins.join(" ");
145
+ const withResourceOrigins = (base) => resource ? `${base} ${resource}` : base;
146
+ const img = ["'self'", "data:", ...resourceOrigins].join(" ");
147
+ const connect = declaredOrigins(csp.connectDomains);
148
+ const frame = declaredOrigins(csp.frameDomains);
149
+ const baseUri = declaredOrigins(csp.baseUriDomains);
150
+ const parts = [
151
+ "default-src 'none'",
152
+ withResourceOrigins("script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval'"),
153
+ withResourceOrigins("style-src 'self' 'unsafe-inline'"),
154
+ `img-src ${img}`,
155
+ `media-src ${["'self'", "data:", ...resourceOrigins].join(" ")}`,
156
+ withResourceOrigins("font-src 'self'"),
157
+ "object-src 'none'",
158
+ `connect-src ${connect.length ? connect.join(" ") : "'none'"}`,
159
+ `frame-src ${frame.length ? frame.join(" ") : "'none'"}`,
160
+ `base-uri ${baseUri.length ? baseUri.join(" ") : "'self'"}`
161
+ ];
162
+ return parts.join("; ");
163
+ }
164
+
165
+ // src/services/mcp-ui/sandboxPage.ts
166
+ var DEFAULT_CSP_LITERAL = JSON.stringify(buildCsp(void 0));
167
+ var SANDBOX_HTML = `<!doctype html>
168
+ <html lang="en">
169
+ <head>
170
+ <meta charset="utf-8" />
171
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
172
+ <title>MCP App Sandbox</title>
173
+ <style>html, body { margin: 0; height: 100%; }</style>
174
+ </head>
175
+ <body>
176
+ <script>
177
+ (function () {
178
+ var HOST_READY = "ui/notifications/sandbox-proxy-ready";
179
+ var RESOURCE_READY = "ui/notifications/sandbox-resource-ready";
180
+ var CONTROL_PREFIX = "ui/notifications/sandbox-";
181
+ var DEFAULT_CSP = ${DEFAULT_CSP_LITERAL};
182
+
183
+ function withCsp(html, csp) {
184
+ var policy = typeof csp === "string" && csp.length > 0 ? csp : DEFAULT_CSP;
185
+ var escaped = policy.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
186
+ var meta = '<meta http-equiv="Content-Security-Policy" content="' + escaped + '">';
187
+ // Insert at the very start (after an optional doctype) so the parser
188
+ // places the meta in the implicit head. Never search for a literal
189
+ // <head>: a resource could hide a decoy inside a comment/string and
190
+ // slip its real head past the splice.
191
+ var doctype = /^\\s*<!doctype[^>]*>/i.exec(html);
192
+ if (doctype) {
193
+ return html.slice(0, doctype[0].length) + meta + html.slice(doctype[0].length);
194
+ }
195
+ return meta + html;
196
+ }
197
+
198
+ // Size reporter injected into every View document. The host renders the
199
+ // View inside an opaque-origin iframe it cannot measure, so auto-height
200
+ // only happens when the View posts ui/notifications/size-changed. Most
201
+ // hand-written plugin apps never do; this script makes every app report
202
+ // its own content size. It only measures and postMessages \u2014 it never
203
+ // mutates the app's DOM.
204
+ var SIZE_REPORTER = [
205
+ "(function(){",
206
+ "function send(){",
207
+ "try{",
208
+ "var d=document.documentElement,b=document.body;",
209
+ "var w=Math.ceil(Math.max(d?d.scrollWidth:0,b?b.scrollWidth:0));",
210
+ "var h=Math.ceil(Math.max(d?d.scrollHeight:0,b?b.scrollHeight:0));",
211
+ "window.parent.postMessage({jsonrpc:'2.0',method:'ui/notifications/size-changed',params:{width:w,height:h}},'*');",
212
+ "}catch(e){}",
213
+ "}",
214
+ "send();",
215
+ "window.addEventListener('load',send);",
216
+ "if(typeof ResizeObserver!=='undefined'){",
217
+ "var ro=new ResizeObserver(send);",
218
+ "ro.observe(document.documentElement);",
219
+ "if(document.body)ro.observe(document.body);",
220
+ "}else{window.addEventListener('resize',send);}",
221
+ "})();"
222
+ ].join("");
223
+
224
+ function withSizeReporter(html) {
225
+ // Split the closing tag so this template literal never contains a
226
+ // literal script terminator that would end the sandbox page's own
227
+ // script block during HTML parsing.
228
+ var tag = "<scr" + "ipt>" + SIZE_REPORTER + "<" + "/scr" + "ipt>";
229
+ var lower = html.toLowerCase();
230
+ var bodyEnd = lower.lastIndexOf("</body>");
231
+ if (bodyEnd >= 0) return html.slice(0, bodyEnd) + tag + html.slice(bodyEnd);
232
+ return html + tag;
233
+ }
234
+
235
+ var inner = null;
236
+
237
+ window.parent.postMessage({ jsonrpc: "2.0", method: HOST_READY, params: {} }, "*");
238
+
239
+ window.addEventListener("message", function (event) {
240
+ if (event.source !== window.parent) return;
241
+ var data = event.data;
242
+ if (!data || typeof data !== "object") return;
243
+
244
+ if (data.method === RESOURCE_READY) {
245
+ var params = data.params || {};
246
+ if (inner && inner.parentNode) inner.parentNode.removeChild(inner);
247
+ inner = document.createElement("iframe");
248
+ inner.setAttribute("sandbox", params.sandbox || "allow-scripts");
249
+ inner.setAttribute("srcdoc", withCsp(withSizeReporter(String(params.html || "")), params.csp));
250
+ inner.style.cssText = "width:100%;height:100%;border:0;display:block";
251
+ document.body.appendChild(inner);
252
+ return;
253
+ }
254
+
255
+ if (typeof data.method === "string" && data.method.indexOf(CONTROL_PREFIX) === 0) return;
256
+ if (inner && inner.contentWindow) inner.contentWindow.postMessage(data, "*");
257
+ });
258
+
259
+ window.addEventListener("message", function (event) {
260
+ if (inner && event.source === inner.contentWindow) {
261
+ var data = event.data;
262
+ if (data && typeof data === "object" && typeof data.method === "string"
263
+ && data.method.indexOf(CONTROL_PREFIX) === 0) return;
264
+ window.parent.postMessage(event.data, "*");
265
+ }
266
+ });
267
+ })();
268
+ </script>
269
+ </body>
270
+ </html>
271
+ `;
272
+
273
+ // src/services/plugins/tenantPluginResolver.ts
274
+ import { listTenantPlugins } from "@axiom-lattice/core";
275
+ async function resolveTenantPluginByType(tenantId, type) {
276
+ return (await listTenantPlugins(tenantId)).find((p) => p.meta.type === type);
277
+ }
278
+
279
+ // src/routes/mcp-apps.ts
280
+ var SANDBOX_HTML_CONTENT_TYPE = "text/html; charset=utf-8";
281
+ var PluginMethodNotSupportedError = class extends Error {
282
+ /**
283
+ * @param method - Unsupported JSON-RPC method.
284
+ */
285
+ constructor(method) {
286
+ super(`plugin backend does not support ${method}`);
287
+ this.name = "PluginMethodNotSupportedError";
288
+ }
289
+ };
290
+ var McpAppScopeError = class extends Error {
291
+ /**
292
+ * @param status - HTTP status to return.
293
+ * @param code - Stable machine-readable denial code.
294
+ */
295
+ constructor(status, code) {
296
+ super(code);
297
+ this.status = status;
298
+ this.code = code;
299
+ this.name = "McpAppScopeError";
300
+ }
301
+ };
302
+ function denyScope(status, code, detail) {
303
+ console.warn(JSON.stringify({ event: "mcp-apps:scope-denied", ...detail, code }));
304
+ return new McpAppScopeError(status, code);
305
+ }
306
+ var ALLOWED_METHODS = /* @__PURE__ */ new Set(["tools/call", "tools/list", "resources/read"]);
307
+ function asRecord(value) {
308
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
309
+ }
310
+ function makeMcpAppHandlers(deps) {
311
+ return {
312
+ /**
313
+ * Resolve the `mcp_app` UI resource for a parsed ref.
314
+ *
315
+ * @param tenantId Tenant scope.
316
+ * @param rawRef Untrusted ref payload.
317
+ * @returns The resource payload plus a ready-to-use `cspPolicy` string
318
+ * ({@link buildCsp}, always non-empty), or a 400/502 error envelope.
319
+ */
320
+ async resource(tenantId, rawRef) {
321
+ const ref = parseMcpUiRef(rawRef);
322
+ if (!ref) return { status: 400, body: { error: "INVALID_REF" } };
323
+ if (ref.kind === "plugin" && ref.pluginType === "") {
324
+ return { status: 400, body: { error: "INVALID_REF" } };
325
+ }
326
+ try {
327
+ const payload = await deps.resolveUiResource(tenantId, ref);
328
+ return { status: 200, body: { ...payload, cspPolicy: buildCsp(payload.csp) } };
329
+ } catch (error) {
330
+ console.warn(JSON.stringify({
331
+ event: "mcp-apps:resource-failed",
332
+ tenantId,
333
+ kind: ref.kind,
334
+ error: error instanceof Error ? error.message : String(error)
335
+ }));
336
+ return { status: 502, body: { error: "RESOURCE_UNAVAILABLE" } };
337
+ }
338
+ },
339
+ /**
340
+ * Proxy an allow-listed JSON-RPC method for a parsed ref.
341
+ *
342
+ * @param tenantId Tenant scope.
343
+ * @param rawRef Untrusted ref payload.
344
+ * @param method JSON-RPC method name.
345
+ * @param params JSON-RPC params.
346
+ * @param context Request-scoped run context forwarded to `tools/call`.
347
+ * @returns The backend result, or a 400/403/502 error envelope.
348
+ */
349
+ async rpc(tenantId, rawRef, method, params, context) {
350
+ const ref = parseMcpUiRef(rawRef);
351
+ if (!ref) return { status: 400, body: { error: "INVALID_REF" } };
352
+ if (!ALLOWED_METHODS.has(method)) return { status: 403, body: { error: "METHOD_NOT_ALLOWED" } };
353
+ try {
354
+ if (method === "tools/call") {
355
+ const name = typeof params.name === "string" ? params.name : "";
356
+ if (name === "") return { status: 400, body: { error: "INVALID_PARAMS" } };
357
+ if ("pluginType" in ref && ref.pluginType === "") {
358
+ return { status: 400, body: { error: "INVALID_REF" } };
359
+ }
360
+ return {
361
+ status: 200,
362
+ body: await deps.callTool(tenantId, ref, name, asRecord(params.arguments) ?? {}, context)
363
+ };
364
+ }
365
+ if (method === "resources/read") {
366
+ const uri = typeof params.uri === "string" ? params.uri : "";
367
+ if (uri === "") return { status: 400, body: { error: "INVALID_PARAMS" } };
368
+ return { status: 200, body: await deps.readResource(tenantId, ref, uri) };
369
+ }
370
+ return { status: 200, body: { tools: await deps.listTools(tenantId, ref) } };
371
+ } catch (error) {
372
+ if (error instanceof McpAppScopeError) {
373
+ return { status: error.status, body: { error: error.code } };
374
+ }
375
+ if (error instanceof PluginToolScopeError) {
376
+ return { status: 403, body: { error: "PLUGIN_TOOL_SCOPE" } };
377
+ }
378
+ if (error instanceof PluginMethodNotSupportedError) {
379
+ return { status: 403, body: { error: "PLUGIN_METHOD_UNSUPPORTED" } };
380
+ }
381
+ const context2 = { tenantId, kind: ref.kind, method };
382
+ if (method === "resources/read") context2.uri = typeof params.uri === "string" ? params.uri : "";
383
+ if (method === "tools/call") context2.name = typeof params.name === "string" ? params.name : "";
384
+ console.warn(JSON.stringify({
385
+ event: "mcp-apps:rpc-failed",
386
+ ...context2,
387
+ error: error instanceof Error ? error.message : String(error)
388
+ }));
389
+ return { status: 502, body: { error: "RPC_FAILED" } };
390
+ }
391
+ }
392
+ };
393
+ }
394
+ async function fetchPluginUiResource(backend, tenantId, pluginType, resource) {
395
+ const plugin = await backend.resolvePlugin(tenantId, pluginType);
396
+ if (!plugin) throw new Error(`plugin "${pluginType}" not found`);
397
+ const tool = (plugin.meta.tools ?? []).find((t) => t.ui?.resource === resource);
398
+ if (!tool?.ui) throw new Error(`plugin "${pluginType}" has no tool declaring ui resource "${resource}"`);
399
+ const inline = plugin.meta.uiResources?.[resource];
400
+ if (inline) {
401
+ return {
402
+ mimeType: inline.mimeType ?? "text/html;profile=mcp-app",
403
+ html: inline.html,
404
+ csp: inline.csp ?? tool.ui.csp,
405
+ permissions: tool.ui.permissions,
406
+ prefersBorder: tool.ui.prefersBorder
407
+ };
408
+ }
409
+ const html = await backend.catalog.readPluginFile(tenantId, pluginType, resource);
410
+ return {
411
+ mimeType: "text/html",
412
+ html,
413
+ csp: tool.ui.csp,
414
+ permissions: tool.ui.permissions,
415
+ prefersBorder: tool.ui.prefersBorder
416
+ };
417
+ }
418
+ async function callPluginTool(backend, tenantId, ref, name, args, context, deps) {
419
+ const scope = ref.scope ? (deps?.verifyScope ?? verifyMcpUiScope)(ref.scope) : null;
420
+ if (ref.scope && !scope) {
421
+ throw denyScope(403, "SCOPE_INVALID", {
422
+ tenantId,
423
+ pluginType: ref.pluginType,
424
+ tool: name
425
+ });
426
+ }
427
+ if (scope) {
428
+ const mismatched = scope.tid !== tenantId || scope.plugin !== ref.pluginType || scope.resource !== ref.resource;
429
+ if (mismatched) {
430
+ throw denyScope(403, "SCOPE_MISMATCH", {
431
+ tenantId,
432
+ pluginType: ref.pluginType,
433
+ tool: name,
434
+ jti: scope.jti,
435
+ threadId: scope.thid
436
+ });
437
+ }
438
+ }
439
+ const manifest = await backend.catalog.getValidatedManifest(tenantId, ref.pluginType);
440
+ const rawConnectionKey = args.connectionKey;
441
+ const explicit = typeof rawConnectionKey === "string" ? rawConnectionKey : void 0;
442
+ const scoped = scope?.ck;
443
+ const connectionRelevant = Boolean(manifest?.connection) || scoped !== void 0;
444
+ if (connectionRelevant && (explicit !== void 0 ? explicit !== scoped : rawConnectionKey !== void 0)) {
445
+ throw denyScope(403, "CONNECTION_NOT_ALLOWED", {
446
+ tenantId,
447
+ pluginType: ref.pluginType,
448
+ tool: name,
449
+ jti: scope?.jti,
450
+ threadId: scope?.thid
451
+ });
452
+ }
453
+ let connection;
454
+ let connectionKey;
455
+ if (scoped !== void 0) {
456
+ const resolveConnections = deps?.resolveConnections ?? resolvePluginConnections;
457
+ const resolved = await resolveConnections(
458
+ ref.pluginType,
459
+ { connections: [scoped] },
460
+ { tenantId }
461
+ );
462
+ if (resolved.length === 0) {
463
+ throw denyScope(403, "CONNECTION_NOT_ALLOWED", {
464
+ tenantId,
465
+ pluginType: ref.pluginType,
466
+ tool: name,
467
+ jti: scope?.jti,
468
+ threadId: scope?.thid
469
+ });
470
+ }
471
+ connection = resolved[0].config;
472
+ connectionKey = resolved[0].key;
473
+ }
474
+ if (manifest?.connection && !connectionKey) {
475
+ throw denyScope(409, "CONNECTION_REQUIRED", {
476
+ tenantId,
477
+ pluginType: ref.pluginType,
478
+ tool: name,
479
+ jti: scope?.jti,
480
+ threadId: scope?.thid
481
+ });
482
+ }
483
+ const identity = scope ? {
484
+ workspaceId: scope.wid,
485
+ projectId: scope.pid,
486
+ threadId: scope.thid,
487
+ assistantId: scope.aid
488
+ } : {
489
+ workspaceId: context?.workspaceId,
490
+ projectId: context?.projectId
491
+ };
492
+ return invokePluginTool(backend, tenantId, ref.pluginType, name, args, {
493
+ ...identity,
494
+ ...connection === void 0 ? {} : { connection }
495
+ });
496
+ }
497
+ function createDefaultPluginBackend() {
498
+ return {
499
+ resolvePlugin: (tenantId, type) => resolveTenantPluginByType(tenantId, type),
500
+ catalog: {
501
+ readPluginFile: (tenantId, pluginType, relPath) => getSandboxPluginCatalog().readPluginFile(tenantId, pluginType, relPath),
502
+ getValidatedManifest: (tenantId, pluginType) => getSandboxPluginCatalog().getValidatedManifest(tenantId, pluginType),
503
+ getExecutor: () => getSandboxPluginCatalog().getExecutor()
504
+ }
505
+ };
506
+ }
507
+ var uiResourceResolverSingleton;
508
+ function getMcpUiResourceResolver() {
509
+ return uiResourceResolverSingleton;
510
+ }
511
+ function resolveMcpAppDeps(pluginBackend = createDefaultPluginBackend()) {
512
+ const uiResourceResolver = new UiResourceResolver({
513
+ store: new InMemoryMcpUiResourceCacheStore(),
514
+ fetchMcp: (tenantId, serverKey, resourceUri) => mcpManager.readUiResource(tenantId, serverKey, resourceUri),
515
+ fetchPlugin: (tenantId, pluginType, resource) => fetchPluginUiResource(pluginBackend, tenantId, pluginType, resource)
516
+ });
517
+ uiResourceResolverSingleton = uiResourceResolver;
518
+ return {
519
+ resolveUiResource: (tenantId, ref) => uiResourceResolver.resolve(tenantId, ref),
520
+ callTool: (tenantId, ref, name, args, context) => ref.kind === "mcp" ? mcpManager.callServerTool(tenantId, ref.serverKey, name, args) : callPluginTool(pluginBackend, tenantId, ref, name, args, context),
521
+ // Plugin refs reach these only after `tools/call`; the handler treats the
522
+ // rejection as an unsupported method (403). The branches are live because
523
+ // the pure handler deliberately delegates method policy to the deps.
524
+ readResource: (tenantId, ref, uri) => ref.kind === "mcp" ? mcpManager.readServerResource(tenantId, ref.serverKey, uri) : Promise.reject(new PluginMethodNotSupportedError("resources/read")),
525
+ listTools: (tenantId, ref) => ref.kind === "mcp" ? mcpManager.getAvailableTools(tenantId, ref.serverKey) : Promise.reject(new PluginMethodNotSupportedError("tools/list"))
526
+ };
527
+ }
528
+ function firstHeader(request, name) {
529
+ const header = request.headers[name];
530
+ return Array.isArray(header) ? header[0] : header;
531
+ }
532
+ function tenantOf(request) {
533
+ const user = request.user;
534
+ return user?.tenantId ?? firstHeader(request, "x-tenant-id") ?? "default";
535
+ }
536
+ function runContextOf(request) {
537
+ return {
538
+ tenantId: tenantOf(request),
539
+ workspaceId: firstHeader(request, "x-workspace-id"),
540
+ projectId: firstHeader(request, "x-project-id")
541
+ };
542
+ }
543
+ function registerMcpAppRoutes(fastify, deps = resolveMcpAppDeps()) {
544
+ const handlers = makeMcpAppHandlers(deps);
545
+ fastify.get("/mcp-apps/sandbox.html", async (_request, reply) => {
546
+ return reply.header("content-type", SANDBOX_HTML_CONTENT_TYPE).header("x-content-type-options", "nosniff").header("cache-control", "no-store").send(SANDBOX_HTML);
547
+ });
548
+ fastify.post("/api/mcp-apps/resource", async (request, reply) => {
549
+ const body = asRecord(request.body) ?? {};
550
+ const result = await handlers.resource(tenantOf(request), body.ref);
551
+ return reply.code(result.status).send(result.body);
552
+ });
553
+ fastify.post("/api/mcp-apps/rpc", async (request, reply) => {
554
+ const body = asRecord(request.body) ?? {};
555
+ const params = asRecord(body.params) ?? {};
556
+ const context = runContextOf(request);
557
+ const result = await handlers.rpc(
558
+ context.tenantId,
559
+ body.ref,
560
+ String(body.method ?? ""),
561
+ params,
562
+ context
563
+ );
564
+ return reply.code(result.status).send(result.body);
565
+ });
566
+ }
567
+
568
+ export {
569
+ listTenantPlugins,
570
+ resolveTenantPluginByType,
571
+ PluginMethodNotSupportedError,
572
+ McpAppScopeError,
573
+ makeMcpAppHandlers,
574
+ fetchPluginUiResource,
575
+ callPluginTool,
576
+ getMcpUiResourceResolver,
577
+ resolveMcpAppDeps,
578
+ registerMcpAppRoutes
579
+ };
580
+ //# sourceMappingURL=chunk-T2HIWZGP.mjs.map