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

Clone the template

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:
EndpointDescription
GET /healthzHealth check — must return 200 for deploys to succeed
GET /sseMCP clients connect here to open a session
POST /messageMCP 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

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

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:
const apiKey = process.env['MY_API_KEY'];
if (!apiKey) throw new Error('MY_API_KEY is required');
Set the variable before deploying:
ezforge env set my-server MY_API_KEY=sk-...
ezforge deploy

Local development

# Start the server locally
npm run dev

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

Build and deploy

# Build TypeScript
npm run build

# Deploy to ezForge
ezforge deploy

Dependencies

PackagePurpose
@modelcontextprotocol/sdkOfficial MCP SDK (server + types)
expressHTTP 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
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"]