skybridge 0.0.0-dev.aeffa48 → 0.0.0-dev.af3ec22

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 (192) hide show
  1. package/dist/cli/build-helpers.d.ts +7 -0
  2. package/dist/cli/build-helpers.js +82 -0
  3. package/dist/cli/build-helpers.js.map +1 -0
  4. package/dist/cli/build-helpers.test.d.ts +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 -1
  14. package/dist/commands/build.js +18 -30
  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 -1
  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/security-schemes.d.ts +11 -0
  66. package/dist/server/auth/security-schemes.js +39 -0
  67. package/dist/server/auth/security-schemes.js.map +1 -0
  68. package/dist/server/auth/security-schemes.test.d.ts +1 -0
  69. package/dist/server/auth/security-schemes.test.js +53 -0
  70. package/dist/server/auth/security-schemes.test.js.map +1 -0
  71. package/dist/server/auth/setup.d.ts +11 -0
  72. package/dist/server/auth/setup.js +82 -0
  73. package/dist/server/auth/setup.js.map +1 -0
  74. package/dist/server/auth/setup.test.d.ts +1 -0
  75. package/dist/server/auth/setup.test.js +347 -0
  76. package/dist/server/auth/setup.test.js.map +1 -0
  77. package/dist/server/auth/verify.d.ts +12 -0
  78. package/dist/server/auth/verify.js +38 -0
  79. package/dist/server/auth/verify.js.map +1 -0
  80. package/dist/server/auth/verify.test.d.ts +1 -0
  81. package/dist/server/auth/verify.test.js +100 -0
  82. package/dist/server/auth/verify.test.js.map +1 -0
  83. package/dist/server/build-manifest.test.d.ts +1 -0
  84. package/dist/server/build-manifest.test.js +27 -0
  85. package/dist/server/build-manifest.test.js.map +1 -0
  86. package/dist/server/express.test.js +61 -0
  87. package/dist/server/express.test.js.map +1 -1
  88. package/dist/server/index.d.ts +9 -2
  89. package/dist/server/index.js +7 -1
  90. package/dist/server/index.js.map +1 -1
  91. package/dist/server/requestOrigin.d.ts +7 -0
  92. package/dist/server/requestOrigin.js +25 -0
  93. package/dist/server/requestOrigin.js.map +1 -0
  94. package/dist/server/server.d.ts +63 -35
  95. package/dist/server/server.js +212 -125
  96. package/dist/server/server.js.map +1 -1
  97. package/dist/server/view-name.test-d.d.ts +1 -0
  98. package/dist/server/view-name.test-d.js +8 -0
  99. package/dist/server/view-name.test-d.js.map +1 -0
  100. package/dist/server/view-resource-resolution.test.d.ts +6 -0
  101. package/dist/server/view-resource-resolution.test.js +103 -0
  102. package/dist/server/view-resource-resolution.test.js.map +1 -0
  103. package/dist/test/view.test.js +69 -101
  104. package/dist/test/view.test.js.map +1 -1
  105. package/dist/web/bridges/adaptor.d.ts +51 -0
  106. package/dist/web/bridges/adaptor.js +310 -0
  107. package/dist/web/bridges/adaptor.js.map +1 -0
  108. package/dist/web/bridges/adaptor.test.d.ts +1 -0
  109. package/dist/web/bridges/adaptor.test.js +197 -0
  110. package/dist/web/bridges/adaptor.test.js.map +1 -0
  111. package/dist/web/bridges/apps-sdk/bridge.d.ts +6 -2
  112. package/dist/web/bridges/apps-sdk/bridge.js +15 -4
  113. package/dist/web/bridges/apps-sdk/bridge.js.map +1 -1
  114. package/dist/web/bridges/apps-sdk/index.d.ts +0 -1
  115. package/dist/web/bridges/apps-sdk/index.js +0 -1
  116. package/dist/web/bridges/apps-sdk/index.js.map +1 -1
  117. package/dist/web/bridges/get-adaptor.d.ts +4 -4
  118. package/dist/web/bridges/get-adaptor.js +6 -11
  119. package/dist/web/bridges/get-adaptor.js.map +1 -1
  120. package/dist/web/bridges/get-adaptor.test.d.ts +1 -0
  121. package/dist/web/bridges/get-adaptor.test.js +32 -0
  122. package/dist/web/bridges/get-adaptor.test.js.map +1 -0
  123. package/dist/web/bridges/mcp-app/bridge.d.ts +15 -3
  124. package/dist/web/bridges/mcp-app/bridge.js +68 -5
  125. package/dist/web/bridges/mcp-app/bridge.js.map +1 -1
  126. package/dist/web/bridges/mcp-app/bridge.test.d.ts +1 -0
  127. package/dist/web/bridges/mcp-app/bridge.test.js +17 -0
  128. package/dist/web/bridges/mcp-app/bridge.test.js.map +1 -0
  129. package/dist/web/bridges/mcp-app/index.d.ts +0 -1
  130. package/dist/web/bridges/mcp-app/index.js +0 -1
  131. package/dist/web/bridges/mcp-app/index.js.map +1 -1
  132. package/dist/web/bridges/mcp-app/view-tools.test.d.ts +1 -0
  133. package/dist/web/bridges/mcp-app/view-tools.test.js +143 -0
  134. package/dist/web/bridges/mcp-app/view-tools.test.js.map +1 -0
  135. package/dist/web/bridges/types.d.ts +44 -1
  136. package/dist/web/bridges/types.js +14 -1
  137. package/dist/web/bridges/types.js.map +1 -1
  138. package/dist/web/bridges/types.test.d.ts +1 -0
  139. package/dist/web/bridges/types.test.js +19 -0
  140. package/dist/web/bridges/types.test.js.map +1 -0
  141. package/dist/web/components/modal-provider.js +4 -3
  142. package/dist/web/components/modal-provider.js.map +1 -1
  143. package/dist/web/create-store.test.js +8 -5
  144. package/dist/web/create-store.test.js.map +1 -1
  145. package/dist/web/data-llm.test.js +9 -5
  146. package/dist/web/data-llm.test.js.map +1 -1
  147. package/dist/web/generate-helpers.test-d.js +4 -2
  148. package/dist/web/generate-helpers.test-d.js.map +1 -1
  149. package/dist/web/hooks/index.d.ts +1 -0
  150. package/dist/web/hooks/index.js +1 -0
  151. package/dist/web/hooks/index.js.map +1 -1
  152. package/dist/web/hooks/use-call-tool.test.js +37 -23
  153. package/dist/web/hooks/use-call-tool.test.js.map +1 -1
  154. package/dist/web/hooks/use-display-mode.test.js +56 -20
  155. package/dist/web/hooks/use-display-mode.test.js.map +1 -1
  156. package/dist/web/hooks/use-download.test.js +11 -3
  157. package/dist/web/hooks/use-download.test.js.map +1 -1
  158. package/dist/web/hooks/use-files.test.js +10 -2
  159. package/dist/web/hooks/use-files.test.js.map +1 -1
  160. package/dist/web/hooks/use-layout.test.js +59 -26
  161. package/dist/web/hooks/use-layout.test.js.map +1 -1
  162. package/dist/web/hooks/use-open-external.test.js +13 -3
  163. package/dist/web/hooks/use-open-external.test.js.map +1 -1
  164. package/dist/web/hooks/use-register-view-tool.d.ts +38 -0
  165. package/dist/web/hooks/use-register-view-tool.js +50 -0
  166. package/dist/web/hooks/use-register-view-tool.js.map +1 -0
  167. package/dist/web/hooks/use-request-close.test.js +35 -40
  168. package/dist/web/hooks/use-request-close.test.js.map +1 -1
  169. package/dist/web/hooks/use-request-modal.test.js +10 -0
  170. package/dist/web/hooks/use-request-modal.test.js.map +1 -1
  171. package/dist/web/hooks/use-request-size.test.js +35 -53
  172. package/dist/web/hooks/use-request-size.test.js.map +1 -1
  173. package/dist/web/hooks/use-set-open-in-app-url.test.js +28 -8
  174. package/dist/web/hooks/use-set-open-in-app-url.test.js.map +1 -1
  175. package/dist/web/hooks/use-tool-info.d.ts +25 -7
  176. package/dist/web/hooks/use-tool-info.js +5 -8
  177. package/dist/web/hooks/use-tool-info.js.map +1 -1
  178. package/dist/web/hooks/use-tool-info.test-d.js +11 -29
  179. package/dist/web/hooks/use-tool-info.test-d.js.map +1 -1
  180. package/dist/web/hooks/use-tool-info.test.js +42 -32
  181. package/dist/web/hooks/use-tool-info.test.js.map +1 -1
  182. package/dist/web/hooks/use-user.test.js +81 -35
  183. package/dist/web/hooks/use-user.test.js.map +1 -1
  184. package/dist/web/hooks/use-view-state.test.js +6 -2
  185. package/dist/web/hooks/use-view-state.test.js.map +1 -1
  186. package/package.json +6 -3
  187. package/dist/web/bridges/apps-sdk/adaptor.d.ts +0 -28
  188. package/dist/web/bridges/apps-sdk/adaptor.js +0 -113
  189. package/dist/web/bridges/apps-sdk/adaptor.js.map +0 -1
  190. package/dist/web/bridges/mcp-app/adaptor.d.ts +0 -52
  191. package/dist/web/bridges/mcp-app/adaptor.js +0 -280
  192. package/dist/web/bridges/mcp-app/adaptor.js.map +0 -1
