Skip to main content
Glama
SekiroKenjii

MCP Server Boilerplate

by SekiroKenjii
README.md
# MCP Server Boilerplate

A production-grade starting point for building **[Model Context Protocol](https://modelcontextprotocol.io) (MCP) servers** in native TypeScript. Clean enough for a weekend project, hardened enough for an enterprise deployment.

- ⚡️ **Native TypeScript**, strict, ESM (`NodeNext`)
- 🔌 **Two transports** out of the box — **stdio** (local clients) and **Streamable HTTP** (remote/hosted) with session management
- 🧱 **Modular structure** — one file per tool / resource / prompt, aggregated automatically
- ✅ **Zod-validated** tool I/O and environment config
- 🪵 **Structured logging** (pino) to **stderr only**, so stdio JSON-RPC stays clean
- 🛡️ **DNS-rebinding protection** (Host-header validation) + CORS + optional **bearer auth**
- 🧪 **Vitest** with fast in-memory client↔server tests
- 🧹 **ESLint (type-checked) + Prettier + Husky** pre-commit hooks
- 🐳 **Multi-stage Dockerfile** for the HTTP transport
- 🤖 **Claude Code tooling** — `CLAUDE.md`, skills, and slash commands baked in

## Requirements

- Node.js `>= 22.13` (required by pnpm 11)
- [pnpm](https://pnpm.io) `>= 11` (`corepack enable` will provide it)

## Quick start

```bash
pnpm install
cp .env.example .env

# Local (stdio) development with hot reload
pnpm dev

# Remote (Streamable HTTP) development with hot reload
pnpm dev:http
```

Then build and run the compiled server:

```bash
pnpm build
pnpm start        # stdio
pnpm start:http   # Streamable HTTP on http://127.0.0.1:3000/mcp
```

## Transports

The transport is chosen by the `--transport` CLI flag (which overrides the `MCP_TRANSPORT`
env var), defaulting to `stdio`:

- **stdio** — the client spawns the server and speaks JSON-RPC over stdin/stdout. Use this for
  Claude Desktop, IDEs, and other local integrations.
- **Streamable HTTP** — an Express server at `POST/GET/DELETE /mcp` with per-session transports
  and a `GET /healthz` liveness endpoint. Use this for remote/hosted deployments.

### Connect from Claude Desktop (stdio)

```jsonc
{
  "mcpServers": {
    "mcp-boilerplate": {
      "command": "node",
      "args": ["/absolute/path/to/dist/index.js"],
    },
  },
}
```

### Authentication (HTTP)

The HTTP transport is unauthenticated by default (protected by `ALLOWED_HOSTS`). To require a
bearer token, set `AUTH_ENABLED=true` and provide `AUTH_TOKENS`:

```bash
AUTH_ENABLED=true AUTH_TOKENS=my-secret-token pnpm start:http
# Requests to /mcp without "Authorization: Bearer my-secret-token" get 401.
```

The bundled verifier in [src/core/auth.ts](src/core/auth.ts) checks a static token allowlist —
**replace `verifyAccessToken` with real JWT verification or OAuth token introspection** for
production. The validated `AuthInfo` is available to tool handlers via `extra.authInfo`.

## Configuration

All configuration is environment-based and validated at startup (see [.env.example](.env.example)):

| Variable                     | Default               | Description                                                            |
| ---------------------------- | --------------------- | ---------------------------------------------------------------------- |
| `MCP_TRANSPORT`              | `stdio`               | `stdio` or `http` (CLI `--transport` wins)                             |
| `HOST`                       | `127.0.0.1`           | HTTP bind address                                                      |
| `PORT`                       | `3000`                | HTTP port                                                              |
| `ALLOWED_HOSTS`              | `127.0.0.1,localhost` | Allowed `Host` hostnames (port-agnostic). Empty disables protection.   |
| `CORS_ORIGINS`               | _(empty)_             | Comma-separated CORS origins for browser clients. Empty disables CORS. |
| `AUTH_ENABLED`               | `false`               | Require a bearer token on `/mcp` (HTTP transport).                     |
| `AUTH_TOKENS`                | _(empty)_             | Comma-separated tokens accepted by the example verifier.               |
| `AUTH_REQUIRED_SCOPES`       | _(empty)_             | Comma-separated scopes every token must carry.                         |
| `AUTH_RESOURCE_METADATA_URL` | _(empty)_             | Protected Resource Metadata URL advertised in 401 responses.           |
| `LOG_LEVEL`                  | `info`                | `fatal`…`trace`/`silent`                                               |
| `NODE_ENV`                   | `development`         | `production` emits plain JSON logs                                     |

## Project structure

```
src/
  index.ts          Entrypoint: transport resolution + graceful shutdown
  server.ts         createServer(): builds McpServer, registers everything
  config/env.ts     Zod-validated environment configuration
  core/             logger (stderr), errors (AppError + toToolError), pkg (name/version)
  transports/       stdio.ts, http.ts (Express + Streamable HTTP)
  tools/            echo.tool.ts + index.ts aggregator
  resources/        system-info.resource.ts + index.ts aggregator
  prompts/          summarize.prompt.ts + index.ts aggregator
tests/              Vitest, in-memory client↔server (helpers/connect.ts)
```

## Adding features

Adding a feature is always: **new file + one import line** in the folder's `index.ts`.

```bash
# With Claude Code:
/new-tool weather "get the forecast for a city"
/new-resource changelog "expose CHANGELOG.md"
/new-prompt review "ask the model to review a diff"
```

Or by hand — copy `src/tools/echo.tool.ts`, define Zod schemas, register it in
`src/tools/index.ts`, and add a test. See [CLAUDE.md](CLAUDE.md) for the full conventions.

## Testing & inspecting

```bash
pnpm test            # run once
pnpm test:watch      # watch mode
pnpm test:coverage   # coverage report
pnpm inspector       # launch the MCP Inspector against the server over stdio
```

## Docker (HTTP transport)

```bash
docker build -t mcp-boilerplate .
docker run --rm -p 3000:3000 \
  -e ALLOWED_HOSTS=localhost,127.0.0.1 \
  mcp-boilerplate

# or with Docker Compose (includes a healthcheck):
docker compose up --build
```

## Commits & releases

- **Conventional Commits** are enforced by a `commit-msg` hook (commitlint). Use
  `feat:`, `fix:`, `chore:`, `docs:`, etc. — e.g. `feat(tools): add weather tool`.
- **Versioning** uses [changesets](https://github.com/changesets/changesets):
  `pnpm changeset` to record a change, `pnpm changeset:version` to bump + update the changelog,
  `pnpm changeset:release` to publish.

## Claude Code tooling

This repo ships agent tooling under [.claude/](.claude/) and [CLAUDE.md](CLAUDE.md):

- **`karpathy-guidelines`** skill — behavioral guardrails (think first, stay simple, surgical
  changes, verify) vendored from
  [multica-ai/andrej-karpathy-skills](https://github.com/multica-ai/andrej-karpathy-skills).
- **`mcp-add-tool` / `mcp-add-resource` / `mcp-add-prompt`** skills — encode this repo's exact
  patterns so new features stay consistent.
- **`/new-tool` / `/new-resource` / `/new-prompt`** slash commands.
- **`settings.json`** — a permission allowlist for the common project commands.

## Scripts

| Script                                                 | Description                          |
| ------------------------------------------------------ | ------------------------------------ |
| `pnpm dev` / `pnpm dev:http`                           | Watch-mode dev (stdio / HTTP)        |
| `pnpm build`                                           | Transpile `src/` → `dist/` with tsup |
| `pnpm start` / `pnpm start:http`                       | Run the built server                 |
| `pnpm typecheck`                                       | `tsc --noEmit`                       |
| `pnpm lint` / `pnpm lint:fix`                          | ESLint (type-checked)                |
| `pnpm format` / `pnpm format:check`                    | Prettier                             |
| `pnpm test` / `pnpm test:watch` / `pnpm test:coverage` | Vitest                               |
| `pnpm inspector`                                       | MCP Inspector over stdio             |

## License

MIT

TDQS

A4.1/5.0

Scored across 1 tool

Disambiguation5/5

Only one tool exists, so there is no possibility of ambiguity between tools.

Naming Consistency5/5

With a single tool named 'echo', naming is consistent by default; it follows a simple verb pattern.

Tool Count1/5

A single trivial 'echo' tool is an extreme mismatch for a server implied to be a boilerplate; it provides minimal functionality.

Completeness3/5

For the simple task of echoing messages, the tool is sufficient, but the server lacks any additional features that might be expected from a functional MCP server.

Maintenance

ActivityStale
ResponsivenessNo issues