Skip to main content
Glama

mcp-server

MCP Server in TypeScript that exposes the team's engineering tools —Bitbucket, Jira, Confluence and ArgoCD— to editors with MCP support (VS Code + Copilot, Claude Code, etc.).

It is a thin wrapper: it does not talk to Bitbucket/Jira/Confluence/ArgoCD, nor does it store credentials for those services. It translates everything into HTTP calls against the internal backend eng-api, which already has connections and credentials resolved.

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

Advantage: no dev needs personal tokens for Bitbucket/Jira/Confluence/ArgoCD. Only an API key for this MCP Server, individually revocable.


1. Requirements

  • Node.js ≥ 22

  • Network access to ENG_API_BASE_URL (the eng-api URL)

Related MCP server: Work Integrations MCP

2. Run locally

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

Other commands:

Command

What it does

npm run dev

Starts in watch mode reading .env

npm run build

Compiles TypeScript to dist/

npm run typecheck

Type-check without emitting

npm start

Starts the compiled version (uses environment variables; this is what runs in the pod)

npm run start:local

Starts the compiled version reading .env

Quick check that it's alive:

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

3. Configuration (.env)

All variables are read from process.env. If a required one is missing or has an invalid value, the process does not start and explains exactly what to fix.

Variable

Required

Default

Description

ENG_API_BASE_URL

Base URL of eng-api, without trailing slash. Must be http(s)://…

MCP_DEV_API_KEYS

Valid API keys for devs against this MCP Server (see §4)

ENG_API_TIMEOUT_MS

10000

Timeout per call to eng-api (1000–120000)

ENG_API_MAX_RETRIES

2

Additional retries on 5xx/429/timeout (0–5)

PORT

3000

HTTP port of the MCP Server

LOG_LEVEL

info

debug | info | warn | error

Example of a failed startup (on purpose):

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. Authentication: one API key per dev

This auth layer is specific to the MCP Server and independent of what eng-api uses towards the final services.

Generate keys

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

Configure them

MCP_DEV_API_KEYS accepts four formats (minimum 24 characters per key, no duplicates):

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

Use the tagged format: the tag appears in the MCP Server logs and is propagated to eng-api in the X-Mcp-Dev header, so you can audit who triggered each operation (e.g., an argocd_sync_app) without exposing the key.

Use them

The MCP client must send in each request:

Authorization: Bearer <API_KEY>

(or, alternatively, x-api-key: <API_KEY>). The comparison is timing-safe over SHA-256 digests.

Situation

Response

Without key

401 + message indicating which header is missing

Invalid/revoked key

403 + message indicating what to check

/healthz, /readyz

No auth (for Kubernetes probes)

Revoke someone = remove their key from MCP_DEV_API_KEYS and restart the Deployment. Since each dev has their own, it does not affect others. In production, store the value in a Kubernetes Secret, never in a ConfigMap.

5. Tool catalog

Names have a service prefix and are action-oriented. All support pagination where applicable (page, pageSize from 1 to 100, default 25).

Bitbucket (read-only)

Tool

Arguments

eng-api endpoint

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

Arguments

eng-api endpoint

jira_search_issues

jql? or simple filters (projectKey?, status?, assignee?, labels?), fields?, page?, pageSize?

POST /jira/issues/search

jira_get_issue

issueKey (format PLAT-4821), fields?, includeComments?

GET /jira/issues/{key}

jira_create_issue ✍️

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

POST /jira/issues

Confluence (read-only)

Tool

Arguments

eng-api endpoint

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

Arguments

eng-api endpoint

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 (exact, no default), revision?, prune?, dryRun?, resources?

POST /argocd/applications/{name}/sync

Annotations (hints for the MCP client)

Tool

readOnlyHint

destructiveHint

idempotentHint

openWorldHint

All read-only ones

jira_create_issue ✍️

argocd_sync_app ⚠️

argocd_sync_app requires the exact app name (no wildcards or default values) and prune/dryRun default to false unless explicitly requested.

All eng-api routes live in src/client/routes.ts. If eng-api changes a path, only that file needs to be touched.

6. Configure VS Code (each dev, with their own key)

Create .vscode/mcp.json in your workspace (or the user mcp.json, if you want it in all projects):

{
  "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 will ask for the key the first time and store it encrypted; it is never committed. Then, open the chat in Agent mode and you will see the 11 tools under the eng server.

For Claude Code (CLI), the equivalent is:

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

Locally, replace the URL with http://localhost:3000/mcp.

7. Test it with MCP Inspector

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

In the Inspector UI:

  1. Transport Type: Streamable HTTP

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

  3. In Authentication, set Header Name to Authorization and the Bearer Token to your API key

  4. ConnectTools tab → List Tools → try any

It can also be tested directly with curl (useful in CI or from a 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'

Call a 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

Multi-stage image on node:22-alpine: the final stage only contains dist/ + production dependencies, runs as user node (no root) and includes a HEALTHCHECK that hits /healthz with Node itself (no curl/wget).

For Kubernetes (manifests are not in this repo):

  • The server is stateless: it does not store sessions in memory, so it scales to N replicas without sticky sessions.

  • Probes: livenessProbeGET /healthz, readinessProbeGET /readyz (both without auth).

  • MCP_DEV_API_KEYS goes in a Secret; ENG_API_BASE_URL and timeouts can go in a ConfigMap.

  • Handles SIGTERM by gracefully shutting down the HTTP server (drain of 10 s max).

9. How to add a new service or tool

The pattern is designed so that adding a service does not touch anything existing. Example with a hypothetical Grafana:

1. Add its routes in src/client/routes.ts:

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

2. Create src/tools/grafana.ts following the same template as the others:

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. Register it in TOOL_REGISTRARS in src/server.ts:

const TOOL_REGISTRARS = [ …, registerGrafanaTools ];

That's it. registerEngTool already gives you for free: Zod validation, response formatting, truncation of huge payloads, error capture and translation into actionable messages, and logging with requestId.

Style rules for new tools:

  • Name service_action_object, in lowercase.

  • Each schema field with .describe() and a concrete example — that's the only thing the model reads to decide how to call it.

  • Honest annotations: if it writes, readOnlyHint: false; if it can delete something, destructiveHint: true.

  • No dangerous defaults in destructive operations: require exact identifiers.

  • Pagination (...paginationShape + paginationQuery(args)) on everything that returns lists.

  • Never build URLs by hand in tools/: always through engApiRoutes.

10. Error handling

No tool returns a bare "Error 500". Each error includes what failed, what to check and a requestId to cross-reference with eng-api logs. Real example:

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

Situation

What the MCP Server does

Timeout / network error

Retries with exponential backoff + jitter (ENG_API_MAX_RETRIES), then explains to check ENG_API_BASE_URL / latency

429, 5xx

Retries (respects Retry-After if present) and, if it persists, points to eng-api logs

400 / 422

Does not retry: the parameters are invalid

401 / 403 from eng-api

Clarifies that it is not your MCP API key, but the eng-api credentials/permissions

404

Suggests verifying exact identifiers and the routes in routes.ts

409

State conflict (e.g., an ArgoCD sync already in progress): check the state and retry later

Non-JSON response

Usually a proxy returning HTML: the route probably does not exist

Giant payload

Truncated to 120,000 characters with a notice to reduce pageSize or narrow filters

11. Project structure

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

Design decisions:

  • Streamable HTTP in stateless mode (sessionIdGenerator: undefined, enableJsonResponse: true): a McpServer + transport is created per request. No shared state between devs, no sticky sessions, scales horizontally, and responses are plain JSON (kinder to ingress/proxies than SSE).

  • Only POST /mcp: GET/DELETE respond with 405, because in stateless there is no server→client stream or session to close.

  • Traceability: each request carries an X-Request-Id (respects the client's if sent) and an X-Mcp-Dev with the dev's label, both propagated to eng-api.

A
license - permissive license
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

View all related MCP servers

Related MCP Connectors

  • A MCP server built for developers enabling Git based project management with project and personal…

  • MCP server for interacting with the Supabase platform

  • An MCP server that let you interact with Cycloid.io Internal Development Portal and Platform

View all MCP Connectors

Latest Blog Posts

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/ElJijuna/mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server