bug(parity): the CLI redirect steers to MCP tools that cannot reach the same target (madt_labels has no dir=) #485

Open
opened 2026-09-14 17:03:31 +00:00 by hexajon · 2 comments
Owner

What happens

I work from an agent session whose MCP server is anchored to forge A. I have a local clone of a repo on forge B. I want that repo's label list.

  • madt_labels action=list owner_repo=<owner>/<repo> returns 404. owner_repo has no host (ParseOwnerRepo, internal/util/repo.go:67), so the lookup goes to forge A.
  • madt_labels has no dir=, so I cannot point it at the forge-B clone.
  • cd <forge-B clone> && madtea labels list would work, because the CLI resolves forge B from the clone's origin. But the plugin hook hooks/scripts/check-git-cli.sh denies it and steers to madt_labels (labels -> madt_labels at line 43, steer set at lines 199-203).

So the hook steers to a tool that cannot do the job, and no sanctioned path is left. The hook's own rule (line 198) is "Steer only verbs with a one-call MCP equivalent". For this target, madt_labels is not an equivalent.

madt_repos and madt_api_call have the same gap: no dir=. madtea org-labels list <org> still works, but only because org-labels is outside the steer set (line 57).

Ask

Audit every steered verb (lines 200-202) for capability parity. #441 and #444 cover name parity. This issue covers reach.

  1. For each verb, list what the CLI can reach and what the MCP tool can reach: the actions, and the targeting (dir= to a clone, a clone on another forge, owner_repo=).
  2. For each gap, pick one fix. Either give the MCP tool dir= (reads, and writes within the foreign-repo rules), or make the hook stay silent (or print a remedy that works) when the CLI target is a clone whose forge is not the session's forge.
  3. Add a check so a steered verb cannot ship without an MCP equivalent that reaches the same targets. It could extend the parity test from #444.

Acceptance

  • A table with one row per steered verb: CLI reach, MCP reach, gap, fix.
  • I can list the labels of a repo on another forge, from its local clone, through one sanctioned surface.
  • The hook never steers a call to a tool that cannot reach that call's target.
  • #468: clone-free targeting of another forge. This issue is about tools that cannot use the clone even when it exists.
  • #441 and #444: name parity between the CLI and MCP.
  • #460: status --json .host uses the cwd origin.
## What happens I work from an agent session whose MCP server is anchored to forge A. I have a local clone of a repo on forge B. I want that repo's label list. - `madt_labels action=list owner_repo=<owner>/<repo>` returns `404`. `owner_repo` has no host (`ParseOwnerRepo`, `internal/util/repo.go:67`), so the lookup goes to forge A. - `madt_labels` has no `dir=`, so I cannot point it at the forge-B clone. - `cd <forge-B clone> && madtea labels list` would work, because the CLI resolves forge B from the clone's origin. But the plugin hook `hooks/scripts/check-git-cli.sh` denies it and steers to `madt_labels` (`labels -> madt_labels` at line 43, steer set at lines 199-203). So the hook steers to a tool that cannot do the job, and no sanctioned path is left. The hook's own rule (line 198) is "Steer only verbs with a one-call MCP equivalent". For this target, `madt_labels` is not an equivalent. `madt_repos` and `madt_api_call` have the same gap: no `dir=`. `madtea org-labels list <org>` still works, but only because `org-labels` is outside the steer set (line 57). ## Ask Audit every steered verb (lines 200-202) for capability parity. #441 and #444 cover name parity. This issue covers reach. 1. For each verb, list what the CLI can reach and what the MCP tool can reach: the actions, and the targeting (`dir=` to a clone, a clone on another forge, `owner_repo=`). 2. For each gap, pick one fix. Either give the MCP tool `dir=` (reads, and writes within the foreign-repo rules), or make the hook stay silent (or print a remedy that works) when the CLI target is a clone whose forge is not the session's forge. 3. Add a check so a steered verb cannot ship without an MCP equivalent that reaches the same targets. It could extend the parity test from #444. ## Acceptance - A table with one row per steered verb: CLI reach, MCP reach, gap, fix. - I can list the labels of a repo on another forge, from its local clone, through one sanctioned surface. - The hook never steers a call to a tool that cannot reach that call's target. ## Related - #468: clone-free targeting of another forge. This issue is about tools that cannot use the clone even when it exists. - #441 and #444: name parity between the CLI and MCP. - #460: `status --json .host` uses the cwd origin.
Author
Owner

Another live case, 2026-09-15. In a clone of a repo on a second forge (Gitea 1.27.3), I wanted that repo's Actions runs and job list.

  • madt_actions has no dir=, so it only reaches the session's forge.
  • madt_api_call has no dir= either.
  • madtea actions ... in the clone was denied by the plugin hook, which steered to madt_actions: "Manage CI/Actions with madt_actions instead of madtea actions".

Only madtea api GET ... in the clone worked, because api is outside the steer set. actions belongs in the capability audit with labels.

Another live case, 2026-09-15. In a clone of a repo on a second forge (Gitea 1.27.3), I wanted that repo's Actions runs and job list. - `madt_actions` has no `dir=`, so it only reaches the session's forge. - `madt_api_call` has no `dir=` either. - `madtea actions ...` in the clone was denied by the plugin hook, which steered to `madt_actions`: "Manage CI/Actions with madt_actions instead of `madtea actions`". Only `madtea api GET ...` in the clone worked, because `api` is outside the steer set. `actions` belongs in the capability audit with `labels`.
Author
Owner

Two more live cases, 2026-09-28.

  1. releases: from a session anchored to forge A, I wanted the release asset download counts of a repo on forge B. madt_releases has no dir= and no host, so owner_repo=OWNER/REPO resolves on forge A. madtea release list in the forge-B clone was denied by the plugin hook, which steered to madt_releases: "Manage releases with madt_releases instead of madtea release". Only an anonymous read of forge B's public API worked.

  2. files: madt_files action=get owner_repo=OWNER/REPO reads from the forge of the session's working directory. The host comes from config.GetConfig (internal/mcp/helpers.go:801-809), and owner_repo only fills /repos/OWNER/REPO. From a session anchored to forge B, the same call aimed at a mirror on forge A fails. The clone shorthand has the same shape (internal/service/git/clone.go:178-185). A full clone URL works, because it names its own host (clone.go:49-52).

releases and files belong in the capability audit with labels and actions.

Two more live cases, 2026-09-28. 1. `releases`: from a session anchored to forge A, I wanted the release asset download counts of a repo on forge B. `madt_releases` has no `dir=` and no host, so `owner_repo=OWNER/REPO` resolves on forge A. `madtea release list` in the forge-B clone was denied by the plugin hook, which steered to `madt_releases`: "Manage releases with madt_releases instead of `madtea release`". Only an anonymous read of forge B's public API worked. 2. `files`: `madt_files action=get owner_repo=OWNER/REPO` reads from the forge of the session's working directory. The host comes from `config.GetConfig` (`internal/mcp/helpers.go:801-809`), and `owner_repo` only fills `/repos/OWNER/REPO`. From a session anchored to forge B, the same call aimed at a mirror on forge A fails. The clone shorthand has the same shape (`internal/service/git/clone.go:178-185`). A full clone URL works, because it names its own host (`clone.go:49-52`). `releases` and `files` belong in the capability audit with `labels` and `actions`.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
sixfold-space/madtea#485
No description provided.