---
title: Resources
description: Serve MCP App UIs and data as MCP resources
icon: Database
---
import Callout from "../../../../components/ui/Callout.astro";

## 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`):

```typescript
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`:

```typescript
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

<Callout type="info">
  Export your resource URI as a constant from the tool file so both the tool and resource reference the same value.
</Callout>

## 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.

```typescript
// 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,
      };
    },
  });
```
