Build1 publisher2 min readPublished
MCP's Python SDK refuses to start when two extensions claim the same method name
A duplicate method name raises ValueError while MCPServer is being constructed. That puts the failure inside whatever code assembles your extensions, and it is why a collision test cannot live in either extension's own package.
The Engineer · Build desk

What happened
- The MCP Python SDK's extension documentation defines three safeguards: extensions cannot claim core methods, duplicate extension methods are rejected at registration, and every binding must declare a supported protocol version.
- In the post's sample, one extension registers and starts normally, and a second extension claiming the same method makes MCPServer construction raise ValueError.
- The sample pins mcp==2.1.1 and targets protocol version 2026-07-28, the version announced in the project's final MCP release post.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint Because extension handlers land in the same dispatch table as the rest of the server, adding an optional extension package means knowing every method name the other packages already own.
- decision The collision only surfaces where extensions are composed, so the ownership test belongs at the composition root rather than in either extension's own suite.
- capability A failure at construction gives you a regression test that survives someone shuffling the extension list, since order can no longer decide which handler answers.
- exposure The guarantees here belong to mcp==2.1.1, so teams running MCP servers on other SDKs have nothing in this evidence telling them their registry rejects a duplicate at all.
Two of the three guards look at one binding at a time. The core-method guard fires when `MethodBinding` is constructed, so a vendor package that tries to bind `tools/list` fails on that object [5]. An empty `protocol_versions` frozenset describes a method that cannot be used under any protocol version, and the SDK rejects it immediately [6]. An extension author can cover both of those inside their own package's tests [2].
The duplicate check cannot live there. In the sample, `CatalogSearch` and `ShadowSearch` each return the same binding, and neither class is invalid on its own; the `ValueError` arrives only when both are handed to one `MCPServer` [9][4]. That is an argument about where a test goes, not about how strict the SDK is. The post's author keeps ownership tests beside the composition root where optional packages are assembled [16].
The counterfactual is what makes ordering the issue. "MCP Python SDK extension method collisions are configuration defects, not runtime edge cases," the author wrote [14]. Under a last-write-wins registry, the handler that answers `com.example/catalog.search` would depend on extension order, and reordering configuration could silently change request behaviour without changing the client call [3]. Refusing to start takes order out of the question: swapping the two classes cannot turn the failure into success [10].
The positive path is the part I would copy. One valid configuration and three invalid ones sit in the same runnable sample, four in total [11][1], and the valid case goes through the SDK's in-memory client, so it opens no port and needs no external MCP host [11]. The handler returns `["mcp-0", "mcp-1"]` [12]. "Without that control case, a test could pass simply because every extension path was broken," the author wrote [13].
What the guards buy you depends on the list you pass to the constructor. The duplicate check runs during `MCPServer` construction, so it sees exactly the extensions given to that call and nothing discovered later [3]. Method names are public contract here because an extension adds behaviour to the same dispatch table the rest of the server uses [2], and the reverse-domain prefix is the mechanism that keeps a vendor name clear of core MCP names [8]. The sample pins `mcp==2.1.1` and targets protocol version `2026-07-28`, announced in the project's final MCP release post, so the checks are reproducible [7]. It reports nothing about the registries in any other MCP SDK, so a TypeScript or Go server's behaviour on the same collision would have to be read out of those docs.
What to watch
- Whether the duplicate and core-method guards behave the same way in the non-Python MCP SDKs; the sample covers only mcp==2.1.1.
- Whether protocol version 2026-07-28 stays the pinned target for extension bindings after the project's final MCP release post.
- Whether extensions registered outside the MCPServer constructor hit the same duplicate check; the sample only exercises the constructor path.