From d27acc42b10a59494b852a89c160607587b3f15f Mon Sep 17 00:00:00 2001 From: Matthew Wang Date: Fri, 16 Jan 2026 21:20:10 -0800 Subject: [PATCH] Add and helper functions --- src/app-bridge.test.ts | 152 +++++++++++++++++++++++++++++++++++++++++ src/app-bridge.ts | 31 ++++++++- 2 files changed, 182 insertions(+), 1 deletion(-) diff --git a/src/app-bridge.test.ts b/src/app-bridge.test.ts index 66d5f830..875c10e4 100644 --- a/src/app-bridge.test.ts +++ b/src/app-bridge.test.ts @@ -17,6 +17,8 @@ import { App } from "./app"; import { AppBridge, getToolUiResourceUri, + isToolVisibilityModelOnly, + isToolVisibilityAppOnly, type McpUiHostCapabilities, } from "./app-bridge"; @@ -921,3 +923,153 @@ describe("getToolUiResourceUri", () => { }); }); }); + +describe("isToolVisibilityModelOnly", () => { + describe("returns true", () => { + it("when visibility is exactly ['model']", () => { + const tool = { + name: "test-tool", + _meta: { ui: { visibility: ["model"] } }, + }; + expect(isToolVisibilityModelOnly(tool)).toBe(true); + }); + }); + + describe("returns false", () => { + it("when visibility is ['app']", () => { + const tool = { + name: "test-tool", + _meta: { ui: { visibility: ["app"] } }, + }; + expect(isToolVisibilityModelOnly(tool)).toBe(false); + }); + + it("when visibility is ['model', 'app']", () => { + const tool = { + name: "test-tool", + _meta: { ui: { visibility: ["model", "app"] } }, + }; + expect(isToolVisibilityModelOnly(tool)).toBe(false); + }); + + it("when visibility is ['app', 'model']", () => { + const tool = { + name: "test-tool", + _meta: { ui: { visibility: ["app", "model"] } }, + }; + expect(isToolVisibilityModelOnly(tool)).toBe(false); + }); + + it("when visibility is empty array", () => { + const tool = { + name: "test-tool", + _meta: { ui: { visibility: [] } }, + }; + expect(isToolVisibilityModelOnly(tool)).toBe(false); + }); + + it("when visibility is undefined", () => { + const tool = { + name: "test-tool", + _meta: { ui: {} }, + }; + expect(isToolVisibilityModelOnly(tool)).toBe(false); + }); + + it("when _meta.ui is missing", () => { + const tool = { + name: "test-tool", + _meta: {}, + }; + expect(isToolVisibilityModelOnly(tool)).toBe(false); + }); + + it("when _meta is missing", () => { + const tool = { name: "test-tool" }; + expect(isToolVisibilityModelOnly(tool)).toBe(false); + }); + + it("when tool has resourceUri but no visibility", () => { + const tool = { + name: "test-tool", + _meta: { ui: { resourceUri: "ui://server/app.html" } }, + }; + expect(isToolVisibilityModelOnly(tool)).toBe(false); + }); + }); +}); + +describe("isToolVisibilityAppOnly", () => { + describe("returns true", () => { + it("when visibility is exactly ['app']", () => { + const tool = { + name: "test-tool", + _meta: { ui: { visibility: ["app"] } }, + }; + expect(isToolVisibilityAppOnly(tool)).toBe(true); + }); + }); + + describe("returns false", () => { + it("when visibility is ['model']", () => { + const tool = { + name: "test-tool", + _meta: { ui: { visibility: ["model"] } }, + }; + expect(isToolVisibilityAppOnly(tool)).toBe(false); + }); + + it("when visibility is ['model', 'app']", () => { + const tool = { + name: "test-tool", + _meta: { ui: { visibility: ["model", "app"] } }, + }; + expect(isToolVisibilityAppOnly(tool)).toBe(false); + }); + + it("when visibility is ['app', 'model']", () => { + const tool = { + name: "test-tool", + _meta: { ui: { visibility: ["app", "model"] } }, + }; + expect(isToolVisibilityAppOnly(tool)).toBe(false); + }); + + it("when visibility is empty array", () => { + const tool = { + name: "test-tool", + _meta: { ui: { visibility: [] } }, + }; + expect(isToolVisibilityAppOnly(tool)).toBe(false); + }); + + it("when visibility is undefined", () => { + const tool = { + name: "test-tool", + _meta: { ui: {} }, + }; + expect(isToolVisibilityAppOnly(tool)).toBe(false); + }); + + it("when _meta.ui is missing", () => { + const tool = { + name: "test-tool", + _meta: {}, + }; + expect(isToolVisibilityAppOnly(tool)).toBe(false); + }); + + it("when _meta is missing", () => { + const tool = { name: "test-tool" }; + expect(isToolVisibilityAppOnly(tool)).toBe(false); + }); + + it("when tool has resourceUri but no visibility", () => { + const tool = { + name: "test-tool", + _meta: { ui: { resourceUri: "ui://server/app.html" } }, + }; + expect(isToolVisibilityAppOnly(tool)).toBe(false); + }); + }); +}); diff --git a/src/app-bridge.ts b/src/app-bridge.ts index 2e969585..0d65d308 100644 --- a/src/app-bridge.ts +++ b/src/app-bridge.ts @@ -78,6 +78,7 @@ import { McpUiRequestDisplayModeRequestSchema, McpUiRequestDisplayModeResult, McpUiResourcePermissions, + McpUiToolMeta, } from "./types"; export * from "./types"; export { RESOURCE_URI_META_KEY, RESOURCE_MIME_TYPE } from "./app"; @@ -110,7 +111,7 @@ export { PostMessageTransport } from "./message-transport"; */ export function getToolUiResourceUri(tool: Partial): string | undefined { // Try new nested format first: _meta.ui.resourceUri - const uiMeta = tool._meta?.ui as { resourceUri?: unknown } | undefined; + const uiMeta = tool._meta?.ui as McpUiToolMeta | undefined; let uri: unknown = uiMeta?.resourceUri; // Fall back to deprecated flat format: _meta["ui/resourceUri"] @@ -126,6 +127,34 @@ export function getToolUiResourceUri(tool: Partial): string | undefined { return undefined; } +/** + * Check if a tool is visible to the model only. + * + * @param tool - Tool object with visibility metadata + * @returns True if the tool is visible to the model only, false otherwise + */ +export function isToolVisibilityModelOnly(tool: Partial): boolean { + const uiMeta = tool._meta?.ui as McpUiToolMeta | undefined; + const visibility = uiMeta?.visibility as Array<"model" | "app"> | undefined; + if (!visibility) return false; + if (visibility.length === 1 && visibility[0] === "model") return true; + return false; +} + +/** + * Check if a tool is visible to the app only. + * + * @param tool - Tool object with visibility metadata + * @returns True if the tool is visible to the app only, false otherwise + */ +export function isToolVisibilityAppOnly(tool: Partial): boolean { + const uiMeta = tool._meta?.ui as McpUiToolMeta | undefined; + const visibility = uiMeta?.visibility as Array<"model" | "app"> | undefined; + if (!visibility) return false; + if (visibility.length === 1 && visibility[0] === "app") return true; + return false; +} + /** * Build iframe `allow` attribute string from permissions. *