> ## Documentation Index
> Fetch the complete documentation index at: https://anvil.thatdevguy.in/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart: Build Your First Anvil JS API in Minutes

> Install Anvil, create your first file-based route, run the dev server, and expose a route as an MCP tool — all in under five minutes.

Anvil gets you from an empty directory to a running TypeScript API in minutes. This guide installs the package, scaffolds a project with `anvil init`, walks through a static route and a validated dynamic route, runs the dev server, exposes that route as an MCP tool with zero duplicated code, and turns a second route into a streaming AI agent — all with the same file-based routing convention.

<Info>
  Coming from an empty directory? Every command below works as-is. Adding Anvil to an existing project? Skip to [Installation](/installation) — `anvil init` merges into an existing `package.json` instead of overwriting it.
</Info>

<Steps>
  <Step title="Install and scaffold">
    Create a project directory and install Anvil plus Zod (used for route and tool schemas):

    <CodeGroup>
      ```bash npm theme={null}
      mkdir my-api && cd my-api
      npm init -y
      npm install anvil-sdk zod
      ```

      ```bash yarn theme={null}
      mkdir my-api && cd my-api
      yarn init -y
      yarn add anvil-sdk zod
      ```

      ```bash pnpm theme={null}
      mkdir my-api && cd my-api
      pnpm init
      pnpm add anvil-sdk zod
      ```
    </CodeGroup>

    Then scaffold the project structure:

    ```bash theme={null}
    npx anvil init
    ```

    ```text theme={null}
               _____________
      ________/             \
    _____/_______________________\___
    \_______________________________/
             \                 /
              \_______________/
                   |||||
                   |||||
              _____|||||_____
             /_______________\
             \_______________/

      ANVIL — Express for humans. Anvil for agents.

    Which template do you want to start from?

      1) Basic API — A minimal REST API — one static route, one dynamic route.
      2) MCP server — REST routes exposed as MCP tools, plus a standalone tool.
      3) Agent route — A streaming agent endpoint using the AI SDK data-stream protocol.

    Pick 1-3 [1]:
    ```

    Pick **1) Basic API** for this walkthrough — press Enter to accept the default. `anvil init` writes:

    * `package.json` — adds `"type": "module"`, the `dev`/`build`/`start`/`lint` scripts, and `anvil-sdk`/`zod` to `dependencies` (merged in, not overwritten, if the file already exists)
    * `tsconfig.json` — a strict, ESM-ready config
    * `.gitignore` — excludes `node_modules/`, `dist/`, `.gen/`, `.anvil/`
    * `server/routes/get.ts` — a starter route:

    ```ts server/routes/get.ts theme={null}
    export default function handler() {
      return { message: 'Hello from Anvil!' };
    }
    ```

    Plain objects returned from a handler are automatically serialized to JSON — no `res.json(...)` boilerplate to write. `anvil init` never overwrites a file that already exists, so re-running it is always safe.

    <Tip>
      Skip the interactive prompt with `npx anvil init --template mcp` (or `agent`), or `npx anvil init -y` to accept the `basic` default — useful when scripting or in CI. Full flag reference: [`anvil init`](/cli/init).
    </Tip>
  </Step>

  <Step title="Install dependencies and start the dev server">
    ```bash theme={null}
    npm install
    npm run dev
    ```

    ```text theme={null}
    [anvil] dev server listening on http://localhost:3000
      GET /
    ```

    Route files are loaded on the fly through `jiti` — no separate compile step, and no restart needed when you edit a handler. Confirm it's working:

    ```bash theme={null}
    curl http://localhost:3000/
    ```

    ```json theme={null}
    { "message": "Hello from Anvil!" }
    ```
  </Step>

  <Step title="Add a dynamic route">
    File-based routing maps folders to URL segments. A `[param]` folder captures a path parameter, typed from the folder name — add one under `server/routes/`:

    ```ts server/routes/users/[id]/get.ts theme={null}
    import { HttpError, type Context } from 'anvil-sdk';

    const USERS = [
      { id: '1', name: 'Ada Lovelace' },
      { id: '2', name: 'Alan Turing' },
    ];

    export default function handler(ctx: Context) {
      const user = USERS.find((u) => u.id === ctx.params.id);
      if (!user) throw new HttpError(404, `No user "${ctx.params.id}"`);
      return user;
    }
    ```

    `ctx.params.id` is typed as a `string` because the compiler reads the `[id]` folder name at build time — no manual param typing, and no way for the folder structure and your handler to drift apart. Try it:

    ```bash theme={null}
    curl http://localhost:3000/users/1
    ```

    ```json theme={null}
    { "id": "1", "name": "Ada Lovelace" }
    ```

    ```bash theme={null}
    curl http://localhost:3000/users/99
    ```

    ```json theme={null}
    { "error": "No user \"99\"" }
    ```

    `HttpError` sets the response status (`404` here) and serializes a clean JSON error body — no manual `ctx.res.status(...)` calls.

    <Tip>
      See [Dynamic Routes](/routing/dynamic-routes) for catch-all segments (`[...path]`) and validating params against a Zod schema so malformed input 400s before your handler runs.
    </Tip>
  </Step>

  <Step title="Expose the route as an MCP tool">
    Add a `meta` export and a `paramsSchema`, and the exact same handler becomes callable by any MCP client — no second implementation, no schema drift:

    ```ts server/routes/users/[id]/get.ts theme={null}
    import { HttpError, type Context } from 'anvil-sdk';
    import { z } from 'zod';

    export const meta = { mcp: { expose: true, description: 'Fetch a user by ID' } };
    export const paramsSchema = z.object({ id: z.string() });

    const USERS = [
      { id: '1', name: 'Ada Lovelace' },
      { id: '2', name: 'Alan Turing' },
    ];

    export default function handler(ctx: Context) {
      const user = USERS.find((u) => u.id === ctx.params.id);
      if (!user) throw new HttpError(404, `No user "${ctx.params.id}"`);
      return user;
    }
    ```

    Serve it over MCP:

    ```bash theme={null}
    npx anvil mcp
    ```

    ```text theme={null}
    [anvil] mcp (Streamable HTTP) on http://localhost:3100/mcp
    [anvil] 1 tool(s): get_users_by_id
    ```

    Any MCP client — Claude Desktop, an agent framework, or a raw `curl` JSON-RPC call — can now discover and call `get_users_by_id`. The `paramsSchema` becomes the tool's input schema automatically; there is no separate tool definition to keep in sync with the route. See [MCP Overview](/mcp/overview) for stdio transport and standalone tools that don't back an HTTP route.
  </Step>

  <Step title="Turn a route into a streaming agent">
    Agent routes use the same file convention — an `agent.ts` file instead of `get.ts`/`post.ts` — and stream responses using the Vercel AI SDK data-stream protocol, so `useChat` works against them with no adapter code:

    ```ts server/routes/chat/agent.ts theme={null}
    import { defineAgent } from 'anvil-sdk/agent';
    import { LlmClient, AnthropicDriver } from 'anvil-sdk/llm';

    const client = new LlmClient({
      drivers: [new AnthropicDriver({ apiKey: process.env.ANTHROPIC_API_KEY })],
      defaultModel: 'claude-opus-4-8',
    });

    export default defineAgent({
      client,
      system: 'You are a helpful assistant.',
    });
    ```

    ```bash theme={null}
    npm install @anthropic-ai/sdk
    curl -N -X POST http://localhost:3000/chat \
      -H 'content-type: application/json' \
      -d '{"messages":[{"role":"user","content":"hi"}]}'
    ```

    <Note>
      No API key handy? Scaffold the **agent** template instead (`npx anvil init --template agent`) — it wires up `MockDriver`, which returns canned responses with no network calls or API key required, so you can see the streaming protocol working immediately.
    </Note>

    See [Agent Routes](/agents/define-agent) for tool calling, [LLM Client](/agents/llm-client) for the OpenAI and Gemini drivers, and [Multi-Agent Orchestration](/agents/orchestration) for composing agents together.
  </Step>

  <Step title="Validate before you ship">
    ```bash theme={null}
    npx anvil lint
    ```

    ```text theme={null}
    [anvil] lint: 3 route(s) checked, 0 error(s), 0 warning(s)
    ```

    `anvil lint` checks that every `paramsSchema` key matches the route's dynamic segments, and that every MCP-exposed schema (`paramsSchema`, `bodySchema`, `querySchema`, `outputSchema`) converts losslessly to JSON Schema — a `.transform()` or `.refine()` on an exposed schema is a **build-time error**, not a runtime surprise discovered by a caller. Run it in CI alongside `anvil build`, which also fails hard on structural route conflicts.
  </Step>