@@ -6,9 +6,12 @@ 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 { authToSecuritySchemes, evaluateSecuritySchemes, securitySchemesAllowAnonymous, wwwAuthenticateHeader, } from "./auth/security-schemes.js";
10
+ import { setupOAuth } from "./auth/setup.js";
9
11
  import { createApp } from "./express.js";
10
12
  import { createMiddlewareEntry } from "./metric.js";
11
13
  import { buildMiddlewareChain, getHandlerMaps } from "./middleware.js";
14
+ import { resolveServerOrigin } from "./requestOrigin.js";
12
15
  import { templateHelper } from "./templateHelper.js";
13
16
  const mergeWithUnion = (target, source) => {
14
17
  return mergeWith(target, source, (targetVal, sourceVal) => {
@@ -17,6 +20,15 @@ const mergeWithUnion = (target, source) => {
17
20
  }
18
21
  });
19
22
  };
23
+ /**
24
+ * Drop the query string from a `ui://` view URI, leaving the bare path. The
25
+ * `?v=` cache key is the only query we append, so a plain split is enough and
26
+ * sidesteps `URL` normalization quirks on the non-special `ui:` scheme.
27
+ */
28
+ function stripQuery(uri) {
29
+ const queryIndex = uri.indexOf("?");
30
+ return queryIndex === -1 ? uri : uri.slice(0, queryIndex);
31
+ }
20
32
  /**
21
33
  * Coerce a tool handler's return value into an MCP `content` array. Strings
22
34
  * become a single `TextContent`; a single block is wrapped in an array;
@@ -65,13 +77,31 @@ const McpServerBaseOmitted = McpServerBase;
65
77
  *
66
78
  * @see https://docs.skybridge.tech/api-reference/mcp-server
67
79
  */
