h3-mcp logoh3-mcp

Resources

Readable data, addressed by URI.

Resources are data a client can read directly — files, config, records — identified by URI rather than invoked as a function. Clients list them with resources/list and fetch them with resources/read.

import { defineMcpResource } from "h3-mcp";

const configResource = defineMcpResource({
  name: "config",
  uri: "app:///config.json",
  title: "App config",
  description: "Application configuration",
  mimeType: "application/json",
  handler: async (uri, event) => ({
    contents: [
      {
        uri: uri.toString(),
        mimeType: "application/json",
        text: JSON.stringify({ theme: "dark", lang: "en" }),
      },
    ],
  }),
});

The handler receives the requested URI as a parsed URL and returns one or more contents entries. A single read may return several — a directory listing, or a document plus its attachments.

#Binary Content

Use blob with base64 data instead of text, and advertise size in bytes when you know it so clients can decide before fetching:

const logoResource = defineMcpResource({
  name: "logo",
  uri: "app:///logo.png",
  description: "Application logo",
  mimeType: "image/png",
  size: 2048,
  handler: async (uri) => ({
    contents: [{ uri: uri.toString(), mimeType: "image/png", blob: "iVBOR..." }],
  }),
});

#Resource Templates

A template describes a family of resources with a RFC 6570 URI template. Clients discover it with resources/templates/list, fill in the parameters, and read the expanded URI:

import { defineMcpResourceTemplate } from "h3-mcp";

const userTemplate = defineMcpResourceTemplate({
  name: "user-profile",
  uriTemplate: "app://users/{userId}",
  title: "User Profile",
  description: "Retrieve a user profile by ID",
  mimeType: "application/json",
  handler: async (uri, event) => {
    const userId = uri.pathname.slice(1); // extract from the resolved URI
    const user = await db.getUser(userId);
    return {
      contents: [{ uri: uri.toString(), mimeType: "application/json", text: JSON.stringify(user) }],
    };
  },
});

Register templates alongside static resources:

defineMcpHandler({
  name: "my-server",
  version: "1.0.0",
  resources: [configResource],
  resourceTemplates: [userTemplate],
});

On resources/read, static resources are matched first; if none match, Level 1 URI templates are tried in declaration order. The resources capability is advertised when either list is non-empty.

Important

The URI in a template read is client-supplied. Treat the extracted parameters exactly like tool arguments — validate them, and never interpolate them into a filesystem path, SQL query, or outbound URL unchecked.

#Autocompletion

Add a complete callback to a template to autocomplete its parameters:

Read more in Completions.

#Caching

On the modern era, resources/read results carry ttlMs and cacheScope. The default is "immediately stale, never shared"; opt into caching per definition:

defineMcpResource({
  name: "profile",
  uri: "app:///me",
  cache: { ttlMs: 1_000, cacheScope: "private" },
  handler: async (uri) => ({ contents: [{ uri: uri.toString(), text: "..." }] }),
});
Read more in Caching.

#Change Notifications

Telling clients a resource changed is era-specific:

  • Modern — push through a subscriptions/listen stream with subscription.resourceUpdated(uri).
  • Legacy — track resources/subscribe / resources/unsubscribe and push notifications/resources/updated over the GET/SSE stream.
h3-mcp logo

h3-mcp  MCP servers, built on H3.