skybridge 0.0.0-dev.d2586b1 → 0.0.0-dev.d26b118

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 (157) hide show
  1. package/README.md +19 -9
  2. package/dist/cli/detect-port.d.ts +18 -0
  3. package/dist/cli/detect-port.js +61 -0
  4. package/dist/cli/detect-port.js.map +1 -0
  5. package/dist/cli/header.js +1 -1
  6. package/dist/cli/header.js.map +1 -1
  7. package/dist/cli/types.d.ts +5 -0
  8. package/dist/cli/types.js +2 -0
  9. package/dist/cli/types.js.map +1 -0
  10. package/dist/cli/use-execute-steps.d.ts +2 -1
  11. package/dist/cli/use-execute-steps.js +6 -1
  12. package/dist/cli/use-execute-steps.js.map +1 -1
  13. package/dist/cli/use-messages.d.ts +3 -0
  14. package/dist/cli/use-messages.js +11 -0
  15. package/dist/cli/use-messages.js.map +1 -0
  16. package/dist/cli/use-nodemon.d.ts +2 -0
  17. package/dist/cli/use-nodemon.js +71 -0
  18. package/dist/cli/use-nodemon.js.map +1 -0
  19. package/dist/cli/use-tunnel.d.ts +14 -0
  20. package/dist/cli/use-tunnel.js +97 -0
  21. package/dist/cli/use-tunnel.js.map +1 -0
  22. package/dist/cli/use-typescript-check.d.ts +8 -0
  23. package/dist/cli/use-typescript-check.js +59 -0
  24. package/dist/cli/use-typescript-check.js.map +1 -0
  25. package/dist/commands/build.js +30 -3
  26. package/dist/commands/build.js.map +1 -1
  27. package/dist/commands/dev.d.ts +3 -1
  28. package/dist/commands/dev.js +30 -21
  29. package/dist/commands/dev.js.map +1 -1
  30. package/dist/commands/start.d.ts +3 -1
  31. package/dist/commands/start.js +37 -15
  32. package/dist/commands/start.js.map +1 -1
  33. package/dist/server/asset-base-url-transform-plugin.d.ts +5 -6
  34. package/dist/server/asset-base-url-transform-plugin.js +8 -9
  35. package/dist/server/asset-base-url-transform-plugin.js.map +1 -1
  36. package/dist/server/asset-base-url-transform-plugin.test.js +12 -13
  37. package/dist/server/asset-base-url-transform-plugin.test.js.map +1 -1
  38. package/dist/server/content-helpers.d.ts +27 -0
  39. package/dist/server/content-helpers.js +40 -0
  40. package/dist/server/content-helpers.js.map +1 -0
  41. package/dist/server/express.d.ts +15 -0
  42. package/dist/server/express.js +77 -0
  43. package/dist/server/express.js.map +1 -0
  44. package/dist/server/express.test.d.ts +1 -0
  45. package/dist/server/express.test.js +252 -0
  46. package/dist/server/express.test.js.map +1 -0
  47. package/dist/server/index.d.ts +4 -2
  48. package/dist/server/index.js +2 -1
  49. package/dist/server/index.js.map +1 -1
  50. package/dist/server/metric.d.ts +14 -0
  51. package/dist/server/metric.js +62 -0
  52. package/dist/server/metric.js.map +1 -0
  53. package/dist/server/middleware.d.ts +124 -0
  54. package/dist/server/middleware.js +93 -0
  55. package/dist/server/middleware.js.map +1 -0
  56. package/dist/server/middleware.test-d.d.ts +1 -0
  57. package/dist/server/middleware.test-d.js +75 -0
  58. package/dist/server/middleware.test-d.js.map +1 -0
  59. package/dist/server/middleware.test.d.ts +1 -0
  60. package/dist/server/middleware.test.js +493 -0
  61. package/dist/server/middleware.test.js.map +1 -0
  62. package/dist/server/server.d.ts +108 -60
  63. package/dist/server/server.js +309 -54
  64. package/dist/server/server.js.map +1 -1
  65. package/dist/server/templates/development.hbs +1 -54
  66. package/dist/server/widgetsDevServer.d.ts +2 -11
  67. package/dist/server/widgetsDevServer.js +22 -29
  68. package/dist/server/widgetsDevServer.js.map +1 -1
  69. package/dist/test/utils.d.ts +6 -14
  70. package/dist/test/utils.js +35 -30
  71. package/dist/test/utils.js.map +1 -1
  72. package/dist/test/widget.test.js +298 -107
  73. package/dist/test/widget.test.js.map +1 -1
  74. package/dist/version.d.ts +1 -0
  75. package/dist/version.js +5 -0
  76. package/dist/version.js.map +1 -0
  77. package/dist/web/bridges/apps-sdk/adaptor.d.ts +7 -5
  78. package/dist/web/bridges/apps-sdk/adaptor.js +39 -16
  79. package/dist/web/bridges/apps-sdk/adaptor.js.map +1 -1
  80. package/dist/web/bridges/apps-sdk/bridge.d.ts +1 -1
  81. package/dist/web/bridges/apps-sdk/index.d.ts +1 -1
  82. package/dist/web/bridges/apps-sdk/index.js.map +1 -1
  83. package/dist/web/bridges/apps-sdk/types.d.ts +24 -13
  84. package/dist/web/bridges/apps-sdk/types.js.map +1 -1
  85. package/dist/web/bridges/mcp-app/adaptor.d.ts +16 -4
  86. package/dist/web/bridges/mcp-app/adaptor.js +121 -38
  87. package/dist/web/bridges/mcp-app/adaptor.js.map +1 -1
  88. package/dist/web/bridges/mcp-app/bridge.d.ts +13 -30
  89. package/dist/web/bridges/mcp-app/bridge.js +43 -196
  90. package/dist/web/bridges/mcp-app/bridge.js.map +1 -1
  91. package/dist/web/bridges/mcp-app/use-mcp-app-context.d.ts +5 -3
  92. package/dist/web/bridges/mcp-app/use-mcp-app-context.js +2 -2
  93. package/dist/web/bridges/mcp-app/use-mcp-app-context.js.map +1 -1
  94. package/dist/web/bridges/mcp-app/use-mcp-app-context.test.js +1 -41
  95. package/dist/web/bridges/mcp-app/use-mcp-app-context.test.js.map +1 -1
  96. package/dist/web/bridges/types.d.ts +15 -4
  97. package/dist/web/components/modal-provider.js +2 -4
  98. package/dist/web/components/modal-provider.js.map +1 -1
  99. package/dist/web/create-store.js +15 -1
  100. package/dist/web/create-store.js.map +1 -1
  101. package/dist/web/create-store.test.js +9 -4
  102. package/dist/web/create-store.test.js.map +1 -1
  103. package/dist/web/data-llm.test.js +11 -8
  104. package/dist/web/data-llm.test.js.map +1 -1
  105. package/dist/web/hooks/index.d.ts +1 -1
  106. package/dist/web/hooks/index.js.map +1 -1
  107. package/dist/web/hooks/test/utils.js +4 -0
  108. package/dist/web/hooks/test/utils.js.map +1 -1
  109. package/dist/web/hooks/use-display-mode.d.ts +3 -3
  110. package/dist/web/hooks/use-display-mode.js.map +1 -1
  111. package/dist/web/hooks/use-display-mode.test-d.d.ts +1 -0
  112. package/dist/web/hooks/use-display-mode.test-d.js +8 -0
  113. package/dist/web/hooks/use-display-mode.test-d.js.map +1 -0
  114. package/dist/web/hooks/use-files.d.ts +2 -1
  115. package/dist/web/hooks/use-files.js +1 -0
  116. package/dist/web/hooks/use-files.js.map +1 -1
  117. package/dist/web/hooks/use-files.test.js +27 -3
  118. package/dist/web/hooks/use-files.test.js.map +1 -1
  119. package/dist/web/hooks/use-layout.test.js +3 -3
  120. package/dist/web/hooks/use-layout.test.js.map +1 -1
  121. package/dist/web/hooks/use-open-external.d.ts +3 -1
  122. package/dist/web/hooks/use-open-external.js +1 -1
  123. package/dist/web/hooks/use-open-external.js.map +1 -1
  124. package/dist/web/hooks/use-open-external.test.js +26 -11
  125. package/dist/web/hooks/use-open-external.test.js.map +1 -1
  126. package/dist/web/hooks/use-request-modal.test.js +5 -1
  127. package/dist/web/hooks/use-request-modal.test.js.map +1 -1
  128. package/dist/web/hooks/use-set-open-in-app-url.test.js +5 -11
  129. package/dist/web/hooks/use-set-open-in-app-url.test.js.map +1 -1
  130. package/dist/web/hooks/use-tool-info.test.js +1 -1
  131. package/dist/web/hooks/use-tool-info.test.js.map +1 -1
  132. package/dist/web/hooks/use-user.js +18 -2
  133. package/dist/web/hooks/use-user.js.map +1 -1
  134. package/dist/web/hooks/use-user.test.js +29 -1
  135. package/dist/web/hooks/use-user.test.js.map +1 -1
  136. package/dist/web/hooks/use-widget-state.test.js +123 -7
  137. package/dist/web/hooks/use-widget-state.test.js.map +1 -1
  138. package/dist/web/index.d.ts +1 -0
  139. package/dist/web/index.js.map +1 -1
  140. package/dist/web/plugin/plugin.d.ts +4 -1
  141. package/dist/web/plugin/plugin.js +140 -17
  142. package/dist/web/plugin/plugin.js.map +1 -1
  143. package/dist/web/plugin/scan-widgets.d.ts +1 -0
  144. package/dist/web/plugin/scan-widgets.js +71 -0
  145. package/dist/web/plugin/scan-widgets.js.map +1 -0
  146. package/dist/web/plugin/transform-data-llm.js +1 -1
  147. package/dist/web/plugin/transform-data-llm.js.map +1 -1
  148. package/dist/web/plugin/validate-widget.d.ts +1 -0
  149. package/dist/web/plugin/validate-widget.js +9 -0
  150. package/dist/web/plugin/validate-widget.js.map +1 -0
  151. package/dist/web/plugin/validate-widget.test.d.ts +1 -0
  152. package/dist/web/plugin/validate-widget.test.js +24 -0
  153. package/dist/web/plugin/validate-widget.test.js.map +1 -0
  154. package/dist/web/proxy.js +0 -1
  155. package/dist/web/proxy.js.map +1 -1
  156. package/package.json +29 -24
  157. package/tsconfig.base.json +33 -0
