
# Getting Started

> Get started with h3-mcp.

<!-- automd:file src="../.partials/unstable.md" -->

> [!WARNING]
> `h3-mcp` is still evolving and may introduce breaking changes.

<!-- /automd -->

## Overview

`h3-mcp` turns any [H3](https://h3.dev) app into a [Model Context Protocol](https://modelcontextprotocol.io/specification/2026-07-28) server. It implements MCP as a self-contained JSON-RPC 2.0 layer — there is **no MCP SDK dependency**, and the whole library is ~34kb min / ~11kb gzip (~19kb / ~6kb if you serve a single protocol era).

You describe your server declaratively — [tools](/guide/basics/tools), [resources](/guide/basics/resources), [prompts](/guide/basics/prompts) — and `defineMcpHandler` gives you back an ordinary H3 event handler you can mount anywhere:

- Every JSON-RPC method of the current spec is implemented for you, on **both protocol eras**.
- All client-supplied input (tool arguments, prompt arguments, resource URIs, cursors, headers) is validated at the transport boundary before it reaches your handler.
- Security defaults — `Origin` checks, body-size and `Content-Type` limits, private cache scopes — are on before you configure anything.

:read-more{to="/guide/eras" title="Protocol eras"}

## Quick Start

Install `h3` and `h3-mcp` as dependencies:

:pm-install{name="h3 h3-mcp"}

Create a server entry:

```ts [server.ts]
import { H3, serve } from "h3";
import { defineMcpHandler, defineMcpTool } from "h3-mcp";

const app = new H3();

app.all(
  "/mcp",
  defineMcpHandler({
    name: "my-server",
    version: "1.0.0",
    tools: [
      defineMcpTool({
        name: "hello",
        description: "Say hello",
        inputSchema: {
          type: "object",
          properties: { name: { type: "string" } },
          required: ["name"],
        },
        handler: async ({ name }) => ({
          content: [{ type: "text", text: `Hello, ${name}!` }],
        }),
      }),
    ],
  }),
);

serve(app, { port: 3000 });
```

Then run it with your favorite runtime:

::code-group

```bash [node]
node --watch ./server.ts
```

```bash [deno]
deno run -A --watch ./server.ts
```

```bash [bun]
bun run --watch server.ts
```

::

Your MCP endpoint is live on `http://localhost:3000/mcp`. Ask it what it can do:

```sh
curl -X POST http://localhost:3000/mcp \
  -H 'content-type: application/json' \
  -H 'accept: application/json, text/event-stream' \
  -H 'mcp-protocol-version: 2026-07-28' \
  -H 'mcp-method: tools/list' \
  -d '{
    "jsonrpc": "2.0", "id": 1,
    "method": "tools/list",
    "params": {
      "_meta": {
        "io.modelcontextprotocol/protocolVersion": "2026-07-28",
        "io.modelcontextprotocol/clientCapabilities": {}
      }
    }
  }'
```

:read-more{to="/examples/curl-requests" title="More request examples"}

### What Happened?

We mounted a single handler with `app.all()`:

```ts
app.all("/mcp", defineMcpHandler({/* ... */}));
```

`all` matters: MCP is `POST`-driven, but the legacy era also uses `GET` (for the SSE stream) and `DELETE` (to tear down a session) on the same path. `defineMcpHandler` answers `405` itself for methods it does not serve, so it is safe to hand it every method.

:read-more{to="/guide/api/exports" title="defineMcpHandler"}

Inside, we declared one tool with `defineMcpTool`:

```ts
defineMcpTool({
  name: "hello",
  inputSchema: {/* JSON Schema, or a Zod / Valibot / ArkType schema */},
  handler: async ({ name }) => ({ content: [{ type: "text", text: `Hello, ${name}!` }] }),
});
```

`defineMcpTool` is an identity function — it returns the definition unchanged and exists purely so TypeScript can infer your handler's argument type from `inputSchema`. The same holds for `defineMcpResource`, `defineMcpResourceTemplate`, and `defineMcpPrompt`.

:read-more{to="/guide/basics/tools" title="Tools"}

That's the whole server. `tools/list`, `tools/call`, `server/discover`, `initialize`, pagination, and input validation are all handled for you.

## Try the Playground

The repository ships a playground server that exercises every feature — tools, resources, templates, prompts, sessions, auth, streaming, caching, MRTR:

```sh
git clone https://github.com/h3js/mcp
cd mcp && pnpm install
pnpm play
```

It serves a small web UI at `http://localhost:6274/` and three endpoints — `/mcp` (dual), `/mcp/modern`, and `/mcp/legacy` — one per [entrypoint](/guide/eras#entrypoints), so you can compare eras against the same definitions.

## Connect a Client

Any MCP client works. For a local `stdio`-only client, bridge with [`mcp-remote`](https://www.npmjs.com/package/mcp-remote):

```json [claude_desktop_config.json]
{
  "mcpServers": {
    "my-server": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "http://localhost:3000/mcp"]
    }
  }
}
```

:read-more{to="/examples/connect-a-client" title="Connecting clients"}
