The gitea-workflow documentation named a gitea_edit_issue tool that no
namespace had ever registered. There was no sanctioned MCP path to change an
issue title or body at all: the only edit tool, gitea_edit_pr, PATCHes the
pull-request endpoint. An authorized body correction on issue #780 therefore
had to be recorded as a discussion comment instead.
Add edit_issue.py as the authoritative rule and gitea_edit_issue as the tool
built on it:
- Only the fields the caller names are sent, so labels, state, assignee, and
milestone cannot be overwritten from a stale read.
- A pull-request number is refused. Gitea serves pull requests from the same
/issues/{n} collection, so without that check the issue path would quietly
become a second, ungated PR edit path. gitea_edit_pr stays PR-only.
- Structurally invalid requests raise before any credential or network work;
a request that would change nothing is reported as an explicit no-op with a
next action rather than a silent success.
- Read-after-write proves the applied title/body and proves that state,
labels, assignees, and milestone did not move. Transport failures on the
pre-read, the PATCH, and the read-back are each reported, redacted, with
the correct performed/verified state.
Gates match every other issue mutation: profile permission via the shared
capability map (resolver task edit_issue, gitea.issue.comment), preflight
purity, branches worktree validation, anti-stomp inventory membership, and
audited mutation.
Fix the drift that hid this. docs/mcp-tool-inventory.md is now the canonical
registered-tool list, and mcp_tool_inventory.py compares it to the live
registry in both directions, plus checks that every tool named under skills/
is registered. The new guard immediately found a second instance of the same
defect: gitea_record_pre_review_command had lost its @mcp.tool() decorator
while the canonical review workflow still instructed reviewers to call it, so
that registration is restored.
Validation:
PASSED: venv/bin/python -m pytest tests/test_issue_781_edit_issue_tool.py -s -q
— 50 passed
FAILED: venv/bin/python -m pytest -s -q — 4095 passed, 11 failed, 6 skipped.
The identical 11 tests fail on clean master 8e149e6 with no changes applied
(254 passed, 11 failed across those five files), so they are pre-existing
and proven by a baseline run rather than asserted.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
5.8 KiB
Registered MCP tool inventory
This is the canonical list of tools the Gitea-Tools MCP server registers. It
exists because documentation and the registered inventory drifted: the workflow
documented a gitea_edit_issue tool that no namespace had ever registered, so a
mutation could be planned against a tool that did not exist and only fail at
execution time (#781).
The rule
Documentation must never name a tool an actor cannot reach.
Two guards enforce it, both in tests/test_issue_781_edit_issue_tool.py:
- The list below must equal the registered tool set exactly — sorted, no duplicates, nothing missing in either direction. Adding a tool without documenting it fails, and documenting a tool without registering it fails.
- Every backticked
gitea_*/mcp_*identifier inskills/**/*.mdmust be a registered tool. Module and script names that share the prefix are listed explicitly inmcp_tool_inventory.NON_TOOL_IDENTIFIERSrather than being waved through by a looser pattern.
Updating this file
When you add or remove an @mcp.tool(), regenerate the block below:
PYTEST_CURRENT_TEST=1 venv/bin/python -c "
import mcp_server, mcp_tool_inventory
print(mcp_tool_inventory.render_inventory_block(
mcp_server.mcp._tool_manager._tools))
"
Replace everything between the markers with that output. Do not hand-edit individual entries — the generator and the guard share one ordering rule.
Registered tools
Namespaces (gitea-tools, gitea-reviewer, gitea-merger, gitea-reconciler)
register the same tool set; what differs per namespace is the execution profile
that gates each call, not which tools exist.
gitea_abandon_workflow_leasegitea_acquire_conflict_fix_leasegitea_acquire_merger_pr_leasegitea_acquire_reviewer_pr_leasegitea_activate_profilegitea_adopt_merger_pr_leasegitea_adopt_workflow_leasegitea_allocate_next_workgitea_assess_already_landed_reconciliationgitea_assess_conflict_fix_classificationgitea_assess_conflict_fix_pushgitea_assess_gitea_operation_pathgitea_assess_master_paritygitea_assess_mcp_namespace_healthgitea_assess_pr_sync_statusgitea_assess_review_merge_state_machinegitea_assess_reviewer_pr_leasegitea_assess_terminal_label_hygienegitea_assess_work_issue_duplicategitea_assess_worktree_cleanup_integritygitea_audit_configgitea_audit_stable_branch_contaminationgitea_audit_worktree_cleanupgitea_authorize_reconciliation_cleanup_phasegitea_authorize_review_correctiongitea_capability_stop_terminal_reportgitea_capture_branches_worktree_snapshotgitea_check_pr_eligibilitygitea_cleanup_merged_pr_branchgitea_cleanup_obsolete_reviewer_comment_leasegitea_cleanup_post_merge_moot_leasegitea_cleanup_stale_claimsgitea_cleanup_stale_review_decision_lockgitea_cleanup_terminal_pr_labelsgitea_close_issuegitea_commit_filesgitea_consume_irrecoverable_decision_lock_provenancegitea_create_issuegitea_create_issue_commentgitea_create_labelgitea_create_prgitea_delete_branchgitea_diagnose_review_decision_lockgitea_diagnose_reviewer_pr_lease_handoffgitea_diagnose_terminalgitea_dry_run_pr_reviewgitea_edit_issuegitea_edit_prgitea_expire_workflow_leasesgitea_get_authenticated_usergitea_get_current_usergitea_get_filegitea_get_pr_review_feedbackgitea_get_profilegitea_get_runtime_contextgitea_get_shell_healthgitea_heartbeat_reviewer_pr_leasegitea_inspect_workflow_leasegitea_issue_irrecoverable_provenance_authorizationgitea_list_issue_commentsgitea_list_issuesgitea_list_labelsgitea_list_profilesgitea_list_prsgitea_list_workflow_leasesgitea_load_review_workflowgitea_lock_issuegitea_mark_final_review_decisiongitea_mark_issuegitea_merge_prgitea_mirror_refsgitea_observability_link_issuegitea_observability_list_projectsgitea_observability_reconcile_incidentgitea_post_heartbeatgitea_quarantine_contaminated_reviewgitea_reclaim_expired_workflow_leasegitea_reconcile_already_landed_prgitea_reconcile_issue_claimsgitea_reconcile_merged_cleanupsgitea_reconcile_superseded_by_merged_prgitea_record_irrecoverable_decision_lock_provenancegitea_record_pre_review_commandgitea_record_shell_spawn_outcomegitea_record_stable_branch_push_attemptgitea_release_merger_pr_leasegitea_release_reviewer_pr_leasegitea_release_workflow_leasegitea_resolve_task_capabilitygitea_resume_review_draftgitea_review_prgitea_route_task_sessiongitea_save_review_draftgitea_scan_already_landed_open_prsgitea_sentry_get_issue_eventsgitea_sentry_link_gitea_issuegitea_sentry_list_issuesgitea_sentry_reconcile_issuegitea_sentry_watchdoggitea_set_issue_labelsgitea_submit_pr_reviewgitea_update_pr_branch_by_mergegitea_validate_review_final_reportgitea_view_issuegitea_view_prgitea_whoamigitea_workflow_dashboardmcp_check_workflow_skill_preflightmcp_get_control_plane_guidemcp_get_skill_guidemcp_list_project_skills
Issue-content editing
gitea_edit_issue is the only path that changes an issue's title or body. It
PATCHes the issue endpoint, refuses a pull-request number, sends only the fields
the caller named, and proves the result by read-after-write — including that
state, labels, assignees, and milestone did not move.
gitea_edit_pr remains pull-request-only. The two paths never merge: a single
tool that accepted either kind would make the narrower capability reachable
through the wider one.