- Go 93.9%
- Shell 5.9%
- JavaScript 0.2%
|
All checks were successful
ci / mcp-conformance (pull_request) Successful in 1m52s
drift / govulncheck (pull_request) Has been skipped
drift / forgejo-smoke (pull_request) Has been skipped
drift / surface-diff (pull_request) Successful in 1m7s
ci / mcp-conformance (push) Successful in 3s
drift / govulncheck (push) Has been skipped
drift / surface-diff (push) Successful in -3s
drift / forgejo-smoke (push) Has been skipped
ci / gate (push) Successful in 11m3s
ci / gate (pull_request) Successful in 19m10s
Closes #425 |
||
|---|---|---|
| .forgejo/workflows | ||
| assets | ||
| cmd | ||
| conformance/mcp-client | ||
| docs | ||
| hooks | ||
| internal | ||
| scripts | ||
| skills | ||
| tests/integration | ||
| .gitignore | ||
| .goreleaser.yaml | ||
| CHANGELOG.md | ||
| CLAUDE.md | ||
| CODE_OF_CONDUCT.md | ||
| CONTRIBUTING.md | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| NOTICE | ||
| project-identity.env | ||
| README.md | ||
| SECURITY.md | ||
| THIRD-PARTY-LICENSES.md | ||
madtea
madtea is a CLI and MCP server for Forgejo and Gitea - the self-hosted forges (GitHub-style code servers) - built for the way code gets written now: you and an AI agent, working in the same repo. One binary, two ways to use it: a gh-style CLI for you, and an MCP server for your agent. Tested against both forges.
This document is a quick start guide and the case for madtea, outlining what it does differently, and the safety model that makes it safe to hand to an agent.
- New to madtea? Start with What is madtea? and Getting started
- Ready to build from source? Jump to Build madtea from source
- Want to contribute? See the Code contributions section and CONTRIBUTING.md
- Looking for detailed documentation? Navigate to docs
Named after the Mad Tea-Party from Alice's Adventures in Wonderland.
Browse the full documentation online at madtea.lol - the same docs tree in this repo, rendered as a site.
Table of contents
What is madtea?
Git tooling was designed when the only developer in your terminal was you. Put an AI agent there too, and familiar commands start failing in new ways:
git remote -vleaks a token into the agent's context,- a well-meaning
issue editpastes over your bug repro steps, git reset --harderases uncommitted work.
madtea is a Forgejo/Gitea client built for the terminal where you and your agent share a keyboard - but not a risk profile. Every command is safe to hand to an agent, and every guardrail explains itself to a human.
Human or Agent? Both
- A CLI for humans: issues, PRs, releases, labels, milestones, repos, and git workflows - every command named the way gh (GitHub's CLI) or tea (Gitea's) names it.
- An MCP server for agents: a compact set of tools designed around how models actually call them - tested against the agent's 1st tool-call attempt, because retries are costly in tokens.
- For Claude Code, a plugin layers guardrail hooks on top: credential-leaking and destructive raw-git commands are stopped before they run.
Why choose madtea?
| Scenario | With existing tools | With madtea |
|---|---|---|
The agent runs git remote -v |
A token embedded in the remote URL lands in its context and logs | Nothing to leak: the token lives in your OS keychain using madtea auth login and reaches git invisibly - never in URLs, argv, ps, or history |
| You ask it to update an issue's status | It pastes its own summary over your whole issue body - repro steps gone, no undo | Your repro steps survive: madtea issue body patch swaps one exact text span; whole-body replace doesn't exist |
The agent reaches for git reset --hard |
Uncommitted work gone | Your work survives: the command doesn't exist, and the guardrail hooks name the safe alternative |
| An unattended agent decides an issue should be deleted | It's gone | It can't: no madtea surface carries a delete-issue operation - the CLI command always refuses and names the forge web UI, so destruction stays with a human at the forge |
| You ship a finished branch | 5 command dance: commit, push, open the PR, merge, pull - and untangle it yourself if it dies halfway | One resumable command: madtea finish executes and picks a half-finished cycle up exactly where it died |
| You merge three green PRs and main turns red | Which one broke it? Revert and rerun CI by hand until you know | The culprit is named: madtea orchestrate merges the batch, tests the combined result, finds the PR that broke it, and reverts - your checkout untouched |
| You ask "what should we work on next?" | Someone rebuilds the dependency picture on a whiteboard - gh and tea can't record issue dependencies at all |
One query: madtea issue rank sorts a repo or the whole org by what unblocks the most |
| Your clones span a public Forgejo instance, a self-hosted Forgejo, and a work Gitea | Configure a login per forge and name it every time; guess wrong, get a 404 | Each clone just knows: forge from its origin, token from your keychain - madtea clone acme/api works from anywhere |
| You want proof the binary you run is the binary that was released | Trust the install; gh doesn't self-update at all |
Provably intact: madtea update GPG-verifies against a pinned key, and update --verify re-hashes the installed binary and repairs drift |
| You keep architecture decision records | Hand-numbered markdown; the index rots quietly | ADRs as a command: madtea adr new numbers race-free, and madtea adr check fails CI when the index drifts |
And the MCP server isn't just the CLI shoved over stdio:
- ~50 consolidated tools - one per resource (
madt_issues,madt_prs, etc.), each dispatching on anactionargument - so the model sees every valid verb up front, and invalid ones fail before a wasted round-trip. - Forgiving inputs, helpful errors - single IDs, comma strings, or arrays; batch calls with per-item results; a wrong argument name gets "did you mean
numbers?" instead of a silent drop. - A lean tool list - low-traffic domains (
admin,wiki,packages, etc.) stay unloaded until an agent asks for them, so they don't bloat context until used. - The same guardrails on every client - safety rules live in the tool descriptions themselves, and we conducted a clean-room test to prove an agent can work correctly from those alone. No hand curating an instruction-file (
CLAUDE.md/AGENTS.md) so the agent remembers the tool. - Help the model can query (
madt_help), and a raw-API escape hatch (madtea api) that steers back to the typed commands, and durable mcp serve and crash logs, so a session that dies mid-call leaves a trace instead of a mystery.
The complete feature-by-feature version of this argument, with source citations, is docs/WHY.md. The command-by-command comparison with gh, glab, tea, and fj is docs/COMPARISON.md.
Getting started
Install madtea
Prerequisites: git. That's the list - madtea is a single self-contained binary.
# Linux (amd64 shown; arm64: swap the arch)
curl -fsSL https://git.cheshirecube.cloud/sixfold-space/madtea/releases/download/latest/madtea_linux_amd64.tar.gz | tar -xz
./madtea_linux_amd64/madtea install # installs to /usr/local/bin; delete the extracted dir after
macOS, Windows, and from-source installs, what madtea install does, MCP wiring, and keeping it current are all in the install guide.
Wire in the MCP server
The interface your agent calls madtea's tools through. madtea install wires
Claude Code; add --mcp-client for the others.
madtea install # wires Claude Code when its CLI is present
madtea install --mcp-client codex,antigravity # also wire Codex and Antigravity
Claude Code users can add the plugin on top for the guardrail hooks and skills clients guide:
/plugin marketplace add https://git.cheshirecube.cloud/sixfold-space/claude-plugins.git
/plugin install madtea@sixfold-plugins
Connect to your forge
madtea auth login # in a clone: detects the forge from origin; anywhere else: asks for the URL
madtea auth whoami # verify: who am I on this forge, what can this token do
madtea repo list # first real call - works before you've cloned anything
madtea clone acme/api # grab a repo; inside it, issue list / pr create / finish all just work
One login per forge: the token lands in your OS keychain (no keychain? pass or systemd-creds; plaintext only if you explicitly ask to be insecure) (ADR 0013). Every command resolves its forge from the repo you are standing in - ambiguity is refused with the candidates listed. Details: the setup guide.
Using madtea from the command line
Every command follows gh's exact name, or tea's, with zero aliases, so existing muscle memory transfers (ADR 0016):
madtea issue list
madtea pr create --title "feat: rate limiter"
madtea release create v1.2.0
madtea repo protect main
The full command reference is generated into docs.
A tour of the workflows
In order: ship a branch, land a batch of PRs, run many hands in one repo, ask the backlog what matters, keep the place tidy - and put your agent on the same roads.
- Ship a branch: finish - one command: commit, push, PR, merge, back to main - resumable if it dies midway.
- Land a whole batch: orchestrate - merge a set of PRs, test the union, binary-search and revert the one that breaks it.
- One repo, many hands: worktrees - per-task isolated trees so parallel agents never fight over the same index.
- Ask the backlog: the issue dependency graph - wire issue deps, rank keystones and free leaves, track epics across repos.
Keep it tidy: init, prune, pull
madtea init my-service --org acme # empty folder → remote repo, first commit pushed
madtea prune --remote # dry run (the default): show which merged branches would go
madtea prune --delete-branches --remote # actually delete them, local and remote
madtea pull # also repairs an upstream master → main rename
prunealso finds merged remote branches no local checkout remembers, and nothing is ever force-deleted.pull's rename repair runs only when it can prove the fix is safe - otherwise it changes nothing and prints the commands for you.
Put your agent on the paved road: the plugin skills
The Claude Code plugin ships two skills - procedures the agent pulls in when the moment matches.
/dev-workflow triggers when work starts, and walks the whole issue lifecycle:
you: "take issue #42"
agent: madt_issues action=get numbers=[42] - reads the ask and acceptance criteria
madt_issues action=develop number=42 - cuts the linked branch: issue-42-rate-limiter
... implements, tests ...
madt_finish title="fix: rate limiter" closes=42 - commit, push, PR, merge, issue closed
/forge-workflow triggers on any forge operation and keeps the agent on the conventions - one batched call instead of a loop, finish to complete a branch, never raw git against a remote:
you: "close the three superseded issues and label the rest"
agent: madt_issues action=close numbers=[12,15,19] reason="superseded by #42" - one call, not three
madt_issues action=edit numbers=[20,21] add_labels=["triage"] - same
Both are thin by design: the procedures and safety rules live in the MCP layer itself, so Codex and Antigravity agents follow the same road - the skills just make Claude Code reach for it at the right moment.
The safety model
Your agent can't hand you an irreversible footgun
madtea simply does not carry the operations that could destroy unrecoverable work or forge data. Deleting a repository, an issue, a release, a tag, or a wiki page is not a madtea operation on any surface - not the CLI, not the MCP tools an agent drives. Each delete command stays discoverable but refuses, naming what it would destroy and pointing you at the forge web UI:
agent runs madtea issue delete 42
|
v
refused: "madtea does not delete an issue ... Delete it yourself in the forge web UI if you truly intend to."
madtea performs a destructive step only when it can machine-prove, from git or forge state, that nothing unrecoverable is lost first - a content-verified prune, a --force-with-lease push, a merged-only branch delete. Everything else stays with you and your own tools (the strict-omission model · ADR 0029).
Where your token goes (and doesn't)
madtea clone acme/api # the URL never carries a token
madtea secret-exec GITEA_TOKEN -- ./deploy.sh # token only in the child's env - never disk, argv, or history
The mechanism: every credentialed git operation writes a temporary GIT_ASKPASS helper, hands git the token through it, and deletes it afterward. The token itself stays in your OS keychain. So it never appears in a URL, in argv, in ps output, in shell history - or in an agent's transcript. And if an agent tries the leaky raw-git command anyway, the Claude Code plugin's seven PreToolUse hooks stop it before it runs (credential safety · safety hooks · secret-exec).
Why your work can't be clobbered
madtea issue body patch 42 --old "Status: in review" --new "Status: shipped"
madtea issue body task-toggle 42 "deploy to staging" --checked
patch swaps one exact span of text - if that text has drifted or appears twice, the edit is rejected rather than forced. Every edit is checked against the version it was read from, so a change written from a stale read can't land. And since a whole-body-replace command simply doesn't exist, there's nothing for an agent to misfire (safe body edits).
Git gets the same treatment - and every refusal names the exact command a human can run instead:
| What could eat your work | With madtea |
|---|---|
reset --hard |
Doesn't exist - nothing an agent can call throws away uncommitted work |
checkout -- <files> |
Doesn't exist - live edits can't be overwritten from a ref |
stash drop |
Doesn't exist - stashes can't be destroyed |
branch -D |
branch delete refuses unmerged work, and a refusal says whether the branch was squash-merged (safe to clean up yourself) or still holds real work |
| root-owned leftovers blocking a worktree removal | worktree remove stops and hands you the cleanup - it never escalates privileges |
The rule generalizes: everything that would move a tree refuses on a dirty tree and names both the files in the way and the fix. Force exists, but you type it:
| Command | Refuses when | Instead of |
|---|---|---|
madtea pull |
uncommitted changes on tracked files | merging over your edits |
madtea worktree remove |
the worktree is dirty or locked | deleting work (no force override on any madtea surface) |
madt_worktrees action=rebase (agents only) |
the worktree has uncommitted changes | rebasing them into oblivion |
madtea orchestrate |
the checkout is off the default branch or dirty | bisecting through your half-done work |
madtea branch create (in another repo's worktree) |
the tree has modified or staged files | carrying someone else's uncommitted work onto a new branch |
madtea commit (in another repo) |
always - unless it's in a worktree you created and you name the files to commit | sweeping up someone else's edits |
madtea finish --no-commit |
the tree still has uncommitted changes | shipping a PR that silently misses them |
madtea carries no way to force-discard a dirty or locked worktree - not a flag, not an MCP parameter, not a consent dialog. Discarding uncommitted work or overriding a deliberate lock is unrecoverable, so it is simply absent from every madtea surface: madtea worktree remove and madt_worktrees action=remove refuse a dirty or locked tree and name the data at risk. Commit or stash first, or - if the work is genuinely disposable - run raw git worktree remove <path> --force yourself in your own shell. Only a clean, unlocked worktree is removed.
And after finish merges to the default branch, it asks the forge one more question: does that branch have any protection rule? If not, it says so loudly - this merge landed with no required reviews or status checks - and names the fix (madtea repo protect main). Most tools assume somebody already set that up.
Agents can't go identity-shopping
Your disk probably holds a dozen clones, each with a working login. An agent session runs in exactly one of them - but with most tooling, the other eleven are wide open: every checkout is one cd away, every token acts as you on its forge. madtea's boundary protects the two things over there that are yours alone:
- Your working state. Each checkout holds whatever you left in it - the branch you were on, the half-finished edits. The agent never touches another repo's checkout, so none of that can be switched, committed over, or pulled out from under you. When it genuinely needs to change that repo's code, it makes itself a second working copy (
git worktree) and gets full write freedom there, while your copy stays exactly as you left it. - Your other identities. A session gets one login: the repo (or folder of repos) it was started in. Every other clone's stored token is off-limits - a tool call can target another repo, but
madteawill not authenticate as that repo's owner to do it. So one confused or prompt-injected session can't spread into every forge account you hold. - Collaboration still works. Cross-repo, that repo's own login may be used for exactly two jobs, both additive and reviewable: pushing the agent's own worktree branch up as a PR, and filing or commenting on an issue at that repo's tracker. Everything else refuses.
A test lists the only operations allowed to touch another clone's credentials, so a future feature can't quietly widen that list (pinned ADR).
The fence is for agents, not for you: the human CLI runs unrestricted, because you own your disk. The boundary sits exactly where the autonomy is.
The project
Community
madtea is maintained by sixfold.space corp.
- Issues and feature requests: the issue tracker
- Something not working: the troubleshooting guide
- Security reports: SECURITY.md
- Contributing: CONTRIBUTING.md
Update madtea from the CLI
madtea update # fetch the latest release, GPG-verified against a key pinned on first use
madtea update --verify # prove the installed binary still matches its release; repair it if not
The updater refuses anything that fails signature verification; --verify re-hashes the binary on disk against the signed manifest and repairs drift through the same pipeline (how releases are signed).
Build madtea from source
Prerequisites: Go 1.26+ and git. scripts/setup-dev.sh bootstraps Go and the dev tools if you don't have them.
go build -o madtea ./cmd/madtea && ./madtea install
To build without installing, go build ./cmd/madtea alone produces a self-contained madtea binary in the working directory.
Run the tests
go test ./... # the full unit and integration suite
./scripts/gate.sh # the complete verification gate: tests, vet, staticcheck, govulncheck, gosec, docs drift, MCP conformance
gate.sh --fast runs the cheap checks only. The gate is the merge bar - CI runs this same script, so everything it checks is enforced in CI by construction.
Documentation
The docs are browsable online at madtea.lol - the site serves this repo's docs/ tree directly.
Everything is indexed from docs: guides, the safety and credential model, architecture and ADRs, and the auto-generated command reference for every CLI command and MCP tool. Start with Why madtea for the complete feature argument, the CLI comparison for command-by-command coverage against gh, glab, tea, and fj, or check API compatibility for the Forgejo/Gitea platform picture.
Code contributions
Contributions are welcome - see CONTRIBUTING.md for the development workflow, the verification gate, and the conventions the test suite enforces. By contributing code to madtea you agree to license it under the project's license.
madtea is licensed under Apache-2.0 (LICENSE · NOTICE).
Releases are GPG-signed. Security policy: SECURITY.md.
"I want a clean cup," interrupted the Hatter: "let's all move one place on."
Maintained by sixfold.space corp. - infrastructure for AI agents. Same author, same threat model, applied to the developer-tooling layer.