Skip to main content

MCP Server

Local MCP server, installed as the mcp-server package. Exposes the platform's project knowledge, devops-server management, and Vault secret management as MCP tools for Claude Desktop / Claude Code.

Capabilities​

Project knowledge

  • list_platform_repos: list the platform's deployed repos (vault-deployer through dashboard, in deployment order), each with a short description of its purpose. mcp-server and documentation are not included.
  • get_deployment_order: return the platform's fixed repo deployment order.

Devops-server: organizations and repositories (repositories scoped to either an org or a user).

  • devops_server_list_orgs: list organizations on the devops-server (first 50 only).
  • devops_server_org_info: fetch metadata for one organization (org).
  • devops_server_create_org: create an organization (named via org or username, plus optional options holding extra Gitea org creation fields).
  • devops_server_edit_org: edit an organization's properties (org, options).
  • devops_server_delete_org: permanently delete an organization and all its repos (org).
  • devops_server_list_repos: list repos owned by an org or a user (first 50 only; exactly one of org/user).
  • devops_server_repo_info: fetch metadata for one repo (repo, owner).
  • devops_server_create_repo: create a repo named name, owned by exactly one of org/user, plus optional options holding extra Gitea repo creation fields. A user-owned repo is created through the admin API.
  • devops_server_edit_repo: edit a repo's properties (repo, owner, options).
  • devops_server_delete_repo: permanently delete a repo (repo, owner).

Vault

Every Vault tool takes a key string in <bucket>/<path> form, split on the first / (a leading or trailing / is stripped).

  • vault_read: read the secret at key. Returns {} if the secret or its bucket doesn't exist.
  • vault_write: create or replace the secret at key with value: a JSON object, or a string holding either a JSON object or the path to a JSON file on the server's own filesystem. If the bucket has no KV version 2 mount yet, one is enabled first.
  • vault_list: list the child key names under key. A bare bucket name lists that bucket's root, and an empty key lists every KV version 2 mount.
  • vault_delete: permanently delete the secret at key and its full history, then every secret nested under it. A bare bucket name disables that bucket's whole KV version 2 mount, removing every secret in it.

Setup​

cd mcp-server
uv sync

Configuration​

The server's settings (project code, transport, bind host/port, the bearer auth key, and Vault connection details) come from a mandatory JSON config file, validated against a pydantic model at startup:

{
"cs_project_code": "<project code that prefixes the platform's Vault paths>",
"mcp_transport": "streamable-http",
"mcp_server_host": "0.0.0.0",
"mcp_server_port": 8000,
"mcp_server_auth_key": "<bearer token clients must present>",
"vault_api_endpoint": "<Vault API base URL>",
"vault_api_key": "<Vault API token>",
"vault_ca_cert_base64": "<base64-encoded PEM of the CA that signed Vault's cert>",
"vault_client_cert_base64": "<base64-encoded PEM of this client's mTLS certificate>",
"vault_client_key_base64": "<base64-encoded PEM of this client's mTLS private key>"
}
  • cs_project_code: project code that prefixes the Vault paths the server reads at startup (<cs_project_code>/certificate-authority/root-ca and <cs_project_code>/devops-server/hosts/<host>/apps/scm/generated).
  • mcp_transport: streamable-http or sse.
  • mcp_server_host: bind host (127.0.0.1 only accepts connections from the same machine).
  • mcp_server_port: bind port.
  • mcp_server_auth_key: bearer token required from every client.
  • vault_api_endpoint: base URL of the Vault secrets store.
  • vault_api_key: token used to authenticate requests to Vault.
  • vault_ca_cert_base64, vault_client_cert_base64, vault_client_key_base64: base64-encoded PEM content, decoded to temp files at runtime to establish mutual TLS with Vault.

Every field is mandatory; the server refuses to start if the config file is missing or any field is absent.

  • MCP_SERVER_CONFIG_JSON: path to that config file, resolved relative to the current working directory if relative, and defaulting to .mcp-server-config.json; if no file exists at that path, the value itself is parsed directly as a JSON string instead. A .env file in this directory, if present, is loaded before this is read, so it can be set there too.

Running​

The server always runs as its own long-lived HTTP process, never spawned by a client. Start it from this directory, with .mcp-server-config.json present (or MCP_SERVER_CONFIG_JSON pointed at it):

uv --offline run --no-sync --no-progress mcp-server

The process keeps running until stopped. Run it in a terminal, nohup/tmux, or under a service manager.

Logging is configured once at startup: console output plus two rotating files in the current working directory, application.log (INFO and above) and error.log (ERROR and above), each capped at 10 MB with 5 backups.

Docker​

Build the image from this directory:

docker build -t mcp-server .

Run it, mounting a config file (see "Configuration" above; use mcp_server_host: "0.0.0.0" so the server stays reachable from outside the container) at the image's working directory, and publishing the bound port:

docker run --rm -v "$(pwd)/.mcp-server-config.json:/app/.mcp-server-config.json:ro" -p 8000:8000 mcp-server

Mount it elsewhere with -e MCP_SERVER_CONFIG_JSON=<path> and a matching -v if /app/.mcp-server-config.json doesn't fit.

The server is reachable at http://<host>:<mcp_server_port>/mcp (or /sse for the sse transport) as soon as the container is up.

The image declares a HEALTHCHECK against the unauthenticated GET /health endpoint, checked via MCP_HEALTHCHECK_ENDPOINT (defaults to http://127.0.0.1:8000, matching the default mcp_server_port). Override it with -e MCP_HEALTHCHECK_ENDPOINT=<url> if mcp_server_port in the mounted config differs from 8000.

Registering with an MCP client​

Point the client at the running server's URL instead of a spawn command:

  • streamable-http serves at http://<host>:<port>/mcp.
  • sse serves at http://<host>:<port>/sse (message endpoint /messages/).

In the client's own MCP config (e.g. Claude Code/Desktop's mcpServers), add an entry with a type/url instead of command/args (the platform controller registers this as "controller-mcp-server" in .mcp.json).

streamable-http:

{
"mcpServers": {
"controller-mcp-server": {
"type": "http",
"url": "http://<host>:<port>/mcp",
"headers": {
"Authorization": "Bearer <mcp_server_auth_key>"
}
}
}
}

sse (older transport, deprecated in most clients; prefer streamable-http/"http" above):

{
"mcpServers": {
"controller-mcp-server": {
"type": "sse",
"url": "http://<host>:<port>/sse",
"headers": {
"Authorization": "Bearer <mcp_server_auth_key>"
}
}
}
}

Bind it to a trusted network, or put it behind your own reverse proxy or an SSH tunnel.

CI​

  • .gitea/workflows/lint.yml: linting, formatting, and type-checking.

  • .gitea/workflows/gitleaks.yml: secret scanning on push, pull request, and a daily schedule.

  • .git-hooks/pre-commit runs the same gitleaks scan locally before each commit, given gitleaks on PATH. Enable it once per clone:

    chmod +x .git-hooks/*
    git config --local core.hooksPath .git-hooks