Skip to content

tech-debt(mcp): four hand-maintained bridge lists can drift — adding manual_mcp touched three of four (#14586) #14631

Closed
0 of 1 issue completed

Description

@mrveiss

Summary

Registering manual_mcp as the twelfth MCP bridge (#14586) required editing
three separately hand-maintained lists of "every governed bridge", and a
fourth — a test asserting an exact set — caught the omission only because it
happened to use == rather than a subset/superset check. This is the same
canonical-source-duplication shape umbrella #13916 tracks (see also its
worked example #13512, and the more recently filed #14584).

The four lists

  1. autobot_shared/auth/mcp_bridge_scan.py — bridge_files() globs
    autobot-backend/api/*_mcp.py + *_mcp/tools.py directly off the
    filesystem, filtered by EXCLUDED_BRIDGE_STEMS (a table, post-security(mcp): manual_mcp exposes three MCP tools with no permission check and is excluded from the bridge scan by name #14586).
  2. autobot-backend/api/mcp_registry.py — _BRIDGE_MODULE_REGISTRY
    (module path, name, /mcp/tools endpoint, feature-tag list per bridge),
    consulted by discover_bridges()'s module-scan fallback and mirrored into
    MCP_BRIDGES at import time.
  3. tools/lint/check_mcp_tool_permission_coverage.py —
    PER_BRIDGE_DISCOVERY_FLOOR, a bridge-name-keyed dict of expected
    per-bridge tool counts.
  4. autobot-backend/tests/unit/api/test_mcp_plugin_discovery.py —
    test_discover_bridges_known_names's expected set, a literal list of
    bridge names compared with == against discover_bridges()'s output.

Adding manual_mcp required edits to #1 (remove exclusion + make its tool
schemas parseable), #2 (add the registry tuple — without it discover_bridges()
never sees the bridge, regardless of #1), and #3 (add its floor, bump the
aggregate). #4 was not touched proactively; CI caught it because the test
happens to assert exact equality rather than "every bridge in the scan is a
subset of expected" — a shard failure, not a design decision, is what
surfaced it (see PR #14627 discussion on #14586).

Recommendation: mcp_bridge_scan.py's filesystem glob is the natural SSOT

It is the only one of the four that cannot silently disagree with
reality
— it reads the actual files on disk rather than a name someone
remembered to type into a second place. The other three should assert
against it or derive from it, not duplicate its membership by hand:

  • List Dev new gui #4 (test literal) is pure duplication and should be deleted.
    test_discover_bridges_known_names doesn't need a hardcoded set at all —
    it can assert names == {bridge_name(p) for p in mcp_bridge_scan.bridge_files()} directly. This removes the fourth
    copy entirely rather than requiring it to be kept in sync.
  • List Docs improvements #2 (_BRIDGE_MODULE_REGISTRY) carries genuine extra metadata
    (endpoint URL, feature tags) the glob cannot supply, so it cannot be
    deleted outright — but every governed bridge that has a MANIFEST (all
    twelve do, after security(mcp): manual_mcp exposes three MCP tools with no permission check and is excluded from the bridge scan by name #14586) already self-describes name, description and
    endpoint. discover_bridges()'s module-scan fallback could iterate
    mcp_bridge_scan.bridge_files() directly, import each module, and read
    its MANIFEST, rather than walking a separately hand-listed
    (module_path, name, endpoint, features) tuple per bridge. That would
    shrink _BRIDGE_MODULE_REGISTRY to a manually curated features list
    keyed by bridge name (metadata the glob genuinely cannot derive), with
    membership and endpoint both sourced from the scan + each bridge's own
    MANIFEST.
  • List Fix frontend errors and remove unused code #3 (PER_BRIDGE_DISCOVERY_FLOOR) is a deliberate snapshot, not a
    live count
    — floors exist specifically to lag reality so a regression is
    detectable, so its values should stay hand-set. But its key set should
    be asserted against the glob: today, a new bridge with no floor entry is
    invisible to bridge_discovery_gaps() (which only iterates
    PER_BRIDGE_DISCOVERY_FLOOR.items()), and would likely still pass the
    aggregate DISCOVERY_FLOOR check since new tools only ever raise the sum.
    That is exactly the kind of silent gap manual_mcp sat in for as long as
    it did. A guard asserting set(PER_BRIDGE_DISCOVERY_FLOOR) == {bridge_name(p) for p in bridge_files()} would have failed loudly the
    moment manual_mcp was un-excluded from the scan, before its floor was
    added by hand.

Suggested scope (separate PR, per #14586's review)

  • Replace test_mcp_plugin_discovery.py's literal expected set with a
    comparison against mcp_bridge_scan.bridge_files().
  • Add a guard (pytest + the required-check script) asserting
    set(PER_BRIDGE_DISCOVERY_FLOOR) == {bridge_name(p) for p in bridge_files()} — every governed bridge has a floor, no orphaned floor
    entries either.
  • Investigate deriving _BRIDGE_MODULE_REGISTRY's module discovery from
    mcp_bridge_scan.bridge_files() + each bridge's MANIFEST, keeping
    only the feature-tag metadata hand-maintained.
  • Land each change with its own guard test, per Umbrella: canonical-source duplication — concepts with no single definition, where the copies have already drifted #13916's stated
    acceptance criterion ("every consolidation lands with a guard test...
    without it the count regrows").

References

Activity

  1. modified the milestones: Backlog, v0.12.0 on Sep 12, 2026
  2. 7 remaining items

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions