diff --git a/README.md b/README.md index cf1fb98..3e39aee 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ pnpm run dev ## Serve the static assets -If you want to preview the generated bundles without the MCP servers, start the static file server after running a build: +All of the MCP servers expect the bundled HTML, JS, and CSS to be served from the local static file server. After every build, start the server before launching any MCP processes: ```bash pnpm run serve @@ -72,6 +72,8 @@ pnpm run serve The assets are exposed at [`http://localhost:4444`](http://localhost:4444) with CORS enabled so that local tooling (including MCP inspectors) can fetch them. +> **Note:** The Python Pizzaz server caches widget HTML with `functools.lru_cache`. If you rebuild or manually edit files in `assets/`, restart the MCP server so it picks up the updated markup. + ## Run the MCP servers The repository ships several demo MCP servers that highlight different widget bundles: @@ -79,8 +81,6 @@ The repository ships several demo MCP servers that highlight different widget bu - **Pizzaz (Node & Python)** – pizza-inspired collection of tools and components - **Solar system (Python)** – 3D solar system viewer -Every tool response includes plain text content, structured JSON, and `_meta.openai/outputTemplate` metadata so the Apps SDK can hydrate the matching widget. - ### Pizzaz Node server ```bash diff --git a/pizzaz_server_node/src/server.ts b/pizzaz_server_node/src/server.ts index 91c43db..3053026 100644 --- a/pizzaz_server_node/src/server.ts +++ b/pizzaz_server_node/src/server.ts @@ -74,7 +74,7 @@ function readWidgetHtml(componentName: string): string { return htmlContents; } -function widgetMeta(widget: PizzazWidget) { +function widgetDescriptorMeta(widget: PizzazWidget) { return { "openai/outputTemplate": widget.templateUri, "openai/toolInvocation/invoking": widget.invoking, @@ -84,6 +84,13 @@ function widgetMeta(widget: PizzazWidget) { } as const; } +function widgetInvocationMeta(widget: PizzazWidget) { + return { + "openai/toolInvocation/invoking": widget.invoking, + "openai/toolInvocation/invoked": widget.invoked, + } as const; +} + const widgets: PizzazWidget[] = [ { id: "pizza-map", @@ -152,7 +159,7 @@ const tools: Tool[] = widgets.map((widget) => ({ description: widget.title, inputSchema: toolInputSchema, title: widget.title, - _meta: widgetMeta(widget), + _meta: widgetDescriptorMeta(widget), // To disable the approval prompt for the widgets annotations: { destructiveHint: false, @@ -166,7 +173,7 @@ const resources: Resource[] = widgets.map((widget) => ({ name: widget.title, description: `${widget.title} widget markup`, mimeType: "text/html+skybridge", - _meta: widgetMeta(widget), + _meta: widgetDescriptorMeta(widget), })); const resourceTemplates: ResourceTemplate[] = widgets.map((widget) => ({ @@ -174,7 +181,7 @@ const resourceTemplates: ResourceTemplate[] = widgets.map((widget) => ({ name: widget.title, description: `${widget.title} widget markup`, mimeType: "text/html+skybridge", - _meta: widgetMeta(widget), + _meta: widgetDescriptorMeta(widget), })); function createPizzazServer(): Server { @@ -213,7 +220,7 @@ function createPizzazServer(): Server { uri: widget.templateUri, mimeType: "text/html+skybridge", text: widget.html, - _meta: widgetMeta(widget), + _meta: widgetDescriptorMeta(widget), }, ], }; @@ -255,7 +262,7 @@ function createPizzazServer(): Server { structuredContent: { pizzaTopping: args.pizzaTopping, }, - _meta: widgetMeta(widget), + _meta: widgetInvocationMeta(widget), }; } ); diff --git a/pizzaz_server_python/main.py b/pizzaz_server_python/main.py index 477ea0a..2eb58e5 100644 --- a/pizzaz_server_python/main.py +++ b/pizzaz_server_python/main.py @@ -146,16 +146,11 @@ def _tool_meta(widget: PizzazWidget) -> Dict[str, Any]: } -def _embedded_widget_resource(widget: PizzazWidget) -> types.EmbeddedResource: - return types.EmbeddedResource( - type="resource", - resource=types.TextResourceContents( - uri=widget.template_uri, - mimeType=MIME_TYPE, - text=widget.html, - title=widget.title, - ), - ) +def _tool_invocation_meta(widget: PizzazWidget) -> Dict[str, Any]: + return { + "openai/toolInvocation/invoking": widget.invoking, + "openai/toolInvocation/invoked": widget.invoked, + } @mcp._mcp_server.list_tools() @@ -262,15 +257,7 @@ async def _call_tool_request(req: types.CallToolRequest) -> types.ServerResult: ) topping = payload.pizza_topping - widget_resource = _embedded_widget_resource(widget) - meta: Dict[str, Any] = { - "openai.com/widget": widget_resource.model_dump(mode="json"), - "openai/outputTemplate": widget.template_uri, - "openai/toolInvocation/invoking": widget.invoking, - "openai/toolInvocation/invoked": widget.invoked, - "openai/widgetAccessible": True, - "openai/resultCanProduceWidget": True, - } + meta = _tool_invocation_meta(widget) return types.ServerResult( types.CallToolResult(