80
+ // Side channel populated by `dist/__entry.js` before user code is imported.
81
+ // Set at module scope rather than passed through the constructor because the
82
+ // wrapper has the manifest before the user's `new McpServer(...)` runs, and
83
+ // threading it through every call site (including user templates) is exactly
84
+ // the boilerplate this design is trying to hide.
85
+ let pendingBuildManifest = null;
86
+ /**
87
+ * Prime the build-time Vite manifest before user code constructs its
88
+ * `McpServer`. Called from the generated `dist/__entry.js`; not part of the
89
+ * user-facing API.
90
+ *
91
+ * @internal
92
+ */
93
+ export function __setBuildManifest(manifest) {
94
+ pendingBuildManifest = manifest;
95
+ }
68
96
  export class McpServer extends McpServerBaseOmitted {
69
97
  /**
70
98
  * The underlying Express app. Use this to extend the HTTP server with
71
99
  * custom routes, middleware, or settings — e.g.
72
100
  * `server.express.get("/health", ...)`.
73
101
  *
74
- * `express.json()` is pre-applied. Register your handlers before `run()`;
102
+ * `express.json()` is pre-applied — tune it via the constructor's third
103
+ * argument, e.g. `new McpServer(info, {}, { json: { limit: "10mb" } })`.
104
+ * Register your handlers before `run()`;
75
105
  * after `run()`, dev-mode middleware, the `/mcp` route, and the default
76
106
  * error handler are appended in that order.
77
107
  *
@@ -84,15 +114,43 @@ export class McpServer extends McpServerBaseOmitted {
84
114
  mcpMiddlewareApplied = false;
85
115
  claimedViews = new Map();
86
116
  viewMetaBuilders = new Map();
117
+ /**
118
+ * Maps a view resource's query-less path to its canonical registered URI
119
+ * (the one carrying the `?v=` cache key). Lets `resources/read` resolve the
120
+ * underlying view no matter which version param the consumer sends, since
121
+ * the param is only a cache key, not part of the resource's identity.
122
+ */
123
+ viewUriByPath = new Map();
87
124
  viteManifest = null;
88
125
  serverInfo;
89
126
  serverOptions;
90
- constructor(serverInfo, options) {
127
+ oauthEnabled = false;
128
+ acceptsAnonymous = false;
129
+ securitySchemesByTool = new Map();
130
+ constructor(serverInfo, options, skybridgeOptions) {
91
131
  super(serverInfo, options);
92
132
  this.serverInfo = serverInfo;
93
133
  this.serverOptions = options;
94
134
  this.express = express();
95
- this.express.use(express.json());
135
+ this.express.use(express.json(skybridgeOptions?.json));
136
+ if (skybridgeOptions?.oauth) {
137
+ this.oauthEnabled = true;
138
+ setupOAuth(this.express, skybridgeOptions.oauth, {
139
+ acceptsAnonymous: () => this.acceptsAnonymous,
140
+ securitySchemesForTool: (name) => this.securitySchemesByTool.has(name)
141
+ ? { schemes: this.securitySchemesByTool.get(name) }
142
+ : undefined,
143
+ });
144
+ }
145
+ // Pick up the manifest if `dist/__entry.js` primed it before importing
146
+ // user code. Consume-once: clear after the first construction so a
147
+ // subsequent test that doesn't prime can't inherit stale state.
148
+ // Explicit `setViteManifest` calls still win because they happen after
149
+ // construction.
150
+ if (pendingBuildManifest) {
151
+ this.setViteManifest(pendingBuildManifest);
152
+ pendingBuildManifest = null;
153
+ }
96
154
  }
97
155
  use(pathOrHandler, ...handlers) {
98
156
  // Branching is load-bearing: Express's `app.use` overloads can't be
@@ -165,10 +223,47 @@ export class McpServer extends McpServerBaseOmitted {
165
223
  return result;
166
224
  },
167
225
  };
226
+ // Resolve a view's `resources/read` by its query-less path so the
227
+ // underlying asset is served no matter the `?v=` value (stale cache key,
228
+ // no param, etc.). The version param is a cache-busting hint for external
229
+ // consumers; it must not gate resolution. We rewrite the lookup URI to the
230
+ // canonical registered one, then restore the requested URI on the response
231
+ // so the consumer-facing URI is never rewritten.
232
+ const viewReadResolveEntry = {
233
+ filter: "resources/read",
234
+ handler: async (req, _extra, next) => {
235
+ const requested = req.params.uri;
236
+ if (typeof requested !== "string") {
237
+ return next();
238
+ }
239
+ const path = stripQuery(requested);
240
+ const canonical = this.viewUriByPath.get(path);
241
+ if (!canonical) {
242
+ return next();
243
+ }
244
+ req.params.uri = canonical;
245
+ try {
246
+ const result = (await next());
247
+ for (const content of result.contents ?? []) {
248
+ if (typeof content.uri === "string" &&
249
+ stripQuery(content.uri) === stripQuery(canonical)) {
250
+ content.uri = requested;
251
+ }
252
+ }
253
+ return result;
254
+ }
255
+ finally {
256
+ // Restore the shared request params so middleware outer to us never
257
+ // observes the rewritten lookup URI after next() unwinds.
258
+ req.params.uri = requested;
259
+ }
260
+ },
261
+ };
168
262
  const monitoringEntry = createMiddlewareEntry();
169
263
  const entries = [
170
264
  ...(monitoringEntry ? [monitoringEntry] : []),
171
265
  viewListMetaEntry,
266
+ viewReadResolveEntry,
172
267
  ...this.mcpMiddlewareEntries,
173
268
  ];
174
269
  if (entries.length === 0) {
@@ -224,10 +319,24 @@ export class McpServer extends McpServerBaseOmitted {
224
319
  *
225
320
  * On Cloudflare Workers / workerd, returns an object exposing `fetch` so
226
321
  * the runtime can bridge incoming requests to the Node HTTP server. On
322
+ * Vercel (`VERCEL === "1"`), returns the Express app directly so the
323
+ * serverless function entry can call it as a `(req, res)` handler. On
227
324
  * Node, returns `undefined` once listening.
228
325
  */
229
326
  async run() {
230
327
  this.applyMcpMiddleware();
328
+ if (process.env.VERCEL === "1") {
329
+ // createApp only reads httpServer inside its dev-only branch
330
+ // (viewsDevServer); under VERCEL=1 + NODE_ENV=production it's a
331
+ // bare object passed to satisfy the required parameter.
332
+ const httpServer = http.createServer();
333
+ await createApp({
334
+ mcpServer: this,
335
+ httpServer,
336
+ errorMiddleware: this.customErrorMiddleware,
337
+ });
338
+ return this.express;
339
+ }
231
340
  const httpServer = http.createServer();
232
341
  await createApp({
233
342
  mcpServer: this,
@@ -283,27 +392,7 @@ export class McpServer extends McpServerBaseOmitted {
283
392
  return Array.isArray(val) ? val[0] : val;
284
393
  };
285
394
  const isClaude = header("user-agent") === "Claude-User";
286
- let serverUrl;
287
- const forwardedHost = header("x-forwarded-host");
288
- const origin = header("origin");
289
- const host = header("host");
290
- if (forwardedHost) {
291
- const proto = header("x-forwarded-proto") || "https";
292
- serverUrl = `${proto}://${forwardedHost}`;
293
- }
294
- else if (origin) {
295
- serverUrl = origin;
296
- }
297
- else if (host) {
298
- const proto = ["127.0.0.1:", "localhost:"].some((p) => host.startsWith(p))
299
- ? "http"
300
- : "https";
301
- serverUrl = `${proto}://${host}`;
302
- }
303
- else {
304
- const devPort = process.env.__PORT || "3000";
305
- serverUrl = `http://localhost:${devPort}`;
306
- }
395
+ const serverUrl = resolveServerOrigin(header);
307
396
  const connectDomains = [serverUrl];
308
397
  if (!isProduction) {
309
398
  const wsUrl = new URL(serverUrl);
@@ -327,111 +416,74 @@ export class McpServer extends McpServerBaseOmitted {
327
416
  return { serverUrl, connectDomains, contentMetaOverrides };
328
417
  }
329
418
  registerViewResources(toolName, view, toolMeta) {
330
- const hosts = view.hosts ?? ["apps-sdk", "mcp-app"];
331
419
  // Append a content-derived version param so hosts (e.g. ChatGPT) bust
332
420
  // their cache when the bundle changes, but keep the URI stable across
333
421
  // `tools/list` calls when the bundle hasn't changed.
334
422
  const versionParam = this.computeViewVersionParam(view.component);
335
- if (hosts.includes("apps-sdk")) {
336
- const viewResource = {
337
- hostType: "apps-sdk",
338
- uri: `ui://views/apps-sdk/${view.component}.html${versionParam}`,
339
- mimeType: "text/html+skybridge",
340
- buildContentMeta: ({ resourceDomains, connectDomains, domain }, overrides) => {
341
- const defaults = {
342
- "openai/widgetCSP": {
343
- resource_domains: resourceDomains,
344
- connect_domains: connectDomains,
345
- },
346
- "openai/widgetDomain": domain,
347
- "openai/widgetDescription": view.description,
348
- };
349
- const fromView = {
350
- "openai/widgetCSP": {
351
- resource_domains: view.csp?.resourceDomains,
352
- connect_domains: view.csp?.connectDomains,
353
- frame_domains: view.csp?.frameDomains,
354
- redirect_domains: view.csp?.redirectDomains,
355
- },
356
- "openai/widgetDomain": view.domain,
357
- "openai/widgetPrefersBorder": view.prefersBorder,
358
- };
359
- const base = mergeWithUnion(mergeWithUnion(defaults, fromView), {
360
- "openai/widgetDomain": overrides.domain,
361
- });
362
- if (view._meta) {
363
- return { ...base, ...view._meta };
364
- }
365
- return base;
366
- },
367
- };
368
- this.registerViewResource({
369
- name: toolName,
370
- viewResource,
371
- view,
372
- });
373
- toolMeta["openai/outputTemplate"] = viewResource.uri;
374
- }
375
- if (hosts.includes("mcp-app")) {
376
- const viewResource = {
377
- hostType: "mcp-app",
378
- uri: `ui://views/ext-apps/${view.component}.html${versionParam}`,
379
- mimeType: "text/html;profile=mcp-app",
380
- buildContentMeta: ({ resourceDomains, connectDomains, domain, baseUriDomains }, overrides) => {
381
- const defaults = {
382
- ui: {
383
- csp: {
384
- resourceDomains,
385
- connectDomains,
386
- baseUriDomains,
387
- },
388
- domain,
423
+ const viewResource = {
424
+ hostType: "mcp-app",
425
+ uri: `ui://views/ext-apps/${view.component}.html${versionParam}`,
426
+ mimeType: "text/html;profile=mcp-app",
427
+ buildContentMeta: ({ resourceDomains, connectDomains, domain, baseUriDomains }, overrides) => {
428
+ const defaults = {
429
+ ui: {
430
+ csp: {
431
+ resourceDomains,
432
+ connectDomains,
433
+ baseUriDomains,
389
434
  },
390
- };
391
- const fromView = {
392
- ui: {
393
- ...(view.description && { description: view.description }),
394
- ...(view.prefersBorder !== undefined && {
395
- prefersBorder: view.prefersBorder,
435
+ domain,
436
+ },
437
+ };
438
+ const fromView = {
439
+ ui: {
440
+ ...(view.description && { description: view.description }),
441
+ ...(view.prefersBorder !== undefined && {
442
+ prefersBorder: view.prefersBorder,
443
+ }),
444
+ ...(view.domain && { domain: view.domain }),
445
+ csp: {
446
+ ...(view.csp?.resourceDomains && {
447
+ resourceDomains: view.csp.resourceDomains,
448
+ }),
449
+ ...(view.csp?.connectDomains && {
450
+ connectDomains: view.csp.connectDomains,
451
+ }),
452
+ ...(view.csp?.frameDomains && {
453
+ frameDomains: view.csp.frameDomains,
454
+ }),
455
+ ...(view.csp?.baseUriDomains && {
456
+ baseUriDomains: view.csp.baseUriDomains,
396
457
  }),
397
- ...(view.domain && { domain: view.domain }),
398
- csp: {
399
- ...(view.csp?.resourceDomains && {
400
- resourceDomains: view.csp.resourceDomains,
401
- }),
402
- ...(view.csp?.connectDomains && {
403
- connectDomains: view.csp.connectDomains,
404
- }),
405
- ...(view.csp?.frameDomains && {
406
- frameDomains: view.csp.frameDomains,
407
- }),
408
- ...(view.csp?.baseUriDomains && {
409
- baseUriDomains: view.csp.baseUriDomains,
410
- }),
411
- ...(view.csp?.redirectDomains && {
412
- redirectDomains: view.csp.redirectDomains,
413
- }),
414
- },
415
458
  },
416
- };
417
- const base = mergeWithUnion(mergeWithUnion(defaults, fromView), {
418
- ui: overrides,
419
- });
420
- if (view._meta) {
421
- return { ...base, ...view._meta };
422
- }
423
- return base;
424
- },
425
- };
426
- this.registerViewResource({
427
- name: toolName,
428
- viewResource,
429
- view,
430
- });
431
- // @ts-expect-error - For backwards compatibility with Claude current implementation of the specs
432
- toolMeta["ui/resourceUri"] = viewResource.uri;
433
- toolMeta.ui = { ...toolMeta.ui, resourceUri: viewResource.uri };
434
- }
459
+ },
460
+ };
461
+ const ui = mergeWithUnion(mergeWithUnion(defaults, fromView), {
462
+ ui: overrides,
463
+ });
464
+ const base = {
465
+ ...ui,
466
+ ...(view.description && {
467
+ "openai/widgetDescription": view.description,
468
+ }),
469
+ ...(view.csp?.redirectDomains && {
470
+ "openai/widgetCSP": { redirect_domains: view.csp.redirectDomains },
471
+ }),
472
+ };
473
+ if (view._meta) {
474
+ return { ...base, ...view._meta };
475
+ }
476
+ return base;
477
+ },
478
+ };
479
+ this.registerViewResource({ name: toolName, viewResource, view });
480
+ // Advertise via the MCP Apps standard pointer only — ChatGPT renders from
481
+ // ui.resourceUri (verified), and not emitting openai/outputTemplate lets us
482
+ // retire the legacy apps-sdk resource later. The legacy apps-sdk URL is still
483
+ // served (see registerViewResource) so already-published apps keep resolving.
484
+ // @ts-expect-error - For backwards compatibility with Claude current implementation of the specs
485
+ toolMeta["ui/resourceUri"] = viewResource.uri;
486
+ toolMeta.ui = { ...toolMeta.ui, resourceUri: viewResource.uri };
435
487
  }
436
488
  registerViewResource({ name, viewResource, view, }) {
437
489
  const { hostType, uri: viewUri, mimeType, buildContentMeta } = viewResource;
@@ -445,6 +497,8 @@ export class McpServer extends McpServerBaseOmitted {
445
497
  }, contentMetaOverrides);
446
498
  };
447
499
  this.viewMetaBuilders.set(viewUri, buildMeta);
500
+ this.viewUriByPath.set(stripQuery(viewUri), viewUri);
501
+ this.serveLegacyAppsSdkUrl(view.component, viewUri);
448
502
  this.registerResource(name, viewUri, { description: view.description }, async (uri, extra) => {
449
503
  const isProduction = process.env.NODE_ENV === "production";
450
504
  const { serverUrl } = this.resolveViewRequestContext(extra);
@@ -467,8 +521,23 @@ export class McpServer extends McpServerBaseOmitted {
467
521
  };
468
522
  });
469
523
  }
470
- wrapHandler(cb, { attachViewUUID }) {
524
+ serveLegacyAppsSdkUrl(component, canonicalUri) {
525
+ this.viewUriByPath.set(`ui://views/apps-sdk/${component}.html`, canonicalUri);
526
+ this.viewUriByPath.set(`ui://widgets/apps-sdk/${component}.html`, canonicalUri);
527
+ this.viewUriByPath.set(`ui://widgets/ext-apps/${component}.html`, canonicalUri);
528
+ }
529
+ wrapHandler(cb, { attachViewUUID, securitySchemes, }) {
471
530
  return async (args, extra) => {
531
+ if (this.oauthEnabled) {
532
+ const failure = evaluateSecuritySchemes(securitySchemes, extra.authInfo);
533
+ if (failure) {
534
+ return {
535
+ isError: true,
536
+ content: [{ type: "text", text: failure.description }],
537
+ _meta: { "mcp/www_authenticate": wwwAuthenticateHeader(failure) },
538
+ };
539
+ }
540
+ }
472
541
  const result = await cb(args, extra);
473
542
  return {
474
543
  ...result,
@@ -539,8 +608,20 @@ export class McpServer extends McpServerBaseOmitted {
539
608
  }
540
609
  const config = args[0];
541
610
  const cb = args[1];
542
- const { name, view, securitySchemes, _meta: userToolMeta, ...toolFields } = config;
611
+ const { name, view, auth, securitySchemes: rawSecuritySchemes, _meta: userToolMeta, ...toolFields } = config;
612
+ if (auth !== undefined) {
613
+ if (rawSecuritySchemes) {
614
+ throw new Error(`Tool "${name}" sets both \`auth\` and \`securitySchemes\`; use one.`);
615
+ }
616
+ if (!this.oauthEnabled) {
617
+ throw new Error(`Tool "${name}" sets \`auth\` but the server has no \`oauth\` provider configured.`);
618
+ }
619
+ }
620
+ const securitySchemes = auth
621
+ ? authToSecuritySchemes(auth)
622
+ : rawSecuritySchemes;
543
623
  const toolMeta = { ...userToolMeta };
624
+ this.securitySchemesByTool.set(name, securitySchemes);
544
625
  if (securitySchemes) {
545
626
  // SEP-1488 puts `securitySchemes` at the top level of the tool
546
627
  // descriptor, but the SDK's `registerTool` drops unknown top-level
@@ -548,12 +629,18 @@ export class McpServer extends McpServerBaseOmitted {
548
629
  // `tools/list`. Use the `_meta` back-compat mirror documented in the
549
630
  // Apps SDK reference until SEP-1488 lands in the spec.
550
631
  toolMeta.securitySchemes = securitySchemes;
632
+ if (securitySchemesAllowAnonymous(securitySchemes)) {
633
+ this.acceptsAnonymous = true;
634
+ }
551
635
  }
552
636
  if (view) {
553
637
  this.enforceOneToolPerView(view.component, name);
554
638
  this.registerViewResources(name, view, toolMeta);
555
639
  }
556
- const wrappedCb = this.wrapHandler(cb, { attachViewUUID: Boolean(view) });
640
+ const wrappedCb = this.wrapHandler(cb, {
641
+ attachViewUUID: Boolean(view),
642
+ securitySchemes,
643
+ });
557
644
  baseFn.call(this, name, { ...toolFields, _meta: toolMeta }, wrappedCb);
558
645
  return this;
559
646
  }