skybridge 0.0.0-dev.d2cb8f6 → 0.0.0-dev.d33e6f5

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 (327) hide show
  1. package/README.md +15 -14
  2. package/dist/cli/build-helpers.d.ts +2 -2
  3. package/dist/cli/build-helpers.js +13 -5
  4. package/dist/cli/build-helpers.js.map +1 -1
  5. package/dist/cli/build-helpers.test.js +30 -4
  6. package/dist/cli/build-helpers.test.js.map +1 -1
  7. package/dist/cli/build-steps.js +17 -6
  8. package/dist/cli/build-steps.js.map +1 -1
  9. package/dist/cli/build-steps.test.js +23 -3
  10. package/dist/cli/build-steps.test.js.map +1 -1
  11. package/dist/cli/detect-port.js +0 -1
  12. package/dist/cli/detect-port.js.map +1 -1
  13. package/dist/cli/resolve-skybridge-config.d.ts +1 -0
  14. package/dist/cli/resolve-skybridge-config.js +11 -1
  15. package/dist/cli/resolve-skybridge-config.js.map +1 -1
  16. package/dist/cli/run-plain.js +2 -1
  17. package/dist/cli/run-plain.js.map +1 -1
  18. package/dist/cli/telemetry.js +2 -0
  19. package/dist/cli/telemetry.js.map +1 -1
  20. package/dist/cli/use-execute-steps.d.ts +1 -1
  21. package/dist/cli/use-nodemon.d.ts +11 -1
  22. package/dist/cli/use-nodemon.js +30 -8
  23. package/dist/cli/use-nodemon.js.map +1 -1
  24. package/dist/cli/use-typescript-check.js +3 -1
  25. package/dist/cli/use-typescript-check.js.map +1 -1
  26. package/dist/commands/dev.js +4 -4
  27. package/dist/commands/dev.js.map +1 -1
  28. package/dist/commands/start.js +2 -2
  29. package/dist/commands/start.js.map +1 -1
  30. package/dist/context-warnings.d.ts +5 -0
  31. package/dist/context-warnings.js +31 -0
  32. package/dist/context-warnings.js.map +1 -0
  33. package/dist/context-warnings.test.js +28 -0
  34. package/dist/context-warnings.test.js.map +1 -0
  35. package/dist/server/app.d.ts +211 -0
  36. package/dist/server/app.js +277 -0
  37. package/dist/server/app.js.map +1 -0
  38. package/dist/server/app.test.js +178 -0
  39. package/dist/server/app.test.js.map +1 -0
  40. package/dist/server/auth/discovery.d.ts +2 -1
  41. package/dist/server/auth/discovery.js +2 -2
  42. package/dist/server/auth/discovery.js.map +1 -1
  43. package/dist/server/auth/index.d.ts +35 -5
  44. package/dist/server/auth/index.js.map +1 -1
  45. package/dist/server/auth/providers/auth0.d.ts +25 -3
  46. package/dist/server/auth/providers/auth0.js +13 -8
  47. package/dist/server/auth/providers/auth0.js.map +1 -1
  48. package/dist/server/auth/providers/auth0.test.js +5 -3
  49. package/dist/server/auth/providers/auth0.test.js.map +1 -1
  50. package/dist/server/auth/providers/authplane.d.ts +4 -2
  51. package/dist/server/auth/providers/authplane.js.map +1 -1
  52. package/dist/server/auth/providers/authplane.test.js +23 -18
  53. package/dist/server/auth/providers/authplane.test.js.map +1 -1
  54. package/dist/server/auth/providers/clerk.d.ts +40 -3
  55. package/dist/server/auth/providers/clerk.js +4 -1
  56. package/dist/server/auth/providers/clerk.js.map +1 -1
  57. package/dist/server/auth/providers/clerk.test.js +5 -3
  58. package/dist/server/auth/providers/clerk.test.js.map +1 -1
  59. package/dist/server/auth/providers/custom.d.ts +16 -6
  60. package/dist/server/auth/providers/custom.js +14 -4
  61. package/dist/server/auth/providers/custom.js.map +1 -1
  62. package/dist/server/auth/providers/custom.test.js +24 -19
  63. package/dist/server/auth/providers/custom.test.js.map +1 -1
  64. package/dist/server/auth/providers/descope.d.ts +32 -3
  65. package/dist/server/auth/providers/descope.js.map +1 -1
  66. package/dist/server/auth/providers/descope.test.js +8 -6
  67. package/dist/server/auth/providers/descope.test.js.map +1 -1
  68. package/dist/server/auth/providers/stytch.d.ts +16 -3
  69. package/dist/server/auth/providers/stytch.js +4 -1
  70. package/dist/server/auth/providers/stytch.js.map +1 -1
  71. package/dist/server/auth/providers/verify-spy.d.ts +12 -0
  72. package/dist/server/auth/providers/verify-spy.js +21 -0
  73. package/dist/server/auth/providers/verify-spy.js.map +1 -0
  74. package/dist/server/auth/providers/workos.d.ts +28 -5
  75. package/dist/server/auth/providers/workos.js +6 -2
  76. package/dist/server/auth/providers/workos.js.map +1 -1
  77. package/dist/server/auth/setup.d.ts +1 -1
  78. package/dist/server/auth/setup.js +4 -5
  79. package/dist/server/auth/setup.js.map +1 -1
  80. package/dist/server/auth/setup.test.js +97 -57
  81. package/dist/server/auth/setup.test.js.map +1 -1
  82. package/dist/server/auth/verify.d.ts +42 -6
  83. package/dist/server/auth/verify.js +27 -3
  84. package/dist/server/auth/verify.js.map +1 -1
  85. package/dist/server/auth/verify.test.js +65 -0
  86. package/dist/server/auth/verify.test.js.map +1 -1
  87. package/dist/server/auth-extra.test-d.js +130 -0
  88. package/dist/server/auth-extra.test-d.js.map +1 -0
  89. package/dist/server/auth.d.ts +43 -6
  90. package/dist/server/auth.js +4 -5
  91. package/dist/server/auth.js.map +1 -1
  92. package/dist/server/build-manifest.test.js +7 -6
  93. package/dist/server/build-manifest.test.js.map +1 -1
  94. package/dist/server/content-helpers.d.ts +1 -1
  95. package/dist/server/content-helpers.js.map +1 -1
  96. package/dist/server/events.d.ts +113 -0
  97. package/dist/server/events.js +318 -0
  98. package/dist/server/events.js.map +1 -0
  99. package/dist/server/events.test.js +172 -0
  100. package/dist/server/events.test.js.map +1 -0
  101. package/dist/server/express.d.ts +24 -3
  102. package/dist/server/express.js +44 -32
  103. package/dist/server/express.js.map +1 -1
  104. package/dist/server/express.test.js +265 -103
  105. package/dist/server/express.test.js.map +1 -1
  106. package/dist/server/file-ref.d.ts +1 -1
  107. package/dist/server/file-ref.js +1 -1
  108. package/dist/server/file-ref.js.map +1 -1
  109. package/dist/server/form-input.d.ts +120 -0
  110. package/dist/server/form-input.js +89 -0
  111. package/dist/server/form-input.js.map +1 -0
  112. package/dist/server/index.d.ts +15 -8
  113. package/dist/server/index.js +8 -2
  114. package/dist/server/index.js.map +1 -1
  115. package/dist/server/mcp-errors.test.js +229 -0
  116. package/dist/server/mcp-errors.test.js.map +1 -0
  117. package/dist/server/metric.d.ts +5 -3
  118. package/dist/server/metric.js +5 -5
  119. package/dist/server/metric.js.map +1 -1
  120. package/dist/server/middleware.d.ts +35 -10
  121. package/dist/server/middleware.js +33 -0
  122. package/dist/server/middleware.js.map +1 -1
  123. package/dist/server/middleware.test-d.js +1 -1
  124. package/dist/server/middleware.test-d.js.map +1 -1
  125. package/dist/server/middleware.test.js +242 -134
  126. package/dist/server/middleware.test.js.map +1 -1
  127. package/dist/server/register-tool.test.js +230 -0
  128. package/dist/server/register-tool.test.js.map +1 -0
  129. package/dist/server/server.d.ts +338 -128
  130. package/dist/server/server.js +389 -305
  131. package/dist/server/server.js.map +1 -1
  132. package/dist/server/skills-integration.test.js +44 -21
  133. package/dist/server/skills-integration.test.js.map +1 -1
  134. package/dist/server/skills.d.ts +14 -10
  135. package/dist/server/skills.js +49 -39
  136. package/dist/server/skills.js.map +1 -1
  137. package/dist/server/skills.test.js +77 -44
  138. package/dist/server/skills.test.js.map +1 -1
  139. package/dist/server/templateHelper.d.ts +1 -3
  140. package/dist/server/templateHelper.js.map +1 -1
  141. package/dist/server/templates.generated.js +28 -18
  142. package/dist/server/templates.generated.js.map +1 -1
  143. package/dist/server/tool-extra.test.d.ts +1 -0
  144. package/dist/server/tool-extra.test.js +167 -0
  145. package/dist/server/tool-extra.test.js.map +1 -0
  146. package/dist/server/view-resource-resolution.test.js +53 -13
  147. package/dist/server/view-resource-resolution.test.js.map +1 -1
  148. package/dist/test/utils.d.ts +9 -11
  149. package/dist/test/utils.js +220 -201
  150. package/dist/test/utils.js.map +1 -1
  151. package/dist/test/view.test.js +41 -37
  152. package/dist/test/view.test.js.map +1 -1
  153. package/dist/views/index.d.ts +10 -0
  154. package/dist/views/index.js +3 -0
  155. package/dist/views/index.js.map +1 -0
  156. package/dist/{web/plugin → views}/scan-views.js +1 -1
  157. package/dist/views/scan-views.js.map +1 -0
  158. package/dist/views/scan-views.test.d.ts +1 -0
  159. package/dist/views/scan-views.test.js.map +1 -0
  160. package/dist/views/validate-view.d.ts +1 -0
  161. package/dist/{web/plugin → views}/validate-view.js +2 -2
  162. package/dist/views/validate-view.js.map +1 -0
  163. package/dist/views/validate-view.test.d.ts +1 -0
  164. package/dist/views/validate-view.test.js.map +1 -0
  165. package/dist/web/bridges/adaptor.d.ts +14 -3
  166. package/dist/web/bridges/adaptor.js +168 -29
  167. package/dist/web/bridges/adaptor.js.map +1 -1
  168. package/dist/web/bridges/adaptor.test.js +77 -9
  169. package/dist/web/bridges/adaptor.test.js.map +1 -1
  170. package/dist/web/bridges/apps-sdk/bridge.js +1 -1
  171. package/dist/web/bridges/apps-sdk/bridge.js.map +1 -1
  172. package/dist/web/bridges/apps-sdk/types.d.ts +3 -4
  173. package/dist/web/bridges/apps-sdk/types.js.map +1 -1
  174. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.d.ts +4 -4
  175. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js +4 -4
  176. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js.map +1 -1
  177. package/dist/web/bridges/get-adaptor.test.js +2 -2
  178. package/dist/web/bridges/get-adaptor.test.js.map +1 -1
  179. package/dist/web/bridges/mcp-app/bridge.d.ts +1 -1
  180. package/dist/web/bridges/mcp-app/bridge.js +10 -3
  181. package/dist/web/bridges/mcp-app/bridge.js.map +1 -1
  182. package/dist/web/bridges/mcp-app/bridge.test.js +2 -2
  183. package/dist/web/bridges/mcp-app/bridge.test.js.map +1 -1
  184. package/dist/web/bridges/mcp-app/types.d.ts +18 -3
  185. package/dist/web/bridges/mcp-app/types.js.map +1 -1
  186. package/dist/web/bridges/mcp-app/use-mcp-app-context.d.ts +2 -2
  187. package/dist/web/bridges/mcp-app/use-mcp-app-context.js +1 -1
  188. package/dist/web/bridges/mcp-app/use-mcp-app-context.js.map +1 -1
  189. package/dist/web/bridges/mcp-app/use-mcp-app-context.test.js +1 -1
  190. package/dist/web/bridges/mcp-app/use-mcp-app-context.test.js.map +1 -1
  191. package/dist/web/bridges/mcp-app/view-tools.test.js +1 -1
  192. package/dist/web/bridges/mcp-app/view-tools.test.js.map +1 -1
  193. package/dist/web/bridges/types.d.ts +74 -16
  194. package/dist/web/bridges/types.js +1 -1
  195. package/dist/web/bridges/types.js.map +1 -1
  196. package/dist/web/create-store.test.js +2 -2
  197. package/dist/web/create-store.test.js.map +1 -1
  198. package/dist/web/data-llm.js +3 -2
  199. package/dist/web/data-llm.js.map +1 -1
  200. package/dist/web/data-llm.test.js +11 -4
  201. package/dist/web/data-llm.test.js.map +1 -1
  202. package/dist/web/generate-helpers.d.ts +23 -19
  203. package/dist/web/generate-helpers.js +21 -17
  204. package/dist/web/generate-helpers.js.map +1 -1
  205. package/dist/web/generate-helpers.test-d.js +3 -3
  206. package/dist/web/generate-helpers.test-d.js.map +1 -1
  207. package/dist/web/generate-helpers.test.js +2 -2
  208. package/dist/web/generate-helpers.test.js.map +1 -1
  209. package/dist/web/hooks/index.d.ts +6 -2
  210. package/dist/web/hooks/index.js +6 -2
  211. package/dist/web/hooks/index.js.map +1 -1
  212. package/dist/web/hooks/openai-extensions.test.d.ts +1 -0
  213. package/dist/web/hooks/openai-extensions.test.js +107 -0
  214. package/dist/web/hooks/openai-extensions.test.js.map +1 -0
  215. package/dist/web/hooks/use-call-tool.d.ts +2 -2
  216. package/dist/web/hooks/use-call-tool.js +15 -10
  217. package/dist/web/hooks/use-call-tool.js.map +1 -1
  218. package/dist/web/hooks/use-call-tool.test.js +15 -4
  219. package/dist/web/hooks/use-call-tool.test.js.map +1 -1
  220. package/dist/web/hooks/use-deep-link.d.ts +22 -0
  221. package/dist/web/hooks/use-deep-link.js +34 -0
  222. package/dist/web/hooks/use-deep-link.js.map +1 -0
  223. package/dist/web/hooks/use-display-mode.d.ts +1 -1
  224. package/dist/web/hooks/use-display-mode.js +1 -1
  225. package/dist/web/hooks/use-display-mode.js.map +1 -1
  226. package/dist/web/hooks/use-display-mode.test.js +1 -1
  227. package/dist/web/hooks/use-display-mode.test.js.map +1 -1
  228. package/dist/web/hooks/use-download.d.ts +1 -3
  229. package/dist/web/hooks/use-download.js +1 -2
  230. package/dist/web/hooks/use-download.js.map +1 -1
  231. package/dist/web/hooks/use-download.test.js +7 -7
  232. package/dist/web/hooks/use-download.test.js.map +1 -1
  233. package/dist/web/hooks/use-file-resource.d.ts +37 -0
  234. package/dist/web/hooks/use-file-resource.js +98 -0
  235. package/dist/web/hooks/use-file-resource.js.map +1 -0
  236. package/dist/web/hooks/use-file-resource.test.d.ts +1 -0
  237. package/dist/web/hooks/use-file-resource.test.js +65 -0
  238. package/dist/web/hooks/use-file-resource.test.js.map +1 -0
  239. package/dist/web/hooks/use-files.test.js +1 -1
  240. package/dist/web/hooks/use-files.test.js.map +1 -1
  241. package/dist/web/hooks/use-host.d.ts +44 -0
  242. package/dist/web/hooks/use-host.js +45 -0
  243. package/dist/web/hooks/use-host.js.map +1 -0
  244. package/dist/web/hooks/use-host.test.d.ts +1 -0
  245. package/dist/web/hooks/{use-host-info.test.js → use-host.test.js} +7 -7
  246. package/dist/web/hooks/use-host.test.js.map +1 -0
  247. package/dist/web/hooks/use-model-context.d.ts +44 -0
  248. package/dist/web/hooks/use-model-context.js +63 -0
  249. package/dist/web/hooks/use-model-context.js.map +1 -0
  250. package/dist/web/hooks/use-open-external.test.js +2 -2
  251. package/dist/web/hooks/use-open-external.test.js.map +1 -1
  252. package/dist/web/hooks/use-open-file.d.ts +20 -0
  253. package/dist/web/hooks/use-open-file.js +25 -0
  254. package/dist/web/hooks/use-open-file.js.map +1 -0
  255. package/dist/web/hooks/use-register-view-tool.d.ts +3 -4
  256. package/dist/web/hooks/use-register-view-tool.js +1 -1
  257. package/dist/web/hooks/use-register-view-tool.js.map +1 -1
  258. package/dist/web/hooks/use-request-close.test.js +1 -1
  259. package/dist/web/hooks/use-request-close.test.js.map +1 -1
  260. package/dist/web/hooks/use-request-modal.test.js +1 -1
  261. package/dist/web/hooks/use-request-modal.test.js.map +1 -1
  262. package/dist/web/hooks/use-request-size.d.ts +1 -1
  263. package/dist/web/hooks/use-request-size.js +1 -1
  264. package/dist/web/hooks/use-request-size.js.map +1 -1
  265. package/dist/web/hooks/use-request-size.test.js +1 -1
  266. package/dist/web/hooks/use-request-size.test.js.map +1 -1
  267. package/dist/web/hooks/use-send-follow-up-message.d.ts +8 -2
  268. package/dist/web/hooks/use-send-follow-up-message.js +6 -0
  269. package/dist/web/hooks/use-send-follow-up-message.js.map +1 -1
  270. package/dist/web/hooks/use-set-open-in-app-url.test.js +2 -8
  271. package/dist/web/hooks/use-set-open-in-app-url.test.js.map +1 -1
  272. package/dist/web/hooks/use-tool-info.d.ts +1 -20
  273. package/dist/web/hooks/use-tool-info.js +0 -1
  274. package/dist/web/hooks/use-tool-info.js.map +1 -1
  275. package/dist/web/hooks/use-tool-info.test-d.js +0 -5
  276. package/dist/web/hooks/use-tool-info.test-d.js.map +1 -1
  277. package/dist/web/hooks/use-tool-info.test.js +2 -7
  278. package/dist/web/hooks/use-tool-info.test.js.map +1 -1
  279. package/dist/web/hooks/use-user.d.ts +3 -2
  280. package/dist/web/hooks/use-user.js +4 -3
  281. package/dist/web/hooks/use-user.js.map +1 -1
  282. package/dist/web/hooks/use-user.test.js +5 -3
  283. package/dist/web/hooks/use-user.test.js.map +1 -1
  284. package/dist/web/hooks/use-view-state.test.js +2 -2
  285. package/dist/web/hooks/use-view-state.test.js.map +1 -1
  286. package/dist/web/hooks/use-viewport.d.ts +20 -0
  287. package/dist/web/hooks/use-viewport.js +21 -0
  288. package/dist/web/hooks/use-viewport.js.map +1 -0
  289. package/dist/web/hooks/use-viewport.test.d.ts +1 -0
  290. package/dist/web/hooks/{use-layout.test.js → use-viewport.test.js} +12 -32
  291. package/dist/web/hooks/use-viewport.test.js.map +1 -0
  292. package/dist/web/mount-view.d.ts +3 -2
  293. package/dist/web/mount-view.js +6 -11
  294. package/dist/web/mount-view.js.map +1 -1
  295. package/package.json +17 -13
  296. package/dist/web/hooks/use-host-info.d.ts +0 -22
  297. package/dist/web/hooks/use-host-info.js +0 -30
  298. package/dist/web/hooks/use-host-info.js.map +0 -1
  299. package/dist/web/hooks/use-host-info.test.js.map +0 -1
  300. package/dist/web/hooks/use-layout.d.ts +0 -24
  301. package/dist/web/hooks/use-layout.js +0 -25
  302. package/dist/web/hooks/use-layout.js.map +0 -1
  303. package/dist/web/hooks/use-layout.test.js.map +0 -1
  304. package/dist/web/plugin/data-llm.test.js +0 -81
  305. package/dist/web/plugin/data-llm.test.js.map +0 -1
  306. package/dist/web/plugin/plugin.d.ts +0 -39
  307. package/dist/web/plugin/plugin.js +0 -189
  308. package/dist/web/plugin/plugin.js.map +0 -1
  309. package/dist/web/plugin/scan-views.js.map +0 -1
  310. package/dist/web/plugin/scan-views.test.js.map +0 -1
  311. package/dist/web/plugin/transform-data-llm.d.ts +0 -12
  312. package/dist/web/plugin/transform-data-llm.js +0 -96
  313. package/dist/web/plugin/transform-data-llm.js.map +0 -1
  314. package/dist/web/plugin/transform-data-llm.test.js +0 -81
  315. package/dist/web/plugin/transform-data-llm.test.js.map +0 -1
  316. package/dist/web/plugin/validate-view.d.ts +0 -1
  317. package/dist/web/plugin/validate-view.js.map +0 -1
  318. package/dist/web/plugin/validate-view.test.js.map +0 -1
  319. /package/dist/{web/hooks/use-host-info.test.d.ts → context-warnings.test.d.ts} +0 -0
  320. /package/dist/{web/hooks/use-layout.test.d.ts → server/app.test.d.ts} +0 -0
  321. /package/dist/{web/plugin/data-llm.test.d.ts → server/auth-extra.test-d.d.ts} +0 -0
  322. /package/dist/{web/plugin/scan-views.test.d.ts → server/events.test.d.ts} +0 -0
  323. /package/dist/{web/plugin/transform-data-llm.test.d.ts → server/mcp-errors.test.d.ts} +0 -0
  324. /package/dist/{web/plugin/validate-view.test.d.ts → server/register-tool.test.d.ts} +0 -0
  325. /package/dist/{web/plugin → views}/scan-views.d.ts +0 -0
  326. /package/dist/{web/plugin → views}/scan-views.test.js +0 -0
  327. /package/dist/{web/plugin → views}/validate-view.test.js +0 -0
