Drone CI/CD MCP Server
MCP server for Drone CI/CD: 45 tools for builds, repos, secrets, users and templates
Que pouvez-vous faire avec Drone CI/CD MCP Server ?
- 45 tools covering repositories, builds, cron jobs, secrets, users and templates
- Read-only by default: the 27 write and destructive tools are only registered with
--enable-write-tools - Streamable HTTP transport with mandatory bearer authentication, DNS rebinding protection and cross-origin protection
- Validated inputs: repository identifiers, names, branches and build numbers are checked before they reach the Drone API
- Resource access: build details via
drone://builds/{owner}/{repo}/{build}
Installation
Ajoutez ceci à la configuration MCP de votre client (Claude Desktop : claude_desktop_config.json ; Cursor : .cursor/mcp.json ; VS Code et les autres utilisent la même forme « mcpServers »).
ghcr.io/yusiwen/drone-mcp-server:2.0.0claude mcp add drone-ci-cd-mcp-server -- docker run -i --rm ghcr.io/yusiwen/drone-mcp-server:2.0.0
{
"mcpServers": {
"drone-ci-cd-mcp-server": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"ghcr.io/yusiwen/drone-mcp-server:2.0.0"
]
}
}
}
Documentation
Tirée du README du projet sur GitHub, actualisée chaque semaine. Les liens ouvrent GitHub.
Sommaire
Drone MCP Server
A Model Context Protocol (MCP) server for interacting with Drone CI/CD. This server provides tools and resources to query build information, repositories, and more from your Drone instance.
Features
- 45 tools covering repositories, builds, cron jobs, secrets, users and templates
- Read-only by default: the 27 write and destructive tools are only registered with
--enable-write-tools - Streamable HTTP transport with mandatory bearer authentication, DNS rebinding protection and cross-origin protection
- Validated inputs: repository identifiers, names, branches and build numbers are checked before they reach the Drone API
- Resource access: build details via
drone://builds/{owner}/{repo}/{build}
Installation
go mod tidy
go build -o drone-mcp-server .
Configuration
Environment variables
| Variable | Required | Description |
|---|---|---|
DRONE_SERVER | yes | Base URL of the Drone instance, e.g. https://drone.example.com |
DRONE_TOKEN | yes | Drone personal access token, sent as Authorization: Bearer <token> on every Drone API call |
MCP_AUTH_TOKEN | HTTP mode | Bearer token that clients must present. HTTP mode refuses to start without it |
MCP_AUTH_TOKEN_FILE | no | File containing the bearer token (Docker/Kubernetes secrets). Takes precedence over MCP_AUTH_TOKEN |
MCP_ENABLE_WRITE_TOOLS | no | true registers the write and destructive tools, equivalent to --enable-write-tools |
export DRONE_SERVER=https://drone.example.com
export DRONE_TOKEN=your_drone_token
export MCP_AUTH_TOKEN="$(openssl rand -base64 32)" # HTTP mode only
Grant the smallest Drone permission set that covers the tools you expose. The read-only tool set only needs read access to repositories and builds, while write tools such as delete_user, create_org_secret or chown_repo require administrator permissions.
Security
This server is a privileged proxy: whatever it can reach, anyone who can talk to it can reach too. The defaults reflect that.
- HTTP mode fails closed. Without
MCP_AUTH_TOKEN(orMCP_AUTH_TOKEN_FILE) the server refuses to start in HTTP mode.--insecure-allow-anonymousoverrides this and logs a warning. - Read-only by default. Only the 18 read-only tools are registered unless write access is requested explicitly. Every tool is annotated with the MCP hints
readOnlyHint,destructiveHintandidempotentHintso clients can gate writes themselves. - Streamable HTTP transport. The legacy SSE transport was removed from the MCP specification and shipped without DNS rebinding or cross-origin protection. This server uses the Go SDK's
StreamableHTTPHandler, which rejects requests that arrive on a loopback address with a non-loopbackHostheader and rejects cross-site browser requests. - Optional allowlists.
--allowed-hostsand--allowed-originsadd explicitHostandOriginallowlists on top of the transport defaults. - Bearer token handling. Tokens are compared in constant time over SHA-256 digests, and repeated failures from one address are throttled (10 per minute, then
429). - Input validation. Repository identifiers, secret and template names, branches and build numbers are validated before reaching the Drone API client, because drone-go assembles request paths by string formatting.
- Log hygiene.
X-Forwarded-For/X-Real-IPare ignored unless--trust-proxyis set, and control characters are stripped from logged values so request data cannot forge log lines. - Bounded requests. Request bodies are capped (1 MiB by default), idle sessions expire, and header read timeouts plus silent-connection timeouts limit slow-client abuse.
Deployment checklist
- Terminate TLS in front of the server (the binary speaks plain HTTP) and never send the bearer token over an untrusted network.
- Keep the loopback default binding and put a reverse proxy in front, or bind to an interface only your MCP clients can reach.
- Generate a high-entropy
MCP_AUTH_TOKEN(32+ random bytes) and inject it as a platform secret rather than on the command line. - Leave write tools disabled unless the workflow needs them.
- Set
--trust-proxyonly when a proxy you control rewritesX-Forwarded-For/X-Real-IP; otherwise the logged client address is attacker controlled. - Read SECURITY.md before exposing the server beyond localhost.
Usage
As an MCP server
Add the server to your MCP client configuration (e.g., Claude Desktop):
{
"mcpServers": {
"drone": {
"command": "/path/to/drone-mcp-server",
"env": {
"DRONE_SERVER": "https://drone.example.com",
"DRONE_TOKEN": "your_token"
}
}
}
}
Direct execution
You can run the server directly for testing:
# Stdio mode (default) - for local MCP clients
./drone-mcp-server
# Streamable HTTP mode - requires MCP_AUTH_TOKEN
export MCP_AUTH_TOKEN="$(openssl rand -base64 32)"
./drone-mcp-server --http --host localhost --port 8080
Transport Modes
- Stdio mode (default): Communicates via stdin/stdout using the MCP protocol. Suitable for local integration with MCP clients; no network exposure and no authentication required.
Streamable HTTP mode (
--http): A single endpoint that acceptsPOST(client to server),GET(server to client stream) andDELETE(session teardown).Upgrading from earlier releases: the old
--sseflag and the SSE transport are gone. Use--httpand a client that speaks Streamable HTTP.# Local only, read-only tools MCP_AUTH_TOKEN=... ./drone-mcp-server --http # Behind a TLS terminating proxy, write tools enabled MCP_AUTH_TOKEN=... ./drone-mcp-server --http --host 127.0.0.1 --port 8080 \ --enable-write-tools --allowed-hosts mcp.example.comClients connect to
http://localhost:8080/by default and must send the header:Authorization: Bearer <MCP_AUTH_TOKEN>Behind a reverse proxy: the transport rejects a public
Hostheader on a loopback connection with403as part of its DNS rebinding protection. Either keep the loopbackHostheader (proxy_set_header Host 127.0.0.1:8080;), or hand Host validation to the allowlist with a single flag:./drone-mcp-server --http --allowed-hosts mcp.example.comSetting
--allowed-hostsreplaces the loopback heuristic, so you do not need--localhost-protection=false. If you pass--localhost-protection=trueexplicitly, both checks are applied and the publicHostis rejected again.
Command line flags
| Flag | Default | Description |
|---|---|---|
--http | false | Use the Streamable HTTP transport |
--host / --port | localhost / 8080 | Listen address (HTTP mode only) |
--path | / | Path of the MCP endpoint (HTTP mode only) |
--enable-write-tools | false | Register the 27 write and destructive tools |
--insecure-allow-anonymous | false | Allow HTTP mode without MCP_AUTH_TOKEN |
--trust-proxy | false | Trust X-Forwarded-For / X-Real-IP from a reverse proxy |
--allowed-hosts | (empty) | Comma-separated Host allowlist; replaces the loopback heuristic |
--allowed-origins | (empty) | Comma-separated browser Origin allowlist |
--localhost-protection | true | Reject loopback connections carrying a non-loopback Host header; set explicitly together with --allowed-hosts to stack both checks |
--session-timeout | 30m | Idle MCP session timeout (HTTP mode only) |
--max-request-bytes | 1048576 | Maximum MCP request body size |
--version | Print version information and exit |
Available Tools
The server implements 45 tools. In the default read-only mode only the 18 read-only tools are registered; the remaining 27 are write or destructive tools and require --enable-write-tools (or MCP_ENABLE_WRITE_TOOLS=true).
Write and destructive tools: enable_repo, disable_repo, repair_repo, chown_repo, sync_repos, create_cron, delete_cron, execute_cron, create_secret, update_secret, delete_secret, create_org_secret, update_org_secret, delete_org_secret, create_user, update_user, delete_user, create_template, update_template, delete_template, restart_build, cancel_build, promote_build, rollback_build, approve_build, decline_build, create_build.
The destructive ones (deletions, disables, cancellations, promotions, rollbacks, ownership changes) are advertised with destructiveHint: true; the rest of the write tools with destructiveHint: false. All 18 read-only tools are advertised with readOnlyHint: true.
Repository Management
list_repos
Lists all repositories in your Drone instance.
get_repo
Get repository details.
Arguments:
owner(string): Repository ownerrepo(string): Repository name
enable_repo
Enable a repository.
Arguments:
owner(string): Repository ownerrepo(string): Repository name
disable_repo
Disable a repository.
Arguments:
owner(string): Repository ownerrepo(string): Repository name
repair_repo
Repair a repository.
Arguments:
owner(string): Repository ownerrepo(string): Repository name
chown_repo
Change repository ownership.
Arguments:
owner(string): Repository ownerrepo(string): Repository name
sync_repos
Synchronize repository list.
list_incomplete
List repositories with incomplete builds.
Build Management
list_builds
Lists builds for a specific repository.
Arguments:
owner(string): Repository ownerrepo(string): Repository name
get_build
Get detailed information about a specific build.
Arguments:
owner(string): Repository ownerrepo(string): Repository namebuild(number): Build number
get_build_last
Get the last build for a repository (optionally by branch).
Arguments:
owner(string): Repository ownerrepo(string): Repository namebranch(string, optional): Branch name
get_build_logs
Get logs for a specific build stage and step.
Arguments:
owner(string): Repository ownerrepo(string): Repository namebuild(number): Build numberstage(number): Stage numberstep(number): Step number
restart_build
Restart a build (optionally with parameters).
Arguments:
owner(string): Repository ownerrepo(string): Repository namebuild(number): Build numberparams(object, optional): Build parameters
cancel_build
Cancel a running build.
Arguments:
owner(string): Repository ownerrepo(string): Repository namebuild(number): Build number
promote_build
Promote a build to a target environment.
Arguments:
owner(string): Repository ownerrepo(string): Repository namebuild(number): Build numbertarget(string): Target environmentparams(object, optional): Promotion parameters
rollback_build
Rollback a deployment to a previous build.
Arguments:
owner(string): Repository ownerrepo(string): Repository namebuild(number): Build numbertarget(string): Target environmentparams(object, optional): Rollback parameters
approve_build
Approve a build stage (for gated deployments).
Arguments:
owner(string): Repository ownerrepo(string): Repository namebuild(number): Build numberstage(number): Stage number
decline_build
Decline a build stage (for gated deployments).
Arguments:
owner(string): Repository ownerrepo(string): Repository namebuild(number): Build numberstage(number): Stage number
create_build
Create a new build from a commit or branch.
Arguments:
owner(string): Repository ownerrepo(string): Repository namecommit(string, optional): Commit SHAbranch(string, optional): Branch nameparams(object, optional): Build parameters
Cron Job Management
list_crons
List cron jobs for a repository.
Arguments:
owner(string): Repository ownerrepo(string): Repository name
get_cron
Get cron job details.
Arguments:
owner(string): Repository ownerrepo(string): Repository namecron(string): Cron job name
create_cron
Create a new cron job.
Arguments:
owner(string): Repository ownerrepo(string): Repository namename(string): Cron job nameexpr(string): Cron expressionbranch(string): Branch namedisable(boolean, optional): Disable the cron job
delete_cron
Delete a cron job.
Arguments:
owner(string): Repository ownerrepo(string): Repository namecron(string): Cron job name
execute_cron
Execute a cron job immediately.
Arguments:
owner(string): Repository ownerrepo(string): Repository namecron(string): Cron job name
Secret Management
list_secrets
List repository secrets.
Arguments:
owner(string): Repository ownerrepo(string): Repository name
get_secret
Get repository secret details.
Arguments:
owner(string): Repository ownerrepo(string): Repository namename(string): Secret name
create_secret
Create a repository secret.
Arguments:
owner(string): Repository ownerrepo(string): Repository namename(string): Secret namevalue(string): Secret valuepull_request(boolean, optional): Allow in pull requestspull_request_push(boolean, optional): Allow in pull request push events
update_secret
Update a repository secret.
Arguments:
owner(string): Repository ownerrepo(string): Repository namename(string): Secret namevalue(string): Secret valuepull_request(boolean, optional): Allow in pull requestspull_request_push(boolean, optional): Allow in pull request push events
delete_secret
Delete a repository secret.
Arguments:
owner(string): Repository ownerrepo(string): Repository namename(string): Secret name
Organization Secrets
list_org_secrets
List organization secrets.
Arguments:
namespace(string): Organization namespace
get_org_secret
Get organization secret details.
Arguments:
namespace(string): Organization namespacename(string): Secret name
create_org_secret
Create an organization secret.
Arguments:
namespace(string): Organization namespacename(string): Secret namevalue(string): Secret valuepull_request(boolean, optional): Allow in pull requestspull_request_push(boolean, optional): Allow in pull request push events
update_org_secret
Update an organization secret.
Arguments:
namespace(string): Organization namespacename(string): Secret namevalue(string): Secret valuepull_request(boolean, optional): Allow in pull requestspull_request_push(boolean, optional): Allow in pull request push events
delete_org_secret
Delete an organization secret.
Arguments:
namespace(string): Organization namespacename(string): Secret name
User Management
get_self
Get current authenticated user.
list_users
List all users.
get_user
Get user details.
Arguments:
login(string): User login name
create_user
Create a new user.
Arguments:
login(string): User login nameemail(string, optional): User emailadmin(boolean, optional): Admin privilegesactive(boolean, optional): Active statustoken(string, optional): User token
update_user
Update a user.
Arguments:
login(string): User login nameadmin(boolean, optional): Admin privilegesactive(boolean, optional): Active status
delete_user
Delete a user.
Arguments:
login(string): User login name
Template Management
list_templates
List templates (optionally by namespace).
Arguments:
namespace(string, optional): Template namespace
get_template
Get template details and data.
Arguments:
namespace(string): Template namespacename(string): Template name
create_template
Create a new template.
Arguments:
namespace(string): Template namespacename(string): Template namedata(string): Template data (YAML)
update_template
Update a template.
Arguments:
namespace(string): Template namespacename(string): Template namedata(string): Template data (YAML)
delete_template
Delete a template.
Arguments:
namespace(string): Template namespacename(string): Template name
Resources
Build details resource
Access build details via resource URI: drone://builds/{owner}/{repo}/{build}
Example:
Read resource: drone://builds/owner1/repo1/123
Docker
A multi-architecture Docker image is available on GitHub Container Registry:
# Pull the latest image
docker pull ghcr.io/yusiwen/drone-mcp-server:latest
# Run in stdio mode (for local MCP clients)
docker run --rm -i \
-e DRONE_SERVER=https://drone.example.com \
-e DRONE_TOKEN=your_token \
ghcr.io/yusiwen/drone-mcp-server
# Run in Streamable HTTP mode: read-only, authenticated, published on loopback
docker run --rm \
-e DRONE_SERVER=https://drone.example.com \
-e DRONE_TOKEN=your_token \
-e MCP_AUTH_TOKEN=your_mcp_token \
-p 127.0.0.1:8080:8080 \
ghcr.io/yusiwen/drone-mcp-server --http --host 0.0.0.0
# Add --enable-write-tools when the workflow must mutate Drone
docker run --rm \
-e DRONE_SERVER=https://drone.example.com \
-e DRONE_TOKEN=your_token \
-e MCP_AUTH_TOKEN=your_mcp_token \
-p 127.0.0.1:8080:8080 \
ghcr.io/yusiwen/drone-mcp-server --http --host 0.0.0.0 --enable-write-tools
--host 0.0.0.0 is required inside a container so that published ports work, which is exactly why the bearer token is mandatory. Publish the port on 127.0.0.1 unless a TLS terminating proxy sits in front.
Prefer a mounted secret over an environment variable:
docker run --rm \
-v /run/secrets/mcp_token:/run/secrets/mcp_token:ro \
-e MCP_AUTH_TOKEN_FILE=/run/secrets/mcp_token \
-e DRONE_SERVER=https://drone.example.com \
-e DRONE_TOKEN=your_token \
-p 127.0.0.1:8080:8080 \
ghcr.io/yusiwen/drone-mcp-server --http --host 0.0.0.0
Docker Compose Example
services:
drone-mcp-server:
image: ghcr.io/yusiwen/drone-mcp-server:latest
environment:
DRONE_SERVER: https://drone.example.com
DRONE_TOKEN: ${DRONE_TOKEN:?DRONE_TOKEN is required}
MCP_AUTH_TOKEN: ${MCP_AUTH_TOKEN:?MCP_AUTH_TOKEN is required}
# MCP_ENABLE_WRITE_TOOLS: "true" # only when the workflow needs writes
ports:
- "127.0.0.1:8080:8080"
command: ["--http", "--host", "0.0.0.0"]
restart: unless-stopped
read_only: true
security_opt:
- no-new-privileges:true
Releases
Pre-built binaries are available for Linux (x64, arm64), macOS (x64, arm64), and Windows (x64) in the GitHub Releases.
Version Information
The binary includes version information:
./drone-mcp-server --version
Output example:
drone-mcp-server
Version: v1.0.0
Commit: abc123
Build date: 2024-01-01T00:00:00Z
Go version: go1.25.1
Project Structure
.
├── main.go # Entry point: CLI flags, transports, auth, HTTP hardening, tool registration
├── main_test.go # Auth middleware, allowlists, log hygiene and tool registration tests
├── tool/ # Tool handlers module
│ ├── build.go # Build-related tools (list_builds, get_build, restart_build, etc.)
│ ├── repo.go # Repository-related tools (list_repos, enable_repo, disable_repo, etc.)
│ ├── resource.go # Resource handling (drone://builds/...)
│ ├── cron.go # Cron job management tools
│ ├── secret.go # Secret management tools
│ ├── user.go # User management tools
│ ├── template.go # Template management tools
│ ├── validate.go # Input validation helpers used by every tool
│ ├── validate_args.go # Validate implementation for each tool argument struct
│ └── validate_test.go # Validation tests, including injection attempts
├── SECURITY.md # Threat model, deployment guidance and reporting process
├── server.json # MCP registry metadata
├── test_env.sh # Manual smoke test script (uses environment variables)
├── test_mcp.go # In-memory MCP integration test (build tag: test)
└── README.md
Development
Dependencies
- Go 1.25.14+ (the toolchain is pinned by
go.mod; earlier Go 1.25 patch releases carry standard library vulnerabilities that fail thegovulncheckgate) - github.com/modelcontextprotocol/go-sdk - MCP SDK (Streamable HTTP transport)
- github.com/drone/drone-go - Drone API client
Building
make build # build with version info injected through ldflags
go build -o drone-mcp-server .
Testing
make test # unit tests
make test-race # with the race detector
make smoke # end-to-end security smoke test
make vuln # govulncheck (module + standard library)
go vet ./...
The unit tests cover the authentication middleware (including constant time comparison and failure throttling), the Host policy, log sanitization, client address handling, input validation and the read-only tool registration. They need no Drone instance.
Verifying the security hardening
scripts/security-smoke-test.sh starts the real binary on a loopback port and asserts the behaviour that matters, without contacting any Drone instance:
./scripts/security-smoke-test.sh # builds the binary first
BIN=./drone-mcp-server ./scripts/security-smoke-test.sh
It checks that HTTP mode refuses to start without a token, that missing/wrong credentials, wrong Content-Type, oversized bodies, forged Host headers and cross-site Origin are rejected with 401/401/415/413/403/403, that the default tool list is exactly 18 read-only tools and that hidden write tools are not callable, that path traversal in tool arguments is refused, that log injection and X-Forwarded-For spoofing are neutralised, that --allowed-hosts replaces the loopback heuristic, that write tools appear only with --enable-write-tools, that --sse is gone, and that SIGTERM shuts the server down. It exits non-zero on the first failure set and runs in CI, so it doubles as a regression gate.
Manual verification against a real Drone
Use a read-only token first and confirm the tools answer:
export DRONE_SERVER=https://ci.example.com
export DRONE_TOKEN=<read-only token>
export MCP_AUTH_TOKEN="$(openssl rand -base64 32)"
./drone-mcp-server --http --host 127.0.0.1 --port 8080
Then point an MCP client at it. Stdio mode:
{
"mcpServers": {
"drone": {
"command": "/path/to/drone-mcp-server",
"env": { "DRONE_SERVER": "https://ci.example.com", "DRONE_TOKEN": "..." }
}
}
}
Streamable HTTP mode (most clients accept a headers block; npx @modelcontextprotocol/inspector works too, pick "Streamable HTTP" and add the same header):
{
"mcpServers": {
"drone": {
"url": "http://127.0.0.1:8080/",
"headers": { "Authorization": "Bearer <MCP_AUTH_TOKEN>" }
}
}
}
Suggested checks against a real instance: get_self (confirms the token works), list_repos, get_build_last, get_build_logs (exercises untrusted content in the model context), then get_repo with owner: "../users" to confirm validation refuses it. Run write tools against a disposable repository only.
Running locally
export DRONE_SERVER=https://drone.example.com
export DRONE_TOKEN=...
# Read-only stdio server
./drone-mcp-server
# HTTP mode with authentication and write tools
MCP_AUTH_TOKEN=dev-token ./drone-mcp-server --http --enable-write-tools
License
MIT
Questions fréquentes
Qu'est-ce que Drone CI/CD MCP Server ?
MCP server for Drone CI/CD: 45 tools for builds, repos, secrets, users and templates
Comment installer Drone CI/CD MCP Server ?
Il s'exécute sur votre machine. Dans Claude Code : claude mcp add drone-ci-cd-mcp-server -- docker run -i --rm ghcr.io/yusiwen/drone-mcp-server:2.0.0. Pour les autres clients, collez la configuration JSON de cette page dans leurs réglages MCP.
Faut-il une clé API pour Drone CI/CD MCP Server ?
Son entrée au registre n'indique aucune clé ni secret obligatoire. Certaines fonctions peuvent tout de même demander un compte auprès du service auquel il se connecte.
Drone CI/CD MCP Server est-il open source ?
Son code est public sur GitHub, mais le dépôt ne déclare pas de licence standard.
Qui maintient Drone CI/CD MCP Server ?
Il est publié par yusiwen. AI.tn le référence sans le maintenir ni le recommander.
Issu du registre MCP officiel. Être listé n'est pas une recommandation : vérifiez le code et les permissions d'un serveur avant de lui donner accès à vos données.