skybridge 0.0.0-dev.89e35fa → 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 (255) 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 +35 -5
  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/express.test.js +31 -0
  116. package/dist/server/express.test.js.map +1 -1
  117. package/dist/server/host.d.ts +2 -0
  118. package/dist/server/host.js +11 -0
  119. package/dist/server/host.js.map +1 -0
  120. package/dist/server/host.test.d.ts +1 -0
  121. package/dist/server/host.test.js +13 -0
  122. package/dist/server/host.test.js.map +1 -0
  123. package/dist/server/index.d.ts +14 -3
  124. package/dist/server/index.js +11 -1
  125. package/dist/server/index.js.map +1 -1
  126. package/dist/server/middleware.d.ts +31 -6
  127. package/dist/server/middleware.js +33 -0
  128. package/dist/server/middleware.js.map +1 -1
  129. package/dist/server/middleware.test.js +37 -1
  130. package/dist/server/middleware.test.js.map +1 -1
  131. package/dist/server/requestOrigin.d.ts +7 -0
  132. package/dist/server/requestOrigin.js +25 -0
  133. package/dist/server/requestOrigin.js.map +1 -0
  134. package/dist/server/server.d.ts +97 -20
  135. package/dist/server/server.js +237 -53
  136. package/dist/server/server.js.map +1 -1
  137. package/dist/server/skills-integration.test.d.ts +1 -0
  138. package/dist/server/skills-integration.test.js +79 -0
  139. package/dist/server/skills-integration.test.js.map +1 -0
  140. package/dist/server/skills.d.ts +29 -0
  141. package/dist/server/skills.js +195 -0
  142. package/dist/server/skills.js.map +1 -0
  143. package/dist/server/skills.test.d.ts +1 -0
  144. package/dist/server/skills.test.js +247 -0
  145. package/dist/server/skills.test.js.map +1 -0
  146. package/dist/server/view-name.test-d.d.ts +1 -0
  147. package/dist/server/view-name.test-d.js +8 -0
  148. package/dist/server/view-name.test-d.js.map +1 -0
  149. package/dist/server/view-resource-resolution.test.d.ts +6 -0
  150. package/dist/server/view-resource-resolution.test.js +171 -0
  151. package/dist/server/view-resource-resolution.test.js.map +1 -0
  152. package/dist/server/viewsDevServer.js +4 -1
  153. package/dist/server/viewsDevServer.js.map +1 -1
  154. package/dist/test/utils.d.ts +3 -3
  155. package/dist/test/view.test.js +37 -11
  156. package/dist/test/view.test.js.map +1 -1
  157. package/dist/web/bridges/adaptor.d.ts +6 -5
  158. package/dist/web/bridges/adaptor.js +50 -37
  159. package/dist/web/bridges/adaptor.js.map +1 -1
  160. package/dist/web/bridges/adaptor.test.js +32 -12
  161. package/dist/web/bridges/adaptor.test.js.map +1 -1
  162. package/dist/web/bridges/apps-sdk/bridge.d.ts +5 -1
  163. package/dist/web/bridges/apps-sdk/bridge.js +12 -0
  164. package/dist/web/bridges/apps-sdk/bridge.js.map +1 -1
  165. package/dist/web/bridges/get-adaptor.d.ts +0 -2
  166. package/dist/web/bridges/get-adaptor.js +1 -13
  167. package/dist/web/bridges/get-adaptor.js.map +1 -1
  168. package/dist/web/bridges/get-adaptor.test.js +3 -3
  169. package/dist/web/bridges/get-adaptor.test.js.map +1 -1
  170. package/dist/web/bridges/mcp-app/bridge.d.ts +14 -2
  171. package/dist/web/bridges/mcp-app/bridge.js +72 -4
  172. package/dist/web/bridges/mcp-app/bridge.js.map +1 -1
  173. package/dist/web/bridges/mcp-app/bridge.test.js +1 -5
  174. package/dist/web/bridges/mcp-app/bridge.test.js.map +1 -1
  175. package/dist/web/bridges/mcp-app/types.d.ts +2 -0
  176. package/dist/web/bridges/mcp-app/types.js.map +1 -1
  177. package/dist/web/bridges/mcp-app/view-tools.test.d.ts +1 -0
  178. package/dist/web/bridges/mcp-app/view-tools.test.js +143 -0
  179. package/dist/web/bridges/mcp-app/view-tools.test.js.map +1 -0
  180. package/dist/web/bridges/types.d.ts +34 -6
  181. package/dist/web/bridges/types.js.map +1 -1
  182. package/dist/web/components/modal-provider.d.ts +1 -1
  183. package/dist/web/create-store.test.js +4 -3
  184. package/dist/web/create-store.test.js.map +1 -1
  185. package/dist/web/data-llm.d.ts +1 -1
  186. package/dist/web/data-llm.test.js +13 -3
  187. package/dist/web/data-llm.test.js.map +1 -1
  188. package/dist/web/generate-helpers.test-d.js +4 -2
  189. package/dist/web/generate-helpers.test-d.js.map +1 -1
  190. package/dist/web/hooks/index.d.ts +2 -0
  191. package/dist/web/hooks/index.js +2 -0
  192. package/dist/web/hooks/index.js.map +1 -1
  193. package/dist/web/hooks/test/utils.d.ts +2 -1
  194. package/dist/web/hooks/test/utils.js +1 -1
  195. package/dist/web/hooks/test/utils.js.map +1 -1
  196. package/dist/web/hooks/use-call-tool.test.js +6 -5
  197. package/dist/web/hooks/use-call-tool.test.js.map +1 -1
  198. package/dist/web/hooks/use-display-mode.test.js +4 -3
  199. package/dist/web/hooks/use-display-mode.test.js.map +1 -1
  200. package/dist/web/hooks/use-download.test.js +5 -5
  201. package/dist/web/hooks/use-download.test.js.map +1 -1
  202. package/dist/web/hooks/use-files.test.js +3 -3
  203. package/dist/web/hooks/use-files.test.js.map +1 -1
  204. package/dist/web/hooks/use-host-info.d.ts +22 -0
  205. package/dist/web/hooks/use-host-info.js +30 -0
  206. package/dist/web/hooks/use-host-info.js.map +1 -0
  207. package/dist/web/hooks/use-host-info.test.d.ts +1 -0
  208. package/dist/web/hooks/use-host-info.test.js +59 -0
  209. package/dist/web/hooks/use-host-info.test.js.map +1 -0
  210. package/dist/web/hooks/use-layout.test.js +5 -5
  211. package/dist/web/hooks/use-layout.test.js.map +1 -1
  212. package/dist/web/hooks/use-open-external.test.js +7 -11
  213. package/dist/web/hooks/use-open-external.test.js.map +1 -1
  214. package/dist/web/hooks/use-register-view-tool.d.ts +38 -0
  215. package/dist/web/hooks/use-register-view-tool.js +50 -0
  216. package/dist/web/hooks/use-register-view-tool.js.map +1 -0
  217. package/dist/web/hooks/use-request-close.test.js +5 -4
  218. package/dist/web/hooks/use-request-close.test.js.map +1 -1
  219. package/dist/web/hooks/use-request-modal.test.js +3 -3
  220. package/dist/web/hooks/use-request-modal.test.js.map +1 -1
  221. package/dist/web/hooks/use-request-size.test.js +3 -3
  222. package/dist/web/hooks/use-request-size.test.js.map +1 -1
  223. package/dist/web/hooks/use-set-open-in-app-url.test.js +2 -2
  224. package/dist/web/hooks/use-set-open-in-app-url.test.js.map +1 -1
  225. package/dist/web/hooks/use-tool-info.d.ts +25 -7
  226. package/dist/web/hooks/use-tool-info.js +5 -8
  227. package/dist/web/hooks/use-tool-info.js.map +1 -1
  228. package/dist/web/hooks/use-tool-info.test-d.js +11 -29
  229. package/dist/web/hooks/use-tool-info.test-d.js.map +1 -1
  230. package/dist/web/hooks/use-tool-info.test.js +8 -8
  231. package/dist/web/hooks/use-tool-info.test.js.map +1 -1
  232. package/dist/web/hooks/use-user.test.js +5 -5
  233. package/dist/web/hooks/use-user.test.js.map +1 -1
  234. package/dist/web/hooks/use-view-state.js +19 -9
  235. package/dist/web/hooks/use-view-state.js.map +1 -1
  236. package/dist/web/hooks/use-view-state.test.js +34 -3
  237. package/dist/web/hooks/use-view-state.test.js.map +1 -1
  238. package/dist/web/plugin/plugin-build.test.d.ts +1 -0
  239. package/dist/web/plugin/plugin-build.test.js +54 -0
  240. package/dist/web/plugin/plugin-build.test.js.map +1 -0
  241. package/dist/web/plugin/plugin.d.ts +6 -0
  242. package/dist/web/plugin/plugin.js +18 -7
  243. package/dist/web/plugin/plugin.js.map +1 -1
  244. package/dist/web/plugin/plugin.test.d.ts +1 -0
  245. package/dist/web/plugin/plugin.test.js +61 -0
  246. package/dist/web/plugin/plugin.test.js.map +1 -0
  247. package/package.json +22 -20
  248. package/dist/cli/resolve-views-dir.d.ts +0 -1
  249. package/dist/cli/resolve-views-dir.js.map +0 -1
  250. package/dist/cli/use-open-tunnel-browser.d.ts +0 -6
  251. package/dist/cli/use-open-tunnel-browser.js +0 -19
  252. package/dist/cli/use-open-tunnel-browser.js.map +0 -1
  253. package/dist/web/bridges/host-context-stores.d.ts +0 -27
  254. package/dist/web/bridges/host-context-stores.js +0 -73
  255. package/dist/web/bridges/host-context-stores.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,29 @@ 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
