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.

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

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

Example:

// 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 implementor (Zod v4, ArkType, Valibot via toStandardJsonSchema()) is validated before the handler runs and its output type is inferred onto args.

Example:

// 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:

// 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:

// 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:

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:

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:

// 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:

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

#McpJsonRpcError()

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

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

ExportPurpose
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
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 in 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.

ExportPurpose
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_EXTENSIONThe io.modelcontextprotocol/tasks identifier
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 in 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.

ExportPurpose
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_EXTENSIONThe io.modelcontextprotocol/ui identifier
APP_MIME_TYPEtext/html;profile=mcp-app
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 in 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.

ExportPurpose
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_EXTENSIONThe io.modelcontextprotocol/skills identifier
DIRECTORY_MIME_TYPEinode/directory, the mimeType of a directory resource
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 in 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.

ExportPurpose
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? }
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 in OAuth.

#Plugin Toolkit

Writing an extension plugin? 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.

ExportPurpose
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
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 in 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:

Exporth3-mcph3-mcp/modernh3-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 in Entrypoints.

#Types

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

TypeUse
HandlerOptionsThe options object
ToolDefinition · ResourceDefinition · ResourceTemplateDefinition · PromptDefinitionDefinition shapes
ResourceDescriptorA listed resource, as a template's list returns it
CallToolResult · ReadResourceResult · GetPromptResultHandler return values
ContentBlock · IconContent and icons
RequestContextevent.context.mcp
InputRequiredResult · InputRequests · InputResponsesMRTR
ElicitParams · ElicitResult · CreateMessageParams · ListRootsResultMRTR request/response payloads
RequestStateOptions · RequestStateCodecSealed requestState
Subscription · SubscriptionFilter · SubscriptionOptionsSubscriptions
CacheOptions · CacheHints · CacheScopeCaching
AuthOptions · AuthIdentity · AuthValidator · AuthCredentials · AuthFailureAuth
OriginOptions · LimitsOptions · SessionOptionsSecurity
Era · Implementation · ClientCapabilities · ExtensionsEra and identity
StandardSchemaV1 · StandardJSONSchemaV1 · StandardTypedV1Standard 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:

DeprecatedModern replacement
sessionstateless requests; explicit handles in tool arguments
onStreamonListen (subscriptions)
onSubscribe / onUnsubscribeonListen
onLogLevelSet_meta log level + mcp.log()

h3-mcp  MCP servers, built on H3.