skybridge 0.0.0-dev.e112094 → 0.0.0-dev.e11ecfc
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.
- package/README.md +123 -124
- package/dist/cli/build-helpers.d.ts +3 -0
- package/dist/cli/build-helpers.js +23 -0
- package/dist/cli/build-helpers.js.map +1 -0
- package/dist/cli/build-helpers.test.d.ts +1 -0
- package/dist/cli/build-helpers.test.js +36 -0
- package/dist/cli/build-helpers.test.js.map +1 -0
- package/dist/cli/detect-port.js.map +1 -1
- package/dist/cli/header.js.map +1 -1
- package/dist/cli/resolve-views-dir.d.ts +1 -0
- package/dist/cli/resolve-views-dir.js +17 -0
- package/dist/cli/resolve-views-dir.js.map +1 -0
- package/dist/cli/run-command.js.map +1 -1
- package/dist/cli/telemetry.js.map +1 -1
- package/dist/cli/tunnel-control-server.d.ts +9 -0
- package/dist/cli/tunnel-control-server.js +31 -0
- package/dist/cli/tunnel-control-server.js.map +1 -0
- package/dist/cli/tunnel-control-server.test.d.ts +1 -0
- package/dist/cli/tunnel-control-server.test.js +39 -0
- package/dist/cli/tunnel-control-server.test.js.map +1 -0
- package/dist/cli/tunnel-handler.d.ts +3 -0
- package/dist/cli/tunnel-handler.js +48 -0
- package/dist/cli/tunnel-handler.js.map +1 -0
- package/dist/cli/tunnel-handler.test.d.ts +1 -0
- package/dist/cli/tunnel-handler.test.js +105 -0
- package/dist/cli/tunnel-handler.test.js.map +1 -0
- package/dist/cli/tunnel.d.ts +57 -0
- package/dist/cli/tunnel.js +154 -0
- package/dist/cli/tunnel.js.map +1 -0
- package/dist/cli/tunnel.test.d.ts +1 -0
- package/dist/cli/tunnel.test.js +190 -0
- package/dist/cli/tunnel.test.js.map +1 -0
- package/dist/cli/types.js.map +1 -1
- package/dist/cli/use-execute-steps.js.map +1 -1
- package/dist/cli/use-messages.js.map +1 -1
- package/dist/cli/use-nodemon.js +11 -2
- package/dist/cli/use-nodemon.js.map +1 -1
- package/dist/cli/use-open-browser.d.ts +1 -0
- package/dist/cli/use-open-browser.js +44 -0
- package/dist/cli/use-open-browser.js.map +1 -0
- package/dist/cli/use-tunnel.d.ts +1 -1
- package/dist/cli/use-tunnel.js +102 -68
- package/dist/cli/use-tunnel.js.map +1 -1
- package/dist/cli/use-typescript-check.d.ts +1 -0
- package/dist/cli/use-typescript-check.js +42 -7
- package/dist/cli/use-typescript-check.js.map +1 -1
- package/dist/commands/build.js +43 -17
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/create.d.ts +9 -0
- package/dist/commands/create.js +30 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/dev.d.ts +1 -0
- package/dist/commands/dev.js +49 -2
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/start.js +7 -1
- package/dist/commands/start.js.map +1 -1
- package/dist/commands/telemetry/disable.js.map +1 -1
- package/dist/commands/telemetry/enable.js.map +1 -1
- package/dist/commands/telemetry/status.js.map +1 -1
- package/dist/server/asset-base-url-transform-plugin.d.ts +1 -0
- package/dist/server/asset-base-url-transform-plugin.js +17 -2
- package/dist/server/asset-base-url-transform-plugin.js.map +1 -1
- package/dist/server/asset-base-url-transform-plugin.test.js +80 -1
- package/dist/server/asset-base-url-transform-plugin.test.js.map +1 -1
- package/dist/server/auth.d.ts +20 -0
- package/dist/server/auth.js +28 -0
- package/dist/server/auth.js.map +1 -0
- package/dist/server/build-manifest.test.d.ts +1 -0
- package/dist/server/build-manifest.test.js +16 -0
- package/dist/server/build-manifest.test.js.map +1 -0
- package/dist/server/content-helpers.d.ts +40 -0
- package/dist/server/content-helpers.js +33 -0
- package/dist/server/content-helpers.js.map +1 -1
- package/dist/server/content-helpers.test.js.map +1 -1
- package/dist/server/express.d.ts +1 -5
- package/dist/server/express.js +31 -7
- package/dist/server/express.js.map +1 -1
- package/dist/server/express.test.js +210 -69
- package/dist/server/express.test.js.map +1 -1
- package/dist/server/file-ref.d.ts +28 -0
- package/dist/server/file-ref.js +27 -0
- package/dist/server/file-ref.js.map +1 -0
- package/dist/server/index.d.ts +4 -2
- package/dist/server/index.js +3 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/inferUtilityTypes.js.map +1 -1
- package/dist/server/metric.js.map +1 -1
- package/dist/server/middleware.d.ts +16 -3
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/middleware.test-d.js.map +1 -1
- package/dist/server/middleware.test.js.map +1 -1
- package/dist/server/server.d.ts +213 -7
- package/dist/server/server.js +236 -77
- package/dist/server/server.js.map +1 -1
- package/dist/server/templateHelper.d.ts +0 -2
- package/dist/server/templateHelper.js +3 -22
- package/dist/server/templateHelper.js.map +1 -1
- package/dist/server/templates.generated.d.ts +4 -0
- package/dist/server/templates.generated.js +47 -0
- package/dist/server/templates.generated.js.map +1 -0
- package/dist/server/tunnel-proxy-router.d.ts +7 -0
- package/dist/server/tunnel-proxy-router.js +110 -0
- package/dist/server/tunnel-proxy-router.js.map +1 -0
- package/dist/server/tunnel-proxy-router.test.d.ts +1 -0
- package/dist/server/tunnel-proxy-router.test.js +229 -0
- package/dist/server/tunnel-proxy-router.test.js.map +1 -0
- package/dist/server/viewsDevServer.js.map +1 -1
- package/dist/test/utils.js.map +1 -1
- package/dist/test/view.test.js +45 -0
- package/dist/test/view.test.js.map +1 -1
- package/dist/version.js +1 -3
- package/dist/version.js.map +1 -1
- package/dist/web/bridges/apps-sdk/adaptor.d.ts +6 -2
- package/dist/web/bridges/apps-sdk/adaptor.js +23 -3
- package/dist/web/bridges/apps-sdk/adaptor.js.map +1 -1
- package/dist/web/bridges/apps-sdk/bridge.d.ts +1 -0
- package/dist/web/bridges/apps-sdk/bridge.js +1 -0
- package/dist/web/bridges/apps-sdk/bridge.js.map +1 -1
- package/dist/web/bridges/apps-sdk/index.js.map +1 -1
- package/dist/web/bridges/apps-sdk/types.d.ts +8 -1
- package/dist/web/bridges/apps-sdk/types.js.map +1 -1
- package/dist/web/bridges/apps-sdk/use-apps-sdk-context.d.ts +11 -0
- package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js +11 -0
- package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js.map +1 -1
- package/dist/web/bridges/get-adaptor.d.ts +7 -0
- package/dist/web/bridges/get-adaptor.js +7 -0
- package/dist/web/bridges/get-adaptor.js.map +1 -1
- package/dist/web/bridges/index.js.map +1 -1
- package/dist/web/bridges/mcp-app/adaptor.d.ts +6 -2
- package/dist/web/bridges/mcp-app/adaptor.js +18 -6
- package/dist/web/bridges/mcp-app/adaptor.js.map +1 -1
- package/dist/web/bridges/mcp-app/bridge.d.ts +1 -0
- package/dist/web/bridges/mcp-app/bridge.js +1 -0
- package/dist/web/bridges/mcp-app/bridge.js.map +1 -1
- package/dist/web/bridges/mcp-app/index.js.map +1 -1
- package/dist/web/bridges/mcp-app/types.js.map +1 -1
- package/dist/web/bridges/mcp-app/use-mcp-app-context.d.ts +12 -0
- package/dist/web/bridges/mcp-app/use-mcp-app-context.js +12 -0
- package/dist/web/bridges/mcp-app/use-mcp-app-context.js.map +1 -1
- package/dist/web/bridges/mcp-app/use-mcp-app-context.test.js.map +1 -1
- package/dist/web/bridges/types.d.ts +65 -3
- package/dist/web/bridges/types.js.map +1 -1
- package/dist/web/bridges/use-host-context.d.ts +5 -0
- package/dist/web/bridges/use-host-context.js +5 -0
- package/dist/web/bridges/use-host-context.js.map +1 -1
- package/dist/web/components/modal-provider.js.map +1 -1
- package/dist/web/create-store.d.ts +26 -0
- package/dist/web/create-store.js +26 -0
- package/dist/web/create-store.js.map +1 -1
- package/dist/web/create-store.test.js.map +1 -1
- package/dist/web/data-llm.d.ts +33 -0
- package/dist/web/data-llm.js +28 -0
- package/dist/web/data-llm.js.map +1 -1
- package/dist/web/data-llm.test.js.map +1 -1
- package/dist/web/generate-helpers.d.ts +2 -0
- package/dist/web/generate-helpers.js +2 -0
- package/dist/web/generate-helpers.js.map +1 -1
- package/dist/web/generate-helpers.test-d.js.map +1 -1
- package/dist/web/generate-helpers.test.js.map +1 -1
- package/dist/web/helpers/state.js.map +1 -1
- package/dist/web/helpers/state.test.js.map +1 -1
- package/dist/web/hooks/index.d.ts +3 -0
- package/dist/web/hooks/index.js +3 -0
- package/dist/web/hooks/index.js.map +1 -1
- package/dist/web/hooks/test/utils.d.ts +6 -2
- package/dist/web/hooks/test/utils.js +13 -2
- package/dist/web/hooks/test/utils.js.map +1 -1
- package/dist/web/hooks/use-call-tool.d.ts +45 -0
- package/dist/web/hooks/use-call-tool.js +28 -0
- package/dist/web/hooks/use-call-tool.js.map +1 -1
- package/dist/web/hooks/use-call-tool.test-d.js.map +1 -1
- package/dist/web/hooks/use-call-tool.test.js +27 -6
- package/dist/web/hooks/use-call-tool.test.js.map +1 -1
- package/dist/web/hooks/use-display-mode.d.ts +20 -0
- package/dist/web/hooks/use-display-mode.js +20 -0
- package/dist/web/hooks/use-display-mode.js.map +1 -1
- package/dist/web/hooks/use-display-mode.test-d.js.map +1 -1
- package/dist/web/hooks/use-display-mode.test.js.map +1 -1
- package/dist/web/hooks/use-download.d.ts +5 -0
- package/dist/web/hooks/use-download.js +8 -0
- package/dist/web/hooks/use-download.js.map +1 -0
- package/dist/web/hooks/use-download.test.d.ts +1 -0
- package/dist/web/hooks/use-download.test.js +95 -0
- package/dist/web/hooks/use-download.test.js.map +1 -0
- package/dist/web/hooks/use-files.d.ts +32 -0
- package/dist/web/hooks/use-files.js +32 -0
- package/dist/web/hooks/use-files.js.map +1 -1
- package/dist/web/hooks/use-files.test.js.map +1 -1
- package/dist/web/hooks/use-layout.d.ts +2 -0
- package/dist/web/hooks/use-layout.js +2 -0
- package/dist/web/hooks/use-layout.js.map +1 -1
- package/dist/web/hooks/use-layout.test.js.map +1 -1
- package/dist/web/hooks/use-open-external.d.ts +17 -0
- package/dist/web/hooks/use-open-external.js +16 -0
- package/dist/web/hooks/use-open-external.js.map +1 -1
- package/dist/web/hooks/use-open-external.test.js.map +1 -1
- package/dist/web/hooks/use-request-close.d.ts +16 -0
- package/dist/web/hooks/use-request-close.js +21 -0
- package/dist/web/hooks/use-request-close.js.map +1 -0
- package/dist/web/hooks/use-request-close.test.d.ts +1 -0
- package/dist/web/hooks/use-request-close.test.js +52 -0
- package/dist/web/hooks/use-request-close.test.js.map +1 -0
- package/dist/web/hooks/use-request-modal.d.ts +16 -1
- package/dist/web/hooks/use-request-modal.js +16 -1
- package/dist/web/hooks/use-request-modal.js.map +1 -1
- package/dist/web/hooks/use-request-modal.test.js.map +1 -1
- package/dist/web/hooks/use-request-size.d.ts +20 -0
- package/dist/web/hooks/use-request-size.js +24 -0
- package/dist/web/hooks/use-request-size.js.map +1 -0
- package/dist/web/hooks/use-request-size.test.d.ts +1 -0
- package/dist/web/hooks/use-request-size.test.js +65 -0
- package/dist/web/hooks/use-request-size.test.js.map +1 -0
- package/dist/web/hooks/use-send-follow-up-message.d.ts +19 -1
- package/dist/web/hooks/use-send-follow-up-message.js +19 -2
- package/dist/web/hooks/use-send-follow-up-message.js.map +1 -1
- package/dist/web/hooks/use-set-open-in-app-url.d.ts +17 -0
- package/dist/web/hooks/use-set-open-in-app-url.js +17 -0
- package/dist/web/hooks/use-set-open-in-app-url.js.map +1 -1
- package/dist/web/hooks/use-set-open-in-app-url.test.js.map +1 -1
- package/dist/web/hooks/use-tool-info.d.ts +33 -0
- package/dist/web/hooks/use-tool-info.js +26 -0
- package/dist/web/hooks/use-tool-info.js.map +1 -1
- package/dist/web/hooks/use-tool-info.test-d.js.map +1 -1
- package/dist/web/hooks/use-tool-info.test.js.map +1 -1
- package/dist/web/hooks/use-user.d.ts +2 -0
- package/dist/web/hooks/use-user.js +2 -0
- package/dist/web/hooks/use-user.js.map +1 -1
- package/dist/web/hooks/use-user.test.js.map +1 -1
- package/dist/web/hooks/use-view-state.d.ts +21 -0
- package/dist/web/hooks/use-view-state.js.map +1 -1
- package/dist/web/hooks/use-view-state.test.js.map +1 -1
- package/dist/web/index.js.map +1 -1
- package/dist/web/mount-view.d.ts +19 -0
- package/dist/web/mount-view.js +19 -0
- package/dist/web/mount-view.js.map +1 -1
- package/dist/web/plugin/data-llm.test.js.map +1 -1
- package/dist/web/plugin/plugin.d.ts +28 -0
- package/dist/web/plugin/plugin.js +63 -15
- package/dist/web/plugin/plugin.js.map +1 -1
- package/dist/web/plugin/scan-views.d.ts +8 -0
- package/dist/web/plugin/scan-views.js +26 -8
- package/dist/web/plugin/scan-views.js.map +1 -1
- package/dist/web/plugin/scan-views.test.js +33 -1
- package/dist/web/plugin/scan-views.test.js.map +1 -1
- package/dist/web/plugin/transform-data-llm.js.map +1 -1
- package/dist/web/plugin/transform-data-llm.test.js.map +1 -1
- package/dist/web/plugin/validate-view.js.map +1 -1
- package/dist/web/plugin/validate-view.test.js.map +1 -1
- package/dist/web/proxy.js.map +1 -1
- package/dist/web/types.d.ts +4 -0
- package/dist/web/types.js.map +1 -1
- package/package.json +13 -7
- package/dist/server/templates/development.hbs +0 -12
- package/dist/server/templates/production.hbs +0 -6
|
@@ -1,12 +1,46 @@
|
|
|
1
1
|
import type { AudioContent, EmbeddedResource, ImageContent, ResourceLink, TextContent } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* MCP content annotations applied to any returned block.
|
|
4
|
+
*
|
|
5
|
+
* - `audience` — who is meant to see the content (`"user"`, `"assistant"`, or both).
|
|
6
|
+
* - `priority` — relative importance hint for the host.
|
|
7
|
+
* - `lastModified` — ISO timestamp for when the content was produced.
|
|
8
|
+
*/
|
|
2
9
|
type ContentAnnotations = {
|
|
3
10
|
audience?: ("user" | "assistant")[];
|
|
4
11
|
priority?: number;
|
|
5
12
|
lastModified?: string;
|
|
6
13
|
};
|
|
14
|
+
/**
|
|
15
|
+
* Build an MCP text content block.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* return { content: [text("Found 3 results.")] };
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
7
22
|
export declare function text(value: string, annotations?: ContentAnnotations): TextContent;
|
|
23
|
+
/**
|
|
24
|
+
* Build an MCP image content block.
|
|
25
|
+
*
|
|
26
|
+
* `data` may be a `Uint8Array` (encoded to base64 for you) or a string that is
|
|
27
|
+
* **already base64-encoded**. Passing a raw byte-string will produce invalid
|
|
28
|
+
* content.
|
|
29
|
+
*/
|
|
8
30
|
export declare function image(data: string | Uint8Array, mimeType: string, annotations?: ContentAnnotations): ImageContent;
|
|
31
|
+
/**
|
|
32
|
+
* Build an MCP audio content block.
|
|
33
|
+
*
|
|
34
|
+
* `data` may be a `Uint8Array` (encoded to base64 for you) or a string that is
|
|
35
|
+
* **already base64-encoded**.
|
|
36
|
+
*/
|
|
9
37
|
export declare function audio(data: string | Uint8Array, mimeType: string, annotations?: ContentAnnotations): AudioContent;
|
|
38
|
+
/**
|
|
39
|
+
* Build an MCP embedded resource — the full content travels inline. Use this
|
|
40
|
+
* when the client needs the bytes themselves rather than a link.
|
|
41
|
+
*
|
|
42
|
+
* Pass either `text` (UTF-8 string) or `blob` (base64-encoded bytes).
|
|
43
|
+
*/
|
|
10
44
|
export declare function embeddedResource(resource: {
|
|
11
45
|
uri: string;
|
|
12
46
|
mimeType?: string;
|
|
@@ -16,6 +50,12 @@ export declare function embeddedResource(resource: {
|
|
|
16
50
|
mimeType?: string;
|
|
17
51
|
blob: string;
|
|
18
52
|
}, annotations?: ContentAnnotations): EmbeddedResource;
|
|
53
|
+
/**
|
|
54
|
+
* Build an MCP resource link — a `type: "resource_link"` block carrying a URI
|
|
55
|
+
* the client can fetch (or subscribe to) on demand. Use a link when the
|
|
56
|
+
* client should retrieve the bytes itself; use {@link embeddedResource} when
|
|
57
|
+
* the content must travel inline with the response.
|
|
58
|
+
*/
|
|
19
59
|
export declare function resourceLink(link: {
|
|
20
60
|
uri: string;
|
|
21
61
|
name: string;
|
|
@@ -10,9 +10,24 @@ function toBase64(data) {
|
|
|
10
10
|
}
|
|
11
11
|
return Buffer.from(data).toString("base64");
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Build an MCP text content block.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* return { content: [text("Found 3 results.")] };
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
13
21
|
export function text(value, annotations) {
|
|
14
22
|
return { type: "text", text: value, ...(annotations && { annotations }) };
|
|
15
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Build an MCP image content block.
|
|
26
|
+
*
|
|
27
|
+
* `data` may be a `Uint8Array` (encoded to base64 for you) or a string that is
|
|
28
|
+
* **already base64-encoded**. Passing a raw byte-string will produce invalid
|
|
29
|
+
* content.
|
|
30
|
+
*/
|
|
16
31
|
export function image(data, mimeType, annotations) {
|
|
17
32
|
return {
|
|
18
33
|
type: "image",
|
|
@@ -21,6 +36,12 @@ export function image(data, mimeType, annotations) {
|
|
|
21
36
|
...(annotations && { annotations }),
|
|
22
37
|
};
|
|
23
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Build an MCP audio content block.
|
|
41
|
+
*
|
|
42
|
+
* `data` may be a `Uint8Array` (encoded to base64 for you) or a string that is
|
|
43
|
+
* **already base64-encoded**.
|
|
44
|
+
*/
|
|
24
45
|
export function audio(data, mimeType, annotations) {
|
|
25
46
|
return {
|
|
26
47
|
type: "audio",
|
|
@@ -29,6 +50,12 @@ export function audio(data, mimeType, annotations) {
|
|
|
29
50
|
...(annotations && { annotations }),
|
|
30
51
|
};
|
|
31
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Build an MCP embedded resource — the full content travels inline. Use this
|
|
55
|
+
* when the client needs the bytes themselves rather than a link.
|
|
56
|
+
*
|
|
57
|
+
* Pass either `text` (UTF-8 string) or `blob` (base64-encoded bytes).
|
|
58
|
+
*/
|
|
32
59
|
export function embeddedResource(resource, annotations) {
|
|
33
60
|
return {
|
|
34
61
|
type: "resource",
|
|
@@ -36,6 +63,12 @@ export function embeddedResource(resource, annotations) {
|
|
|
36
63
|
...(annotations && { annotations }),
|
|
37
64
|
};
|
|
38
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Build an MCP resource link — a `type: "resource_link"` block carrying a URI
|
|
68
|
+
* the client can fetch (or subscribe to) on demand. Use a link when the
|
|
69
|
+
* client should retrieve the bytes itself; use {@link embeddedResource} when
|
|
70
|
+
* the content must travel inline with the response.
|
|
71
|
+
*/
|
|
39
72
|
export function resourceLink(link, annotations) {
|
|
40
73
|
return {
|
|
41
74
|
type: "resource_link",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-helpers.js","sourceRoot":"","sources":["../../src/server/content-helpers.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"content-helpers.js","sourceRoot":"","sources":["../../src/server/content-helpers.ts"],"names":[],"mappings":"AAqBA;;;;;GAKG;AACH,SAAS,QAAQ,CAAC,IAAyB;IACzC,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAC9C,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,IAAI,CAClB,KAAa,EACb,WAAgC;IAEhC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,WAAW,IAAI,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC;AAC5E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,KAAK,CACnB,IAAyB,EACzB,QAAgB,EAChB,WAAgC;IAEhC,OAAO;QACL,IAAI,EAAE,OAAO;QACb,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC;QACpB,QAAQ;QACR,GAAG,CAAC,WAAW,IAAI,EAAE,WAAW,EAAE,CAAC;KACpC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,KAAK,CACnB,IAAyB,EACzB,QAAgB,EAChB,WAAgC;IAEhC,OAAO;QACL,IAAI,EAAE,OAAO;QACb,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC;QACpB,QAAQ;QACR,GAAG,CAAC,WAAW,IAAI,EAAE,WAAW,EAAE,CAAC;KACpC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAC9B,QAEoD,EACpD,WAAgC;IAEhC,OAAO;QACL,IAAI,EAAE,UAAU;QAChB,QAAQ;QACR,GAAG,CAAC,WAAW,IAAI,EAAE,WAAW,EAAE,CAAC;KACpC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAC1B,IAOC,EACD,WAAgC;IAEhC,OAAO;QACL,IAAI,EAAE,eAAe;QACrB,GAAG,IAAI;QACP,GAAG,CAAC,WAAW,IAAI,EAAE,WAAW,EAAE,CAAC;KACpC,CAAC;AACJ,CAAC","sourcesContent":["import type {\n AudioContent,\n EmbeddedResource,\n ImageContent,\n ResourceLink,\n TextContent,\n} from \"@modelcontextprotocol/sdk/types.js\";\n\n/**\n * MCP content annotations applied to any returned block.\n *\n * - `audience` — who is meant to see the content (`\"user\"`, `\"assistant\"`, or both).\n * - `priority` — relative importance hint for the host.\n * - `lastModified` — ISO timestamp for when the content was produced.\n */\ntype ContentAnnotations = {\n audience?: (\"user\" | \"assistant\")[];\n priority?: number;\n lastModified?: string;\n};\n\n/**\n * Returns a base64-encoded string.\n * - `Uint8Array` input is encoded via `Buffer.toString(\"base64\")`.\n * - `string` input is assumed to be **already base64-encoded** and is returned\n * as-is. Passing raw/unencoded string bytes will produce invalid MCP content.\n */\nfunction toBase64(data: string | Uint8Array): string {\n if (typeof data === \"string\") {\n return data;\n }\n return Buffer.from(data).toString(\"base64\");\n}\n\n/**\n * Build an MCP text content block.\n *\n * @example\n * ```ts\n * return { content: [text(\"Found 3 results.\")] };\n * ```\n */\nexport function text(\n value: string,\n annotations?: ContentAnnotations,\n): TextContent {\n return { type: \"text\", text: value, ...(annotations && { annotations }) };\n}\n\n/**\n * Build an MCP image content block.\n *\n * `data` may be a `Uint8Array` (encoded to base64 for you) or a string that is\n * **already base64-encoded**. Passing a raw byte-string will produce invalid\n * content.\n */\nexport function image(\n data: string | Uint8Array,\n mimeType: string,\n annotations?: ContentAnnotations,\n): ImageContent {\n return {\n type: \"image\",\n data: toBase64(data),\n mimeType,\n ...(annotations && { annotations }),\n };\n}\n\n/**\n * Build an MCP audio content block.\n *\n * `data` may be a `Uint8Array` (encoded to base64 for you) or a string that is\n * **already base64-encoded**.\n */\nexport function audio(\n data: string | Uint8Array,\n mimeType: string,\n annotations?: ContentAnnotations,\n): AudioContent {\n return {\n type: \"audio\",\n data: toBase64(data),\n mimeType,\n ...(annotations && { annotations }),\n };\n}\n\n/**\n * Build an MCP embedded resource — the full content travels inline. Use this\n * when the client needs the bytes themselves rather than a link.\n *\n * Pass either `text` (UTF-8 string) or `blob` (base64-encoded bytes).\n */\nexport function embeddedResource(\n resource:\n | { uri: string; mimeType?: string; text: string }\n | { uri: string; mimeType?: string; blob: string },\n annotations?: ContentAnnotations,\n): EmbeddedResource {\n return {\n type: \"resource\",\n resource,\n ...(annotations && { annotations }),\n };\n}\n\n/**\n * Build an MCP resource link — a `type: \"resource_link\"` block carrying a URI\n * the client can fetch (or subscribe to) on demand. Use a link when the\n * client should retrieve the bytes itself; use {@link embeddedResource} when\n * the content must travel inline with the response.\n */\nexport function resourceLink(\n link: {\n uri: string;\n name: string;\n title?: string;\n description?: string;\n mimeType?: string;\n size?: number;\n },\n annotations?: ContentAnnotations,\n): ResourceLink {\n return {\n type: \"resource_link\",\n ...link,\n ...(annotations && { annotations }),\n };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-helpers.test.js","sourceRoot":"","sources":["../../src/server/content-helpers.test.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"content-helpers.test.js","sourceRoot":"","sources":["../../src/server/content-helpers.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport {\n audio,\n embeddedResource,\n image,\n resourceLink,\n text,\n} from \"./content-helpers.js\";\n\ndescribe(\"text\", () => {\n it(\"returns a TextContent without annotations when none given\", () => {\n expect(text(\"hello\")).toEqual({ type: \"text\", text: \"hello\" });\n });\n\n it(\"includes annotations when provided\", () => {\n expect(text(\"hi\", { priority: 1 })).toEqual({\n type: \"text\",\n text: \"hi\",\n annotations: { priority: 1 },\n });\n });\n});\n\ndescribe(\"image\", () => {\n it(\"base64-encodes Uint8Array data\", () => {\n const bytes = new Uint8Array([104, 105]);\n expect(image(bytes, \"image/png\")).toEqual({\n type: \"image\",\n data: \"aGk=\",\n mimeType: \"image/png\",\n });\n });\n\n it(\"passes string data through unchanged (caller is responsible for base64)\", () => {\n expect(image(\"YWxyZWFkeS1iNjQ=\", \"image/png\")).toEqual({\n type: \"image\",\n data: \"YWxyZWFkeS1iNjQ=\",\n mimeType: \"image/png\",\n });\n });\n});\n\ndescribe(\"audio\", () => {\n it(\"base64-encodes Uint8Array data\", () => {\n const bytes = new Uint8Array([104, 105]);\n expect(audio(bytes, \"audio/mpeg\")).toMatchObject({\n type: \"audio\",\n data: \"aGk=\",\n mimeType: \"audio/mpeg\",\n });\n });\n});\n\ndescribe(\"embeddedResource\", () => {\n it(\"wraps a text resource with a type tag\", () => {\n expect(\n embeddedResource({\n uri: \"file:///a.txt\",\n mimeType: \"text/plain\",\n text: \"x\",\n }),\n ).toEqual({\n type: \"resource\",\n resource: { uri: \"file:///a.txt\", mimeType: \"text/plain\", text: \"x\" },\n });\n });\n\n it(\"wraps a blob resource with a type tag\", () => {\n expect(embeddedResource({ uri: \"file:///a.bin\", blob: \"YmFy\" })).toEqual({\n type: \"resource\",\n resource: { uri: \"file:///a.bin\", blob: \"YmFy\" },\n });\n });\n});\n\ndescribe(\"resourceLink\", () => {\n it(\"spreads link fields alongside the type tag\", () => {\n expect(resourceLink({ uri: \"file:///a\", name: \"a\", title: \"A\" })).toEqual({\n type: \"resource_link\",\n uri: \"file:///a\",\n name: \"a\",\n title: \"A\",\n });\n });\n});\n"]}
|
package/dist/server/express.d.ts
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import type http from "node:http";
|
|
2
2
|
import express from "express";
|
|
3
3
|
import type { McpServer } from "./server.js";
|
|
4
|
-
export declare function createApp({ mcpServer, httpServer,
|
|
4
|
+
export declare function createApp({ mcpServer, httpServer, errorMiddleware, }: {
|
|
5
5
|
mcpServer: McpServer;
|
|
6
6
|
httpServer: http.Server;
|
|
7
|
-
customMiddleware?: {
|
|
8
|
-
path?: string;
|
|
9
|
-
handlers: express.RequestHandler[];
|
|
10
|
-
}[];
|
|
11
7
|
errorMiddleware?: {
|
|
12
8
|
path?: string;
|
|
13
9
|
handlers: express.ErrorRequestHandler[];
|
package/dist/server/express.js
CHANGED
|
@@ -2,6 +2,16 @@ import path from "node:path";
|
|
|
2
2
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
3
3
|
import cors from "cors";
|
|
4
4
|
import express from "express";
|
|
5
|
+
function parseControlPort(raw) {
|
|
6
|
+
if (raw === undefined) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
const n = Number.parseInt(raw, 10);
|
|
10
|
+
if (!Number.isFinite(n) || n <= 0 || n >= 65536) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
return n;
|
|
14
|
+
}
|
|
5
15
|
function applyMiddlewares(app, middlewares) {
|
|
6
16
|
for (const middleware of middlewares) {
|
|
7
17
|
if (middleware.path) {
|
|
@@ -22,18 +32,25 @@ function defaultErrorHandler(err, _req, res, _next) {
|
|
|
22
32
|
});
|
|
23
33
|
}
|
|
24
34
|
}
|
|
25
|
-
export async function createApp({ mcpServer, httpServer,
|
|
26
|
-
const app = express
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
if (env !== "production") {
|
|
35
|
+
export async function createApp({ mcpServer, httpServer, errorMiddleware = [], }) {
|
|
36
|
+
const app = mcpServer.express;
|
|
37
|
+
// Read `process.env.NODE_ENV` inline: wrangler/esbuild only substitute the literal expression,
|
|
38
|
+
// so a local const would defeat dead-code elimination of the dev-only imports below.
|
|
39
|
+
if (process.env.NODE_ENV !== "production") {
|
|
31
40
|
const { devtoolsStaticServer } = await import("@skybridge/devtools");
|
|
32
41
|
app.use(await devtoolsStaticServer());
|
|
33
42
|
const { viewsDevServer } = await import("./viewsDevServer.js");
|
|
34
43
|
app.use(await viewsDevServer(httpServer));
|
|
44
|
+
const controlPort = parseControlPort(process.env.__TUNNEL_CONTROL_PORT);
|
|
45
|
+
if (controlPort !== null) {
|
|
46
|
+
const { createTunnelProxyRouter } = await import("./tunnel-proxy-router.js");
|
|
47
|
+
app.use(createTunnelProxyRouter(controlPort));
|
|
48
|
+
}
|
|
49
|
+
else if (process.env.__TUNNEL_CONTROL_PORT !== undefined) {
|
|
50
|
+
console.warn(`Ignoring invalid __TUNNEL_CONTROL_PORT=${process.env.__TUNNEL_CONTROL_PORT}`);
|
|
51
|
+
}
|
|
35
52
|
}
|
|
36
|
-
|
|
53
|
+
else {
|
|
37
54
|
const assetsPath = path.join(process.cwd(), "dist", "assets");
|
|
38
55
|
app.use("/assets", cors());
|
|
39
56
|
app.use("/assets", express.static(assetsPath));
|
|
@@ -59,6 +76,13 @@ const mcpMiddleware = (server) => {
|
|
|
59
76
|
try {
|
|
60
77
|
const transport = new StreamableHTTPServerTransport({
|
|
61
78
|
sessionIdGenerator: undefined,
|
|
79
|
+
// Respond with a single JSON body instead of SSE. Skybridge's stateless
|
|
80
|
+
// transport never streams server-initiated messages, so SSE adds no
|
|
81
|
+
// capability — and on workerd specifically, `cloudflare:node`'s http
|
|
82
|
+
// bridge silently drops chunked writes that happen after the request
|
|
83
|
+
// handler awaits, which manifests as a 200 with empty body for any
|
|
84
|
+
// async tools/call.
|
|
85
|
+
enableJsonResponse: true,
|
|
62
86
|
});
|
|
63
87
|
res.on("close", () => {
|
|
64
88
|
transport.close();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"express.js","sourceRoot":"","sources":["../../src/server/express.ts"],"names":[],"mappings":"AACA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AACnG,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,OAAO,MAAM,SAAS,CAAC;AAG9B,SAAS,gBAAgB,CACvB,GAAoB,EACpB,WAGE;IAEF,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACrC,IAAI,UAAU,CAAC,IAAI,EAAE,CAAC;YACpB,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;QACnD,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,mBAAmB,CAC1B,GAAY,EACZ,IAAqB,EACrB,GAAqB,EACrB,KAA2B;IAE3B,OAAO,CAAC,KAAK,CAAC,6BAA6B,EAAE,GAAG,CAAC,CAAC;IAClD,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;QACrB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YACnB,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,uBAAuB,EAAE;YACzD,EAAE,EAAE,IAAI;SACT,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,EAC9B,SAAS,EACT,UAAU,EACV,
|
|
1
|
+
{"version":3,"file":"express.js","sourceRoot":"","sources":["../../src/server/express.ts"],"names":[],"mappings":"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","sourcesContent":["import type http from \"node:http\";\nimport path from \"node:path\";\nimport { StreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\nimport cors from \"cors\";\nimport express from \"express\";\nimport type { McpServer } from \"./server.js\";\n\nfunction parseControlPort(raw: string | undefined): number | null {\n if (raw === undefined) {\n return null;\n }\n const n = Number.parseInt(raw, 10);\n if (!Number.isFinite(n) || n <= 0 || n >= 65536) {\n return null;\n }\n return n;\n}\n\nfunction applyMiddlewares(\n app: express.Express,\n middlewares: Array<{\n path?: string;\n handlers: express.ErrorRequestHandler[];\n }>,\n): void {\n for (const middleware of middlewares) {\n if (middleware.path) {\n app.use(middleware.path, ...middleware.handlers);\n } else {\n app.use(...middleware.handlers);\n }\n }\n}\n\nfunction defaultErrorHandler(\n err: unknown,\n _req: express.Request,\n res: express.Response,\n _next: express.NextFunction,\n) {\n console.error(\"Error handling MCP request:\", err);\n if (!res.headersSent) {\n res.status(500).json({\n jsonrpc: \"2.0\",\n error: { code: -32603, message: \"Internal server error\" },\n id: null,\n });\n }\n}\n\nexport async function createApp({\n mcpServer,\n httpServer,\n errorMiddleware = [],\n}: {\n mcpServer: McpServer;\n httpServer: http.Server;\n errorMiddleware?: {\n path?: string;\n handlers: express.ErrorRequestHandler[];\n }[];\n}): Promise<express.Express> {\n const app = mcpServer.express;\n\n // Read `process.env.NODE_ENV` inline: wrangler/esbuild only substitute the literal expression,\n // so a local const would defeat dead-code elimination of the dev-only imports below.\n if (process.env.NODE_ENV !== \"production\") {\n const { devtoolsStaticServer } = await import(\"@skybridge/devtools\");\n app.use(await devtoolsStaticServer());\n const { viewsDevServer } = await import(\"./viewsDevServer.js\");\n app.use(await viewsDevServer(httpServer));\n\n const controlPort = parseControlPort(process.env.__TUNNEL_CONTROL_PORT);\n if (controlPort !== null) {\n const { createTunnelProxyRouter } = await import(\n \"./tunnel-proxy-router.js\"\n );\n app.use(createTunnelProxyRouter(controlPort));\n } else if (process.env.__TUNNEL_CONTROL_PORT !== undefined) {\n console.warn(\n `Ignoring invalid __TUNNEL_CONTROL_PORT=${process.env.__TUNNEL_CONTROL_PORT}`,\n );\n }\n } else {\n const assetsPath = path.join(process.cwd(), \"dist\", \"assets\");\n\n app.use(\"/assets\", cors());\n app.use(\"/assets\", express.static(assetsPath));\n }\n\n app.use(\"/mcp\", mcpMiddleware(mcpServer));\n\n applyMiddlewares(app, errorMiddleware);\n\n app.use(\"/mcp\", defaultErrorHandler);\n\n return app;\n}\n\nconst mcpMiddleware = (server: McpServer): express.RequestHandler => {\n return async (\n req: express.Request,\n res: express.Response,\n next: express.NextFunction,\n ) => {\n if (req.method !== \"POST\") {\n res.writeHead(405).end(\n JSON.stringify({\n jsonrpc: \"2.0\",\n error: {\n code: -32000,\n message: \"Method not allowed.\",\n },\n id: null,\n }),\n );\n return;\n }\n\n try {\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: undefined,\n // Respond with a single JSON body instead of SSE. Skybridge's stateless\n // transport never streams server-initiated messages, so SSE adds no\n // capability — and on workerd specifically, `cloudflare:node`'s http\n // bridge silently drops chunked writes that happen after the request\n // handler awaits, which manifests as a 200 with empty body for any\n // async tools/call.\n enableJsonResponse: true,\n });\n\n res.on(\"close\", () => {\n transport.close();\n });\n\n await server.connectStatelessTransport(transport);\n // Express strips the mount path from req.url (e.g. \"/mcp\" becomes \"/\").\n // Restore it so the SDK builds the correct requestInfo.url.\n req.url = req.originalUrl;\n await transport.handleRequest(req, res, req.body);\n } catch (error) {\n next(error);\n }\n };\n};\n"]}
|
|
@@ -7,7 +7,6 @@ vi.mock("@skybridge/devtools", () => ({
|
|
|
7
7
|
vi.mock("./viewsDevServer.js", () => ({
|
|
8
8
|
viewsDevServer: (_httpServer) => ((_req, _res, next) => next()),
|
|
9
9
|
}));
|
|
10
|
-
const fakeServer = {};
|
|
11
10
|
async function listen(app) {
|
|
12
11
|
const server = http.createServer(app);
|
|
13
12
|
await new Promise((resolve) => server.listen(0, resolve));
|
|
@@ -26,6 +25,94 @@ async function postMcp(port) {
|
|
|
26
25
|
async function postApi(port) {
|
|
27
26
|
return fetch(`http://localhost:${port}/api/test`, { method: "POST" });
|
|
28
27
|
}
|
|
28
|
+
describe("McpServer.express", () => {
|
|
29
|
+
it("exposes a ready Express app immediately after construction", () => {
|
|
30
|
+
const server = new McpServer({ name: "t", version: "0.0.0" });
|
|
31
|
+
expect(server.express).toBeDefined();
|
|
32
|
+
expect(typeof server.express.use).toBe("function");
|
|
33
|
+
expect(typeof server.express.get).toBe("function");
|
|
34
|
+
});
|
|
35
|
+
it("server.express.get registers a route reachable alongside /mcp", async () => {
|
|
36
|
+
const { createApp } = await import("./express.js");
|
|
37
|
+
const server = new McpServer({ name: "t", version: "0.0.0" });
|
|
38
|
+
server.express.get("/health", (_req, res) => {
|
|
39
|
+
res.json({ status: "ok" });
|
|
40
|
+
});
|
|
41
|
+
const httpServer = http.createServer();
|
|
42
|
+
await createApp({ mcpServer: server, httpServer });
|
|
43
|
+
const { port, server: listening } = await listen(server.express);
|
|
44
|
+
openServer = listening;
|
|
45
|
+
const health = await fetch(`http://localhost:${port}/health`);
|
|
46
|
+
expect(health.status).toBe(200);
|
|
47
|
+
expect(await health.json()).toEqual({ status: "ok" });
|
|
48
|
+
// /mcp still works (POST returns 200/4xx, not 404)
|
|
49
|
+
const mcp = await postMcp(port);
|
|
50
|
+
expect(mcp.status).not.toBe(404);
|
|
51
|
+
});
|
|
52
|
+
it("server.use and server.express.use produce the same registration order", async () => {
|
|
53
|
+
const { createApp } = await import("./express.js");
|
|
54
|
+
const callsA = [];
|
|
55
|
+
const callsB = [];
|
|
56
|
+
const buildServer = () => new McpServer({ name: "t", version: "0.0.0" });
|
|
57
|
+
const sA = buildServer();
|
|
58
|
+
sA.use((_req, _res, next) => {
|
|
59
|
+
callsA.push("first");
|
|
60
|
+
next();
|
|
61
|
+
});
|
|
62
|
+
sA.express.use((_req, _res, next) => {
|
|
63
|
+
callsA.push("second");
|
|
64
|
+
next();
|
|
65
|
+
});
|
|
66
|
+
const sB = buildServer();
|
|
67
|
+
sB.express.use((_req, _res, next) => {
|
|
68
|
+
callsB.push("first");
|
|
69
|
+
next();
|
|
70
|
+
});
|
|
71
|
+
sB.use((_req, _res, next) => {
|
|
72
|
+
callsB.push("second");
|
|
73
|
+
next();
|
|
74
|
+
});
|
|
75
|
+
for (const s of [sA, sB]) {
|
|
76
|
+
s.express.get("/probe", (_req, res) => res.json({ ok: true }));
|
|
77
|
+
const httpServer = http.createServer();
|
|
78
|
+
await createApp({ mcpServer: s, httpServer });
|
|
79
|
+
const { port, server: listening } = await listen(s.express);
|
|
80
|
+
openServer = listening;
|
|
81
|
+
await fetch(`http://localhost:${port}/probe`);
|
|
82
|
+
listening.close();
|
|
83
|
+
}
|
|
84
|
+
expect(callsA).toEqual(["first", "second"]);
|
|
85
|
+
expect(callsB).toEqual(["first", "second"]);
|
|
86
|
+
});
|
|
87
|
+
it("useOnError still wraps thrown /mcp errors after the route is mounted", async () => {
|
|
88
|
+
const { createApp } = await import("./express.js");
|
|
89
|
+
const server = new McpServer({ name: "t", version: "0.0.0" });
|
|
90
|
+
// Register the error handler BEFORE createApp — useOnError should still
|
|
91
|
+
// apply it after /mcp, so /mcp errors hit it.
|
|
92
|
+
const seen = [];
|
|
93
|
+
server.useOnError((_err, _req, res, _next) => {
|
|
94
|
+
seen.push("useOnError");
|
|
95
|
+
res.status(503).json({ from: "useOnError" });
|
|
96
|
+
});
|
|
97
|
+
// Force the /mcp handler to throw so the error pipeline runs.
|
|
98
|
+
vi.spyOn(server, "connectStatelessTransport").mockRejectedValue(new Error("boom"));
|
|
99
|
+
const httpServer = http.createServer();
|
|
100
|
+
await createApp({
|
|
101
|
+
mcpServer: server,
|
|
102
|
+
httpServer,
|
|
103
|
+
// Mirror what run() does: forward the McpServer's useOnError handlers
|
|
104
|
+
// to createApp so they get applied after /mcp.
|
|
105
|
+
// biome-ignore lint/complexity/useLiteralKeys: test mirrors run() internals to verify useOnError ordering
|
|
106
|
+
errorMiddleware: server["customErrorMiddleware"],
|
|
107
|
+
});
|
|
108
|
+
const { port, server: listening } = await listen(server.express);
|
|
109
|
+
openServer = listening;
|
|
110
|
+
const res = await postMcp(port);
|
|
111
|
+
expect(seen).toEqual(["useOnError"]);
|
|
112
|
+
expect(res.status).toBe(503);
|
|
113
|
+
expect(await res.json()).toEqual({ from: "useOnError" });
|
|
114
|
+
});
|
|
115
|
+
});
|
|
29
116
|
describe("createApp", () => {
|
|
30
117
|
it("runs global custom middleware before the /mcp handler", async () => {
|
|
31
118
|
const { createApp } = await import("./express.js");
|
|
@@ -34,14 +121,12 @@ describe("createApp", () => {
|
|
|
34
121
|
calls.push("custom");
|
|
35
122
|
next();
|
|
36
123
|
};
|
|
124
|
+
const server = new McpServer({ name: "t", version: "0.0.0" });
|
|
125
|
+
server.use(mw);
|
|
37
126
|
const httpServer = http.createServer();
|
|
38
|
-
const app = await createApp({
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
customMiddleware: [{ handlers: [mw] }],
|
|
42
|
-
});
|
|
43
|
-
const { port, server } = await listen(app);
|
|
44
|
-
openServer = server;
|
|
127
|
+
const app = await createApp({ mcpServer: server, httpServer });
|
|
128
|
+
const { port, server: httpListening } = await listen(app);
|
|
129
|
+
openServer = httpListening;
|
|
45
130
|
await postMcp(port);
|
|
46
131
|
expect(calls).toEqual(["custom"]);
|
|
47
132
|
});
|
|
@@ -52,14 +137,12 @@ describe("createApp", () => {
|
|
|
52
137
|
calls.push("auth");
|
|
53
138
|
next();
|
|
54
139
|
};
|
|
140
|
+
const server = new McpServer({ name: "t", version: "0.0.0" });
|
|
141
|
+
server.use("/mcp", mw);
|
|
55
142
|
const httpServer = http.createServer();
|
|
56
|
-
const app = await createApp({
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
customMiddleware: [{ path: "/mcp", handlers: [mw] }],
|
|
60
|
-
});
|
|
61
|
-
const { port, server } = await listen(app);
|
|
62
|
-
openServer = server;
|
|
143
|
+
const app = await createApp({ mcpServer: server, httpServer });
|
|
144
|
+
const { port, server: httpListening } = await listen(app);
|
|
145
|
+
openServer = httpListening;
|
|
63
146
|
await postMcp(port);
|
|
64
147
|
expect(calls).toEqual(["auth"]);
|
|
65
148
|
});
|
|
@@ -70,14 +153,12 @@ describe("createApp", () => {
|
|
|
70
153
|
calls.push("reject");
|
|
71
154
|
res.status(401).json({ error: "Unauthorized" });
|
|
72
155
|
};
|
|
156
|
+
const server = new McpServer({ name: "t", version: "0.0.0" });
|
|
157
|
+
server.use("/mcp", reject);
|
|
73
158
|
const httpServer = http.createServer();
|
|
74
|
-
const app = await createApp({
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
customMiddleware: [{ path: "/mcp", handlers: [reject] }],
|
|
78
|
-
});
|
|
79
|
-
const { port, server } = await listen(app);
|
|
80
|
-
openServer = server;
|
|
159
|
+
const app = await createApp({ mcpServer: server, httpServer });
|
|
160
|
+
const { port, server: httpListening } = await listen(app);
|
|
161
|
+
openServer = httpListening;
|
|
81
162
|
const res = await postMcp(port);
|
|
82
163
|
expect(calls).toEqual(["reject"]);
|
|
83
164
|
expect(res.status).toBe(401);
|
|
@@ -94,14 +175,13 @@ describe("createApp", () => {
|
|
|
94
175
|
calls.push("B");
|
|
95
176
|
next();
|
|
96
177
|
};
|
|
178
|
+
const server = new McpServer({ name: "t", version: "0.0.0" });
|
|
179
|
+
server.use(mwA);
|
|
180
|
+
server.use(mwB);
|
|
97
181
|
const httpServer = http.createServer();
|
|
98
|
-
const app = await createApp({
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
customMiddleware: [{ handlers: [mwA] }, { handlers: [mwB] }],
|
|
102
|
-
});
|
|
103
|
-
const { port, server } = await listen(app);
|
|
104
|
-
openServer = server;
|
|
182
|
+
const app = await createApp({ mcpServer: server, httpServer });
|
|
183
|
+
const { port, server: httpListening } = await listen(app);
|
|
184
|
+
openServer = httpListening;
|
|
105
185
|
await postMcp(port);
|
|
106
186
|
expect(calls).toEqual(["A", "B"]);
|
|
107
187
|
});
|
|
@@ -112,14 +192,12 @@ describe("createApp", () => {
|
|
|
112
192
|
calls.push("api");
|
|
113
193
|
next();
|
|
114
194
|
};
|
|
195
|
+
const server = new McpServer({ name: "t", version: "0.0.0" });
|
|
196
|
+
server.use("/api", apiMw);
|
|
115
197
|
const httpServer = http.createServer();
|
|
116
|
-
const app = await createApp({
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
customMiddleware: [{ path: "/api", handlers: [apiMw] }],
|
|
120
|
-
});
|
|
121
|
-
const { port, server } = await listen(app);
|
|
122
|
-
openServer = server;
|
|
198
|
+
const app = await createApp({ mcpServer: server, httpServer });
|
|
199
|
+
const { port, server: httpListening } = await listen(app);
|
|
200
|
+
openServer = httpListening;
|
|
123
201
|
// Hit /mcp — the /api middleware should NOT fire
|
|
124
202
|
await postMcp(port);
|
|
125
203
|
expect(calls).toEqual([]);
|
|
@@ -131,14 +209,12 @@ describe("createApp", () => {
|
|
|
131
209
|
router.get("/health", (_req, res) => {
|
|
132
210
|
res.json({ status: "ok" });
|
|
133
211
|
});
|
|
212
|
+
const server = new McpServer({ name: "t", version: "0.0.0" });
|
|
213
|
+
server.use(router);
|
|
134
214
|
const httpServer = http.createServer();
|
|
135
|
-
const app = await createApp({
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
customMiddleware: [{ handlers: [router] }],
|
|
139
|
-
});
|
|
140
|
-
const { port, server } = await listen(app);
|
|
141
|
-
openServer = server;
|
|
215
|
+
const app = await createApp({ mcpServer: server, httpServer });
|
|
216
|
+
const { port, server: httpListening } = await listen(app);
|
|
217
|
+
openServer = httpListening;
|
|
142
218
|
const res = await fetch(`http://localhost:${port}/health`);
|
|
143
219
|
expect(res.status).toBe(200);
|
|
144
220
|
expect(await res.json()).toEqual({ status: "ok" });
|
|
@@ -150,16 +226,12 @@ describe("createApp", () => {
|
|
|
150
226
|
router.get("/data", (_req, res) => {
|
|
151
227
|
res.json({ value: 42 });
|
|
152
228
|
});
|
|
229
|
+
const server = new McpServer({ name: "t", version: "0.0.0" });
|
|
230
|
+
server.use("/api", router);
|
|
153
231
|
const httpServer = http.createServer();
|
|
154
|
-
const app = await createApp({
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
customMiddleware: [
|
|
158
|
-
{ path: "/api", handlers: [router] },
|
|
159
|
-
],
|
|
160
|
-
});
|
|
161
|
-
const { port, server } = await listen(app);
|
|
162
|
-
openServer = server;
|
|
232
|
+
const app = await createApp({ mcpServer: server, httpServer });
|
|
233
|
+
const { port, server: httpListening } = await listen(app);
|
|
234
|
+
openServer = httpListening;
|
|
163
235
|
const res = await fetch(`http://localhost:${port}/api/data`);
|
|
164
236
|
expect(res.status).toBe(200);
|
|
165
237
|
expect(await res.json()).toEqual({ value: 42 });
|
|
@@ -169,14 +241,12 @@ describe("createApp", () => {
|
|
|
169
241
|
const throwing = () => {
|
|
170
242
|
throw new Error("boom");
|
|
171
243
|
};
|
|
244
|
+
const server = new McpServer({ name: "t", version: "0.0.0" });
|
|
245
|
+
server.use("/explode", throwing);
|
|
172
246
|
const httpServer = http.createServer();
|
|
173
|
-
const app = await createApp({
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
customMiddleware: [{ path: "/explode", handlers: [throwing] }],
|
|
177
|
-
});
|
|
178
|
-
const { port, server } = await listen(app);
|
|
179
|
-
openServer = server;
|
|
247
|
+
const app = await createApp({ mcpServer: server, httpServer });
|
|
248
|
+
const { port, server: httpListening } = await listen(app);
|
|
249
|
+
openServer = httpListening;
|
|
180
250
|
const res = await fetch(`http://localhost:${port}/explode`);
|
|
181
251
|
expect(res.status).toBe(500);
|
|
182
252
|
// Server process did not crash — it still accepts connections
|
|
@@ -186,10 +256,15 @@ describe("createApp", () => {
|
|
|
186
256
|
it("returns 500 JSON-RPC error when the MCP handler throws and no error middleware is registered", async () => {
|
|
187
257
|
const { createApp } = await import("./express.js");
|
|
188
258
|
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
259
|
+
const mcpServer = new McpServer({ name: "t", version: "0.0.0" });
|
|
260
|
+
// Force the express-level error path: make connectStatelessTransport
|
|
261
|
+
// reject so the request handler hits its try/catch and calls next(error),
|
|
262
|
+
// which lands in the default /mcp error handler.
|
|
263
|
+
vi.spyOn(mcpServer, "connectStatelessTransport").mockRejectedValue(new Error("boom"));
|
|
189
264
|
const httpServer = http.createServer();
|
|
190
|
-
const app = await createApp({ mcpServer
|
|
191
|
-
const { port, server } = await listen(app);
|
|
192
|
-
openServer =
|
|
265
|
+
const app = await createApp({ mcpServer, httpServer });
|
|
266
|
+
const { port, server: httpListening } = await listen(app);
|
|
267
|
+
openServer = httpListening;
|
|
193
268
|
const res = await postMcp(port);
|
|
194
269
|
expect(res.status).toBe(500);
|
|
195
270
|
expect(await res.json()).toEqual({
|
|
@@ -207,14 +282,16 @@ describe("createApp", () => {
|
|
|
207
282
|
calls.push("error-handler");
|
|
208
283
|
res.status(503).json({ custom: true });
|
|
209
284
|
};
|
|
285
|
+
const mcpServer = new McpServer({ name: "t", version: "0.0.0" });
|
|
286
|
+
vi.spyOn(mcpServer, "connectStatelessTransport").mockRejectedValue(new Error("boom"));
|
|
210
287
|
const httpServer = http.createServer();
|
|
211
288
|
const app = await createApp({
|
|
212
|
-
mcpServer
|
|
289
|
+
mcpServer,
|
|
213
290
|
httpServer,
|
|
214
291
|
errorMiddleware: [{ handlers: [errorHandler] }],
|
|
215
292
|
});
|
|
216
|
-
const { port, server } = await listen(app);
|
|
217
|
-
openServer =
|
|
293
|
+
const { port, server: httpListening } = await listen(app);
|
|
294
|
+
openServer = httpListening;
|
|
218
295
|
const res = await postMcp(port);
|
|
219
296
|
expect(calls).toEqual(["error-handler"]);
|
|
220
297
|
expect(res.status).toBe(503);
|
|
@@ -230,15 +307,17 @@ describe("createApp", () => {
|
|
|
230
307
|
const throwingApiRoute = (_req, _res, next) => {
|
|
231
308
|
next(new Error("api error"));
|
|
232
309
|
};
|
|
310
|
+
const mcpServer = new McpServer({ name: "t", version: "0.0.0" });
|
|
311
|
+
vi.spyOn(mcpServer, "connectStatelessTransport").mockRejectedValue(new Error("boom"));
|
|
312
|
+
mcpServer.use("/api/test", throwingApiRoute);
|
|
233
313
|
const httpServer = http.createServer();
|
|
234
314
|
const app = await createApp({
|
|
235
|
-
mcpServer
|
|
315
|
+
mcpServer,
|
|
236
316
|
httpServer,
|
|
237
|
-
customMiddleware: [{ path: "/api/test", handlers: [throwingApiRoute] }],
|
|
238
317
|
errorMiddleware: [{ path: "/mcp", handlers: [mcpErrorHandler] }],
|
|
239
318
|
});
|
|
240
|
-
const { port, server } = await listen(app);
|
|
241
|
-
openServer =
|
|
319
|
+
const { port, server: httpListening } = await listen(app);
|
|
320
|
+
openServer = httpListening;
|
|
242
321
|
const mcpRes = await postMcp(port);
|
|
243
322
|
expect(calls).toEqual(["mcp-error-handler"]);
|
|
244
323
|
expect(mcpRes.status).toBe(503);
|
|
@@ -286,4 +365,66 @@ describe("createApp", () => {
|
|
|
286
365
|
consoleSpy.mockRestore();
|
|
287
366
|
});
|
|
288
367
|
});
|
|
368
|
+
describe("createApp tunnel routes", () => {
|
|
369
|
+
it("proxies POST /__skybridge/tunnel to the cli control server in dev mode", async () => {
|
|
370
|
+
// Stand up a fake control listener that returns a known JSON body.
|
|
371
|
+
const control = http.createServer((_req, res) => {
|
|
372
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
373
|
+
res.end('{"status":"idle"}');
|
|
374
|
+
});
|
|
375
|
+
await new Promise((resolve) => control.listen(0, "127.0.0.1", resolve));
|
|
376
|
+
const controlAddr = control.address();
|
|
377
|
+
if (typeof controlAddr === "string" || controlAddr === null) {
|
|
378
|
+
control.close();
|
|
379
|
+
throw new Error("control server has no address");
|
|
380
|
+
}
|
|
381
|
+
const controlPort = controlAddr.port;
|
|
382
|
+
const prev = process.env.__TUNNEL_CONTROL_PORT;
|
|
383
|
+
process.env.__TUNNEL_CONTROL_PORT = String(controlPort);
|
|
384
|
+
try {
|
|
385
|
+
const { createApp } = await import("./express.js");
|
|
386
|
+
const mcpServer = new McpServer({ name: "t", version: "0.0.0" });
|
|
387
|
+
const httpServer = http.createServer();
|
|
388
|
+
const app = await createApp({ mcpServer, httpServer });
|
|
389
|
+
const { port, server } = await listen(app);
|
|
390
|
+
openServer = server;
|
|
391
|
+
const res = await fetch(`http://localhost:${port}/__skybridge/tunnel`, {
|
|
392
|
+
method: "POST",
|
|
393
|
+
});
|
|
394
|
+
expect(res.status).toBe(200);
|
|
395
|
+
expect(await res.json()).toEqual({ status: "idle" });
|
|
396
|
+
}
|
|
397
|
+
finally {
|
|
398
|
+
if (prev === undefined) {
|
|
399
|
+
delete process.env.__TUNNEL_CONTROL_PORT;
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
process.env.__TUNNEL_CONTROL_PORT = prev;
|
|
403
|
+
}
|
|
404
|
+
await new Promise((resolve) => control.close(() => resolve()));
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
it("does not expose /__skybridge/tunnel in production mode", async () => {
|
|
408
|
+
const prevEnv = process.env.NODE_ENV;
|
|
409
|
+
process.env.NODE_ENV = "production";
|
|
410
|
+
try {
|
|
411
|
+
vi.resetModules();
|
|
412
|
+
const { createApp } = await import("./express.js");
|
|
413
|
+
const { McpServer: ReloadedMcpServer } = await import("./server.js");
|
|
414
|
+
const mcpServer = new ReloadedMcpServer({ name: "t", version: "0.0.0" });
|
|
415
|
+
const httpServer = http.createServer();
|
|
416
|
+
const app = await createApp({ mcpServer, httpServer });
|
|
417
|
+
const { port, server } = await listen(app);
|
|
418
|
+
openServer = server;
|
|
419
|
+
const res = await fetch(`http://localhost:${port}/__skybridge/tunnel`, {
|
|
420
|
+
method: "POST",
|
|
421
|
+
});
|
|
422
|
+
expect(res.status).toBe(404);
|
|
423
|
+
}
|
|
424
|
+
finally {
|
|
425
|
+
process.env.NODE_ENV = prevEnv;
|
|
426
|
+
vi.resetModules();
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
});
|
|
289
430
|
//# sourceMappingURL=express.test.js.map
|