+ }
40
+ /**
41
+ * Drop the query string from a `ui://` view URI, leaving the bare path. The
42
+ * `?v=` cache key is the only query we append, so a plain split is enough and
43
+ * sidesteps `URL` normalization quirks on the non-special `ui:` scheme.
44
+ */
45
+ function stripQuery(uri) {
46
+ const queryIndex = uri.indexOf("?");
47
+ return queryIndex === -1 ? uri : uri.slice(0, queryIndex);
48
+ }
20
49
  /**
21
50
  * Coerce a tool handler's return value into an MCP `content` array. Strings
22
51
  * become a single `TextContent`; a single block is wrapped in an array;
@@ -81,13 +110,56 @@ let pendingBuildManifest = null;
81
110
  export function __setBuildManifest(manifest) {
82
111
  pendingBuildManifest = manifest;
83
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
+ }
84
154
  export class McpServer extends McpServerBaseOmitted {
85
155
  /**
86
156
  * The underlying Express app. Use this to extend the HTTP server with
87
157
  * custom routes, middleware, or settings — e.g.
88
158
  * `server.express.get("/health", ...)`.
89
159
  *
90
- * `express.json()` is pre-applied. Register your handlers before `run()`;
160
+ * `express.json()` is pre-applied — tune it via the constructor's third
161
+ * argument, e.g. `new McpServer(info, {}, { json: { limit: "10mb" } })`.
162
+ * Register your handlers before `run()`;
91
163
  * after `run()`, dev-mode middleware, the `/mcp` route, and the default
92
164
  * error handler are appended in that order.
93
165
  *
@@ -100,15 +172,30 @@ export class McpServer extends McpServerBaseOmitted {
100
172
  mcpMiddlewareApplied = false;
101
173
  claimedViews = new Map();
102
174
  viewMetaBuilders = new Map();
175
+ /**
176
+ * Maps a view resource's query-less path to its canonical registered URI
177
+ * (the one carrying the `?v=` cache key). Lets `resources/read` resolve the
178
+ * underlying view no matter which version param the consumer sends, since
179
+ * the param is only a cache key, not part of the resource's identity.
180
+ */
181
+ viewUriByPath = new Map();
103
182
  viteManifest = null;
