#4522: The Sixth Shim: When MCP Becomes a Middleware Factory

Six servers, six shims, no architecture. A taxonomy of MCP integration pain.

Featuring
Listen
0:00
0:00
Episode Details
Episode ID
MWP-4701
Published
Duration
34:27
Audio
Direct link
Pipeline
V5
TTS Engine
chatterbox-regular
Script Writing Agent
deepseek-v4-pro

AI-Generated Content: This podcast is created using AI personas. Please verify any important information independently.

The MCP specification, as of July 2026, defines transport, JSON-RPC message shapes, and lifecycle — but it deliberately leaves composition, routing, and middleware unspecified. That open space is where shims are born. The first one feels productive: forty lines of Python, a systemd unit, problem solved in an afternoon. By the fifth shim, you're not building architecture anymore — you're accumulating accretion. Each wrapper is individually trivial, but collectively they represent a failure to decide where the boundary lives between your gateway and the servers it fronts.

Of the six problems that drive engineers to write shims, four are fully generalizable and should never be custom-built in July 2026. Transport translation from stdio to HTTP is pure protocol conversion with multiple existing open-source implementations. Credential and header injection is infrastructure, not per-server logic. Retry and timeout policies differ only in parameters, not mechanism. Logging and tracing frameworks apply uniformly across every server. Tool renaming and filtering is mostly generalizable — build the rule engine once, configure mappings per server. Schema coercion splits down the middle: adding a missing JSON-RPC version field is general, but converting temperature strings to integers is server-specific semantic repair.

The decision rule is brutally simple: would a second server ever want this behavior? If yes, build a shared filter. If no, it's a server-scoped plugin — and the honest answer is almost always no for pagination quirks, semantic response differences, and API-specific transformations. The infrastructure impulse to generalize after seeing a problem twice is what makes good engineers, but generalizing too early produces abstractions that handle exactly two cases and nothing else.

Downloads

Episode Audio

Download the full episode as an MP3 file

Download MP3
Transcript (TXT)

Plain text transcript file

Transcript (PDF)

Formatted PDF with styling

#4522: The Sixth Shim: When MCP Becomes a Middleware Factory

Corn
You know that sinking feeling when you realize you've become a middleware factory instead of building a product? Daniel wrote to us about exactly that moment.
Herman
Oh, I know this one. The fifth shim.
Corn
The fifth shim. Here's what he sent. "I'm running an MCP aggregation gateway. Server after server does not quite fit. This one only speaks stdio and the gateway wants HTTP. That one needs an API key injected as a custom header the gateway has no slot for. Another emits tool names too long for the client. Another returns responses that are technically not valid JSON-RPC. Another has forty tools when I want six. Another paginates in a way nothing else does. Each time the fix is a small wrapper, a shim, and each shim is its own process, its own config, its own thing to restart. Six servers in, I have six middleware layers and no coherent story." Then he asks us to do a taxonomy. Which of these problems are generalizable and which are not. He wants the better architecture described, the costs honestly named, and he wants us to talk about the option people skip — deleting the shim and patching upstream. And he says to mention the real prior art, not stay abstract.
Herman
This is a good prompt. It's the kind of thing you only send when you've actually lived it. You don't wake up one morning and think "I should write a taxonomy of shim patterns." You write it because you're on shim number six and the seventh is staring at you from your backlog and you need to know if you're doing this wrong.
Corn
And the fact that he can list all six problems from memory, with that level of specificity — the custom header slot, the forty tools when he wants six — that's not a hypothetical. That's a scar.
Herman
Every detail in that prompt is a scar. So today we're going to build a taxonomy, then tear it down, then build something better. And we're going to name names on the tools that already exist.
Corn
Let's start by naming what that fifth shim actually tells us.
Herman
Because the thing is, the first shim feels like a quick fix. You're in flow, you need this server to talk to that gateway, you write forty lines of Python, you wrap it in a systemd unit, and you move on. It feels productive. You solved the problem in an afternoon.
Corn
The second one, fine, patterns emerge. You notice you're doing something similar to last time, but the details are different enough that copy-pasting feels faster than abstracting. And at two shims, you're probably right. The abstraction wouldn't pay for itself yet.
Herman
By the third you start to wonder. You're opening the same config template for the third time, changing the same three fields, and a little voice in your head says "shouldn't this be a function by now?" But you've got a deadline, so you copy the file, change the fields, and promise yourself you'll refactor later.
Corn
By the fifth you know. You don't have an architecture, you have accretion. Each shim individually is trivial. Maybe forty lines of code. A process wrapper, a config file, a systemd unit. But collectively they're a statement that you never decided where the boundary lives between your gateway and the servers it fronts. You've been making local decisions that were each correct in isolation and together they've produced a system nobody can explain without a whiteboard.
Herman
And that's the core tension right there. MCP is a protocol, not a runtime. The spec, as of July 2026, defines transport, JSON-RPC message shapes, and lifecycle — initialization, capabilities negotiation, tool listing, invocation. It says nothing about composition, routing, or middleware. Those are deliberately left open. Every shim you write is you implementing what the spec chose not to specify.
Corn
Which is not a criticism of the spec, by the way. Protocols that try to specify the runtime end up as CORBA. The spec did the right thing. But it means the shim problem is inevitable once you're running more than two or three servers.
Herman
And the question is whether you notice it and do something structural, or whether you just keep writing shim number seven, eight, nine, until you've got a dozen little daemons that all do roughly the same shape of work but can't share anything. And the thing about a dozen daemons is they don't just consume compute resources. They consume cognitive resources. Every new team member has to learn all twelve. Every incident response starts with "which shim handles that server again?"
Corn
And the stakes are genuinely interesting here because neither approach is obviously right. Six bespoke wrappers that each fail independently — that's resilient in a certain way. One server's shim crashes, the other five keep running. Versus one shared layer that handles all six — if that layer goes down, everything goes down. The blast radius tradeoff is real.
Herman
Right. And the debugging story is different too. Six independent processes means six independent log files, six places to look, but also six things that can't interfere with each other. One shared pipeline means you get a single trace across all six servers, but also one bad filter can corrupt every request. You fix a bug in the auth filter and suddenly all six servers are rejecting valid tokens.
Corn
So we're not walking into this saying consolidation is obviously correct. We're walking in saying the shim sprawl is a symptom, and the treatment has side effects worth understanding. The right answer for a three-person team running two servers is different from the right answer for a thirty-person team running twenty.
Herman
Let's sort the prompt's six problems into two piles. Generalizable and not.
Corn
The prompt gives us six specific problems. Let's go through them one at a time and be ruthless about which pile each one lands in.
Herman
Problem one, transport translation. Server speaks stdio, the gateway wants HTTP. This is the most obviously generalizable problem in the whole space. Every stdio-only server needs exactly the same treatment — spawn the process, connect to its stdin and stdout, bridge those byte streams to HTTP requests and responses. There is nothing server-specific about this. It's pure protocol conversion. You could write this once and use it for every stdio server you ever encounter.
Corn
And in fact, people have written it once. Multiple times. We'll get to the prior art, but this problem is so generalizable that there are already half a dozen open-source implementations of exactly this bridge. Nobody should be writing a custom stdio-to-HTTP shim in July 2026.
Herman
Problem two, credential and header injection. The server needs an API key in a custom header. The gateway doesn't have a slot for it. Again, this is completely general. Every server behind a corporate proxy, every third-party API, every authenticated internal service — they all need some header or token injected, and the mechanism for doing it is identical regardless of what the header is called or what the token value is. The header name and the token are configuration. The injection logic is infrastructure.
Corn
Problem three, tool renaming and namespacing and filtering. Server emits tool names that are too long, or forty tools when you want six. The renaming and filtering logic is general — you want a rule engine that says "rename this pattern to that pattern" and "only expose tools matching this list." But the specific mappings are per-server. This one is mostly generalizable. The mechanism is shared, the configuration is not. You build the rename engine once, and each server brings its own mapping file.
Herman
Problem four, schema coercion. The server returns responses that are technically not valid JSON-RPC. Maybe it's missing the jsonrpc version field. Maybe it wraps the result in an extra envelope. This is generalizable in the sense that a schema coercion filter is a standard piece of infrastructure — validate, repair, pass through. But the specific repairs might get weird. If one server puts the temperature as a string with a degree symbol and another returns it as an integer, you're not writing a general coercion rule anymore, you're writing server-specific response munging.
Corn
And this is where the line gets fuzzy. A filter that adds a missing jsonrpc field to every response — that's general. A filter that strips a known-broken envelope wrapper — that's general if the wrapper pattern is the same across servers, but it usually isn't. A filter that converts temperature strings to integers — that's not general at all. That's one server's data model leaking into your pipeline.
Herman
Problem five, retry and timeout policy. Completely generalizable. Every server needs retry logic, every server needs a timeout. The parameters differ — this server is slow, that one fails transiently a lot — but the mechanism is identical. A retry handler with configurable backoff and max attempts. You write the retry loop once and configure the backoff per server.
Corn
Problem six, logging and tracing. Also completely generalizable. You want structured logs with request IDs at every stage, you want to know which server took how long, you want to trace a request from the client through the gateway to the server and back. That's one logging framework applied uniformly. If you're doing this per shim, you're going to end up with six different log formats that you have to reconcile later.
Herman
So of the six problems, four are fully generalizable — transport, auth injection, retry and timeout, logging and tracing. One is mostly generalizable — tool renaming and filtering. One splits down the middle — schema coercion is general until it becomes semantic repair, at which point it's not.
Corn
And then the prompt mentions the non-generalizable ones explicitly. Bespoke pagination. Server X returns fifty results with a cursor in a header, server Y returns a hundred with an offset in the body. There is no general pagination filter that handles both of those. You can't abstract that away without building a full query federation engine, and at that point you've built a different product. That's not a shim problem anymore, that's a distributed query problem.
Herman
Semantic quirks in what a specific tool returns. getWeather returns temp as a string with a degree symbol. Another server's getWeather returns it as an integer in Kelvin. You can't write a general "fix weather responses" filter because the fix is different for every weather API. That's server-scoped logic. You either fix it upstream or you write a server-specific transform and you're honest that it's a one-off.
Corn
Response munging that only makes sense for one API. Strip PII from one specific endpoint's output. Redact internal IDs from another. These are one-offs. Trying to fold them into a shared pipeline just means your shared pipeline has a folder called "server-specific hacks" and you're back where you started. You've just moved the shims into a monorepo.
Herman
So here's the test. The architectural decision rule. Would a second server ever want this behaviour? If yes, it's a shared filter. If no, it's a server-scoped plugin. And you have to be honest when you answer. The temptation is to say "well, maybe someday another server will need PII stripping" and promote a one-off into the shared layer. Don't. If it hasn't happened by server three, it's not going to happen.
Corn
That test is the whole taxonomy in one sentence. And it's worth sitting with because it forces you to distinguish between "this is a common problem" and "I wish this were a common problem so I could justify building infrastructure for it." Those two things feel very similar when you're the one writing the code. They are not the same.
Herman
The infrastructure impulse is strong. You see a problem twice and your brain wants to generalize it. That's what makes you a good engineer. But generalizing too early is how you end up with an abstraction that handles exactly two cases awkwardly and can't handle the third case at all.
Corn
Let's talk about the prior art, because this space is not empty. People have been building pieces of this, and Daniel specifically asked us not to stay abstract.
Herman
Start with the simplest thing. stdio-to-HTTP bridges. mcp-gateway on GitHub, showed up in 2025. mcp-http-bridge on npm, early 2026. These do one thing: they take an MCP server that speaks stdio and expose it over HTTP. Single-purpose, no pipeline, no interceptor chain. They solve problem one and nothing else.
Corn
And they solve it well, for what they are. But if you use one of these, you've solved transport and you still have five other problems to solve. You're going to end up wrapping the bridge in something else, and now you've got a shim in front of your shim. You've taken one problem and split it into two processes that have to coordinate.
Herman
Then there's the aggregating meta-servers. MCP-Proxy and Supergateway. These compose multiple servers behind a single endpoint. They handle server-to-server differences at a basic level — different transports, different tool lists — and present a unified interface to the client. But they don't offer interceptor chains. You can't say "for server A, apply these three filters in this order." You get whatever composition logic the tool ships with.
Corn
Supergateway in particular is interesting because it does handle the transport translation and basic tool aggregation, but the moment you need custom header injection or tool renaming, you're back to writing wrapper code around it. It's a -server, not a middleware platform. It aggregates servers but it doesn't give you a place to hang your own logic between the aggregation layer and the individual servers.
Herman
The closest things to the pipeline idea are Portkey's MCP gateway and the open-source mcp-router. Portkey is a commercial offering, containerized, with a full filter chain — you can configure auth, rate limiting, tool filtering, logging, all as pipeline stages. mcp-router is the open-source counterpart, lighter weight, supports stdio and HTTP, has a basic filter chain. Both are early-stage but they prove the pattern works.
Corn
What's notable is that none of these — not even Portkey — has really nailed the interceptor interface yet. They all have some notion of middleware, but it's not the clean "function that takes a request, does something, passes it on, takes the response, does something, passes it back" that you see in a mature framework. The interface is still being figured out.
Herman
And you can see the growing pains in the open issues on these repos. Someone wants to add a filter that runs after the response comes back but before it's sent to the client. Someone else wants to abort the chain early under certain conditions. Someone else wants filters to communicate with each other. These are all solved problems in mature middleware frameworks, but they haven't landed in the MCP ecosystem yet.
Corn
Which makes sense because MCP itself is still being figured out. The working group only added tool annotations for deprecation hints in January 2026. That's six months ago. The protocol is moving fast and the middleware layer is trailing behind. You can't build a stable middleware platform on top of a protocol that's still adding fundamental features.
Herman
So we know what's generalizable and what's not. Now let's build the thing that handles the generalizable ones.
Corn
One conformance proxy with an ordered interceptor pipeline. Each server gets a declarative config, not a new process. The pipeline has stages — transport adapter, auth injector, tool renamer, schema coercer, retry handler, logger. Each stage is a filter that implements a standard interface.
Herman
The interface is the key. Every filter takes an MCP request, does something, and passes it to the next filter. On the way back, it takes the response, does something, and passes it back up the chain. That's it. That shape — request in, request out, response in, response out — is the same shape as nginx's filter chain, Envoy's HTTP filter chain, LiteLLM's middleware stack. It's a pattern that has solved this exact problem in three different domains already.
Corn
Let's make that concrete. nginx has a request processing pipeline — phases, they call them — where each phase is a point in the request lifecycle and you can plug in modules that run at that phase. Rewrite, access, content, filter. A module doesn't know about other modules. It just sees the request, does its thing, hands off. That's how you get URL rewriting, authentication, compression, and caching all coexisting without stepping on each other. And the key insight nginx got right is that the phases are ordered but the modules within a phase don't know about each other. You can add a new auth module without touching the compression module.
Herman
Envoy's HTTP filter chain is the same idea for the service mesh world. L7 policy — rate limiting, fault injection, header manipulation, routing — all implemented as filters in an ordered chain. The filter doesn't know whether it's talking to a Kubernetes pod or a VM or a bare-metal server. It just sees the HTTP request and does its job. And critically, Envoy decouples the filter chain from the upstream — you configure filters per virtual host or per route, not per upstream process. That's the exact decoupling we want for MCP servers.
Corn
LiteLLM is the model gateway analogy and it's maybe the closest parallel. You've got a dozen model providers — OpenAI, Anthropic, Cohere, whatever — each with slightly different API shapes, different auth patterns, different error formats. LiteLLM gives you one unified interface and handles the per-provider translation behind the scenes. Rate limiting, fallback, logging, cost tracking — all middleware that applies regardless of which provider the request ends up at. That's exactly the MCP shim problem restated for language models. Instead of six MCP servers with different auth patterns, you've got six LLM providers with different auth patterns. The solution shape is identical.
Herman
And the lesson from all three is the same. The filter chain is infrastructure. The per-server config is data. You don't write code to add a new server, you write a YAML stanza. The filters are reusable packages — one auth filter, one rename filter, one schema coercion filter — configured per server via parameters, not per server via a new Python file. When server seven arrives, you don't open your editor and start a new script. You add twenty lines to a config file and restart the proxy.
Corn
So here's what the config actually looks like. You've got a YAML file — or JSON, pick your poison — with a servers section. Each server entry has a name, a transport block that says "this one's stdio with this command" or "this one's HTTP at this URL," a credentials block with the API key and header name, a tools block with rename mappings and an allowlist, and a filters list that says "apply these filters in this order." The proxy reads this at startup, builds the pipeline, and routes each MCP request through the chain.
Herman
The filter list might look like "transport, auth, rename, coerce, retry, log." That's six stages. Each stage is maybe fifty lines of code because the interface is so simple. And when server seven arrives — and it will — you add a new config entry, not a new process. If it needs a filter you don't have yet, you write the filter once and it's available to every server from then on. That's the compounding benefit. Each new server costs less to integrate than the previous one.
Corn
That's the pitch. Now let's undercut it honestly, because Daniel asked us to name the costs.
Herman
Blast radius. One process means one crash takes down every server. With six separate shims, if the weather server's shim segfaults, your calendar and email and database tools keep working. With the consolidated proxy, one bad filter panics and all six servers are unreachable. That's not a theoretical concern. A null pointer in your auth filter and suddenly nobody can use any tool.
Corn
The mitigations are real but they're mitigations, not solutions. Process supervision — systemd with automatic restarts, Kubernetes liveness probes that kill and recreate the pod. If the proxy crashes, it comes back in seconds. Graceful degradation — if one server's filter panics, you catch the panic, mark that server as unhealthy, and keep routing to the other five. The proxy stays up, the broken server gets isolated.
Herman
That graceful degradation is the critical piece and it's not trivial to implement. You need per-server error boundaries inside the shared process. If the auth filter for server A throws an exception, you have to catch it at the server A boundary, not let it propagate up and kill the whole proxy. This is doable — it's basically a try-catch around each server's filter chain — but you have to design for it from day one, not bolt it on after the first outage. And you have to test it, which means you need integration tests that simulate individual filter failures and verify that the other servers stay healthy.
Corn
Debugging complexity. Six independent shims means six independent log files. You want to trace a request? You grep six files and correlate by timestamp. It's annoying but each log file is self-contained. With a shared pipeline, a single trace spans multiple filters. You need structured logging with request IDs that propagate through every filter boundary. You need a debug mode that dumps the request and response at each stage so you can see exactly which filter mangled the tool name.
Herman
And when something goes wrong, you're now debugging a pipeline, not a script. The request came in, passed through transport — fine. Passed through auth — fine. Passed through rename — wait, why did the tool name come out the other side as an empty string? Was it the regex in the rename rule? Was it an upstream change in the server's tool list? Was it an interaction between the rename filter and the coerce filter that runs next? You need tooling to answer those questions, and that tooling doesn't exist yet in the MCP ecosystem. You're going to be building your own observability on top of a proxy you maybe also built yourself.
Corn
This is the hidden cost of consolidation. When you had six shims, each was simple enough that you could debug it by reading the forty lines of code. With the pipeline proxy, you've built a framework, and frameworks need framework-level debugging tools. You're not just building the thing, you're building the tools to understand the thing.
Herman
Upgrade risk. With six separate shims, you upgrade one, test it, move on. If the upgrade breaks something, one server is affected. With a shared proxy, a new version could break all six servers at once. The mitigation is canary deployments — roll the new version to one instance, watch it, then roll to the rest. And per-server filter version pinning — if server A's rename filter works fine on version 1.2, don't force it to 1.3 just because server B needs a new feature. Let filters specify their version in the config.
Corn
This is basically the dependency management problem that package managers solved a decade ago, but now applied to a running proxy's internal modules. It's solvable but it's work, and if you're a small team running six MCP servers, you might reasonably decide that six independent shims with their own upgrade schedules is simpler than building a whole version-pinning system for your proxy's filter chain. The proxy pattern has a minimum viable scale, and not everyone is above it.
Herman
And that's the honest answer. Consolidation is not always the right call. If you're running three servers and they're stable, keep the shims. If you're running six and growing, and you're spending more time debugging shim interactions than building features, the proxy pattern starts to look worth the cost.
Corn
There's another option we haven't talked about yet. The one people skip. The one Daniel called out specifically.
Herman
Delete the shim.
Corn
Delete the shim. Some gaps should be fixed upstream in the server rather than papered over downstream. If a server returns invalid JSON-RPC, the right fix is a pull request to the server, not a coercion filter that silently repairs broken responses forever.
Herman
The thing is, the MCP ecosystem is young enough that upstream fixes are actually possible. The working group is active. Server maintainers are responsive. If you file an issue saying "your server returns responses missing the jsonrpc version field, here's a three-line fix," there's a decent chance it gets merged. Try that with a ten-year-old enterprise product and you'll get a polite "thank you for your feedback" and nothing changes. But MCP servers are mostly maintained by people who are actively working on them and want them to be spec-compliant.
Corn
The January 2026 draft adding tool annotations for deprecation hints is a perfect example. Before that draft, if a server renamed a tool from get_weather_v3_deprecated to get_weather, every client had to handle the rename themselves. Now the server can annotate the old name as deprecated and clients can adapt automatically. That eliminates an entire class of rename shims. The fix came from the protocol level, not the middleware level.
Herman
That's the thing about protocol-level fixes. One change to the spec eliminates thousands of shims across hundreds of deployments. It's the highest-leverage fix available. But it requires someone to notice the pattern, write the proposal, build consensus, and get it merged. That takes time. In the meantime, you've got shims.
Corn
The discipline is this. Every shim you write gets an explicit removal condition. A comment at the top of the file that says "Remove when server X version Y ships" or "Remove when upstream merges PR number one twenty three." Set a calendar reminder. If the condition expires — if the upstream fix shipped and you've verified it — delete the shim. Don't let it become permanent by default.
Herman
Do a monthly shim audit. Pull up every shim you're running, read the removal condition, check whether it's been satisfied. If a shim has been running for six months and the upstream issue is still open, maybe it's time to send the patch yourself. If a shim has no removal condition, add one or delete the shim. A shim without an expiration date is technical debt with an infinite repayment schedule.
Corn
The best shim is the one you delete. The second best is the one you never had to write because the architecture handled it. The worst is the one that's been running for two years and nobody remembers why it exists but everyone's afraid to remove it.
Herman
That's the architecture. But architecture without action is just a diagram. Here's what you do Monday.
Corn
Step one. When you reach shim number three, stop and audit. Don't wait for shim five. By shim three, the pattern is visible. Classify each shim using the test — would a second server ever want this behaviour? If two or more shims are generalizable, it's time to build or adopt a pipeline proxy.
Herman
Step two. Evaluate the existing tools. mcp-router for a lightweight open-source pipeline — it does transport and basic filtering and you can extend it. Portkey if you want a managed solution with a full filter chain and you're willing to pay for it. Or build your own — the filter interface is simple enough that a basic proxy with a plugin system is a week of work, not a month. The interface is request-in-request-out, response-in-response-out. You can scaffold that in an afternoon.
Corn
Step three. For every new shim you write, the removal condition goes in first. Before the code. If you can't articulate when this shim should die, you don't understand the problem well enough yet. Write the condition, then write the shim. "Remove when upstream issue #47 is closed." "Remove when weather-api v2.1 ships with the jsonrpc field fix." "Remove when the MCP spec adds standard pagination." Whatever it is, write it down.
Herman
Step four. Set a monthly shim audit on your calendar. Recurring. Fifteen minutes. Open the shim directory, read the removal conditions, check upstream. Delete anything that's eligible for deletion. If nothing's eligible, ask why. Are your removal conditions too vague? Are you not actually checking upstream? Is there an upstream fix you could contribute to speed things along?
Corn
The -takeaway here is that the shim problem is a protocol maturity signal. MCP is where HTTP was in the late nineties. Everyone's writing their own adapters because the ecosystem hasn't standardized the middleware layer yet. The people who build the middleware win. Nginx didn't win because it was a better web server — there were dozens of web servers. It won because it was a better reverse proxy with a filter chain that made per-upstream customization declarative instead of programmatic.
Herman
LiteLLM is winning in the model gateway space for exactly the same reason. They didn't build a better model. They built a better routing layer with middleware that handles the per-provider differences so application developers don't have to. The value isn't in the individual features — everyone has rate limiting and logging. The value is in the composition. The fact that you can stack those features in any order and apply them per provider without writing code.
Corn
The MCP equivalent of that hasn't been built yet. Or rather, it's being built right now, in pieces, across mcp-router and Portkey and Supergateway and a dozen other projects. The shim you're writing today is a feature request for the proxy that will exist in a year. Every time you write a shim, you're casting a vote for what the eventual middleware platform needs to support.
Herman
One last thought on the future of this whole pattern.
Corn
Will the MCP spec eventually standardize a filter chain? Or will it remain an implementation concern, with the working group sticking to "protocol, not runtime"?
Herman
The working group's current stance is firmly "protocol, not runtime," and I think that's correct for now. The protocol is still evolving — the deprecation annotations draft only landed in January. Standardizing a middleware interface before the protocol stabilizes would be premature. You'd end up with a filter chain spec that doesn't quite fit the message shapes that emerge a year later.
Corn
But as adoption grows, the pressure will build. When every major MCP deployment is running some form of interceptor pipeline, the argument for standardizing at least the filter interface becomes strong. Not the implementation — leave that to the market — but the interface. What does a filter look like? How does it signal errors? How does it declare its position in the chain? If everyone implements the same pattern slightly differently, the ecosystem fragments and the spec loses its power as an interoperability guarantee.
Herman
I'd bet on an RFC or a spec extension within the next eighteen months. The pattern is too universal and the pain is too widely felt. Someone's going to write it up, and if it's good, it'll get adopted. And the people writing shims today are going to be the ones who know exactly what that interface needs to look like because they've implemented it six different ways.
Corn
Until then, you're building your own. And now you know which parts belong in it and which parts don't.

Hilbert: If I'm running two MCP servers and they both work fine with a couple of small wrappers, is all of this overkill for me right now?
Corn
Yes. For two servers, keep the shims. The proxy pattern pays off around server four or five, when you notice you're copy-pasting the same auth logic for the third time. Before that, you're solving a problem you don't have yet. Don't build a framework for two things. Frameworks are for when the number of things is unbounded.
Herman
But write the removal conditions anyway. When server three arrives, you'll be glad you did. And when server four arrives, you'll know exactly which shims can become filters and which need to stay one-offs. The removal condition is the cheapest form of architectural hygiene available.
Corn
Thanks, Hilbert.
Herman
If you liked this episode, rate us five stars and tell a friend who's on shim number four. They'll know what you mean. They'll probably send you a screenshot of their systemd unit directory.
Corn
This has been My Weird Prompts. We'll be back soon.
Herman
Thanks to our producer Hilbert Flumingtop. I'm Herman Poppleberry.
Corn
I'm Corn. Delete your shims.

This episode was generated with AI assistance. Hosts Herman and Corn are AI personalities.