Engineering Notes · agent operations · supply chain
Every plugin and MCP server my coding agents load comes from a copy I hold. Nothing is pulled live from npm, GitHub or a marketplace at launch time. It costs me some upkeep. In return, the instructions and code my agents run only change when I decide they change.
It is easy to think of a Claude Code plugin as a convenience pack: a few slash commands, maybe an agent or two. Look inside one and it is mostly instructions. Skill files tell the agent how to approach a task. Agent definitions set which model runs, which tools it gets and what it is told to do. Hooks are shell commands that run on every tool call. The agent reads all of it as guidance it should follow, with my permissions, inside my client projects.
An MCP server is the same deal with more reach. It is a process running on my machine, often holding real credentials: a database login, a browser profile, a filesystem root. Its tool descriptions go straight into the model's context, and the model treats them as part of its instructions.
So the question I care about is not whether a plugin is good. It is who gets to change it, and whether I will notice when they do.
The rule: plugins and MCP servers load only from a clone or copy I hold. No live installs from a registry, no auto-update, no @latest. Upstream is something I read and pull from deliberately, never something my agents subscribe to.
This is the common one, and nobody has to be malicious for it to happen. A maintainer changes a default that suits their setup, and your setup now behaves differently.
The planning plugin I use, HCF, is a good example of a well-run project that still does not match my setup out of the box. Upstream pins its TDD workers and its orchestration skill to a specific mid-tier model. On my fleet, worker intelligence is meant to follow whatever model the session is on, so I can raise it for a hard plan by switching models. If I installed HCF live, every upstream sync would quietly decide which model writes my code. Nothing would error. The output would just get a bit worse, and I would be the last to find out why.
The Claude Code harness itself taught me the same lesson twice. Two releases each changed behaviour my config depended on: one started loading rule files recursively, and one changed the payload that post-tool hooks receive. Neither was a bug on their side. Both silently broke carefully tuned setup on mine.
Package registries have a long record of this. In 2018 the popular npm package event-stream passed to a new maintainer, who added a dependency carrying code aimed at a particular Bitcoin wallet. Everyone installing the latest version got it.
MCP servers add a version of the attack that needs no binary payload at all. A tool description is plain text that the model reads as instructions, so a poisoned description can tell the agent to read a credentials file, or to quietly copy data into a tool argument. Security researchers demonstrated this during 2025, including servers that show a harmless description when you first approve them and swap it later. A live npx -y some-mcp-server runs whatever the registry serves that morning, and the first sign of trouble would be the agent doing something odd.
Upstream owes me nothing. I spent real time on a code-graph tool that broke on upgrade after upgrade and then changed to a non-commercial licence. With my own copy, that kind of change becomes a decision I get to make when it suits me: keep the last good version, fork it, or move on. It never becomes an outage in the middle of a client job.
The mechanics are dull on purpose. Each piece takes away one way for code or instructions to change without my say-so.
directory source pointing at a seed folder mounted into every project container, rather than a GitHub repo. Editing that folder is how a plugin gets published to the fleet. Nothing outside it can push a change in.grep to prove they took. An upstream default can't overwrite a deliberate local choice without me seeing it.vendor/ folder, built once with a lockfile, and starts from that local build. Updating is git pull, read the diff, rebuild. Never a fetch at launch.Holding a copy only helps if I also refresh it. A mirror nobody refreshes is just a slower way of falling behind on security fixes. So the update path has its own tooling:
upstream release
-> watch posts changelog + impact summary
-> I read the diff (as text, hidden-character check on)
-> sync into the mirror, re-apply patch notes
-> rule evals on the new version
-> move the pin / publish to the seed folder
A step stays manual only where it needs judgment. I don't automate that part away, because skipping it is what a live install does by default.
There is a real cost here. Every mirror is something I maintain. I am always some way behind upstream, and a security fix reaches my fleet when I pull it, not when it is published. That is why the pin expires and the watch runs every day: the lag has to be visible and bounded, or the copies become their own risk.
For a one-person consultancy with agents working inside client codebases, I think that is the right trade. The usual failure is a quiet behaviour change that ends up in a client's production code, and a compromise is rarer but much worse. Both start with the same thing: something I run changed without anyone deciding it should.
Rule of thumb: if a dependency can change what your agent is told to do, treat it like code you are about to commit. Read the diff, keep a copy, and make updating an action someone has to take.
From my claude-skills-central repo. The plugin mirrors live in a separate private repo.
I use my tooling predominantly on Mage-OS (Adobe Commerce / Magento) e-commerce projects, and my own AI Booking Agent. If you have not swapped to Mage-OS yet, you are falling behind ;)