Plan: Woodpecker MCP Server

plan-2026-02-28-woodpecker-mcp Plan

plan deferred

Vision

Professional, stable MCP tooling for every platform service, built on swagger-generated SDKs with pytest integration tests and PyPI pipelines. Eliminate raw API calls from agent sessions.

Projects & Repos Touched

Project/Repo Platform Role in this plan
woodpecker-mcp (new) Forgejo Base MCP server with workflow-level tools built on woodpecker-sdk (stdio transport)
woodpecker-mcp-remote (new, Phase 2) Forgejo Streamable HTTP wrapper enabling claude.ai access
woodpecker-sdk Forgejo Dependency — Python SDK providing 117 endpoints

Context

The woodpecker-sdk is complete (117 endpoints, 18 mixins, 70 integration tests, PR #2 and #4 merged in parent plan, v0.1.0 published to Forgejo PyPI). Agents currently make raw HTTP calls to Woodpecker CI, wasting 500-1000 tokens per interaction with no type safety, no error handling, and no retry logic. This plan builds the MCP server that wraps the SDK into workflow-level tools that agents, skills, and other MCP consumers can use directly.
The paradigm: The SDK is a faithful, complete 1:1 wrapper over every Woodpecker API endpoint (117 endpoints). The MCP is the expertise layer — it composes those SDK calls into tools that encode how Woodpecker should be used: resolving repo names to IDs, fetching context before acting, returning curated results. An agent using the MCP shouldn't need to understand Woodpecker's internal data model. Agents wrap skills, skills wrap MCP endpoints, MCP endpoints wrap SDK calls.
Reference implementations:
  • forgejo-mcp (~/forgejo-mcp/) — FastMCP server with 11 workflow tools. Same base architecture.
  • gcal-mcp + gcal-mcp-remote — The stdio + Streamable HTTP dual pattern. Base MCP defines tools, remote wrapper patches get_client() with ContextVar for per-request isolation and adds OAuth.
  • mcp-remote-auth (~/mcp-remote-auth/) — Shared OAuth infrastructure: OAuthProxyProvider, TokenStore, auth middleware.
  • pal-e-docs-mcp, notion-mcp — Larger MCPs that split tools into multiple domain files.
What's already done:
  • [x] woodpecker-sdk complete — 117 endpoints, 18 mixins (plan-2026-02-28-woodpecker-sdk-mcp Phase 1)
  • [x] Integration tests — 70 tests, response shape validation
  • [x] SDK published to Forgejo PyPI (v0.1.0)
  • [x] CI pipeline for woodpecker-sdk (lint, test, publish)
  • [x] forgejo-mcp pattern studied and documented
  • [x] mcp-remote pattern studied — gcal-mcp-remote, gmail-mcp-remote, mcp-remote-auth analyzed
  • [x] Tool design: 25 focused tools across 6 domain files
  • [x] SDK endpoint surface fully mapped and verified against plan
  • [x] woodpecker-mcp repo created (Phase 1a)
  • [x] Phase 1a PR #2 submitted — 25 tools, 19 integration tests, registered in ~/.mcp.json
  • [x] Phase 1a PR #2 reviewed and merged — 3 QA rounds, 9 nits resolved, squash-merged 2026-03-01
  • [ ] Phase 1b: activate_repo tool added
  • [ ] woodpecker-mcp-remote repo created

Previous Plan

plan-2026-02-28-woodpecker-sdk-mcp — Phase 3 promoted to this plan

Depends On

phase-2026-02-28-2-pypi-pipeline (parent plan) — ✅ SATISFIED. SDK v0.1.0 published to Forgejo PyPI.

Decisions Made

Decision Rationale
Follow forgejo-mcp architecture exactly FastMCP + @mcp.tool() decorators. server.py (app, lazy singleton client, _ok()/_error_response() helpers) + tools/*.py. Proven pattern across forgejo, notion, gmail, gcal, pal-e-docs MCPs.
Repo name as primary input, not integer IDs Agents think in repo names ("forgejo_admin/my-repo"), not Woodpecker's internal integer IDs. Every tool resolves name → ID internally via <code>lookup_repo()</code>. Optional <code>repo_id</code> override for advanced/scripted use.
Separate focused tools, not compound "manage" tools Each CRUD operation gets its own tool (list_repo_secrets, create_repo_secret, etc.) instead of a single "manage_repo_secret" with an action parameter. Clearer MCP schema, self-documenting parameters, matches forgejo-mcp pattern. Results in 25 tools instead of 12, but each tool has a focused description and obvious parameters.
Smart log fetching <code>get_pipeline_logs</code> takes repo name + pipeline number, automatically fetches the pipeline to discover steps, and returns logs for the failed step by default (or all steps, or a named step). Eliminates the two-call dance agents would otherwise need.
Split tools by domain (6 files) 25 tools is too many for a single workflows.py. Split into pipelines.py, repos.py, secrets.py, crons.py, system.py, queue.py. Matches the pattern in larger MCPs (notion-mcp, gcal-mcp, pal-e-docs-mcp).
get_client() must be patchable All tool modules import <code>get_client</code> from server.py — never use the global <code>_client</code> directly. This is what makes the mcp-remote wrapper work: it replaces <code>get_client</code> in all tool modules before they're imported. Critical for Phase 2 (mcp-remote).
Pull system info tools into Tier 1 healthz, get_version, get_current_user are zero-parameter one-liners — trivially useful for agents checking CI status. No reason to defer.
Separate repo: woodpecker-mcp Consistent with forgejo-sdk / forgejo-mcp split. SDK and MCP have different dependency trees and release cadences.
Development with editable installs During development, <code>pip install -e ../woodpecker-sdk</code>. Production pyproject.toml depends on published <code>ldraney-woodpecker-sdk&gt;=0.1.0</code>.
mcp-remote is Phase 2, not separate plan Woodpecker remote is simpler than Google/Notion (single PAT, no per-user OAuth). Keeps the full MCP lifecycle in one plan. Remote wrapper is a separate repo but same plan.
Pull activate_repo into Phase 1b Real-world agent transcript showed an agent spending ~8 bash calls (3 failures) to activate repos via raw API. Without activate_repo, agents must cross-reference Forgejo API for forge_remote_id, then construct POST to Woodpecker. The MCP tool accepts a Forgejo repo full name and handles ID resolution internally. Added as Phase 1b to avoid blocking Phase 1a PR review.
MCP registered in global ~/.mcp.json The woodpecker MCP is registered in the global <code>~/.mcp.json</code> file (not ~/.claude/mcp.json). Enabled via <code>enabledMcpjsonServers</code> in settings.local.json. Uses <code>uv run --directory ~/woodpecker-mcp</code> with WOODPECKER_URL and WOODPECKER_TOKEN env vars.

Architecture

Base MCP Pattern (Phase 1 — stdio transport)

Key patterns (verified across 5+ existing MCPs):
  • Module-level mcp = FastMCP("woodpecker") singleton
  • Lazy client via global _client + get_client() reading WOODPECKER_URL and WOODPECKER_TOKEN from environment
  • All tool modules import get_client from server.py (patchable for mcp-remote)
  • __init__.py exports mcp via __all__ (allows remote wrapper to import it)
  • All tools return str (JSON-formatted) via _ok() helper
  • All tools have try/except returning _error_response(exc) for HTTP errors
  • Parameters use Annotated[type, Field(description="...")] from pydantic
  • Response shaping: tools return curated fields, not raw API dumps
  • Registered in ~/.mcp.json via uv run --directory, enabled in settings.local.json

Remote Wrapper Pattern (Phase 2 — Streamable HTTP)

Key pattern: The remote wrapper imports the FastMCP instance from the base MCP, patches get_client() with a ContextVar-based version for per-request isolation, configures auth, and runs with transport="streamable-http". The base MCP knows nothing about HTTP or auth.
Woodpecker simplification: Unlike Google/Notion MCPs which need three-party OAuth (each user has their own credentials), Woodpecker uses a single admin PAT. The remote wrapper is simpler — it injects the server-side PAT into per-request clients rather than managing per-user OAuth flows. Auth for the remote endpoint itself can use a simple API key or the mcp-remote-auth pattern with a lightweight provider.

Proposed MCP Tools (Phase 1a — 25 tools)

Pipeline tools (6) — tools/pipelines.py

Tool name SDK calls composed Description
<code>trigger_pipeline</code> lookup_repo + create_pipeline Trigger a build by repo name + branch, with optional variables
<code>get_pipeline_status</code> lookup_repo + get_pipeline Get pipeline status, steps, timing by repo name + pipeline number
<code>get_pipeline_logs</code> lookup_repo + get_pipeline + get_step_logs Fetch logs for a pipeline. Auto-discovers steps; returns failed step by default, or all steps, or a specific step name. Supports max_lines truncation.
<code>restart_pipeline</code> lookup_repo + restart_pipeline Restart a pipeline by repo name + number
<code>cancel_pipeline</code> lookup_repo + cancel_pipeline Cancel a running pipeline by repo name + number
<code>list_pipelines</code> lookup_repo + list_pipelines List recent pipelines with filtering (branch, status, event, before, after, ref, page, per_page)

Repo tools (2) — tools/repos.py

Tool name SDK calls composed Description
<code>list_repos</code> list_repos (direct) List all Woodpecker-activated repos with optional active/page/per_page filters
<code>get_repo</code> lookup_repo or get_repo Get repo config and status by name or ID

Repo secret tools (4) — tools/secrets.py

Tool name SDK calls composed Description
<code>list_repo_secrets</code> lookup_repo + list_repo_secrets List secrets for a repo (names only, values are masked) with page/per_page
<code>create_repo_secret</code> lookup_repo + create_repo_secret Create a new secret on a repo with name, value, and event filters
<code>update_repo_secret</code> lookup_repo + update_repo_secret Update an existing repo secret's value or event filters
<code>delete_repo_secret</code> lookup_repo + delete_repo_secret Delete a repo secret by name

Global secret tools (4) — tools/secrets.py

Tool name SDK calls composed Description
<code>list_global_secrets</code> list_global_secrets (direct) List all global secrets (names only, values are masked) with page/per_page
<code>create_global_secret</code> create_global_secret (direct) Create a new global secret
<code>update_global_secret</code> update_global_secret (direct) Update a global secret's value or event filters
<code>delete_global_secret</code> delete_global_secret (direct) Delete a global secret by name

Cron tools (5) — tools/crons.py

Tool name SDK calls composed Description
<code>list_cron_jobs</code> lookup_repo + list_cron_jobs List cron jobs for a repo with page/per_page
<code>create_cron_job</code> lookup_repo + create_cron_job Create a cron job with name, schedule, and optional branch
<code>update_cron_job</code> lookup_repo + update_cron_job Update a cron job's schedule or branch
<code>delete_cron_job</code> lookup_repo + delete_cron_job Delete a cron job by name
<code>run_cron_job</code> lookup_repo + run_cron_job Manually trigger a cron job

System tools (3) — tools/system.py

Tool name SDK calls composed Description
<code>healthz</code> healthz (direct) Check if Woodpecker server is healthy
<code>get_version</code> get_version (direct) Get Woodpecker server version (curated: version + source)
<code>get_current_user</code> get_current_user (direct) Get the authenticated user's info (curated: id, login, email, admin, active)

Queue tools (1) — tools/queue.py

Tool name SDK calls composed Description
<code>get_queue_status</code> get_queue_info + list_queued_pipelines Overview of what's running and waiting (curated: stats + pipeline summaries)

Phase 1b Tool — activate_repo

Tool name SDK calls composed Description
<code>activate_repo</code> activate_repo(forge_remote_id=...) Activate a Forgejo repo in Woodpecker CI. Accepts a <code>forge_remote_id</code> (the Forgejo repo's integer ID). Returns the activated Woodpecker repo with its new ID and config.
Rationale: Real-world agent transcript showed ~8 raw API calls (3 failures) to activate repos. The SDK's activate_repo(forge_remote_id=str) method handles this in one call. The MCP tool wraps it cleanly. Note: the agent still needs the Forgejo repo ID (from forgejo-mcp's get_repo), but the Woodpecker-side complexity is eliminated.

Research: SDK Endpoint Surface (117 endpoints, 18 mixins)

Tier 1 — Covered in Phase 1a+1b (26 tools)

Mixin Count Key methods used by MCP tools
Pipelines 10 list_pipelines, create_pipeline, get_pipeline, restart_pipeline, cancel_pipeline (5 of 10 used directly)
PipelineLogs 4 get_step_logs (1 of 4 — delete/stream not needed for Tier 1)
Repositories 13 list_repos, lookup_repo, get_repo, activate_repo (4 of 13)
RepoSecrets 5 All 5: list, create, get, delete, update
Secrets (global) 5 All 5: list, create, get, delete, update
CronJobs 6 All 6: list, create, get, run, delete, update
PipelineQueues 5 get_queue_info, list_queued_pipelines (2 of 5)
System 6 healthz, get_version (2 of 6)
User 5 get_current_user (1 of 5)

Tier 2 — Phase 4 (admin tools, future)

Mixin Count Key methods
Agents 10 global + org-scoped agent CRUD, list_agent_tasks
Organizations 15 org CRUD, org secrets/registries/agents/permissions
Users (admin) 5 admin user CRUD
Forges 5 CRUD forges
RepoRegistries 5 CRUD repo registries
Registries (global) 5 CRUD global registries
Remaining Pipeline ops 5 approve, decline, delete, get_config, get_metadata
Remaining Repo ops 9 delete, update, move, chown, branches, PRs, permissions, repair, repair_all
Remaining Queue ops 3 pause, resume, wait_for_running

Tier 3 — Skip (not useful via MCP)

Mixin Count Reason to skip
Debug 10 pprof endpoints — binary profile data, not useful to agents
Badges 2 SVG/XML output — not useful to agents
Events 1 SSE streaming — doesn't fit MCP request/response model

Phases

Phase 1a — Base MCP server with 25 workflow tools ✅ COMPLETED

Slug: phase-2026-02-28-1a-mcp-base
Goal: Create woodpecker-mcp repo with FastMCP server, 25 focused workflow tools across 6 domain files, integration tests, and register in Claude config. Design for mcp-remote compatibility (patchable get_client).
Owner: Agent
Issue: issue-woodpecker-mcp-base-server
PR: #2 — squash-merged 2026-03-01
Deliverables:
  • 25 tools across 6 domain modules (pipelines, repos, secrets, crons, system, queue)
  • 19 integration tests with proper pytest patterns
  • All responses curated (no raw SDK passthrough)
  • All list tools expose pagination (page/per_page)
  • list_pipelines exposes 8 filter params (branch, status, event, before, after, ref, page, per_page)
  • get_pipeline_logs supports max_lines truncation
  • get_client() patchable — confirmed via QA (no tool module accesses _client directly)
  • Registered in ~/.mcp.json, enabled via enabledMcpjsonServers in settings.local.json
  • 3 QA rounds, 9 nits resolved before merge
Steps:
  • ✅ Create woodpecker-mcp repo on Forgejo
  • ✅ Scaffold server following verified pattern
  • ✅ Implement repo name → ID resolution helper
  • ✅ Implement 6 pipeline tools (tools/pipelines.py)
  • ✅ Implement 2 repo tools (tools/repos.py)
  • ✅ Implement 8 secret tools (tools/secrets.py)
  • ✅ Implement 5 cron tools (tools/crons.py)
  • ✅ Implement 3 system tools (tools/system.py)
  • ✅ Implement 1 queue tool (tools/queue.py)
  • ✅ Integration tests (19 tests passing)
  • ✅ Register in ~/.mcp.json, enable in settings.local.json
  • ✅ QA review (3 rounds, 9 nits resolved)
  • ✅ Merge PR #2

Phase 1b — Add activate_repo tool

Slug: phase-2026-02-28-1b-activate-repo
Goal: Add activate_repo tool to tools/repos.py, bringing the total to 26 tools. Closes the gap exposed by real-world agent transcript where repo activation via raw API required ~8 calls with multiple failures.
Owner: Agent
Issue: TBD (create when starting)
Depends on: Phase 1a ✅
Steps:
  • Add activate_repo to tools/repos.py (accepts forge_remote_id: str)
  • Add integration test
  • Update register_all_tools if needed
  • Open PR, review, merge

Phase 2 — Streamable HTTP remote wrapper (claude.ai access)

Slug: phase-2026-02-28-2-mcp-remote
Goal: Create woodpecker-mcp-remote repo that wraps the base MCP over Streamable HTTP, enabling claude.ai to connect to Woodpecker CI tools.
Owner: Agent
Issue: TBD
Depends on: Phase 1a ✅
Context: The mcp-remote pattern is proven across gcal-mcp-remote, gmail-mcp-remote, and notion-mcp-remote. Woodpecker is simpler than those because it uses a single admin PAT (no per-user OAuth). The remote wrapper imports the base MCP's FastMCP instance, patches get_client() with ContextVar-based per-request isolation, configures auth, and serves over Streamable HTTP.
Key decisions (to be made when starting):
  • Auth strategy for the remote endpoint — simple API key, or mcp-remote-auth with lightweight provider? (Woodpecker PAT is server-side, not per-user)
  • Deployment target — Kubernetes on pal-e cluster, or systemd on dev machine?
  • Network exposure — Tailnet-only, or public with auth?
  • Whether to use mcp-remote-auth shared library or build simpler bespoke auth
Steps:
  • Create woodpecker-mcp-remote repo on Forgejo
  • Implement client_patch.py (ContextVar-based get_client replacement)
  • Implement server.py (import base mcp, apply patch, configure auth + transport)
  • Add /health endpoint
  • Configure deployment (systemd service or K8s manifest)
  • Test from claude.ai — verify tool discovery and invocation

Phase 3 — Tier 2 admin tools (future)

Slug: phase-2026-02-28-3-mcp-tier2
Goal: Add admin/org/queue/user management tools to woodpecker-mcp base.
Owner: Agent
Issue: TBD

Key Files

Phase File Repo Change
1a src/woodpecker_mcp/server.py woodpecker-mcp FastMCP app, WoodpeckerClient lifecycle, _ok()/_error_response(), get_client() (patchable)
1a src/woodpecker_mcp/tools/pipelines.py woodpecker-mcp 6 pipeline tools including smart log fetching with max_lines
1a src/woodpecker_mcp/tools/repos.py woodpecker-mcp 2 repo tools (list with filters, get)
1a src/woodpecker_mcp/tools/secrets.py woodpecker-mcp 8 secret tools (repo + global CRUD) with pagination
1a src/woodpecker_mcp/tools/crons.py woodpecker-mcp 5 cron tools with pagination and clean cron_id handling
1a src/woodpecker_mcp/tools/system.py woodpecker-mcp 3 system/info tools with curated responses
1a src/woodpecker_mcp/tools/queue.py woodpecker-mcp 1 queue status tool with curated stats
1a src/woodpecker_mcp/tools/__init__.py woodpecker-mcp register_all_tools() imports all 6 tool modules
1a src/woodpecker_mcp/__init__.py woodpecker-mcp Package init, exports mcp via __all__
1a src/woodpecker_mcp/__main__.py woodpecker-mcp python -m runner
1a pyproject.toml woodpecker-mcp Package config: deps on mcp + woodpecker-sdk, console script
1a tests/ woodpecker-mcp 19 integration tests against live Woodpecker
1b src/woodpecker_mcp/tools/repos.py woodpecker-mcp Add activate_repo tool (3 tools total in repos.py)
2 src/woodpecker_mcp_remote/server.py woodpecker-mcp-remote Import base mcp, configure auth + Streamable HTTP transport
2 src/woodpecker_mcp_remote/client_patch.py woodpecker-mcp-remote ContextVar-based get_client() replacement
2 pyproject.toml woodpecker-mcp-remote Deps on woodpecker-mcp + mcp-remote-auth + uvicorn

Verification

Phase 1a ✅

  • [x] pip install -e . works with local SDK dep
  • [x] All 25 tools callable from MCP client
  • [x] trigger_pipeline works by repo name (resolves name → ID internally)
  • [x] get_pipeline_logs auto-discovers steps and returns failed step logs
  • [x] Secret CRUD works (create, list, update, delete) for both repo and global
  • [x] Cron CRUD works (create, list, update, delete, run)
  • [x] System tools work (healthz, get_version, get_current_user)
  • [x] Registered in ~/.mcp.json — agent session can see and call tools
  • [x] pytest passes against live Woodpecker instance (19 tests)
  • [x] get_client() is patchable (all tool modules import it from server.py)
  • [x] All responses curated (no raw SDK passthrough)
  • [x] All list tools expose pagination
  • [x] QA clean pass (3 rounds, 9 nits resolved)

Phase 1b

  • [ ] activate_repo works with forge_remote_id
  • [ ] Integration test passes

Phase 2

  • [ ] Remote wrapper imports base MCP's FastMCP instance
  • [ ] client_patch.py replaces get_client() with ContextVar version
  • [ ] Streamable HTTP transport works
  • [ ] /health endpoint responds
  • [ ] claude.ai can discover and call all tools
  • [ ] Auth prevents unauthorized access

Next Plan Seeds

  • Skills wrapping woodpecker-mcp tools (e.g., /deploy, /ci-status)
  • Enhanced /review-pr skill that checks Woodpecker CI status
  • forgejo-mcp audit against this pattern (todo-forgejo-mcp-audit)
  • PyPI pipeline for woodpecker-mcp (once SDK pipeline pattern is proven across repos)
  • Tier 2 admin tools (Phase 3 of this plan)
  • plan-2026-02-28-woodpecker-sdk-mcp — parent plan (SDK + PyPI pipeline) ✅ COMPLETED
  • plan-2026-03-01-forgejo-pypi-migration — PyPI pipeline pattern for all Python repos
  • todo-woodpecker-mcp — originating TODO ✅ DONE
  • todo-forgejo-mcp-audit — companion audit
  • forgejo-mcp (~/forgejo-mcp/) — base MCP reference
  • gcal-mcp + gcal-mcp-remote (~/calendar-mcp/, ~/gcal-mcp-remote/) — mcp-remote reference
  • mcp-remote-auth (~/mcp-remote-auth/) — shared OAuth infrastructure
  • forgejo-sdk (~/forgejo-sdk/) — SDK reference