Skip to main content
Glama
ljutreras
by ljutreras
README.md
# MCP Server Template

Template hexagonal para crear MCP servers en Node.js/TypeScript.
**Objetivo: agregar un nuevo tool = crear 1 archivo + 1 línea.**

## Estructura

```
src/
├── index.ts                     ← entrypoint (3 líneas)
│
├── core/
│   ├── registry.ts              ← ToolRegistry: registro + dispatcher automático
│   ├── server.ts                ← factory de instancias McpServer
│   └── run.ts                   ← transporte dual stdio/HTTP
│
├── tools/
│   ├── index.ts                 ← ← ← ÚNICO ARCHIVO QUE EDITAS AL AGREGAR TOOLS
│   └── examples/
│       └── es-search.tool.ts    ← ejemplo completo (patrón a seguir)
│
├── infra/
│   ├── clients/
│   │   └── elasticsearch.client.ts   ← singleton de ES
│   └── formatters/
│       └── result.formatter.ts       ← limpieza de resultados para el LLM
│
├── config/
│   ├── env.ts                   ← variables de entorno validadas con Zod
│   └── logger.ts                ← pino → stderr (MCP-safe)
│
└── shared/
    ├── types/
    │   └── tool.types.ts        ← ToolDefinition interface
    └── errors/
        └── mcp.error.ts         ← toMcpError / toMcpSuccess helpers
```

## Agregar un nuevo tool (3 pasos)

### Paso 1 — Crea `src/tools/mi-feature.tool.ts`

```typescript
import { z } from "zod";
import { ToolDefinition } from "../shared/types/tool.types.js";
import { toMcpSuccess } from "../shared/errors/mcp.error.js";

const MiInput = z.object({
  param: z.string().describe("Descripción para el LLM"),
});

async function miHandler(input: z.infer<typeof MiInput>) {
  // tu lógica aquí
  return toMcpSuccess({ resultado: input.param });
}

export const miTool: ToolDefinition<z.infer<typeof MiInput>> = {
  name: "mi_tool",
  description: "Descripción para el LLM",
  inputSchema: MiInput,
  handler: miHandler,
};
```

### Paso 2 — Regístralo en `src/tools/index.ts`

```typescript
import { miTool } from "./mi-feature.tool.js";

export function registerAllTools(registry: ToolRegistry): void {
  registry
    .register(esSearchKeywordTool)
    .register(miTool);  // ← esta línea
}
```

### Paso 3 — Listo. El dispatcher, JSON Schema y listado MCP se actualizan solos.

## Variables de entorno

```env
SERVER_NAME=mi-mcp-server
SERVER_VERSION=1.0.0
MCP_TRANSPORT=stdio          # stdio | http
PORT=3001
ELASTICSEARCH_URL=http://localhost:9200
LOG_LEVEL=info
```

## Comandos

```bash
npm install
npm run dev           # dev stdio
npm run dev:http      # dev MCP over HTTP
npm run prod          # prod MCP over HTTP
```

## Regla de oro del MCP logging

**Nunca uses `console.log` en un MCP server en modo stdio.**
`stdout` es el canal del protocolo. Un `console.log` rompe el handshake.
Usa siempre `logger.info(...)` de `config/logger.ts` — escribe a stderr.