@@ -1,43 +1,68 @@
1
- import type { McpUiResourceMeta } from "@modelcontextprotocol/ext-apps";
1
+ import type { McpUiToolMeta } from "@modelcontextprotocol/ext-apps";
2
2
  import { McpServer as McpServerBase, type RegisteredTool } 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 { CallToolResult, Resource, ServerNotification, ServerRequest, ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
5
+ import type { ContentBlock, ServerNotification, ServerRequest, ServerResult, ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
6
+ import type { ErrorRequestHandler, RequestHandler } from "express";
7
+ import type { McpExtra, McpExtraFor, McpMethodString, McpMiddlewareFilter, McpMiddlewareFn, McpResultFor, McpTypedMiddlewareFn, McpWildcard } from "./middleware.js";
6
8
  export type ToolDef<TInput = unknown, TOutput = unknown, TResponseMetadata = unknown> = {
7
9
  input: TInput;
8
10
  output: TOutput;
9
11
  responseMetadata: TResponseMetadata;
10
12
  };
11
- /**
12
- * Extended MCP Apps CSP with upcoming fields from ext-apps PR #158
13
- * and Skybridge-specific fields for OpenAI compatibility
14
- * @see https://github.com/modelcontextprotocol/ext-apps/pull/158
15
- */
16
- type ExtendedMcpUiResourceCsp = McpUiResourceMeta["csp"] & {
17
- /**
18
- * Origins that can receive openExternal redirects without safe-link modal (OpenAI-specific)
19
- * @see https://developers.openai.com/apps-sdk/reference#component-resource-_meta-fields
20
- */
21
- redirectDomains?: string[];
22
- };
23
- /** Extended MCP Apps resource metadata with upcoming CSP fields */
24
- type ExtendedMcpUiResourceMeta = Omit<McpUiResourceMeta, "csp"> & {
25
- csp?: ExtendedMcpUiResourceCsp;
26
- };
27
- /** User-provided resource configuration with optional CSP override */
28
- export type WidgetResourceMeta = {
29
- ui?: ExtendedMcpUiResourceMeta;
30
- } & Resource["_meta"];
31
13
  export type WidgetHostType = "apps-sdk" | "mcp-app";
32
- type McpServerOriginalResourceConfig = Omit<Resource, "uri" | "name" | "mimeType" | "_meta"> & {
33
- _meta?: WidgetResourceMeta;
34
- /** Restrict host types to a specific subset */
14
+ export interface ViewCsp {
15
+ resourceDomains?: string[];
16
+ connectDomains?: string[];
17
+ frameDomains?: string[];
18
+ redirectDomains?: string[];
19
+ baseUriDomains?: string[];
20
+ }
21
+ export interface WidgetNameRegistry {
22
+ }
23
+ export type WidgetName = keyof WidgetNameRegistry extends never ? string : keyof WidgetNameRegistry & string;
24
+ export interface ViewConfig {
25
+ component: WidgetName;
26
+ description?: string;
35
27
  hosts?: WidgetHostType[];
28
+ prefersBorder?: boolean;
29
+ domain?: string;
30
+ csp?: ViewCsp;
31
+ _meta?: Record<string, unknown>;
32
+ }
33
+ export interface KnownToolMeta {
34
+ "openai/widgetAccessible"?: boolean;
35
+ "openai/toolInvocation/invoking"?: string;
36
+ "openai/toolInvocation/invoked"?: string;
37
+ }
38
+ export type ToolMeta = KnownToolMeta & Record<string, unknown>;
39
+ export type HandlerContent = string | ContentBlock | ContentBlock[];
40
+ /** @see https://developers.openai.com/apps-sdk/reference#tool-descriptor-parameters */
41
+ type OpenaiToolMeta = {
42
+ "openai/outputTemplate": string;
43
+ "openai/widgetAccessible"?: boolean;
44
+ "openai/toolInvocation/invoking"?: string;
45
+ "openai/toolInvocation/invoked"?: string;
46
+ };
47
+ /** @see https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx#resource-discovery */
48
+ type McpAppsToolMeta = {
49
+ ui: McpUiToolMeta;
36
50
  };
37
- type McpServerOriginalToolConfig = Omit<Parameters<typeof McpServerBase.prototype.registerTool<ZodRawShapeCompat, ZodRawShapeCompat>>[1], "inputSchema" | "outputSchema">;
51
+ type InternalToolMeta = Partial<OpenaiToolMeta & McpAppsToolMeta>;
52
+ /**
53
+ * Type-level marker interface for cross-package type inference.
54
+ */
55
+ export interface McpServerTypes<TTools extends Record<string, ToolDef>> {
56
+ readonly tools: TTools;
57
+ }
38
58
  type Simplify<T> = {
39
59
  [K in keyof T]: T[K];
40
60
  };
61
+ type ShapeOutput<Shape extends ZodRawShapeCompat> = Simplify<{
62
+ [K in keyof Shape as undefined extends SchemaOutput<Shape[K]> ? never : K]: SchemaOutput<Shape[K]>;
63
+ } & {
64
+ [K in keyof Shape as undefined extends SchemaOutput<Shape[K]> ? K : never]?: SchemaOutput<Shape[K]>;
65
+ }>;
41
66
  type ExtractStructuredContent<T> = T extends {
42
67
  structuredContent: infer SC;
43
68
  } ? Simplify<SC> : never;
@@ -48,52 +73,75 @@ type ExtractMeta<T> = [Extract<T, {
48
73
  }> extends {
49
74
  _meta: infer M;
50
75
  } ? Simplify<M> : unknown;
51
- /**
52
- * Type-level marker interface for cross-package type inference.
53
- * This enables TypeScript to infer tool types across package boundaries
54
- * using structural typing on the $types property, rather than relying on
55
- * class generic inference which fails when McpServer comes from different
56
- * package installations.
57
- *
58
- * Inspired by tRPC's _def pattern and Hono's type markers.
59
- */
60
- export interface McpServerTypes<TTools extends Record<string, ToolDef>> {
61
- readonly tools: TTools;
62
- }
63
- type ShapeOutput<Shape extends ZodRawShapeCompat> = Simplify<{
64
- [K in keyof Shape as undefined extends SchemaOutput<Shape[K]> ? never : K]: SchemaOutput<Shape[K]>;
65
- } & {
66
- [K in keyof Shape as undefined extends SchemaOutput<Shape[K]> ? K : never]?: SchemaOutput<Shape[K]>;
67
- }>;
68
76
  type AddTool<TTools, TName extends string, TInput extends ZodRawShapeCompat, TOutput, TResponseMetadata = unknown> = McpServer<TTools & {
69
77
  [K in TName]: ToolDef<ShapeOutput<TInput>, TOutput, TResponseMetadata>;
70
78
  }>;
71
- type ToolConfig<TInput extends ZodRawShapeCompat | AnySchema> = {
79
+ interface UnifiedToolConfig<TInput extends ZodRawShapeCompat | AnySchema> {
80
+ name: string;
72
81
  title?: string;
73
82
  description?: string;
74
83
  inputSchema?: TInput;
75
84
  outputSchema?: ZodRawShapeCompat | AnySchema;
76
85
  annotations?: ToolAnnotations;
77
- _meta?: Record<string, unknown>;
78
- };
86
+ view?: ViewConfig;
87
+ _meta?: ToolMeta;
88
+ }
79
89
  type ToolHandler<TInput extends ZodRawShapeCompat, TReturn extends {
80
- content: CallToolResult["content"];
81
- } = CallToolResult> = (args: ShapeOutput<TInput>, extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => TReturn | Promise<TReturn>;
82
- export declare class McpServer<TTools extends Record<string, ToolDef> = Record<never, ToolDef>> extends McpServerBase {
90
+ content: HandlerContent;
91
+ } = {
92
+ content: HandlerContent;
93
+ }> = (args: ShapeOutput<TInput>, extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => TReturn | Promise<TReturn>;
94
+ export declare function normalizeContent(content: HandlerContent): ContentBlock[];
95
+ interface McpServerBaseOmitted extends Omit<McpServerBase, "registerTool" | "connect"> {
96
+ }
97
+ declare const McpServerBaseOmitted: new (...args: ConstructorParameters<typeof McpServerBase>) => McpServerBaseOmitted;
98
+ export declare class McpServer<TTools extends Record<string, ToolDef> = Record<never, ToolDef>> extends McpServerBaseOmitted {
83
99
  readonly $types: McpServerTypes<TTools>;
84
- registerWidget<TName extends string, TInput extends ZodRawShapeCompat, TReturn extends {
85
- content: CallToolResult["content"];
86
- }>(name: TName, resourceConfig: McpServerOriginalResourceConfig, toolConfig: McpServerOriginalToolConfig & {
87
- inputSchema?: TInput;
88
- outputSchema?: ZodRawShapeCompat | AnySchema;
89
- }, toolCallback: ToolHandler<TInput, TReturn>): AddTool<TTools, TName, TInput, ExtractStructuredContent<TReturn>, ExtractMeta<TReturn>>;
90
- registerTool<TName extends string, InputArgs extends ZodRawShapeCompat, TReturn extends {
91
- content: CallToolResult["content"];
92
- }>(name: TName, config: ToolConfig<InputArgs>, cb: ToolHandler<InputArgs, TReturn>): AddTool<TTools, TName, InputArgs, ExtractStructuredContent<TReturn>, ExtractMeta<TReturn>>;
93
- registerTool<InputArgs extends ZodRawShapeCompat>(name: string, config: ToolConfig<InputArgs>, cb: ToolHandler<InputArgs>): RegisteredTool;
100
+ private express?;
101
+ private customMiddleware;
102
+ private customErrorMiddleware;
103
+ private mcpMiddlewareEntries;
104
+ private mcpMiddlewareApplied;
105
+ private claimedWidgets;
106
+ use(...handlers: RequestHandler[]): this;
107
+ use(path: string, ...handlers: RequestHandler[]): this;
108
+ useOnError(...handlers: ErrorRequestHandler[]): this;
109
+ useOnError(path: string, ...handlers: ErrorRequestHandler[]): this;
110
+ mcpMiddleware(handler: McpMiddlewareFn): this;
111
+ mcpMiddleware(filter: "request", handler: (request: {
112
+ method: string;
113
+ params: Record<string, unknown>;
114
+ }, extra: McpExtra, next: () => Promise<ServerResult>) => Promise<unknown> | unknown): this;
115
+ mcpMiddleware(filter: "notification", handler: (request: {
116
+ method: string;
117
+ params: Record<string, unknown>;
118
+ }, extra: undefined, next: () => Promise<undefined>) => Promise<unknown> | unknown): this;
119
+ mcpMiddleware<M extends McpMethodString>(filter: M, handler: McpTypedMiddlewareFn<M>): this;
120
+ mcpMiddleware<W extends McpWildcard>(filter: W, handler: (request: {
121
+ method: string;
122
+ params: Record<string, unknown>;
123
+ }, extra: McpExtraFor<W>, next: () => Promise<McpResultFor<W>>) => Promise<unknown> | unknown): this;
124
+ mcpMiddleware(filter: McpMiddlewareFilter, handler: McpMiddlewareFn): this;
125
+ private applyMcpMiddleware;
126
+ connect(transport: Parameters<typeof McpServerBase.prototype.connect>[0]): Promise<void>;
127
+ run(): Promise<void>;
128
+ /** @internal */
129
+ enforceOneToolPerWidget(component: string, toolName: string): void;
130
+ /** @internal */
131
+ registerViewResources(toolName: string, view: ViewConfig, toolMeta: InternalToolMeta): void;
94
132
  private registerWidgetResource;
133
+ /** @internal */
134
+ wrapHandlerWithViewUUID<InputArgs extends ZodRawShapeCompat>(cb: ToolHandler<InputArgs>): ToolHandler<InputArgs>;
135
+ /** @internal */
136
+ wrapHandlerWithContentNormalization<InputArgs extends ZodRawShapeCompat>(cb: ToolHandler<InputArgs>): ToolHandler<InputArgs>;
137
+ private lookupWidgetFile;
95
138
  private lookupDistFile;
96
- private lookupDistFileWithIndexFallback;
97
139
  private readManifest;
140
+ registerTool<TName extends string, InputArgs extends ZodRawShapeCompat, TReturn extends {
141
+ content: HandlerContent;
142
+ }>(config: UnifiedToolConfig<InputArgs> & {
143
+ name: TName;
144
+ }, cb: ToolHandler<InputArgs, TReturn>): AddTool<TTools, TName, InputArgs, ExtractStructuredContent<TReturn>, ExtractMeta<TReturn>>;
145
+ registerTool<InputArgs extends ZodRawShapeCompat>(config: UnifiedToolConfig<InputArgs>, cb: ToolHandler<InputArgs>): RegisteredTool;
98
146
  }
99
147
  export {};
@@ -1,7 +1,12 @@
1
+ import crypto from "node:crypto";
1
2
  import { readFileSync } from "node:fs";
3
+ import http from "node:http";
2
4
  import path from "node:path";
3
5
  import { McpServer as McpServerBase, } from "@modelcontextprotocol/sdk/server/mcp.js";
4
6
  import { mergeWith, union } from "es-toolkit";
7
+ import { createApp } from "./express.js";
8
+ import { createMiddlewareEntry } from "./metric.js";
9
+ import { buildMiddlewareChain, getHandlerMaps } from "./middleware.js";
5
10
  import { templateHelper } from "./templateHelper.js";
6
11
  const mergeWithUnion = (target, source) => {
7
12
  return mergeWith(target, source, (targetVal, sourceVal) => {
@@ -10,111 +15,309 @@ const mergeWithUnion = (target, source) => {
10
15
  }
11
16
  });
12
17
  };
13
- export class McpServer extends McpServerBase {
14
- registerWidget(name, resourceConfig, toolConfig, toolCallback) {
15
- const userMeta = resourceConfig._meta;
16
- const toolMeta = {
17
- ...toolConfig._meta,
18
+ // ---------------------------------------------------------------------------
19
+ // Content normalization
20
+ // ---------------------------------------------------------------------------
21
+ export function normalizeContent(content) {
22
+ if (typeof content === "string") {
23
+ return [{ type: "text", text: content }];
24
+ }
25
+ if (Array.isArray(content)) {
26
+ return content;
27
+ }
28
+ return [content];
29
+ }
30
+ const McpServerBaseOmitted = McpServerBase;
31
+ export class McpServer extends McpServerBaseOmitted {
32
+ express;
33
+ customMiddleware = [];
34
+ customErrorMiddleware = [];
35
+ mcpMiddlewareEntries = [];
36
+ mcpMiddlewareApplied = false;
37
+ claimedWidgets = new Map();
38
+ use(pathOrHandler, ...handlers) {
39
+ if (typeof pathOrHandler === "string") {
40
+ this.customMiddleware.push({
41
+ path: pathOrHandler,
42
+ handlers,
43
+ });
44
+ }
45
+ else {
46
+ this.customMiddleware.push({
47
+ handlers: [pathOrHandler, ...handlers],
48
+ });
49
+ }
50
+ return this;
51
+ }
52
+ useOnError(pathOrHandler, ...handlers) {
53
+ if (typeof pathOrHandler === "string") {
54
+ this.customErrorMiddleware.push({ path: pathOrHandler, handlers });
55
+ }
56
+ else {
57
+ this.customErrorMiddleware.push({
58
+ handlers: [pathOrHandler, ...handlers],
59
+ });
60
+ }
61
+ return this;
62
+ }
63
+ mcpMiddleware(filterOrHandler,
64
+ // biome-ignore lint/suspicious/noExplicitAny: overloads narrow the handler type at call sites; implementation must accept all variants
65
+ maybeHandler) {
66
+ if (this.mcpMiddlewareApplied) {
67
+ throw new Error("Cannot register MCP middleware after run() or connect() has been called");
68
+ }
69
+ const handler = maybeHandler;
70
+ if (typeof filterOrHandler === "function") {
71
+ this.mcpMiddlewareEntries.push({
72
+ filter: null,
73
+ handler: filterOrHandler,
74
+ });
75
+ }
76
+ else if (handler) {
77
+ this.mcpMiddlewareEntries.push({
78
+ filter: filterOrHandler,
79
+ handler,
80
+ });
81
+ }
82
+ else {
83
+ throw new Error("mcpMiddleware requires a handler function when a filter is provided");
84
+ }
85
+ return this;
86
+ }
87
+ applyMcpMiddleware() {
88
+ if (this.mcpMiddlewareApplied) {
89
+ return;
90
+ }
91
+ this.mcpMiddlewareApplied = true;
92
+ const monitoringEntry = createMiddlewareEntry();
93
+ const entries = monitoringEntry
94
+ ? [monitoringEntry, ...this.mcpMiddlewareEntries]
95
+ : this.mcpMiddlewareEntries;
96
+ if (entries.length === 0) {
97
+ return;
98
+ }
99
+ const { requestHandlers, notificationHandlers } = getHandlerMaps(this.server);
100
+ const instrumentMap = (map, isNotification) => {
101
+ for (const [method, handler] of map) {
102
+ map.set(method, buildMiddlewareChain(method, isNotification, handler, entries));
103
+ }
104
+ const originalSet = map.set.bind(map);
105
+ map.set = (method, handler) => originalSet(method, buildMiddlewareChain(method, isNotification, handler, entries));
18
106
  };
19
- if (!resourceConfig.hosts || resourceConfig.hosts.includes("apps-sdk")) {
107
+ instrumentMap(requestHandlers, false);
108
+ instrumentMap(notificationHandlers, true);
109
+ }
110
+ async connect(transport) {
111
+ this.applyMcpMiddleware();
112
+ return McpServerBase.prototype.connect.call(this, transport);
113
+ }
114
+ async run() {
115
+ this.applyMcpMiddleware();
116
+ 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
+ }
125
+ httpServer.on("request", this.express);
126
+ return new Promise((resolve, reject) => {
127
+ httpServer.on("error", (error) => {
128
+ console.error("Failed to start server:", error);
129
+ reject(error);
130
+ });
131
+ const port = parseInt(process.env.__PORT ?? "3000", 10);
132
+ httpServer.listen(port, () => {
133
+ resolve();
134
+ });
135
+ });
136
+ }
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);
145
+ 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.`);
147
+ }
148
+ this.claimedWidgets.set(component, toolName);
149
+ }
150
+ /** @internal */
151
+ registerViewResources(toolName, view, toolMeta) {
152
+ const hosts = view.hosts ?? ["apps-sdk", "mcp-app"];
153
+ if (hosts.includes("apps-sdk")) {
20
154
  const widgetConfig = {
21
155
  hostType: "apps-sdk",
22
- uri: `ui://widgets/apps-sdk/${name}.html`,
156
+ uri: `ui://widgets/apps-sdk/${view.component}.html`,
23
157
  mimeType: "text/html+skybridge",
24
- buildContentMeta: ({ resourceDomains, connectDomains, domain }) => {
25
- const userUi = userMeta?.ui;
26
- const userCsp = userUi?.csp;
158
+ buildContentMeta: ({ resourceDomains, connectDomains, domain }, overrides) => {
27
159
  const defaults = {
28
160
  "openai/widgetCSP": {
29
161
  resource_domains: resourceDomains,
30
162
  connect_domains: connectDomains,
31
163
  },
32
164
  "openai/widgetDomain": domain,
33
- "openai/widgetDescription": toolConfig.description,
165
+ "openai/widgetDescription": view.description,
34
166
  };
35
- const fromUi = {
167
+ const fromView = {
36
168
  "openai/widgetCSP": {
37
- resource_domains: userCsp?.resourceDomains,
38
- connect_domains: userCsp?.connectDomains,
39
- frame_domains: userCsp?.frameDomains,
40
- redirect_domains: userCsp?.redirectDomains,
169
+ resource_domains: view.csp?.resourceDomains,
170
+ connect_domains: view.csp?.connectDomains,
171
+ frame_domains: view.csp?.frameDomains,
172
+ redirect_domains: view.csp?.redirectDomains,
41
173
  },
42
- "openai/widgetDomain": userUi?.domain,
43
- "openai/widgetPrefersBorder": userUi?.prefersBorder,
174
+ "openai/widgetDomain": view.domain,
175
+ "openai/widgetPrefersBorder": view.prefersBorder,
44
176
  };
45
- const directOpenaiKeys = Object.fromEntries(Object.entries(userMeta ?? {}).filter(([key]) => key.startsWith("openai/")));
46
- return mergeWithUnion(mergeWithUnion(defaults, fromUi), directOpenaiKeys);
177
+ const base = mergeWithUnion(mergeWithUnion(defaults, fromView), {
178
+ "openai/widgetDomain": overrides.domain,
179
+ });
180
+ if (view._meta) {
181
+ return { ...base, ...view._meta };
182
+ }
183
+ return base;
47
184
  },
48
185
  };
49
186
  this.registerWidgetResource({
50
- name,
187
+ name: toolName,
51
188
  widgetConfig,
52
- resourceConfig,
189
+ view,
53
190
  });
54
191
  toolMeta["openai/outputTemplate"] = widgetConfig.uri;
55
192
  }
56
- if (!resourceConfig.hosts || resourceConfig.hosts.includes("mcp-app")) {
193
+ if (hosts.includes("mcp-app")) {
57
194
  const widgetConfig = {
58
195
  hostType: "mcp-app",
59
- uri: `ui://widgets/ext-apps/${name}.html`,
196
+ uri: `ui://widgets/ext-apps/${view.component}.html`,
60
197
  mimeType: "text/html;profile=mcp-app",
61
- buildContentMeta: ({ resourceDomains, connectDomains, domain }) => {
198
+ buildContentMeta: ({ resourceDomains, connectDomains, domain, baseUriDomains }, overrides) => {
62
199
  const defaults = {
63
200
  ui: {
64
201
  csp: {
65
202
  resourceDomains,
66
203
  connectDomains,
204
+ baseUriDomains,
67
205
  },
68
206
  domain,
69
207
  },
70
208
  };
71
- return mergeWithUnion(defaults, { ui: userMeta?.ui });
209
+ const fromView = {
210
+ ui: {
211
+ ...(view.description && { description: view.description }),
212
+ ...(view.prefersBorder !== undefined && {
213
+ prefersBorder: view.prefersBorder,
214
+ }),
215
+ ...(view.domain && { domain: view.domain }),
216
+ csp: {
217
+ ...(view.csp?.resourceDomains && {
218
+ resourceDomains: view.csp.resourceDomains,
219
+ }),
220
+ ...(view.csp?.connectDomains && {
221
+ connectDomains: view.csp.connectDomains,
222
+ }),
223
+ ...(view.csp?.frameDomains && {
224
+ frameDomains: view.csp.frameDomains,
225
+ }),
226
+ ...(view.csp?.baseUriDomains && {
227
+ baseUriDomains: view.csp.baseUriDomains,
228
+ }),
229
+ ...(view.csp?.redirectDomains && {
230
+ redirectDomains: view.csp.redirectDomains,
231
+ }),
232
+ },
233
+ },
234
+ };
235
+ const base = mergeWithUnion(mergeWithUnion(defaults, fromView), {
236
+ ui: overrides,
237
+ });
238
+ if (view._meta) {
239
+ return { ...base, ...view._meta };
240
+ }
241
+ return base;
72
242
  },
73
243
  };
74
244
  this.registerWidgetResource({
75
- name,
245
+ name: toolName,
76
246
  widgetConfig,
77
- resourceConfig,
247
+ view,
78
248
  });
249
+ // @ts-expect-error - For backwards compatibility with Claude current implementation of the specs
250
+ toolMeta["ui/resourceUri"] = widgetConfig.uri;
79
251
  toolMeta.ui = { resourceUri: widgetConfig.uri };
80
252
  }
81
- this.registerTool(name, {
82
- ...toolConfig,
83
- _meta: toolMeta,
84
- }, toolCallback);
85
- return this;
86
- }
87
- registerTool(name, config, cb) {
88
- super.registerTool(name, config, cb);
89
- return this;
90
253
  }
91
- registerWidgetResource({ name, widgetConfig, resourceConfig, }) {
254
+ registerWidgetResource({ name, widgetConfig, view, }) {
92
255
  const { hostType, uri: widgetUri, mimeType, buildContentMeta, } = widgetConfig;
93
- this.registerResource(name, widgetUri, { ...resourceConfig, _meta: resourceConfig._meta }, async (uri, extra) => {
256
+ this.registerResource(name, widgetUri, { description: view.description }, async (uri, extra) => {
94
257
  const isProduction = process.env.NODE_ENV === "production";
95
- const useForwardedHost = process.env.SKYBRIDGE_USE_FORWARDED_HOST === "true";
96
- const serverUrl = isProduction || useForwardedHost
97
- ? `https://${extra?.requestInfo?.headers?.["x-forwarded-host"] ?? extra?.requestInfo?.headers?.host}`
98
- : "http://localhost:3000";
258
+ const isClaude = extra?.requestInfo?.headers?.["user-agent"] === "Claude-User";
259
+ const headers = extra?.requestInfo?.headers || {};
260
+ const header = (key) => {
261
+ const val = headers[key];
262
+ return Array.isArray(val) ? val[0] : val;
263
+ };
264
+ let serverUrl;
265
+ const forwardedHost = header("x-forwarded-host");
266
+ const origin = header("origin");
267
+ const host = header("host");
268
+ if (forwardedHost) {
269
+ const proto = header("x-forwarded-proto") || "https";
270
+ serverUrl = `${proto}://${forwardedHost}`;
271
+ }
272
+ else if (origin) {
273
+ serverUrl = origin;
274
+ }
275
+ else if (host) {
276
+ const proto = ["127.0.0.1:", "localhost:"].some((p) => host.startsWith(p))
277
+ ? "http"
278
+ : "https";
279
+ serverUrl = `${proto}://${host}`;
280
+ }
281
+ else {
282
+ const devPort = process.env.__PORT || "3000";
283
+ serverUrl = `http://localhost:${devPort}`;
284
+ }
99
285
  const html = isProduction
100
286
  ? templateHelper.renderProduction({
101
287
  hostType,
102
288
  serverUrl,
103
- widgetFile: this.lookupDistFileWithIndexFallback(`src/widgets/${name}`),
289
+ widgetFile: this.lookupWidgetFile(view.component),
104
290
  styleFile: this.lookupDistFile("style.css"),
105
291
  })
106
292
  : templateHelper.renderDevelopment({
107
293
  hostType,
108
294
  serverUrl,
109
- widgetName: name,
295
+ widgetName: view.component,
110
296
  });
111
- const VITE_HMR_WEBSOCKET_DEFAULT_URL = "ws://localhost:24678";
297
+ const connectDomains = [serverUrl];
298
+ if (!isProduction) {
299
+ const wsUrl = new URL(serverUrl);
300
+ wsUrl.protocol = wsUrl.protocol === "https:" ? "wss:" : "ws:";
301
+ connectDomains.push(wsUrl.origin);
302
+ }
303
+ let contentMetaOverrides = {};
304
+ if (isClaude) {
305
+ const pathname = extra?.requestInfo?.url?.pathname ?? "";
306
+ const rawUrl = header("x-alpic-forwarded-url") ?? `${serverUrl}${pathname}`;
307
+ const url = rawUrl.endsWith("/") ? rawUrl.slice(0, -1) : rawUrl;
308
+ const hash = crypto
309
+ .createHash("sha256")
310
+ .update(url)
311
+ .digest("hex")
312
+ .slice(0, 32);
313
+ contentMetaOverrides = { domain: `${hash}.claudemcpcontent.com` };
314
+ }
112
315
  const contentMeta = buildContentMeta({
113
316
  resourceDomains: [serverUrl],
114
- connectDomains: !isProduction ? [VITE_HMR_WEBSOCKET_DEFAULT_URL] : [],
317
+ connectDomains,
115
318
  domain: serverUrl,
116
319
  baseUriDomains: [serverUrl],
117
- });
320
+ }, contentMetaOverrides);
118
321
  return {
119
322
  contents: [
120
323
  { uri: uri.href, mimeType, text: html, _meta: contentMeta },
@@ -122,18 +325,70 @@ export class McpServer extends McpServerBase {
122
325
  };
123
326
  });
124
327
  }
125
- lookupDistFile(key) {
328
+ // -------------------------------------------------------------------------
329
+ // Handler wrapping
330
+ // -------------------------------------------------------------------------
331
+ /** @internal */
332
+ wrapHandlerWithViewUUID(cb) {
333
+ return async (args, extra) => {
334
+ const result = await cb(args, extra);
335
+ return {
336
+ ...result,
337
+ content: normalizeContent(result.content),
338
+ _meta: {
339
+ ...result._meta,
340
+ viewUUID: crypto.randomUUID(),
341
+ },
342
+ };
343
+ };
344
+ }
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
+ };
354
+ }
355
+ // -------------------------------------------------------------------------
356
+ // Manifest helpers
357
+ // -------------------------------------------------------------------------
358
+ lookupWidgetFile(widgetName) {
126
359
  const manifest = this.readManifest();
127
- return manifest[key]?.file;
360
+ const file = manifest[widgetName]?.file;
361
+ if (!file) {
362
+ throw new Error(`Widget "${widgetName}" not found in Vite manifest. Did the build complete successfully? Expected key "${widgetName}" in dist/assets/.vite/manifest.json.`);
363
+ }
364
+ return file;
128
365
  }
129
- lookupDistFileWithIndexFallback(basePath) {
366
+ lookupDistFile(key) {
130
367
  const manifest = this.readManifest();
131
- const flatFileKey = `${basePath}.tsx`;
132
- const indexFileKey = `${basePath}/index.tsx`;
133
- return manifest[flatFileKey]?.file ?? manifest[indexFileKey]?.file;
368
+ return manifest[key]?.file;
134
369
  }
135
370
  readManifest() {
136
371
  return JSON.parse(readFileSync(path.join(process.cwd(), "dist", "assets", ".vite", "manifest.json"), "utf-8"));
137
372
  }
373
+ registerTool(...args) {
374
+ const baseFn = McpServerBase.prototype.registerTool;
375
+ if (typeof args[0] === "string") {
376
+ baseFn.call(this, args[0], args[1], args[2]);
377
+ return this;
378
+ }
379
+ const config = args[0];
380
+ const cb = args[1];
381
+ const { name, view, _meta: userToolMeta, ...toolFields } = config;
382
+ const toolMeta = { ...userToolMeta };
383
+ if (view) {
384
+ this.enforceOneToolPerWidget(view.component, name);
385
+ this.registerViewResources(name, view, toolMeta);
386
+ }
387
+ const wrappedCb = view
388
+ ? this.wrapHandlerWithViewUUID(cb)
389
+ : this.wrapHandlerWithContentNormalization(cb);
390
+ baseFn.call(this, name, { ...toolFields, _meta: toolMeta }, wrappedCb);
391
+ return this;
392
+ }
138
393
  }
139
394
  //# sourceMappingURL=server.js.map