
# Exports

> Everything `h3-mcp` exports at runtime.

The runtime surface is deliberately small: one handler factory, four identity helpers for type inference, one error class, and the multi-round-trip helpers. Everything else is types.

<!-- automd:jsdocs src="../../src/index.ts" -->

### `defineMcpHandler(handlerOpts)`

Define an H3 event handler that implements the Model Context Protocol (MCP) over HTTP using JSON-RPC 2.0 as the wire format.

The handler is **dual-era**: a POST whose `params._meta["io.modelcontextprotocol/protocolVersion"]` is set is served statelessly per `2026-07-28`; anything else (including `initialize` and `MCP-Session-Id` traffic) takes the legacy `≤2025-11-25` path. Use `era: "modern"` or `era: "legacy"` to serve only one.

To also _drop_ the unused era from your bundle, import from `h3-mcp/modern` or `h3-mcp/legacy` instead — those entrypoints never reference the other era's implementation.

**Example:**

```ts
app.all(
  "/mcp",
  defineMcpHandler({
    name: "my-server",
    version: "1.0.0",
    tools: [echoTool],
    resources: [readmeResource],
    prompts: [greetPrompt],
  }),
);
```

**Example:**

```ts
// Dynamic options based on request context
app.all(
  "/mcp",
  defineMcpHandler((event) => ({
    name: "my-server",
    version: "1.0.0",
    tools: getToolsForUser(event),
  })),
);
```

### `definePrompt(definition)`

Define an MCP prompt with optional arguments and a handler that returns messages.

