Gestion MCP Server
Human Resources Management — MCP Server
Servidor MCP (Model Context Protocol) para el sistema de gestión de recursos humanos. Expone tools que un LLM o agente puede invocar mediante Streamable HTTP (y opcionalmente stdio).
Stack
Componente | Tecnología |
Runtime | Node.js 20 Alpine |
Lenguaje | TypeScript 5.7 (compilado con |
Sistema de módulos | ESM ( |
SDK MCP |
|
Transporte HTTP |
|
Transporte Stdio |
|
Validación | Zod v4 |
Servidor HTTP |
|
Contenedor | Docker (Dockerfile en |
Arquitectura: Clean Architecture con módulos
El código sigue Clean Architecture (Puertos y Adaptadores). Las dependencias siempre apuntan hacia adentro:
Infrastructure ──→ Application ──→ Domain
(adaptadores) (casos de uso) (lógica pura)src/shared/types.ts es accesible desde cualquier capa.
Estructura de directorios
src/
├── main.ts ← Entry point: elige transporte según --http / --stdio
│
├── shared/
│ └── types.ts ← Tipos transversales: MCPToolResponse, Result<T>
│
├── template/ ← MÓDULO: tools de plantilla/demo
│ ├── domain/
│ │ └── entities/
│ │ └── HealthStatus.ts ← Entidad de dominio: estado del servidor
│ ├── application/
│ │ ├── dto/
│ │ │ └── index.ts ← DTOs planos: GreetInput/Output, CalculateInput/Output
│ │ └── use-cases/
│ │ ├── greet/
│ │ │ ├── IGreetUseCase.ts ← Puerto (interfaz)
│ │ │ └── GreetUseCase.ts ← Implementación del caso de uso
│ │ ├── calculate/
│ │ │ ├── ICalculateUseCase.ts
│ │ │ └── CalculateUseCase.ts
│ │ └── health-check/
│ │ ├── IHealthCheckUseCase.ts
│ │ └── HealthCheckUseCase.ts
│ └── infrastructure/
│ └── mcp/
│ └── schemas.ts ← Zod schemas + registerTemplateTools()
│
├── infrastructure/ ← Infraestructura COMPARTIDA entre módulos
│ ├── config/
│ │ └── env.ts ← Tipado y carga de variables de entorno
│ ├── mcp/
│ │ └── server-factory.ts ← Orquestador: crea McpServer y registra tools de todos los módulos
│ └── transport/
│ ├── http-server.ts ← Servidor HTTP (StreamableHTTP, stateless)
│ └── stdio-server.ts ← Servidor stdio (para MCP Inspector)
│
└── (futuro) personnel/ ← EJEMPLO: futuro módulo que conectará al Backend
├── domain/...
├── application/...
└── infrastructure/mcp/schemas.tsReglas de dependencia por capa
Capa | Puede importar de | NO puede importar de |
|
|
|
|
|
|
|
| — |
|
| otros módulos directamente |
Responsabilidad de cada archivo
Archivo | Capa | Función |
| — | Punto de entrada. Parsea |
| Shared |
|
| Infra |
|
| Infra |
|
| Infra |
|
| Infra |
|
| Domain | Entidad pura con factory methods |
| Application | DTOs planos: |
| Application | Puerto: interfaz del caso de uso ( |
| Application | Implementación del caso de uso (lógica de negocio) |
| Infra (módulo) | Zod schemas + |
Flujo de una tool call
┌─────────────────────────────────────────────────────────────────────┐
│ 1. Cliente HTTP POST /mcp │
│ Body: {"jsonrpc":"2.0","method":"tools/call","params":{...}} │
│ Headers: Accept: application/json, text/event-stream │
└────────────────────────────┬────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 2. http-server.ts: node:http.createServer │
│ readRequestBody() → toWebRequest() → transport.handleRequest() │
└────────────────────────────┬────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 3. WebStandardStreamableHTTPServerTransport (stateless) │
│ Parsea JSON-RPC, enruta al McpServer │
└────────────────────────────┬────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 4. McpServer (creado en server-factory.ts) │
│ Valida input con Zod schema → ejecuta handler de la tool │
└────────────────────────────┬────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 5. template/infrastructure/mcp/schemas.ts │
│ Handler: Zod valida params → crea DTO → llama UseCase.execute() │
└────────────────────────────┬────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 6. UseCase.execute(dto) → lógica pura │
│ Puede usar entidades de domain/ │
└────────────────────────────┬────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 7. Handler retorna { content: [{ type: "text", text: "..." }] } │
│ McpServer → Transport → JSON-RPC response → HTTP response │
└─────────────────────────────────────────────────────────────────────┘Tools actuales (módulo template)
Tool | Descripción | Input | Output |
| Saludo personalizado |
|
|
| Operaciones aritméticas |
|
|
| Estado del servidor |
|
|
Guía: Cómo agregar una nueva tool
Dentro de un módulo existente (ej: template)
Paso 1: Agregar DTOs en src/template/application/dto/index.ts
export class MyInput {
constructor(public readonly param: string) {}
}
export class MyOutput {
constructor(public readonly result: string) {}
}Paso 2: Crear interfaz src/template/application/use-cases/my-tool/IMyUseCase.ts
import type { MyInput, MyOutput } from '../../dto/index.js';
export interface IMyUseCase {
execute(input: MyInput): Promise<MyOutput>;
}Paso 3: Crear implementación src/template/application/use-cases/my-tool/MyUseCase.ts
import type { IMyUseCase } from './IMyUseCase.js';
import { MyInput, MyOutput } from '../../dto/index.js';
export class MyUseCase implements IMyUseCase {
public async execute(input: MyInput): Promise<MyOutput> {
return new MyOutput(`Processed: ${input.param}`);
}
}Paso 4: Agregar Zod schema y handler en src/template/infrastructure/mcp/schemas.ts
const myInputSchema = z.object({
param: z.string().describe('Description of param'),
});
// Dentro de registerTemplateTools():
server.registerTool(
'my_tool',
{
description: 'What this tool does.',
inputSchema: myInputSchema,
},
async (params: z.infer<typeof myInputSchema>) => {
const input = new MyInput(params.param);
const output = await myUseCase.execute(input);
return {
content: [{ type: 'text' as const, text: output.result }],
};
}
);Errores en tools
Para reportar errores al LLM, retorna isError: true:
catch (error) {
return {
content: [{ type: 'text' as const, text: `Error: ${error.message}` }],
isError: true,
};
}Guía: Cómo crear un nuevo módulo
Cuando necesites una funcionalidad completamente nueva (ej: conexión al backend para gestión de personal), crea un módulo:
Paso 1: Crear la estructura de carpetas
src/personnel/
├── domain/
│ └── entities/
│ └── Employee.ts
├── application/
│ ├── dto/
│ │ └── index.ts
│ └── use-cases/
│ └── get-employee/
│ ├── IGetEmployeeUseCase.ts
│ └── GetEmployeeUseCase.ts
└── infrastructure/
└── mcp/
└── schemas.ts ← export function registerPersonnelTools(server, version)Paso 2: El schemas.ts del módulo debe exportar una función con esta firma:
import type { McpServer } from '@modelcontextprotocol/server';
import { z } from 'zod';
export function registerPersonnelTools(server: McpServer, version: string): void {
const useCase = new GetEmployeeUseCase(/* dependencias */);
server.registerTool('get_employee', {
description: 'Get employee by ID',
inputSchema: z.object({ id: z.string() }),
}, async ({ id }) => {
const output = await useCase.execute(new GetEmployeeInput(id));
return { content: [{ type: 'text' as const, text: JSON.stringify(output) }] };
});
}Paso 3: Registrar el módulo en src/infrastructure/mcp/server-factory.ts
import { registerTemplateTools } from '../../template/infrastructure/mcp/schemas.js';
import { registerPersonnelTools } from '../../personnel/infrastructure/mcp/schemas.js'; // ← nuevo
export function createServerFactory(): McpServer {
const server = new McpServer({ name: 'hrm-mcp', version: SERVER_VERSION });
registerTemplateTools(server, SERVER_VERSION);
registerPersonnelTools(server, SERVER_VERSION); // ← nuevo
return server;
}Transportes
HTTP (Streamable HTTP) — por defecto
Endpoint:
POST /mcp(también aceptaPOST /)Headers requeridos:
Accept: application/json, text/event-streamModo: Stateless (
sessionIdGenerator: undefined). Cada request es independiente.Puerto:
3001(configurable conPORT)Health check:
GET /health→{"status":"ok"}Arranque:
node dist/main.jsonode dist/main.js --http
Stdio — opcional
Arranque:
node dist/main.js --stdioUso: MCP Inspector o clientes que lancen el proceso como hijo
Logs: Usar
console.error(stdout es el canal del protocolo)
Docker
Dockerfile
Ubicado en ../docker/mcp.Dockerfile. Tiene 3 stages:
Stage | Propósito | Comando |
| Clona repo, | — |
| Imagen mínima con solo |
|
| Copia source, instala deps, compila y ejecuta |
|
Comandos Docker Compose
# Construir sin caché
docker compose -f docker-compose.dev.yml build --no-cache mcp-server
# Ejecutar
docker compose -f docker-compose.dev.yml up mcp-server
# Ejecutar en background
docker compose -f docker-compose.dev.yml up -d mcp-server
# Ver logs
docker compose -f docker-compose.dev.yml logs -f mcp-server
# Detener
docker compose -f docker-compose.dev.yml downVolumes en desarrollo
volumes:
- ./HumanResourcesManagement-MCP:/app # Código fuente (live)
- mcp_node_modules:/app/node_modules # node_modules preservadoEl CMD del stage development ejecuta npm run build && node dist/main.js --http, por lo que recompila el TypeScript al iniciar el contenedor.
Testing manual
Health check
curl http://localhost:3001/healthMCP: Listar tools
curl -X POST http://localhost:3001/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","method":"tools/list","id":1}'MCP: Inicializar conexión
curl -X POST http://localhost:3001/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2026-07-28","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}},"id":1}'MCP: Llamar una tool
curl -X POST http://localhost:3001/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"greet","arguments":{"name":"World"}},"id":2}'Variables de entorno
Variable | Default | Descripción |
|
| Entorno ( |
|
| Puerto del servidor HTTP |
|
| URL del backend (para futura conexión) |
|
| Nivel de log ( |
Se cargan desde ../.env.dev en el contenedor de desarrollo.
Convenciones de código
Imports
Todos los imports relativos llevan extensión .js al final (requerido por "moduleResolution": "NodeNext"):
// ✅ Correcto
import { GreetUseCase } from '../../application/use-cases/greet/GreetUseCase.js';
// ❌ Incorrecto
import { GreetUseCase } from '../../application/use-cases/greet/GreetUseCase';Logging
Usar console.error para todos los logs. En modo stdio, stdout es el canal del protocolo JSON-RPC.
console.error('Server started on port 3001'); // ✅
console.log('Server started on port 3001'); // ❌ (corrompe el protocolo en stdio)Tipos literales en tool responses
return {
content: [{ type: 'text' as const, text: 'Hello' }],
};El as const es necesario para que TypeScript infiera el tipo literal 'text' en lugar de string.
Entidades de dominio
Constructor
private+ factory methods estáticos (create,reconstitute)Propiedades privadas con prefijo
_(_status,_version)Getters públicos para acceso de solo lectura
Sin imports de
application/niinfrastructure/
DTOs
Clases con
constructor(public readonly ...)— objetos planos, sin lógicaSin dependencias externas
Sin validaciones de negocio
Use cases
Interfaz (
I*UseCase) + implementación (*UseCase) en archivos separadosReciben DTOs de entrada, retornan DTOs de salida
Pueden usar entidades de dominio y
shared/types.tsNo conocen MCP, HTTP, ni Zod
Dependencias npm
{
"dependencies": {
"@modelcontextprotocol/server": "^2.0.0",
"zod": "^4.4.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"typescript": "^5.7.0"
}
}Nota: El paquete @modelcontextprotocol/server v2 exporta desde su entry point principal WebStandardStreamableHTTPServerTransport y McpServer. StdioServerTransport se importa desde el subpath @modelcontextprotocol/server/stdio.
Scripts npm
npm run build # tsc → dist/
npm start # node dist/main.js (HTTP por defecto)
npm run start:http # node dist/main.js --http
npm run start:stdio # node dist/main.js --stdioResumen para un LLM
Si eres un LLM que va a modificar este código, recuerda:
La lógica de negocio va en use cases (
application/use-cases/), nunca en schemas ni en handlers.Los DTOs son planos, sin comportamiento. Se crean en
application/dto/.Cada tool se registra en
<modulo>/infrastructure/mcp/schemas.tsconserver.registerTool(name, config, handler).El handler de la tool convierte
params(validados por Zod) → DTO → UseCase → DTO →{ content: [...] }.Para crear un nuevo módulo, replica la estructura de
template/y registra su función enserver-factory.ts.Nunca uses
console.log— usaconsole.errorpara logs.Todos los imports relativos llevan
.jsal final.No instales dependencias en el host — todo se prueba dentro del contenedor Docker.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/ErnestoZeferinoDiaz/GeSoft-MCP'
If you have feedback or need assistance with the MCP directory API, please join our Discord server