Spec Driven Development
MCP Server that facilitates spec-driven development workflows, not just Vibe Coding.
Description from the maker (original English)
Persistence hook auditor: scan repos for auto-exec hooks, flag Shai-Hulud patterns.
These three are never merged into one score: opinions, measurements and popularity are different things.
We have not written a plain-language summary for this one yet. Below are the facts we read from the registry.
Pick the AI app you use, then copy the settings.
You need
Which app do you use?
Pick your app above. We remember it, so next time the button says “Install in …”.
Open Claude Desktop → Settings → Developer → Edit Config, and paste this into claude_desktop_config.json
{
"mcpServers": {
"hookcheck": {
"command": "npx",
"args": [
"-y",
"@reneturcios/hookcheck",
"mcp"
]
}
}
}Run this in your terminal
claude mcp add hookcheck -- npx -y @reneturcios/hookcheck mcpPaste into .cursor/mcp.json in your project, or ~/.cursor/mcp.json
{
"mcpServers": {
"hookcheck": {
"command": "npx",
"args": [
"-y",
"@reneturcios/hookcheck",
"mcp"
]
}
}
}Your browser asks to open the app, then the app shows the settings and asks you to confirm. Key values are left empty for you to fill in.
Paste into .vscode/mcp.json in your project
{
"servers": {
"hookcheck": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"@reneturcios/hookcheck",
"mcp"
]
}
}
}Your browser asks to open the app, then the app shows the settings and asks you to confirm. Key values are left empty for you to fill in.
This one runs on your computer, and ChatGPT can only connect to servers on the internet. Pick another app.
Your choice stays on this device in a cookie; no account needed.
These settings are generated from the maker’s registry entry. If the README says otherwise, follow the README.
Runs on your computer; the tool list appears in your AI app after you install it.
We check information anyone can see; we have not read the code. Checks that do not apply are left out of the count. · Checked Nov 6, 2026
The registry name was verified with the GitHub account CloudCorpRecords, which owns the repo
We have not read this repo from GitHub yet
We have not read this repo from GitHub yet
This one runs on your computer; there is no internet endpoint
Runs on your computer and uses local settings instead
Runs on your computer; we can only read tool lists from internet endpoints
Compared with every active server in this category in the registry. Stars count only when the publisher owns the repo.
It passed 1 of 1 applicable basic checks. We have not read the code. Before adding a key or token, see what access the server asks for and use a key with only the permissions you need.
The registry does not list a required key, though some servers ask you to sign in on first use.
It runs on your computer. Your AI app starts it when needed, and data does not pass through the maker’s servers (unless it calls an online service itself).
The GitHub repo last changed –, and the latest version (0.1.18) reached the registry 14 days ago.
Apps that support MCP, such as Claude Desktop, Claude Code, Cursor and VS Code. You also need Node.js, Docker on your computer.
In the same category: Spec Driven Development, Remote Desktop Commander, debuff
Servers in the same category from other makers, sorted by stars.
Reading a card:★ GitHub starsiThree pillars: reviews · checks · usageiUpdated = latest version published to the registryi
MCP Server that facilitates spec-driven development workflows, not just Vibe Coding.
Remote Desktop Commander: use files, terminal and processes on your own computers from any AI app.
Let coding agents play, test and tune your 2D web game: live state, explained errors, replays, fuzz.
Copied from the maker’s repo as written. PopNx MCP has not reviewed or endorsed it. HTML, images and scripts are removed. Links lead off this site.
Find the hooks that run before you do. One scan, every auto-exec surface, one verdict.
npx @reneturcios/hookcheck scan ./some-repo
Modern supply chain attacks do not need you to run anything. They persist through hooks that run automatically: an npm postinstall script, a Claude Code SessionStart hook in .claude/settings.json, a VS Code task set to run on folder open. The Shai-Hulud npm worm used exactly this pattern, planting a preinstall dropper and then persisting through agent and editor hooks so it survived cleanup and stole credentials on the next open.
hookcheck scans a local repo for every auto-exec hook, classifies each one, and prints a single verdict: CLEAN, REVIEW, or COMPROMISED-PATTERN, with the file, command, rules, and evidence for every finding. Deterministic only. No LLM, no network in the core scan.
Reports in October 2026 described AI agents orchestrating attacks and a self-propagating npm worm that backdoored hundreds of packages. Its persistence layer was not exotic malware. It was ordinary config files that every developer and every coding agent already trusts: package.json install scripts, .claude/settings.json hooks, and .vscode/tasks.json folderOpen tasks. Defenders needed a fast local check that an agent itself can call before opening, installing, or trusting a repo. That is hookcheck.
| surface | what it looks for | |
|---|---|---|
package.json | preinstall, install, postinstall, prepare scripts. The root package.json and nested workspace package.json files (for example packages/*/package.json, apps/*/package.json, any directory up to 8 levels below the root, node_modules excluded) are inventoried the same way | |
.claude/settings.json and settings.local.json | SessionStart, PreToolUse, and other hook commands. Also discovered: the GNU stow layout (claude/settings.json, no dot) and settings nested one level under a config dir (for example config/.claude/settings.json). A settings path that exists as a directory is reported as a warning, not silently skipped | |
| Claude Code plugin hooks | hooks/hooks.json in a plugin root: at the repo root, or nested under plugin directories (for example plugins/ /hooks/hooks.json), with the plugin root at most 8 levels below the scan root. The file carries the same events map as the hooks key of .claude/settings.json (SessionStart, PreToolUse, PostToolUse, Stop, UserPromptSubmit, and the rest of the Claude Code event set) and its commands are classified exactly the same way. The sibling hooks/hooks-cursor.json variant carries the same command object shape under Cursor event names and is inventoried the same way | |
.vscode/tasks.json | tasks with runOn folderOpen, plus task inventory | |
.husky/ | every hook file and its full command content | |
lefthook.yml | run commands | |
.pre-commit-config.yaml | entry commands and remote repo URLs | |
.cursor/ and .codex/ | config commands and files | |
| MCP configs | .mcp.json, mcp.json, .vscode/mcp.json, .cursor/mcp.json, .windsurf/mcp.json, .codex/mcp.json servers that autostart, each inventoried exactly once | |
.github/workflows/ | run steps, as inventory, including block scalar bodies (`run: | and run: >, with chomping variants). Every workflow finding carries the line number of its run:` key |
.githooks/ | hook files |
Every surface above is discovered by one bounded recursive walk, not only at the scan root: a surface that appears inside a subdirectory (for example packages/foo/.claude/settings.json, sub/.vscode/tasks.json, or sub/.github/workflows/ci.yml) is inventoried from a parent scan with its real relative path in the finding, within the discovery bound documented under Limits. A surface file is inventoried exactly once however many discovery paths reach it.
Classification per hook:
env -u NAME unsets a variable, os.environ.setdefault(...) writes one key with a default, and a [ -n "$NAME" ] test checks existence. A hook whose target is a shell script is classified by that script's contents, not by the fact that it is a shell script. Comments are stripped before classification, so a docs URL inside a comment is not a fetch, and a fetch whose every URL is loopback (a localhost health check) is not treated as network behavior. In script files (JavaScript, Python), the contents of string and regex literals are data, not behavior: a defensive guard whose pattern list quotes eval( or curl | sh performs neither, and hookcheck does not classify it as an attacker. Literals handed to exec style calls (execSync("..."), os.system("...")) are code by construction and are still matched. Two rules are about value flow, not co-occurrence: ENV-EXFIL fires only when a credential value demonstrably flows into an outbound send (a request payload, a query string, a pipe into a network tool, or printed output next to a send), and CRED-EXFIL when credential store contents are piped or posted straight into a network send. Environment inheritance is not a send: when the only environment movement is inheritance or bulk access passed as a subprocess environment (env=os.environ, a {**os.environ, ...} spread, dict(os.environ), os.environ.copy(), and their JavaScript forms), ENV-EXFIL additionally requires a non-loopback destination in the flow, so a test harness that starts its own server on 127.0.0.1 and hands it the inherited environment does not verdict as exfiltration; the same forms serialized into a payload, or paired with a remote destination, still fire. The ENV-BULK evidence names the form it saw: inheritance and copy forms are described as bulk access in inheritance or copy form, not as a serialization or dump. A token used as authentication (an Authorization header, the userinfo of a push URL to the repo's own host, a --token flag) is authentication, not exfiltration, and never fires either rule. Authentication positions are recognized by shape, not only by the Authorization spelling: any curl -H header argument whatever its name, any dict or object entry whose key is an authentication header name (Authorization, x-api-key, *-api-key, *-auth-token, and friends) in a one hop script file, and any header shaped entry inside a headers construction (a dict assigned to a headers variable, a headers= argument, a headers property of a request options object) all count as authentication positions. Further authentication positions are recognized by the same shape principle: a curl -u or --user basic auth pair, the value configured for a git http.extraheader key (an Authorization header value however it was built), an ::add-mask:: registration (printing a value there is the masking idiom itself), a credential hand-off position (the value configured for a git credential.helper key, including the body of a shell function the helper value names: git reads the helper's echoed username and password lines as its authentication protocol, so that echo is presentation, not disclosure; the same family covers a core.askPass value, a GIT_ASKPASS or SSH_ASKPASS assignment, input piped into a git credential invocation, and a heredoc feeding one), and destination positions in the URL of a send itself: a credential that is an entire URL path segment, the whole URL, or the URL origin (capability URLs are credentials by design: a build hook URL, a preview token in the path, a token query parameter), the value of an authentication named query parameter (token, api_key, access_token, auth, secret, signature, key), a bare reference in the curl URL operand position, and the destination first argument of a fetch style call. One more authentication position in the same family (since v0.1.18): a credential written to ~/.npmrc, the npm client's own auth file, as the target of a redirection (the canonical npm publish step, echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc) is presented to its own client and does not count as credential store access on its own; reading ~/.npmrc, copying it, or posting its contents or the token anywhere else still counts at full strength. The body test decides: a credential presented in one of those positions with no credential or bulk data in the body is authentication, while the same credential in a body, in a data named query parameter, in a partial URL path segment, or in printed output still counts as exfiltration, and a capability URL shape plus credential data in the body still fires. Taint does not pass through a locally defined helper function's return value unless the helper itself returns tainted data (its body is analyzed as part of the same text), a printed length or count of a value (len(x), x.length, ${#NAME}) is metadata rather than a disclosure of the value, and a name occurrence that is an object key, a property name, an assignment target, or a parameter declaration is a binding, not a reference to the variable of that name. A shell variable assigned in the same text from a value containing no credential atom (a token named response variable bound to a literal file path, used as a curl --output sink), or bound via a command substitution whose only credential atoms sit in destination positions (an HTTP status code variable), is not itself a credential value. Fetch detection requires command position: curl or wget counts as a network fetch only when it is the invoked command (the head of a statement or pipeline segment, after sudo or env wrappers, or the head of a command string handed to an interpreter), never when the word merely names a package in an install line (pacman -S curl, apt-get install curl). In script files the remote URL rule follows the same data view as the other behavior rules: a URL that appears only inside a string literal or a comment (an error message, a documentation link, an API base constant) is data, while a URL handed to a network call (fetch, requests, http.get, urlopen, axios, sendBeacon) is a fetch target and still fires. Inline code execution (DOWNLOAD-EXEC) fires only when the evaluated content itself carries a download or remote reference; constant inline code such as a node -e version comparison downloads nothing and is not flagged. Since v0.1.17 the same rule also fires on the file level loader shape: when content fetched over the network reaches an eval style call (eval, Function, exec, a vm runIn context call), directly or through variables (a fetch result bound to one variable, its body text bound to a second, the second handed to eval), the finding fires DOWNLOAD-EXEC together with REMOTE-EVAL and verdicts COMPROMISED-PATTERN. A fetch whose result is only logged or parsed as data, and an eval of a constant or of a variable with no fetch in its derivation, do not fireOnly the beginning is shown; read the rest in the repo