Full-Code Guides

Resources

Serve MCP App UIs and data as MCP resources

What Are MCP Resources?

MCP resources are read-only data exposed by your MCP server, identified by URI. In the context of MCP Apps, resources primarily serve single-file HTML bundles that render as rich UIs inside MCP clients.

MCP App Resources

An MCP App resource serves the built HTML bundle as a rich UI for a tool:

  • MIME type: text/html;profile=mcp-app
  • Content: single-file HTML (CSS + JS inlined) built by Vite + vite-plugin-singlefile
  • Linked to tools via _meta.ui.resourceUri

Creating a Resource

Basic Example

Here’s the hello resource from the template ( api/resources/hello.ts ):

 import { readFile } from "node:fs/promises";
import { join } from "node:path";
import { createPublicResource } from "@decocms/runtime/tools";
import { HELLO_RESOURCE_URI } from "../tools/hello.ts";
import type { Env } from "../types/env.ts";

const RESOURCE_MIME_TYPE = "text/html;profile=mcp-app";

function getDistPath(): string {
  const projectRoot = join(import.meta.dir, "../..");
  return join(projectRoot, "dist", "client", "index.html");
}

export const helloAppResource = (_env: Env) =>
  createPublicResource({
    uri: HELLO_RESOURCE_URI,
    name: "Hello UI",
    description: "Interactive greeting display powered by MCP Apps",
    mimeType: RESOURCE_MIME_TYPE,
    read: async () => {
      const html = await readFile(getDistPath(), "utf-8");
      return {
        uri: HELLO_RESOURCE_URI,
        mimeType: RESOURCE_MIME_TYPE,
        text: html,
      };
    },
  }); 

Key parts:

  • createPublicResource from @decocms/runtime/tools — creates a publicly accessible resource
  • uri — must match the _meta.ui.resourceUri in the tool definition
  • mimeType text/html;profile=mcp-app tells the MCP client this is an MCP App
  • read() — async function that returns the HTML content

Registering Resources

Add your resource creator to the resources array in withRuntime() in api/main.ts :

 const runtime = withRuntime<Env, typeof StateSchema>({
  configuration: { state: StateSchema },
  tools,
  resources: [helloAppResource, myToolResource],
}); 

Resource URIs

  • Convention: ui://<app-name>/<tool-name> (e.g., ui://mcp-app/hello )
  • The URI must match the _meta.ui.resourceUri in the corresponding tool definition
  • The MCP client uses this URI to find and load the UI for a tool result

Export your resource URI as a constant from the tool file so both the tool and resource reference the same value.

Build Output

Vite builds web/ into dist/client/index.html :

  • All CSS and JS are inlined into a single HTML file (via vite-plugin-singlefile )
  • The resource reads this file at runtime and serves it to MCP clients
  • In development, bun run dev runs vite build --watch to keep the bundle updated

Multiple Resources

If your app has multiple tools with UIs, each tool gets its own resource definition — but they all serve the same HTML bundle. The router inside the bundle ( web/router.tsx ) dispatches to the correct tool UI based on toolName from the host context.

 // api/resources/analytics.ts
export const analyticsAppResource = (_env: Env) =>
  createPublicResource({
    uri: ANALYTICS_RESOURCE_URI,
    name: "Analytics UI",
    description: "Interactive analytics dashboard",
    mimeType: "text/html;profile=mcp-app",
    read: async () => {
      const html = await readFile(getDistPath(), "utf-8");
      return {
        uri: ANALYTICS_RESOURCE_URI,
        mimeType: "text/html;profile=mcp-app",
        text: html,
      };
    },
  }); 
Previous

Found an error or want to improve this page?

Edit this page