Skip to main content
Glama
gurunate

node-mcp-poc

by gurunate
README.md
# node-mcp-poc

A minimal [Model Context Protocol](https://modelcontextprotocol.io) server in
Node.js/TypeScript. It runs over **HTTP** (Express + Streamable HTTP) by
default, with **stdio** available as a fallback, and is attachable to
**Claude Code**.

It exposes four demo tools:

| Tool        | Input                             | Returns                             |
| ----------- | --------------------------------- | ----------------------------------- |
| `add`       | `{ a: number, b: number }`        | the sum, e.g. `2 + 3 = 5`           |
| `echo`      | `{ text: string }`                | the same text                       |
| `now`       | `{ timezone?: string }`           | current time (ISO + localized)      |
| `fetch_url` | `{ url: string, maxChars?: int }` | HTTP GET body (JSON pretty-printed) |

## Build

Requires **Node.js 24** (see `.nvmrc`) and **pnpm**. Builds and tests run on
[Vite](https://vite.dev) / [Vitest](https://vitest.dev).

```bash
pnpm install
pnpm build     # vite build -> build/index.js (server) + build/lambda.js (Lambda)
```

## Run

The server reads its config from the environment (a `.env` file is loaded
automatically — see `.env.example`):

| Variable        | Default | Purpose                                            |
| --------------- | ------- | -------------------------------------------------- |
| `MCP_TRANSPORT` | `http`  | `http` (Express) or `stdio`                        |
| `PORT`          | `3219`  | HTTP port; endpoint is `http://localhost:PORT/mcp` |
| `LOG_LEVEL`     | `info`  | pino log level                                     |
| `NODE_ENV`      | —       | `development` pretty-prints HTTP logs              |

```bash
pnpm start          # HTTP server on http://localhost:3219/mcp
pnpm start:stdio    # stdio transport instead
```

## Attach to Claude Code

This repo ships a project-level `.mcp.json` pointing at the HTTP endpoint, so
within this directory the server is picked up automatically once it's running:

```jsonc
{
    "mcpServers": {
        "node-poc": { "type": "http", "url": "http://localhost:3219/mcp" },
    },
}
```

Start the server (`pnpm start`), then start (or restart) a Claude Code session
and try:

- "add 2 and 3"
- "echo hello"
- "what time is it in America/New_York"
- "fetch https://api.github.com/zen via node-poc"

Check it's connected with `claude mcp list`.

To register it from anywhere instead of relying on `.mcp.json`:

```bash
# HTTP (server must already be running)
claude mcp add --transport http node-poc http://localhost:3219/mcp

# or stdio (Claude Code launches the process for you)
claude mcp add node-poc -- node /ABSOLUTE/PATH/TO/node-mcp-poc/build/index.js
```

Remove it with:

```bash
claude mcp remove node-poc
```

## Develop / debug

```bash
pnpm dev        # rebuild + node --watch
pnpm test       # vitest run
pnpm test:watch # vitest (watch mode)
pnpm typecheck  # tsc --noEmit
pnpm smoke      # build + stdio client smoke test (scripts/smoke.mjs)
pnpm smoke:http # HTTP client smoke test (scripts/smoke-http.mjs)
pnpm logs       # tail logs/server.log through pino-pretty
pnpm inspect    # open the MCP Inspector against the server
```

## Deploy

Deploying to AWS? See **[docs/README.md](docs/README.md)**.

- **Lambda (SAM CLI)** — `src/lambda.ts` is a stateless variant of the server;
  `template.yaml` deploys it on `nodejs24.x` behind a Function URL.
  `pnpm sam:build && sam deploy --guided`.
- **Container (App Runner / ECS Fargate)** — the prime target for the stateful
  `src/index.ts` server (in-memory sessions + SSE), which Lambda can't host.

## Notes

- The tools are defined once in `src/mcp.ts` (`createMcpServer`) and shared by
  both entry points: `src/index.ts` (long-running, stateful HTTP/stdio) and
  `src/lambda.ts` (stateless, for Lambda).
- In **HTTP** mode each MCP session gets its own server instance, keyed by the
  `Mcp-Session-Id` header; logs go to stdout (pretty-printed when
  `NODE_ENV=development`).
- In **stdio** mode stdout is reserved for the JSON-RPC wire, so logs go to
  stderr **and** `logs/server.log` (view with `pnpm logs`).
- Add new tools in `src/mcp.ts` via `server.registerTool(...)`.