skybridge 0.0.0-dev.8eed82d → 0.0.0-dev.8f4a954

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 (231) hide show
  1. package/README.md +8 -8
  2. package/dist/cli/detect-port.js.map +1 -1
  3. package/dist/cli/header.js.map +1 -1
  4. package/dist/cli/run-command.js.map +1 -1
  5. package/dist/cli/telemetry.js.map +1 -1
  6. package/dist/cli/tunnel-control-server.d.ts +9 -0
  7. package/dist/cli/tunnel-control-server.js +31 -0
  8. package/dist/cli/tunnel-control-server.js.map +1 -0
  9. package/dist/cli/tunnel-control-server.test.js +39 -0
  10. package/dist/cli/tunnel-control-server.test.js.map +1 -0
  11. package/dist/cli/tunnel-handler.d.ts +3 -0
  12. package/dist/cli/tunnel-handler.js +48 -0
  13. package/dist/cli/tunnel-handler.js.map +1 -0
  14. package/dist/cli/tunnel-handler.test.js +105 -0
  15. package/dist/cli/tunnel-handler.test.js.map +1 -0
  16. package/dist/cli/tunnel.d.ts +57 -0
  17. package/dist/cli/tunnel.js +154 -0
  18. package/dist/cli/tunnel.js.map +1 -0
  19. package/dist/cli/tunnel.test.js +190 -0
  20. package/dist/cli/tunnel.test.js.map +1 -0
  21. package/dist/cli/types.js.map +1 -1
  22. package/dist/cli/use-execute-steps.js.map +1 -1
  23. package/dist/cli/use-messages.js.map +1 -1
  24. package/dist/cli/use-nodemon.js +11 -2
  25. package/dist/cli/use-nodemon.js.map +1 -1
  26. package/dist/cli/use-open-browser.d.ts +1 -0
  27. package/dist/cli/use-open-browser.js +44 -0
  28. package/dist/cli/use-open-browser.js.map +1 -0
  29. package/dist/cli/use-tunnel.d.ts +1 -1
  30. package/dist/cli/use-tunnel.js +102 -68
  31. package/dist/cli/use-tunnel.js.map +1 -1
  32. package/dist/cli/use-typescript-check.d.ts +1 -0
  33. package/dist/cli/use-typescript-check.js +41 -6
  34. package/dist/cli/use-typescript-check.js.map +1 -1
  35. package/dist/commands/build.js +62 -7
  36. package/dist/commands/build.js.map +1 -1
  37. package/dist/commands/dev.d.ts +1 -0
  38. package/dist/commands/dev.js +33 -2
  39. package/dist/commands/dev.js.map +1 -1
  40. package/dist/commands/start.js.map +1 -1
  41. package/dist/commands/telemetry/disable.js.map +1 -1
  42. package/dist/commands/telemetry/enable.js.map +1 -1
  43. package/dist/commands/telemetry/status.js.map +1 -1
  44. package/dist/server/asset-base-url-transform-plugin.js +1 -1
  45. package/dist/server/asset-base-url-transform-plugin.js.map +1 -1
  46. package/dist/server/asset-base-url-transform-plugin.test.js +29 -0
  47. package/dist/server/asset-base-url-transform-plugin.test.js.map +1 -1
  48. package/dist/server/content-helpers.js.map +1 -1
  49. package/dist/server/content-helpers.test.js +1 -1
  50. package/dist/server/content-helpers.test.js.map +1 -1
  51. package/dist/server/express.d.ts +1 -5
  52. package/dist/server/express.js +34 -10
  53. package/dist/server/express.js.map +1 -1
  54. package/dist/server/express.test.js +249 -71
  55. package/dist/server/express.test.js.map +1 -1
  56. package/dist/server/index.d.ts +2 -2
  57. package/dist/server/index.js +1 -1
  58. package/dist/server/index.js.map +1 -1
  59. package/dist/server/inferUtilityTypes.d.ts +6 -6
  60. package/dist/server/inferUtilityTypes.js.map +1 -1
  61. package/dist/server/metric.js.map +1 -1
  62. package/dist/server/middleware.js.map +1 -1
  63. package/dist/server/middleware.test-d.js.map +1 -1
  64. package/dist/server/middleware.test.js.map +1 -1
  65. package/dist/server/server.d.ts +90 -40
  66. package/dist/server/server.js +150 -77
  67. package/dist/server/server.js.map +1 -1
  68. package/dist/server/templateHelper.d.ts +5 -7
  69. package/dist/server/templateHelper.js +3 -22
  70. package/dist/server/templateHelper.js.map +1 -1
  71. package/dist/server/templates.generated.d.ts +4 -0
  72. package/dist/server/templates.generated.js +47 -0
  73. package/dist/server/templates.generated.js.map +1 -0
  74. package/dist/server/tunnel-proxy-router.d.ts +7 -0
  75. package/dist/server/tunnel-proxy-router.js +110 -0
  76. package/dist/server/tunnel-proxy-router.js.map +1 -0
  77. package/dist/server/tunnel-proxy-router.test.js +229 -0
  78. package/dist/server/tunnel-proxy-router.test.js.map +1 -0
  79. package/dist/server/viewsDevServer.d.ts +14 -0
  80. package/dist/server/{widgetsDevServer.js → viewsDevServer.js} +17 -5
  81. package/dist/server/viewsDevServer.js.map +1 -0
  82. package/dist/test/utils.d.ts +7 -7
  83. package/dist/test/utils.js +21 -21
  84. package/dist/test/utils.js.map +1 -1
  85. package/dist/test/view.test.d.ts +1 -0
  86. package/dist/test/{widget.test.js → view.test.js} +128 -59
  87. package/dist/test/view.test.js.map +1 -0
  88. package/dist/version.js +1 -3
  89. package/dist/version.js.map +1 -1
  90. package/dist/web/bridges/apps-sdk/adaptor.d.ts +5 -3
  91. package/dist/web/bridges/apps-sdk/adaptor.js +19 -4
  92. package/dist/web/bridges/apps-sdk/adaptor.js.map +1 -1
  93. package/dist/web/bridges/apps-sdk/bridge.js.map +1 -1
  94. package/dist/web/bridges/apps-sdk/index.js.map +1 -1
  95. package/dist/web/bridges/apps-sdk/types.d.ts +8 -1
  96. package/dist/web/bridges/apps-sdk/types.js.map +1 -1
  97. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js.map +1 -1
  98. package/dist/web/bridges/get-adaptor.js.map +1 -1
  99. package/dist/web/bridges/index.js.map +1 -1
  100. package/dist/web/bridges/mcp-app/adaptor.d.ts +9 -7
  101. package/dist/web/bridges/mcp-app/adaptor.js +33 -30
  102. package/dist/web/bridges/mcp-app/adaptor.js.map +1 -1
  103. package/dist/web/bridges/mcp-app/bridge.js.map +1 -1
  104. package/dist/web/bridges/mcp-app/index.js.map +1 -1
  105. package/dist/web/bridges/mcp-app/types.js.map +1 -1
  106. package/dist/web/bridges/mcp-app/use-mcp-app-context.js.map +1 -1
  107. package/dist/web/bridges/mcp-app/use-mcp-app-context.test.js.map +1 -1
  108. package/dist/web/bridges/types.d.ts +17 -9
  109. package/dist/web/bridges/types.js.map +1 -1
  110. package/dist/web/bridges/use-host-context.js.map +1 -1
  111. package/dist/web/components/modal-provider.js +1 -1
  112. package/dist/web/components/modal-provider.js.map +1 -1
  113. package/dist/web/create-store.js +9 -9
  114. package/dist/web/create-store.js.map +1 -1
  115. package/dist/web/create-store.test.js +14 -16
  116. package/dist/web/create-store.test.js.map +1 -1
  117. package/dist/web/data-llm.d.ts +1 -1
  118. package/dist/web/data-llm.js +3 -3
  119. package/dist/web/data-llm.js.map +1 -1
  120. package/dist/web/data-llm.test.js +22 -22
  121. package/dist/web/data-llm.test.js.map +1 -1
  122. package/dist/web/generate-helpers.d.ts +14 -14
  123. package/dist/web/generate-helpers.js +14 -14
  124. package/dist/web/generate-helpers.js.map +1 -1
  125. package/dist/web/generate-helpers.test-d.js +26 -26
  126. package/dist/web/generate-helpers.test-d.js.map +1 -1
  127. package/dist/web/generate-helpers.test.js.map +1 -1
  128. package/dist/web/helpers/state.d.ts +2 -2
  129. package/dist/web/helpers/state.js +11 -11
  130. package/dist/web/helpers/state.js.map +1 -1
  131. package/dist/web/helpers/state.test.js +9 -9
  132. package/dist/web/helpers/state.test.js.map +1 -1
  133. package/dist/web/hooks/index.d.ts +3 -1
  134. package/dist/web/hooks/index.js +3 -1
  135. package/dist/web/hooks/index.js.map +1 -1
  136. package/dist/web/hooks/test/utils.js.map +1 -1
  137. package/dist/web/hooks/use-call-tool.js.map +1 -1
  138. package/dist/web/hooks/use-call-tool.test-d.js.map +1 -1
  139. package/dist/web/hooks/use-call-tool.test.js +0 -4
  140. package/dist/web/hooks/use-call-tool.test.js.map +1 -1
  141. package/dist/web/hooks/use-display-mode.js.map +1 -1
  142. package/dist/web/hooks/use-display-mode.test-d.js.map +1 -1
  143. package/dist/web/hooks/use-display-mode.test.js.map +1 -1
  144. package/dist/web/hooks/use-files.js.map +1 -1
  145. package/dist/web/hooks/use-files.test.js.map +1 -1
  146. package/dist/web/hooks/use-layout.js.map +1 -1
  147. package/dist/web/hooks/use-layout.test.js.map +1 -1
  148. package/dist/web/hooks/use-open-external.js.map +1 -1
  149. package/dist/web/hooks/use-open-external.test.js.map +1 -1
  150. package/dist/web/hooks/use-request-close.d.ts +2 -0
  151. package/dist/web/hooks/use-request-close.js +8 -0
  152. package/dist/web/hooks/use-request-close.js.map +1 -0
  153. package/dist/web/hooks/use-request-close.test.d.ts +1 -0
  154. package/dist/web/hooks/use-request-close.test.js +52 -0
  155. package/dist/web/hooks/use-request-close.test.js.map +1 -0
  156. package/dist/web/hooks/use-request-modal.d.ts +1 -1
  157. package/dist/web/hooks/use-request-modal.js +4 -4
  158. package/dist/web/hooks/use-request-modal.js.map +1 -1
  159. package/dist/web/hooks/use-request-modal.test.js +1 -1
  160. package/dist/web/hooks/use-request-modal.test.js.map +1 -1
  161. package/dist/web/hooks/use-request-size.d.ts +3 -0
  162. package/dist/web/hooks/use-request-size.js +8 -0
  163. package/dist/web/hooks/use-request-size.js.map +1 -0
  164. package/dist/web/hooks/use-request-size.test.d.ts +1 -0
  165. package/dist/web/hooks/use-request-size.test.js +65 -0
  166. package/dist/web/hooks/use-request-size.test.js.map +1 -0
  167. package/dist/web/hooks/use-send-follow-up-message.d.ts +2 -1
  168. package/dist/web/hooks/use-send-follow-up-message.js +2 -2
  169. package/dist/web/hooks/use-send-follow-up-message.js.map +1 -1
  170. package/dist/web/hooks/use-set-open-in-app-url.js.map +1 -1
  171. package/dist/web/hooks/use-set-open-in-app-url.test.js.map +1 -1
  172. package/dist/web/hooks/use-tool-info.js.map +1 -1
  173. package/dist/web/hooks/use-tool-info.test-d.js.map +1 -1
  174. package/dist/web/hooks/use-tool-info.test.js.map +1 -1
  175. package/dist/web/hooks/use-user.js.map +1 -1
  176. package/dist/web/hooks/use-user.test.js.map +1 -1
  177. package/dist/web/hooks/use-view-state.d.ts +4 -0
  178. package/dist/web/hooks/use-view-state.js +32 -0
  179. package/dist/web/hooks/use-view-state.js.map +1 -0
  180. package/dist/web/hooks/use-view-state.test.d.ts +1 -0
  181. package/dist/web/hooks/{use-widget-state.test.js → use-view-state.test.js} +17 -17
  182. package/dist/web/hooks/use-view-state.test.js.map +1 -0
  183. package/dist/web/index.d.ts +1 -3
  184. package/dist/web/index.js +1 -2
  185. package/dist/web/index.js.map +1 -1
  186. package/dist/web/mount-view.d.ts +1 -0
  187. package/dist/web/{mount-widget.js → mount-view.js} +2 -2
  188. package/dist/web/mount-view.js.map +1 -0
  189. package/dist/web/plugin/data-llm.test.js.map +1 -1
  190. package/dist/web/plugin/plugin.d.ts +1 -1
  191. package/dist/web/plugin/plugin.js +87 -41
  192. package/dist/web/plugin/plugin.js.map +1 -1
  193. package/dist/web/plugin/scan-views.d.ts +16 -0
  194. package/dist/web/plugin/scan-views.js +88 -0
  195. package/dist/web/plugin/scan-views.js.map +1 -0
  196. package/dist/web/plugin/scan-views.test.d.ts +1 -0
  197. package/dist/web/plugin/scan-views.test.js +99 -0
  198. package/dist/web/plugin/scan-views.test.js.map +1 -0
  199. package/dist/web/plugin/transform-data-llm.js.map +1 -1
  200. package/dist/web/plugin/transform-data-llm.test.js.map +1 -1
  201. package/dist/web/plugin/{validate-widget.js → validate-view.js} +1 -1
  202. package/dist/web/plugin/validate-view.js.map +1 -0
  203. package/dist/web/plugin/validate-view.test.d.ts +1 -0
  204. package/dist/web/plugin/{validate-widget.test.js → validate-view.test.js} +6 -6
  205. package/dist/web/plugin/validate-view.test.js.map +1 -0
  206. package/dist/web/proxy.js.map +1 -1
  207. package/dist/web/types.js.map +1 -1
  208. package/package.json +26 -20
  209. package/dist/server/templates/development.hbs +0 -12
  210. package/dist/server/templates/production.hbs +0 -6
  211. package/dist/server/widgetsDevServer.d.ts +0 -3
  212. package/dist/server/widgetsDevServer.js.map +0 -1
  213. package/dist/test/widget.test.js.map +0 -1
  214. package/dist/web/hooks/use-widget-state.d.ts +0 -4
  215. package/dist/web/hooks/use-widget-state.js +0 -32
  216. package/dist/web/hooks/use-widget-state.js.map +0 -1
  217. package/dist/web/hooks/use-widget-state.test.js.map +0 -1
  218. package/dist/web/mount-widget.d.ts +0 -1
  219. package/dist/web/mount-widget.js.map +0 -1
  220. package/dist/web/plugin/scan-widgets.d.ts +0 -8
  221. package/dist/web/plugin/scan-widgets.js +0 -68
  222. package/dist/web/plugin/scan-widgets.js.map +0 -1
  223. package/dist/web/plugin/scan-widgets.test.js +0 -96
  224. package/dist/web/plugin/scan-widgets.test.js.map +0 -1
  225. package/dist/web/plugin/validate-widget.js.map +0 -1
  226. package/dist/web/plugin/validate-widget.test.js.map +0 -1
  227. /package/dist/{test/widget.test.d.ts → cli/tunnel-control-server.test.d.ts} +0 -0
  228. /package/dist/{web/hooks/use-widget-state.test.d.ts → cli/tunnel-handler.test.d.ts} +0 -0
  229. /package/dist/{web/plugin/scan-widgets.test.d.ts → cli/tunnel.test.d.ts} +0 -0
  230. /package/dist/{web/plugin/validate-widget.test.d.ts → server/tunnel-proxy-router.test.d.ts} +0 -0
  231. /package/dist/web/plugin/{validate-widget.d.ts → validate-view.d.ts} +0 -0
