Skip to main content
Glama

mcp-server

MCP-сервер на TypeScript, который предоставляет инженерные инструменты команды — Bitbucket, Jira, Confluence и ArgoCD — редакторам с поддержкой MCP (VS Code + Copilot, Claude Code и т.д.).

Это тонкая обёртка: он не общается напрямую с Bitbucket/Jira/Confluence/ArgoCD и не хранит учётные данные этих сервисов. Всё транслируется в HTTP-вызовы внутреннего бэкенда eng-api, который уже имеет настроенные соединения и учётные данные.

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

Преимущество: ни одному разработчику не нужны личные токены Bitbucket/Jira/Confluence/ArgoCD. Только API-ключ этого MCP-сервера, отзываемый индивидуально.


1. Требования

  • Node.js ≥ 22

  • Сетевой доступ к ENG_API_BASE_URL (URL eng-api)

Related MCP server: Work Integrations MCP

2. Запуск локально

npm ci
cp .env.example .env      # y rellena los valores (ver sección 3)
npm run dev               # hot-reload, lee .env automáticamente

Другие команды:

Команда

Что делает

npm run dev

Запуск в режиме watch с чтением .env

npm run build

Компиляция TypeScript в dist/

npm run typecheck

Проверка типов без сборки

npm start

Запуск скомпилированного кода (использует переменные окружения; то, что работает в поде)

npm run start:local

Запуск скомпилированного кода с чтением .env

Быстрая проверка, что сервер работает:

curl http://localhost:3000/healthz
# {"status":"ok","server":"mcp-server","version":"0.1.0"}

3. Конфигурация (.env)

Все переменные читаются из process.env. Если обязательная переменная отсутствует или имеет недопустимое значение, процесс не запускается и объясняет, что именно нужно исправить.

Переменная

Обязательная

По умолчанию

Описание

ENG_API_BASE_URL

✅

—

Базовый URL eng-api, без косой черты в конце. Должен быть http(s)://…

MCP_DEV_API_KEYS

✅

—

Допустимые API-ключи разработчиков для этого MCP-сервера (см. §4)

ENG_API_TIMEOUT_MS

—

10000

Тайм-аут на один вызов eng-api (1000–120000)

ENG_API_MAX_RETRIES

—

2

Дополнительные повторные попытки при 5xx/429/тайм-ауте (0–5)

PORT

—

3000

HTTP-порт MCP-сервера

LOG_LEVEL

—

info

debug | info | warn | error

Пример неудачного запуска (намеренно):

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. Аутентификация: один API-ключ на разработчика

Этот уровень аутентификации является собственностью MCP-сервера и независим от того, как eng-api аутентифицируется к конечным сервисам.

Генерация ключей

openssl rand -hex 32     # una por cada persona del equipo

Настройка ключей

MCP_DEV_API_KEYS принимает четыре формата (минимум 24 символа на ключ, без дубликатов):

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

Используйте тегированный формат: метка отображается в логах MCP-сервера и передаётся в eng-api в заголовке X-Mcp-Dev, что позволяет отследить, кто инициировал каждую операцию (например, argocd_sync_app), не раскрывая ключ.

Использование ключей

Клиент MCP должен отправлять в каждом запросе:

Authorization: Bearer <API_KEY>

(или, как альтернатива, x-api-key: <API_KEY>). Сравнение выполняется с защитой от временных атак на основе SHA-256-дайджестов.

Ситуация

Ответ

Без ключа

401 + сообщение, указывающее, какой заголовок отсутствует

Неверный/отозванный ключ

403 + сообщение, указывающее, что проверить

/healthz, /readyz

Без аутентификации (для проб Kubernetes)

Отозвать доступ у пользователя = удалить его ключ из MCP_DEV_API_KEYS и перезапустить Deployment. Поскольку у каждого разработчика свой ключ, это не влияет на остальных. В production храните значение в Secret Kubernetes, никогда в ConfigMap.

5. Каталог инструментов

Названия содержат префикс сервиса и ориентированы на действие. Все поддерживают пагинацию, где применимо (page, pageSize от 1 до 100, по умолчанию 25).

Bitbucket (только чтение)

Инструмент

Аргументы

Конечная точка 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

Инструмент

Аргументы

Конечная точка eng-api

jira_search_issues

jql? или простые фильтры (projectKey?, status?, assignee?, labels?), fields?, page?, pageSize?

POST /jira/issues/search

jira_get_issue

issueKey (формат PLAT-4821), fields?, includeComments?

GET /jira/issues/{key}

jira_create_issue ✍️

projectKey, issueType, summary, description?, assignee?, labels?, priority?, parentKey?, extraFields?

POST /jira/issues

Confluence (только чтение)

Инструмент

Аргументы

Конечная точка 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

Инструмент

Аргументы

Конечная точка 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 (точное, без значения по умолчанию), revision?, prune?, dryRun?, resources?

POST /argocd/applications/{name}/sync

Аннотации (подсказки для клиента MCP)

Инструмент

readOnlyHint

destructiveHint

idempotentHint

openWorldHint

Все инструменты чтения

✅

❌

✅

✅

jira_create_issue ✍️

❌

❌

❌

✅

argocd_sync_app ⚠️

❌

✅

❌

✅

argocd_sync_app требует точное имя приложения (без подстановочных знаков или значений по умолчанию), а prune/dryRun по умолчанию false, если их не запросить явно.