104
183
  serverInfo;
105
184
  serverOptions;
106
- constructor(serverInfo, options) {
107
- super(serverInfo, options);
185
+ oauthEnabled = false;
186
+ resolveResourceMetadataUrl;
187
+ securitySchemesByTool = new Map();
188
+ constructor(serverInfo, options, skybridgeOptions) {
189
+ const mergedOptions = withSkillsCapability(options, skybridgeOptions);
190
+ super(serverInfo, mergedOptions);
108
191
  this.serverInfo = serverInfo;
109
- this.serverOptions = options;
192
+ this.serverOptions = mergedOptions;
110
193
  this.express = express();
111
- this.express.use(express.json());
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
+ }
112
199
  // Pick up the manifest if `dist/__entry.js` primed it before importing
113
200
  // user code. Consume-once: clear after the first construction so a
114
201
  // subsequent test that doesn't prime can't inherit stale state.
@@ -118,6 +205,19 @@ export class McpServer extends McpServerBaseOmitted {
118
205
  this.setViteManifest(pendingBuildManifest);
119
206
  pendingBuildManifest = null;
120
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);
121
221
  }
122
222
  use(pathOrHandler, ...handlers) {
123
223
  // Branching is load-bearing: Express's `app.use` overloads can't be
@@ -190,15 +290,69 @@ export class McpServer extends McpServerBaseOmitted {
190
290
  return result;
191
291
  },
192
292
  };
