mcp-server
A thin TypeScript MCP server that exposes engineering tools (Bitbucket, Jira, Confluence, ArgoCD) to MCP-compatible editors via a single API key per developer, delegating all service interactions to an internal backend.
README
mcp-server
MCP Server en TypeScript que expone las herramientas de ingeniería del equipo —Bitbucket, Jira, Confluence y ArgoCD— a los editores con soporte MCP (VS Code + Copilot, Claude Code, etc.).
Es una capa delgada (thin wrapper): no habla con Bitbucket/Jira/Confluence/ArgoCD, ni guarda credenciales de esos servicios. Todo lo traduce a llamadas HTTP contra el backend interno eng-api, que ya tiene resueltas conexiones y credenciales.
VS Code (dev A) ─┐
VS Code (dev B) ─┼─► MCP Server ───► eng-api ───► Bitbucket / Jira / Confluence / ArgoCD
VS Code (dev C) ─┘ (este repo) (credenciales viven aquí)
Streamable HTTP HTTP
+ API key por dev
Ventaja: ningún dev necesita tokens personales de Bitbucket/Jira/Confluence/ArgoCD. Solo una API key de este MCP Server, revocable individualmente.
1. Requisitos
- Node.js ≥ 22
- Acceso de red a
ENG_API_BASE_URL(la URL de eng-api)
2. Correr localmente
npm ci
cp .env.example .env # y rellena los valores (ver sección 3)
npm run dev # hot-reload, lee .env automáticamente
Otros comandos:
| Comando | Qué hace |
|---|---|
npm run dev |
Arranca en modo watch leyendo .env |
npm run build |
Compila TypeScript a dist/ |
npm run typecheck |
Type-check sin emitir |
npm start |
Arranca lo compilado (usa variables del entorno; es lo que corre en el pod) |
npm run start:local |
Arranca lo compilado leyendo .env |
Comprobación rápida de que está vivo:
curl http://localhost:3000/healthz
# {"status":"ok","server":"mcp-server","version":"0.1.0"}
3. Configuración (.env)
Todas las variables se leen de process.env. Si falta una obligatoria o tiene un valor inválido, el proceso no arranca y explica exactamente qué corregir.
| Variable | Obligatoria | Default | Descripción |
|---|---|---|---|
ENG_API_BASE_URL |
✅ | — | URL base de eng-api, sin barra final. Debe ser http(s)://… |
MCP_DEV_API_KEYS |
✅ | — | API keys válidas de los devs contra este MCP Server (ver §4) |
ENG_API_TIMEOUT_MS |
— | 10000 |
Timeout por llamada a eng-api (1000–120000) |
ENG_API_MAX_RETRIES |
— | 2 |
Reintentos adicionales ante 5xx/429/timeout (0–5) |
PORT |
— | 3000 |
Puerto HTTP del MCP Server |
LOG_LEVEL |
— | info |
debug | info | warn | error |
Ejemplo de arranque fallido (a propósito):
Configuración inválida: el MCP Server no puede arrancar.
- Falta la variable obligatoria ENG_API_BASE_URL. Debe apuntar a la URL base de eng-api, ej. https://eng-api.internal.example/api/v1
Revisa tu archivo .env (usa .env.example como plantilla) o el ConfigMap/Secret del Deployment.
4. Autenticación: una API key por dev
Esta capa de auth es propia del MCP Server e independiente de la que eng-api use hacia los servicios finales.
Generar keys
openssl rand -hex 32 # una por cada persona del equipo
Configurarlas
MCP_DEV_API_KEYS acepta cuatro formatos (mínimo 24 caracteres por key, sin duplicados):
MCP_DEV_API_KEYS=<key1>,<key2> # CSV simple
MCP_DEV_API_KEYS=alice:<key1>,bob:<key2> # CSV etiquetado ← recomendado
MCP_DEV_API_KEYS=["<key1>","<key2>"] # JSON array
MCP_DEV_API_KEYS={"alice":"<key1>","bob":"<key2>"} # JSON objeto
Usa el formato etiquetado: la etiqueta aparece en los logs del MCP Server y se propaga a eng-api en el header X-Mcp-Dev, así que se puede auditar quién disparó cada operación (por ejemplo, un argocd_sync_app) sin exponer la key.
Usarlas
El cliente MCP debe enviar en cada petición:
Authorization: Bearer <API_KEY>
(o, como alternativa, x-api-key: <API_KEY>). La comparación es timing-safe sobre digests SHA-256.
| Situación | Respuesta |
|---|---|
| Sin key | 401 + mensaje indicando qué header falta |
| Key inválida/revocada | 403 + mensaje indicando qué revisar |
/healthz, /readyz |
Sin auth (para los probes de Kubernetes) |
Revocar a alguien = quitar su key de MCP_DEV_API_KEYS y reiniciar el Deployment. Como cada dev tiene la suya, no afecta al resto. En producción, guarda el valor en un Secret de Kubernetes, nunca en un ConfigMap.
5. Catálogo de tools
Los nombres llevan prefijo del servicio y son orientados a acción. Todos soportan paginación donde aplica (page, pageSize de 1 a 100, por defecto 25).
Bitbucket (solo lectura)
| Tool | Argumentos | Endpoint eng-api |
|---|---|---|
bitbucket_list_prs |
workspace, repoSlug, state? (OPEN|MERGED|DECLINED|ALL), author?, page?, pageSize? |
GET /bitbucket/repositories/{ws}/{repo}/pull-requests |
bitbucket_get_pr |
workspace, repoSlug, pullRequestId |
GET /bitbucket/repositories/{ws}/{repo}/pull-requests/{id} |
bitbucket_get_commits |
workspace, repoSlug, branch, sinceCommit?, sinceDate?, page?, pageSize? |
GET /bitbucket/repositories/{ws}/{repo}/commits |
Jira
| Tool | Argumentos | Endpoint eng-api |
|---|---|---|
jira_search_issues |
jql? o filtros simples (projectKey?, status?, assignee?, labels?), fields?, page?, pageSize? |
POST /jira/issues/search |
jira_get_issue |
issueKey (formato PLAT-4821), fields?, includeComments? |
GET /jira/issues/{key} |
jira_create_issue ✍️ |
projectKey, issueType, summary, description?, assignee?, labels?, priority?, parentKey?, extraFields? |
POST /jira/issues |
Confluence (solo lectura)
| Tool | Argumentos | Endpoint eng-api |
|---|---|---|
confluence_search_pages |
query, spaceKey?, page?, pageSize? |
GET /confluence/pages/search |
confluence_get_page |
pageId, format? (plain|storage|view) |
GET /confluence/pages/{id} |
ArgoCD
| Tool | Argumentos | Endpoint eng-api |
|---|---|---|
argocd_list_apps |
project?, namespace?, syncStatus?, healthStatus?, page?, pageSize? |
GET /argocd/applications |
argocd_get_app_status |
appName |
GET /argocd/applications/{name} |
argocd_sync_app ⚠️ |
appName (exacto, sin default), revision?, prune?, dryRun?, resources? |
POST /argocd/applications/{name}/sync |
Anotaciones (hints para el cliente MCP)
| Tool | readOnlyHint |
destructiveHint |
idempotentHint |
openWorldHint |
|---|---|---|---|---|
| Todos los de lectura | ✅ | ❌ | ✅ | ✅ |
jira_create_issue ✍️ |
❌ | ❌ | ❌ | ✅ |
argocd_sync_app ⚠️ |
❌ | ✅ | ❌ | ✅ |
argocd_sync_app exige el nombre exacto de la app (sin comodines ni valores por defecto) y prune/dryRun van en false salvo que se pidan explícitamente.
Las rutas de eng-api viven todas en src/client/routes.ts. Si eng-api cambia un path, se toca solo ese archivo.
6. Configurar VS Code (cada dev, con su propia key)
Crea .vscode/mcp.json en tu workspace (o el mcp.json de usuario, si lo quieres en todos los proyectos):
{
"inputs": [
{
"type": "promptString",
"id": "eng-mcp-api-key",
"description": "Tu API key personal del MCP Server de ingeniería",
"password": true
}
],
"servers": {
"eng": {
"type": "http",
"url": "https://<host-del-mcp-server>/mcp",
"headers": {
"Authorization": "Bearer ${input:eng-mcp-api-key}"
}
}
}
}
VS Code pedirá la key la primera vez y la guardará cifrada; no se commitea nunca. Después, abre el chat en modo Agent y verás los 11 tools bajo el servidor eng.
Para Claude Code (CLI), el equivalente es:
claude mcp add --transport http eng https://<host-del-mcp-server>/mcp \
--header "Authorization: Bearer <TU_API_KEY>"
En local, sustituye la URL por http://localhost:3000/mcp.
7. Probarlo con MCP Inspector
npm run build && npm run start:local # en una terminal
npx @modelcontextprotocol/inspector # en otra
En la UI del Inspector:
- Transport Type:
Streamable HTTP - URL:
http://localhost:3000/mcp - En Authentication, pon Header Name
Authorizationy el Bearer Token con tu API key - Connect → pestaña Tools → List Tools → prueba cualquiera
También se puede probar con curl directamente (útil en CI o desde un pod):
KEY=<tu-api-key>
curl -s -X POST http://localhost:3000/mcp \
-H "Accept: application/json, text/event-stream" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $KEY" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq '.result.tools[].name'
Llamar a un tool:
curl -s -X POST http://localhost:3000/mcp \
-H "Accept: application/json, text/event-stream" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $KEY" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{
"name":"bitbucket_list_prs",
"arguments":{"workspace":"acme","repoSlug":"web-frontend","state":"OPEN","pageSize":10}}}'
8. Docker
docker build -t mcp-server:0.1.0 .
docker run --rm -p 3000:3000 \
-e ENG_API_BASE_URL="https://<eng-api>/api/v1" \
-e MCP_DEV_API_KEYS="alice:<key1>,bob:<key2>" \
mcp-server:0.1.0
Imagen multi-stage sobre node:22-alpine: la final solo lleva dist/ + dependencias de producción, corre como usuario node (sin root) e incluye un HEALTHCHECK que golpea /healthz con el propio Node (sin curl/wget).
Para Kubernetes (los manifiestos no están en este repo):
- El servidor es stateless: no guarda sesiones en memoria, así que escala a N réplicas sin sticky sessions.
- Probes:
livenessProbe→GET /healthz,readinessProbe→GET /readyz(ambos sin auth). MCP_DEV_API_KEYSva en unSecret;ENG_API_BASE_URLy los timeouts pueden ir en unConfigMap.- Maneja
SIGTERMcerrando el servidor HTTP con gracia (drenaje de 10 s como máximo).
9. Cómo añadir un servicio o tool nuevo
El patrón está pensado para que añadir un servicio no toque nada existente. Ejemplo con un hipotético Grafana:
1. Añade sus rutas en src/client/routes.ts:
grafana: {
listDashboards: (): string => "/grafana/dashboards",
getDashboard: (uid: string): string => `/grafana/dashboards/${seg(uid)}`,
},
2. Crea src/tools/grafana.ts siguiendo el mismo molde que los demás:
export function registerGrafanaTools(server: McpServer, deps: ToolDeps): void {
registerEngTool(server, deps, {
name: "grafana_list_dashboards", // prefijo de servicio + acción
title: "Grafana: listar dashboards",
description: "Qué hace y cuándo usarlo.",
inputSchema: { query: z.string().optional().describe('Texto a buscar. Ejemplo: "latencia checkout".'),
...paginationShape },
annotations: readOnlyAnnotations("Grafana: listar dashboards"),
describeOperation: (args) => `listar dashboards de Grafana`, // encaja tras "al …"
execute: (args, { client, context }) =>
client.get(engApiRoutes.grafana.listDashboards(), {
query: { query: args.query, ...paginationQuery(args) },
context,
}),
});
}
3. Regístralo en TOOL_REGISTRARS de src/server.ts:
const TOOL_REGISTRARS = [ …, registerGrafanaTools ];
Eso es todo. registerEngTool ya te da gratis: validación Zod, formateo de la respuesta, truncado de payloads enormes, captura de errores y traducción a mensajes accionables, y logging con requestId.
Reglas de estilo para tools nuevos:
- Nombre
servicio_accion_objeto, en minúsculas. - Cada campo del schema con
.describe()y un ejemplo concreto — es lo único que el modelo lee para decidir cómo llamarlo. - Anotaciones honestas: si escribe,
readOnlyHint: false; si puede borrar algo,destructiveHint: true. - Nada de defaults peligrosos en operaciones destructivas: exige identificadores exactos.
- Paginación (
...paginationShape+paginationQuery(args)) en todo lo que devuelva listas. - Nunca construyas URLs a mano en
tools/: siempre a través deengApiRoutes.
10. Manejo de errores
Ningún tool devuelve un "Error 500" pelado. Cada error incluye qué falló, qué revisar y un requestId para cruzarlo con los logs de eng-api. Ejemplo real:
No existe el recurso al obtener el estado de la aplicación boom (404). Verifica los identificadores
exactos (workspace/repo, key de issue, id de página, nombre de app) — distinguen mayúsculas. Si los
identificadores son correctos, la ruta de eng-api puede haber cambiado (src/client/routes.ts).
[requestId=8a4bf9e6-…, intentos=1, upstream=GET /argocd/applications/boom]
Respuesta de eng-api: {"error":"application not found"}
| Situación | Qué hace el MCP Server |
|---|---|
| Timeout / error de red | Reintenta con backoff exponencial + jitter (ENG_API_MAX_RETRIES), luego explica que revises ENG_API_BASE_URL / la latencia |
429, 5xx |
Reintenta (respeta Retry-After si viene) y, si persiste, apunta a los logs de eng-api |
400 / 422 |
No reintenta: los parámetros son inválidos |
401 / 403 de eng-api |
Aclara que no es tu API key del MCP, sino las credenciales/permisos de eng-api |
404 |
Sugiere verificar identificadores exactos y las rutas de routes.ts |
409 |
Conflicto de estado (p. ej. un sync de ArgoCD ya en curso): consulta el estado y reintenta luego |
| Respuesta no-JSON | Suele ser un proxy devolviendo HTML: la ruta probablemente no existe |
| Payload gigante | Se trunca a 120 000 caracteres con un aviso para reducir pageSize o acotar filtros |
11. Estructura del proyecto
src/
├── index.ts # entrypoint: Express + Streamable HTTP (stateless), /healthz, /readyz
├── config.ts # lectura y validación de env vars, fail-fast
├── auth.ts # middleware de API key (timing-safe)
├── logger.ts # logs JSON de una línea, aptos para Cloud Logging
├── server.ts # createMcpServer(): registra todas las familias de tools
├── client/
│ ├── routes.ts # ÚNICO sitio con las rutas de eng-api
│ ├── errors.ts # EngApiError → mensajes accionables
│ └── engApiClient.ts # fetch + timeout + retry con backoff
└── tools/
├── shared.ts # registerEngTool(), paginación, formateo, errores
├── bitbucket.ts ├── jira.ts ├── confluence.ts └── argocd.ts
Decisiones de diseño:
- Streamable HTTP en modo stateless (
sessionIdGenerator: undefined,enableJsonResponse: true): se crea unMcpServer+ transport por petición. Sin estado compartido entre devs, sin sticky sessions, escala horizontalmente y las respuestas son JSON plano (más amables con ingress/proxies que SSE). - Solo
POST /mcp:GET/DELETEresponden405, porque en stateless no hay stream servidor→cliente ni sesión que cerrar. - Trazabilidad: cada petición lleva un
X-Request-Id(se respeta el del cliente si lo manda) y unX-Mcp-Devcon la etiqueta del dev, ambos propagados a eng-api.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.