Все пути eng-api находятся в src/client/routes.ts. Если eng-api меняет какой-то путь, изменяется только этот файл.

6. Настройка VS Code (каждый разработчик со своим ключом)

Создайте .vscode/mcp.json в вашем workspace (или пользовательский mcp.json, если хотите использовать во всех проектах):

{
  "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 запросит ключ при первом запуске и сохранит его зашифрованным; не коммитится никогда. После этого откройте чат в режиме Agent, и вы увидите 11 инструментов под сервером eng.

Для Claude Code (CLI) эквивалент:

claude mcp add --transport http eng https://<host-del-mcp-server>/mcp \
  --header "Authorization: Bearer <TU_API_KEY>"

Локально замените URL на http://localhost:3000/mcp.

7. Тестирование с MCP Inspector

npm run build && npm run start:local     # en una terminal
npx @modelcontextprotocol/inspector      # en otra

В UI Inspector:

  1. Transport Type: Streamable HTTP

  2. URL: http://localhost:3000/mcp

  3. В Authentication укажите Header Name Authorization и Bearer Token с вашим API-ключом

  4. Connect → вкладка Tools → List Tools → протестируйте любой

Также можно протестировать напрямую через curl (полезно в CI или из пода):

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'

Вызов инструмента:

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

Многоступенчатый образ на основе node:22-alpine: финальный содержит только dist/ + зависимости production, запускается от пользователя node (без root) и включает HEALTHCHECK, который обращается к /healthz через сам Node (без curl/wget).

Для Kubernetes (манифесты не в этом репозитории):

  • Сервер stateless: не хранит сессии в памяти, поэтому масштабируется до N реплик без привязанных сессий.

  • Пробы: livenessProbe → GET /healthz, readinessProbe → GET /readyz (оба без аутентификации).

  • MCP_DEV_API_KEYS хранится в Secret; ENG_API_BASE_URL и таймауты можно хранить в ConfigMap.

  • Обрабатывает SIGTERM, корректно закрывая HTTP-сервер (дренаж до 10 секунд).

9. Как добавить новый сервис или инструмент

Паттерн разработан так, чтобы добавление сервиса не затрагивало существующий код. Пример с гипотетическим Grafana:

1. Добавьте его маршруты в src/client/routes.ts:

grafana: {
  listDashboards: (): string => "/grafana/dashboards",
  getDashboard: (uid: string): string => `/grafana/dashboards/${seg(uid)}`,
},

2. Создайте src/tools/grafana.ts, следуя тому же шаблону, что и остальные:

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. Зарегистрируйте его в TOOL_REGISTRARS в src/server.ts:

const TOOL_REGISTRARS = [ …, registerGrafanaTools ];

Всё. registerEngTool уже предоставляет бесплатно: валидацию Zod, форматирование ответа, обрезку огромных полезных нагрузок, перехват ошибок и перевод в действенные сообщения, а также логирование с requestId.

Правила стиля для новых инструментов:

  • Имя сервис_действие_объект, в нижнем регистре.

  • Каждое поле схемы с .describe() и конкретным примером — это единственное, что модель читает, чтобы решить, как его вызвать.

  • Честные аннотации: если пишет, readOnlyHint: false; если может что-то удалить, destructiveHint: true.

  • Никаких опасных значений по умолчанию в деструктивных операциях: требуйте точные идентификаторы.

  • Пагинация (...paginationShape + paginationQuery(args)) во всём, что возвращает списки.

  • Никогда не конструируйте URL вручную в tools/: всегда через engApiRoutes.

10. Обработка ошибок

Ни один инструмент не возвращает голую ошибку "500". Каждая ошибка включает что не удалось, что проверить и requestId для сопоставления с логами eng-api. Пример:

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"}

Ситуация

Что делает MCP Server

Тайм-аут / ошибка сети

Повторяет с экспоненциальной задержкой + джиттер (ENG_API_MAX_RETRIES), затем объясняет, что нужно проверить ENG_API_BASE_URL / задержку

429, 5xx

Повторяет (учитывает Retry-After, если есть) и, если не проходит, указывает на логи eng-api

400 / 422

Не повторяет: параметры недействительны

401 / 403 от eng-api

Уточняет, что это не ваш API-ключ MCP, а учётные данные/разрешения eng-api

404

Предлагает проверить точные идентификаторы и пути в routes.ts

409

Конфликт состояния (например, синхронизация ArgoCD уже выполняется): проверьте состояние и повторите позже

Ответ не-JSON

Обычно это прокси, возвращающий HTML: вероятно, путь не существует

Огромная полезная нагрузка

Обрезается до 120 000 символов с предупреждением уменьшить pageSize или сузить фильтры

11. Структура проекта

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

Проектные решения:

  • Streamable HTTP в stateless-режиме (sessionIdGenerator: undefined, enableJsonResponse: true): создаётся McpServer + transport на каждый запрос. Нет общего состояния между разработчиками, нет sticky sessions, горизонтальное масштабирование, ответы — простой JSON (более дружелюбны к ingress/прокси, чем SSE).

  • Только POST /mcp: GET/DELETE отвечают 405, потому что в stateless нет потока сервер→клиент или сессии для закрытия.

  • Отслеживаемость: каждый запрос содержит X-Request-Id (уважается, если клиент его отправляет) и X-Mcp-Dev с меткой разработчика, оба передаются в eng-api.

Related MCP Connectors

Related MCP Servers