feat(basic-host): improve host UI with collapsible panels and SDK types (#244)
* refactor(basic-host): use SDK types for ModelContext and AppMessage - Import McpUiUpdateModelContextRequest and McpUiMessageRequest from SDK - Replace custom ModelContext with McpUiUpdateModelContextRequest['params'] - Replace custom AppMessage with McpUiMessageRequest['params'] - Pass full message params instead of extracting text - Format messages by extracting text from content blocks in display * feat(basic-host): handle non-text content blocks in display - Add formatContentBlock helper for text, image, audio, resource types - Show placeholders like <image: image/png> for non-text content - Apply to both messages and model context display * test: update shadertoy screenshot * test: update screenshots from CI (linux x64)
This commit is contained in:
parent
e29523c113
commit
741b1d3925
1 changed files with 302 additions and 43 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { RESOURCE_MIME_TYPE, getToolUiResourceUri, type McpUiSandboxProxyReadyNotification, AppBridge, PostMessageTransport, type McpUiResourceCsp, type McpUiResourcePermissions } from "@modelcontextprotocol/ext-apps/app-bridge";
|
||||
import { RESOURCE_MIME_TYPE, getToolUiResourceUri, type McpUiSandboxProxyReadyNotification, AppBridge, PostMessageTransport, type McpUiResourceCsp, type McpUiResourcePermissions, buildAllowAttribute, type McpUiUpdateModelContextRequest, type McpUiMessageRequest } from "@modelcontextprotocol/ext-apps/app-bridge";
|
||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
||||
import type { CallToolResult, Tool } from "@modelcontextprotocol/sdk/types.js";
|
||||
|
|
@ -122,12 +122,19 @@ async function getUiResource(serverInfo: ServerInfo, uri: string): Promise<UiRes
|
|||
export function loadSandboxProxy(
|
||||
iframe: HTMLIFrameElement,
|
||||
csp?: McpUiResourceCsp,
|
||||
permissions?: McpUiResourcePermissions,
|
||||
): Promise<boolean> {
|
||||
// Prevent reload
|
||||
if (iframe.src) return Promise.resolve(false);
|
||||
|
||||
iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms");
|
||||
|
||||
// Set Permission Policy allow attribute based on requested permissions
|
||||
const allowAttribute = buildAllowAttribute(permissions);
|
||||
if (allowAttribute) {
|
||||
iframe.setAttribute("allow", allowAttribute);
|
||||
}
|
||||
|
||||
const readyNotification: McpUiSandboxProxyReadyNotification["method"] =
|
||||
"ui/notifications/sandbox-proxy-ready";
|
||||
|
||||
|
|
@ -215,12 +222,26 @@ function hookInitializedCallback(appBridge: AppBridge): Promise<void> {
|
|||
}
|
||||
|
||||
|
||||
export function newAppBridge(serverInfo: ServerInfo, iframe: HTMLIFrameElement): AppBridge {
|
||||
export type ModelContext = McpUiUpdateModelContextRequest["params"];
|
||||
export type AppMessage = McpUiMessageRequest["params"];
|
||||
|
||||
export interface AppBridgeCallbacks {
|
||||
onContextUpdate?: (context: ModelContext | null) => void;
|
||||
onMessage?: (message: AppMessage) => void;
|
||||
}
|
||||
|
||||
export function newAppBridge(
|
||||
serverInfo: ServerInfo,
|
||||
iframe: HTMLIFrameElement,
|
||||
callbacks?: AppBridgeCallbacks,
|
||||
): AppBridge {
|
||||
const serverCapabilities = serverInfo.client.getServerCapabilities();
|
||||
const appBridge = new AppBridge(serverInfo.client, IMPLEMENTATION, {
|
||||
openLinks: {},
|
||||
serverTools: serverCapabilities?.tools,
|
||||
serverResources: serverCapabilities?.resources,
|
||||
// Declare support for model context updates
|
||||
updateModelContext: { text: {} },
|
||||
});
|
||||
|
||||
// Register all handlers before calling connect(). The Guest UI can start
|
||||
|
|
@ -229,6 +250,7 @@ export function newAppBridge(serverInfo: ServerInfo, iframe: HTMLIFrameElement):
|
|||
|
||||
appBridge.onmessage = async (params, _extra) => {
|
||||
log.info("Message from MCP App:", params);
|
||||
callbacks?.onMessage?.(params);
|
||||
return {};
|
||||
};
|
||||
|
||||
|
|
@ -242,6 +264,15 @@ export function newAppBridge(serverInfo: ServerInfo, iframe: HTMLIFrameElement):
|
|||
log.info("Log message from MCP App:", params);
|
||||
};
|
||||
|
||||
appBridge.onupdatemodelcontext = async (params) => {
|
||||
log.info("Model context update from MCP App:", params);
|
||||
// Normalize: empty content array means clear context
|
||||
const hasContent = params.content && params.content.length > 0;
|
||||
const hasStructured = params.structuredContent && Object.keys(params.structuredContent).length > 0;
|
||||
callbacks?.onContextUpdate?.(hasContent || hasStructured ? params : null);
|
||||
return {};
|
||||
};
|
||||
|
||||
appBridge.onsizechange = async ({ width, height }) => {
|
||||
// The MCP App has requested a `width` and `height`, but if
|
||||
// `box-sizing: border-box` is applied to the outer iframe element, then we
|
||||
|
|
|
|||
Loading…
Reference in a new issue