skybridge 0.0.0-dev.cb51733 → 0.0.0-dev.cba8734

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 (191) hide show
  1. package/README.md +9 -0
  2. package/dist/cli/build-helpers.d.ts +7 -0
  3. package/dist/cli/build-helpers.js +82 -0
  4. package/dist/cli/build-helpers.js.map +1 -0
  5. package/dist/cli/build-helpers.test.js +64 -0
  6. package/dist/cli/build-helpers.test.js.map +1 -0
  7. package/dist/cli/detect-port.d.ts +2 -2
  8. package/dist/cli/detect-port.js +9 -20
  9. package/dist/cli/detect-port.js.map +1 -1
  10. package/dist/cli/resolve-views-dir.d.ts +1 -0
  11. package/dist/cli/resolve-views-dir.js +17 -0
  12. package/dist/cli/resolve-views-dir.js.map +1 -0
  13. package/dist/commands/build.d.ts +0 -6
  14. package/dist/commands/build.js +52 -122
  15. package/dist/commands/build.js.map +1 -1
  16. package/dist/commands/dev.js +16 -0
  17. package/dist/commands/dev.js.map +1 -1
  18. package/dist/commands/start.js +7 -5
  19. package/dist/commands/start.js.map +1 -1
  20. package/dist/server/auth/discovery.d.ts +32 -0
  21. package/dist/server/auth/discovery.js +56 -0
  22. package/dist/server/auth/discovery.js.map +1 -0
  23. package/dist/server/auth/discovery.test.d.ts +1 -0
  24. package/dist/server/auth/discovery.test.js +93 -0
  25. package/dist/server/auth/discovery.test.js.map +1 -0
  26. package/dist/server/auth/index.d.ts +18 -0
  27. package/dist/server/auth/index.js +2 -0
  28. package/dist/server/auth/index.js.map +1 -0
  29. package/dist/server/auth/providers/auth0.d.ts +18 -0
  30. package/dist/server/auth/providers/auth0.js +31 -0
  31. package/dist/server/auth/providers/auth0.js.map +1 -0
  32. package/dist/server/auth/providers/auth0.test.d.ts +1 -0
  33. package/dist/server/auth/providers/auth0.test.js +48 -0
  34. package/dist/server/auth/providers/auth0.test.js.map +1 -0
  35. package/dist/server/auth/providers/clerk.d.ts +14 -0
  36. package/dist/server/auth/providers/clerk.js +16 -0
  37. package/dist/server/auth/providers/clerk.js.map +1 -0
  38. package/dist/server/auth/providers/clerk.test.d.ts +1 -0
  39. package/dist/server/auth/providers/clerk.test.js +28 -0
  40. package/dist/server/auth/providers/clerk.test.js.map +1 -0
  41. package/dist/server/auth/providers/custom.d.ts +24 -0
  42. package/dist/server/auth/providers/custom.js +37 -0
  43. package/dist/server/auth/providers/custom.js.map +1 -0
  44. package/dist/server/auth/providers/custom.test.d.ts +1 -0
  45. package/dist/server/auth/providers/custom.test.js +107 -0
  46. package/dist/server/auth/providers/custom.test.js.map +1 -0
  47. package/dist/server/auth/providers/descope.d.ts +15 -0
  48. package/dist/server/auth/providers/descope.js +33 -0
  49. package/dist/server/auth/providers/descope.js.map +1 -0
  50. package/dist/server/auth/providers/descope.test.d.ts +1 -0
  51. package/dist/server/auth/providers/descope.test.js +37 -0
  52. package/dist/server/auth/providers/descope.test.js.map +1 -0
  53. package/dist/server/auth/providers/shared.d.ts +2 -0
  54. package/dist/server/auth/providers/shared.js +6 -0
  55. package/dist/server/auth/providers/shared.js.map +1 -0
  56. package/dist/server/auth/providers/shared.test.d.ts +1 -0
  57. package/dist/server/auth/providers/shared.test.js +10 -0
  58. package/dist/server/auth/providers/shared.test.js.map +1 -0
  59. package/dist/server/auth/providers/stytch.d.ts +12 -0
  60. package/dist/server/auth/providers/stytch.js +13 -0
  61. package/dist/server/auth/providers/stytch.js.map +1 -0
  62. package/dist/server/auth/providers/workos.d.ts +11 -0
  63. package/dist/server/auth/providers/workos.js +12 -0
  64. package/dist/server/auth/providers/workos.js.map +1 -0
  65. package/dist/server/auth/setup.d.ts +4 -0
  66. package/dist/server/auth/setup.js +51 -0
  67. package/dist/server/auth/setup.js.map +1 -0
  68. package/dist/server/auth/setup.test.d.ts +1 -0
  69. package/dist/server/auth/setup.test.js +185 -0
  70. package/dist/server/auth/setup.test.js.map +1 -0
  71. package/dist/server/auth/verify.d.ts +12 -0
  72. package/dist/server/auth/verify.js +38 -0
  73. package/dist/server/auth/verify.js.map +1 -0
  74. package/dist/server/auth/verify.test.d.ts +1 -0
  75. package/dist/server/auth/verify.test.js +100 -0
  76. package/dist/server/auth/verify.test.js.map +1 -0
  77. package/dist/server/build-manifest.test.d.ts +1 -0
  78. package/dist/server/build-manifest.test.js +27 -0
  79. package/dist/server/build-manifest.test.js.map +1 -0
  80. package/dist/server/express.js +1 -3
  81. package/dist/server/express.js.map +1 -1
  82. package/dist/server/express.test.js +31 -28
  83. package/dist/server/express.test.js.map +1 -1
  84. package/dist/server/index.d.ts +9 -2
  85. package/dist/server/index.js +7 -1
  86. package/dist/server/index.js.map +1 -1
  87. package/dist/server/requestOrigin.d.ts +7 -0
  88. package/dist/server/requestOrigin.js +25 -0
  89. package/dist/server/requestOrigin.js.map +1 -0
  90. package/dist/server/server.d.ts +44 -33
  91. package/dist/server/server.js +158 -122
  92. package/dist/server/server.js.map +1 -1
  93. package/dist/server/view-resource-resolution.test.d.ts +6 -0
  94. package/dist/server/view-resource-resolution.test.js +103 -0
  95. package/dist/server/view-resource-resolution.test.js.map +1 -0
  96. package/dist/test/view.test.js +69 -101
  97. package/dist/test/view.test.js.map +1 -1
  98. package/dist/web/bridges/adaptor.d.ts +51 -0
  99. package/dist/web/bridges/adaptor.js +310 -0
  100. package/dist/web/bridges/adaptor.js.map +1 -0
  101. package/dist/web/bridges/adaptor.test.d.ts +1 -0
  102. package/dist/web/bridges/adaptor.test.js +197 -0
  103. package/dist/web/bridges/adaptor.test.js.map +1 -0
  104. package/dist/web/bridges/apps-sdk/bridge.d.ts +6 -2
  105. package/dist/web/bridges/apps-sdk/bridge.js +15 -4
  106. package/dist/web/bridges/apps-sdk/bridge.js.map +1 -1
  107. package/dist/web/bridges/apps-sdk/index.d.ts +0 -1
  108. package/dist/web/bridges/apps-sdk/index.js +0 -1
  109. package/dist/web/bridges/apps-sdk/index.js.map +1 -1
  110. package/dist/web/bridges/get-adaptor.d.ts +4 -4
  111. package/dist/web/bridges/get-adaptor.js +6 -11
  112. package/dist/web/bridges/get-adaptor.js.map +1 -1
  113. package/dist/web/bridges/get-adaptor.test.d.ts +1 -0
  114. package/dist/web/bridges/get-adaptor.test.js +32 -0
  115. package/dist/web/bridges/get-adaptor.test.js.map +1 -0
  116. package/dist/web/bridges/mcp-app/bridge.d.ts +15 -3
  117. package/dist/web/bridges/mcp-app/bridge.js +68 -5
  118. package/dist/web/bridges/mcp-app/bridge.js.map +1 -1
  119. package/dist/web/bridges/mcp-app/bridge.test.d.ts +1 -0
  120. package/dist/web/bridges/mcp-app/bridge.test.js +17 -0
  121. package/dist/web/bridges/mcp-app/bridge.test.js.map +1 -0
  122. package/dist/web/bridges/mcp-app/index.d.ts +0 -1
  123. package/dist/web/bridges/mcp-app/index.js +0 -1
  124. package/dist/web/bridges/mcp-app/index.js.map +1 -1
  125. package/dist/web/bridges/mcp-app/view-tools.test.d.ts +1 -0
  126. package/dist/web/bridges/mcp-app/view-tools.test.js +143 -0
  127. package/dist/web/bridges/mcp-app/view-tools.test.js.map +1 -0
  128. package/dist/web/bridges/types.d.ts +44 -1
  129. package/dist/web/bridges/types.js +14 -1
  130. package/dist/web/bridges/types.js.map +1 -1
  131. package/dist/web/bridges/types.test.d.ts +1 -0
  132. package/dist/web/bridges/types.test.js +19 -0
  133. package/dist/web/bridges/types.test.js.map +1 -0
  134. package/dist/web/components/modal-provider.js +4 -3
  135. package/dist/web/components/modal-provider.js.map +1 -1
  136. package/dist/web/create-store.test.js +8 -5
  137. package/dist/web/create-store.test.js.map +1 -1
  138. package/dist/web/data-llm.test.js +9 -5
  139. package/dist/web/data-llm.test.js.map +1 -1
  140. package/dist/web/generate-helpers.test-d.js +4 -2
  141. package/dist/web/generate-helpers.test-d.js.map +1 -1
  142. package/dist/web/hooks/index.d.ts +1 -0
  143. package/dist/web/hooks/index.js +1 -0
  144. package/dist/web/hooks/index.js.map +1 -1
  145. package/dist/web/hooks/use-call-tool.test.js +37 -23
  146. package/dist/web/hooks/use-call-tool.test.js.map +1 -1
  147. package/dist/web/hooks/use-display-mode.test.js +56 -20
  148. package/dist/web/hooks/use-display-mode.test.js.map +1 -1
  149. package/dist/web/hooks/use-download.test.js +11 -3
  150. package/dist/web/hooks/use-download.test.js.map +1 -1
  151. package/dist/web/hooks/use-files.test.js +10 -2
  152. package/dist/web/hooks/use-files.test.js.map +1 -1
  153. package/dist/web/hooks/use-layout.test.js +59 -26
  154. package/dist/web/hooks/use-layout.test.js.map +1 -1
  155. package/dist/web/hooks/use-open-external.test.js +13 -3
  156. package/dist/web/hooks/use-open-external.test.js.map +1 -1
  157. package/dist/web/hooks/use-register-view-tool.d.ts +38 -0
  158. package/dist/web/hooks/use-register-view-tool.js +50 -0
  159. package/dist/web/hooks/use-register-view-tool.js.map +1 -0
  160. package/dist/web/hooks/use-request-close.test.js +35 -40
  161. package/dist/web/hooks/use-request-close.test.js.map +1 -1
  162. package/dist/web/hooks/use-request-modal.test.js +10 -0
  163. package/dist/web/hooks/use-request-modal.test.js.map +1 -1
  164. package/dist/web/hooks/use-request-size.test.js +35 -53
  165. package/dist/web/hooks/use-request-size.test.js.map +1 -1
  166. package/dist/web/hooks/use-set-open-in-app-url.test.js +28 -8
  167. package/dist/web/hooks/use-set-open-in-app-url.test.js.map +1 -1
  168. package/dist/web/hooks/use-tool-info.d.ts +25 -7
  169. package/dist/web/hooks/use-tool-info.js +5 -8
  170. package/dist/web/hooks/use-tool-info.js.map +1 -1
  171. package/dist/web/hooks/use-tool-info.test-d.js +11 -29
  172. package/dist/web/hooks/use-tool-info.test-d.js.map +1 -1
  173. package/dist/web/hooks/use-tool-info.test.js +42 -32
  174. package/dist/web/hooks/use-tool-info.test.js.map +1 -1
  175. package/dist/web/hooks/use-user.test.js +81 -35
  176. package/dist/web/hooks/use-user.test.js.map +1 -1
  177. package/dist/web/hooks/use-view-state.test.js +6 -2
  178. package/dist/web/hooks/use-view-state.test.js.map +1 -1
  179. package/package.json +6 -3
  180. package/dist/commands/build-helpers.d.ts +0 -26
  181. package/dist/commands/build-helpers.js +0 -93
  182. package/dist/commands/build-helpers.js.map +0 -1
  183. package/dist/commands/build-helpers.test.js +0 -116
  184. package/dist/commands/build-helpers.test.js.map +0 -1
  185. package/dist/web/bridges/apps-sdk/adaptor.d.ts +0 -28
  186. package/dist/web/bridges/apps-sdk/adaptor.js +0 -113
  187. package/dist/web/bridges/apps-sdk/adaptor.js.map +0 -1
  188. package/dist/web/bridges/mcp-app/adaptor.d.ts +0 -52
  189. package/dist/web/bridges/mcp-app/adaptor.js +0 -280
  190. package/dist/web/bridges/mcp-app/adaptor.js.map +0 -1
  191. /package/dist/{commands → cli}/build-helpers.test.d.ts +0 -0
