skybridge 0.0.0-dev.89e1390 → 0.0.0-dev.89e35fa
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/dist/cli/use-open-tunnel-browser.d.ts +6 -0
- package/dist/cli/use-open-tunnel-browser.js +19 -0
- package/dist/cli/use-open-tunnel-browser.js.map +1 -0
- package/dist/commands/dev.js +3 -1
- package/dist/commands/dev.js.map +1 -1
- package/dist/server/express.test.js +0 -31
- package/dist/server/express.test.js.map +1 -1
- package/dist/server/index.d.ts +1 -2
- package/dist/server/index.js.map +1 -1
- package/dist/server/server.d.ts +14 -29
- package/dist/server/server.js +80 -163
- package/dist/server/server.js.map +1 -1
- package/dist/test/view.test.js +57 -99
- package/dist/test/view.test.js.map +1 -1
- package/dist/web/bridges/adaptor.d.ts +50 -0
- package/dist/web/bridges/adaptor.js +314 -0
- package/dist/web/bridges/adaptor.js.map +1 -0
- package/dist/web/bridges/adaptor.test.js +205 -0
- package/dist/web/bridges/adaptor.test.js.map +1 -0
- package/dist/web/bridges/apps-sdk/bridge.d.ts +1 -1
- package/dist/web/bridges/apps-sdk/bridge.js +3 -4
- package/dist/web/bridges/apps-sdk/bridge.js.map +1 -1
- package/dist/web/bridges/apps-sdk/index.d.ts +0 -1
- package/dist/web/bridges/apps-sdk/index.js +0 -1
- package/dist/web/bridges/apps-sdk/index.js.map +1 -1
- package/dist/web/bridges/get-adaptor.d.ts +6 -4
- package/dist/web/bridges/get-adaptor.js +16 -9
- package/dist/web/bridges/get-adaptor.js.map +1 -1
- package/dist/web/bridges/get-adaptor.test.js +32 -0
- package/dist/web/bridges/get-adaptor.test.js.map +1 -0
- package/dist/web/bridges/host-context-stores.d.ts +27 -0
- package/dist/web/bridges/host-context-stores.js +73 -0
- package/dist/web/bridges/host-context-stores.js.map +1 -0
- package/dist/web/bridges/mcp-app/bridge.d.ts +3 -4
- package/dist/web/bridges/mcp-app/bridge.js +2 -26
- package/dist/web/bridges/mcp-app/bridge.js.map +1 -1
- package/dist/web/bridges/mcp-app/bridge.test.js +21 -0
- package/dist/web/bridges/mcp-app/bridge.test.js.map +1 -0
- package/dist/web/bridges/mcp-app/index.d.ts +0 -1
- package/dist/web/bridges/mcp-app/index.js +0 -1
- package/dist/web/bridges/mcp-app/index.js.map +1 -1
- package/dist/web/bridges/types.d.ts +16 -34
- package/dist/web/bridges/types.js +14 -1
- package/dist/web/bridges/types.js.map +1 -1
- package/dist/web/bridges/types.test.d.ts +1 -0
- package/dist/web/bridges/types.test.js +19 -0
- package/dist/web/bridges/types.test.js.map +1 -0
- package/dist/web/components/modal-provider.js +4 -3
- package/dist/web/components/modal-provider.js.map +1 -1
- package/dist/web/create-store.test.js +7 -5
- package/dist/web/create-store.test.js.map +1 -1
- package/dist/web/data-llm.test.js +8 -5
- package/dist/web/data-llm.test.js.map +1 -1
- package/dist/web/generate-helpers.test-d.js +2 -4
- package/dist/web/generate-helpers.test-d.js.map +1 -1
- package/dist/web/hooks/index.d.ts +0 -1
- package/dist/web/hooks/index.js +0 -1
- package/dist/web/hooks/index.js.map +1 -1
- package/dist/web/hooks/use-call-tool.test.js +36 -23
- package/dist/web/hooks/use-call-tool.test.js.map +1 -1
- package/dist/web/hooks/use-display-mode.test.js +55 -20
- package/dist/web/hooks/use-display-mode.test.js.map +1 -1
- package/dist/web/hooks/use-download.test.js +11 -3
- package/dist/web/hooks/use-download.test.js.map +1 -1
- package/dist/web/hooks/use-files.test.js +10 -2
- package/dist/web/hooks/use-files.test.js.map +1 -1
- package/dist/web/hooks/use-layout.test.js +59 -26
- package/dist/web/hooks/use-layout.test.js.map +1 -1
- package/dist/web/hooks/use-open-external.test.js +19 -5
- package/dist/web/hooks/use-open-external.test.js.map +1 -1
- package/dist/web/hooks/use-request-close.test.js +34 -40
- package/dist/web/hooks/use-request-close.test.js.map +1 -1
- package/dist/web/hooks/use-request-modal.test.js +10 -0
- package/dist/web/hooks/use-request-modal.test.js.map +1 -1
- package/dist/web/hooks/use-request-size.test.js +35 -53
- package/dist/web/hooks/use-request-size.test.js.map +1 -1
- package/dist/web/hooks/use-set-open-in-app-url.test.js +28 -8
- package/dist/web/hooks/use-set-open-in-app-url.test.js.map +1 -1
- package/dist/web/hooks/use-tool-info.d.ts +7 -25
- package/dist/web/hooks/use-tool-info.js +8 -5
- package/dist/web/hooks/use-tool-info.js.map +1 -1
- package/dist/web/hooks/use-tool-info.test-d.js +29 -11
- package/dist/web/hooks/use-tool-info.test-d.js.map +1 -1
- package/dist/web/hooks/use-tool-info.test.js +42 -32
- package/dist/web/hooks/use-tool-info.test.js.map +1 -1
- package/dist/web/hooks/use-user.test.js +81 -35
- package/dist/web/hooks/use-user.test.js.map +1 -1
- package/dist/web/hooks/use-view-state.test.js +6 -2
- package/dist/web/hooks/use-view-state.test.js.map +1 -1
- package/package.json +3 -4
- package/dist/server/auth/index.d.ts +0 -20
- package/dist/server/auth/index.js +0 -2
- package/dist/server/auth/index.js.map +0 -1
- package/dist/server/auth/setup.d.ts +0 -4
- package/dist/server/auth/setup.js +0 -52
- package/dist/server/auth/setup.js.map +0 -1
- package/dist/server/auth/setup.test.js +0 -210
- package/dist/server/auth/setup.test.js.map +0 -1
- package/dist/server/auth/verify.d.ts +0 -11
- package/dist/server/auth/verify.js +0 -37
- package/dist/server/auth/verify.js.map +0 -1
- package/dist/server/auth/verify.test.js +0 -87
- package/dist/server/auth/verify.test.js.map +0 -1
- package/dist/server/requestOrigin.d.ts +0 -6
- package/dist/server/requestOrigin.js +0 -30
- package/dist/server/requestOrigin.js.map +0 -1
- package/dist/server/view-resource-resolution.test.d.ts +0 -6
- package/dist/server/view-resource-resolution.test.js +0 -88
- package/dist/server/view-resource-resolution.test.js.map +0 -1
- package/dist/web/bridges/apps-sdk/adaptor.d.ts +0 -29
- package/dist/web/bridges/apps-sdk/adaptor.js +0 -117
- package/dist/web/bridges/apps-sdk/adaptor.js.map +0 -1
- package/dist/web/bridges/mcp-app/adaptor.d.ts +0 -53
- package/dist/web/bridges/mcp-app/adaptor.js +0 -283
- package/dist/web/bridges/mcp-app/adaptor.js.map +0 -1
- package/dist/web/bridges/mcp-app/view-tools.test.js +0 -144
- package/dist/web/bridges/mcp-app/view-tools.test.js.map +0 -1
- package/dist/web/hooks/use-register-view-tool.d.ts +0 -38
- package/dist/web/hooks/use-register-view-tool.js +0 -50
- package/dist/web/hooks/use-register-view-tool.js.map +0 -1
- /package/dist/{server/auth/setup.test.d.ts → web/bridges/adaptor.test.d.ts} +0 -0
- /package/dist/{server/auth/verify.test.d.ts → web/bridges/get-adaptor.test.d.ts} +0 -0
- /package/dist/web/bridges/mcp-app/{view-tools.test.d.ts → bridge.test.d.ts} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge.js","sourceRoot":"","sources":["../../../../src/web/bridges/apps-sdk/bridge.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,sBAAsB,GAEvB,MAAM,YAAY,CAAC;AAEpB,
|
|
1
|
+
{"version":3,"file":"bridge.js","sourceRoot":"","sources":["../../../../src/web/bridges/apps-sdk/bridge.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,sBAAsB,GAEvB,MAAM,YAAY,CAAC;AAEpB,qFAAqF;AACrF,MAAM,OAAO,aAAa;IAChB,MAAM,CAAC,QAAQ,GAAyB,IAAI,CAAC;IAE9C,MAAM,CAAC,WAAW;QACvB,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAChC,MAAM,IAAI,KAAK,CACb,4DAA4D,CAC7D,CAAC;QACJ,CAAC;QACD,IAAI,aAAa,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;YACpC,aAAa,CAAC,QAAQ,GAAG,IAAI,aAAa,EAAE,CAAC;QAC/C,CAAC;QACD,OAAO,aAAa,CAAC,QAAQ,CAAC;IAChC,CAAC;IAEM,MAAM,CAAC,aAAa;QACzB,IAAI,aAAa,CAAC,QAAQ,EAAE,CAAC;YAC3B,aAAa,CAAC,QAAQ,GAAG,IAAI,CAAC;QAChC,CAAC;IACH,CAAC;IAIM,SAAS,CACd,SAAmE;QAEnE,MAAM,IAAI,GAAsC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC;YACtE,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QAChB,OAAO,CAAC,QAAoB,EAAE,EAAE;YAC9B,MAAM,eAAe,GAAG,CAAC,KAAsB,EAAE,EAAE;gBACjD,MAAM,iBAAiB,GAAG,IAAI,CAAC,IAAI,CACjC,CAAC,GAAG,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,SAAS,CACjD,CAAC;gBACF,IAAI,CAAC,iBAAiB,EAAE,CAAC;oBACvB,OAAO;gBACT,CAAC;gBACD,QAAQ,EAAE,CAAC;YACb,CAAC,CAAC;YAEF,MAAM,CAAC,gBAAgB,CAAC,sBAAsB,EAAE,eAAe,EAAE;gBAC/D,OAAO,EAAE,IAAI;aACd,CAAC,CAAC;YAEH,OAAO,GAAG,EAAE;gBACV,MAAM,CAAC,mBAAmB,CAAC,sBAAsB,EAAE,eAAe,CAAC,CAAC;YACtE,CAAC,CAAC;QACJ,CAAC,CAAC;IACJ,CAAC;IAEM,WAAW,GAAG,CAAiC,GAAM,EAAE,EAAE;QAC9D,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAChC,MAAM,IAAI,KAAK,CACb,+EAA+E,GAAG,8CAA8C,CACjI,CAAC;QACJ,CAAC;QAED,OAAO,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC,CAAC","sourcesContent":["import type { Bridge, Subscribe } from \"../types.js\";\nimport {\n type AppsSdkContext,\n SET_GLOBALS_EVENT_TYPE,\n type SetGlobalsEvent,\n} from \"./types.js\";\n\n/** @internal Singleton bridge over `window.openai` events. Used by `HostAdaptor`. */\nexport class AppsSdkBridge implements Bridge<AppsSdkContext> {\n private static instance: AppsSdkBridge | null = null;\n\n public static getInstance(): AppsSdkBridge {\n if (window.openai === undefined) {\n throw new Error(\n \"Apps SDK Bridge requires window.openai (Apps SDK runtime).\",\n );\n }\n if (AppsSdkBridge.instance === null) {\n AppsSdkBridge.instance = new AppsSdkBridge();\n }\n return AppsSdkBridge.instance;\n }\n\n public static resetInstance(): void {\n if (AppsSdkBridge.instance) {\n AppsSdkBridge.instance = null;\n }\n }\n\n public subscribe(key: keyof AppsSdkContext): Subscribe;\n public subscribe(keys: readonly (keyof AppsSdkContext)[]): Subscribe;\n public subscribe(\n keyOrKeys: keyof AppsSdkContext | readonly (keyof AppsSdkContext)[],\n ): Subscribe {\n const keys: readonly (keyof AppsSdkContext)[] = Array.isArray(keyOrKeys)\n ? keyOrKeys\n : [keyOrKeys];\n return (onChange: () => void) => {\n const handleSetGlobal = (event: SetGlobalsEvent) => {\n const hasRelevantChange = keys.some(\n (key) => event.detail.globals[key] !== undefined,\n );\n if (!hasRelevantChange) {\n return;\n }\n onChange();\n };\n\n window.addEventListener(SET_GLOBALS_EVENT_TYPE, handleSetGlobal, {\n passive: true,\n });\n\n return () => {\n window.removeEventListener(SET_GLOBALS_EVENT_TYPE, handleSetGlobal);\n };\n };\n }\n\n public getSnapshot = <K extends keyof AppsSdkContext>(key: K) => {\n if (window.openai === undefined) {\n throw new Error(\n `window.openai is not available. Make sure you're calling the hook requiring ${key} within the OpenAI iFrame skybridge runtime.`,\n );\n }\n\n return window.openai[key];\n };\n}\n"]}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
export { AppsSdkAdaptor } from "./adaptor.js";
|
|
2
1
|
export { AppsSdkBridge } from "./bridge.js";
|
|
3
2
|
export type { AppsSdkContext, AppsSdkMethods, AppsSdkWidgetState, ToolResponseEvent, } from "./types.js";
|
|
4
3
|
export { SET_GLOBALS_EVENT_TYPE, SetGlobalsEvent, TOOL_RESPONSE_EVENT_TYPE, } from "./types.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/web/bridges/apps-sdk/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/web/bridges/apps-sdk/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAO5C,OAAO,EACL,sBAAsB,EACtB,eAAe,EACf,wBAAwB,GACzB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC","sourcesContent":["export { AppsSdkBridge } from \"./bridge.js\";\nexport type {\n AppsSdkContext,\n AppsSdkMethods,\n AppsSdkWidgetState,\n ToolResponseEvent,\n} from \"./types.js\";\nexport {\n SET_GLOBALS_EVENT_TYPE,\n SetGlobalsEvent,\n TOOL_RESPONSE_EVENT_TYPE,\n} from \"./types.js\";\nexport { useAppsSdkContext } from \"./use-apps-sdk-context.js\";\n"]}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { Adaptor } from "./types.js";
|
|
2
2
|
/**
|
|
3
3
|
* @internal
|
|
4
|
-
* Resolve the host
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Resolve the single host {@link Adaptor} instance. Prefer the documented
|
|
5
|
+
* hooks (`useCallTool`, `useViewState`, etc.) over calling this directly;
|
|
6
|
+
* it's the escape hatch used by the hooks themselves and by advanced
|
|
7
|
+
* integrations.
|
|
8
8
|
*/
|
|
9
9
|
export declare const getAdaptor: () => Adaptor;
|
|
10
|
+
/** @internal Test-only. */
|
|
11
|
+
export declare const _resetAdaptor: () => void;
|
|
@@ -1,15 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { HostAdaptor } from "./adaptor.js";
|
|
2
|
+
let cached = null;
|
|
3
3
|
/**
|
|
4
4
|
* @internal
|
|
5
|
-
* Resolve the host
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* Resolve the single host {@link Adaptor} instance. Prefer the documented
|
|
6
|
+
* hooks (`useCallTool`, `useViewState`, etc.) over calling this directly;
|
|
7
|
+
* it's the escape hatch used by the hooks themselves and by advanced
|
|
8
|
+
* integrations.
|
|
9
9
|
*/
|
|
10
10
|
export const getAdaptor = () => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
if (cached) {
|
|
12
|
+
return cached;
|
|
13
|
+
}
|
|
14
|
+
cached = new HostAdaptor();
|
|
15
|
+
return cached;
|
|
16
|
+
};
|
|
17
|
+
/** @internal Test-only. */
|
|
18
|
+
export const _resetAdaptor = () => {
|
|
19
|
+
cached?.cleanup();
|
|
20
|
+
cached = null;
|
|
14
21
|
};
|
|
15
22
|
//# sourceMappingURL=get-adaptor.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-adaptor.js","sourceRoot":"","sources":["../../../src/web/bridges/get-adaptor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"get-adaptor.js","sourceRoot":"","sources":["../../../src/web/bridges/get-adaptor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAG3C,IAAI,MAAM,GAAuB,IAAI,CAAC;AAEtC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,GAAY,EAAE;IACtC,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;IAC3B,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAEF,2BAA2B;AAC3B,MAAM,CAAC,MAAM,aAAa,GAAG,GAAS,EAAE;IACtC,MAAM,EAAE,OAAO,EAAE,CAAC;IAClB,MAAM,GAAG,IAAI,CAAC;AAChB,CAAC,CAAC","sourcesContent":["import { HostAdaptor } from \"./adaptor.js\";\nimport type { Adaptor } from \"./types.js\";\n\nlet cached: HostAdaptor | null = null;\n\n/**\n * @internal\n * Resolve the single host {@link Adaptor} instance. Prefer the documented\n * hooks (`useCallTool`, `useViewState`, etc.) over calling this directly;\n * it's the escape hatch used by the hooks themselves and by advanced\n * integrations.\n */\nexport const getAdaptor = (): Adaptor => {\n if (cached) {\n return cached;\n }\n cached = new HostAdaptor();\n return cached;\n};\n\n/** @internal Test-only. */\nexport const _resetAdaptor = (): void => {\n cached?.cleanup();\n cached = null;\n};\n"]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { HostAdaptor } from "./adaptor.js";
|
|
3
|
+
import { AppsSdkBridge } from "./apps-sdk/bridge.js";
|
|
4
|
+
import { _resetAdaptor, getAdaptor } from "./get-adaptor.js";
|
|
5
|
+
import { McpAppBridge } from "./mcp-app/bridge.js";
|
|
6
|
+
describe("getAdaptor", () => {
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
_resetAdaptor();
|
|
9
|
+
McpAppBridge.resetInstance();
|
|
10
|
+
AppsSdkBridge.resetInstance();
|
|
11
|
+
vi.stubGlobal("parent", { postMessage: vi.fn() });
|
|
12
|
+
});
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
vi.unstubAllGlobals();
|
|
15
|
+
_resetAdaptor();
|
|
16
|
+
McpAppBridge.resetInstance();
|
|
17
|
+
AppsSdkBridge.resetInstance();
|
|
18
|
+
});
|
|
19
|
+
it("returns a HostAdaptor instance", () => {
|
|
20
|
+
vi.stubGlobal("skybridge", { hostType: "mcp-app" });
|
|
21
|
+
vi.stubGlobal("openai", undefined);
|
|
22
|
+
expect(getAdaptor()).toBeInstanceOf(HostAdaptor);
|
|
23
|
+
});
|
|
24
|
+
it("memoizes the instance", () => {
|
|
25
|
+
vi.stubGlobal("skybridge", { hostType: "mcp-app" });
|
|
26
|
+
vi.stubGlobal("openai", undefined);
|
|
27
|
+
const a = getAdaptor();
|
|
28
|
+
const b = getAdaptor();
|
|
29
|
+
expect(a).toBe(b);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
//# sourceMappingURL=get-adaptor.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-adaptor.test.js","sourceRoot":"","sources":["../../../src/web/bridges/get-adaptor.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,UAAU,CAAC,GAAG,EAAE;QACd,aAAa,EAAE,CAAC;QAChB,YAAY,CAAC,aAAa,EAAE,CAAC;QAC7B,aAAa,CAAC,aAAa,EAAE,CAAC;QAC9B,EAAE,CAAC,UAAU,CAAC,QAAQ,EAAE,EAAE,WAAW,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IACH,SAAS,CAAC,GAAG,EAAE;QACb,EAAE,CAAC,gBAAgB,EAAE,CAAC;QACtB,aAAa,EAAE,CAAC;QAChB,YAAY,CAAC,aAAa,EAAE,CAAC;QAC7B,aAAa,CAAC,aAAa,EAAE,CAAC;IAChC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;QACxC,EAAE,CAAC,UAAU,CAAC,WAAW,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC;QACpD,EAAE,CAAC,UAAU,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QACnC,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uBAAuB,EAAE,GAAG,EAAE;QAC/B,EAAE,CAAC,UAAU,CAAC,WAAW,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC;QACpD,EAAE,CAAC,UAAU,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QACnC,MAAM,CAAC,GAAG,UAAU,EAAE,CAAC;QACvB,MAAM,CAAC,GAAG,UAAU,EAAE,CAAC;QACvB,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\nimport { HostAdaptor } from \"./adaptor.js\";\nimport { AppsSdkBridge } from \"./apps-sdk/bridge.js\";\nimport { _resetAdaptor, getAdaptor } from \"./get-adaptor.js\";\nimport { McpAppBridge } from \"./mcp-app/bridge.js\";\n\ndescribe(\"getAdaptor\", () => {\n beforeEach(() => {\n _resetAdaptor();\n McpAppBridge.resetInstance();\n AppsSdkBridge.resetInstance();\n vi.stubGlobal(\"parent\", { postMessage: vi.fn() });\n });\n afterEach(() => {\n vi.unstubAllGlobals();\n _resetAdaptor();\n McpAppBridge.resetInstance();\n AppsSdkBridge.resetInstance();\n });\n\n it(\"returns a HostAdaptor instance\", () => {\n vi.stubGlobal(\"skybridge\", { hostType: \"mcp-app\" });\n vi.stubGlobal(\"openai\", undefined);\n expect(getAdaptor()).toBeInstanceOf(HostAdaptor);\n });\n\n it(\"memoizes the instance\", () => {\n vi.stubGlobal(\"skybridge\", { hostType: \"mcp-app\" });\n vi.stubGlobal(\"openai\", undefined);\n const a = getAdaptor();\n const b = getAdaptor();\n expect(a).toBe(b);\n });\n});\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { McpAppBridge } from "./mcp-app/bridge.js";
|
|
2
|
+
import type { HostContextStore } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Build the host-context store map sourced from the MCP App bridge.
|
|
5
|
+
* `display` and `viewState` are *not* included here; those are handled
|
|
6
|
+
* by the adaptor itself because they have transport-specific overlays
|
|
7
|
+
* and additional state (polyfill modal state, localStorage hydration).
|
|
8
|
+
*/
|
|
9
|
+
export declare function buildMcpContextStores(bridge: McpAppBridge): {
|
|
10
|
+
theme: HostContextStore<"theme">;
|
|
11
|
+
locale: HostContextStore<"locale">;
|
|
12
|
+
safeArea: HostContextStore<"safeArea">;
|
|
13
|
+
displayMode: HostContextStore<"displayMode">;
|
|
14
|
+
maxHeight: HostContextStore<"maxHeight">;
|
|
15
|
+
userAgent: HostContextStore<"userAgent">;
|
|
16
|
+
toolInput: HostContextStore<"toolInput">;
|
|
17
|
+
toolOutput: HostContextStore<"toolOutput">;
|
|
18
|
+
toolResponseMetadata: HostContextStore<"toolResponseMetadata">;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Build the Apps-SDK-sourced overlay stores (`display`, `viewState`).
|
|
22
|
+
* Used by `HostAdaptor` only when `window.openai` is present.
|
|
23
|
+
*/
|
|
24
|
+
export declare function buildAppsSdkOverlayStores(bridge: import("./apps-sdk/bridge.js").AppsSdkBridge): {
|
|
25
|
+
display: HostContextStore<"display">;
|
|
26
|
+
viewState: HostContextStore<"viewState">;
|
|
27
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { dequal } from "dequal/lite";
|
|
2
|
+
/**
|
|
3
|
+
* Build a memoized {@link HostContextStore} backed by one or more
|
|
4
|
+
* `McpAppBridge` context keys. Recomputed values are deep-compared via
|
|
5
|
+
* `dequal` so unchanged snapshots return the cached reference.
|
|
6
|
+
*/
|
|
7
|
+
function createMcpStore(bridge, keys, computeSnapshot) {
|
|
8
|
+
let cachedValue;
|
|
9
|
+
return {
|
|
10
|
+
subscribe: bridge.subscribe(keys),
|
|
11
|
+
getSnapshot: () => {
|
|
12
|
+
const context = Object.fromEntries(keys.map((k) => [k, bridge.getSnapshot(k)]));
|
|
13
|
+
const newValue = computeSnapshot(context);
|
|
14
|
+
if (cachedValue !== undefined && dequal(cachedValue, newValue)) {
|
|
15
|
+
return cachedValue;
|
|
16
|
+
}
|
|
17
|
+
cachedValue = newValue;
|
|
18
|
+
return newValue;
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Build the host-context store map sourced from the MCP App bridge.
|
|
24
|
+
* `display` and `viewState` are *not* included here; those are handled
|
|
25
|
+
* by the adaptor itself because they have transport-specific overlays
|
|
26
|
+
* and additional state (polyfill modal state, localStorage hydration).
|
|
27
|
+
*/
|
|
28
|
+
export function buildMcpContextStores(bridge) {
|
|
29
|
+
return {
|
|
30
|
+
theme: createMcpStore(bridge, ["theme"], ({ theme }) => theme ?? "light"),
|
|
31
|
+
locale: createMcpStore(bridge, ["locale"], ({ locale }) => locale ?? "en-US"),
|
|
32
|
+
safeArea: createMcpStore(bridge, ["safeAreaInsets"], ({ safeAreaInsets }) => ({
|
|
33
|
+
insets: safeAreaInsets ?? { top: 0, right: 0, bottom: 0, left: 0 },
|
|
34
|
+
})),
|
|
35
|
+
displayMode: createMcpStore(bridge, ["displayMode"], ({ displayMode }) => displayMode ?? "inline"),
|
|
36
|
+
maxHeight: createMcpStore(bridge, ["containerDimensions"], ({ containerDimensions }) => {
|
|
37
|
+
if (containerDimensions && "maxHeight" in containerDimensions) {
|
|
38
|
+
return containerDimensions.maxHeight;
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}),
|
|
42
|
+
userAgent: createMcpStore(bridge, ["platform", "deviceCapabilities"], ({ platform, deviceCapabilities }) => ({
|
|
43
|
+
device: {
|
|
44
|
+
type: platform === "web" ? "desktop" : (platform ?? "unknown"),
|
|
45
|
+
},
|
|
46
|
+
capabilities: {
|
|
47
|
+
hover: true,
|
|
48
|
+
touch: true,
|
|
49
|
+
...deviceCapabilities,
|
|
50
|
+
},
|
|
51
|
+
})),
|
|
52
|
+
toolInput: createMcpStore(bridge, ["toolInput"], ({ toolInput }) => toolInput ?? null),
|
|
53
|
+
toolOutput: createMcpStore(bridge, ["toolResult"], ({ toolResult }) => toolResult?.structuredContent ?? null),
|
|
54
|
+
toolResponseMetadata: createMcpStore(bridge, ["toolResult"], ({ toolResult }) => toolResult?._meta ?? null),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Build the Apps-SDK-sourced overlay stores (`display`, `viewState`).
|
|
59
|
+
* Used by `HostAdaptor` only when `window.openai` is present.
|
|
60
|
+
*/
|
|
61
|
+
export function buildAppsSdkOverlayStores(bridge) {
|
|
62
|
+
return {
|
|
63
|
+
display: {
|
|
64
|
+
subscribe: bridge.subscribe("view"),
|
|
65
|
+
getSnapshot: () => bridge.getSnapshot("view"),
|
|
66
|
+
},
|
|
67
|
+
viewState: {
|
|
68
|
+
subscribe: bridge.subscribe("widgetState"),
|
|
69
|
+
getSnapshot: () => bridge.getSnapshot("widgetState")?.modelContent ?? null,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=host-context-stores.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-context-stores.js","sourceRoot":"","sources":["../../../src/web/bridges/host-context-stores.ts"],"names":[],"mappings":"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","sourcesContent":["import { dequal } from \"dequal/lite\";\nimport type { McpAppBridge } from \"./mcp-app/bridge.js\";\nimport type { McpAppContext, McpAppContextKey } from \"./mcp-app/types.js\";\nimport type { HostContextStore } from \"./types.js\";\n\ntype PickContext<K extends readonly McpAppContextKey[]> = {\n [P in K[number]]: McpAppContext[P];\n};\n\n/**\n * Build a memoized {@link HostContextStore} backed by one or more\n * `McpAppBridge` context keys. Recomputed values are deep-compared via\n * `dequal` so unchanged snapshots return the cached reference.\n */\nfunction createMcpStore<const Keys extends readonly McpAppContextKey[], R>(\n bridge: McpAppBridge,\n keys: Keys,\n computeSnapshot: (context: PickContext<Keys>) => R,\n) {\n let cachedValue: R | undefined;\n return {\n subscribe: bridge.subscribe(keys),\n getSnapshot: () => {\n const context = Object.fromEntries(\n keys.map((k) => [k, bridge.getSnapshot(k)]),\n ) as PickContext<Keys>;\n const newValue = computeSnapshot(context);\n if (cachedValue !== undefined && dequal(cachedValue, newValue)) {\n return cachedValue;\n }\n cachedValue = newValue;\n return newValue;\n },\n };\n}\n\n/**\n * Build the host-context store map sourced from the MCP App bridge.\n * `display` and `viewState` are *not* included here; those are handled\n * by the adaptor itself because they have transport-specific overlays\n * and additional state (polyfill modal state, localStorage hydration).\n */\nexport function buildMcpContextStores(bridge: McpAppBridge): {\n theme: HostContextStore<\"theme\">;\n locale: HostContextStore<\"locale\">;\n safeArea: HostContextStore<\"safeArea\">;\n displayMode: HostContextStore<\"displayMode\">;\n maxHeight: HostContextStore<\"maxHeight\">;\n userAgent: HostContextStore<\"userAgent\">;\n toolInput: HostContextStore<\"toolInput\">;\n toolOutput: HostContextStore<\"toolOutput\">;\n toolResponseMetadata: HostContextStore<\"toolResponseMetadata\">;\n} {\n return {\n theme: createMcpStore(bridge, [\"theme\"], ({ theme }) => theme ?? \"light\"),\n locale: createMcpStore(\n bridge,\n [\"locale\"],\n ({ locale }) => locale ?? \"en-US\",\n ),\n safeArea: createMcpStore(\n bridge,\n [\"safeAreaInsets\"],\n ({ safeAreaInsets }) => ({\n insets: safeAreaInsets ?? { top: 0, right: 0, bottom: 0, left: 0 },\n }),\n ),\n displayMode: createMcpStore(\n bridge,\n [\"displayMode\"],\n ({ displayMode }) => displayMode ?? \"inline\",\n ),\n maxHeight: createMcpStore(\n bridge,\n [\"containerDimensions\"],\n ({ containerDimensions }) => {\n if (containerDimensions && \"maxHeight\" in containerDimensions) {\n return containerDimensions.maxHeight;\n }\n return undefined;\n },\n ),\n userAgent: createMcpStore(\n bridge,\n [\"platform\", \"deviceCapabilities\"],\n ({ platform, deviceCapabilities }) => ({\n device: {\n type: platform === \"web\" ? \"desktop\" : (platform ?? \"unknown\"),\n },\n capabilities: {\n hover: true,\n touch: true,\n ...deviceCapabilities,\n },\n }),\n ),\n toolInput: createMcpStore(\n bridge,\n [\"toolInput\"],\n ({ toolInput }) => toolInput ?? null,\n ),\n toolOutput: createMcpStore(\n bridge,\n [\"toolResult\"],\n ({ toolResult }) => toolResult?.structuredContent ?? null,\n ),\n toolResponseMetadata: createMcpStore(\n bridge,\n [\"toolResult\"],\n ({ toolResult }) => toolResult?._meta ?? null,\n ),\n };\n}\n\n/**\n * Build the Apps-SDK-sourced overlay stores (`display`, `viewState`).\n * Used by `HostAdaptor` only when `window.openai` is present.\n */\nexport function buildAppsSdkOverlayStores(\n bridge: import(\"./apps-sdk/bridge.js\").AppsSdkBridge,\n): {\n display: HostContextStore<\"display\">;\n viewState: HostContextStore<\"viewState\">;\n} {\n return {\n display: {\n subscribe: bridge.subscribe(\"view\"),\n getSnapshot: () => bridge.getSnapshot(\"view\"),\n },\n viewState: {\n subscribe: bridge.subscribe(\"widgetState\"),\n getSnapshot: () =>\n bridge.getSnapshot(\"widgetState\")?.modelContent ?? null,\n },\n };\n}\n"]}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { App } from "@modelcontextprotocol/ext-apps";
|
|
2
|
-
import {
|
|
3
|
-
import type {
|
|
2
|
+
import type { Implementation } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import type { Bridge, Subscribe } from "../types.js";
|
|
4
4
|
import type { McpAppContext, McpAppContextKey } from "./types.js";
|
|
5
|
-
/** @internal Singleton bridge over the `ext-apps` JSON-RPC App connection. Used by
|
|
5
|
+
/** @internal Singleton bridge over the `ext-apps` JSON-RPC App connection. Used by `HostAdaptor`. */
|
|
6
6
|
export declare class McpAppBridge implements Bridge<McpAppContext> {
|
|
7
7
|
private static instance;
|
|
8
8
|
context: McpAppContext;
|
|
@@ -14,7 +14,6 @@ export declare class McpAppBridge implements Bridge<McpAppContext> {
|
|
|
14
14
|
});
|
|
15
15
|
private connect;
|
|
16
16
|
getApp(): Promise<App>;
|
|
17
|
-
registerViewTool(config: ViewToolConfig, handler: AnyViewToolHandler): () => void;
|
|
18
17
|
static getInstance(options?: Partial<{
|
|
19
18
|
appInfo: Implementation;
|
|
20
19
|
}>): McpAppBridge;
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { App } from "@modelcontextprotocol/ext-apps";
|
|
2
|
-
|
|
3
|
-
import * as z from "zod";
|
|
4
|
-
/** @internal Singleton bridge over the `ext-apps` JSON-RPC App connection. Used by {@link McpAppAdaptor}. */
|
|
2
|
+
/** @internal Singleton bridge over the `ext-apps` JSON-RPC App connection. Used by `HostAdaptor`. */
|
|
5
3
|
export class McpAppBridge {
|
|
6
4
|
static instance = null;
|
|
7
5
|
context = {
|
|
@@ -13,10 +11,7 @@ export class McpAppBridge {
|
|
|
13
11
|
app;
|
|
14
12
|
connectPromise;
|
|
15
13
|
constructor(options) {
|
|
16
|
-
this.app = new App(options.appInfo
|
|
17
|
-
this.app.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
18
|
-
tools: [],
|
|
19
|
-
}));
|
|
14
|
+
this.app = new App(options.appInfo);
|
|
20
15
|
this.app.ontoolinput = (params) => {
|
|
21
16
|
this.updateContext({ toolInput: params.arguments ?? {} });
|
|
22
17
|
};
|
|
@@ -50,26 +45,7 @@ export class McpAppBridge {
|
|
|
50
45
|
await this.connectPromise;
|
|
51
46
|
return this.app;
|
|
52
47
|
}
|
|
53
|
-
registerViewTool(config, handler) {
|
|
54
|
-
const inputSchema = config.inputSchema
|
|
55
|
-
? z.object(config.inputSchema)
|
|
56
|
-
: z.object({});
|
|
57
|
-
const registered = this.app.registerTool(config.name, {
|
|
58
|
-
...(config.title !== undefined ? { title: config.title } : {}),
|
|
59
|
-
...(config.description !== undefined
|
|
60
|
-
? { description: config.description }
|
|
61
|
-
: {}),
|
|
62
|
-
inputSchema,
|
|
63
|
-
...(config.annotations ? { annotations: config.annotations } : {}),
|
|
64
|
-
}, handler);
|
|
65
|
-
return () => {
|
|
66
|
-
registered.remove();
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
48
|
static getInstance(options) {
|
|
70
|
-
if (window.skybridge.hostType !== "mcp-app") {
|
|
71
|
-
throw new Error("MCP App Bridge can only be used in the mcp-app runtime");
|
|
72
|
-
}
|
|
73
49
|
if (McpAppBridge.instance && options) {
|
|
74
50
|
console.warn("McpAppBridge.getInstance: options ignored, instance already exists");
|
|
75
51
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge.js","sourceRoot":"","sources":["../../../../src/web/bridges/mcp-app/bridge.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"bridge.js","sourceRoot":"","sources":["../../../../src/web/bridges/mcp-app/bridge.ts"],"names":[],"mappings":"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","sourcesContent":["import { App } from \"@modelcontextprotocol/ext-apps\";\nimport type { Implementation } from \"@modelcontextprotocol/sdk/types.js\";\nimport type { Bridge, Subscribe } from \"../types.js\";\nimport type { McpAppContext, McpAppContextKey } from \"./types.js\";\n\n/** @internal Singleton bridge over the `ext-apps` JSON-RPC App connection. Used by `HostAdaptor`. */\nexport class McpAppBridge implements Bridge<McpAppContext> {\n private static instance: McpAppBridge | null = null;\n public context: McpAppContext = {\n toolInput: null,\n toolCancelled: null,\n toolResult: null,\n };\n private listeners = new Map<McpAppContextKey, Set<() => void>>();\n private app: App;\n private connectPromise: Promise<void>;\n\n constructor(options: { appInfo: Implementation }) {\n this.app = new App(options.appInfo);\n\n this.app.ontoolinput = (params) => {\n this.updateContext({ toolInput: params.arguments ?? {} });\n };\n\n this.app.ontoolinputpartial = (params) => {\n this.updateContext({ toolInput: params.arguments ?? {} });\n };\n\n this.app.ontoolresult = (params) => {\n this.updateContext({ toolResult: params });\n };\n\n this.app.ontoolcancelled = (params) => {\n this.updateContext({ toolCancelled: params });\n };\n\n this.app.onhostcontextchanged = (params) => {\n this.updateContext(params);\n };\n\n this.connectPromise = this.connect();\n }\n\n private async connect() {\n try {\n await this.app.connect();\n const hostContext = this.app.getHostContext();\n if (hostContext) {\n this.updateContext(hostContext);\n }\n } catch (err) {\n console.error(err);\n }\n }\n\n public async getApp(): Promise<App> {\n await this.connectPromise;\n return this.app;\n }\n\n public static getInstance(\n options?: Partial<{ appInfo: Implementation }>,\n ): McpAppBridge {\n if (McpAppBridge.instance && options) {\n console.warn(\n \"McpAppBridge.getInstance: options ignored, instance already exists\",\n );\n }\n if (!McpAppBridge.instance) {\n const defaultOptions = {\n appInfo: { name: \"skybridge-app\", version: \"0.0.1\" },\n };\n McpAppBridge.instance = new McpAppBridge({\n ...defaultOptions,\n ...options,\n });\n }\n return McpAppBridge.instance;\n }\n\n public subscribe(key: McpAppContextKey): Subscribe;\n public subscribe(keys: readonly McpAppContextKey[]): Subscribe;\n public subscribe(\n keyOrKeys: McpAppContextKey | readonly McpAppContextKey[],\n ): Subscribe {\n const keys = Array.isArray(keyOrKeys) ? keyOrKeys : [keyOrKeys];\n return (onChange: () => void) => {\n for (const key of keys) {\n this.listeners.set(\n key,\n new Set([...(this.listeners.get(key) || []), onChange]),\n );\n }\n return () => {\n for (const key of keys) {\n this.listeners.get(key)?.delete(onChange);\n }\n };\n };\n }\n\n public getSnapshot<K extends keyof McpAppContext>(key: K): McpAppContext[K] {\n return this.context[key];\n }\n\n public cleanup = () => {\n this.listeners.clear();\n };\n\n public static resetInstance(): void {\n if (McpAppBridge.instance) {\n McpAppBridge.instance.cleanup();\n McpAppBridge.instance = null;\n }\n }\n\n private emit(key: McpAppContextKey) {\n this.listeners.get(key)?.forEach((listener) => {\n listener();\n });\n }\n\n private updateContext(context: Partial<McpAppContext>) {\n this.context = { ...this.context, ...context };\n for (const key of Object.keys(context)) {\n this.emit(key);\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { McpAppBridge } from "./bridge.js";
|
|
3
|
+
describe("McpAppBridge.getInstance", () => {
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
McpAppBridge.resetInstance();
|
|
6
|
+
vi.stubGlobal("parent", { postMessage: vi.fn() });
|
|
7
|
+
});
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
vi.unstubAllGlobals();
|
|
10
|
+
McpAppBridge.resetInstance();
|
|
11
|
+
});
|
|
12
|
+
it("instantiates when hostType is apps-sdk", () => {
|
|
13
|
+
vi.stubGlobal("skybridge", { hostType: "apps-sdk" });
|
|
14
|
+
expect(() => McpAppBridge.getInstance()).not.toThrow();
|
|
15
|
+
});
|
|
16
|
+
it("instantiates when hostType is mcp-app", () => {
|
|
17
|
+
vi.stubGlobal("skybridge", { hostType: "mcp-app" });
|
|
18
|
+
expect(() => McpAppBridge.getInstance()).not.toThrow();
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
//# sourceMappingURL=bridge.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge.test.js","sourceRoot":"","sources":["../../../../src/web/bridges/mcp-app/bridge.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,QAAQ,CAAC,0BAA0B,EAAE,GAAG,EAAE;IACxC,UAAU,CAAC,GAAG,EAAE;QACd,YAAY,CAAC,aAAa,EAAE,CAAC;QAC7B,EAAE,CAAC,UAAU,CAAC,QAAQ,EAAE,EAAE,WAAW,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IACH,SAAS,CAAC,GAAG,EAAE;QACb,EAAE,CAAC,gBAAgB,EAAE,CAAC;QACtB,YAAY,CAAC,aAAa,EAAE,CAAC;IAC/B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,EAAE,CAAC,UAAU,CAAC,WAAW,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC,CAAC;QACrD,MAAM,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,EAAE,CAAC,UAAU,CAAC,WAAW,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC;QACpD,MAAM,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IACzD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\nimport { McpAppBridge } from \"./bridge.js\";\n\ndescribe(\"McpAppBridge.getInstance\", () => {\n beforeEach(() => {\n McpAppBridge.resetInstance();\n vi.stubGlobal(\"parent\", { postMessage: vi.fn() });\n });\n afterEach(() => {\n vi.unstubAllGlobals();\n McpAppBridge.resetInstance();\n });\n\n it(\"instantiates when hostType is apps-sdk\", () => {\n vi.stubGlobal(\"skybridge\", { hostType: \"apps-sdk\" });\n expect(() => McpAppBridge.getInstance()).not.toThrow();\n });\n\n it(\"instantiates when hostType is mcp-app\", () => {\n vi.stubGlobal(\"skybridge\", { hostType: \"mcp-app\" });\n expect(() => McpAppBridge.getInstance()).not.toThrow();\n });\n});\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/web/bridges/mcp-app/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/web/bridges/mcp-app/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAM3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC","sourcesContent":["export { McpAppBridge } from \"./bridge.js\";\nexport type {\n McpAppContext,\n McpAppContextKey,\n McpToolState,\n} from \"./types.js\";\nexport { useMcpAppContext } from \"./use-mcp-app-context.js\";\n"]}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { CallToolResult, EmbeddedResource, ResourceLink, ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
|
|
1
|
+
import type { CallToolResult, EmbeddedResource, ResourceLink } from "@modelcontextprotocol/sdk/types.js";
|
|
3
2
|
import type { useSyncExternalStore } from "react";
|
|
4
3
|
import type { ViewHostType } from "../../server/index.js";
|
|
5
4
|
/**
|
|
@@ -144,37 +143,6 @@ export type DownloadParams = {
|
|
|
144
143
|
export type DownloadResult = {
|
|
145
144
|
isError?: boolean;
|
|
146
145
|
};
|
|
147
|
-
/**
|
|
148
|
-
* Args passed to a {@link ViewToolHandler}, inferred from the tool's
|
|
149
|
-
* `inputSchema` (optionality preserved). Mirrors the server's `registerTool`.
|
|
150
|
-
*/
|
|
151
|
-
export type InferViewToolArgs<Shape extends ZodRawShapeCompat> = {
|
|
152
|
-
[K in keyof Shape as undefined extends SchemaOutput<Shape[K]> ? never : K]: SchemaOutput<Shape[K]>;
|
|
153
|
-
} & {
|
|
154
|
-
[K in keyof Shape as undefined extends SchemaOutput<Shape[K]> ? K : never]?: SchemaOutput<Shape[K]>;
|
|
155
|
-
};
|
|
156
|
-
/**
|
|
157
|
-
* Declares a tool the view exposes to the host/model (the MCP Apps
|
|
158
|
-
* "app-provided tools" feature). Mirrors the server-side `registerTool` config.
|
|
159
|
-
* Namespace `name` (e.g. `chess_make_move`) to avoid clashing with server tools.
|
|
160
|
-
*/
|
|
161
|
-
export type ViewToolConfig<TInput extends ZodRawShapeCompat = ZodRawShapeCompat> = {
|
|
162
|
-
name: string;
|
|
163
|
-
title?: string;
|
|
164
|
-
description?: string;
|
|
165
|
-
inputSchema?: TInput;
|
|
166
|
-
annotations?: ToolAnnotations;
|
|
167
|
-
};
|
|
168
|
-
/**
|
|
169
|
-
* Value a {@link ViewToolHandler} returns — a standard MCP `CallToolResult`
|
|
170
|
-
* (`content` blocks plus optional `structuredContent` / `isError` / `_meta`),
|
|
171
|
-
* exactly as `ext-apps`' app tool callbacks return it.
|
|
172
|
-
*/
|
|
173
|
-
export type ViewToolResult = CallToolResult;
|
|
174
|
-
/** Handler run when the host calls a view tool. Receives validated, typed args. */
|
|
175
|
-
export type ViewToolHandler<TInput extends ZodRawShapeCompat = ZodRawShapeCompat> = (args: InferViewToolArgs<TInput>) => ViewToolResult | Promise<ViewToolResult>;
|
|
176
|
-
/** @internal Untyped handler signature stored by the adaptor/bridge after type erasure. */
|
|
177
|
-
export type AnyViewToolHandler = (args: Record<string, unknown>) => ViewToolResult | Promise<ViewToolResult>;
|
|
178
146
|
/**
|
|
179
147
|
* @internal
|
|
180
148
|
* Low-level interface every host bridge implements. End-user code should use
|
|
@@ -200,5 +168,19 @@ export interface Adaptor {
|
|
|
200
168
|
selectFiles(): Promise<FileMetadata[]>;
|
|
201
169
|
openModal(options: RequestModalOptions): void;
|
|
202
170
|
setOpenInAppUrl(href: string): Promise<void>;
|
|
203
|
-
|
|
171
|
+
/**
|
|
172
|
+
* Close a modal opened via {@link Adaptor.openModal}. In the Apps SDK
|
|
173
|
+
* runtime this is a no-op (the host owns modal lifecycle). In the MCP App
|
|
174
|
+
* runtime it dismisses the in-iframe polyfill.
|
|
175
|
+
*/
|
|
176
|
+
closeModal(): void;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Thrown when a host bridge method is called in a runtime that doesn't
|
|
180
|
+
* support it (e.g. `uploadFile` outside the Apps SDK runtime).
|
|
181
|
+
*/
|
|
182
|
+
export declare class NotSupportedError extends Error {
|
|
183
|
+
readonly method: string;
|
|
184
|
+
readonly reason?: string | undefined;
|
|
185
|
+
constructor(method: string, reason?: string | undefined);
|
|
204
186
|
}
|
|
@@ -1,2 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Thrown when a host bridge method is called in a runtime that doesn't
|
|
3
|
+
* support it (e.g. `uploadFile` outside the Apps SDK runtime).
|
|
4
|
+
*/
|
|
5
|
+
export class NotSupportedError extends Error {
|
|
6
|
+
method;
|
|
7
|
+
reason;
|
|
8
|
+
constructor(method, reason) {
|
|
9
|
+
super(`${method} is not supported in this runtime${reason ? `: ${reason}` : ""}`);
|
|
10
|
+
this.method = method;
|
|
11
|
+
this.reason = reason;
|
|
12
|
+
this.name = "NotSupportedError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
2
15
|
//# sourceMappingURL=types.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/web/bridges/types.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/web/bridges/types.ts"],"names":[],"mappings":"AA4MA;;;GAGG;AACH,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAExB;IACA;IAFlB,YACkB,MAAc,EACd,MAAe;QAE/B,KAAK,CACH,GAAG,MAAM,oCAAoC,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAC3E,CAAC;QALc,WAAM,GAAN,MAAM,CAAQ;QACd,WAAM,GAAN,MAAM,CAAS;QAK/B,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF","sourcesContent":["import type {\n CallToolResult,\n EmbeddedResource,\n ResourceLink,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport type { useSyncExternalStore } from \"react\";\nimport type { ViewHostType } from \"../../server/index.js\";\n\n/**\n * Globals injected on `window.skybridge` by the host. Tells the view which\n * runtime it's running under and where to reach the MCP server.\n */\nexport type SkybridgeProperties = {\n hostType: ViewHostType;\n serverUrl: string;\n};\n\ndeclare global {\n interface Window {\n skybridge: SkybridgeProperties;\n }\n}\n\n/** Arguments passed to a tool call. `null` for tools that take no input. */\nexport type CallToolArgs = Record<string, unknown> | null;\n\n/**\n * Result of a tool call as surfaced to the view: MCP `content` blocks plus\n * the typed `structuredContent` and optional `meta`. `isError` is set when\n * the server marks the call as failed.\n */\nexport type CallToolResponse = {\n content: CallToolResult[\"content\"];\n structuredContent: NonNullable<CallToolResult[\"structuredContent\"]>;\n isError: NonNullable<CallToolResult[\"isError\"]>;\n meta?: CallToolResult[\"_meta\"];\n};\n\n/**\n * How the view is laid out by the host. `\"modal\"` is host-driven (see\n * {@link useRequestModal}); `\"pip\"`, `\"inline\"`, and `\"fullscreen\"` are\n * requestable via {@link useDisplayMode}.\n */\nexport type DisplayMode = \"pip\" | \"inline\" | \"fullscreen\" | \"modal\";\n/** Subset of {@link DisplayMode} that the view can request from the host. */\nexport type RequestDisplayMode = Exclude<DisplayMode, \"modal\">;\n\n/** Host theme. Mirror this in your view's styling for a native feel. */\nexport type Theme = \"light\" | \"dark\";\n\n/** Coarse device class reported by the host. `\"unknown\"` when unavailable. */\nexport type DeviceType = \"mobile\" | \"tablet\" | \"desktop\" | \"unknown\";\n\n/** Pixel insets the view should keep clear of (notches, home indicators, etc.). */\nexport type SafeAreaInsets = {\n top: number;\n right: number;\n bottom: number;\n left: number;\n};\n\n/** Wrapper around {@link SafeAreaInsets} exposed via {@link useLayout}. */\nexport type SafeArea = {\n insets: SafeAreaInsets;\n};\n\n/** Device and input-capability hints exposed via {@link useUser}. */\nexport type UserAgent = {\n device: {\n type: DeviceType;\n };\n capabilities: {\n hover: boolean;\n touch: boolean;\n };\n};\n\n/**\n * Full snapshot of state the host exposes to the view. Most fields are\n * better accessed through their dedicated hooks (`useLayout`, `useUser`,\n * `useToolInfo`, etc.) — read this directly only for advanced cases.\n */\nexport interface HostContext {\n theme: Theme;\n locale: string;\n displayMode: DisplayMode;\n safeArea: SafeArea;\n maxHeight: number | undefined;\n userAgent: UserAgent;\n toolInput: Record<string, unknown> | null;\n toolOutput: Record<string, unknown> | null;\n toolResponseMetadata: Record<string, unknown> | null;\n display: {\n mode: DisplayMode;\n params?: Record<string, unknown>;\n };\n viewState: Record<string, unknown> | null;\n}\n\n/** @internal `useSyncExternalStore` subscribe signature, re-exported for bridge implementations. */\nexport type Subscribe = Parameters<typeof useSyncExternalStore>[0];\n\n/** @internal Bridge contract implemented by per-host bridge classes. */\nexport interface Bridge<Context> {\n subscribe(key: keyof Context): Subscribe;\n subscribe(keys: readonly (keyof Context)[]): Subscribe;\n getSnapshot<K extends keyof Context>(key: K): Context[K] | undefined;\n}\n\n/** @internal Per-key snapshot store backing {@link useHostContext}. */\nexport type HostContextStore<K extends keyof HostContext> = {\n subscribe: Subscribe;\n getSnapshot: () => HostContext[K];\n};\n\n/** Persisted view state shape (a plain object). See {@link useViewState}. */\nexport type ViewState = Record<string, unknown>;\n\n/** Updater form accepted when writing to view state. */\nexport type SetViewStateAction =\n | ViewState\n | ((prevState: ViewState | null) => ViewState);\n\n/** Reference to a host-managed file (returned by {@link useFiles}). */\nexport type FileMetadata = {\n fileId: string;\n fileName?: string;\n mimeType?: string;\n};\n\n/** Options for {@link useFiles}'s `upload`. `library: true` saves into the user's library when supported. */\nexport type UploadFileOptions = { library?: boolean };\n\n/** Options for {@link useRequestModal}'s `open` call. */\nexport type RequestModalOptions = {\n title?: string;\n params?: Record<string, unknown>;\n template?: string;\n anchor?: { top?: number; left?: number; width?: number; height?: number };\n};\n\n/**\n * Options for {@link useOpenExternal}. Set `redirectUrl: false` to tell the\n * host not to append its `?redirectUrl=…` tracking query parameter when\n * opening allowlisted targets.\n */\nexport type OpenExternalOptions = {\n redirectUrl?: false;\n};\n\n/** Options for {@link useSendFollowUpMessage}. */\nexport type SendFollowUpMessageOptions = { scrollToBottom?: boolean };\n\n/** Options for {@link useRequestSize}. Omit a dimension to leave it unchanged. */\nexport type RequestSizeOptions = {\n width?: number;\n height?: number;\n};\n\nexport type DownloadParams = {\n contents: (EmbeddedResource | ResourceLink)[];\n};\n\nexport type DownloadResult = {\n isError?: boolean;\n};\n\n/**\n * @internal\n * Low-level interface every host bridge implements. End-user code should use\n * the React hooks (`useCallTool`, `useViewState`, `useFiles`, …) rather than\n * calling this directly.\n */\nexport interface Adaptor {\n getHostContextStore<K extends keyof HostContext>(key: K): HostContextStore<K>;\n callTool<\n ToolArgs extends CallToolArgs = null,\n ToolResponse extends CallToolResponse = CallToolResponse,\n >(name: string, args: ToolArgs): Promise<ToolResponse>;\n requestDisplayMode(mode: RequestDisplayMode): Promise<{\n mode: RequestDisplayMode;\n }>;\n requestClose(): Promise<void>;\n requestSize(size: RequestSizeOptions): Promise<void>;\n sendFollowUpMessage(\n prompt: string,\n options?: SendFollowUpMessageOptions,\n ): Promise<void>;\n openExternal(href: string, options?: OpenExternalOptions): void;\n download(params: DownloadParams): Promise<DownloadResult>;\n setViewState(stateOrUpdater: SetViewStateAction): Promise<void>;\n uploadFile(file: File, options?: UploadFileOptions): Promise<FileMetadata>;\n getFileDownloadUrl(file: FileMetadata): Promise<{ downloadUrl: string }>;\n selectFiles(): Promise<FileMetadata[]>;\n openModal(options: RequestModalOptions): void;\n setOpenInAppUrl(href: string): Promise<void>;\n /**\n * Close a modal opened via {@link Adaptor.openModal}. In the Apps SDK\n * runtime this is a no-op (the host owns modal lifecycle). In the MCP App\n * runtime it dismisses the in-iframe polyfill.\n */\n closeModal(): void;\n}\n\n/**\n * Thrown when a host bridge method is called in a runtime that doesn't\n * support it (e.g. `uploadFile` outside the Apps SDK runtime).\n */\nexport class NotSupportedError extends Error {\n constructor(\n public readonly method: string,\n public readonly reason?: string,\n ) {\n super(\n `${method} is not supported in this runtime${reason ? `: ${reason}` : \"\"}`,\n );\n this.name = \"NotSupportedError\";\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { NotSupportedError } from "./types.js";
|
|
3
|
+
describe("NotSupportedError", () => {
|
|
4
|
+
it("includes method name in message", () => {
|
|
5
|
+
const err = new NotSupportedError("uploadFile");
|
|
6
|
+
expect(err.message).toBe("uploadFile is not supported in this runtime");
|
|
7
|
+
expect(err.name).toBe("NotSupportedError");
|
|
8
|
+
expect(err.method).toBe("uploadFile");
|
|
9
|
+
});
|
|
10
|
+
it("includes reason when provided", () => {
|
|
11
|
+
const err = new NotSupportedError("callTool", "MCP transport unavailable");
|
|
12
|
+
expect(err.message).toBe("callTool is not supported in this runtime: MCP transport unavailable");
|
|
13
|
+
expect(err.reason).toBe("MCP transport unavailable");
|
|
14
|
+
});
|
|
15
|
+
it("is an instance of Error", () => {
|
|
16
|
+
expect(new NotSupportedError("x")).toBeInstanceOf(Error);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
//# sourceMappingURL=types.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.test.js","sourceRoot":"","sources":["../../../src/web/bridges/types.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE/C,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,GAAG,GAAG,IAAI,iBAAiB,CAAC,YAAY,CAAC,CAAC;QAChD,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,6CAA6C,CAAC,CAAC;QACxE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;QAC3C,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,GAAG,GAAG,IAAI,iBAAiB,CAAC,UAAU,EAAE,2BAA2B,CAAC,CAAC;QAC3E,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,CACtB,sEAAsE,CACvE,CAAC;QACF,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yBAAyB,EAAE,GAAG,EAAE;QACjC,MAAM,CAAC,IAAI,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { NotSupportedError } from \"./types.js\";\n\ndescribe(\"NotSupportedError\", () => {\n it(\"includes method name in message\", () => {\n const err = new NotSupportedError(\"uploadFile\");\n expect(err.message).toBe(\"uploadFile is not supported in this runtime\");\n expect(err.name).toBe(\"NotSupportedError\");\n expect(err.method).toBe(\"uploadFile\");\n });\n\n it(\"includes reason when provided\", () => {\n const err = new NotSupportedError(\"callTool\", \"MCP transport unavailable\");\n expect(err.message).toBe(\n \"callTool is not supported in this runtime: MCP transport unavailable\",\n );\n expect(err.reason).toBe(\"MCP transport unavailable\");\n });\n\n it(\"is an instance of Error\", () => {\n expect(new NotSupportedError(\"x\")).toBeInstanceOf(Error);\n });\n});\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useSyncExternalStore } from "react";
|
|
3
|
-
import {
|
|
3
|
+
import { getAdaptor } from "../bridges/index.js";
|
|
4
4
|
const modalStyles = `
|
|
5
5
|
.sb-modal-backdrop {
|
|
6
6
|
position: fixed;
|
|
@@ -20,8 +20,9 @@ const modalStyles = `
|
|
|
20
20
|
}
|
|
21
21
|
`;
|
|
22
22
|
export function ModalProvider({ children }) {
|
|
23
|
-
const adaptor =
|
|
24
|
-
const
|
|
23
|
+
const adaptor = getAdaptor();
|
|
24
|
+
const displayStore = adaptor.getHostContextStore("display");
|
|
25
|
+
const { mode } = useSyncExternalStore(displayStore.subscribe, displayStore.getSnapshot);
|
|
25
26
|
const isOpen = mode === "modal";
|
|
26
27
|
const handleBackdropClick = (e) => {
|
|
27
28
|
if (e.target === e.currentTarget) {
|