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.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。
mcp-server-qdrant
这个仓库展示了如何为向量搜索引擎 Qdrant 创建一个 MCP (Managed Control Plane) 服务器的示例。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。