Skip to main content
Glama

mcp-server

MCP Server 用 TypeScript 编写,将团队的工程工具——Bitbucket、Jira、Confluence 和 ArgoCD——暴露给支持 MCP 的编辑器(VS Code + Copilot、Claude Code 等)。

它是一个薄层封装:不与 Bitbucket/Jira/Confluence/ArgoCD 直接通信,也不保存这些服务的凭据。所有操作都转换为对内部后端 eng-api 的 HTTP 调用,该后端已处理连接和凭据。

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 的个人令牌。只需要这个 MCP Server 的 API 密钥,且可单独撤销。


1. 要求

  • Node.js ≥ 22

  • 能够访问 ENG_API_BASE_URL(eng-api 的 URL)

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

以监听模式启动,读取 .env

npm run build

将 TypeScript 编译到 dist/

npm run typecheck

仅类型检查,不输出

npm start

启动编译后的代码(使用环境变量;这是 Pod 中运行的命令)

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

✅

—

eng-api 的基础 URL,末尾无斜杠。必须为 http(s)://…

MCP_DEV_API_KEYS

✅

—

针对此 MCP Server 的有效开发者 API 密钥(参见 §4)

ENG_API_TIMEOUT_MS

—

10000

每次调用 eng-api 的超时时间(1000–120000)

ENG_API_MAX_RETRIES

—

2

遇到 5xx/429/超时时的额外重试次数(0–5)

PORT

—

3000

MCP Server 的 HTTP 端口

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 Server 自身 的,与 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 Server 的日志中,并传播到 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。由于每个开发者都有自己的密钥,不会影响其他人。在生产环境中,将值保存在 Kubernetes 的 Secret 中,切勿放在 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(或用户级别的 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 模式下打开聊天,你会看到 eng 服务器下的 11 个工具。

对于 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

在 Inspector 的 UI 中:

  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 或 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'

调用一个工具:

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/ 和运行时依赖,以 node 用户身份运行(非 root),并包含一个 HEALTHCHECK,使用 Node 自身(无需 curl/wget)访问 /healthz。

对于 Kubernetes(清单不在本仓库中):

  • 服务器是无状态的:不在内存中保存会话,因此可以扩展到 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. 在 src/server.ts 的 TOOL_REGISTRARS 中注册它:

const TOOL_REGISTRARS = [ …, registerGrafanaTools ];

仅此而已。registerEngTool 已经免费为你提供了:Zod 验证、响应格式化、大负载截断、错误捕获并转换为可操作消息、以及带有 requestId 的日志记录。

新工具的样式规则:

  • 名称格式:服务_动作_对象,小写。

  • Schema 中的每个字段必须使用 .describe() 并提供具体示例——这是模型决定如何调用它的唯一依据。

  • 诚实的注释:如果是写入操作,readOnlyHint: false;如果可能删除内容,destructiveHint: true。

  • 破坏性操作中不要使用危险的默认值:要求精确的标识符。

  • 所有返回列表的工具都要支持分页(...paginationShape + paginationQuery(args))。

  • 切勿在 tools/ 中手动构建 URL:始终通过 engApiRoutes。

10. 错误处理

任何工具都不会返回赤裸裸的“Error 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 服务器的行为

Timeout / 网络错误

使用指数退避 + 抖动重试(ENG_API_MAX_RETRIES),否则解释请检查 ENG_API_BASE_URL / 延迟

429, 5xx

重试(如果存在则遵循 Retry-After),若持续失败,则指向 eng-api 日志

400 / 422

不重试:参数无效

401 / 403 来自 eng-api

澄清这不是你的 MCP API 密钥,而是 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 无状态模式(sessionIdGenerator: undefined, enableJsonResponse: true):每个请求创建一个 McpServer + transport。开发者之间无共享状态,无粘性会话,水平扩展,响应为纯 JSON(比 SSE 对 ingress/proxy 更友好)。

  • 仅 POST /mcp:GET/DELETE 返回 405,因为在无状态下没有服务器→客户端流或需要关闭的会话。

  • 可追溯性:每个请求都带有 X-Request-Id(如果客户端发送则保留)和 X-Mcp-Dev(带有开发者标签),两者都会传播到 eng-api。

Related MCP Connectors

Related MCP Servers