Skip to main content
Anvil gets you from zero to a running TypeScript API in minutes. This guide walks you through installing the package, creating a minimal project, writing your first route, running the dev server, and exposing that same route as an MCP tool — no extra code, no second codebase.
1

Install and scaffold

Create a project directory, install Anvil plus Zod (used for route and tool schemas), then scaffold:
mkdir my-api && cd my-api
npm init -y
npm install anvil zod
npx anvil init
anvil init prompts you to pick a template — choose basic for this walkthrough. It writes package.json ("type": "module" plus the dev/build/start/lint scripts), tsconfig.json, .gitignore, and a starter route at server/routes/get.ts:
server/routes/get.ts
export default function handler() {
  return { message: 'Hello from Anvil!' };
}
Plain objects returned from a handler are automatically serialized to JSON — no res.json(...) boilerplate. See anvil init for the MCP and agent templates, and Installation for optional peer dependencies (Anthropic, OpenAI, Gemini, SQLite).
2

Start the dev server

npm install
npm run dev
[anvil] dev server listening on http://localhost:3000
  GET /
Routes are loaded through jiti, so edits to route files take effect immediately — no separate compile step. Confirm it works:
curl http://localhost:3000/
# {"message":"Hello from Anvil!"}
3

Add a dynamic route

Add a [param] folder for a path parameter:
server/routes/users/[id]/get.ts
import { HttpError, type Context } from 'anvil';

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

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;
}
curl http://localhost:3000/users/1
# {"id":"1","name":"Ada Lovelace"}
See Dynamic Routes for catch-all segments and Zod-validated params.
4

Expose the route as an MCP tool

Add two exports — meta and a paramsSchema — and the same handler becomes an MCP tool, with zero duplicated schema:
server/routes/users/[id]/get.ts
import { HttpError, type Context } from 'anvil';
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' }];

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:
npx anvil mcp
# [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, curl) can now call get_users_by_id — see MCP Overview.
5

Validate before you ship

npx anvil lint
Checks that paramsSchema keys match the route’s dynamic segments, and that every MCP-exposed schema converts losslessly to JSON Schema — catching drift at build time instead of at call time. Run it in CI alongside anvil build.

Next steps

File-Based Routing

Learn the full routing convention: verbs, groups, catch-alls, and the route manifest.

Dynamic Routes

Understand [param] and [...param] segments, Zod validation, and naming rules.

MCP Overview

Expose routes and standalone tools as MCP tools — no separate MCP server.

Agent Routes

Turn a route into a streaming AI agent with defineAgent and an LlmClient.