Tea MCP reroll: audit and plan #9

Closed
opened 2026-10-15 00:24:01 +00:00 by teahubxyz · 1 comment
Owner

Audit of both Tea MCPs as of 2026-10-15, with a plan for one smaller one. The operator asked for it, expecting a refactor or golf.

What exists

old Tea MCP (the shell) Teahub MCP (prod)
source matcha/teahub tea_mcp/ on hb-5 teahubxyz/teahub mcp/tea_mcp/server.py
size ~8,500 lines, 24 modules, ~50 tools, 50 test files 408 lines, 1 file, 17 tools, no tests
runs as container teahub-mcp on hb-5 :8090, plus the phast sidecar on :8091 Fly green-tea-mcp, mcp.teahub.xyz
authority mixed: caller tokens, plus a factory service token (oracle), an operator/member gate, shell and agent lanes always the caller's own Gitea token, no service token, nothing deletes
status dead. GITEA_URL=https://matcha.teahub.xyz no longer resolves, PUBLIC_URL=https://mcp.teahub.xyz is prod's now, and nothing public reaches it. The only remaining callers are Boring's boring_teahub_* tools. live. hb-5's Codex config points at it, so Codex needs to re-authorize against the new OAuth.

The old one: keep, port or drop

  • Already on prod: repo, issues, comments, file get/change, actions list, workflow dispatch, repo create.
  • Port (tools that today's work missed):
    • tea_repo_commit: several files in one commit, including binary files such as screenshots.
    • tea_action_jobs / tea_action_logs: job logs. Today they were read through raw admin API calls.
    • chat_tools uploads: binary files.
  • Drop, now done elsewhere:
    • phast sidecar → phast-app
    • persimmons / human vault / juice → persimmons/app (persimmons/app#1)
    • breakglass, domains → Boring
    • pages → green-tea-pages
    • factory milestones and children → Time
  • Drop, contradicts prod's model:
    • oracle: a factory service-token passthrough
    • shell_exec, agent_*, queue, usage: the old Codex worker. Seats now run in Time's Actions.
    • tea_boring_*: the reciprocal lane. Boring deploys through its own workflow in the shell now.
  • Drop, experiments: tea_status_app, signal_dive_app, the avatar and voice modules. They can stay in the shell's history.

Prod MCP: findings

  1. Validation boilerplate is repeated in every tool (number >= 1, page/limit ranges, state in {...}, name regexes): about 60 lines. Annotated[int, Field(ge=1, le=50)], Literal[...] and a RepoName type would let FastMCP validate it and publish the limits in the JSON schema, so clients see them.
  2. tea_issue_create drops unknown label names silently, and only reads the first 50 labels. It should refuse unknown labels, or say which ones it skipped.
  3. tea_file_change decides create vs update by matching "404" in an error string. One get_or_none() helper would fix this, and tea_file_get/tea_tree could use it too.
  4. tea_org_add_member defaults to the Owners team. That's a sharp default for a one-call tool; the team should be required.
  5. Inconsistent defaults: tea_repo_create makes private repos, tea_org_repo_create makes public ones.
  6. Silent truncation: tea_whoami lists only 50 repos, tea_tree caps at 500 entries. Both should say when they truncate.
  7. The CIMD monkeypatch (fastmcp_cimd.ssrf_safe_fetch_response) and the hardcoded Claude client fallback are global patches that a FastMCP upgrade can break. They need a comment explaining why they exist, and a check whether 4.0.10 still needs them.
  8. No tests. One fake-Gitea test file (Time already has the pattern) would cover the helpers and the tools.
  9. Missing tools forced temporary admin tokens. Six tokens were minted on the Fly box on 2026-10-14 (ids 35–40), plus 42 on 2026-10-15, all deleted. They covered archiving repos, webhooks, multi-file and binary pushes, and job logs. As caller-authority tools, these would work for anyone Gitea already allows, with no token minting.

Plan (one file, about 450 lines, about 27 tools)

  • Typed parameters with Annotated/Literal, one get_or_none, a truthful truncated flag. This is the golf, about −80 lines.
  • Add, all as the caller:
    • pulls: tea_pull_get (with diff and status), tea_pull_create, tea_pull_review, tea_pull_merge
    • tea_labels and label/unlabel
    • tea_branch_create
    • tea_commit (multi-file, base64 for binaries)
    • tea_action_logs
    • tea_repo_edit (description, website, archived)
    • tea_hook_create (push hooks for pages)
    • tea_secret_set / tea_variable_set
  • Fix findings 2–7. Add tests/test_mcp.py.
  • Retire the old container teahub-mcp and its phast sidecar on hb-5. Point Boring's boring_teahub_* at green-tea-mcp (Fly status, logs, restart) or remove them; that ships through deploy-boring-runtime.
  • Re-authorize Codex on hb-5 against the new mcp.teahub.xyz.

DONE: one file, typed, tested. Today's chores (archive a repo, add a pages hook, commit a screenshot, read a job log, set a secret) work through the MCP as a signed-in owner, with no admin token minted. The old container is stopped.

Audit of both Tea MCPs as of 2026-10-15, with a plan for one smaller one. The operator asked for it, expecting a refactor or golf. ## What exists | | old Tea MCP (the shell) | Teahub MCP (prod) | |---|---|---| | source | `matcha/teahub` `tea_mcp/` on hb-5 | `teahubxyz/teahub` `mcp/tea_mcp/server.py` | | size | ~8,500 lines, 24 modules, ~50 tools, 50 test files | 408 lines, 1 file, 17 tools, no tests | | runs as | container `teahub-mcp` on hb-5 :8090, plus the phast sidecar on :8091 | Fly `green-tea-mcp`, mcp.teahub.xyz | | authority | mixed: caller tokens, plus a factory service token (`oracle`), an operator/member gate, shell and agent lanes | always the caller's own Gitea token, no service token, nothing deletes | | status | **dead.** `GITEA_URL=https://matcha.teahub.xyz` no longer resolves, `PUBLIC_URL=https://mcp.teahub.xyz` is prod's now, and nothing public reaches it. The only remaining callers are Boring's `boring_teahub_*` tools. | live. hb-5's Codex config points at it, so Codex needs to re-authorize against the new OAuth. | ## The old one: keep, port or drop - **Already on prod:** repo, issues, comments, file get/change, actions list, workflow dispatch, repo create. - **Port** (tools that today's work missed): - `tea_repo_commit`: several files in one commit, including binary files such as screenshots. - `tea_action_jobs` / `tea_action_logs`: job logs. Today they were read through raw admin API calls. - `chat_tools` uploads: binary files. - **Drop, now done elsewhere:** - phast sidecar → `phast-app` - persimmons / human vault / juice → `persimmons/app` (persimmons/app#1) - breakglass, domains → Boring - pages → `green-tea-pages` - factory milestones and children → Time - **Drop, contradicts prod's model:** - `oracle`: a factory service-token passthrough - `shell_exec`, `agent_*`, `queue`, `usage`: the old Codex worker. Seats now run in Time's Actions. - `tea_boring_*`: the reciprocal lane. Boring deploys through its own workflow in the shell now. - **Drop, experiments:** `tea_status_app`, `signal_dive_app`, the avatar and voice modules. They can stay in the shell's history. ## Prod MCP: findings 1. **Validation boilerplate** is repeated in every tool (`number >= 1`, page/limit ranges, `state in {...}`, name regexes): about 60 lines. `Annotated[int, Field(ge=1, le=50)]`, `Literal[...]` and a `RepoName` type would let FastMCP validate it and publish the limits in the JSON schema, so clients see them. 2. **`tea_issue_create` drops unknown label names silently**, and only reads the first 50 labels. It should refuse unknown labels, or say which ones it skipped. 3. **`tea_file_change` decides create vs update by matching `"404"` in an error string.** One `get_or_none()` helper would fix this, and `tea_file_get`/`tea_tree` could use it too. 4. **`tea_org_add_member` defaults to the Owners team.** That's a sharp default for a one-call tool; the team should be required. 5. **Inconsistent defaults:** `tea_repo_create` makes private repos, `tea_org_repo_create` makes public ones. 6. **Silent truncation:** `tea_whoami` lists only 50 repos, `tea_tree` caps at 500 entries. Both should say when they truncate. 7. **The CIMD monkeypatch** (`fastmcp_cimd.ssrf_safe_fetch_response`) and the hardcoded Claude client fallback are global patches that a FastMCP upgrade can break. They need a comment explaining why they exist, and a check whether 4.0.10 still needs them. 8. **No tests.** One fake-Gitea test file (Time already has the pattern) would cover the helpers and the tools. 9. **Missing tools forced temporary admin tokens.** Six tokens were minted on the Fly box on 2026-10-14 (ids 35–40), plus 42 on 2026-10-15, all deleted. They covered archiving repos, webhooks, multi-file and binary pushes, and job logs. As caller-authority tools, these would work for anyone Gitea already allows, with no token minting. ## Plan (one file, about 450 lines, about 27 tools) - [ ] Typed parameters with Annotated/Literal, one `get_or_none`, a truthful `truncated` flag. This is the golf, about −80 lines. - [ ] Add, all as the caller: - pulls: `tea_pull_get` (with diff and status), `tea_pull_create`, `tea_pull_review`, `tea_pull_merge` - `tea_labels` and label/unlabel - `tea_branch_create` - `tea_commit` (multi-file, base64 for binaries) - `tea_action_logs` - `tea_repo_edit` (description, website, archived) - `tea_hook_create` (push hooks for pages) - `tea_secret_set` / `tea_variable_set` - [ ] Fix findings 2–7. Add `tests/test_mcp.py`. - [ ] Retire the old container `teahub-mcp` and its phast sidecar on hb-5. Point Boring's `boring_teahub_*` at `green-tea-mcp` (Fly status, logs, restart) or remove them; that ships through `deploy-boring-runtime`. - [ ] Re-authorize Codex on hb-5 against the new mcp.teahub.xyz. DONE: one file, typed, tested. Today's chores (archive a repo, add a pages hook, commit a screenshot, read a job log, set a secret) work through the MCP as a signed-in owner, with no admin token minted. The old container is stopped.
Author
Owner

Done, except for two things this code can't do on its own (job logs and Codex's sign-in, below).

  • Server: mcp/tea_mcp/server.py (fb8acb0, then fbb1159 = 2.0.1) is one file, 650 lines for 29 tools, all acting as the caller. Parameters are typed, so bad arguments are refused before Gitea is asked. Everything planned is there: pulls get/create/review/merge, tea_labels and tea_label, tea_branch_create, tea_commit (text or base64, up to 50 files), tea_repo_edit (archive included), tea_hook_create, tea_secret_set and tea_variable_set. Findings 1–6 and 8 are fixed; tests are mcp/tests/test_server.py (27, against a fake Gitea). For 7, the two shims now have a comment saying why they exist. I haven't tested whether FastMCP 4.0.10 still needs them.
  • Checked live: every tool ran once against teahub.xyz in a scratch repo and org, both deleted afterwards. That covered a binary commit, an archive, a hook, a secret, a variable, a squash merge (the head branch was kept) and a dispatched workflow that ran on fly-iad-1. The Claude connector kept working through both deploys with no new sign-in.
  • Job logs can't work on prod yet. Gitea 1.24.7 has no API that lists a run's jobs. /actions/runs returns 404 there, which means the old tea_actions never worked. tea_actions now reads /actions/tasks and links each run's page. The shell's Gitea (28.0.0) does have the runs/jobs/logs API, so upgrading prod's Gitea would make tea_action_logs possible.
  • Old container: teahub-mcp on hb-5 (with its Phast sidecar) had been idle since the cutover. It is stopped and set not to restart; the container and its volume are kept.
  • Boring: boring_teahub_* now operate green-tea-mcp (matcha/boring 122f0b3, shipped by deploy-boring-runtime). boring_teahub_deploy shipped 2.0.1 from the exact main SHA, and boring_teahub_status reads that SHA back from the src-<sha> image tag (fresh: true).
  • Codex on hb-5 points at mcp.teahub.xyz, but codex mcp list shows tea (and boring) as "Not logged in". Running codex mcp login tea on the Mac signs it in; it needs the operator's own sign-in.
  • Tokens: 43 and 45 (teahubxyz) were deleted by id. The table now shows only 25 (time), and the API answers 401 to both. The receipt is in docs/decisions.md (a5a6688).
Done, except for two things this code can't do on its own (job logs and Codex's sign-in, below). - **Server:** `mcp/tea_mcp/server.py` (`fb8acb0`, then `fbb1159` = 2.0.1) is one file, 650 lines for 29 tools, all acting as the caller. Parameters are typed, so bad arguments are refused before Gitea is asked. Everything planned is there: pulls get/create/review/merge, `tea_labels` and `tea_label`, `tea_branch_create`, `tea_commit` (text or base64, up to 50 files), `tea_repo_edit` (archive included), `tea_hook_create`, `tea_secret_set` and `tea_variable_set`. Findings 1–6 and 8 are fixed; tests are `mcp/tests/test_server.py` (27, against a fake Gitea). For 7, the two shims now have a comment saying why they exist. I haven't tested whether FastMCP 4.0.10 still needs them. - **Checked live:** every tool ran once against teahub.xyz in a scratch repo and org, both deleted afterwards. That covered a binary commit, an archive, a hook, a secret, a variable, a squash merge (the head branch was kept) and a dispatched workflow that ran on `fly-iad-1`. The Claude connector kept working through both deploys with no new sign-in. - **Job logs can't work on prod yet.** Gitea 1.24.7 has no API that lists a run's jobs. `/actions/runs` returns 404 there, which means the old `tea_actions` never worked. `tea_actions` now reads `/actions/tasks` and links each run's page. The shell's Gitea (28.0.0) does have the runs/jobs/logs API, so upgrading prod's Gitea would make `tea_action_logs` possible. - **Old container:** `teahub-mcp` on hb-5 (with its Phast sidecar) had been idle since the cutover. It is stopped and set not to restart; the container and its volume are kept. - **Boring:** `boring_teahub_*` now operate `green-tea-mcp` (matcha/boring `122f0b3`, shipped by `deploy-boring-runtime`). `boring_teahub_deploy` shipped 2.0.1 from the exact main SHA, and `boring_teahub_status` reads that SHA back from the `src-<sha>` image tag (`fresh: true`). - **Codex on hb-5** points at mcp.teahub.xyz, but `codex mcp list` shows `tea` (and `boring`) as "Not logged in". Running `codex mcp login tea` on the Mac signs it in; it needs the operator's own sign-in. - **Tokens:** 43 and 45 (`teahubxyz`) were deleted by id. The table now shows only 25 (`time`), and the API answers 401 to both. The receipt is in docs/decisions.md (`a5a6688`).
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: teahubxyz/teahub#9