@@ -0,0 +1,211 @@
1
+ import type { Implementation, Server as SdkServer, ServerOptions } from "@modelcontextprotocol/server";
2
+ import type { ErrorRequestHandler, Express, RequestHandler } from "express";
3
+ import type { OAuthConfig, OAuthProvider } from "./auth/index.js";
4
+ import type { ExtraClaims } from "./auth.js";
5
+ import type { McpMiddlewareEntry } from "./middleware.js";
6
+ import { type JsonOptions, McpServer, type McpServerTypes, type ToolDef } from "./server.js";
7
+ /**
8
+ * The `handler` field of {@link SkybridgeConfig}: builds the app's MCP
9
+ * surface. Runs again for **every incoming request**, on a fresh
10
+ * {@link McpServer}, so keep it to registration: pools, clients and any other
11
+ * one-time work belong in `setup`, whose result is the second argument. It
12
+ * must **return** the chained server so `typeof app` carries the registered
13
+ * tool types.
14
+ *
15
+ * @typeParam TConfig - What `setup` resolved to, passed as the second argument.
16
+ */
17
+ export type SkybridgeHandler<TTools extends Record<string, ToolDef>, TConfig, TAuthExtra extends ExtraClaims> = (server: McpServer<Record<never, ToolDef>, TAuthExtra>, config: TConfig) => McpServer<TTools, TAuthExtra>;
18
+ /**
19
+ * What the `oauth` field accepts: an {@link OAuthConfig}, a provider
20
+ * (`oauth: auth0Provider(...)`), or a function of the `setup` result returning
21
+ * either. Providers and functions resolve once, at {@link Skybridge.run} or on
22
+ * the first request, never at module import. Anything asynchronous belongs in
23
+ * a provider's `resolve`.
24
+ */
25
+ export type SkybridgeOAuthInput<TConfig, TExtra extends ExtraClaims> = OAuthConfig<TExtra> | OAuthProvider<TExtra> | ((config: TConfig) => OAuthConfig<TExtra> | OAuthProvider<TExtra>);
26
+ /**
27
+ * Everything a Skybridge app needs in one bag: the MCP implementation info
28
+ * (`name`, `version`, …), the SDK's {@link ServerOptions} (`capabilities`,
29
+ * `instructions`, …), the Express and skills options, and the app's behavior
30
+ * (`setup`, `oauth`, `handler`).
31
+ *
32
+ * All type parameters are inferred from the value: the config from `setup`,
33
+ * the auth claims from `oauth`, and the tool registry from the server
34
+ * `handler` returns.
35
+ */
36
+ export type SkybridgeConfig<TTools extends Record<string, ToolDef> = Record<never, ToolDef>, TConfig = undefined, TAuthExtra extends ExtraClaims = ExtraClaims> = Implementation & ServerOptions & {
37
+ /** Options for the built-in `express.json()` middleware, e.g. `{ limit: "10mb" }`. */
38
+ json?: JsonOptions;
39
+ /**
40
+ * @experimental Serve Agent Skills from `src/skills` over MCP (SEP-2640).
41
+ * API may change.
42
+ */
43
+ skills?: boolean;
44
+ /**
45
+ * Replaces the error reporter of the built-in `/mcp` handler. Receives every
46
+ * error the MCP SDK handler or its Node adapter reports: rejected requests,
47
+ * including the `UnsupportedProtocolVersionError` the default skips, and
48
+ * failures to build or serve the per-request server. Reporting only: the
49
+ * response does not change. It can run before the response is sent, so keep
50
+ * it fast and synchronous. Default: `console.error` for every error the MCP
51
+ * SDK handler reports, except `UnsupportedProtocolVersionError`. Node
52
+ * adapter errors are not logged by default.
53
+ */
54
+ onMcpError?: (error: Error) => void;
55
+ /**
56
+ * Loads whatever the app needs up front (remote config, secrets, datasets,
57
+ * …). Runs **once** — at {@link Skybridge.run} or on the first request,
58
+ * never at module import — and its awaited return value is passed to an
59
+ * `oauth` function and to `handler` as the second argument.
60
+ */
61
+ setup?: () => TConfig;
62
+ /**
63
+ * Resource-server OAuth. When set, mounts the well-known metadata routes
64
+ * and bearer auth on `/mcp`, and the verifier's claims type
65
+ * `extra.http.authInfo.extra` in tool handlers.
66
+ */
67
+ oauth?: SkybridgeOAuthInput<Awaited<TConfig>, TAuthExtra>;
68
+ /** Registers the MCP surface, per request. See {@link SkybridgeHandler}. */
69
+ handler: SkybridgeHandler<TTools, Awaited<TConfig>, TAuthExtra>;
70
+ };
71
+ /**
72
+ * A Skybridge app: the HTTP surface (Express, OAuth metadata, the `/mcp`
73
+ * route) plus a handler that builds the MCP server for each request.
74
+ *
75
+ * The handler runs for every request, so tools, resources, prompts and views
76
+ * are always registered on the instance that serves the request. Anything in
77
+ * the handler body other than registration therefore runs per request too.
78
+ * Anything asynchronous the app needs (remote config, secrets, …) goes in
79
+ * `setup`, which runs once and feeds the handler's second argument.
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * export const app = new Skybridge({
84
+ * name: "my-app",
85
+ * version: "1.0.0",
86
+ * setup: async () => loadConfig(),
87
+ * oauth: (config) => descopeProvider({ url: config.mcpServerUrl }),
88
+ * handler: (server, config) =>
89
+ * server.registerTool(
90
+ * {
91
+ * name: "search",
92
+ * inputSchema: { query: z.string() },
93
+ * view: { component: "search" },
94
+ * },
95
+ * async ({ query }) => ({ content: `Results for ${query}` }),
96
+ * ),
97
+ * });
98
+ *
99
+ * export type AppType = typeof app;
100
+ * ```
101
+ *
102
+ * @see https://docs.skybridge.tech/api-reference/mcp-server
103
+ */
104
+ export declare class Skybridge<TTools extends Record<string, ToolDef> = Record<never, ToolDef>, TConfig = undefined, TAuthExtra extends ExtraClaims = ExtraClaims> {
105
+ readonly $types: McpServerTypes<TTools>;
106
+ private readonly serverInfo;
107
+ private readonly serverOptions;
108
+ private readonly skills?;
109
+ private oauthEnabled;
110
+ private readonly handler;
111
+ private readonly setup?;
112
+ private readonly oauthInput?;
113
+ private config?;
114
+ private readonly expressApp;
115
+ private readonly onMcpError?;
116
+ private readonly errorMiddleware;
117
+ private readonly mcpMiddlewareEntries;
118
+ private resolveResourceMetadataUrl?;
119
+ private readyPromise?;
120
+ private slowHandlerWarned;
121
+ constructor({ name, title, version, description, icons, websiteUrl, json, skills, onMcpError, setup, oauth, handler, ...serverOptions }: SkybridgeConfig<TTools, TConfig, TAuthExtra>);
122
+ /**
123
+ * Resolve `setup` and `oauth`, then wire OAuth onto the Express app. Runs
124
+ * once; every entry point that needs a built server awaits it, and a failed
125
+ * attempt is retried on the next call.
126
+ *
127
+ * @internal
128
+ */
129
+ ready(): Promise<void>;
130
+ /**
131
+ * The underlying Express app. Use this to extend the HTTP server with
132
+ * custom routes, middleware, or settings — e.g.
133
+ * `app.express.get("/health", ...)`.
134
+ *
135
+ * `express.json()` is pre-applied — tune it via the `json` config field,
136
+ * e.g. `new Skybridge({ name, version, json: { limit: "10mb" }, handler })`.
137
+ * Register your handlers before `run()`; after `run()`, dev-mode middleware,
138
+ * the `/mcp` route, and the default error handler are appended in that order.
139
+ *
140
+ * Note: Alpic Cloud only routes traffic to `/mcp` — custom routes work
141
+ * locally and on self-hosted deployments.
142
+ */
143
+ get express(): Express;
144
+ /**
145
+ * Build a fresh server for one stateless HTTP request, as
146
+ * `createMcpHandler`'s factory contract requires: the handler runs again so
147
+ * the SDK's handler closures belong to the instance whose protocol era it
148
+ * stamps. Sharing one instance's handler maps instead would bind them to an
149
+ * instance that is never marked, pinning every request to the 2025 codec and
150
+ * letting concurrent callers overwrite each other's negotiated version.
151
+ *
152
+ * Awaits `setup` and the `oauth` input on first use. Additional middleware
153
+ * wraps the app pipeline for this instance only; it is not registered on the app.
154
+ *
155
+ * @internal
156
+ */
157
+ createServerInstance(middleware?: McpMiddlewareEntry[]): Promise<SdkServer>;
158
+ /** Register outer MCP middleware on this app, in registration order. @internal */
159
+ withMcpMiddleware(entry: McpMiddlewareEntry): this;
160
+ /**
161
+ * Connect a Skybridge app to an MCP transport. Use this when you're
162
+ * embedding Skybridge in a host that already manages its own transport
163
+ * (e.g. stdio for desktop apps); for HTTP, prefer {@link Skybridge.run}
164
+ * which sets the transport up for you.
165
+ */
166
+ connect(transport: Parameters<SdkServer["connect"]>[0]): Promise<void>;
167
+ /**
168
+ * Register Express middleware on the underlying app. Mirrors `app.use` —
169
+ * pass handlers directly or a path-prefixed handler list. Register before
170
+ * {@link Skybridge.run}; ordering matches Express.
171
+ *
172
+ * Note: Alpic Cloud only routes traffic to `/mcp`. Custom paths work
173
+ * locally and on self-hosted deployments.
174
+ */
175
+ use(...handlers: RequestHandler[]): this;
176
+ use(path: string, ...handlers: RequestHandler[]): this;
177
+ /**
178
+ * Register Express error-handling middleware to run after the built-in
179
+ * `/mcp` route (or your custom route). Use this to log or transform errors
180
+ * thrown by tool handlers before the default error handler responds.
181
+ *
182
+ * @example
183
+ * ```ts
184
+ * app.useOnError((err, _req, _res, next) => {
185
+ * logger.error(err);
186
+ * next(err);
187
+ * });
188
+ * ```
189
+ */
190
+ useOnError(...handlers: ErrorRequestHandler[]): this;
191
+ useOnError(path: string, ...handlers: ErrorRequestHandler[]): this;
192
+ /**
193
+ * Start the HTTP server. Listens on `process.env.__PORT` (default `3000`),
194
+ * mounts the `/mcp` route, and applies any custom Express middleware
195
+ * registered via {@link Skybridge.use} / {@link Skybridge.useOnError}.
196
+ *
197
+ * On Cloudflare Workers / workerd, returns an object exposing `fetch` so
198
+ * the runtime can bridge incoming requests to the Node HTTP server. On
199
+ * Vercel (`VERCEL === "1"`), returns the Express app directly so the
200
+ * serverless function entry can call it as a `(req, res)` handler. On
201
+ * Node, returns `undefined` once listening. When the process was spawned
202
+ * with an IPC channel, the bound port is reported to the parent as
203
+ * `{ type: "skybridge:listening", port }`, the readiness signal test runners
204
+ * wait on instead of polling.
205
+ */
206
+ run(): Promise<{
207
+ fetch: (...args: unknown[]) => unknown;
208
+ } | Express | undefined>;
209
+ private buildServer;
210
+ private instrumentHandlers;
211
+ }
@@ -0,0 +1,277 @@
1
+ import http from "node:http";
2
+ import { setupOAuth } from "./auth/setup.js";
3
+ import { buildMcpHandler, createApp, createBaseApp } from "./express.js";
4
+ import { registerMonitoring } from "./metric.js";
5
+ import { buildMiddlewareChain, getHandlerMaps } from "./middleware.js";
6
+ import { McpServer, } from "./server.js";
7
+ const SLOW_HANDLER_THRESHOLD_MS = 50;
8
+ async function resolveOAuthInput(input, config) {
9
+ const resolved = typeof input === "function" ? input(config) : input;
10
+ return resolved && "resolve" in resolved
11
+ ? await resolved.resolve()
12
+ : resolved;
13
+ }
14
+ /**
15
+ * A Skybridge app: the HTTP surface (Express, OAuth metadata, the `/mcp`
16
+ * route) plus a handler that builds the MCP server for each request.
17
+ *
18
+ * The handler runs for every request, so tools, resources, prompts and views
19
+ * are always registered on the instance that serves the request. Anything in
20
+ * the handler body other than registration therefore runs per request too.
21
+ * Anything asynchronous the app needs (remote config, secrets, …) goes in
22
+ * `setup`, which runs once and feeds the handler's second argument.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * export const app = new Skybridge({
27
+ * name: "my-app",
28
+ * version: "1.0.0",
29
+ * setup: async () => loadConfig(),
30
+ * oauth: (config) => descopeProvider({ url: config.mcpServerUrl }),
31
+ * handler: (server, config) =>
32
+ * server.registerTool(
33
+ * {
34
+ * name: "search",
35
+ * inputSchema: { query: z.string() },
36
+ * view: { component: "search" },
37
+ * },
38
+ * async ({ query }) => ({ content: `Results for ${query}` }),
39
+ * ),
40
+ * });
41
+ *
42
+ * export type AppType = typeof app;
43
+ * ```
44
+ *
45
+ * @see https://docs.skybridge.tech/api-reference/mcp-server
46
+ */
47
+ export class Skybridge {
48
+ serverInfo;
49
+ serverOptions;
50
+ skills;
51
+ oauthEnabled = false;
52
+ handler;
53
+ setup;
54
+ oauthInput;
55
+ config;
56
+ expressApp;
57
+ onMcpError;
58
+ errorMiddleware = [];
59
+ mcpMiddlewareEntries = [];
60
+ resolveResourceMetadataUrl;
61
+ readyPromise;
62
+ slowHandlerWarned = false;
63
+ constructor({ name, title, version, description, icons, websiteUrl, json, skills, onMcpError, setup, oauth, handler, ...serverOptions }) {
64
+ this.serverInfo = { name, title, version, description, icons, websiteUrl };
65
+ this.serverOptions = serverOptions;
66
+ this.skills = skills;
67
+ this.onMcpError = onMcpError;
68
+ this.setup = setup;
69
+ this.oauthInput = oauth;
70
+ this.handler = handler;
71
+ this.expressApp = createBaseApp(json);
72
+ registerMonitoring(this);
73
+ }
74
+ /**
75
+ * Resolve `setup` and `oauth`, then wire OAuth onto the Express app. Runs
76
+ * once; every entry point that needs a built server awaits it, and a failed
77
+ * attempt is retried on the next call.
78
+ *
79
+ * @internal
80
+ */
81
+ ready() {
82
+ this.readyPromise ??= (async () => {
83
+ if (this.setup) {
84
+ this.config = await this.setup();
85
+ }
86
+ const oauth = await resolveOAuthInput(this.oauthInput, this.config);
87
+ this.oauthEnabled = Boolean(oauth);
88
+ const sample = this.buildServer();
89
+ if (oauth) {
90
+ this.resolveResourceMetadataUrl = setupOAuth(this.expressApp, oauth, sample.securitySchemesByTool);
91
+ }
92
+ })().catch((error) => {
93
+ this.readyPromise = undefined;
94
+ throw error;
95
+ });
96
+ return this.readyPromise;
97
+ }
98
+ /**
99
+ * The underlying Express app. Use this to extend the HTTP server with
100
+ * custom routes, middleware, or settings — e.g.
101
+ * `app.express.get("/health", ...)`.
102
+ *
103
+ * `express.json()` is pre-applied — tune it via the `json` config field,
104
+ * e.g. `new Skybridge({ name, version, json: { limit: "10mb" }, handler })`.
105
+ * Register your handlers before `run()`; after `run()`, dev-mode middleware,
106
+ * the `/mcp` route, and the default error handler are appended in that order.
107
+ *
108
+ * Note: Alpic Cloud only routes traffic to `/mcp` — custom routes work
109
+ * locally and on self-hosted deployments.
110
+ */
111
+ get express() {
112
+ return this.expressApp;
113
+ }
114
+ /**
115
+ * Build a fresh server for one stateless HTTP request, as
116
+ * `createMcpHandler`'s factory contract requires: the handler runs again so
117
+ * the SDK's handler closures belong to the instance whose protocol era it
118
+ * stamps. Sharing one instance's handler maps instead would bind them to an
119
+ * instance that is never marked, pinning every request to the 2025 codec and
120
+ * letting concurrent callers overwrite each other's negotiated version.
121
+ *
122
+ * Awaits `setup` and the `oauth` input on first use. Additional middleware
123
+ * wraps the app pipeline for this instance only; it is not registered on the app.
124
+ *
125
+ * @internal
126
+ */
127
+ async createServerInstance(middleware = []) {
128
+ await this.ready();
129
+ const server = this.buildServer();
130
+ this.instrumentHandlers(server, middleware);
131
+ return server.server;
132
+ }
133
+ /** Register outer MCP middleware on this app, in registration order. @internal */
134
+ withMcpMiddleware(entry) {
135
+ this.mcpMiddlewareEntries.push(entry);
136
+ return this;
137
+ }
138
+ /**
139
+ * Connect a Skybridge app to an MCP transport. Use this when you're
140
+ * embedding Skybridge in a host that already manages its own transport
141
+ * (e.g. stdio for desktop apps); for HTTP, prefer {@link Skybridge.run}
142
+ * which sets the transport up for you.
143
+ */
144
+ async connect(transport) {
145
+ const instance = await this.createServerInstance();
146
+ await instance.connect(transport);
147
+ }
148
+ use(pathOrHandler, ...handlers) {
149
+ // Branching is load-bearing: Express's `app.use` overloads can't be
150
+ // resolved against a `string | RequestHandler` union, so we narrow.
151
+ if (typeof pathOrHandler === "string") {
152
+ this.expressApp.use(pathOrHandler, ...handlers);
153
+ }
154
+ else {
155
+ this.expressApp.use(pathOrHandler, ...handlers);
156
+ }
157
+ return this;
158
+ }
159
+ useOnError(pathOrHandler, ...handlers) {
160
+ if (typeof pathOrHandler === "string") {
161
+ this.errorMiddleware.push({ path: pathOrHandler, handlers });
162
+ }
163
+ else {
164
+ this.errorMiddleware.push({ handlers: [pathOrHandler, ...handlers] });
165
+ }
166
+ return this;
167
+ }
168
+ /**
169
+ * Start the HTTP server. Listens on `process.env.__PORT` (default `3000`),
170
+ * mounts the `/mcp` route, and applies any custom Express middleware
171
+ * registered via {@link Skybridge.use} / {@link Skybridge.useOnError}.
172
+ *
173
+ * On Cloudflare Workers / workerd, returns an object exposing `fetch` so
174
+ * the runtime can bridge incoming requests to the Node HTTP server. On
175
+ * Vercel (`VERCEL === "1"`), returns the Express app directly so the
176
+ * serverless function entry can call it as a `(req, res)` handler. On
177
+ * Node, returns `undefined` once listening. When the process was spawned
178
+ * with an IPC channel, the bound port is reported to the parent as
179
+ * `{ type: "skybridge:listening", port }`, the readiness signal test runners
180
+ * wait on instead of polling.
181
+ */
182
+ async run() {
183
+ if (process.env.VERCEL === "1") {
184
+ // createApp only reads httpServer inside its dev-only branch
185
+ // (viewsDevServer); under VERCEL=1 + NODE_ENV=production it's a
186
+ // bare object passed to satisfy the required parameter.
187
+ const httpServer = http.createServer();
188
+ await createApp({
189
+ app: this,
190
+ httpServer,
191
+ onMcpError: this.onMcpError,
192
+ errorMiddleware: this.errorMiddleware,
193
+ });
194
+ return this.expressApp;
195
+ }
196
+ const httpServer = http.createServer();
197
+ const mcpHandler = buildMcpHandler(this, this.onMcpError);
198
+ await createApp({
199
+ app: this,
200
+ httpServer,
201
+ mcpHandler,
202
+ onMcpError: this.onMcpError,
203
+ errorMiddleware: this.errorMiddleware,
204
+ });
205
+ httpServer.on("request", this.expressApp);
206
+ const intendedPort = parseInt(process.env.__PORT ?? "3000", 10);
207
+ await new Promise((resolve, reject) => {
208
+ httpServer.on("error", (error) => {
209
+ console.error("Failed to start server:", error);
210
+ reject(error);
211
+ });
212
+ httpServer.listen(intendedPort, () => {
213
+ resolve();
214
+ });
215
+ });
216
+ const { port } = httpServer.address();
217
+ process.send?.({ type: "skybridge:listening", port });
218
+ // On workerd, bridge the Node http server to a Workers fetch handler.
219
+ // The specifier is held in a variable to sidestep tsc's module resolution
220
+ // (`cloudflare:node` only exists under wrangler/workerd).
221
+ if (typeof navigator !== "undefined" &&
222
+ navigator.userAgent === "Cloudflare-Workers") {
223
+ const cloudflareNode = "cloudflare:node";
224
+ const { httpServerHandler } = await import(cloudflareNode);
225
+ return httpServerHandler({ port });
226
+ }
227
+ const shutdown = () => {
228
+ // Drop both handlers so a second signal falls through to Node's default
229
+ // (force-quit on a second Ctrl+C while drain is hanging).
230
+ process.off("SIGTERM", shutdown);
231
+ process.off("SIGINT", shutdown);
232
+ mcpHandler.close().catch(() => { });
233
+ httpServer.close(() => process.exit(0));
234
+ // Force exit if connections don't drain in time so the port is still
235
+ // released promptly (e.g. for nodemon restarts).
236
+ setTimeout(() => process.exit(0), 3000).unref();
237
+ };
238
+ process.on("SIGTERM", shutdown);
239
+ process.on("SIGINT", shutdown);
240
+ return undefined;
241
+ }
242
+ buildServer() {
243
+ const server = new McpServer(this.serverInfo, this.serverOptions, { skills: this.skills, oauth: this.oauthEnabled });
244
+ if (this.resolveResourceMetadataUrl) {
245
+ server.setResourceMetadataUrlResolver(this.resolveResourceMetadataUrl);
246
+ }
247
+ const startedAt = performance.now();
248
+ const built = this.handler(server, this.config);
249
+ const elapsed = performance.now() - startedAt;
250
+ if (typeof built.then === "function") {
251
+ throw new Error("The Skybridge handler must be synchronous — it runs on every request. Load config or secrets in `setup` instead and read them from the handler's second argument.");
252
+ }
253
+ if (elapsed > SLOW_HANDLER_THRESHOLD_MS && !this.slowHandlerWarned) {
254
+ this.slowHandlerWarned = true;
255
+ console.warn(`The Skybridge handler took ${Math.round(elapsed)}ms — it runs on every request, so this cost is paid per request. Move expensive work into \`setup\`, whose result is passed to the handler.`);
256
+ }
257
+ return built;
258
+ }
259
+ instrumentHandlers(server, middleware) {
260
+ const entries = [
261
+ ...middleware,
262
+ ...this.mcpMiddlewareEntries,
263
+ ...server.protocolMiddlewareEntries(),
264
+ ];
265
+ const { requestHandlers, notificationHandlers } = getHandlerMaps(server.server);
266
+ const instrumentMap = (map, isNotification) => {
267
+ for (const [method, handler] of map) {
268
+ map.set(method, buildMiddlewareChain(method, isNotification, handler, entries));
269
+ }
270
+ const originalSet = map.set.bind(map);
271
+ map.set = (method, handler) => originalSet(method, buildMiddlewareChain(method, isNotification, handler, entries));
272
+ };
273
+ instrumentMap(requestHandlers, false);
274
+ instrumentMap(notificationHandlers, true);
275
+ }
276
+ }
277
+ //# sourceMappingURL=app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/server/app.ts"],"names":[],"mappings":"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","sourcesContent":["import http from \"node:http\";\nimport type { AddressInfo } from \"node:net\";\nimport type {\n Implementation,\n Server as SdkServer,\n ServerOptions,\n} from \"@modelcontextprotocol/server\";\nimport type { ErrorRequestHandler, Express, RequestHandler } from \"express\";\nimport type { OAuthConfig, OAuthProvider } from \"./auth/index.js\";\nimport { type ResourceMetadataUrlResolver, setupOAuth } from \"./auth/setup.js\";\nimport type { ExtraClaims } from \"./auth.js\";\nimport { buildMcpHandler, createApp, createBaseApp } from \"./express.js\";\nimport { registerMonitoring } from \"./metric.js\";\nimport type { McpMiddlewareEntry } from \"./middleware.js\";\nimport { buildMiddlewareChain, getHandlerMaps } from \"./middleware.js\";\nimport {\n type JsonOptions,\n McpServer,\n type McpServerTypes,\n type ToolDef,\n} from \"./server.js\";\n\nconst SLOW_HANDLER_THRESHOLD_MS = 50;\n\ntype ErrorMiddlewareConfig = {\n path?: string;\n handlers: ErrorRequestHandler[];\n};\n\n/**\n * The `handler` field of {@link SkybridgeConfig}: builds the app's MCP\n * surface. Runs again for **every incoming request**, on a fresh\n * {@link McpServer}, so keep it to registration: pools, clients and any other\n * one-time work belong in `setup`, whose result is the second argument. It\n * must **return** the chained server so `typeof app` carries the registered\n * tool types.\n *\n * @typeParam TConfig - What `setup` resolved to, passed as the second argument.\n */\nexport type SkybridgeHandler<\n TTools extends Record<string, ToolDef>,\n TConfig,\n TAuthExtra extends ExtraClaims,\n> = (\n server: McpServer<Record<never, ToolDef>, TAuthExtra>,\n config: TConfig,\n) => McpServer<TTools, TAuthExtra>;\n\n/**\n * What the `oauth` field accepts: an {@link OAuthConfig}, a provider\n * (`oauth: auth0Provider(...)`), or a function of the `setup` result returning\n * either. Providers and functions resolve once, at {@link Skybridge.run} or on\n * the first request, never at module import. Anything asynchronous belongs in\n * a provider's `resolve`.\n */\nexport type SkybridgeOAuthInput<TConfig, TExtra extends ExtraClaims> =\n | OAuthConfig<TExtra>\n | OAuthProvider<TExtra>\n | ((config: TConfig) => OAuthConfig<TExtra> | OAuthProvider<TExtra>);\n\nasync function resolveOAuthInput<TConfig, TExtra extends ExtraClaims>(\n input: SkybridgeOAuthInput<TConfig, TExtra> | undefined,\n config: TConfig,\n): Promise<OAuthConfig<TExtra> | undefined> {\n const resolved = typeof input === \"function\" ? input(config) : input;\n return resolved && \"resolve\" in resolved\n ? await resolved.resolve()\n : resolved;\n}\n\n/**\n * Everything a Skybridge app needs in one bag: the MCP implementation info\n * (`name`, `version`, …), the SDK's {@link ServerOptions} (`capabilities`,\n * `instructions`, …), the Express and skills options, and the app's behavior\n * (`setup`, `oauth`, `handler`).\n *\n * All type parameters are inferred from the value: the config from `setup`,\n * the auth claims from `oauth`, and the tool registry from the server\n * `handler` returns.\n */\nexport type SkybridgeConfig<\n TTools extends Record<string, ToolDef> = Record<never, ToolDef>,\n TConfig = undefined,\n TAuthExtra extends ExtraClaims = ExtraClaims,\n> = Implementation &\n ServerOptions & {\n /** Options for the built-in `express.json()` middleware, e.g. `{ limit: \"10mb\" }`. */\n json?: JsonOptions;\n /**\n * @experimental Serve Agent Skills from `src/skills` over MCP (SEP-2640).\n * API may change.\n */\n skills?: boolean;\n /**\n * Replaces the error reporter of the built-in `/mcp` handler. Receives every\n * error the MCP SDK handler or its Node adapter reports: rejected requests,\n * including the `UnsupportedProtocolVersionError` the default skips, and\n * failures to build or serve the per-request server. Reporting only: the\n * response does not change. It can run before the response is sent, so keep\n * it fast and synchronous. Default: `console.error` for every error the MCP\n * SDK handler reports, except `UnsupportedProtocolVersionError`. Node\n * adapter errors are not logged by default.\n */\n onMcpError?: (error: Error) => void;\n /**\n * Loads whatever the app needs up front (remote config, secrets, datasets,\n * …). Runs **once** — at {@link Skybridge.run} or on the first request,\n * never at module import — and its awaited return value is passed to an\n * `oauth` function and to `handler` as the second argument.\n */\n setup?: () => TConfig;\n /**\n * Resource-server OAuth. When set, mounts the well-known metadata routes\n * and bearer auth on `/mcp`, and the verifier's claims type\n * `extra.http.authInfo.extra` in tool handlers.\n */\n oauth?: SkybridgeOAuthInput<Awaited<TConfig>, TAuthExtra>;\n /** Registers the MCP surface, per request. See {@link SkybridgeHandler}. */\n handler: SkybridgeHandler<TTools, Awaited<TConfig>, TAuthExtra>;\n };\n\n/**\n * A Skybridge app: the HTTP surface (Express, OAuth metadata, the `/mcp`\n * route) plus a handler that builds the MCP server for each request.\n *\n * The handler runs for every request, so tools, resources, prompts and views\n * are always registered on the instance that serves the request. Anything in\n * the handler body other than registration therefore runs per request too.\n * Anything asynchronous the app needs (remote config, secrets, …) goes in\n * `setup`, which runs once and feeds the handler's second argument.\n *\n * @example\n * ```ts\n * export const app = new Skybridge({\n * name: \"my-app\",\n * version: \"1.0.0\",\n * setup: async () => loadConfig(),\n * oauth: (config) => descopeProvider({ url: config.mcpServerUrl }),\n * handler: (server, config) =>\n * server.registerTool(\n * {\n * name: \"search\",\n * inputSchema: { query: z.string() },\n * view: { component: \"search\" },\n * },\n * async ({ query }) => ({ content: `Results for ${query}` }),\n * ),\n * });\n *\n * export type AppType = typeof app;\n * ```\n *\n * @see https://docs.skybridge.tech/api-reference/mcp-server\n */\nexport class Skybridge<\n TTools extends Record<string, ToolDef> = Record<never, ToolDef>,\n TConfig = undefined,\n TAuthExtra extends ExtraClaims = ExtraClaims,\n> {\n declare readonly $types: McpServerTypes<TTools>;\n private readonly serverInfo: Implementation;\n private readonly serverOptions: ServerOptions;\n private readonly skills?: boolean;\n private oauthEnabled = false;\n private readonly handler: SkybridgeHandler<\n TTools,\n Awaited<TConfig>,\n TAuthExtra\n >;\n private readonly setup?: () => TConfig;\n private readonly oauthInput?: SkybridgeOAuthInput<\n Awaited<TConfig>,\n TAuthExtra\n >;\n private config?: Awaited<TConfig>;\n private readonly expressApp: Express;\n private readonly onMcpError?: (error: Error) => void;\n private readonly errorMiddleware: ErrorMiddlewareConfig[] = [];\n private readonly mcpMiddlewareEntries: McpMiddlewareEntry[] = [];\n private resolveResourceMetadataUrl?: ResourceMetadataUrlResolver;\n private readyPromise?: Promise<void>;\n private slowHandlerWarned = false;\n\n constructor({\n name,\n title,\n version,\n description,\n icons,\n websiteUrl,\n json,\n skills,\n onMcpError,\n setup,\n oauth,\n handler,\n ...serverOptions\n }: SkybridgeConfig<TTools, TConfig, TAuthExtra>) {\n this.serverInfo = { name, title, version, description, icons, websiteUrl };\n this.serverOptions = serverOptions;\n this.skills = skills;\n this.onMcpError = onMcpError;\n this.setup = setup;\n this.oauthInput = oauth;\n this.handler = handler;\n this.expressApp = createBaseApp(json);\n registerMonitoring(this);\n }\n\n /**\n * Resolve `setup` and `oauth`, then wire OAuth onto the Express app. Runs\n * once; every entry point that needs a built server awaits it, and a failed\n * attempt is retried on the next call.\n *\n * @internal\n */\n ready(): Promise<void> {\n this.readyPromise ??= (async () => {\n if (this.setup) {\n this.config = await this.setup();\n }\n const oauth = await resolveOAuthInput(\n this.oauthInput,\n this.config as Awaited<TConfig>,\n );\n this.oauthEnabled = Boolean(oauth);\n const sample = this.buildServer();\n if (oauth) {\n this.resolveResourceMetadataUrl = setupOAuth(\n this.expressApp,\n oauth,\n sample.securitySchemesByTool,\n );\n }\n })().catch((error) => {\n this.readyPromise = undefined;\n throw error;\n });\n return this.readyPromise;\n }\n\n /**\n * The underlying Express app. Use this to extend the HTTP server with\n * custom routes, middleware, or settings — e.g.\n * `app.express.get(\"/health\", ...)`.\n *\n * `express.json()` is pre-applied — tune it via the `json` config field,\n * e.g. `new Skybridge({ name, version, json: { limit: \"10mb\" }, handler })`.\n * Register your handlers before `run()`; after `run()`, dev-mode middleware,\n * the `/mcp` route, and the default error handler are appended in that order.\n *\n * Note: Alpic Cloud only routes traffic to `/mcp` — custom routes work\n * locally and on self-hosted deployments.\n */\n get express(): Express {\n return this.expressApp;\n }\n\n /**\n * Build a fresh server for one stateless HTTP request, as\n * `createMcpHandler`'s factory contract requires: the handler runs again so\n * the SDK's handler closures belong to the instance whose protocol era it\n * stamps. Sharing one instance's handler maps instead would bind them to an\n * instance that is never marked, pinning every request to the 2025 codec and\n * letting concurrent callers overwrite each other's negotiated version.\n *\n * Awaits `setup` and the `oauth` input on first use. Additional middleware\n * wraps the app pipeline for this instance only; it is not registered on the app.\n *\n * @internal\n */\n async createServerInstance(\n middleware: McpMiddlewareEntry[] = [],\n ): Promise<SdkServer> {\n await this.ready();\n const server = this.buildServer();\n this.instrumentHandlers(server, middleware);\n return server.server;\n }\n\n /** Register outer MCP middleware on this app, in registration order. @internal */\n withMcpMiddleware(entry: McpMiddlewareEntry): this {\n this.mcpMiddlewareEntries.push(entry);\n return this;\n }\n\n /**\n * Connect a Skybridge app to an MCP transport. Use this when you're\n * embedding Skybridge in a host that already manages its own transport\n * (e.g. stdio for desktop apps); for HTTP, prefer {@link Skybridge.run}\n * which sets the transport up for you.\n */\n async connect(transport: Parameters<SdkServer[\"connect\"]>[0]): Promise<void> {\n const instance = await this.createServerInstance();\n await instance.connect(transport);\n }\n\n /**\n * Register Express middleware on the underlying app. Mirrors `app.use` —\n * pass handlers directly or a path-prefixed handler list. Register before\n * {@link Skybridge.run}; ordering matches Express.\n *\n * Note: Alpic Cloud only routes traffic to `/mcp`. Custom paths work\n * locally and on self-hosted deployments.\n */\n use(...handlers: RequestHandler[]): this;\n use(path: string, ...handlers: RequestHandler[]): this;\n use(\n pathOrHandler: string | RequestHandler,\n ...handlers: RequestHandler[]\n ): this {\n // Branching is load-bearing: Express's `app.use` overloads can't be\n // resolved against a `string | RequestHandler` union, so we narrow.\n if (typeof pathOrHandler === \"string\") {\n this.expressApp.use(pathOrHandler, ...handlers);\n } else {\n this.expressApp.use(pathOrHandler, ...handlers);\n }\n return this;\n }\n\n /**\n * Register Express error-handling middleware to run after the built-in\n * `/mcp` route (or your custom route). Use this to log or transform errors\n * thrown by tool handlers before the default error handler responds.\n *\n * @example\n * ```ts\n * app.useOnError((err, _req, _res, next) => {\n * logger.error(err);\n * next(err);\n * });\n * ```\n */\n useOnError(...handlers: ErrorRequestHandler[]): this;\n useOnError(path: string, ...handlers: ErrorRequestHandler[]): this;\n useOnError(\n pathOrHandler: string | ErrorRequestHandler,\n ...handlers: ErrorRequestHandler[]\n ): this {\n if (typeof pathOrHandler === \"string\") {\n this.errorMiddleware.push({ path: pathOrHandler, handlers });\n } else {\n this.errorMiddleware.push({ handlers: [pathOrHandler, ...handlers] });\n }\n return this;\n }\n\n /**\n * Start the HTTP server. Listens on `process.env.__PORT` (default `3000`),\n * mounts the `/mcp` route, and applies any custom Express middleware\n * registered via {@link Skybridge.use} / {@link Skybridge.useOnError}.\n *\n * On Cloudflare Workers / workerd, returns an object exposing `fetch` so\n * the runtime can bridge incoming requests to the Node HTTP server. On\n * Vercel (`VERCEL === \"1\"`), returns the Express app directly so the\n * serverless function entry can call it as a `(req, res)` handler. On\n * Node, returns `undefined` once listening. When the process was spawned\n * with an IPC channel, the bound port is reported to the parent as\n * `{ type: \"skybridge:listening\", port }`, the readiness signal test runners\n * wait on instead of polling.\n */\n async run(): Promise<\n { fetch: (...args: unknown[]) => unknown } | Express | undefined\n > {\n if (process.env.VERCEL === \"1\") {\n // createApp only reads httpServer inside its dev-only branch\n // (viewsDevServer); under VERCEL=1 + NODE_ENV=production it's a\n // bare object passed to satisfy the required parameter.\n const httpServer = http.createServer();\n await createApp({\n app: this,\n httpServer,\n onMcpError: this.onMcpError,\n errorMiddleware: this.errorMiddleware,\n });\n return this.expressApp;\n }\n\n const httpServer = http.createServer();\n const mcpHandler = buildMcpHandler(this, this.onMcpError);\n\n await createApp({\n app: this,\n httpServer,\n mcpHandler,\n onMcpError: this.onMcpError,\n errorMiddleware: this.errorMiddleware,\n });\n\n httpServer.on(\"request\", this.expressApp);\n const intendedPort = parseInt(process.env.__PORT ?? \"3000\", 10);\n await new Promise<void>((resolve, reject) => {\n httpServer.on(\"error\", (error: Error) => {\n console.error(\"Failed to start server:\", error);\n reject(error);\n });\n httpServer.listen(intendedPort, () => {\n resolve();\n });\n });\n\n const { port } = httpServer.address() as AddressInfo;\n process.send?.({ type: \"skybridge:listening\", port });\n\n // On workerd, bridge the Node http server to a Workers fetch handler.\n // The specifier is held in a variable to sidestep tsc's module resolution\n // (`cloudflare:node` only exists under wrangler/workerd).\n if (\n typeof navigator !== \"undefined\" &&\n navigator.userAgent === \"Cloudflare-Workers\"\n ) {\n const cloudflareNode = \"cloudflare:node\";\n const { httpServerHandler } = await import(cloudflareNode);\n return httpServerHandler({ port });\n }\n\n const shutdown = () => {\n // Drop both handlers so a second signal falls through to Node's default\n // (force-quit on a second Ctrl+C while drain is hanging).\n process.off(\"SIGTERM\", shutdown);\n process.off(\"SIGINT\", shutdown);\n mcpHandler.close().catch(() => {});\n httpServer.close(() => process.exit(0));\n // Force exit if connections don't drain in time so the port is still\n // released promptly (e.g. for nodemon restarts).\n setTimeout(() => process.exit(0), 3000).unref();\n };\n process.on(\"SIGTERM\", shutdown);\n process.on(\"SIGINT\", shutdown);\n return undefined;\n }\n\n private buildServer(): McpServer<TTools, TAuthExtra> {\n const server = new McpServer<Record<never, ToolDef>, TAuthExtra>(\n this.serverInfo,\n this.serverOptions,\n { skills: this.skills, oauth: this.oauthEnabled },\n );\n if (this.resolveResourceMetadataUrl) {\n server.setResourceMetadataUrlResolver(this.resolveResourceMetadataUrl);\n }\n const startedAt = performance.now();\n const built = this.handler(server, this.config as Awaited<TConfig>);\n const elapsed = performance.now() - startedAt;\n if (typeof (built as { then?: unknown }).then === \"function\") {\n throw new Error(\n \"The Skybridge handler must be synchronous — it runs on every request. Load config or secrets in `setup` instead and read them from the handler's second argument.\",\n );\n }\n if (elapsed > SLOW_HANDLER_THRESHOLD_MS && !this.slowHandlerWarned) {\n this.slowHandlerWarned = true;\n console.warn(\n `The Skybridge handler took ${Math.round(elapsed)}ms — it runs on every request, so this cost is paid per request. Move expensive work into \\`setup\\`, whose result is passed to the handler.`,\n );\n }\n return built;\n }\n\n private instrumentHandlers(\n server: McpServer<Record<never, ToolDef>, TAuthExtra>,\n middleware: McpMiddlewareEntry[],\n ): void {\n const entries = [\n ...middleware,\n ...this.mcpMiddlewareEntries,\n ...server.protocolMiddlewareEntries(),\n ];\n const { requestHandlers, notificationHandlers } = getHandlerMaps(\n server.server,\n );\n\n const instrumentMap = (\n map: Map<string, (...args: unknown[]) => Promise<unknown>>,\n isNotification: boolean,\n ) => {\n for (const [method, handler] of map) {\n map.set(\n method,\n buildMiddlewareChain(method, isNotification, handler, entries),\n );\n }\n const originalSet = map.set.bind(map);\n map.set = (\n method: string,\n handler: (...args: unknown[]) => Promise<unknown>,\n ) =>\n originalSet(\n method,\n buildMiddlewareChain(method, isNotification, handler, entries),\n );\n };\n\n instrumentMap(requestHandlers, false);\n instrumentMap(notificationHandlers, true);\n }\n}\n"]}