@@ -6,9 +6,11 @@ import { Server as SdkServer, } from "@modelcontextprotocol/sdk/server/index.js"
6
6
  import { McpServer as McpServerBase } from "@modelcontextprotocol/sdk/server/mcp.js";
7
7
  import { mergeWith, union } from "es-toolkit";
8
8
  import express, {} from "express";
9
+ import { setupOAuth } from "./auth/setup.js";
9
10
  import { createApp } from "./express.js";
10
11
  import { createMiddlewareEntry } from "./metric.js";
11
12
  import { buildMiddlewareChain, getHandlerMaps } from "./middleware.js";
13
+ import { resolveServerOrigin } from "./requestOrigin.js";
12
14
  import { templateHelper } from "./templateHelper.js";
13
15
  const mergeWithUnion = (target, source) => {
14
16
  return mergeWith(target, source, (targetVal, sourceVal) => {
@@ -17,6 +19,15 @@ const mergeWithUnion = (target, source) => {
17
19
  }
18
20
  });
19
21
  };
22
+ /**
23
+ * Drop the query string from a `ui://` view URI, leaving the bare path. The
24
+ * `?v=` cache key is the only query we append, so a plain split is enough and
25
+ * sidesteps `URL` normalization quirks on the non-special `ui:` scheme.
26
+ */
27
+ function stripQuery(uri) {
28
+ const queryIndex = uri.indexOf("?");
29
+ return queryIndex === -1 ? uri : uri.slice(0, queryIndex);
30
+ }
20
31
  /**
21
32
  * Coerce a tool handler's return value into an MCP `content` array. Strings
22
33
  * become a single `TextContent`; a single block is wrapped in an array;
@@ -65,13 +76,31 @@ const McpServerBaseOmitted = McpServerBase;
65
76
  *
66
77
  * @see https://docs.skybridge.tech/api-reference/mcp-server
67
78
  */
