- Book: AI That Acts
- The series: AI in TypeScript — 5 books, from your first LLM call to agents in production — all five here
- My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools
- Me: xgabriel.com | GitHub
On October 13, 2026, a user opened issue #2424 on REA, the open-source MCP server and CLI that lets coding agents reverse engineer binaries, Electron apps, APKs and more. The title: "a fake repo, rea-tools/rea, distributes a trojan \"Windows exe\"."
According to the issue, the copy reused REA's README and added a "Windows Standalone .exe" section pointing at an 82.6 MB archive. Microsoft Defender flags the archive as Trojan:Script/Wacatac.B!ml and the extracted rea-x64.exe as Trojan:Win32/Wacatac.B!ml, 8 of 45 VirusTotal engines flagged it, and it had 172 downloads. The issue adds that the rea-tools organization was created on October 10 and held that one repository. Its name echoes REA's real site, rea.tools.
REA was a big target: about 145,000 GitHub stars within days of going viral (Kunal Ganglani, October 12), more than 280,000 on the repo today. The maintainer closed the issue the same day with PR #2429. The README now says "Get REA from morluto/rea, rea.tools, or the rea-agents npm package," and that "a similar repository name or copied README does not establish that a download is official."
That sentence is the whole problem with MCP servers. You find one in a thread, paste a line into your agent's config, and the agent runs it with your permissions. Here are eight checks to run first, with REA's real package as the worked example. Checks 1 and 2 alone catch the rea-tools copy, which shipped a Windows .exe. The other six cover the path that copy didn't take but the next one might: the npm package your config pulls with npx.
Checks 1–3: is this the real project?
1. Get the name from the project, not from a search
Take the package name and repo from the project's own README or site, then compare them character by character with what is in your config. REA's README names three sources: morluto/rea, rea.tools and the npm package rea-agents. Anything else, including a repo with the same README, is not REA. A lookalike usually differs by one thing: an org named after the domain, a dash, a plural.
2. Check who owns the repo, and since when
The GitHub REST API returns a repo's creation date, whether it is a fork, and its parent if it is one. gh api prints just those fields with --jq:
gh api repos/morluto/rea \
--jq '.created_at, .fork, .parent.full_name'
If the owner is an organization, check it too. GET /orgs/{org} returns the org's own created_at and its public_repos count:
gh api orgs/<org> --jq '.created_at, .public_repos'
An org created three days before you found it, holding one repo of a tool with six-figure stars, is the shape the REA issue described. A copy pushed as a fresh repo shows fork: false, so the dates carry more weight than the fork flag.
3. Check that npm points back to the same repo
npm view reads registry metadata without installing anything, several fields at once:
npm view rea-agents repository.url maintainers time.created
For rea-agents, the registry record lists git+https://github.com/morluto/rea.git as the repository and one maintainer, the account that owns the repo. That is the match you want. But repository is a field the publisher writes into package.json, and anyone can claim any repo there. On its own it proves nothing. Check 4 backs it up.

