Merge pull request #294 from matteo8p/create-helper-functions-tool-visibility

Add `isToolVisibilityModelOnly` and `isToolVisibilityAppOnly` helper functions
This commit is contained in:
Anton Pidkuiko 2026-02-02 23:15:46 +00:00 • committed by GitHub
commit 398f5b4e4a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 182 additions and 1 deletions

View file

@ -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);
});
});
});

View file

@ -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<Tool>): 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<Tool>): 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<Tool>): 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<Tool>): 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.
*