293
+ // Resolve a view's `resources/read` by its query-less path so the
294
+ // underlying asset is served no matter the `?v=` value (stale cache key,
295
+ // no param, etc.). The version param is a cache-busting hint for external
296
+ // consumers; it must not gate resolution. We rewrite the lookup URI to the
297
+ // canonical registered one, then restore the requested URI on the response
298
+ // so the consumer-facing URI is never rewritten.
299
+ const viewReadResolveEntry = {
300
+ filter: "resources/read",
301
+ handler: async (req, _extra, next) => {
302
+ const requested = req.params.uri;
303
+ if (typeof requested !== "string") {
304
+ return next();
305
+ }
306
+ const path = stripQuery(requested);
307
+ const canonical = this.viewUriByPath.get(path);
308
+ if (!canonical) {
309
+ return next();
310
+ }
311
+ req.params.uri = canonical;
312
+ try {
313
+ const result = (await next());
314
+ for (const content of result.contents ?? []) {
315
+ if (typeof content.uri === "string" &&
316
+ stripQuery(content.uri) === stripQuery(canonical)) {
317
+ content.uri = requested;
318
+ }
319
+ }
320
+ return result;
321
+ }
322
+ finally {
323
+ // Restore the shared request params so middleware outer to us never
324
+ // observes the rewritten lookup URI after next() unwinds.
325
+ req.params.uri = requested;
326
+ }
327
+ },
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
+ };
193
348
  const monitoringEntry = createMiddlewareEntry();
194
349
  const entries = [
195
350
  ...(monitoringEntry ? [monitoringEntry] : []),
196
351
  viewListMetaEntry,
352
+ viewReadResolveEntry,
353
+ toolsListSecuritySchemesEntry,
197
354
  ...this.mcpMiddlewareEntries,
198
355
  ];
199
- if (entries.length === 0) {
200
- return;
201
- }
202
356
  const { requestHandlers, notificationHandlers } = getHandlerMaps(this.server);
