- Go 96.7%
- Dockerfile 3.3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .forgejo/workflows | ||
| internal | ||
| .gitignore | ||
| bugzilla-mcp.container | ||
| bugzilla-mcp.network | ||
| Containerfile | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| main.go | ||
| README.md | ||
bugzilla-mcp
A Go MCP server for any Bugzilla bug tracker, wrapping the Bugzilla REST API. It
exposes read tools plus optional write tools over the Model Context Protocol. The
target instance is configurable with --bugzilla-base-url (env-friendly) and
works with any Bugzilla — the default is only an example and does not bind the
server to any particular host. Read is anonymous and always available; write
requires an API key and is off by default.
Note: tool responses include Russian text, and tool descriptions carry bilingual RU/EN keywords so the client can pick the right tool from prompts in either language.
Tools
Read (2, always, anonymous)
| Tool | Purpose |
|---|---|
bug_get |
Details of a bug by numeric id: status, product/component, first comments |
bug_search |
Search bugs via quicksearch (keywords, package, component) |
Write (3, only when write mode is enabled)
| Tool | Purpose |
|---|---|
create_bug |
Create/file a NEW real bug in the configured Bugzilla instance |
add_comment |
Add a comment to an existing bug |
update_bug |
Change status/resolution, close a bug, mark as duplicate (+optional comment) |
The write tools are registered and appear in tools/list only when an API key
is set and read-only mode is not enabled (see below). Otherwise only the two
read tools are available.
The "not found" behavior and Bugzilla errors are returned as friendly text rather than a protocol error.
Build and run
go build -o bugzilla-mcp .
# stdio transport (default) — for local MCP clients
./bugzilla-mcp --transport=stdio --bugzilla-base-url=https://bugzilla.example.org
# streamable-http — /mcp endpoint
./bugzilla-mcp --transport=http --addr=:8091 --bugzilla-base-url=https://bugzilla.example.org
Flags
| Flag | Env | Default | Description |
|---|---|---|---|
--transport |
stdio |
stdio or http |
|
--addr |
:8091 |
address for the http transport | |
--bugzilla-base-url |
https://bugzilla.altlinux.org |
Bugzilla base URL, REST at /rest (any instance; the default is only an example) |
|
--bugzilla-api-key |
BUGZILLA_API_KEY |
(none) | Static Bugzilla API key forwarded to Bugzilla. Optional — read is anonymous if unset, required for the write tools |
--bugzilla-auth-mode |
BUGZILLA_AUTH_MODE |
query |
How the key is sent to Bugzilla: query (?api_key=) or bearer (Authorization: Bearer, for Red Hat Bugzilla) |
--mcp-auth-header |
MCP_AUTH_HEADER |
(none) | http only: HTTP header clients use to send a Bugzilla API key to this server. Unset → inbound header auth disabled, client headers ignored |
--read-only |
BUGZILLA_READ_ONLY |
false |
force read-only |
For each flag with an env var, the flag takes priority, otherwise the env var is used.
Authentication
Access is anonymous by default: with no key the server makes unauthenticated
read-only requests, which work on public Bugzilla instances (e.g.
bugzilla.altlinux.org). A key is only needed for the write tools or private data.
- Outbound (MCP → Bugzilla). The static key comes from
--bugzilla-api-key/BUGZILLA_API_KEY.--bugzilla-auth-modeselects how it is sent:query(default) as?api_key=<KEY>, orbearerasAuthorization: Bearer <KEY>(required by Red Hat Bugzilla and similar). The key never leaks into error messages. - Inbound (client → MCP), http transport only. When
--mcp-auth-header/MCP_AUTH_HEADERnames a header, each client request may carry its own Bugzilla key in that header, which takes priority over the static key for that session. When unset, inbound header auth is disabled and client headers are ignored. Forstdiothe key is always the static one. - Resolution order (http): per-request header →
--bugzilla-api-key→ anonymous.
Write gating. The write tools are enabled only when
a key is present && not --read-only. At startup (stdio) the mode is logged:
bugzilla-mcp: read-write mode (BUGZILLA_API_KEY set)— a key is present;... read-only mode (anonymous, no BUGZILLA_API_KEY)— no key is set;... read-only mode (--read-only)— write is force-disabled by flag/env.
In http mode write availability is evaluated per session, so a client that supplies a key via the inbound header gets the write tools.
The read tools (bug_get/bug_search) stay anonymous and work in all modes.
--read-only (or BUGZILLA_READ_ONLY=1) removes the write tools from
tools/list entirely, even if a key is set.
In HTTP mode it also serves GET /healthz (a health-check for quadlet/orchestrator).
Usage with MCP clients
The server speaks MCP over stdio; the binary is bugzilla-mcp (make sure it
is on your PATH). Point --bugzilla-base-url at your Bugzilla instance, and
supply BUGZILLA_API_KEY only if you want the write tools.
Claude (.mcp.json)
{
"mcpServers": {
"bugzilla": {
"command": "bugzilla-mcp",
"args": ["--transport=stdio", "--bugzilla-base-url=https://bugzilla.example.org"],
"env": { "BUGZILLA_API_KEY": "<optional for write>" }
}
}
}
Codex (~/.codex/config.toml)
[mcp_servers.bugzilla]
command = "bugzilla-mcp"
args = ["--transport=stdio", "--bugzilla-base-url=https://bugzilla.example.org"]
env = { BUGZILLA_API_KEY = "..." }
opencode (opencode.json)
{
"mcp": {
"bugzilla": {
"type": "local",
"command": ["bugzilla-mcp", "--transport=stdio", "--bugzilla-base-url=https://bugzilla.example.org"],
"enabled": true
}
}
}
Tests
go test ./...
The client tests (internal/bugzilla/*_test.go) cover the HTTP layer (a 4xx
error body → domain layer, query/bearer auth modes and anonymous requests,
HTTPError on non-JSON), fetching
bugs and comments (including the single-bug fallback, private comments → auth
error), quicksearch, and human-readable formatting (FormatBug, FormatSearch,
StatusLine, firstLines, NormalizeLimit). The write tests cover create/
comment/update requests and friendly error handling.
Container (podman/docker)
podman build -t bugzilla-mcp -f Containerfile .
podman run --rm -p 127.0.0.1:8091:8091 bugzilla-mcp \
--transport=http --addr=:8091 --bugzilla-base-url=https://bugzilla.example.org
The image is multi-stage: build on golang:1.25 (CGO_ENABLED=0, static binary),
the final stage is FROM scratch with a CA bundle for HTTPS to the configured
Bugzilla instance, USER 65534.
Quadlet (rootless systemd)
The bugzilla-mcp.network and bugzilla-mcp.container files are for
deploying as a rootless service. Copy them into ~/.config/containers/systemd/,
then:
systemctl --user daemon-reload
systemctl --user start bugzilla-mcp
bugzilla-mcp.network sets Options=mtu=1500 — required, otherwise outbound
TCP connections from the container break. The port is published on
127.0.0.1:8091.
Layout
main.go # CLI + server startup (stdio/http)
internal/bugzilla/ # Bugzilla client + tests
http.go # HTTP GET, auth modes (query/bearer), 4xx handling, User-Agent
errors.go # BugNotFound / BugzillaSearchError / BugzillaAuthError
bugs.go # FetchBug, FetchComments, FormatBug
search.go # quicksearch, FormatSearch, NormalizeLimit
write.go # CreateBug/AddComment/UpdateBug (POST/PUT + WriteError)
internal/tools/ # MCP tools (Input structs + handlers)
tools.go # read: bug_get, bug_search + Register gating
write.go # write: create_bug, add_comment, update_bug
Containerfile
bugzilla-mcp.network bugzilla-mcp.container