Gleipnir
Blog

Plugins unplugged

I needed one thing MCP couldn't do, so I built a plugin system and bundled everything into it. The 2026-07-28 spec gave it a home, so the bundle comes apart.

In May, I began work in earnest on the plugins architecture for Gleipnir. The idea was to give Gleipnir a second way to take on tools, alongside MCP. MCP existed, but it didn’t seem to have everything I was looking for as I continued to develop Gleipnir. The primary surface that I wanted was a way to use an MCP server to trigger an agent run in Gleipnir. Something simple like a Slack message to a bot account could wake up the agent and have it perform an action for me. Once I determined that I needed a custom surface to support that feature, bundling the other things I wanted MCP to do into the plugin system made a lot of sense. So I wrote a plugin system with a set of tools modeled directly on MCP’s, hard interception of tool calls that needed approval, and a serious amount of security design and research (signing, trust-on-first-use pinning, manifest verification) to make sure the plugin you got was the plugin you thought you were getting.

I was proud of it. It worked without a hitch, and it was thorough. I had pictured a marketplace down the line: third-party plugins an admin could add and actually trust, because the signing and the manifest would tell them exactly what they were getting.

Finding extensions

The release candidate for the 2026-07-28 MCP spec had been public since May 21st. I found it in July, a few weeks after I shipped.

The piece that mattered was extensions. MCP had a couple of loose escape hatches before this: an experimental capability flag and a _meta field you could use for arbitrary data. Neither one was a real mechanism. There was no identifier format, no way to negotiate support, no versioning rules, and no way for two parties to even know they were talking about the same extension. The new spec turns all of that into an actual framework. You claim a reverse DNS prefix for a domain you own, dev.gleipnir in my case, and you publish capabilities under it without needing anyone’s permission. Clients and servers negotiate which extensions they both understand, extensions are off by default, and anything that doesn’t recognize yours falls back to standard behavior.

Two other things stood out on that read. Elicitation had been redesigned so a server can ask for human input without holding a session open, which is very close to what I had built the approval interception to do. And triggers, the whole reason I went down the plugin road in the first place, still weren’t in the spec at all.

Plugin surfaceMCP before 2026-07-28MCP 2026-07-28Where it lands
Tool callsYes, since the first revisionLoosened to full JSON Schema 2020-12MCP core
Human approvalElicitation since 2025-06-18, but it needed an open sessionStateless continuation, no session requiredMCP core
Which human approvedNo identity on the response, by designStill none, identity comes from authorizationdev.gleipnir extension
TriggersNot coveredStill not covered, “on the horizon”dev.gleipnir extension
Notification fan-outPoint-to-point onlyExplicitly out of scopedev.gleipnir extension
Long-running workExperimental, in coreMoved out into an official extensionOfficial extension
A sanctioned extension seamexperimental flag and _meta, no rulesReverse DNS identifiers, negotiation, independent versioningMCP core
Signed, attested packagingNothingStill nothing, server identity is self-reportedDropped

I went back and forth on whether to support extensions alongside the plugin system and keep both. But the reality was that, with an open, industry-supported version of the same thing, no one would use my custom plugin specification.

Unplugging plugins

Once extensions were supported by the MCP standard, the justification for the plugin bundle dissolved. The tools surface I had written was a carbon copy of MCP’s, so it just goes back to being the original. Triggers, approvals that carry a human identity, and notification fan-out become a dev.gleipnir extension. And everything else only ever existed to hold those pieces together: the plugin host, the gRPC transport, the SDK, the manifest system, the signing, and the pinning. All of it comes out.

I also didn’t want to be in the business of creating my own walled garden of plugins that only work with Gleipnir. If someone already has an MCP server they trust, or finds one next week that does exactly what they need, I want them using it here to full effect rather than waiting on me to write a Gleipnir-flavored version of it. MCP servers get written once and work everywhere, and extensions are opt-in and degrade gracefully, so a dev.gleipnir extension can add approvals and triggers on top of an ordinary MCP server without that server caring whether Gleipnir exists at all. Best with Gleipnir, not only with Gleipnir. A private plugin protocol can’t offer that; it’s all-in or nothing. On top of that, keeping my own protocol alive meant I was personally responsible for maintaining a large section of code that Gleipnir no longer needed in order to work.

What I lose is the ability to vouch for what’s inside. The signing, the pinning, and the manifest checks were there so an admin could add something they hadn’t written and still know what they were getting. With the removal of the plugin protocol, admins fall back on ordinary industry practices for validating the MCP servers they ingest.

What doesn’t change is what happens once a server is running. Provenance was only ever half of the trust story, and the other half was containing what a tool could do after it was loaded. Capability scoping, approval gates, and the audit trail did that job, and they apply to an MCP server exactly the way they applied to a plugin.

With that settled, I sketched out the transition plan and dug further into how the standard worked, what it unlocked, and how to build on it. Once it officially landed, I started the implementation. That work is still in progress as I write this, with the new MCP layer going in as the plugin layer comes out. Gleipnir is still in beta, and a community of plugin authors never formed, so the wholesale removal of plugins doesn’t affect anyone. That means I can be more aggressive now than I would normally be.

What elicitation actually is

Getting off the custom plugin layer also pushed me to read what elicitation actually does at the MCP level. I had assumed it covered any case where a server needed more information back, including the cases where the agent could work out the answer on its own. It doesn’t. Elicitation is specifically a request for a human to supply something, which is the same problem my tool-call interception was there to solve.

I’m still of the opinion that the agent shouldn’t be the one fielding that request. The work underway now handles elicitations in the runtime, and the agent never sees them at all. What the protocol doesn’t carry is who answered. There’s no identity attached to an elicitation response, and the spec is explicit that a server shouldn’t trust client-supplied identity because it can be forged. Identity has to come from authorization instead. Tying an approval to a verified human and getting that into the audit trail is the piece I’m still building, and it’s one of the things the dev.gleipnir extension exists to carry.

What survived

Fortunately, the teardown wasn’t total. Certain concepts from that experimentation stuck around, the biggest of which was the reusable, admin-configured audience. An audience is a named, ordered list of the places an agent can reach a human, and policy refers to it by name. Notifications fan out to every entry that can take one, and an approval request goes to the first entry that can answer it. The built-in in-app channel gets appended to the end automatically, so there’s no way to configure yourself into a state where the agent can’t reach anybody. The audience turned out to be a real primitive for how agents reach their operators, not just a way to say “tell someone.” Building audiences for the plugin system showed me how much more is possible in that space. What started as a way to route approvals to different plugins has grown into a full-fledged feature with more on the way.

Where this goes

What actually happened here is that the standard caught up to a set of problems I had been solving on my own, and it did so in a way that left me more room rather than less. Each new release unlocks things that weren’t possible a revision ago, and I would much rather build on top of that than maintain my own version of it.

What I do differently now is read the roadmap and the SEP discussions instead of waiting for a finished spec to turn up. The release candidate was public for six weeks before I found it, and everything in it had been argued about in the open long before that.

Triggers are the one piece that still isn’t there. They’re the reason I built plugins in the first place, and the spec still doesn’t cover them. There is a Triggers and Events working group now, chartered back in March, but nothing has been accepted yet, and the design is still wide open. In the meantime, I’m writing triggers as a dev.gleipnir extension, along with a custom Slack MCP server that carries the extensions Gleipnir needs. I’m joining the working group as an observer to follow where it goes. Showing up with something that runs in production seems like a better way to have an opinion than showing up with an opinion.