203
357
  const instrumentMap = (map, isNotification) => {
204
358
  for (const [method, handler] of map) {
@@ -321,28 +475,13 @@ export class McpServer extends McpServerBaseOmitted {
321
475
  const val = headers[key];
322
476
  return Array.isArray(val) ? val[0] : val;
323
477
  };
324
- const isClaude = header("user-agent") === "Claude-User";
325
- let serverUrl;
326
- const forwardedHost = header("x-forwarded-host");
327
- const origin = header("origin");
328
- const host = header("host");
329
- if (forwardedHost) {
330
- const proto = header("x-forwarded-proto") || "https";
331
- serverUrl = `${proto}://${forwardedHost}`;
332
- }
333
- else if (origin) {
334
- serverUrl = origin;
335
- }
336
- else if (host) {
337
- const proto = ["127.0.0.1:", "localhost:"].some((p) => host.startsWith(p))
338
- ? "http"
339
- : "https";
340
- serverUrl = `${proto}://${host}`;
341
- }
342
- else {
343
- const devPort = process.env.__PORT || "3000";
344
- serverUrl = `http://localhost:${devPort}`;
345
- }
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"));
346
485
  const connectDomains = [serverUrl];
347
486
  if (!isProduction) {
348
487
  const wsUrl = new URL(serverUrl);
@@ -363,11 +502,9 @@ export class McpServer extends McpServerBaseOmitted {
363
502
  .slice(0, 32);
364
503
  contentMetaOverrides = { domain: `${hash}.claudemcpcontent.com` };
365
504
  }
366
- return { serverUrl, connectDomains, contentMetaOverrides };
505
+ return { serverUrl, assetsBasePath, connectDomains, contentMetaOverrides };
367
506
  }
368
507
  registerViewResources(toolName, view, toolMeta) {
369
- // `hosts` is deprecated and ignored — every view emits the single ext-apps
370
- // resource. Kept for backwards compatibility; see ViewConfig.hosts.
371
508
  // Append a content-derived version param so hosts (e.g. ChatGPT) bust
372
509
  // their cache when the bundle changes, but keep the URI stable across
373
510
  // `tools/list` calls when the bundle hasn't changed.
@@ -407,15 +544,21 @@ export class McpServer extends McpServerBaseOmitted {
407
544
  ...(view.csp?.baseUriDomains && {
408
545
  baseUriDomains: view.csp.baseUriDomains,
409
546
  }),
410
- ...(view.csp?.redirectDomains && {
411
- redirectDomains: view.csp.redirectDomains,
412
- }),
413
547
  },
414
548
  },
415
549
  };
416
- const base = mergeWithUnion(mergeWithUnion(defaults, fromView), {
550
+ const ui = mergeWithUnion(mergeWithUnion(defaults, fromView), {
417
551
  ui: overrides,
418
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
+ };
419
562
  if (view._meta) {
420
563
  return { ...base, ...view._meta };
421
564
  }
@@ -423,9 +566,10 @@ export class McpServer extends McpServerBaseOmitted {
423
566
  },
424
567
  };
425
568
  this.registerViewResource({ name: toolName, viewResource, view });
426
- // Advertise the single resource through all three known pointers so both
427
- // ext-apps hosts (ui.resourceUri) and ChatGPT (openai/outputTemplate) resolve it.
428
- toolMeta["openai/outputTemplate"] = viewResource.uri;
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.
429
573
  // @ts-expect-error - For backwards compatibility with Claude current implementation of the specs
430
574
  toolMeta["ui/resourceUri"] = viewResource.uri;
431
575
  toolMeta.ui = { ...toolMeta.ui, resourceUri: viewResource.uri };
@@ -442,19 +586,25 @@ export class McpServer extends McpServerBaseOmitted {
442
586
  }, contentMetaOverrides);
443
587
  };
444
588
  this.viewMetaBuilders.set(viewUri, buildMeta);
589
+ this.viewUriByPath.set(stripQuery(viewUri), viewUri);
590
+ this.serveLegacyAppsSdkUrl(view.component, viewUri);
445
591
  this.registerResource(name, viewUri, { description: view.description }, async (uri, extra) => {
446
592
  const isProduction = process.env.NODE_ENV === "production";
447
- 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}`;
448
598
  const html = isProduction
449
599
  ? templateHelper.renderProduction({
450
600
  hostType,
451
- serverUrl,
601
+ serverUrl: viewBase,
452
602
  viewFile: this.lookupViewFile(view.component),
453
603
  styleFile: this.lookupDistFile("style.css") ?? "",
454
604
  })
455
605
  : templateHelper.renderDevelopment({
456
606
  hostType,
457
- serverUrl,
607
+ serverUrl: viewBase,
458
608
  viewName: view.component,
459
609
  });
460
610
  return {
@@ -464,9 +614,34 @@ export class McpServer extends McpServerBaseOmitted {
464
614
  };
465
615
  });
466
616
  }
467
- 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, }) {
468
623
  return async (args, extra) => {
469
- 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);
470
645
  return {
471
646
  ...result,
472
647
  content: normalizeContent(result.content),
@@ -530,14 +705,19 @@ export class McpServer extends McpServerBaseOmitted {
530
705
  }
531
706
  registerTool(...args) {
532
707
  const baseFn = McpServerBase.prototype.registerTool;
533
- if (typeof args[0] === "string") {
534
- baseFn.call(this, args[0], args[1], args[2]);
535
- 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.`);
536
716
  }
537
- const config = args[0];
538
- const cb = args[1];
539
- const { name, view, securitySchemes, _meta: userToolMeta, ...toolFields } = config;
717
+ const securitySchemes = rawSecuritySchemes ??
718
+ (auth && this.oauthEnabled ? authToSecuritySchemes(auth) : undefined);
540
719
  const toolMeta = { ...userToolMeta };
720
+ this.securitySchemesByTool.set(name, securitySchemes);
541
721
  if (securitySchemes) {
542
722
  // SEP-1488 puts `securitySchemes` at the top level of the tool
543
723
  // descriptor, but the SDK's `registerTool` drops unknown top-level
@@ -550,7 +730,11 @@ export class McpServer extends McpServerBaseOmitted {
550
730
  this.enforceOneToolPerView(view.component, name);
551
731
  this.registerViewResources(name, view, toolMeta);
552
732
  }
553
- const wrappedCb = this.wrapHandler(cb, { attachViewUUID: Boolean(view) });
733
+ const wrappedCb = this.decorateToolHandler(cb, {
734
+ attachViewUUID: Boolean(view),
735
+ securitySchemes,
736
+ toolName: name,
737
+ });
554
738
  baseFn.call(this, name, { ...toolFields, _meta: toolMeta }, wrappedCb);
555
739
  return this;
556
740
  }