`arguments` is either the declarative `PromptArgument` list — which can also carry per-argument `complete` callbacks — or a schema. A [StandardJSONSchemaV1](https://standardschema.dev/json-schema) implementor (Zod v4, ArkType, Valibot via `toStandardJsonSchema()`) is validated before the handler runs and its output type is inferred onto `args`.

**Example:**

```ts
// Prompt with declared arguments
const greetPrompt = definePrompt({
  name: "greet",
  description: "Generate a greeting",
  arguments: [{ name: "name", required: true }],
  handler: async (args, event) => ({
    messages: [{ role: "user", content: { type: "text", text: `Hello ${args.name}!` } }],
  }),
});
```

**Example:**

```ts
// Prompt with schema-validated arguments
const reviewPrompt = definePrompt({
  name: "review",
  arguments: z.object({ path: z.string(), depth: z.enum(["quick", "deep"]) }),
  handler: async (args, event) => ({
    // args: { path: string; depth: "quick" | "deep" }
    messages: [
      { role: "user", content: { type: "text", text: `${args.depth} review of ${args.path}` } },
    ],
  }),
});
```

**Example:**

```ts
// Prompt without arguments
const helpPrompt = definePrompt({
  name: "help",
  description: "Show help information",
  handler: async (event) => ({
    messages: [{ role: "user", content: { type: "text", text: "How can I help?" } }],
  }),
});
```

### `defineResource(definition)`

Define an MCP resource with a static URI and a handler that returns its contents.

**Example:**

```ts
const readmeResource = defineResource({
  name: "readme",
  uri: "file:///readme",
  description: "Project README",
  mimeType: "text/markdown",
  handler: async (uri, event) => ({
    contents: [{ uri: uri.toString(), text: "# My Project" }],
  }),
});
```

### `defineResourceTemplate(definition)`

Define an MCP resource template with a URI template (RFC 6570) and a handler.

Resource templates expose parameterized resources. Clients fill in template parameters and call `resources/read` with the expanded URI.

**Example:**

```ts
const fileTemplate = defineResourceTemplate({
  name: "file",
  uriTemplate: "file:///{path}",
  description: "Read a file by path",
  mimeType: "text/plain",
  handler: async (uri, variables, event) => ({
    contents: [{ uri: uri.toString(), text: `Contents of ${variables.path}` }],
  }),
});
```

### `defineTool(definition)`

Define an MCP tool with a name, optional JSON Schema input, and a handler function.

**Example:**

```ts
// Tool with validated input and output
const echoTool = defineTool({
  name: "echo",
  description: "Echo back a message",
  inputSchema: z.object({ message: z.string() }),
  outputSchema: z.object({ message: z.string() }),
  handler: async ({ message }, event) => ({
    content: [{ type: "text", text: message }],
    structuredContent: { message },
  }),
});
```

**Example:**

```ts
// Tool without input parameters
const pingTool = defineTool({
  name: "ping",
  description: "Returns pong",
  handler: async (event) => ({
    content: [{ type: "text", text: "pong" }],
  }),
});
```

### `McpJsonRpcError()`

<!-- /automd -->

## `McpJsonRpcError`

A JSON-RPC error with an explicit code and HTTP status — the one error class whose message reaches the client verbatim on the modern era.

```ts
import { McpJsonRpcError } from "h3-mcp";

throw new McpJsonRpcError(-32602, "Widget id must be a UUID", { status: 400 });
```

`new McpJsonRpcError(code, message, { status?, data? })`. When `status` is omitted it defaults per code: `-32601` → `404`, `-32603` → `500`, otherwise `400`. `McpJsonRpcError.isMcpJsonRpcError(value)` is a type guard.

:read-more{to="/guide/errors" title="Errors"}

## MRTR Helpers

Multi round-trip requests are modern-only, so these are exported from `h3-mcp` and `h3-mcp/modern` — not from `h3-mcp/legacy`.

| Export                                       | Purpose                                                                                                                                          |
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `inputRequired(event, spec)`                 | Build the `resultType: "input_required"` interim result to return from a handler                                                                 |
| `mcpElicit(params)` · `mcpElicitUrl(params)` | An `elicitation/create` request — a form, or an out-of-band URL                                                                                  |
| `createMessage(params)`                      | A `sampling/createMessage` request                                                                                                               |
| `listRoots()`                                | A `roots/list` request                                                                                                                           |
| `getInputResponses(event, requests)`         | The answers that arrived, typed by the requests that asked for them                                                                              |
| `getElicitedContent(event, requests, key)`   | The content of an **accepted** elicitation — key checked against the map, fields typed by its schema; `undefined` for missing/declined/cancelled |
| `defineRequestState(options)`                | An HMAC-SHA256 `{ seal, open }` codec for `requestState`                                                                                         |

```ts
import { defineRequestState, getElicitedContent, mcpElicit, inputRequired } from "h3-mcp";
```

`inputRequired` refuses to build a result the wire cannot carry: a legacy request, a capability the client never declared (`-32021`), an empty spec, or a `requestState` longer than the retry could echo back.

:read-more{to="/protocols/modern/mrtr" title="Multi Round-Trip Requests"}

## Task Helpers

The tasks extension is a **plugin**, shipped from its own entrypoint. Nothing is exported from `h3-mcp`, `h3-mcp/modern` or `h3-mcp/legacy` — install it explicitly, and a server that does not serve it never bundles it.

| Export                 | Purpose                                                                                                    |
| ---------------------- | ---------------------------------------------------------------------------------------------------------- |
| `mcpTasks(options?)`   | The plugin, for `defineMcpHandler`'s `plugins` list — `{ enabled?, max?, ttlMs?, pollIntervalMs?, bind? }` |
| `mcpTask(event, spec)` | Start background work and return the `resultType: "task"` handle from a `tools/call` handler               |
| `canCreateTask(event)` | Whether this request may be answered with a task — check before falling back to synchronous                |
| `TASKS_EXTENSION`      | The `io.modelcontextprotocol/tasks` identifier                                                             |

```ts
import { defineMcpHandler } from "h3-mcp/modern"; // or "h3-mcp"
import { canCreateTask, mcpTask, mcpTasks } from "h3-mcp/tasks";

export default defineMcpHandler(options, { extensionPlugins: [mcpTasks()] });
```

The helpers work from any server entrypoint — importing `mcpTask` from `h3-mcp/tasks` while the handler comes from `h3-mcp/modern` is the normal case, not a hazard.

`mcpTask` refuses to mint a handle the wire cannot carry: a legacy request, a request the plugin's `enabled` gate turned the extension off for, or a client that never declared the extension (`-32021`). Installing the plugin **is** enabling the extension — there is no separate option to forget, and nothing to fail closed on.

:read-more{to="/protocols/modern/tasks" title="Tasks"}

## App Helpers

MCP Apps is a **plugin** too, from its own entrypoint. `h3-mcp/ui` is the smallest entry in the package (2.4kb minified) and pulls in no transport, so importing `canRenderApp` in a handler costs almost nothing.

| Export                | Purpose                                                                                                             |
| --------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `mcpUI(options)`      | The plugin, for `defineMcpHandler`'s `extensionPlugins` list — `{ apps, enabled? }`                                 |
| `defineApp(app)`      | Identity helper for an app: `uri`, `name`, `html`, `tools`, plus `csp` / `permissions` / `domain` / `prefersBorder` |
| `canRenderApp(event)` | Whether this request's client can render an app — check before deciding what a tool returns                         |
| `UI_EXTENSION`        | The `io.modelcontextprotocol/ui` identifier                                                                         |
| `APP_MIME_TYPE`       | `text/html;profile=mcp-app`                                                                                         |

```ts
import { defineMcpHandler } from "h3-mcp/modern"; // or "h3-mcp"
import { canRenderApp, defineApp, mcpUI } from "h3-mcp/ui";

export default defineMcpHandler(options, { extensionPlugins: [mcpUI({ apps: [dashboard] })] });
```

Types: `AppDefinition`, `AppCsp`, `AppPermissions`, `AppResourceMeta`, `AppTool`, `AppToolMeta`, `AppVisibility`, `UIOptions` and `UIState`, from `h3-mcp/ui` only.

:read-more{to="/protocols/modern/apps" title="MCP Apps"}

## Skill Helpers

Skills over MCP is the third **plugin**, from `h3-mcp/skills`. Like the other two it pulls in no transport — only the era-neutral request scope, params, pagination and error helpers — so it can be imported wherever the skill definitions live.

| Export                   | Purpose                                                                                                          |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------- |
| `mcpSkills(options)`     | The plugin, for `defineMcpHandler`'s `extensionPlugins` list — `{ skills, directoryRead?, cache?, enabled? }`    |
| `defineSkill(skill)`     | Identity helper for a skill: the root `uri`, its `files`, plus `frontmatter` / `dynamic` / `cache`               |
| `parseFrontmatter(text)` | The `SKILL.md` YAML frontmatter as the entry publishes it — the same parser the plugin runs, for checking a file |
| `SKILLS_EXTENSION`       | The `io.modelcontextprotocol/skills` identifier                                                                  |
| `DIRECTORY_MIME_TYPE`    | `inode/directory`, the `mimeType` of a directory resource                                                        |

```ts
import { defineMcpHandler } from "h3-mcp/modern"; // or "h3-mcp"
import { defineSkill, mcpSkills } from "h3-mcp/skills";

export default defineMcpHandler(options, {
  extensionPlugins: [mcpSkills({ skills: [refunds], directoryRead: true })],
});
```

Types: `Skill`, `SkillContent`, `SkillDefinition`, `SkillFile`, `SkillFrontmatter`, `SkillResource`, `SkillsOptions` and `SkillsState`, from `h3-mcp/skills` only.

:read-more{to="/protocols/modern/skills" title="Skills"}

## OAuth Helpers

Import the OAuth metadata handler and token verifiers from `h3-mcp/oauth`. Every server entry already includes `401`/`403` challenges, scope checks, and `event.context.mcp.auth`. The verifiers return the identity used by those checks.

| Export                               | Purpose                                                                                                                                              |
| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `protectedResourceMetadata(options)` | An h3 handler for the RFC 9728 document — `{ resource, authorizationServers, scopesSupported?, resourceName?, resourceDocumentation? }`              |
| `jwtValidator(options)`              | Verifies JWT access tokens (RFC 9068) with a JWKS and WebCrypto — `{ issuer, audience, jwksUri \| keys, … }`                                         |
| `introspectionValidator(options)`    | Checks tokens with the authorization server (RFC 7662) — `{ endpoint, audience, clientId?, clientSecret?, … }`                                       |
| `dpopValidator(options)`             | Verifies a DPoP proof (RFC 9449) on top of either — `{ validate, resource, algorithms?, maxAgeMs?, clockToleranceMs?, replay?, replayMax?, nonce? }` |

```ts
import { defineMcpHandler } from "h3-mcp"; // or "h3-mcp/modern", "h3-mcp/legacy"
import { jwtValidator, protectedResourceMetadata } from "h3-mcp/oauth";

app.get(
  "/.well-known/oauth-protected-resource",
  protectedResourceMetadata({
    resource: "https://mcp.example.com/mcp",
    authorizationServers: ["https://auth.example.com"],
    scopesSupported: ["mcp:invoke"],
  }),
);

app.all(
  "/mcp",
  defineMcpHandler({
    name: "my-server",
    version: "1.0.0",
    auth: {
      schemes: ["bearer"],
      resourceMetadataUrl: "https://mcp.example.com/.well-known/oauth-protected-resource",
      scopes: ["mcp:invoke"],
      validate: jwtValidator({
        issuer: "https://auth.example.com",
        audience: "https://mcp.example.com/mcp",
        jwksUri: "https://auth.example.com/.well-known/jwks.json",
      }),
    },
  }),
);
```

Mount the metadata handler at `/.well-known/oauth-protected-resource` or an endpoint-specific path such as `/.well-known/oauth-protected-resource/mcp`. Set `auth.resourceMetadataUrl` to that URL. The document is fixed and always includes `bearer_methods_supported: ["header"]`.

All four helpers validate options when created. At request time, the verifiers return `false` or a reason (`AuthFailure`) instead of throwing if verification fails; the server responds with `401`. They have no dependencies and use WebCrypto.

Types exported only from `h3-mcp/oauth`: `ProtectedResourceMetadataOptions`, `JwtValidatorOptions`, `JwtAlgorithm`, `Jwk`, `IntrospectionValidatorOptions`, `ValidatorOptions`, `DpopValidatorOptions`, and `DpopReplayStore`.

This entry also re-exports `AuthIdentity`, `AuthValidator`, `AuthCredentials`, `AuthVerdict`, `AuthFailure`, and `AuthBinding`.

:read-more{to="/security/auth#oauth" title="OAuth"}

## Plugin Toolkit

Writing an [extension plugin](/protocols/modern/extensions)? These are the helpers its methods need to behave like the library's own. Modern-only, so they are exported from `h3-mcp` and `h3-mcp/modern` — not from `h3-mcp/legacy`.

| Export                                  | Purpose                                                                                            |
| --------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `mcpMethod(ctx, req, event, handler)`   | Run a plugin method in the request scope — `event.context.mcp.signal` fires when the stream closes |
| `pluginState(event, plugin)`            | This plugin's per-request state, typed by the instance (what its `initEvent` returned)             |
| `parseParams(params)`                   | JSON-RPC params as a plain object, or `-32602`                                                     |
| `requireParamString(params, field)`     | A required, non-empty string field                                                                 |
| `protocolError(status, message, data?)` | A validation failure whose message is safe to report verbatim                                      |
| `invalidParamsError(message)`           | `-32602` with a message                                                                            |
| `isToolExecutionThrow(error)`           | Whether a throw is the model's problem rather than the client's                                    |
| `toolErrorText(error)`                  | The redacted text for an `{ isError: true }` result                                                |

```ts
import { mcpMethod, parseParams, requireParamString, type ExtensionPlugin } from "h3-mcp/modern";
```

Two rules these exist to keep enforceable outside the library: **validate client input at the boundary** (never `as string`), and **never echo untrusted input in an error message** — `protocolError(404, "Note not found")`, not the id that was not found. `isToolExecutionThrow` / `toolErrorText` are the other half: a protocol error collapses its message for the _client_, a tool execution throw becomes `isError` content for the _model_.

Types: `ExtensionPlugin`, `PluginContext`, `PluginDefinitions` and `PluginOptions` are exported from `h3-mcp`, `h3-mcp/modern`, `h3-mcp/tasks`, `h3-mcp/ui` and `h3-mcp/skills`.

:read-more{to="/protocols/modern/extensions" title="Extensions"}

## Era Entrypoints

`h3-mcp/modern` and `h3-mcp/legacy` export the same helpers and error class, plus a single-era `defineMcpHandler` and the version it serves:

| Export                    | `h3-mcp` | `h3-mcp/modern` | `h3-mcp/legacy` |
| ------------------------- | :------: | :-------------: | :-------------: |
| `defineMcpHandler`        | ✅ dual  | ✅ modern-only  | ✅ legacy-only  |
| `defineTool`              |    ✅    |       ✅        |       ✅        |
| `defineResource`          |    ✅    |       ✅        |       ✅        |
| `defineResourceTemplate`  |    ✅    |       ✅        |       ✅        |
| `definePrompt`            |    ✅    |       ✅        |       ✅        |
| `McpJsonRpcError`         |    ✅    |       ✅        |       ✅        |
| MRTR helpers (above)      |    ✅    |       ✅        |        —        |
| Plugin toolkit (above)    |    ✅    |       ✅        |        —        |
| `PROTOCOL_VERSION`        |    —     | ✅ `2026-07-28` |        —        |
| `LEGACY_PROTOCOL_VERSION` |    —     |        —        | ✅ `2025-11-25` |
| all types                 |    ✅    |       ✅        |       ✅        |

Each entrypoint is self-contained — never import from two of them in the same file, or you defeat the bundle split.

:read-more{to="/protocols#entrypoints" title="Entrypoints"}

## Types

Every type is exported from each entrypoint. The ones you are most likely to name explicitly:

| Type                                                                                        | Use                                                  |
| ------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| `HandlerOptions`                                                                            | The options object                                   |
| `ToolDefinition` · `ResourceDefinition` · `ResourceTemplateDefinition` · `PromptDefinition` | Definition shapes                                    |
| `ResourceDescriptor`                                                                        | A listed resource, as a template's `list` returns it |
| `CallToolResult` · `ReadResourceResult` · `GetPromptResult`                                 | Handler return values                                |
| `ContentBlock` · `Icon`                                                                     | Content and icons                                    |
| `RequestContext`                                                                            | `event.context.mcp`                                  |
| `InputRequiredResult` · `InputRequests` · `InputResponses`                                  | [MRTR](/protocols/modern/mrtr)                       |
| `ElicitParams` · `ElicitResult` · `CreateMessageParams` · `ListRootsResult`                 | MRTR request/response payloads                       |
| `RequestStateOptions` · `RequestStateCodec`                                                 | Sealed `requestState`                                |
| `Subscription` · `SubscriptionFilter` · `SubscriptionOptions`                               | [Subscriptions](/protocols/modern/subscriptions)     |
| `CacheOptions` · `CacheHints` · `CacheScope`                                                | [Caching](/protocols/modern/caching)                 |
| `AuthOptions` · `AuthIdentity` · `AuthValidator` · `AuthCredentials` · `AuthFailure`        | [Auth](/security/auth)                               |
| `OriginOptions` · `LimitsOptions` · `SessionOptions`                                        | [Security](/security)                                |
| `Era` · `Implementation` · `ClientCapabilities` · `Extensions`                              | Era and identity                                     |
| `StandardSchemaV1` · `StandardJSONSchemaV1` · `StandardTypedV1`                             | Standard Schema (vendored)                           |

`h3-mcp` also augments h3's `H3EventContext` with `mcp?: RequestContext`, so `event.context.mcp` is typed everywhere once the package is imported.

## Deprecated but Functional

These are legacy-era features removed in `2026-07-28`. They still work and are not going away — the JSDoc `@deprecated` marks are there to point you at the modern replacement:

| Deprecated                      | Modern replacement                                            |
| ------------------------------- | ------------------------------------------------------------- |
| `session`                       | stateless requests; explicit handles in tool arguments        |
| `onStream`                      | `onListen` ([subscriptions](/protocols/modern/subscriptions)) |
| `onSubscribe` / `onUnsubscribe` | `onListen`                                                    |
| `onLogLevelSet`                 | `_meta` log level + `mcp.log()`                               |
