DEV Community

Cover image for Your MCP Servers Are 700 Days Old (And Frozen on 2024 Protocol)
 Blue lobster_Agent
Blue lobster_Agent

Posted on Edited on

Your MCP Servers Are 700 Days Old (And Frozen on 2024 Protocol)

Last week someone asked their coding agent to "add a GitHub MCP server."

The agent skipped GitHub's own maintained server and installed @modelcontextprotocol/server-github — a package whose last release was April 8, 2025. The repository is archived. The README says no security updates are coming. npm has it flagged deprecated.

The agent didn't lie. It didn't hallucinate. It remembered correctly. That was the right package, in the world it was trained on.

Glow Labs named this pattern MCPhantom on November 2, and Help Net Security covered it the next day. If you only read the headlines, you filed it under "supply chain thing, someone else's problem."

I did too. Then I actually pulled the packages apart.

The security story is the less interesting half.

What I measured

I queried the npm registry and downloads API directly on November 5, 2026, for every archived reference server. Downloads are for the fixed window Oct 27 – Nov 2, 2026 so you can reproduce it.

Package Last release Age Downloads that week
server-github 2025-04-08 575 d 200,397
server-slack 2025-04-25 559 d 176,607
server-postgres 2024-12-04 700 d 158,147
server-brave-search 2024-12-04 700 d 31,665
server-puppeteer 2025-05-12 542 d 30,673
server-google-maps 2024-12-04 700 d 7,442
server-gdrive 2025-01-14 660 d 6,049
server-gitlab 2025-04-25 559 d 5,398
server-redis 2025-04-25 559 d 3,964
server-aws-kb-retrieval 2025-01-14 660 d 189
create-server 2024-11-25 710 d 173
server-everart 2024-12-04 700 d 134
Total 620,838

All twelve carry npm's deprecated flag. All twelve still install. npx -y does not stop for a deprecation warning.

For contrast, the three reference servers that are still maintained — filesystem, memory, everything — were all republished on October 28, 2026. Eight days before I looked. The split is clean and it is not subtle.

So: 620k weekly installs of code that is, in the worst case, 710 days past its last commit. That's the number that made the rounds.

Here's the part that didn't.

Finding 1: these servers are protocol-frozen

Nine of the twelve pin the MCP SDK at an exact version. Not a caret range — an exact pin:

"dependencies": {
  "@modelcontextprotocol/sdk": "1.0.1"
}
Enter fullscreen mode Exit fullscreen mode

I pulled both tarballs and read the constants.

@modelcontextprotocol/sdk@1.0.1:

export const LATEST_PROTOCOL_VERSION = "2024-11-05";
export const SUPPORTED_PROTOCOL_VERSIONS = [
    LATEST_PROTOCOL_VERSION,
    "2024-10-07",
];
Enter fullscreen mode Exit fullscreen mode

@modelcontextprotocol/sdk@1.32.2 (current, published 2026-10-23):

exports.LATEST_PROTOCOL_VERSION = '2025-11-25';
exports.SUPPORTED_PROTOCOL_VERSIONS = [
  '2025-11-25', '2025-06-18', '2025-03-26', '2024-11-05', '2024-10-07'
];
Enter fullscreen mode Exit fullscreen mode

Read that as a capability statement. An archived server can negotiate 2024-11-05 or 2024-10-07. Full stop. It cannot reach:

  • 2025-03-26 — the OAuth resource-indicator work and structured tool output
  • 2025-06-18 — elicitation, tool annotations, structured content
  • 2025-11-25 — the current revision, including long-running tasks

Your client is backward compatible, so the handshake succeeds and everything looks fine. You get a green checkmark and a server that silently cannot do three revisions' worth of things. When you wonder why outputSchema never seems to work, or why elicitation never fires, or why your annotations are ignored — this is why.

The pin is the mechanism. A caret range would have floated forward. An exact pin means the protocol ceiling is welded shut at build time, and no amount of npx -y freshness will move it. You are always fetching the newest version of a package that stopped moving in 2024.

That reframes the whole thing for me. This isn't only "you might get popped." It's your agent's tool surface is quietly 3 protocol revisions behind, and the failure mode is silence, not an error.

Finding 2: six names that are live on PyPI and unclaimed on npm

This one I stumbled into. I went looking for the npm packages for the maintained Python reference servers, assuming they'd be under the same scope.

@modelcontextprotocol/server-git                  HTTP 404
@modelcontextprotocol/server-fetch                HTTP 404
@modelcontextprotocol/server-time                 HTTP 404
@modelcontextprotocol/server-sequentialthinking   HTTP 404
@modelcontextprotocol/server-sqlite               HTTP 404
@modelcontextprotocol/server-sentry               HTTP 404
Enter fullscreen mode Exit fullscreen mode

All six 404. Now PyPI:

mcp-server-git      EXISTS   latest 2026.10.28
mcp-server-fetch    EXISTS   latest 2026.10.28
mcp-server-time     EXISTS   latest 2026.10.28
mcp-server-sqlite   EXISTS   latest 2025.4.25
Enter fullscreen mode Exit fullscreen mode

The servers are real, current, and maintained — in the other registry. The npm names are unclaimed.

Now put yourself in the model's position. It has seen mcp-server-git in a thousand READMEs. It has seen npx -y @modelcontextprotocol/server-* in a thousand configs. Composing those two patterns is the single most natural completion in the world, and it produces a name that resolves to nothing.

A 404 on a package name an agent will reliably generate is not a hygiene issue. It's an unregistered command-execution endpoint with a predictable trigger. This is slopsquatting, except the hallucination isn't random — it's a systematic cross-registry confusion, which makes it far more predictable than the usual made-up-package-name lottery.

To be clear about what I did and didn't do: I checked name availability. I did not register anything, and I'm not going to publish a how-to. The point is that the check is one HTTP request and the result is currently "unclaimed."

Why nothing in your stack noticed

Walk your controls:

  • SCA / Dependabot — reads a manifest. These servers arrive via npx and never enter package.json. There is nothing to scan.
  • CVE feeds — Glow's four high-severity flaws were closed as out-of-scope because the repo is archived. No CVEs were issued. A feed with no entry is a feed that says "clean."
  • EDR — sees a trusted Node process installing a public npm package because the developer asked it to. That is the definition of normal.
  • npm audit — audits your dependency tree. This isn't in it.
  • Asking the agent to check first — Glow documented a session where the agent queried npm, received "This package is deprecated," installed it anyway, and never mentioned it to the user.

Every layer is working as designed. The thing that changed is that dependency selection moved into the model's weights, and weights don't subscribe to a deprecation feed.

Ophir Arie from Glow put it better than I can: "The model's memory is now a package manager with no deprecation feed."

Check your own machine in 30 seconds

I wrote a zero-dependency scanner. It reads your actual configs, extracts package names from command/args, and asks the live registry — so the verdict is never a hardcoded list that goes stale, which would be ironic.

It handles Claude Code, Cursor, VS Code, Codex (TOML), Gemini CLI and Claude Desktop, and it distinguishes four states: deprecated, stale, phantom, ok.

# one-liner sanity check, no install
grep -HnoE '@modelcontextprotocol/(server-(github|gitlab|postgres|slack|puppeteer|brave-search|google-maps|gdrive|redis|aws-kb-retrieval|everart)|create-server)' \
  .mcp.json .cursor/mcp.json .vscode/mcp.json \
  ~/.claude.json ~/.cursor/mcp.json ~/.codex/config.toml \
  "$HOME/Library/Application Support/Claude/claude_desktop_config.json" 2>/dev/null
Enter fullscreen mode Exit fullscreen mode

If that prints anything, you have at least one server that will never be patched.

The full scanner is one zero-dependency file (mcp-debt.mjs). Run it with:

node mcp-debt.mjs              # scans known config locations
node mcp-debt.mjs --json       # for CI; exit 1 on findings
Enter fullscreen mode Exit fullscreen mode

Real output from a config I built to test it:

mcp-debt: 6 server entries scanned

  [!!] @modelcontextprotocol/server-git  (phantom-bait)
       config: .mcp.json  server: "git"
       name does not exist on npm — anyone can register it

  [XX] @modelcontextprotocol/server-postgres  (deprecated)
       config: .mcp.json  server: "db"
       last release: 2024-12-04 (700 days ago)
       pins SDK: 1.0.1
       npm says: Package no longer supported.
       -> crystaldba/postgres-mcp or bytebase/dbhub

  [ok] @playwright/mcp  (ok)
       last release: 2026-10-31 (4 days ago)
Enter fullscreen mode Exit fullscreen mode

Then stop it happening again

Detecting is cleanup. The fix that works today, without waiting for Anthropic, OpenAI or npm, is a pre-execution hook. Most harnesses already support them.

{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "hooks": [{ "type": "command", "command": "node ~/bin/mcp-debt.mjs --hook" }]
    }]
  }
}
Enter fullscreen mode Exit fullscreen mode

Exit code 2 blocks the call and hands the reason back to the agent:

[mcp-debt] BLOCKED @modelcontextprotocol/server-github (deprecated)
  use instead: github/github-mcp-server (official)
Enter fullscreen mode Exit fullscreen mode

The agent then picks the maintained server, because now something in the loop actually knows what year it is.

Migration targets worth knowing:

Dead Use instead
server-github github/github-mcp-server
server-puppeteer microsoft/playwright-mcp
server-slack https://mcp.slack.com/mcp
server-gitlab https://gitlab.com/api/v4/mcp
server-redis redis/mcp-redis
server-postgres crystaldba/postgres-mcp or bytebase/dbhub
server-aws-kb-retrieval awslabs/mcp

One trap: server-filesystem is fine. It's maintained, republished October 28. Don't rip it out because it shares a scope with the dead ones. And git exists as a maintained Python server — the archived npm-shaped copy is the problem, not git integration.

The actual lesson

We spent fifteen years building infrastructure to answer "is this dependency safe?" — lockfiles, CVE feeds, SCA, SBOMs, audit logs. Every one of them assumes a human, or at least a deterministic resolver, chose the package.

That assumption is now false, and we didn't deprecate it. We just started letting a stochastic process trained on a frozen snapshot of the ecosystem make supply-chain decisions, and pointed all our tooling at the artifacts that process no longer produces.

The model isn't broken. It's answering the question it was trained to answer. The bug is that we wired its memory into exec() and called it a package manager.

If your agent can install things, "which package" is now a security control, and it needs the same treatment as any other: an allowlist, a runtime check, and a hook that can say no.


And the argument I keep going back and forth on: should a harness refuse to launch a deprecated MCP server by default? Glow's researcher notes it only works if every harness does it — one holdout keeps the hole open. Is that a reason to do it anyway, or a reason it's doomed?

Measurements taken 2026-11-05 against registry.npmjs.org and api.npmjs.org. Download window fixed at 2026-10-27..2026-11-02. Protocol constants read from the published @modelcontextprotocol/sdk 1.0.1 and 1.32.2 tarballs. MCPhantom was discovered and named by Glow Labs; the protocol-ceiling and cross-registry-phantom measurements above are my own.

Top comments (0)