mcp-server

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.

Category
Visit Server

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:

  1. Transport Type: Streamable HTTP
  2. URL: http://localhost:3000/mcp
  3. En Authentication, pon Header Name Authorization y el Bearer Token con tu API key
  4. Connect → pestaña ToolsList 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: livenessProbeGET /healthz, readinessProbeGET /readyz (ambos sin auth).
  • MCP_DEV_API_KEYS va en un Secret; ENG_API_BASE_URL y los timeouts pueden ir en un ConfigMap.
  • Maneja SIGTERM cerrando 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 de engApiRoutes.

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 un McpServer + 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/DELETE responden 405, 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 un X-Mcp-Dev con la etiqueta del dev, ambos propagados a eng-api.

Recommended Servers

playwright-mcp

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.

Official
Featured
TypeScript
Magic Component Platform (MCP)

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.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

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.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

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.

Official
Featured
TypeScript
Kagi MCP Server

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.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

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.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured