reshuffle, use -vanilla consistently
This commit is contained in:
parent
24dd9691de
commit
e4e7edf284
3 changed files with 2 additions and 2 deletions
21
examples/simple-host/example-host-vanilla.html
Normal file
21
examples/simple-host/example-host-vanilla.html
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<style>
|
||||||
|
#chat-root {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script src="/src/example-host-vanilla.ts" type="module"></script>
|
||||||
|
<title>Example MCP View Host</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Example MCP View Host</h1>
|
||||||
|
|
||||||
|
<div id="controls"></div>
|
||||||
|
<div id="chat-root"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start:server": "tsx server.ts",
|
"start:server": "tsx server.ts",
|
||||||
"start:mcp-server": "cd ../simple-server && npm install && npm run start",
|
"start:mcp-server": "cd ../simple-server && npm install && npm run start",
|
||||||
"build": "concurrently 'INPUT=example-host.html vite build' 'INPUT=example-host-react.html vite build' 'INPUT=sandbox.html vite build'",
|
"build": "concurrently 'INPUT=example-host-vanilla.html vite build' 'INPUT=example-host-react.html vite build' 'INPUT=sandbox.html vite build'",
|
||||||
"server": "bun server.ts",
|
"server": "bun server.ts",
|
||||||
"start": "NODE_ENV=development npm run build && concurrently 'npm run start:server' 'npm run start:mcp-server'"
|
"start": "NODE_ENV=development npm run build && concurrently 'npm run start:server' 'npm run start:mcp-server'"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
159
examples/simple-host/src/example-host-vanilla.ts
Normal file
159
examples/simple-host/src/example-host-vanilla.ts
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
import {
|
||||||
|
AppBridge,
|
||||||
|
PostMessageTransport,
|
||||||
|
} from "@modelcontextprotocol/ext-apps/app-bridge";
|
||||||
|
import {
|
||||||
|
setupSandboxProxyIframe,
|
||||||
|
getToolUiResourceUri,
|
||||||
|
readToolUiResourceHtml,
|
||||||
|
} from "../src/app-host-utils";
|
||||||
|
|
||||||
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||||
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
||||||
|
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
||||||
|
import { LoggingMessageNotificationSchema } from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
import {
|
||||||
|
McpUiOpenLinkRequestSchema,
|
||||||
|
McpUiMessageRequestSchema,
|
||||||
|
McpUiSizeChangeNotificationSchema,
|
||||||
|
} from "@modelcontextprotocol/ext-apps";
|
||||||
|
|
||||||
|
const SANDBOX_PROXY_URL = URL.parse("/sandbox.html", location.href)!;
|
||||||
|
|
||||||
|
window.addEventListener("load", async () => {
|
||||||
|
const client = new Client({
|
||||||
|
name: "MCP UI Proxy Server",
|
||||||
|
version: "1.0.0",
|
||||||
|
});
|
||||||
|
const mcpServerUrl = new URL("http://localhost:3001/mcp");
|
||||||
|
console.log("[Client] Attempting SSE connection to", mcpServerUrl.href);
|
||||||
|
try {
|
||||||
|
await client.connect(new SSEClientTransport(mcpServerUrl));
|
||||||
|
console.log("[Client] SSE connection successful");
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(
|
||||||
|
"[Client] SSE connection failed, falling back to HTTP:",
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
await client.connect(new StreamableHTTPClientTransport(mcpServerUrl));
|
||||||
|
console.log("[Client] HTTP connection successful");
|
||||||
|
}
|
||||||
|
|
||||||
|
const tools = Object.fromEntries(
|
||||||
|
(await client.listTools()).tools.map((t) => [t.name, t]),
|
||||||
|
);
|
||||||
|
|
||||||
|
const controlsDiv = document.getElementById("controls") as HTMLDivElement;
|
||||||
|
const chatRootDiv = document.getElementById("chat-root") as HTMLDivElement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a tool UI instance for the given tool name.
|
||||||
|
* This demonstrates the new simplified API where the host manages the full proxy lifecycle.
|
||||||
|
*/
|
||||||
|
async function createToolUI(
|
||||||
|
toolName: string,
|
||||||
|
toolInput: Record<string, unknown>,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
// Step 1: Create iframe and wait for sandbox proxy ready
|
||||||
|
const { iframe, onReady } =
|
||||||
|
await setupSandboxProxyIframe(SANDBOX_PROXY_URL);
|
||||||
|
|
||||||
|
chatRootDiv.appendChild(iframe);
|
||||||
|
|
||||||
|
// Wait for sandbox proxy to be ready
|
||||||
|
await onReady;
|
||||||
|
|
||||||
|
// Step 2: Create proxy server instance
|
||||||
|
const serverCapabilities = client.getServerCapabilities();
|
||||||
|
const appBridge = new AppBridge(
|
||||||
|
client,
|
||||||
|
{
|
||||||
|
name: "Example MCP UI Host",
|
||||||
|
version: "1.0.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
openLinks: {},
|
||||||
|
serverTools: serverCapabilities?.tools,
|
||||||
|
serverResources: serverCapabilities?.resources,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Step 3: Register handlers BEFORE connecting
|
||||||
|
appBridge.oninitialized = () => {
|
||||||
|
console.log("[Example] Inner iframe MCP client initialized");
|
||||||
|
|
||||||
|
// Send tool input once iframe is ready
|
||||||
|
appBridge.sendToolInput({ arguments: toolInput });
|
||||||
|
};
|
||||||
|
|
||||||
|
appBridge.onopenlink = async ({url}) => {
|
||||||
|
console.log("[Example] Open link requested:", url);
|
||||||
|
window.open(url, "_blank", "noopener,noreferrer");
|
||||||
|
return {isError: false};
|
||||||
|
};
|
||||||
|
|
||||||
|
appBridge.onmessage = async (params) => {
|
||||||
|
console.log("[Example] Message requested:", params);
|
||||||
|
return {isError: false};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle size changes by resizing the iframe
|
||||||
|
appBridge.onsizechange = ({ width, height }) => {
|
||||||
|
if (width !== undefined) {
|
||||||
|
iframe.style.width = `${width}px`;
|
||||||
|
}
|
||||||
|
if (height !== undefined) {
|
||||||
|
iframe.style.height = `${height}px`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
appBridge.onloggingmessage = async params => {
|
||||||
|
console.log("[Tool UI Log]", params);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Step 4: Connect proxy to iframe (triggers MCP initialization)
|
||||||
|
// Pass iframe.contentWindow as both target and source for proper message filtering
|
||||||
|
await appBridge.connect(
|
||||||
|
new PostMessageTransport(iframe.contentWindow!, iframe.contentWindow!),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Step 5: Fetch and send UI resource
|
||||||
|
const resourceInfo = await getToolUiResourceUri(client, toolName);
|
||||||
|
if (!resourceInfo) {
|
||||||
|
throw new Error(`Tool ${toolName} has no UI resource`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const html = await readToolUiResourceHtml(client, {
|
||||||
|
uri: resourceInfo.uri,
|
||||||
|
});
|
||||||
|
await appBridge.sendSandboxResourceReady({ html });
|
||||||
|
|
||||||
|
console.log("[Example] Tool UI setup complete for:", toolName);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[Example] Error setting up tool UI:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create buttons for available tools
|
||||||
|
if ([...Object.keys(tools)].some((n) => n.startsWith("pizza-"))) {
|
||||||
|
for (const t of Object.values(tools)) {
|
||||||
|
if (t.name.startsWith("pizza-")) {
|
||||||
|
controlsDiv.appendChild(
|
||||||
|
Object.assign(document.createElement("button"), {
|
||||||
|
innerText: t.name,
|
||||||
|
onclick: () => createToolUI(t.name, { pizzaTopping: "Mushrooms" }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
controlsDiv.appendChild(
|
||||||
|
Object.assign(document.createElement("button"), {
|
||||||
|
innerText: "Add MCP UI View",
|
||||||
|
onclick: () =>
|
||||||
|
createToolUI("create-ui-vanilla", { message: "Hello from Host!" }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue