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-deployerthroughdashboard, in deployment order), each with a short description of its purpose.mcp-serveranddocumentationare 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 viaorgorusername, plus optionaloptionsholding 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 oforg/user).devops_server_repo_info: fetch metadata for one repo (repo,owner).devops_server_create_repo: create a repo namedname, owned by exactly one oforg/user, plus optionaloptionsholding 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 atkey. Returns{}if the secret or its bucket doesn't exist.vault_write: create or replace the secret atkeywithvalue: 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 underkey. A bare bucket name lists that bucket's root, and an emptykeylists every KV version 2 mount.vault_delete: permanently delete the secret atkeyand 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-caand<cs_project_code>/devops-server/hosts/<host>/apps/scm/generated).mcp_transport:streamable-httporsse.mcp_server_host: bind host (127.0.0.1only 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.envfile 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-httpserves athttp://<host>:<port>/mcp.sseserves athttp://<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-commitruns the samegitleaksscan locally before each commit, givengitleaksonPATH. Enable it once per clone:chmod +x .git-hooks/*git config --local core.hooksPath .git-hooks