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

# Node.js Template

> Build and deploy a TypeScript MCP server with the official SDK

The Node.js template is a minimal, production-ready starting point for building MCP servers with TypeScript.

## Clone the template

```bash theme={null}
git clone https://github.com/ezforgeai/template-nodejs-mcp-server my-server
cd my-server
npm install
```

## Project structure

```
my-server/
├── src/
│   └── index.ts        # MCP server — edit this to add your tools
├── Dockerfile          # Multi-stage build for production
├── package.json
├── tsconfig.json
└── ezforge.toml        # Deploy configuration
```

## How it works

The template implements MCP over **HTTP + SSE** (Server-Sent Events) using Express:

| Endpoint        | Description                                             |
| --------------- | ------------------------------------------------------- |
| `GET /healthz`  | Health check — must return `200` for deploys to succeed |
| `GET /sse`      | MCP clients connect here to open a session              |
| `POST /message` | MCP clients send tool calls here                        |

The MCP `Server` from `@modelcontextprotocol/sdk` handles the protocol layer. You only need to register tools.

## Adding tools

Open `src/index.ts` and modify the two request handlers:

### 1. Register the tool in `ListToolsRequestSchema`

```typescript theme={null}
server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: 'my_tool',
      description: 'Does something useful.',
      inputSchema: {
        type: 'object',
        properties: {
          input: { type: 'string', description: 'Input value.' },
        },
        required: ['input'],
      },
    },
    // ... other tools
  ],
}));
```

### 2. Handle the tool call in `CallToolRequestSchema`

```typescript theme={null}
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  if (name === 'my_tool') {
    const { input } = args as { input: string };
    // Do something with input
    const result = `Processed: ${input}`;
    return { content: [{ type: 'text', text: result }] };
  }

  throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
});
```

## Using environment variables

Access secrets injected by `ezforge env set`:

```typescript theme={null}
const apiKey = process.env['MY_API_KEY'];
if (!apiKey) throw new Error('MY_API_KEY is required');
```

Set the variable before deploying:

```bash theme={null}
ezforge env set my-server MY_API_KEY=sk-...
ezforge deploy
```

## Local development

```bash theme={null}
# Start the server locally
npm run dev

# Test the health check
curl http://localhost:8080/healthz
# {"status":"ok"}
```

## Build and deploy

```bash theme={null}
# Build TypeScript
npm run build

# Deploy to ezForge
ezforge deploy
```

## Dependencies

| Package                     | Purpose                           |
| --------------------------- | --------------------------------- |
| `@modelcontextprotocol/sdk` | Official MCP SDK (server + types) |
| `express`                   | HTTP transport layer              |

## Dockerfile

The template uses a multi-stage build:

1. **Build stage** — TypeScript compilation
2. **Production stage** — Minimal Node.js image with only compiled output

```dockerfile theme={null}
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY tsconfig.json ./
COPY src/ ./src/
RUN npm run build

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
EXPOSE 8080
CMD ["node", "dist/index.js"]
```