A lookalike copies the README. The dates and the publisher are harder to copy.
Checks 4–6: who built it, and what runs on install
4. Check provenance, then the signatures
npm provenance publicly establishes "where a package was built and who published a package". The registry record for rea-agents@6.5.0, the latest tag as of October 16, 2026, carries a provenance attestation (https://slsa.dev/provenance/v1) and was published by GitHub Actions through trusted publishing. Read the attestation type directly:
npm view rea-agents@6.5.0 \
dist.attestations.provenance.predicateType
To verify the package and everything it pulls in, install it into a scratch folder without running scripts, then run npm audit signatures:
mkdir mcp-check && cd mcp-check
npm init -y
npm install --ignore-scripts rea-agents@6.5.0
npm audit signatures
npm's docs show the result as counts of packages with "verified registry signatures" and "verified attestations". They also state the limit: provenance "does not guarantee the package has no malicious code." It tells you which repo and workflow built the tarball. Whether that code is safe is still your call.
5. Read the lifecycle scripts before they run
npm's scripts docs list preinstall, install, postinstall and prepare among the hooks that also run on npm install -g <pkg-name> (prepare only for git and local-folder installs), and a package with a binding.gyp and no install or preinstall script gets node-gyp rebuild as its install step. Ask for those fields:
npm view rea-agents@6.5.0 scripts.preinstall \
scripts.install scripts.postinstall scripts.prepare
REA 6.5.0 has no preinstall, install or postinstall. It does list "prepare": "node scripts/prepare.mjs", but npm's docs say prepare doesn't run when you install a package by name from the registry. So from npm, nothing runs at install time. If a server you check does have an install hook, find the file it points to and read it. Check 8 gets you that file.
6. Know what your npm does with scripts
Run npm --version, because the answer changed this year. npm 12 skips dependency install scripts by default unless you allow the package. For npx and global installs, which have no project package.json to hold an allowlist, the npm 12 docs say to pass --allow-scripts=<pkg> or persist it with npm config set allow-scripts=<pkg> --location=user.
On npm 11 or older, scripts run unless you say otherwise, so add --ignore-scripts to the npx arguments in your MCP config. The config docs say it overrides allow-scripts, so it is harmless on 12. If the server breaks without its install step, it needs install-time code. Read that code, then decide.
Checks 7–8: pin it, then read the tarball
7. Pin an exact version, not @latest
REA's README uses npx -y rea-agents@latest for a one-off CLI run, and plenty of MCP install snippets do the same. In an MCP config, that line runs every time the agent starts. latest is a moving tag: whatever is published next, by whoever controls publishing for that package, is what it points to, and -y skips the npx prompt.
Pin the version you checked: rea-agents@6.5.0, not rea-agents@latest. For the day you bump it, npm's min-release-age only installs versions "that were available more than the given number of days ago":
# ~/.npmrc
min-release-age=7
A week gives a hijacked release time to get reported before it reaches your agent.
8. Diff the tarball against the source
Your agent runs the tarball on the registry, not the repo you read on GitHub. npm pack with a package spec fetches it and saves <name>-<version>.tgz:
npm pack rea-agents@6.5.0
tar -xzf rea-agents-6.5.0.tgz
git clone https://github.com/morluto/rea
git -C rea checkout <release-tag-or-commit>
diff -rq package rea | grep -v '^Only in rea'
Check out the release tag, or the commit the provenance attestation names. The filter drops files that exist only in the repo, such as tests, and keeps two kinds of line: files only in the tarball, and files that differ from the tag. Built output that isn't committed will show up, and that is expected. Read everything else on that list, starting with what bin and the lifecycle scripts point at. Nobody reviewed those files on GitHub.
Where the pin goes, agent by agent
Each agent CLI keeps its own MCP config, so pinning in one file does nothing for the next agent:
| Agent | Project file | User file |
|---|---|---|
| Claude Code | .mcp.json |
~/.claude.json |
| Codex | .codex/config.toml |
~/.codex/config.toml |
| Antigravity CLI | .agents/mcp_config.json |
~/.gemini/config/mcp_config.json |
| Copilot CLI | see its docs | ~/.copilot/mcp-config.json |
| OpenCode |
opencode.json (mcp key) |
see its docs |
Sources: Claude Code, Codex (project file for trusted projects only), Antigravity CLI, Copilot CLI, OpenCode. Gemini CLI reads .gemini/settings.json, but its docs say that for unpaid-tier and Google One users it "was replaced by Antigravity CLI on June 18th, 2026."
The pinned entry in Claude Code and Codex, with a placeholder package:
claude mcp add --scope project your-server \
-- npx --ignore-scripts -y your-mcp-server@1.4.2
[mcp_servers.your-server]
command = "npx"
args = ["--ignore-scripts", "-y", "your-mcp-server@1.4.2"]
OpenCode takes the same arguments as one command array under "type": "local".
Treat .mcp.json like code. Claude Code asks before using project-scoped servers in an interactive session, but in claude -p, Agent SDK and cloud runs it "loads project-scoped servers without asking", per its docs. If your CI runs claude -p on the PR branch, a one-line change to that file in a pull request runs the next time that job starts.
Several agents, one repo, several config files
I build Hermes IDE, so weigh this section with that in mind. Hermes runs Claude Code, Codex, Antigravity CLI, GitHub Copilot CLI, OpenCode and other agents side by side, each in a real terminal "exactly as it would anywhere else", per its README. So each agent reads its own MCP file from the table. Pin a server in .mcp.json, start the same task in Codex, and Codex still runs whatever ~/.codex/config.toml says.

The ⌘N New task sheet's agent picker: Claude Code and Codex installed, the other supported agents listed as not installed, plus Custom agent.
Run the checklist once per server, then apply the pin once per agent you start. Each task can get its own hermes/ branch and worktree. A committed .mcp.json is a tracked file, so it comes along into every task's worktree. A worktree doesn't copy your home folder, so the user-level files are the same for every task.
If an agent edits the committed .mcp.json during a task, the edit lands on the task's branch, not yours. The Review Desk (⌘G, Ctrl+Shift+G on Windows and Linux) shows changes by turn or by file before you land anything, so a new server line shows up in a diff you read before merging. An edit to a user-level file in your home folder is outside the repo, so no diff shows it. Check those files yourself.

The Review Desk (⌘G) in By turn view: two turns of a Claude Code task, turn 1's diff, the changed files and a commit message drafted from the turns.
Hermes doesn't vet MCP servers. The eight checks are still yours to run. It is free, source-available under BSL 1.1, and runs on macOS, Windows and Linux. It is not affiliated with Nous Research, Anthropic, OpenAI, Google, GitHub or any agent vendor named here.
If this was useful
Every MCP server is a bundle of tools your agent can decide to call, with your permissions. AI That Acts builds tool calling, functions and a first working agent in TypeScript, so you see what a tool can reach once the model picks it. That is the frame for deciding which servers earn a line in your config.
If you run several agents, download Hermes IDE, and star it on GitHub if it helps.
The whole AI in TypeScript series, from your first LLM call to agents in production: all five books.














