Merge pull request #212 from jonathanhefner/video-resource-server-safeareainsets

Support `safeAreaInsets` + type-checking in video-resource-server
This commit is contained in:
Jonathan Hefner 2026-01-12 12:58:28 -06:00 • committed by GitHub
commit dadbb6ac3d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 35 additions and 17 deletions

View file

@ -16,7 +16,7 @@
"dist"
],
"scripts": {
"build": "cross-env INPUT=mcp-app.html vite build",
"build": "tsc --noEmit && cross-env INPUT=mcp-app.html vite build",
"watch": "cross-env INPUT=mcp-app.html vite build --watch",
"serve": "bun server.ts",
"start": "cross-env NODE_ENV=development npm run build && npm run serve",

View file

@ -4,7 +4,7 @@
* Demonstrates fetching binary content (video) via MCP resources.
* The video is served as a base64 blob and converted to a data URI for playback.
*/
import { App } from "@modelcontextprotocol/ext-apps";
import { App, type McpUiHostContext } from "@modelcontextprotocol/ext-apps";
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import { ReadResourceResultSchema } from "@modelcontextprotocol/sdk/types.js";
import "./global.css";
@ -16,6 +16,7 @@ const log = {
};
// Get element references
const mainEl = document.querySelector(".main") as HTMLElement;
const loadingEl = document.getElementById("loading")!;
const loadingTextEl = document.getElementById("loading-text")!;
const errorEl = document.getElementById("error")!;
@ -83,16 +84,13 @@ app.ontoolresult = async (result) => {
ReadResourceResultSchema,
);
log.info(
"Resource received, blob size:",
resourceResult.contents[0]?.blob?.length,
);
const content = resourceResult.contents[0];
if (!content?.blob) {
if (!content || !("blob" in content)) {
throw new Error("Resource response did not contain blob data");
}
log.info("Resource received, blob size:", content.blob.length);
showLoading("Converting to data URI...");
const mimeType = content.mimeType || "video/mp4";
@ -112,5 +110,21 @@ app.onerror = (err) => {
showError(err instanceof Error ? err.message : String(err));
};
function handleHostContextChanged(ctx: McpUiHostContext) {
if (ctx.safeAreaInsets) {
mainEl.style.paddingTop = `${ctx.safeAreaInsets.top}px`;
mainEl.style.paddingRight = `${ctx.safeAreaInsets.right}px`;
mainEl.style.paddingBottom = `${ctx.safeAreaInsets.bottom}px`;
mainEl.style.paddingLeft = `${ctx.safeAreaInsets.left}px`;
}
}
app.onhostcontextchanged = handleHostContextChanged;
// Connect to host
app.connect();
app.connect().then(() => {
const ctx = app.getHostContext();
if (ctx) {
handleHostContextChanged(ctx);
}
});

View file

@ -1,15 +1,19 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"noEmit": true,
"strict": true,
"skipLibCheck": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"declaration": false,
"outDir": "dist",
"lib": ["ESNext", "DOM"]
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["server.ts", "src/**/*"]
"include": ["src", "server.ts"]
}