@@ -1,30 +1,35 @@
1
- import type { McpUiToolMeta } from "@modelcontextprotocol/ext-apps";
1
+ import { type ServerOptions } from "@modelcontextprotocol/sdk/server/index.js";
2
2
  import { McpServer as McpServerBase } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import type { AnySchema, SchemaOutput, ZodRawShapeCompat } from "@modelcontextprotocol/sdk/server/zod-compat.js";
4
4
  import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
5
- import type { ContentBlock, ServerNotification, ServerRequest, ServerResult, ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
6
- import type { ErrorRequestHandler, RequestHandler } from "express";
5
+ import type { ContentBlock, Implementation, ServerNotification, ServerRequest, ServerResult, ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
6
+ import { type ErrorRequestHandler, type Express, type RequestHandler } from "express";
7
7
  import type { McpExtra, McpExtraFor, McpMethodString, McpMiddlewareFilter, McpMiddlewareFn, McpResultFor, McpTypedMiddlewareFn, McpWildcard } from "./middleware.js";
8
8
  export type ToolDef<TInput = unknown, TOutput = unknown, TResponseMetadata = unknown> = {
9
9
  input: TInput;
10
10
  output: TOutput;
11
11
  responseMetadata: TResponseMetadata;
12
12
  };
13
- export type WidgetHostType = "apps-sdk" | "mcp-app";
13
+ export type ViewHostType = "apps-sdk" | "mcp-app";
14
14
  export interface ViewCsp {
15
+ /** Origins for static assets (images, fonts, scripts, styles). */
15
16
  resourceDomains?: string[];
17
+ /** Origins the view may contact via fetch/XHR. */
16
18
  connectDomains?: string[];
19
+ /** Origins allowed for iframe embeds (opts into stricter app review). */
17
20
  frameDomains?: string[];
21
+ /** Origins that can receive openExternal redirects without the safe-link modal. */
18
22
  redirectDomains?: string[];
23
+ /** Origins allowed in `<base href>` tags (mcp-apps only). */
19
24
  baseUriDomains?: string[];
20
25
  }
21
- export interface WidgetNameRegistry {
26
+ export interface ViewNameRegistry {
22
27
  }
23
- export type WidgetName = keyof WidgetNameRegistry extends never ? string : keyof WidgetNameRegistry & string;
28
+ export type ViewName = keyof ViewNameRegistry & string;
24
29
  export interface ViewConfig {
25
- component: WidgetName;
30
+ component: ViewName;
26
31
  description?: string;
27
- hosts?: WidgetHostType[];
32
+ hosts?: ViewHostType[];
28
33
  prefersBorder?: boolean;
29
34
  domain?: string;
30
35
  csp?: ViewCsp;
@@ -37,19 +42,15 @@ export interface KnownToolMeta {
37
42
  }
38
43
  export type ToolMeta = KnownToolMeta & Record<string, unknown>;
39
44
  export type HandlerContent = string | ContentBlock | ContentBlock[];
40
- type OpenaiToolMeta = {
41
- "openai/outputTemplate": string;
42
- "openai/widgetAccessible"?: boolean;
43
- "openai/toolInvocation/invoking"?: string;
44
- "openai/toolInvocation/invoked"?: string;
45
- };
46
- /** @see https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx#resource-discovery */
47
- type McpAppsToolMeta = {
48
- ui: McpUiToolMeta;
49
- };
50
- type InternalToolMeta = Partial<OpenaiToolMeta & McpAppsToolMeta>;
51
45
  /**
52
46
  * Type-level marker interface for cross-package type inference.
47
+ *
48
+ * Consumers infer tool types via the structural `$types` property rather than
49
+ * the `McpServer` class generic, because class-generic inference breaks when
50
+ * `McpServer` comes from different package installations (e.g. a consumer
51
+ * with its own `skybridge` dep vs. the in-tree workspace version).
52
+ *
53
+ * Inspired by tRPC's `_def` pattern and Hono's type markers.
53
54
  */
54
55
  export interface McpServerTypes<TTools extends Record<string, ToolDef>> {
55
56
  readonly tools: TTools;
@@ -75,7 +76,7 @@ type ExtractMeta<T> = [Extract<T, {
75
76
  type AddTool<TTools, TName extends string, TInput extends ZodRawShapeCompat, TOutput, TResponseMetadata = unknown> = McpServer<TTools & {
76
77
  [K in TName]: ToolDef<ShapeOutput<TInput>, TOutput, TResponseMetadata>;
77
78
  }>;
78
- interface UnifiedToolConfig<TInput extends ZodRawShapeCompat | AnySchema> {
79
+ interface ToolConfig<TInput extends ZodRawShapeCompat | AnySchema> {
79
80
  name: string;
80
81
  title?: string;
81
82
  description?: string;
@@ -86,61 +87,110 @@ interface UnifiedToolConfig<TInput extends ZodRawShapeCompat | AnySchema> {
86
87
  _meta?: ToolMeta;
87
88
  }
88
89
  type ToolHandler<TInput extends ZodRawShapeCompat, TReturn extends {
89
- content: HandlerContent;
90
+ content?: HandlerContent;
90
91
  } = {
91
- content: HandlerContent;
92
+ content?: HandlerContent;
92
93
  }> = (args: ShapeOutput<TInput>, extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => TReturn | Promise<TReturn>;
93
- export declare function normalizeContent(content: HandlerContent): ContentBlock[];
94
+ export declare function normalizeContent(content: HandlerContent | undefined): ContentBlock[];
94
95
  interface McpServerBaseOmitted extends Omit<McpServerBase, "registerTool" | "connect"> {
95
96
  }
96
97
  declare const McpServerBaseOmitted: new (...args: ConstructorParameters<typeof McpServerBase>) => McpServerBaseOmitted;
97
98
  export declare class McpServer<TTools extends Record<string, ToolDef> = Record<never, ToolDef>> extends McpServerBaseOmitted {
98
99
  readonly $types: McpServerTypes<TTools>;
99
- private express?;
100
- private customMiddleware;
100
+ /**
101
+ * The underlying Express app. Use this to extend the HTTP server with
102
+ * custom routes, middleware, or settings — e.g.
103
+ * `server.express.get("/health", ...)`.
104
+ *
105
+ * `express.json()` is pre-applied. Register your handlers before `run()`;
106
+ * after `run()`, dev-mode middleware, the `/mcp` route, and the default
107
+ * error handler are appended in that order.
108
+ *
109
+ * Note: Alpic Cloud only routes traffic to `/mcp` — custom routes work
110
+ * locally and on self-hosted deployments.
111
+ */
112
+ readonly express: Express;
101
113
  private customErrorMiddleware;
102
114
  private mcpMiddlewareEntries;
103
115
  private mcpMiddlewareApplied;
104
- private claimedWidgets;
116
+ private claimedViews;
117
+ private viteManifest;
118
+ private readonly serverInfo;
119
+ private readonly serverOptions?;
120
+ constructor(serverInfo: Implementation, options?: ServerOptions);
105
121
  use(...handlers: RequestHandler[]): this;
106
122
  use(path: string, ...handlers: RequestHandler[]): this;
107
123
  useOnError(...handlers: ErrorRequestHandler[]): this;
108
124
  useOnError(path: string, ...handlers: ErrorRequestHandler[]): this;
125
+ /** Register MCP protocol-level middleware (catch-all). */
109
126
  mcpMiddleware(handler: McpMiddlewareFn): this;
127
+ /** Register MCP protocol-level middleware for all requests (`extra` is `McpExtra`). */
110
128
  mcpMiddleware(filter: "request", handler: (request: {
111
129
  method: string;
112
130
  params: Record<string, unknown>;
113
131
  }, extra: McpExtra, next: () => Promise<ServerResult>) => Promise<unknown> | unknown): this;
132
+ /** Register MCP protocol-level middleware for all notifications (`extra` is `undefined`). */
114
133
  mcpMiddleware(filter: "notification", handler: (request: {
115
134
  method: string;
116
135
  params: Record<string, unknown>;
117
136
  }, extra: undefined, next: () => Promise<undefined>) => Promise<unknown> | unknown): this;
137
+ /**
138
+ * Register MCP protocol-level middleware for an exact method.
139
+ * Narrows `params`, `extra`, and `next()` result based on the method string.
140
+ */
118
141
  mcpMiddleware<M extends McpMethodString>(filter: M, handler: McpTypedMiddlewareFn<M>): this;
142
+ /**
143
+ * Register MCP protocol-level middleware for a wildcard pattern (e.g. `"tools/*"`).
144
+ * `next()` returns the union of result types for matching methods.
145
+ */
119
146
  mcpMiddleware<W extends McpWildcard>(filter: W, handler: (request: {
120
147
  method: string;
121
148
  params: Record<string, unknown>;
122
149
  }, extra: McpExtraFor<W>, next: () => Promise<McpResultFor<W>>) => Promise<unknown> | unknown): this;
150
+ /**
151
+ * Register MCP protocol-level middleware with a method filter.
152
+ * Filter can be an exact method (`"tools/call"`), wildcard (`"tools/*"`),
153
+ * category (`"request"` | `"notification"`), or an array of those.
154
+ */
123
155
  mcpMiddleware(filter: McpMiddlewareFilter, handler: McpMiddlewareFn): this;
124
156
  private applyMcpMiddleware;
125
157
  connect(transport: Parameters<typeof McpServerBase.prototype.connect>[0]): Promise<void>;
126
- run(): Promise<void>;
127
- /** @internal */
128
- enforceOneToolPerWidget(component: string, toolName: string): void;
129
- /** @internal */
130
- registerViewResources(toolName: string, view: ViewConfig, toolMeta: InternalToolMeta): void;
131
- private registerWidgetResource;
132
- /** @internal */
133
- wrapHandlerWithViewUUID<InputArgs extends ZodRawShapeCompat>(cb: ToolHandler<InputArgs>): ToolHandler<InputArgs>;
134
- /** @internal */
135
- wrapHandlerWithContentNormalization<InputArgs extends ZodRawShapeCompat>(cb: ToolHandler<InputArgs>): ToolHandler<InputArgs>;
136
- private lookupWidgetFile;
158
+ /**
159
+ * Per-request stateless connect. The SDK's `Protocol` only allows one
160
+ * transport per instance, so we can't reuse this `McpServer` across
161
+ * concurrent requests. The SDK's idiomatic fix is a `() => McpServer`
162
+ * factory, but that would break Skybridge's singleton API — so instead
163
+ * we build a fresh underlying `Server` per request and share the main
164
+ * server's handler maps by reference. The cast is unavoidable: there's
165
+ * no public API to inject handler maps. `getHandlerMaps` validates the
166
+ * read side and fails fast on SDK field renames.
167
+ */
168
+ connectStatelessTransport(transport: Parameters<typeof McpServerBase.prototype.connect>[0]): Promise<void>;
169
+ run(): Promise<{
170
+ fetch: (...args: unknown[]) => unknown;
171
+ } | undefined>;
172
+ private enforceOneToolPerView;
173
+ private registerViewResources;
174
+ private registerViewResource;
175
+ private wrapHandler;
176
+ private computeViewVersionParam;
177
+ private lookupViewFile;
137
178
  private lookupDistFile;
179
+ /**
180
+ * Inject the Vite manifest as a value rather than letting `readManifest()`
181
+ * load it from disk. Required for runtimes without a usable filesystem
182
+ * (Cloudflare Workers, etc.) — the user's `skybridge build` emits the
183
+ * manifest as a JS module which the entry imports and passes here.
184
+ */
185
+ setViteManifest(manifest: Record<string, {
186
+ file: string;
187
+ }>): this;
138
188
  private readManifest;
139
189
  registerTool<TName extends string, InputArgs extends ZodRawShapeCompat, TReturn extends {
140
- content: HandlerContent;
141
- }>(config: UnifiedToolConfig<InputArgs> & {
190
+ content?: HandlerContent;
191
+ }>(config: ToolConfig<InputArgs> & {
142
192
  name: TName;
143
193
  }, cb: ToolHandler<InputArgs, TReturn>): AddTool<TTools, TName, InputArgs, ExtractStructuredContent<TReturn>, ExtractMeta<TReturn>>;
144
- registerTool<InputArgs extends ZodRawShapeCompat>(config: UnifiedToolConfig<InputArgs>, cb: ToolHandler<InputArgs>): this;
194
+ registerTool<InputArgs extends ZodRawShapeCompat>(config: ToolConfig<InputArgs>, cb: ToolHandler<InputArgs>): this;
145
195
  }
146
196
  export {};
@@ -2,8 +2,10 @@ import crypto from "node:crypto";
2
2
  import { readFileSync } from "node:fs";
3
3
  import http from "node:http";
4
4
  import path from "node:path";
5
+ import { Server as SdkServer, } from "@modelcontextprotocol/sdk/server/index.js";
5
6
  import { McpServer as McpServerBase } from "@modelcontextprotocol/sdk/server/mcp.js";
6
7
  import { mergeWith, union } from "es-toolkit";
8
+ import express, {} from "express";
7
9
  import { createApp } from "./express.js";
8
10
  import { createMiddlewareEntry } from "./metric.js";
9
11
  import { buildMiddlewareChain, getHandlerMaps } from "./middleware.js";
@@ -15,10 +17,10 @@ const mergeWithUnion = (target, source) => {
15
17
  }
16
18
  });
17
19
  };
18
- // ---------------------------------------------------------------------------
19
- // Content normalization
20
- // ---------------------------------------------------------------------------
21
20
  export function normalizeContent(content) {
21
+ if (content === undefined) {
22
+ return [];
23
+ }
22
24
  if (typeof content === "string") {
23
25
  return [{ type: "text", text: content }];
24
26
  }
@@ -29,23 +31,41 @@ export function normalizeContent(content) {
29
31
  }
30
32
  const McpServerBaseOmitted = McpServerBase;
31
33
  export class McpServer extends McpServerBaseOmitted {
34
+ /**
35
+ * The underlying Express app. Use this to extend the HTTP server with
36
+ * custom routes, middleware, or settings — e.g.
37
+ * `server.express.get("/health", ...)`.
38
+ *
39
+ * `express.json()` is pre-applied. Register your handlers before `run()`;
40
+ * after `run()`, dev-mode middleware, the `/mcp` route, and the default
41
+ * error handler are appended in that order.
42
+ *
43
+ * Note: Alpic Cloud only routes traffic to `/mcp` — custom routes work
44
+ * locally and on self-hosted deployments.
45
+ */
32
46
  express;
33
- customMiddleware = [];
34
47
  customErrorMiddleware = [];
35
48
  mcpMiddlewareEntries = [];
36
49
  mcpMiddlewareApplied = false;
37
- claimedWidgets = new Map();
50
+ claimedViews = new Map();
51
+ viteManifest = null;
52
+ serverInfo;
53
+ serverOptions;
54
+ constructor(serverInfo, options) {
55
+ super(serverInfo, options);
56
+ this.serverInfo = serverInfo;
57
+ this.serverOptions = options;
58
+ this.express = express();
59
+ this.express.use(express.json());
60
+ }
38
61
  use(pathOrHandler, ...handlers) {
62
+ // Branching is load-bearing: Express's `app.use` overloads can't be
63
+ // resolved against a `string | RequestHandler` union, so we narrow.
39
64
  if (typeof pathOrHandler === "string") {
40
- this.customMiddleware.push({
41
- path: pathOrHandler,
42
- handlers,
43
- });
65
+ this.express.use(pathOrHandler, ...handlers);
44
66
  }
45
67
  else {
46
- this.customMiddleware.push({
47
- handlers: [pathOrHandler, ...handlers],
48
- });
68
+ this.express.use(pathOrHandler, ...handlers);
49
69
  }
50
70
  return this;
51
71
  }
@@ -111,49 +131,84 @@ export class McpServer extends McpServerBaseOmitted {
111
131
  this.applyMcpMiddleware();
112
132
  return McpServerBase.prototype.connect.call(this, transport);
113
133
  }
134
+ /**
135
+ * Per-request stateless connect. The SDK's `Protocol` only allows one
136
+ * transport per instance, so we can't reuse this `McpServer` across
137
+ * concurrent requests. The SDK's idiomatic fix is a `() => McpServer`
138
+ * factory, but that would break Skybridge's singleton API — so instead
139
+ * we build a fresh underlying `Server` per request and share the main
140
+ * server's handler maps by reference. The cast is unavoidable: there's
141
+ * no public API to inject handler maps. `getHandlerMaps` validates the
142
+ * read side and fails fast on SDK field renames.
143
+ */
144
+ async connectStatelessTransport(transport) {
145
+ this.applyMcpMiddleware();
146
+ const { requestHandlers, notificationHandlers } = getHandlerMaps(this.server);
147
+ const fresh = new SdkServer(this.serverInfo, this.serverOptions);
148
+ const target = fresh;
149
+ target._requestHandlers = requestHandlers;
150
+ target._notificationHandlers = notificationHandlers;
151
+ await fresh.connect(transport);
152
+ }
114
153
  async run() {
115
154
  this.applyMcpMiddleware();
116
155
  const httpServer = http.createServer();
117
- if (!this.express) {
118
- this.express = await createApp({
119
- mcpServer: this,
120
- httpServer,
121
- customMiddleware: this.customMiddleware,
122
- errorMiddleware: this.customErrorMiddleware,
123
- });
124
- }
156
+ await createApp({
157
+ mcpServer: this,
158
+ httpServer,
159
+ errorMiddleware: this.customErrorMiddleware,
160
+ });
125
161
  httpServer.on("request", this.express);
126
- return new Promise((resolve, reject) => {
162
+ const port = parseInt(process.env.__PORT ?? "3000", 10);
163
+ await new Promise((resolve, reject) => {
127
164
  httpServer.on("error", (error) => {
128
165
  console.error("Failed to start server:", error);
129
166
  reject(error);
130
167
  });
131
- const port = parseInt(process.env.__PORT ?? "3000", 10);
132
168
  httpServer.listen(port, () => {
133
169
  resolve();
134
170
  });
135
171
  });
172
+ // On workerd, bridge the Node http server to a Workers fetch handler.
173
+ // The specifier is held in a variable to sidestep tsc's module resolution
174
+ // (`cloudflare:node` only exists under wrangler/workerd).
175
+ if (typeof navigator !== "undefined" &&
176
+ navigator.userAgent === "Cloudflare-Workers") {
177
+ const cloudflareNode = "cloudflare:node";
178
+ const { httpServerHandler } = await import(cloudflareNode);
179
+ return httpServerHandler({ port });
180
+ }
181
+ const shutdown = () => {
182
+ // Drop both handlers so a second signal falls through to Node's default
183
+ // (force-quit on a second Ctrl+C while drain is hanging).
184
+ process.off("SIGTERM", shutdown);
185
+ process.off("SIGINT", shutdown);
186
+ httpServer.close(() => process.exit(0));
187
+ // Force exit if connections don't drain in time so the port is still
188
+ // released promptly (e.g. for nodemon restarts).
189
+ setTimeout(() => process.exit(0), 3000).unref();
190
+ };
191
+ process.on("SIGTERM", shutdown);
192
+ process.on("SIGINT", shutdown);
193
+ return undefined;
136
194
  }
137
- // registerTool is defined via interface merging + prototype assignment below
138
- // to avoid TypeScript override signature incompatibility with the base class.
139
- // -------------------------------------------------------------------------
140
- // View (widget) resource registration
141
- // -------------------------------------------------------------------------
142
- /** @internal */
143
- enforceOneToolPerWidget(component, toolName) {
144
- const existingTool = this.claimedWidgets.get(component);
195
+ enforceOneToolPerView(component, toolName) {
196
+ const existingTool = this.claimedViews.get(component);
145
197
  if (existingTool) {
146
- throw new Error(`skybridge: widget "${component}" is already used by tool "${existingTool}". Tool "${toolName}" cannot also reference it — each widget backs exactly one tool.`);
198
+ throw new Error(`skybridge: view "${component}" is already used by tool "${existingTool}". Tool "${toolName}" cannot also reference it — each view backs exactly one tool.`);
147
199
  }
148
- this.claimedWidgets.set(component, toolName);
200
+ this.claimedViews.set(component, toolName);
149
201
  }
150
- /** @internal */
151
202
  registerViewResources(toolName, view, toolMeta) {
152
203
  const hosts = view.hosts ?? ["apps-sdk", "mcp-app"];
204
+ // Append a content-derived version param so hosts (e.g. ChatGPT) bust
205
+ // their cache when the bundle changes, but keep the URI stable across
206
+ // `tools/list` calls when the bundle hasn't changed.
207
+ const versionParam = this.computeViewVersionParam(view.component);
153
208
  if (hosts.includes("apps-sdk")) {
154
- const widgetConfig = {
209
+ const viewResource = {
155
210
  hostType: "apps-sdk",
156
- uri: `ui://widgets/apps-sdk/${view.component}.html`,
211
+ uri: `ui://views/apps-sdk/${view.component}.html${versionParam}`,
157
212
  mimeType: "text/html+skybridge",
158
213
  buildContentMeta: ({ resourceDomains, connectDomains, domain }, overrides) => {
159
214
  const defaults = {
@@ -183,17 +238,17 @@ export class McpServer extends McpServerBaseOmitted {
183
238
  return base;
184
239
  },
185
240
  };
186
- this.registerWidgetResource({
241
+ this.registerViewResource({
187
242
  name: toolName,
188
- widgetConfig,
243
+ viewResource,
189
244
  view,
190
245
  });
191
- toolMeta["openai/outputTemplate"] = widgetConfig.uri;
246
+ toolMeta["openai/outputTemplate"] = viewResource.uri;
192
247
  }
193
248
  if (hosts.includes("mcp-app")) {
194
- const widgetConfig = {
249
+ const viewResource = {
195
250
  hostType: "mcp-app",
196
- uri: `ui://widgets/ext-apps/${view.component}.html`,
251
+ uri: `ui://views/ext-apps/${view.component}.html${versionParam}`,
197
252
  mimeType: "text/html;profile=mcp-app",
198
253
  buildContentMeta: ({ resourceDomains, connectDomains, domain, baseUriDomains }, overrides) => {
199
254
  const defaults = {
@@ -241,19 +296,19 @@ export class McpServer extends McpServerBaseOmitted {
241
296
  return base;
242
297
  },
243
298
  };
244
- this.registerWidgetResource({
299
+ this.registerViewResource({
245
300
  name: toolName,
246
- widgetConfig,
301
+ viewResource,
247
302
  view,
248
303
  });
249
304
  // @ts-expect-error - For backwards compatibility with Claude current implementation of the specs
250
- toolMeta["ui/resourceUri"] = widgetConfig.uri;
251
- toolMeta.ui = { resourceUri: widgetConfig.uri };
305
+ toolMeta["ui/resourceUri"] = viewResource.uri;
306
+ toolMeta.ui = { resourceUri: viewResource.uri };
252
307
  }
253
308
  }
254
- registerWidgetResource({ name, widgetConfig, view, }) {
255
- const { hostType, uri: widgetUri, mimeType, buildContentMeta, } = widgetConfig;
256
- this.registerResource(name, widgetUri, { description: view.description }, async (uri, extra) => {
309
+ registerViewResource({ name, viewResource, view, }) {
310
+ const { hostType, uri: viewUri, mimeType, buildContentMeta } = viewResource;
311
+ this.registerResource(name, viewUri, { description: view.description }, async (uri, extra) => {
257
312
  const isProduction = process.env.NODE_ENV === "production";
258
313
  const isClaude = extra?.requestInfo?.headers?.["user-agent"] === "Claude-User";
259
314
  const headers = extra?.requestInfo?.headers || {};
@@ -286,13 +341,13 @@ export class McpServer extends McpServerBaseOmitted {
286
341
  ? templateHelper.renderProduction({
287
342
  hostType,
288
343
  serverUrl,
289
- widgetFile: this.lookupWidgetFile(view.component),
344
+ viewFile: this.lookupViewFile(view.component),
290
345
  styleFile: this.lookupDistFile("style.css") ?? "",
291
346
  })
292
347
  : templateHelper.renderDevelopment({
293
348
  hostType,
294
349
  serverUrl,
295
- widgetName: view.component,
350
+ viewName: view.component,
296
351
  });
297
352
  const connectDomains = [serverUrl];
298
353
  if (!isProduction) {
@@ -304,6 +359,8 @@ export class McpServer extends McpServerBaseOmitted {
304
359
  if (isClaude) {
305
360
  const pathname = extra?.requestInfo?.url?.pathname ?? "";
306
361
  const rawUrl = header("x-alpic-forwarded-url") ?? `${serverUrl}${pathname}`;
362
+ // Strip a lone trailing slash so the hash matches the connector URL
363
+ // as registered with Claude (which has no trailing slash on bare origins).
307
364
  const url = rawUrl.endsWith("/") ? rawUrl.slice(0, -1) : rawUrl;
308
365
  const hash = crypto
309
366
  .createHash("sha256")
@@ -325,50 +382,68 @@ export class McpServer extends McpServerBaseOmitted {
325
382
  };
326
383
  });
327
384
  }
328
- // -------------------------------------------------------------------------
329
- // Handler wrapping
330
- // -------------------------------------------------------------------------
331
- /** @internal */
332
- wrapHandlerWithViewUUID(cb) {
385
+ wrapHandler(cb, { attachViewUUID }) {
333
386
  return async (args, extra) => {
334
387
  const result = await cb(args, extra);
335
388
  return {
336
389
  ...result,
337
390
  content: normalizeContent(result.content),
338
- _meta: {
339
- ...result._meta,
340
- viewUUID: crypto.randomUUID(),
341
- },
391
+ ...(attachViewUUID && {
392
+ _meta: {
393
+ ...result._meta,
394
+ viewUUID: crypto.randomUUID(),
395
+ },
396
+ }),
342
397
  };
343
398
  };
344
399
  }
345
- /** @internal */
346
- wrapHandlerWithContentNormalization(cb) {
347
- return async (args, extra) => {
348
- const result = await cb(args, extra);
349
- return {
350
- ...result,
351
- content: normalizeContent(result.content),
352
- };
353
- };
400
+ computeViewVersionParam(viewName) {
401
+ if (process.env.NODE_ENV !== "production") {
402
+ return "";
403
+ }
404
+ try {
405
+ const viewFile = this.lookupViewFile(viewName);
406
+ const styleFile = this.lookupDistFile("style.css") ?? "";
407
+ const hash = crypto
408
+ .createHash("sha256")
409
+ .update(viewFile)
410
+ .update("\0")
411
+ .update(styleFile)
412
+ .digest("hex")
413
+ .slice(0, 8);
414
+ return `?v=${hash}`;
415
+ }
416
+ catch {
417
+ return "";
418
+ }
354
419
  }
355
- // -------------------------------------------------------------------------
356
- // Manifest helpers
357
- // -------------------------------------------------------------------------
358
- lookupWidgetFile(widgetName) {
420
+ lookupViewFile(viewName) {
359
421
  const manifest = this.readManifest();
360
422
  for (const entry of Object.values(manifest)) {
361
- if (entry?.isEntry && entry.name === widgetName && entry.file) {
423
+ if (entry?.isEntry && entry.name === viewName && entry.file) {
362
424
  return entry.file;
363
425
  }
364
426
  }
365
- throw new Error(`Widget "${widgetName}" not found in Vite manifest. Did the build complete successfully? Look for an entry with name "${widgetName}" in dist/assets/.vite/manifest.json.`);
427
+ throw new Error(`View "${viewName}" not found in Vite manifest. Did the build complete successfully? Look for an entry with name "${viewName}" in dist/assets/.vite/manifest.json.`);
366
428
  }
367
429
  lookupDistFile(key) {
368
430
  const manifest = this.readManifest();
369
431
  return manifest[key]?.file;
370
432
  }
433
+ /**
434
+ * Inject the Vite manifest as a value rather than letting `readManifest()`
435
+ * load it from disk. Required for runtimes without a usable filesystem
436
+ * (Cloudflare Workers, etc.) — the user's `skybridge build` emits the
437
+ * manifest as a JS module which the entry imports and passes here.
438
+ */
439
+ setViteManifest(manifest) {
440
+ this.viteManifest = manifest;
441
+ return this;
442
+ }
371
443
  readManifest() {
444
+ if (this.viteManifest) {
445
+ return this.viteManifest;
446
+ }
372
447
  return JSON.parse(readFileSync(path.join(process.cwd(), "dist", "assets", ".vite", "manifest.json"), "utf-8"));
373
448
  }
374
449
  registerTool(...args) {
@@ -382,12 +457,10 @@ export class McpServer extends McpServerBaseOmitted {
382
457
  const { name, view, _meta: userToolMeta, ...toolFields } = config;
383
458
  const toolMeta = { ...userToolMeta };
384
459
  if (view) {
385
- this.enforceOneToolPerWidget(view.component, name);
460
+ this.enforceOneToolPerView(view.component, name);
386
461
  this.registerViewResources(name, view, toolMeta);
387
462
  }
388
- const wrappedCb = view
389
- ? this.wrapHandlerWithViewUUID(cb)
390
- : this.wrapHandlerWithContentNormalization(cb);
463
+ const wrappedCb = this.wrapHandler(cb, { attachViewUUID: Boolean(view) });
391
464
  baseFn.call(this, name, { ...toolFields, _meta: toolMeta }, wrappedCb);
392
465
  return this;
393
466
  }