</Steps>

## What you just built

A single route file (`server/routes/users/[id]/get.ts`) that is simultaneously a typed REST endpoint, a schema-validated MCP tool, and lint-checked at build time — with no duplicated logic between any of those surfaces. That's the core idea behind Anvil: one file-based route definition, multiple protocols.

## Next steps

<CardGroup cols={2}>
  <Card title="File-Based Routing" icon="folder-tree" href="/routing/file-based-routing">
    The full routing convention: verbs, groups, catch-alls, and the generated route manifest.
  </Card>

  <Card title="Dynamic Routes" icon="brackets-curly" href="/routing/dynamic-routes">
    `[param]` and `[...param]` segments, Zod validation, and route-conflict rules.
  </Card>

  <Card title="MCP Overview" icon="plug" href="/mcp/overview">
    Expose routes and standalone tools as MCP tools — stdio and Streamable HTTP.
  </Card>

  <Card title="Agent Routes" icon="robot" href="/agents/define-agent">
    Tool calling, streaming, context assembly, and the `LlmClient` driver model.
  </Card>

  <Card title="anvil init" icon="wand-magic-sparkles" href="/cli/init">
    Every template, flag, and file `anvil init` writes — including merging into an existing project.
  </Card>

  <Card title="Observability" icon="chart-line" href="/observability/tracing">
    Every agent run traced automatically, with a built-in dashboard at `/_anvil`.
  </Card>
</CardGroup>
