skybridge 0.0.0-dev.89f68f1 → 0.0.0-dev.89f9a0f

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 (256) hide show
  1. package/README.md +2 -0
  2. package/dist/cli/build-helpers.d.ts +6 -2
  3. package/dist/cli/build-helpers.js +37 -12
  4. package/dist/cli/build-helpers.js.map +1 -1
  5. package/dist/cli/build-helpers.test.js +71 -3
  6. package/dist/cli/build-helpers.test.js.map +1 -1
  7. package/dist/cli/build-steps.d.ts +2 -0
  8. package/dist/cli/build-steps.js +73 -0
  9. package/dist/cli/build-steps.js.map +1 -0
  10. package/dist/cli/build-steps.test.d.ts +1 -0
  11. package/dist/cli/build-steps.test.js +52 -0
  12. package/dist/cli/build-steps.test.js.map +1 -0
  13. package/dist/cli/header.d.ts +1 -1
  14. package/dist/cli/resolve-skybridge-config.d.ts +5 -0
  15. package/dist/cli/{resolve-views-dir.js → resolve-skybridge-config.js} +4 -3
  16. package/dist/cli/resolve-skybridge-config.js.map +1 -0
  17. package/dist/cli/run-plain.d.ts +18 -0
  18. package/dist/cli/run-plain.js +89 -0
  19. package/dist/cli/run-plain.js.map +1 -0
  20. package/dist/cli/use-nodemon.d.ts +14 -0
  21. package/dist/cli/use-nodemon.js +71 -60
  22. package/dist/cli/use-nodemon.js.map +1 -1
  23. package/dist/cli/use-typescript-check.d.ts +8 -2
  24. package/dist/cli/use-typescript-check.js +70 -67
  25. package/dist/cli/use-typescript-check.js.map +1 -1
  26. package/dist/commands/build.d.ts +0 -2
  27. package/dist/commands/build.js +2 -61
  28. package/dist/commands/build.js.map +1 -1
  29. package/dist/commands/dev.d.ts +1 -0
  30. package/dist/commands/dev.js +34 -2
  31. package/dist/commands/dev.js.map +1 -1
  32. package/dist/context-warnings.d.ts +5 -0
  33. package/dist/context-warnings.js +31 -0
  34. package/dist/context-warnings.js.map +1 -0
  35. package/dist/context-warnings.test.d.ts +1 -0
  36. package/dist/context-warnings.test.js +28 -0
  37. package/dist/context-warnings.test.js.map +1 -0
  38. package/dist/server/auth/discovery.d.ts +32 -0
  39. package/dist/server/auth/discovery.js +56 -0
  40. package/dist/server/auth/discovery.js.map +1 -0
  41. package/dist/server/auth/discovery.test.d.ts +1 -0
  42. package/dist/server/auth/discovery.test.js +93 -0
  43. package/dist/server/auth/discovery.test.js.map +1 -0
  44. package/dist/server/auth/index.d.ts +44 -0
  45. package/dist/server/auth/index.js +2 -0
  46. package/dist/server/auth/index.js.map +1 -0
  47. package/dist/server/auth/providers/auth0.d.ts +40 -0
  48. package/dist/server/auth/providers/auth0.js +31 -0
  49. package/dist/server/auth/providers/auth0.js.map +1 -0
  50. package/dist/server/auth/providers/auth0.test.d.ts +1 -0
  51. package/dist/server/auth/providers/auth0.test.js +50 -0
  52. package/dist/server/auth/providers/auth0.test.js.map +1 -0
  53. package/dist/server/auth/providers/authplane.d.ts +49 -0
  54. package/dist/server/auth/providers/authplane.js +57 -0
  55. package/dist/server/auth/providers/authplane.js.map +1 -0
  56. package/dist/server/auth/providers/authplane.test.d.ts +1 -0
  57. package/dist/server/auth/providers/authplane.test.js +140 -0
  58. package/dist/server/auth/providers/authplane.test.js.map +1 -0
  59. package/dist/server/auth/providers/clerk.d.ts +51 -0
  60. package/dist/server/auth/providers/clerk.js +19 -0
  61. package/dist/server/auth/providers/clerk.js.map +1 -0
  62. package/dist/server/auth/providers/clerk.test.d.ts +1 -0
  63. package/dist/server/auth/providers/clerk.test.js +30 -0
  64. package/dist/server/auth/providers/clerk.test.js.map +1 -0
  65. package/dist/server/auth/providers/custom.d.ts +40 -0
  66. package/dist/server/auth/providers/custom.js +56 -0
  67. package/dist/server/auth/providers/custom.js.map +1 -0
  68. package/dist/server/auth/providers/custom.test.d.ts +1 -0
  69. package/dist/server/auth/providers/custom.test.js +158 -0
  70. package/dist/server/auth/providers/custom.test.js.map +1 -0
  71. package/dist/server/auth/providers/descope.d.ts +44 -0
  72. package/dist/server/auth/providers/descope.js +44 -0
  73. package/dist/server/auth/providers/descope.js.map +1 -0
  74. package/dist/server/auth/providers/descope.test.d.ts +1 -0
  75. package/dist/server/auth/providers/descope.test.js +62 -0
  76. package/dist/server/auth/providers/descope.test.js.map +1 -0
  77. package/dist/server/auth/providers/shared.d.ts +2 -0
  78. package/dist/server/auth/providers/shared.js +6 -0
  79. package/dist/server/auth/providers/shared.js.map +1 -0
  80. package/dist/server/auth/providers/shared.test.d.ts +1 -0
  81. package/dist/server/auth/providers/shared.test.js +10 -0
  82. package/dist/server/auth/providers/shared.test.js.map +1 -0
  83. package/dist/server/auth/providers/stytch.d.ts +25 -0
  84. package/dist/server/auth/providers/stytch.js +16 -0
  85. package/dist/server/auth/providers/stytch.js.map +1 -0
  86. package/dist/server/auth/providers/verify-spy.d.ts +12 -0
  87. package/dist/server/auth/providers/verify-spy.js +21 -0
  88. package/dist/server/auth/providers/verify-spy.js.map +1 -0
  89. package/dist/server/auth/providers/workos.d.ts +33 -0
  90. package/dist/server/auth/providers/workos.js +15 -0
  91. package/dist/server/auth/providers/workos.js.map +1 -0
  92. package/dist/server/auth/security-schemes.d.ts +23 -0
  93. package/dist/server/auth/security-schemes.js +64 -0
  94. package/dist/server/auth/security-schemes.js.map +1 -0
  95. package/dist/server/auth/security-schemes.test.d.ts +1 -0
  96. package/dist/server/auth/security-schemes.test.js +99 -0
  97. package/dist/server/auth/security-schemes.test.js.map +1 -0
  98. package/dist/server/auth/setup.d.ts +6 -0
  99. package/dist/server/auth/setup.js +92 -0
  100. package/dist/server/auth/setup.js.map +1 -0
  101. package/dist/server/auth/setup.test.d.ts +1 -0
  102. package/dist/server/auth/setup.test.js +508 -0
  103. package/dist/server/auth/setup.test.js.map +1 -0
  104. package/dist/server/auth/verify.d.ts +46 -0
  105. package/dist/server/auth/verify.js +62 -0
  106. package/dist/server/auth/verify.js.map +1 -0
  107. package/dist/server/auth/verify.test.d.ts +1 -0
  108. package/dist/server/auth/verify.test.js +149 -0
  109. package/dist/server/auth/verify.test.js.map +1 -0
  110. package/dist/server/auth-extra.test-d.d.ts +1 -0
  111. package/dist/server/auth-extra.test-d.js +82 -0
  112. package/dist/server/auth-extra.test-d.js.map +1 -0
  113. package/dist/server/auth.d.ts +39 -1
  114. package/dist/server/auth.js.map +1 -1
  115. package/dist/server/host.d.ts +2 -0
  116. package/dist/server/host.js +11 -0
  117. package/dist/server/host.js.map +1 -0
  118. package/dist/server/host.test.d.ts +1 -0
  119. package/dist/server/host.test.js +13 -0
  120. package/dist/server/host.test.js.map +1 -0
  121. package/dist/server/index.d.ts +13 -2
  122. package/dist/server/index.js +11 -1
  123. package/dist/server/index.js.map +1 -1
  124. package/dist/server/middleware.d.ts +31 -6
  125. package/dist/server/middleware.js +33 -0
  126. package/dist/server/middleware.js.map +1 -1
  127. package/dist/server/middleware.test.js +37 -1
  128. package/dist/server/middleware.test.js.map +1 -1
  129. package/dist/server/requestOrigin.d.ts +7 -0
  130. package/dist/server/requestOrigin.js +25 -0
  131. package/dist/server/requestOrigin.js.map +1 -0
  132. package/dist/server/server.d.ts +89 -24
  133. package/dist/server/server.js +226 -140
  134. package/dist/server/server.js.map +1 -1
  135. package/dist/server/skills-integration.test.d.ts +1 -0
  136. package/dist/server/skills-integration.test.js +79 -0
  137. package/dist/server/skills-integration.test.js.map +1 -0
  138. package/dist/server/skills.d.ts +29 -0
  139. package/dist/server/skills.js +195 -0
  140. package/dist/server/skills.js.map +1 -0
  141. package/dist/server/skills.test.d.ts +1 -0
  142. package/dist/server/skills.test.js +247 -0
  143. package/dist/server/skills.test.js.map +1 -0
  144. package/dist/server/view-name.test-d.d.ts +1 -0
  145. package/dist/server/view-name.test-d.js +8 -0
  146. package/dist/server/view-name.test-d.js.map +1 -0
  147. package/dist/server/view-resource-resolution.test.js +93 -10
  148. package/dist/server/view-resource-resolution.test.js.map +1 -1
  149. package/dist/server/viewsDevServer.js +4 -1
  150. package/dist/server/viewsDevServer.js.map +1 -1
  151. package/dist/test/utils.d.ts +3 -3
  152. package/dist/test/view.test.js +86 -102
  153. package/dist/test/view.test.js.map +1 -1
  154. package/dist/web/bridges/adaptor.d.ts +51 -0
  155. package/dist/web/bridges/adaptor.js +327 -0
  156. package/dist/web/bridges/adaptor.js.map +1 -0
  157. package/dist/web/bridges/adaptor.test.d.ts +1 -0
  158. package/dist/web/bridges/adaptor.test.js +225 -0
  159. package/dist/web/bridges/adaptor.test.js.map +1 -0
  160. package/dist/web/bridges/apps-sdk/bridge.d.ts +6 -2
  161. package/dist/web/bridges/apps-sdk/bridge.js +15 -4
  162. package/dist/web/bridges/apps-sdk/bridge.js.map +1 -1
  163. package/dist/web/bridges/apps-sdk/index.d.ts +0 -1
  164. package/dist/web/bridges/apps-sdk/index.js +0 -1
  165. package/dist/web/bridges/apps-sdk/index.js.map +1 -1
  166. package/dist/web/bridges/get-adaptor.d.ts +4 -4
  167. package/dist/web/bridges/get-adaptor.js +6 -11
  168. package/dist/web/bridges/get-adaptor.js.map +1 -1
  169. package/dist/web/bridges/get-adaptor.test.d.ts +1 -0
  170. package/dist/web/bridges/get-adaptor.test.js +32 -0
  171. package/dist/web/bridges/get-adaptor.test.js.map +1 -0
  172. package/dist/web/bridges/mcp-app/bridge.d.ts +13 -2
  173. package/dist/web/bridges/mcp-app/bridge.js +51 -7
  174. package/dist/web/bridges/mcp-app/bridge.js.map +1 -1
  175. package/dist/web/bridges/mcp-app/bridge.test.d.ts +1 -0
  176. package/dist/web/bridges/mcp-app/bridge.test.js +17 -0
  177. package/dist/web/bridges/mcp-app/bridge.test.js.map +1 -0
  178. package/dist/web/bridges/mcp-app/index.d.ts +0 -1
  179. package/dist/web/bridges/mcp-app/index.js +0 -1
  180. package/dist/web/bridges/mcp-app/index.js.map +1 -1
  181. package/dist/web/bridges/mcp-app/types.d.ts +2 -0
  182. package/dist/web/bridges/mcp-app/types.js.map +1 -1
  183. package/dist/web/bridges/mcp-app/view-tools.test.js +5 -6
  184. package/dist/web/bridges/mcp-app/view-tools.test.js.map +1 -1
  185. package/dist/web/bridges/types.d.ts +10 -0
  186. package/dist/web/bridges/types.js +14 -1
  187. package/dist/web/bridges/types.js.map +1 -1
  188. package/dist/web/bridges/types.test.d.ts +1 -0
  189. package/dist/web/bridges/types.test.js +19 -0
  190. package/dist/web/bridges/types.test.js.map +1 -0
  191. package/dist/web/components/modal-provider.d.ts +1 -1
  192. package/dist/web/components/modal-provider.js +4 -3
  193. package/dist/web/components/modal-provider.js.map +1 -1
  194. package/dist/web/create-store.test.js +8 -5
  195. package/dist/web/create-store.test.js.map +1 -1
  196. package/dist/web/data-llm.d.ts +1 -1
  197. package/dist/web/data-llm.test.js +18 -5
  198. package/dist/web/data-llm.test.js.map +1 -1
  199. package/dist/web/hooks/index.d.ts +1 -0
  200. package/dist/web/hooks/index.js +1 -0
  201. package/dist/web/hooks/index.js.map +1 -1
  202. package/dist/web/hooks/test/utils.d.ts +2 -1
  203. package/dist/web/hooks/test/utils.js +1 -1
  204. package/dist/web/hooks/test/utils.js.map +1 -1
  205. package/dist/web/hooks/use-call-tool.test.js +37 -23
  206. package/dist/web/hooks/use-call-tool.test.js.map +1 -1
  207. package/dist/web/hooks/use-display-mode.test.js +56 -20
  208. package/dist/web/hooks/use-display-mode.test.js.map +1 -1
  209. package/dist/web/hooks/use-download.test.js +11 -3
  210. package/dist/web/hooks/use-download.test.js.map +1 -1
  211. package/dist/web/hooks/use-files.test.js +10 -2
  212. package/dist/web/hooks/use-files.test.js.map +1 -1
  213. package/dist/web/hooks/use-host-info.d.ts +22 -0
  214. package/dist/web/hooks/use-host-info.js +30 -0
  215. package/dist/web/hooks/use-host-info.js.map +1 -0
  216. package/dist/web/hooks/use-host-info.test.d.ts +1 -0
  217. package/dist/web/hooks/use-host-info.test.js +59 -0
  218. package/dist/web/hooks/use-host-info.test.js.map +1 -0
  219. package/dist/web/hooks/use-layout.test.js +59 -26
  220. package/dist/web/hooks/use-layout.test.js.map +1 -1
  221. package/dist/web/hooks/use-open-external.test.js +13 -3
  222. package/dist/web/hooks/use-open-external.test.js.map +1 -1
  223. package/dist/web/hooks/use-request-close.test.js +35 -40
  224. package/dist/web/hooks/use-request-close.test.js.map +1 -1
  225. package/dist/web/hooks/use-request-modal.test.js +10 -0
  226. package/dist/web/hooks/use-request-modal.test.js.map +1 -1
  227. package/dist/web/hooks/use-request-size.test.js +35 -53
  228. package/dist/web/hooks/use-request-size.test.js.map +1 -1
  229. package/dist/web/hooks/use-set-open-in-app-url.test.js +28 -8
  230. package/dist/web/hooks/use-set-open-in-app-url.test.js.map +1 -1
  231. package/dist/web/hooks/use-tool-info.test.js +37 -27
  232. package/dist/web/hooks/use-tool-info.test.js.map +1 -1
  233. package/dist/web/hooks/use-user.test.js +81 -35
  234. package/dist/web/hooks/use-user.test.js.map +1 -1
  235. package/dist/web/hooks/use-view-state.js +19 -9
  236. package/dist/web/hooks/use-view-state.js.map +1 -1
  237. package/dist/web/hooks/use-view-state.test.js +37 -2
  238. package/dist/web/hooks/use-view-state.test.js.map +1 -1
  239. package/dist/web/plugin/plugin-build.test.d.ts +1 -0
  240. package/dist/web/plugin/plugin-build.test.js +54 -0
  241. package/dist/web/plugin/plugin-build.test.js.map +1 -0
  242. package/dist/web/plugin/plugin.d.ts +6 -0
  243. package/dist/web/plugin/plugin.js +18 -7
  244. package/dist/web/plugin/plugin.js.map +1 -1
  245. package/dist/web/plugin/plugin.test.d.ts +1 -0
  246. package/dist/web/plugin/plugin.test.js +61 -0
  247. package/dist/web/plugin/plugin.test.js.map +1 -0
  248. package/package.json +21 -19
  249. package/dist/cli/resolve-views-dir.d.ts +0 -1
  250. package/dist/cli/resolve-views-dir.js.map +0 -1
  251. package/dist/web/bridges/apps-sdk/adaptor.d.ts +0 -29
  252. package/dist/web/bridges/apps-sdk/adaptor.js +0 -117
  253. package/dist/web/bridges/apps-sdk/adaptor.js.map +0 -1
  254. package/dist/web/bridges/mcp-app/adaptor.d.ts +0 -53
  255. package/dist/web/bridges/mcp-app/adaptor.js +0 -283
  256. package/dist/web/bridges/mcp-app/adaptor.js.map +0 -1