79
+ // Side channel populated by `dist/__entry.js` before user code is imported.
80
+ // Set at module scope rather than passed through the constructor because the
81
+ // wrapper has the manifest before the user's `new McpServer(...)` runs, and
82
+ // threading it through every call site (including user templates) is exactly
83
+ // the boilerplate this design is trying to hide.
84
+ let pendingBuildManifest = null;
85
+ /**
86
+ * Prime the build-time Vite manifest before user code constructs its
87
+ * `McpServer`. Called from the generated `dist/__entry.js`; not part of the
88
+ * user-facing API.
89
+ *
90
+ * @internal
91
+ */
92
+ export function __setBuildManifest(manifest) {
93
+ pendingBuildManifest = manifest;
94
+ }
68
95
  export class McpServer extends McpServerBaseOmitted {
69
96
  /**
70
97
  * The underlying Express app. Use this to extend the HTTP server with
71
98
  * custom routes, middleware, or settings — e.g.
72
99
  * `server.express.get("/health", ...)`.
73
100
  *
74
- * `express.json()` is pre-applied. Register your handlers before `run()`;
101
+ * `express.json()` is pre-applied — tune it via the constructor's third
102
+ * argument, e.g. `new McpServer(info, {}, { json: { limit: "10mb" } })`.
103
+ * Register your handlers before `run()`;
75
104
  * after `run()`, dev-mode middleware, the `/mcp` route, and the default
76
105
  * error handler are appended in that order.
77
106
  *
@@ -84,15 +113,34 @@ export class McpServer extends McpServerBaseOmitted {
84
113
  mcpMiddlewareApplied = false;
85
114
  claimedViews = new Map();
86
115
  viewMetaBuilders = new Map();
116
+ /**
117
+ * Maps a view resource's query-less path to its canonical registered URI
118
+ * (the one carrying the `?v=` cache key). Lets `resources/read` resolve the
119
+ * underlying view no matter which version param the consumer sends, since
120
+ * the param is only a cache key, not part of the resource's identity.
121
+ */
122
+ viewUriByPath = new Map();
87
123
  viteManifest = null;
88
124
  serverInfo;
89
125
  serverOptions;
90
- constructor(serverInfo, options) {
126
+ constructor(serverInfo, options, skybridgeOptions) {
91
127
  super(serverInfo, options);
92
128
  this.serverInfo = serverInfo;
93
129
  this.serverOptions = options;
94
130
  this.express = express();
95
- this.express.use(express.json());
131
+ this.express.use(express.json(skybridgeOptions?.json));
132
+ if (skybridgeOptions?.oauth) {
133
+ setupOAuth(this.express, skybridgeOptions.oauth);
134
+ }
135
+ // Pick up the manifest if `dist/__entry.js` primed it before importing
136
+ // user code. Consume-once: clear after the first construction so a
137
+ // subsequent test that doesn't prime can't inherit stale state.
138
+ // Explicit `setViteManifest` calls still win because they happen after
139
+ // construction.
140
+ if (pendingBuildManifest) {
141
+ this.setViteManifest(pendingBuildManifest);
142
+ pendingBuildManifest = null;
143
+ }
96
144
  }
97
145
  use(pathOrHandler, ...handlers) {
98
146
  // Branching is load-bearing: Express's `app.use` overloads can't be
@@ -165,10 +213,47 @@ export class McpServer extends McpServerBaseOmitted {
165
213
  return result;
166
214
  },
167
215
  };
216
+ // Resolve a view's `resources/read` by its query-less path so the
217
+ // underlying asset is served no matter the `?v=` value (stale cache key,
218
+ // no param, etc.). The version param is a cache-busting hint for external
219
+ // consumers; it must not gate resolution. We rewrite the lookup URI to the
220
+ // canonical registered one, then restore the requested URI on the response
221
+ // so the consumer-facing URI is never rewritten.
222
+ const viewReadResolveEntry = {
223
+ filter: "resources/read",
224
+ handler: async (req, _extra, next) => {
225
+ const requested = req.params.uri;
226
+ if (typeof requested !== "string") {
227
+ return next();
228
+ }
229
+ const path = stripQuery(requested);
230
+ const canonical = this.viewUriByPath.get(path);
231
+ if (!canonical) {
232
+ return next();
233
+ }
234
+ req.params.uri = canonical;
235
+ try {
236
+ const result = (await next());
237
+ for (const content of result.contents ?? []) {
238
+ if (typeof content.uri === "string" &&
239
+ stripQuery(content.uri) === stripQuery(canonical)) {
240
+ content.uri = requested;
241
+ }
242
+ }
243
+ return result;
244
+ }
245
+ finally {
246
+ // Restore the shared request params so middleware outer to us never
247
+ // observes the rewritten lookup URI after next() unwinds.
248
+ req.params.uri = requested;
249
+ }
250
+ },
251
+ };
168
252
  const monitoringEntry = createMiddlewareEntry();
169
253
  const entries = [
170
254
  ...(monitoringEntry ? [monitoringEntry] : []),
171
255
  viewListMetaEntry,
256
+ viewReadResolveEntry,
172
257
  ...this.mcpMiddlewareEntries,
173
258
  ];
174
259
  if (entries.length === 0) {
@@ -231,6 +316,9 @@ export class McpServer extends McpServerBaseOmitted {
231
316
  async run() {
232
317
  this.applyMcpMiddleware();
233
318
  if (process.env.VERCEL === "1") {
319
+ // createApp only reads httpServer inside its dev-only branch
320
+ // (viewsDevServer); under VERCEL=1 + NODE_ENV=production it's a
321
+ // bare object passed to satisfy the required parameter.
234
322
  const httpServer = http.createServer();
235
323
  await createApp({
236
324
  mcpServer: this,
@@ -294,27 +382,7 @@ export class McpServer extends McpServerBaseOmitted {
294
382
  return Array.isArray(val) ? val[0] : val;
295
383
  };
296
384
  const isClaude = header("user-agent") === "Claude-User";
297
- let serverUrl;
298
- const forwardedHost = header("x-forwarded-host");
299
- const origin = header("origin");
300
- const host = header("host");
301
- if (forwardedHost) {
302
- const proto = header("x-forwarded-proto") || "https";
303
- serverUrl = `${proto}://${forwardedHost}`;
304
- }
305
- else if (origin) {
306
- serverUrl = origin;
307
- }
308
- else if (host) {
309
- const proto = ["127.0.0.1:", "localhost:"].some((p) => host.startsWith(p))
310
- ? "http"
311
- : "https";
312
- serverUrl = `${proto}://${host}`;
313
- }
314
- else {
315
- const devPort = process.env.__PORT || "3000";
316
- serverUrl = `http://localhost:${devPort}`;
317
- }
385
+ const serverUrl = resolveServerOrigin(header);
318
386
  const connectDomains = [serverUrl];
319
387
  if (!isProduction) {
320
388
  const wsUrl = new URL(serverUrl);
@@ -338,111 +406,74 @@ export class McpServer extends McpServerBaseOmitted {
338
406
  return { serverUrl, connectDomains, contentMetaOverrides };
339
407
  }
340
408
  registerViewResources(toolName, view, toolMeta) {
341
- const hosts = view.hosts ?? ["apps-sdk", "mcp-app"];
342
409
  // Append a content-derived version param so hosts (e.g. ChatGPT) bust
343
410
  // their cache when the bundle changes, but keep the URI stable across
344
411
  // `tools/list` calls when the bundle hasn't changed.
345
412
  const versionParam = this.computeViewVersionParam(view.component);
346
- if (hosts.includes("apps-sdk")) {
347
- const viewResource = {
348
- hostType: "apps-sdk",
349
- uri: `ui://views/apps-sdk/${view.component}.html${versionParam}`,
350
- mimeType: "text/html+skybridge",
351
- buildContentMeta: ({ resourceDomains, connectDomains, domain }, overrides) => {
352
- const defaults = {
353
- "openai/widgetCSP": {
354
- resource_domains: resourceDomains,
355
- connect_domains: connectDomains,
413
+ const viewResource = {
414
+ hostType: "mcp-app",
415
+ uri: `ui://views/ext-apps/${view.component}.html${versionParam}`,
416
+ mimeType: "text/html;profile=mcp-app",
417
+ buildContentMeta: ({ resourceDomains, connectDomains, domain, baseUriDomains }, overrides) => {
418
+ const defaults = {
419
+ ui: {
420
+ csp: {
421
+ resourceDomains,
422
+ connectDomains,
423
+ baseUriDomains,
356
424
  },
357
- "openai/widgetDomain": domain,
358
- "openai/widgetDescription": view.description,
359
- };
360
- const fromView = {
361
- "openai/widgetCSP": {
362
- resource_domains: view.csp?.resourceDomains,
363
- connect_domains: view.csp?.connectDomains,
364
- frame_domains: view.csp?.frameDomains,
365
- redirect_domains: view.csp?.redirectDomains,
366
- },
367
- "openai/widgetDomain": view.domain,
368
- "openai/widgetPrefersBorder": view.prefersBorder,
369
- };
370
- const base = mergeWithUnion(mergeWithUnion(defaults, fromView), {
371
- "openai/widgetDomain": overrides.domain,
372
- });
373
- if (view._meta) {
374
- return { ...base, ...view._meta };
375
- }
376
- return base;
377
- },
378
- };
379
- this.registerViewResource({
380
- name: toolName,
381
- viewResource,
382
- view,
383
- });
384
- toolMeta["openai/outputTemplate"] = viewResource.uri;
385
- }
386
- if (hosts.includes("mcp-app")) {
387
- const viewResource = {
388
- hostType: "mcp-app",
389
- uri: `ui://views/ext-apps/${view.component}.html${versionParam}`,
390
- mimeType: "text/html;profile=mcp-app",
391
- buildContentMeta: ({ resourceDomains, connectDomains, domain, baseUriDomains }, overrides) => {
392
- const defaults = {
393
- ui: {
394
- csp: {
395
- resourceDomains,
396
- connectDomains,
397
- baseUriDomains,
398
- },
399
- domain,
400
- },
401
- };
402
- const fromView = {
403
- ui: {
404
- ...(view.description && { description: view.description }),
405
- ...(view.prefersBorder !== undefined && {
406
- prefersBorder: view.prefersBorder,
425
+ domain,
426
+ },
427
+ };
428
+ const fromView = {
429
+ ui: {
430
+ ...(view.description && { description: view.description }),
431
+ ...(view.prefersBorder !== undefined && {
432
+ prefersBorder: view.prefersBorder,
433
+ }),
434
+ ...(view.domain && { domain: view.domain }),
435
+ csp: {
436
+ ...(view.csp?.resourceDomains && {
437
+ resourceDomains: view.csp.resourceDomains,
438
+ }),
439
+ ...(view.csp?.connectDomains && {
440
+ connectDomains: view.csp.connectDomains,
441
+ }),
442
+ ...(view.csp?.frameDomains && {
443
+ frameDomains: view.csp.frameDomains,
444
+ }),
445
+ ...(view.csp?.baseUriDomains && {
446
+ baseUriDomains: view.csp.baseUriDomains,
407
447
  }),
408
- ...(view.domain && { domain: view.domain }),
409
- csp: {
410
- ...(view.csp?.resourceDomains && {
411
- resourceDomains: view.csp.resourceDomains,
412
- }),
413
- ...(view.csp?.connectDomains && {
414
- connectDomains: view.csp.connectDomains,
415
- }),
416
- ...(view.csp?.frameDomains && {
417
- frameDomains: view.csp.frameDomains,
418
- }),
419
- ...(view.csp?.baseUriDomains && {
420
- baseUriDomains: view.csp.baseUriDomains,
421
- }),
422
- ...(view.csp?.redirectDomains && {
423
- redirectDomains: view.csp.redirectDomains,
424
- }),
425
- },
426
448
  },
427
- };
428
- const base = mergeWithUnion(mergeWithUnion(defaults, fromView), {
429
- ui: overrides,
430
- });
431
- if (view._meta) {
432
- return { ...base, ...view._meta };
433
- }
434
- return base;
435
- },
436
- };
437
- this.registerViewResource({
438
- name: toolName,
439
- viewResource,
440
- view,
441
- });
442
- // @ts-expect-error - For backwards compatibility with Claude current implementation of the specs
443
- toolMeta["ui/resourceUri"] = viewResource.uri;
444
- toolMeta.ui = { ...toolMeta.ui, resourceUri: viewResource.uri };
445
- }
449
+ },
450
+ };
451
+ const ui = mergeWithUnion(mergeWithUnion(defaults, fromView), {
452
+ ui: overrides,
453
+ });
454
+ const base = {
455
+ ...ui,
456
+ ...(view.description && {
457
+ "openai/widgetDescription": view.description,
458
+ }),
459
+ ...(view.csp?.redirectDomains && {
460
+ "openai/widgetCSP": { redirect_domains: view.csp.redirectDomains },
461
+ }),
462
+ };
463
+ if (view._meta) {
464
+ return { ...base, ...view._meta };
465
+ }
466
+ return base;
467
+ },
468
+ };
469
+ this.registerViewResource({ name: toolName, viewResource, view });
470
+ // Advertise via the MCP Apps standard pointer only — ChatGPT renders from
471
+ // ui.resourceUri (verified), and not emitting openai/outputTemplate lets us
472
+ // retire the legacy apps-sdk resource later. The legacy apps-sdk URL is still
473
+ // served (see registerViewResource) so already-published apps keep resolving.
474
+ // @ts-expect-error - For backwards compatibility with Claude current implementation of the specs
475
+ toolMeta["ui/resourceUri"] = viewResource.uri;
476
+ toolMeta.ui = { ...toolMeta.ui, resourceUri: viewResource.uri };
446
477
  }
447
478
  registerViewResource({ name, viewResource, view, }) {
448
479
  const { hostType, uri: viewUri, mimeType, buildContentMeta } = viewResource;
@@ -456,6 +487,8 @@ export class McpServer extends McpServerBaseOmitted {
456
487
  }, contentMetaOverrides);
457
488
  };
458
489
  this.viewMetaBuilders.set(viewUri, buildMeta);
490
+ this.viewUriByPath.set(stripQuery(viewUri), viewUri);
491
+ this.serveLegacyAppsSdkUrl(view.component, viewUri);
459
492
  this.registerResource(name, viewUri, { description: view.description }, async (uri, extra) => {
460
493
  const isProduction = process.env.NODE_ENV === "production";
461
494
  const { serverUrl } = this.resolveViewRequestContext(extra);
@@ -478,6 +511,9 @@ export class McpServer extends McpServerBaseOmitted {
478
511
  };
479
512
  });
480
513
  }
514
+ serveLegacyAppsSdkUrl(component, canonicalUri) {
515
+ this.viewUriByPath.set(`ui://views/apps-sdk/${component}.html`, canonicalUri);
516
+ }
481
517
  wrapHandler(cb, { attachViewUUID }) {
482
518
  return async (args, extra) => {
483
519
  const result = await cb(args, extra);