MCPResilience
Enables MCP clients to seamlessly operate across legacy and modern protocol versions, auto-detecting the client's era on the first request and supporting the experimental Tasks extension with full compatibility.
README
🛡️ MCPResilience
A Resilient, Spec-Compliant MCP Server Built on the Official SDK v2
Speak MCP however your client speaks it. MCPResilience auto-detects legacy and modern protocol eras on the very first request — and survives the difference.
🔌 Client Modes
MCPResilience auto-detects which protocol era a connecting client speaks — no configuration needed:
- ⚡ Modern Stateless Clients — clients whose first request carries the
_metaenvelope (io.modelcontextprotocol/protocolVersion+clientInfo) skip the handshake entirely.tools/callcan be their very first message. - 🤝 Legacy Handshake Clients — clients without that envelope are routed through the traditional
initializeflow, with-32600 Invalid request parametersenforced on anything sent beforeinitializecompletes.
See Protocol Support for the full breakdown of both eras.
🧠 What This Is
MCPResilience exists because swapping a hand-rolled MCP server for the official SDK isn't a drop-in change — the wire format shifts in ways that break naive migrations. This project tackles that in two stages:
- SDK Migration — replace a hand-rolled MCP server core with the official MCP SDK v2, targeting the
2026-07-28spec, to gain a stateless core and type-safe Pydantic serialization. - Compatibility Hardening — make sure the migration doesn't silently drop support for clients still using the legacy handshake, lose data to upstream schema gaps, or break the experimental Tasks extension mid-flight.
Both stages are documented honestly below, including the one upstream SDK bug that surfaced along the way.
📊 Key Results
All Phase 5 compatibility tests and Phase 6 benchmarks pass, end-to-end, on the official MCP SDK v2 — with full support for both protocol eras and the experimental Tasks extension, plus one upstream SDK bug identified and patched (see Known SDK Quirk).
Tasks extension: what changed under the SDK migration
| Aspect | Legacy behavior | SDK v2 behavior |
|---|---|---|
| Declaring task support | Boolean longRunning: true flag |
execution object, e.g. execution: {"taskSupport": "required"} |
| Task handle location | Top-level taskHandle in result |
Moved to metadata envelope: result._meta.taskHandle |
| Terminal success state | "succeeded" |
"completed" |
| Task content delivery | Returned via tasks/get polling |
Delivered only via the tools/call response stream — tasks/get returns status metadata only (statusMessage, createdAt, etc.) |
| Re-cancelling a finished task | {cancelled: true} or -32602 error |
Idempotent — returns CancelTaskResult with status: "cancelled" |
🏗️ How It Works
Incoming connection
│
▼
First request received
│
▼
Does it carry the _meta envelope?
(protocolVersion + clientInfo)
│
┌────┴────┐
Yes No
│ │
▼ ▼
Modern Era Legacy Era
(stateless) (handshake required)
│ │
▼ ▼
tools/call initialize → any request
runs (initialize enforced,
immediately notifications/initialized
not blocked)
│ │
└─────┬─────┘
▼
Era locked for the
life of the connection
📡 Protocol Support
Stateless Era (2026-07-28)
Under the modern spec, the traditional initialize → notifications/initialized handshake is obsolete. The server runs a serve_dual_era_loop:
- If the first request includes the
_metaenvelope withio.modelcontextprotocol/protocolVersionandio.modelcontextprotocol/clientInfo, the server locks into the modern stateless era. - Clients can send
tools/callas their very first request — noinitializecall needed.
Legacy Era
If the first request lacks the modern _meta envelope, the server locks into the legacy era:
- Any request sent before
initialize(e.g.tools/call) is rejected with-32600 Invalid request parameters. - Once
initializehas been answered, the server does not wait fornotifications/initializedbefore processing further requests.
Version Mismatch Handling
Modern requests specifying an unsupported protocol version in the _meta envelope are rejected cleanly with -32022 Unsupported protocol version — the connection itself is preserved rather than dropped.
🧩 Tasks Extension Deep Dive
The experimental Tasks extension underwent the most wire-format churn of anything in the migration (see the comparison table in Key Results). Two behaviors are worth calling out specifically:
tasks/getis metadata-only now. Task content is delivered exclusively through thetools/callresponse stream; pollingtasks/getwill only ever return status fields likestatusMessageandcreatedAt— never the payload itself.- Cancellation is idempotent by design. Re-cancelling a task that's already
completedorcancelledreturns a successfulCancelTaskResultrather than an error, unlike the legacy server's-32602on repeat cancellation.
Known SDK Quirk
SDK Issue #2156 — execution field stripped from tools/list. The current Pydantic schema for v2026_07_28.Tool doesn't define the experimental execution field, so serialize_server_result silently strips it from tools/list responses.
Workaround: a targeted monkeypatch on mcp_types.methods.serialize_server_result intercepts the validated output and restores the execution dictionary from the original handler data. This is a stopgap — remove it once the upstream schema ships the field natively.
🔧 Technical Notes (the parts that weren't trivial)
- Era detection happens exactly once, on the first request. There's no mid-connection upgrade path — a client that opens without the
_metaenvelope stays in the legacy era for the life of that connection, even if it starts sending modern-shaped requests later. - The task handle didn't just move, its contract changed. Relocating
taskHandlefrom the top-levelresulttoresult._metaalso freed up the top-levelresultobject to be reserved purely for immediate content output and theisErrorflag — a cleaner separation than the legacy shape allowed. - The monkeypatch is scoped narrowly on purpose. It only intercepts
serialize_server_resultto restore one missing field, rather than forking or wrapping the SDK's schema wholesale — keeping the patch easy to delete the moment upstream ships a fix.
🛠️ Tech Stack
- Protocol: JSON-RPC 2.0 over the Model Context Protocol, spec
2026-07-28 - SDK: Official MCP SDK v2 — Pydantic-based schema validation & serialization
- Server core: Python, stateless-first request handling (
serve_dual_era_loop) - Testing: Phase 5 compatibility suite + Phase 6 benchmark run
🚀 Getting Started
git clone https://github.com/HoorShumail/MCPResilience.git
cd MCPResilience
pip install -r requirements.txt
Adjust the commands above to match your actual package layout and entry point.
Run the compatibility suite and benchmarks with:
pytest
⚠️ Honest Limitations
- The Tasks extension is still experimental upstream. It isn't finalized in the core MCP spec, so its wire format could shift again in a future SDK release — this server tracks the SDK's current experimental implementation, not a stable target.
- The
execution-field fix is a monkeypatch, not a permanent solution. It patchesserialize_server_resultat runtime rather than fixing the underlying schema — it needs to be removed once SDK Issue #2156 ships an upstream fix. - Era detection is first-request-only. A client locked into the legacy era at connection start has no path to "upgrade" to the stateless era mid-connection, even if its later requests look modern.
🙏 Acknowledgments
- Official MCP SDK v2 — Model Context Protocol maintainers
- Model Context Protocol specification (
2026-07-28)
🧑💻 Author
Hoor Shumail AI | Machine Learning | Agentic AI | Multi-Agent Systems | Career Intelligence
- GitHub: https://github.com/HoorShumail
- LinkedIn: https://www.linkedin.com/in/hoor-shumail-a3a076326/
📜 License
This project is developed for educational, research, and portfolio purposes.
It builds upon the official Model Context Protocol SDK — refer to that SDK's own license and the Model Context Protocol specification for terms governing those components.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.