@@ -6,9 +6,15 @@ 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 { warnOnLargeToolOutput } from "../context-warnings.js";
10
+ import { authToSecuritySchemes, evaluateSecuritySchemes, inBandChallengeResult, } from "./auth/security-schemes.js";
11
+ import { setupOAuth } from "./auth/setup.js";
9
12
  import { createApp } from "./express.js";
13
+ import { hostFromUserAgent } from "./host.js";
10
14
  import { createMiddlewareEntry } from "./metric.js";
11
- import { buildMiddlewareChain, getHandlerMaps } from "./middleware.js";
15
+ import { buildMiddlewareChain, captureToolError, getHandlerMaps, } from "./middleware.js";
16
+ import { resolveServerOrigin } from "./requestOrigin.js";
17
+ import { discoverSkills, registerSkills, SKILLS_EXTENSION_KEY, } from "./skills.js";
12
18
  import { templateHelper } from "./templateHelper.js";
13
19
  const mergeWithUnion = (target, source) => {
14
20
  return mergeWith(target, source, (targetVal, sourceVal) => {
@@ -17,6 +23,20 @@ const mergeWithUnion = (target, source) => {
17
23
  }
18
24
  });
19
25
  };
26
+ const SKILLS_DIR = "src/skills";
27
+ /**
28
+ * Normalize an `x-forwarded-prefix` value into a leading-slash, no-trailing-slash
29
+ * path. Takes the first hop of a comma-separated proxy chain.
30
+ * "/v1/", "v1", "/v1, /internal" → "/v1"; "", "/", undefined → "".
31
+ */
32
+ function normalizeForwardedPrefix(raw) {
33
+ const firstHop = raw?.split(",")[0]?.trim() ?? "";
34
+ const trimmed = firstHop.replace(/\/+$/, "");
35
+ if (trimmed === "") {
36
+ return "";
37
+ }
38
+ return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
39
+ }
20
40
  /**
21
41
  * Drop the query string from a `ui://` view URI, leaving the bare path. The
22
42
  * `?v=` cache key is the only query we append, so a plain split is enough and
@@ -90,6 +110,47 @@ let pendingBuildManifest = null;
90
110
  export function __setBuildManifest(manifest) {
91
111
  pendingBuildManifest = manifest;
92
112
  }
113
+ let pendingSkillsManifest = null;
114
+ export function __setSkillsManifest(manifest) {
115
+ pendingSkillsManifest = manifest;
116
+ }
117
+ // Pure and `this`-free so it can run inside the `super(...)` call, before `this`
118
+ // exists — the capability must be present for the `initialize` response.
119
+ function withSkillsCapability(options, skybridgeOptions) {
120
+ if (!skybridgeOptions?.skills) {
121
+ return options;
122
+ }
123
+ return {
124
+ ...options,
125
+ capabilities: {
126
+ ...options?.capabilities,
127
+ extensions: {
128
+ ...options?.capabilities?.extensions,
129
+ [SKILLS_EXTENSION_KEY]: { directoryRead: true },
130
+ },
131
+ },
132
+ };
133
+ }
134
+ // Collapses registerTool's two overloads into a single { config, cb } shape.
135
+ // registerTool("greet", { description }, handler)
136
+ // -> { config: { name: "greet", description }, cb: handler }
137
+ // registerTool({ name: "greet", description }, handler)
138
+ // -> { config: { name: "greet", description }, cb: handler }
139
+ function normalizeRegisterToolArgs(args) {
140
+ if (typeof args[0] === "string") {
141
+ return {
142
+ config: {
143
+ name: args[0],
144
+ ...args[1],
145
+ },
146
+ cb: args[2],
147
+ };
148
+ }
149
+ return {
150
+ config: args[0],
151
+ cb: args[1],
152
+ };
153
+ }
93
154
  export class McpServer extends McpServerBaseOmitted {
94
155
  /**
95
156
  * The underlying Express app. Use this to extend the HTTP server with
@@ -121,12 +182,20 @@ export class McpServer extends McpServerBaseOmitted {
121
182
  viteManifest = null;
122
183
  serverInfo;
123
184
  serverOptions;
185
+ oauthEnabled = false;
186
+ resolveResourceMetadataUrl;
187
+ securitySchemesByTool = new Map();
124
188
  constructor(serverInfo, options, skybridgeOptions) {
125
- super(serverInfo, options);
189
+ const mergedOptions = withSkillsCapability(options, skybridgeOptions);
190
+ super(serverInfo, mergedOptions);
126
191
  this.serverInfo = serverInfo;
127
- this.serverOptions = options;
192
+ this.serverOptions = mergedOptions;
128
193
  this.express = express();
129
194
  this.express.use(express.json(skybridgeOptions?.json));
195
+ if (skybridgeOptions?.oauth) {
196
+ this.oauthEnabled = true;
197
+ this.resolveResourceMetadataUrl = setupOAuth(this.express, skybridgeOptions.oauth, this.securitySchemesByTool);
198
+ }
130
199
  // Pick up the manifest if `dist/__entry.js` primed it before importing
131
200
  // user code. Consume-once: clear after the first construction so a
132
201
  // subsequent test that doesn't prime can't inherit stale state.
@@ -136,6 +205,19 @@ export class McpServer extends McpServerBaseOmitted {
136
205
  this.setViteManifest(pendingBuildManifest);
137
206
  pendingBuildManifest = null;
138
207
  }
208
+ this.setupSkills(Boolean(skybridgeOptions?.skills));
209
+ }
210
+ setupSkills(enabled) {
211
+ const manifest = pendingSkillsManifest;
212
+ pendingSkillsManifest = null;
213
+ if (!enabled) {
214
+ return;
215
+ }
216
+ const skills = manifest ?? discoverSkills(SKILLS_DIR);
217
+ if (skills.length === 0) {
218
+ console.warn(`skybridge: the "skills" option is enabled but no skills were found in "${SKILLS_DIR}". Add a <name>/SKILL.md there, or remove the option.`);
219
+ }
220
+ registerSkills(this, skills);
139
221
  }
140
222
  use(pathOrHandler, ...handlers) {
141
223
  // Branching is load-bearing: Express's `app.use` overloads can't be
@@ -231,7 +313,7 @@ export class McpServer extends McpServerBaseOmitted {
231
313
  const result = (await next());
232
314
  for (const content of result.contents ?? []) {
233
315
  if (typeof content.uri === "string" &&
234
- stripQuery(content.uri) === path) {
316
+ stripQuery(content.uri) === stripQuery(canonical)) {
235
317
  content.uri = requested;
236
318
  }
237
319
  }
@@ -244,16 +326,33 @@ export class McpServer extends McpServerBaseOmitted {
244
326
  }
245
327
  },
246
328
  };
329
+ // ChatGPT reads `securitySchemes` at the tool descriptor top level (SEP-1488,
330
+ // still Draft), but the SDK's registerTool strips unknown top-level fields, so
331
+ // it's stashed in `_meta` at registration. This restores it to the top level
332
+ // on tools/list output. Remove once SEP-1488 lands and the SDK preserves it.
333
+ // { name: "checkout", _meta: { securitySchemes: [{ type: "oauth2" }] } }
334
+ // -> { name: "checkout", _meta: {…}, securitySchemes: [{ type: "oauth2" }] }
335
+ const toolsListSecuritySchemesEntry = {
336
+ filter: "tools/list",
337
+ handler: async (_req, _extra, next) => {
338
+ const result = (await next());
339
+ for (const tool of result.tools) {
340
+ const schemes = tool._meta?.securitySchemes;
341
+ if (schemes && !("securitySchemes" in tool)) {
342
+ tool.securitySchemes = schemes;
343
+ }
344
+ }
345
+ return result;
346
+ },
347
+ };
247
348
  const monitoringEntry = createMiddlewareEntry();
248
349
  const entries = [
249
350
  ...(monitoringEntry ? [monitoringEntry] : []),
250
351
  viewListMetaEntry,
251
352
  viewReadResolveEntry,
353
+ toolsListSecuritySchemesEntry,
252
354
  ...this.mcpMiddlewareEntries,
253
355
  ];
254
- if (entries.length === 0) {
255
- return;
256
- }
257
356
  const { requestHandlers, notificationHandlers } = getHandlerMaps(this.server);
258
357
  const instrumentMap = (map, isNotification) => {
259
358
  for (const [method, handler] of map) {
@@ -376,28 +475,13 @@ export class McpServer extends McpServerBaseOmitted {
376
475
  const val = headers[key];
377
476
  return Array.isArray(val) ? val[0] : val;
378
477
  };
379
- const isClaude = header("user-agent") === "Claude-User";
380
- let serverUrl;
381
- const forwardedHost = header("x-forwarded-host");
382
- const origin = header("origin");
383
- const host = header("host");
384
- if (forwardedHost) {
385
- const proto = header("x-forwarded-proto") || "https";
386
- serverUrl = `${proto}://${forwardedHost}`;
387
- }
388
- else if (origin) {
389
- serverUrl = origin;
390
- }
391
- else if (host) {
392
- const proto = ["127.0.0.1:", "localhost:"].some((p) => host.startsWith(p))
393
- ? "http"
394
- : "https";
395
- serverUrl = `${proto}://${host}`;
396
- }
397
- else {
398
- const devPort = process.env.__PORT || "3000";
399
- serverUrl = `http://localhost:${devPort}`;
400
- }
478
+ const isClaude = hostFromUserAgent(header("user-agent")) === "claude";
479
+ const serverUrl = resolveServerOrigin(header);
480
+ // Path prefix the proxy routed this request under (e.g. `foo.com/v1`). Read
481
+ // per-request so one process can serve many hosts/prefixes at once: the
482
+ // origin is recovered from x-forwarded-host, the prefix from
483
+ // x-forwarded-prefix. Empty when served at the origin root.
484
+ const assetsBasePath = normalizeForwardedPrefix(header("x-forwarded-prefix"));
401
485
  const connectDomains = [serverUrl];
402
486
  if (!isProduction) {
403
487
  const wsUrl = new URL(serverUrl);
@@ -418,114 +502,77 @@ export class McpServer extends McpServerBaseOmitted {
418
502
  .slice(0, 32);
419
503
  contentMetaOverrides = { domain: `${hash}.claudemcpcontent.com` };
420
504
  }
421
- return { serverUrl, connectDomains, contentMetaOverrides };
505
+ return { serverUrl, assetsBasePath, connectDomains, contentMetaOverrides };
422
506
  }
423
507
  registerViewResources(toolName, view, toolMeta) {
424
- const hosts = view.hosts ?? ["apps-sdk", "mcp-app"];
425
508
  // Append a content-derived version param so hosts (e.g. ChatGPT) bust
426
509
  // their cache when the bundle changes, but keep the URI stable across
427
510
  // `tools/list` calls when the bundle hasn't changed.
428
511
  const versionParam = this.computeViewVersionParam(view.component);
429
- if (hosts.includes("apps-sdk")) {
430
- const viewResource = {
431
- hostType: "apps-sdk",
432
- uri: `ui://views/apps-sdk/${view.component}.html${versionParam}`,
433
- mimeType: "text/html+skybridge",
434
- buildContentMeta: ({ resourceDomains, connectDomains, domain }, overrides) => {
435
- const defaults = {
436
- "openai/widgetCSP": {
437
- resource_domains: resourceDomains,
438
- connect_domains: connectDomains,
439
- },
440
- "openai/widgetDomain": domain,
441
- "openai/widgetDescription": view.description,
442
- };
443
- const fromView = {
444
- "openai/widgetCSP": {
445
- resource_domains: view.csp?.resourceDomains,
446
- connect_domains: view.csp?.connectDomains,
447
- frame_domains: view.csp?.frameDomains,
448
- redirect_domains: view.csp?.redirectDomains,
449
- },
450
- "openai/widgetDomain": view.domain,
451
- "openai/widgetPrefersBorder": view.prefersBorder,
452
- };
453
- const base = mergeWithUnion(mergeWithUnion(defaults, fromView), {
454
- "openai/widgetDomain": overrides.domain,
455
- });
456
- if (view._meta) {
457
- return { ...base, ...view._meta };
458
- }
459
- return base;
460
- },
461
- };
462
- this.registerViewResource({
463
- name: toolName,
464
- viewResource,
465
- view,
466
- });
467
- toolMeta["openai/outputTemplate"] = viewResource.uri;
468
- }
469
- if (hosts.includes("mcp-app")) {
470
- const viewResource = {
471
- hostType: "mcp-app",
472
- uri: `ui://views/ext-apps/${view.component}.html${versionParam}`,
473
- mimeType: "text/html;profile=mcp-app",
474
- buildContentMeta: ({ resourceDomains, connectDomains, domain, baseUriDomains }, overrides) => {
475
- const defaults = {
476
- ui: {
477
- csp: {
478
- resourceDomains,
479
- connectDomains,
480
- baseUriDomains,
481
- },
482
- domain,
512
+ const viewResource = {
513
+ hostType: "mcp-app",
514
+ uri: `ui://views/ext-apps/${view.component}.html${versionParam}`,
515
+ mimeType: "text/html;profile=mcp-app",
516
+ buildContentMeta: ({ resourceDomains, connectDomains, domain, baseUriDomains }, overrides) => {
517
+ const defaults = {
518
+ ui: {
519
+ csp: {
520
+ resourceDomains,
521
+ connectDomains,
522
+ baseUriDomains,
483
523
  },
484
- };
485
- const fromView = {
486
- ui: {
487
- ...(view.description && { description: view.description }),
488
- ...(view.prefersBorder !== undefined && {
489
- prefersBorder: view.prefersBorder,
524
+ domain,
525
+ },
526
+ };
527
+ const fromView = {
528
+ ui: {
529
+ ...(view.description && { description: view.description }),
530
+ ...(view.prefersBorder !== undefined && {
531
+ prefersBorder: view.prefersBorder,
532
+ }),
533
+ ...(view.domain && { domain: view.domain }),
534
+ csp: {
535
+ ...(view.csp?.resourceDomains && {
536
+ resourceDomains: view.csp.resourceDomains,
537
+ }),
538
+ ...(view.csp?.connectDomains && {
539
+ connectDomains: view.csp.connectDomains,
540
+ }),
541
+ ...(view.csp?.frameDomains && {
542
+ frameDomains: view.csp.frameDomains,
543
+ }),
544
+ ...(view.csp?.baseUriDomains && {
545
+ baseUriDomains: view.csp.baseUriDomains,
490
546
  }),
491
- ...(view.domain && { domain: view.domain }),
492
- csp: {
493
- ...(view.csp?.resourceDomains && {
494
- resourceDomains: view.csp.resourceDomains,
495
- }),
496
- ...(view.csp?.connectDomains && {
497
- connectDomains: view.csp.connectDomains,
498
- }),
499
- ...(view.csp?.frameDomains && {
500
- frameDomains: view.csp.frameDomains,
501
- }),
502
- ...(view.csp?.baseUriDomains && {
503
- baseUriDomains: view.csp.baseUriDomains,
504
- }),
505
- ...(view.csp?.redirectDomains && {
506
- redirectDomains: view.csp.redirectDomains,
507
- }),
508
- },
509
547
  },
510
- };
511
- const base = mergeWithUnion(mergeWithUnion(defaults, fromView), {
512
- ui: overrides,
513
- });
514
- if (view._meta) {
515
- return { ...base, ...view._meta };
516
- }
517
- return base;
518
- },
519
- };
520
- this.registerViewResource({
521
- name: toolName,
522
- viewResource,
523
- view,
524
- });
525
- // @ts-expect-error - For backwards compatibility with Claude current implementation of the specs
526
- toolMeta["ui/resourceUri"] = viewResource.uri;
527
- toolMeta.ui = { ...toolMeta.ui, resourceUri: viewResource.uri };
528
- }
548
+ },
549
+ };
550
+ const ui = mergeWithUnion(mergeWithUnion(defaults, fromView), {
551
+ ui: overrides,
552
+ });
553
+ const base = {
554
+ ...ui,
555
+ ...(view.description && {
556
+ "openai/widgetDescription": view.description,
557
+ }),
558
+ ...(view.csp?.redirectDomains && {
559
+ "openai/widgetCSP": { redirect_domains: view.csp.redirectDomains },
560
+ }),
561
+ };
562
+ if (view._meta) {
563
+ return { ...base, ...view._meta };
564
+ }
565
+ return base;
566
+ },
567
+ };
568
+ this.registerViewResource({ name: toolName, viewResource, view });
569
+ // Advertise via the MCP Apps standard pointer only — ChatGPT renders from
570
+ // ui.resourceUri (verified), and not emitting openai/outputTemplate lets us
571
+ // retire the legacy apps-sdk resource later. The legacy apps-sdk URL is still
572
+ // served (see registerViewResource) so already-published apps keep resolving.
573
+ // @ts-expect-error - For backwards compatibility with Claude current implementation of the specs
574
+ toolMeta["ui/resourceUri"] = viewResource.uri;
575
+ toolMeta.ui = { ...toolMeta.ui, resourceUri: viewResource.uri };
529
576
  }
530
577
  registerViewResource({ name, viewResource, view, }) {
531
578
  const { hostType, uri: viewUri, mimeType, buildContentMeta } = viewResource;
@@ -540,19 +587,24 @@ export class McpServer extends McpServerBaseOmitted {
540
587
  };
541
588
  this.viewMetaBuilders.set(viewUri, buildMeta);
542
589
  this.viewUriByPath.set(stripQuery(viewUri), viewUri);
590
+ this.serveLegacyAppsSdkUrl(view.component, viewUri);
543
591
  this.registerResource(name, viewUri, { description: view.description }, async (uri, extra) => {
544
592
  const isProduction = process.env.NODE_ENV === "production";
545
- const { serverUrl } = this.resolveViewRequestContext(extra);
593
+ const { serverUrl, assetsBasePath } = this.resolveViewRequestContext(extra);
594
+ // The view resolves all assets (template imports + runtime lazy chunks
595
+ // via `window.skybridge.serverUrl`) against this base, so it carries the
596
+ // proxy path prefix. CSP domains in `buildMeta` stay the bare origin.
597
+ const viewBase = `${serverUrl}${assetsBasePath}`;
546
598
  const html = isProduction
547
599
  ? templateHelper.renderProduction({
548
600
  hostType,
549
- serverUrl,
601
+ serverUrl: viewBase,
550
602
  viewFile: this.lookupViewFile(view.component),
551
603
  styleFile: this.lookupDistFile("style.css") ?? "",
552
604
  })
553
605
  : templateHelper.renderDevelopment({
554
606
  hostType,
555
- serverUrl,
607
+ serverUrl: viewBase,
556
608
  viewName: view.component,
557
609
  });
558
610
  return {
@@ -562,9 +614,34 @@ export class McpServer extends McpServerBaseOmitted {
562
614
  };
563
615
  });
564
616
  }
565
- wrapHandler(cb, { attachViewUUID }) {
617
+ serveLegacyAppsSdkUrl(component, canonicalUri) {
618
+ this.viewUriByPath.set(`ui://views/apps-sdk/${component}.html`, canonicalUri);
619
+ this.viewUriByPath.set(`ui://widgets/apps-sdk/${component}.html`, canonicalUri);
620
+ this.viewUriByPath.set(`ui://widgets/ext-apps/${component}.html`, canonicalUri);
621
+ }
622
+ decorateToolHandler(cb, { attachViewUUID, securitySchemes, toolName, }) {
566
623
  return async (args, extra) => {
567
- const result = await cb(args, extra);
624
+ const toolExtra = extra ?? args;
625
+ if (this.oauthEnabled) {
626
+ const failure = evaluateSecuritySchemes(securitySchemes, toolExtra?.authInfo);
627
+ if (failure) {
628
+ const headers = toolExtra?.requestInfo?.headers ?? {};
629
+ const header = (key) => {
630
+ const value = headers[key];
631
+ return Array.isArray(value) ? value[0] : value;
632
+ };
633
+ return inBandChallengeResult(failure, this.resolveResourceMetadataUrl?.(header));
634
+ }
635
+ }
636
+ let result;
637
+ try {
638
+ result = await cb(args, extra);
639
+ }
640
+ catch (error) {
641
+ captureToolError(toolExtra, error);
642
+ throw error;
643
+ }
644
+ warnOnLargeToolOutput(result, toolName);
568
645
  return {
569
646
  ...result,
570
647
  content: normalizeContent(result.content),
@@ -628,14 +705,19 @@ export class McpServer extends McpServerBaseOmitted {
628
705
  }
629
706
  registerTool(...args) {
630
707
  const baseFn = McpServerBase.prototype.registerTool;
631
- if (typeof args[0] === "string") {
632
- baseFn.call(this, args[0], args[1], args[2]);
633
- return this;
708
+ const { config, cb } = normalizeRegisterToolArgs(args);
709
+ const { name, view, auth, securitySchemes: rawSecuritySchemes, _meta: userToolMeta, ...toolFields } = config;
710
+ const authNeedsProvider = auth !== undefined &&
711
+ (!auth.allowsAnonymous || Boolean(auth.scopes?.length));
712
+ if (rawSecuritySchemes === undefined &&
713
+ authNeedsProvider &&
714
+ !this.oauthEnabled) {
715
+ throw new Error(`Tool "${name}" sets \`auth: ${JSON.stringify(auth)}\` but the server has no \`oauth\` provider configured.`);
634
716
  }
635
- const config = args[0];
636
- const cb = args[1];
637
- const { name, view, securitySchemes, _meta: userToolMeta, ...toolFields } = config;
717
+ const securitySchemes = rawSecuritySchemes ??
718
+ (auth && this.oauthEnabled ? authToSecuritySchemes(auth) : undefined);
638
719
  const toolMeta = { ...userToolMeta };
720
+ this.securitySchemesByTool.set(name, securitySchemes);
639
721
  if (securitySchemes) {
640
722
  // SEP-1488 puts `securitySchemes` at the top level of the tool
641
723
  // descriptor, but the SDK's `registerTool` drops unknown top-level
@@ -648,7 +730,11 @@ export class McpServer extends McpServerBaseOmitted {
648
730
  this.enforceOneToolPerView(view.component, name);
649
731
  this.registerViewResources(name, view, toolMeta);
650
732
  }
651
- const wrappedCb = this.wrapHandler(cb, { attachViewUUID: Boolean(view) });
733
+ const wrappedCb = this.decorateToolHandler(cb, {
734
+ attachViewUUID: Boolean(view),
735
+ securitySchemes,
736
+ toolName: name,
737
+ });
652
738
  baseFn.call(this, name, { ...toolFields, _meta: toolMeta }, wrappedCb);
653
739
  return this;
654
740
  }