feat(mcp): wire entry-point provider plugins into the MCP server#100
Merged
Conversation
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>
# Conflicts: # CHANGELOG.md
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds the missing MCP boundary so a third-party provider discovered via the
mureo.providersentry-point group can surface its operations asmcp__mureo__*tools. Until now the registry (P1-07) discovered providers andthe skill matcher (P1-08) gated them by capability, but a discovered provider
had no way to be exposed as MCP tools —
mureo/mcp/server.pybuilt a statictool 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:
zero MCP code; matches the long-term vision but large, and cannot expose
platform-specific operations outside the Protocol).
MCPToolProvider(
mcp_tools()+async handle_mcp_tool()), mirroring the shape every built-inplatform 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 owndocstring mandates extending via secondary Protocols rather than expanding it —
B follows that.
What changed
mureo/mcp/tool_provider.py(new):MCPToolProviderruntime-checkableProtocol +
collect_plugin_tools()— entry-point discovery, no-arg construct,isinstance gate, async-handler gate, per-plugin fault isolation (incl.
SystemExit;KeyboardInterruptre-raised), built-ins win on name collision,first-wins on plugin-vs-plugin collision.
mureo/mcp/server.py: purely additive —reserved_names= union ofall built-in tool-name frozensets,
_ALL_TOOLS.extend(plugin tools), onedispatch branch after all built-in families. No plugins means byte-identical
behaviour to before.
Safety / regression
radius — the additive guarantee is locked by a dedicated
test_no_plugins_is_additive_no_op.precedence; covered end-to-end).
(construct /
mcp_tools()/ total-discovery faults each isolated).ruffclean;mypyclean for the two changed source files (a pre-existingunrelated
search_console/client.py:69unused-ignore is out of scope).(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 rejectiontests/test_mcp_server_plugin_wiring.py— list/dispatch, built-insintact, unknown-tool raises, real-core-tool shadow rejected, additive no-op
gh pr checks) — pending on this PRCompanion (private, separate repo)
logly/mureo-logly-bridgealready implements theMCPToolProvidersurface(commit
17dbf6c). It needs no further change once this lands — it was builtstructurally against this contract. No OSS reference to it (verified by
git grep).🤖 Generated with Claude Code