跳到正文
DEV Community· Aftab Bashir·· 4 小時前AI 評分59

Aftab Bashir 將 ContextOS MCP 伺服器改造成 Claude Code 插件,梳理故障與修正

Turned my MCP server into a Claude Code plugin. Here's everything that broke.

AI 導讀

Aftab Bashir 將 ContextOS MCP 伺服器改造成 Claude Code 插件,並整理開發與升級期間遇到的故障和修正。插件可用 `/plugin marketplace add aftabkh4n/contextos` 和 `/plugin install contextos@contextos` 安裝。

正文

In my last post I introduced ContextOS, an MCP server that gives AI coding agents memory across sessions. You open Claude Code in a repo, and it already knows the branch you're on, the decisions you made, and what's still open.

It worked. But installing it took a shell script, a config file, and a claude mcp add command. That's where most people stop.

So I packaged it as a Claude Code plugin. Now it's two commands:

/plugin marketplace add aftabkh4n/contextos
/plugin install contextos@contextos

Getting there took four patch releases in three days. Every bug was something I'd never have found without installing it on my own machine and using it like a stranger would. If you're building a plugin, I hope this saves you some of them.

The plugin layout

A plugin is a folder with a manifest and some optional parts:

plugins/contextos/
├── .claude-plugin/plugin.json   # name, version, MCP server
├── skills/contextos/SKILL.md    # when to remember and recall
├── hooks/hooks.json             # SessionStart hook
└── scripts/ensure-binary.sh     # downloads the binary

Plus a marketplace.json so people can install it by name.

Two small things caught me straight away:

  • The validator rejects .. in marketplace paths. I wanted the marketplace in its own folder pointing at ../contextos. It won't allow that, so the marketplace manifest moved to the repo root. One repo, one install command. Better anyway.
  • The install command needs the full name. /plugin install contextos only searches the default marketplace. For your own marketplace it's plugin@marketplace, so contextos@contextos. My README had it wrong, and so did I, the first time I tried.

You can't bundle a 118 MB binary

ContextOS is a self-contained .NET binary with a local embedding model inside it. Each platform build is about 118 MB. That doesn't belong in a plugin folder.

So a SessionStart hook downloads the right build for your OS into ${CLAUDE_PLUGIN_DATA}, a folder that survives plugin updates. The MCP server entry in plugin.json points there.

First bug: the script copied only the binary. The embedding model lives in a Models/ folder next to it, so the first version installed a server that failed its own selftest on every machine. Validation passed, of course. Validation checks the manifest, not whether your program runs.

Bug 1: My new feature deleted everyone's old memories

This one wasn't in the plugin at all. It was in v0.2.0, released the week before.

I'd added memory decay: memories not recalled in 90 days get archived. To support it I added a last_recalled_at column. Existing rows got NULL. The decay rule said "never recalled, so count from the creation date."

Every memory older than 90 days was archived on the first start after upgrading.

I only noticed because my test workspace suddenly had "zero decisions," when I knew it had one.

The fix was two migrations: backfill last_recalled_at with the upgrade time so existing memories get a fresh window, and a repair migration that restores rows already archived by the bug. On my machine the log said Restored 1 memories, and the missing decision came back.

Lesson: when you add a column and a rule that reads it in the same release, the default value for existing rows is a product decision, not a database detail.

Bug 2: Claude ignored the instructions

I wanted "What was I working on?" answered from the injected context, with no tool calls. I put that rule in SKILL.md.

Claude ran git log, listed files, read my old session history, then answered.

The reason: a skill's body only loads when Claude decides the skill applies. The rule wasn't in context when it mattered.

ContextOS already injects workspace state through the MCP initialize handshake, in serverInfo.instructions, which is always loaded. I moved the rule there, one line:

Use this context to answer questions about prior work, decisions or todos. Do not run git, list files, or read session history to answer them.

Next test: zero tool calls, answered in five seconds.

Lesson: skills are for "how to do this when it comes up." Anything that must always apply belongs somewhere that's always loaded.

Bug 3: Updates never actually updated

The download script started like this:

if [ -f "$BINARY" ]; then
  exit 0
fi

It checks whether a binary exists, not which version. And ${CLAUDE_PLUGIN_DATA} survives plugin updates, which is the whole point of it. So after updating the plugin, everyone kept the old binary forever.

Fix: write a .version file after each install and compare it with the version in plugin.json.

Bug 4: The server and the hook start at the same time

On session start, Claude Code launches the MCP server and runs the SessionStart hook in parallel. On a fresh install, the hook is still downloading 118 MB when Claude Code tries to start a binary that doesn't exist yet.

The server fails, and Claude Code remembers the failure for 15 minutes. So the user's first impression is a broken plugin.

Bug 5: My fix killed every window's server

To replace the binary during an upgrade on Windows, I stopped the running process first:

taskkill //F //IM contextos.exe

Combined with Bug 4, the hook killed the server Claude Code had just started. Worse, taskkill by image name kills every contextos.exe on the machine. If you had two Claude Code windows open, updating in one broke both.

My first idea was to swap the binary at session end instead. The docs ended that quickly: SessionEnd hooks get a 1.5 second budget that plugins can't raise, and they also fire on /clear and /resume, when the session keeps running.

The fix that finally worked was to stop replacing binaries at all:

bin/0.2.3/contextos.exe
bin/0.2.4/contextos.exe

Each version installs into its own folder, and plugin.json points at the folder for its own version. Nothing running is ever touched, so there's nothing to kill and no file locks. Old folders get cleaned up later, when nothing is using them.

The tradeoff: right after an update, the new folder is still downloading, so the server fails once and you choose Reconnect in /mcp. The hook prints a line into Claude's context explaining that. A known, one-time failure with a clear fix beats a silent one.

I also added a test that reads plugin.json and fails the build if the server path doesn't contain the manifest's own version.

Bug 6: Linux needed a library minimal systems don't have

While cleaning up the release archives, the Linux binary aborted on start in WSL:

Couldn't find a valid ICU package installed on the system.

Minimal distros and Docker images often don't ship libicu, and that's exactly where people run agents. The fix is one line in the project file:

<InvariantGlobalization>true</InvariantGlobalization>

I didn't want that to quietly change embeddings for memories people had already stored, so I embedded four strings before and after, including accented text, a Turkish İ, and Chinese. The max difference was 0.0 on all of them. Then it shipped.

One more thing about versions

The marketplace reads plugin.json from the main branch. So the moment I pushed a version bump, new installs pointed at a GitHub release that didn't exist yet, and got a 404. Now I tag right after CI goes green, every time.

Where it ended up

  • Two-command install
  • Upgrades that never touch a running binary
  • Claude answers "What was I working on?" from injected context with zero tool calls
  • Runs on Linux without libicu
  • Four tools, under 700 tokens of tool definitions

That last number taught me something about my own setup. When I checked, my Claude Code sessions were loading 140 MCP servers and 294 skills from plugins I'd installed once to try. ContextOS was one server out of 140. If your sessions feel heavy, run /context and look before blaming any single tool.

Try it

/plugin marketplace add aftabkh4n/contextos
/plugin install contextos@contextos

Restart Claude Code. If contextos shows as failed in /mcp on the first start, choose Reconnect while the binary finishes downloading. Then open any git repo and ask: "What was I working on?"

Repo: github.com/aftabkh4n/contextos

If something breaks for you, open an issue. Apparently that's how this thing gets better.

來源:DEV Community · dev.to