Skip to content

feat(mcp): wire entry-point provider plugins into the MCP server#100

Merged
hyoshi merged 3 commits into
mainfrom
feat/plugin-mcp-wiring
May 16, 2026
Merged

feat(mcp): wire entry-point provider plugins into the MCP server#100
hyoshi merged 3 commits into
mainfrom
feat/plugin-mcp-wiring

Conversation

@hyoshi
Copy link
Copy Markdown
Collaborator

@hyoshi hyoshi commented May 16, 2026

Summary

Adds the missing MCP boundary so a third-party provider discovered via the
mureo.providers entry-point group can surface its operations as
mcp__mureo__* tools. Until now the registry (P1-07) discovered providers and
the skill matcher (P1-08) gated them by capability, but a discovered provider
had no way to be exposed as MCP toolsmureo/mcp/server.py built a static
tool list and never called registry.discover().

Single commit off main (93282d5). Not for self-merge — opening for review.

Design decision (the architecture call)

Two designs were considered:

  • A — auto-generate MCP tools from the domain Protocol (plugin authors write
    zero MCP code; matches the long-term vision but large, and cannot expose
    platform-specific operations outside the Protocol).
  • B (this PR) — an opt-in secondary Protocol MCPToolProvider
    (mcp_tools() + async handle_mcp_tool()), mirroring the shape every built-in
    platform module already uses (TOOLS + async handle_tool).

B was chosen as the pragmatic, industry-standard plugin pattern (pytest /
Flask / setuptools style). It is strictly more flexible for platform-specific
APIs and does not preclude A as a later evolution. BaseProvider's own
docstring mandates extending via secondary Protocols rather than expanding it —
B follows that.

What changed

  • mureo/mcp/tool_provider.py (new): MCPToolProvider runtime-checkable
    Protocol + collect_plugin_tools() — entry-point discovery, no-arg construct,
    isinstance gate, async-handler gate, per-plugin fault isolation (incl.
    SystemExit; KeyboardInterrupt re-raised), built-ins win on name collision,
    first-wins on plugin-vs-plugin collision.
  • mureo/mcp/server.py: purely additivereserved_names = union of
    all built-in tool-name frozensets, _ALL_TOOLS.extend(plugin tools), one
    dispatch branch after all built-in families. No plugins means byte-identical
    behaviour to before.

Safety / regression

  • 938 tests pass across the mcp/provider/registry/skill/adapter blast
    radius — the additive guarantee is locked by a dedicated
    test_no_plugins_is_additive_no_op.
  • A plugin cannot shadow a core tool (reserved-set drop + built-in dispatch
    precedence; covered end-to-end).
  • A broken/malicious plugin cannot crash the server or starve siblings
    (construct / mcp_tools() / total-discovery faults each isolated).
  • ruff clean; mypy clean for the two changed source files (a pre-existing
    unrelated search_console/client.py:69 unused-ignore is out of scope).
  • python-reviewer: APPROVE, 0 CRITICAL/HIGH; flagged MEDIUM/LOW
    (BaseException isolation, end-to-end shadow test, first-wins identity,
    sync-handler rejection) all addressed in this commit with regression tests.

Test plan

  • tests/test_mcp_tool_provider.py — discovery, collision, dedupe,
    isolation (incl. SystemExit), sync-handler rejection
  • tests/test_mcp_server_plugin_wiring.py — list/dispatch, built-ins
    intact, unknown-tool raises, real-core-tool shadow rejected, additive no-op
  • 938-test blast-radius regression, ruff, mypy
  • CI (gh pr checks) — pending on this PR

Companion (private, separate repo)

logly/mureo-logly-bridge already implements the MCPToolProvider surface
(commit 17dbf6c). It needs no further change once this lands — it was built
structurally against this contract. No OSS reference to it (verified by
git grep).

🤖 Generated with Claude Code

hyoshi and others added 3 commits May 16, 2026 12:36
Adds the missing MCP boundary so a third-party provider discovered
via the `mureo.providers` entry-point group can surface its operations
as `mcp__mureo__*` tools. Opt-in secondary Protocol (MCPToolProvider)
per BaseProvider's "extend via secondary Protocols" rule — a provider
that doesn't implement it is still discovered/skill-matched, just not
exposed (graceful).

- mureo/mcp/tool_provider.py: MCPToolProvider Protocol + collect_plugin_tools()
  (entry-point discovery, no-arg construct, isinstance gate, async-handler
  gate, per-plugin fault isolation incl. SystemExit, built-ins-win name
  collision, plugin-vs-plugin first-wins)
- mureo/mcp/server.py: purely additive — extends _ALL_TOOLS and adds a
  plugin dispatch branch after all built-in families. No plugins => no-op.
- Tests: isolation, collision, dedupe, sync-handler rejection, end-to-end
  core-tool-shadow rejection, additive no-op regression guard.

Verified: 938 mcp/provider/registry/skill/adapter tests pass (additive
guarantee), ruff + (this module) mypy clean. python-reviewer: APPROVE,
0 CRITICAL/HIGH; flagged MEDIUM/LOW addressed in this commit.

Branch off main (93282d5); not merged. Companion private bridge gains
its MCPToolProvider adapter separately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Companion docs for the plugin→MCP wiring (same PR). Closes the doc gap
flagged in review: a new public extension point shipped without docs.

- plugin-authoring.md: corrected the now-false "cannot do: MCP tool
  auto-exposure" bullet (it referred to the absence this PR fills) and
  added §3 "Exposing operations as MCP tools (MCPToolProvider)" — the
  opt-in contract, the server-enforced rules (no-arg construct,
  static/credential-free mcp_tools, async handler, namespacing,
  built-ins-win, arg-validation), and the Design-B rationale.
- ABI-stability.md: MCPToolProvider added to the §1 stable surface;
  §12 "MCP tool surface" caveat refined to distinguish the stable
  Protocol from the (non-ABI) built-in tool list.
- mcp-server.md: "Plugin-Provided Tools" section (additive,
  collision/dedupe, fault isolation, discovered-once).
- CHANGELOG.md: Unreleased entry.

Docs only — no code change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@hyoshi hyoshi merged commit f721243 into main May 16, 2026
8 checks passed
@hyoshi hyoshi deleted the feat/plugin-mcp-wiring branch May 16, 2026 04:15
hyoshi added a commit that referenced this pull request May 16, 2026
A few early commits (e.g. PR #100) carried a
`Co-Authored-By: Claude … <noreply@anthropic.com>` trailer, which
made GitHub's contributors graph list a spurious "claude"
contributor. .mailmap canonicalizes that identity to the maintainer
so shortlog / blame / the GitHub contributors graph no longer show
it. No history is rewritten; tags, releases (v0.9.0) and PyPI are
untouched. Future attribution is separately disabled via
~/.claude/settings.json (includeCoAuthoredBy: false).
hyoshi added a commit that referenced this pull request May 16, 2026
The .mailmap was added (#107) to drop the spurious "claude"
contributor, but GitHub does not remap Co-authored-by trailer
attribution via .mailmap, so it did not achieve its purpose. The
single legacy co-author trailer on #100 is accepted as-is; future
attribution is prevented at the source via ~/.claude/settings.json
(includeCoAuthoredBy: false + attribution commit/pr empty). Removing
the dead file.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant