servers

What does it do?

This repository collects reference implementations of Model Context Protocol (MCP) servers, which are programs that give large language models controlled access to tools and data sources. [1a][1b] It is meant to demonstrate MCP features and software development kit (SDK) usage as educational examples for developers building their own MCP servers, not as production-ready solutions. [2a][2b]

Activity, over time

Daily observations from the public event archive.

Attention

Daily stars & forks

Loading attention chart

Star accumulation

Cumulative stars

Loading star accumulation chart

Pull requests

Daily opens & merges

Loading pull requests chart

Conversations

Daily issues & comments

Loading conversations chart

Community

Daily accounts & reviews

Loading community chart

Releases

Daily releases

Loading releases chart

Mentions

44posts · 30d
Nov 41 post
  1. DEV · · @rulestack

    A nested .git file let the Git MCP server out of its --repository until 2026.10.28. Which version does your uvx run? --- published_at: 2026-11-04 02:17 +0000 --- > mcp-server-git --repository <path> is the one setting that keeps the reference Git MCP server inside a single repository. An advisory published on 2026-10-28 showed that a nested directory with a .git file pointing elsewhere let tool calls read and change a repository outside that boundary; the fix is version 2026.10.28. The configuration the project's README shows runs the server through uvx with no version in it, and a sibling advisory for the filesystem server, a regression, shows what each way of pinning costs. What the advisory found The advisory is GHSA-w766-5x43-whvw, mcp-server-git: nested gitdir file bypasses --repository confinement, reported by ShieldPi on 2026-10-27 and rated Moderate (CVSS 5.3). Affected versions are everything before 2026.10.28, which is the patched version. On PyPI, 2026.10.28 was uploaded on 2026-10-28 at 21:06 UTC, and it was still the newest release when we checked again on 2026-11-03 (UTC). It is not the first advisory about keeping this server inside a repository. The repository's advisory list also has GHSA-j22h-9j4x-23w5, "Missing path validation when using --repository flag in mcp-server-git", published on 2025-12-17 with 2025.12.18 as the patched version, and GHSA-vjqx-cfc4-9h6v, "Path traversal in git_add allows staging files outside repository boundaries in mcp-server-git", published on 2026-02-25 with 2026.1.14 as the patched version. Each fix closed one way out of the boundary. That history is the practical reason to know which version your launch command runs, which is where this article ends up. The mechanism rests on an ordinary Git feature. Git's repository layout documentation explains that instead of a .git directory "you can have a plain text file .git at the root of your working tree, containing gitdir: <path> to point at the real directory that has the repository." Submodules and worktrees use this every day. The server checked the path it was given, not the repository Git ended up opening. In the report's words, the path check "canonicalize[s] only the caller-supplied repo_path and check[s] that it is inside allowed_repository", and then the repository is opened, at which point "GitPython resolves that redirection later. There is no check after opening the repository to ensure that the repository actually selected by Git is the allowed one." The reporter confirmed two effects through the real MCP interface: reading committed content from the outside repository, and creating a branch in it. "This bypasses the server's claimed repository boundary and permits unauthorized read and modification with the MCP process's privileges." The report is also explicit about what it does not claim: it "does not claim arbitrary host-file write or code execution", and it is "not claimed to be an unauthenticated standalone remote exploit". The precondition is a second server The interesting part of the report is how the nested .git file gets there. The caller has to be able to "create a nested directory and a .git file inside the allowed repository, or cause such content to be placed there", and to guess the path of another repository the process can reach. The report names one realistic setup: "a deployment that also gives a filesystem MCP server write access to the same allowed directory." At the tested commit, the filesystem server's write_file "has no special .git exclusion." That is a plausible configuration. Both servers live in the same reference repository, both are easy to point at the same project in Claude Code or Cursor, and each server's boundary looks fine on its own. The combination is what crossed it. An agent steered by text it read, in an issue or a file, would only need to use both tools in the right order. What the README says after the fix The mcp-server-git README is worth rereading, because it states the boundary more precisely than most people configure it. "Without --repository, the server reads and writes any Git repository the server process can reach." The README adds that this holds "for the tools that change a repository too", and that "--repository is the only confinement the server offers". !Quote from the mcp-server-git README: without --repository, the server reads and writes any Git repository the server process can reach It also widens what the flag admits: "At startup the server walks up to the enclosing repository root", so "A path inside the repository therefore allows tool calls anywhere in that repository, not only under the subdirectory given." If you pointed --repository at packages/api hoping to keep the server out of the rest of a monorepo, it does not do that. And it describes the fix: a tool call's repo_path must be inside the repository, "and so must the directories Git then uses for it: a repository nested inside whose .git file, commondir or core.worktree points outside is refused." Which version does your launch command run? Here is the configuration the README gives for Claude Desktop, and the same command is what most people pass to claude mcp add: { "command": "uvx", "args": ["mcp-server-git", "--repository", "path/to/git/repo"] } There is no version in it. uv's tools guide gives two ways to say which version you want: "To run a tool at a specific version, use command@<version>", and "To run a tool at the latest version, use command@latest". It also notes that "the @ syntax cannot be used for anything other than an exact version." The bare name is a third choice, and the guide we read does not promise it means the newest release, which is reason enough to write one of the other two. The filesystem server's launch commands follow the same pattern with npx -y. npm's npm exec documentation says the CLI "utilizes its internal package cache when using the package name specified", and describes --prefer-online as the flag that "Forces staleness checks for packages, making the cli look for updates immediately even if the package is already in the cache." To see what Claude Code launches, claude mcp list shows each configured server and its status, and claude mcp get <name> shows the details of one, according to the MCP documentation. The command and arguments live in .mcp.json for project scope and in ~/.claude.json for local and user scope. Pin or float: the filesystem regression shows both costs Three days before the Git advisory, the same repository published GHSA-4gxg-m5jx-9j57 for server-filesystem. On a case-insensitive file system, such as the macOS and Windows defaults, versions 2026.8.31, 1.0.0 and 2.0.0 could create files outside the allowed directories through a symlink spelled with different letter case. The detail that matters for pinning is one line: "It is a regression: 2026.7.10 refuses these requests." The fix is 2.0.1. The npm registry's release times make the trade-off concrete: | How the version is chosen | What it got from July to October 2026 | | :- | :- | | Exact pin to 2026.7.10 | Never had this regression, and never got any later change either (whether it is affected by the other advisories published that day is a separate question) | | Always the newest release | Picked up the regression on 2026-08-31 and the fix on 2026-10-25 (2.0.1) | | Exact pin to 2.0.1 | Fixed, and stays there until someone edits the command | Neither column is free. An exact pin keeps out a bad release, including a malicious one pushed with a stolen token, but it also keeps out the next security fix until a person moves it. Floating gets fixes as soon as they ship and regressions just as fast. What we would avoid is the bare name, where the version is decided by a cache you did not choose. What to change today 1. Check the version. Run claude mcp get <name> for your Git server and look at its arguments. If it says mcp-server-git with no version, you do not know whether you are on 2026.10.28. 2. Write the version you mean. For an exact pin, uvx mcp-server-git@2026.10.28 --repository /path/to/repo. For always-newest, uvx mcp-server-git@latest .... With claude mcp add, the command goes after --, as in claude mcp add git -- uvx mcp-server-git@2026.10.28 --repository /path/to/repo. 3. Do the same for server-filesystem. Version 2.0.1 or later fixes the case-variant symlink problem. The npm registry still listed 2.1.0 as the latest release on 2026-11-03 (UTC). 4. Always pass --repository. The README's warning about running without it is the clearest sentence in the whole document, and the flag admits the whole enclosing repository, so pick the repository you mean. 5. Look at the servers together. If a filesystem server can write inside the directory a Git server is confined to, the precondition of this advisory is already met. Before 2026.10.28 that pair could cross the Git boundary; after it, the Git server refuses the redirect, which is the fix you want installed. We read the boundary flags of MCP servers as promises from one server. This advisory is a reminder that they are promises about one server's own checks, and that the agent holding several tools at once can combine them in ways no single server sees. Do you pin your MCP server versions in .mcp.json, float on @latest, or leave the bare name? If you have a way to get told when a pinned server has a fix waiting, describe it in the comments below.

  2. Fetch MCP 能让 AI 把网页读成 Markdown,源码把请求超时设为30秒。 官方默认拦截内网地址,但注明挡不住 DNS 重绑定。给 agent 接网页读取工具,仍要限制它能访问的网络,别当成完整隔离。 modelcontextprotocol/servers

    3 likes · 93 views
  3. mcp-server-git's --repository should keep it in one repo, but a nested gitdir file could point it at another, with read and write access. Fixed in 2026.10.28: github.com/modelcontextprotocol/servers/security/… What else fences your MCP servers? #MCP #Security

  4. DEV · · @aobaworks

    Claude Code MCP Servers: Setup, Config & Cron-Safe Template Originally published at Unattended. Claude Code MCP servers are external programs or hosted endpoints that give Claude Code extra tools, such as a browser, GitHub, Sentry or a database. You add one with claude mcp add, check it with claude mcp list and remove it with claude mcp remove <name>. In the words of the official MCP docs, "MCP servers give Claude Code access to your tools, databases, and APIs." Most guides stop at the interactive setup. This one also covers what changes when nobody is at the keyboard: which servers a cron run loads, how to scope their permissions, which timeouts apply, and what happens when a server goes down partway through a job. Which option is right for you? Choose the scope and transport that fit how you work, then open the matching page. Each row gives a one-line reason. | Your situation | What to pick first | First page to open | |---|---|---| | Beginner, never added a server | One hosted HTTP server at the default local scope. It needs no install or sign-in | Connect to MCP servers (quickstart) | | Daily interactive work across many repos | --scope user for the 1–3 servers you use everywhere, because they load in every project | MCP reference: scopes | | Small team sharing one repo | --scope project (.mcp.json committed), so teammates get the same config | Quickstart: Edit .mcp.json directly | | Cron / headless jobs | A separate JSON file passed with --mcp-config plus --strict-mcp-config, so the job loads only what you list | Run Claude Code programmatically | | Tight token budget | Fewer servers, because every connected server's tool names load into each session | Quickstart note on context | For a cron job, keep the automation's server list out of your personal config. The rest of this guide explains why. What is a Claude Code MCP server? A Claude Code MCP server is a process or hosted URL that exposes extra tools to Claude Code over the Model Context Protocol. Claude Code calls those tools like its built-in ones. The quickstart describes it as letting Claude Code use tools "beyond its built-in set, such as searching an issue tracker, querying a database, or controlling a web browser." Servers come in two shapes: - Local (stdio): Claude Code starts the program as a subprocess on your machine. The Playwright server is one example. Use this shape for anything that needs local resources such as a browser, the filesystem or a database socket (quickstart). - Remote (HTTP): the vendor hosts the server and you register its URL. Anthropic's remote MCP announcement pitches this as the lower-maintenance option because you have no local server to manage. Each tool appears to Claude under a prefixed name, mcp__<server>__<tool>. That name matters later, because permission rules match against it (permissions docs). So, can Claude Code use MCP servers? Yes, in every surface. The CLI, the desktop app, VS Code and cloud sessions can all connect. Connectors you add at claude.ai also load in the CLI when you sign in with that account (quickstart: Connect from other surfaces). How do you install Claude Code MCP servers? For most servers, installing is the same step as registering. You run claude mcp add, and either Claude Code fetches the package through npx the first time it starts the server, or you point it at a hosted URL. Prerequisites, per the quickstart: 1. Install and authenticate Claude Code. 2. Open a terminal in a project directory. An empty directory works. 3. For npx-based local servers, install Node.js. The Playwright server, for example, requires Node.js 18 or later. Then run the add command in your shell, not inside a claude session: # Remote example from the docs: no auth needed claude mcp add --transport http claude-code-docs https://code.claude.com/docs/mcp # Local example from the docs: Playwright browser server claude mcp add playwright -- npx -y @playwright/mcp@latest Common install mistakes and fixes: - "Added" but ✘ Failed to connect. The Added message only means the entry was saved. On the first check, npx may still be downloading the package, so wait and run claude mcp list again (quickstart). - Startup times out. The default startup timeout is 30 seconds. Raise it with MCP_TIMEOUT=60000 claude. The value is in milliseconds (quickstart troubleshooting). - Need to diagnose a local server. Run the server command yourself, for example npx -y @playwright/mcp@latest. If it starts and waits for input, the server works and the problem is in your Claude Code entry. If you already set up servers in the Claude Desktop chat app, claude mcp add-from-claude-desktop copies them into the CLI on macOS or WSL (quickstart). How do you configure Claude Code MCP servers (config file)? Claude Code stores MCP server configuration in ~/.claude.json or a project's .mcp.json, depending on scope. ~/.claude.json holds the local and user scopes, and .mcp.json in the project root holds the project scope. The file depends on the --scope you pick (quickstart). | Scope | File | Who gets it | |---|---|---| | local (default) | ~/.claude.json, under this project's entry | Only you, only this project | | project | .mcp.json in project root | Everyone who clones the repo | | user | ~/.claude.json, top-level mcpServers | Only you, all projects | On Windows, ~/.claude.json resolves to %USERPROFILE%\.claude.json. If CLAUDE_CONFIG_DIR is set, Claude Code reads the file from that directory instead (quickstart). When one name exists in several scopes, precedence runs local, then project, then user, then plugins, then claude.ai connectors (MCP reference). Here is a hand-written .mcp.json example. The first two entries come from the docs. The ${...} variables and the timeout field are documented options: { "mcpServers": { "claude-code-docs": { "type": "http", "url": "https://code.claude.com/docs/mcp" }, "playwright": { "type": "stdio", "command": "npx", "args": ["-y", "@playwright/mcp@latest"] }, "internal-api": { "type": "http", "url": "${API_BASE_URL:-https://api.example.com}/mcp", "headers": { "Authorization": "Bearer ${API_KEY}" }, "timeout": 600000 } } } The details that matter, all from the MCP reference: - ${VAR} and ${VAR:-default} expand in command, args and env for stdio servers, and in url and headers for HTTP servers. Keep tokens in the environment, not in a committed file. - Some credentials always read as empty in remote URLs and headers, on purpose: ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, NPM_TOKEN and a few others. If your header comes out blank, this is likely why. - An entry with a url but no type is an error. Add "type": "http". - A per-server timeout in milliseconds overrides MCP_TOOL_TIMEOUT for that server only. Pitfall: editing the wrong file. Claude Code does not read ~/.claude/.mcp.json, ~/.claude/mcp.json, ~/.claude/config/mcp.json or %APPDATA%\Claude\mcp.json (quickstart troubleshooting). Edits to .mcp.json take effect only when a new session starts (quickstart troubleshooting). A malformed entry is skipped while the others still load, so run claude mcp list and look for the parse warning (quickstart troubleshooting). How do you add and remove a Claude Code MCP server? You add a Claude Code MCP server with claude mcp add (or add-json) and remove it with claude mcp remove <name>. Both commands print the file they modified (quickstart). Add, by server type (MCP reference): 1. Remote HTTP: claude mcp add --transport http <name> <url> 2. Remote with a static token: append --header "Authorization: Bearer <token>". The docs' GitHub example is claude mcp add --transport http github https://api.githubcopilot.com/mcp/ --header "Authorization: Bearer YOUR_GITHUB_PAT". 3. Local stdio: claude mcp add [options] <name> -- <command> [args...], for example --env AIRTABLE_API_KEY=YOUR_KEY. 4. Raw JSON: claude mcp add-json <name> '<json>', which is also the only way to add a WebSocket (ws) server. 5. OAuth servers such as Sentry (https://mcp.sentry.dev/mcp): add the URL, start claude, run /mcp, select the server and choose Authenticate. You can also run claude mcp login <name> from the shell. The -- separator is the most common mistake. Your own options (--transport, --env, --scope) go before it, and the docs say "Everything after -- is passed to the server untouched" (MCP reference). If claude mcp get <name> shows a different command from the one you typed, remove the server and add it again with -- in place. Remove or pause: claude mcp remove claude-code-docs # default scope claude mcp remove claude-code-docs --scope local # when it exists in several scopes If the name exists in more than one scope, remove reports exists in multiple scopes, and you choose one with --scope (quickstart). To pause a server without deleting it, toggle it off in the /mcp panel. Claude Code records that choice per project in a disabledMcpServers list in ~/.claude.json (MCP reference). You can't change a server's scope in place. Remove it and add it again at the new scope (quickstart). How do you connect Claude Code to local vs remote MCP servers? Claude Code connects to local servers over stdio and to remote servers over HTTP. SSE still works but is deprecated, and WebSocket can only be configured through JSON (MCP reference). For unattended runs, the biggest difference between them is how each transport fails and recovers. | | Local (stdio) | Remote (HTTP) | |---|---|---| | Runs where | Subprocess on your machine | Vendor's infrastructure | | Auth | Env vars (--env) | Header token, OAuth, or headersHelper script | | Default idle timeout per tool call | 1,800,000 ms (30 min) | 300,000 ms (5 min) | | If it drops mid-session | In claude -p, retried on the next tool call | Up to 5 retries with exponential backoff starting at 1 s | | If first connect fails | Marked failed; use /mcp Reconnect | Transient errors retried up to 3 times | | Main risk unattended | Missing runtime (Node), slow first npx download | Network, expired token, vendor outage | The idle timeout figures come from the environment variables reference (CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT, v2.1.187 or later). The retry behavior comes from the MCP reference. Authentication errors are different: Claude Code stops retrying and waits for you to sign in again. A cron job can't do that, which is why OAuth servers fit unattended runs poorly unless their credentials stay valid. Quick connectivity check. For an HTTP server, run curl -I <url>. A 404 or 405 still means the server is up, because many MCP endpoints answer only POST. A 401 or 403 means you need to authenticate. No response at all points to the URL or the network (quickstart troubleshooting). How do you use a Claude Code MCP server in a session? You use a Claude Code MCP server by asking for the task in plain language. Claude picks the relevant tool on its own, and you approve the first call when prompted (quickstart). 1. Run claude mcp list and confirm the server shows ✔ Connected. 2. Start claude in the same project. Servers at local scope are tied to the directory where you added them. 3. Ask for something that needs the server, for example: Use playwright to open https://example.com and tell me the page title. 4. Approve the permission prompt. Each tool call is labeled with the server name, such as browser_navigate from playwright. 5. Run /mcp at any time to see status and tool lists, reconnect a server or authenticate. Naming the server in your prompt isn't required. The docs do it in the walkthrough so the test definitely goes through the new server rather than a built-in tool such as web fetch. Watch the output size. Claude Code shows a warning when an MCP tool returns more than 10,000 tokens and caps output at 25,000 tokens by default. MAX_MCP_OUTPUT_TOKENS changes the cap (MCP reference). Large database or log queries hit that cap quickly. For ways to trim them, see how to see token usage and reduce it. Status meanings you'll see (quickstart): - ! Needs authentication: the server is reachable but needs a browser sign-in or a token. - ! Connected · tools fetch failed: the server connected but couldn't list its tools. Run claude mcp get <name> for details. - ⏸ Pending approval: a project-scoped server you haven't approved yet. - The server connects but shows no tools: usually a missing required env var, such as an API key. Claude Code MCP servers list: which ones are worth running? For recurring solo-builder work, start with two or three servers that each cover a job you would otherwise script by hand. Each server you connect adds context cost and permission surface (quickstart). | Server | Transport | Good for | Not for / limitation | |---|---|---|---| | Claude Code docs (https://code.claude.com/docs/mcp) | HTTP, no auth | Your first test; looking up flags | Only searches docs, no real work | | GitHub (https://api.githubcopilot.com/mcp/) | HTTP + PAT | Triage issues and PRs on a schedule | The token's scope is the blast radius; use a fine-grained PAT | | Playwright (@playwright/mcp) | stdio, Node 18+ | Smoke-checking a deployed page | Needs a browser on the host, heavy on a small VPS | | Sentry (https://mcp.sentry.dev/mcp) | HTTP + OAuth | Morning error summaries | OAuth expiry can block cron runs | | Filesystem / Git / Fetch / Time (reference servers) | stdio | Learning, narrow local tasks | Reference code only (see below) | Sources: the quickstart for docs, Playwright and Sentry, the MCP reference for GitHub, and the modelcontextprotocol/servers repo for the reference set. Two things many "best MCP servers" lists get wrong as of October 2026. First, the official repo now maintains seven reference servers: Everything, Fetch, Filesystem, Git, Memory, Sequential Thinking and Time. Second, it archived thirteen others, including GitHub, PostgreSQL, Slack, Puppeteer and SQLite, in servers-archived (servers repo). If a tutorial tells you to install the old GitHub or Postgres package from that repo, look for the vendor's own server instead. Where do you find and vet Claude Code MCP servers on GitHub? Find Claude Code MCP servers in the official MCP Registry, Anthropic's reviewed Anthropic Directory, or vendor GitHub repos. Vet each one before an automation depends on it. The official repo says plainly that its servers are educational examples, "not as production-ready solutions" (modelcontextprotocol/servers). Anthropic's MCP page adds: "Verify you trust each server before connecting it." It also warns that servers which fetch external content carry prompt injection risk (MCP reference). A short vetting checklist before a server goes into a scheduled job: 1. Who publishes it? Prefer the vendor's own server (GitHub, Sentry, Microsoft's Playwright) over a third-party wrapper. 2. What does the token allow? A server can only do what its credential permits. Create a dedicated token with read-only or single-repo scope where the vendor offers it. 3. Is it pinned? @latest in npx args can change under a cron job overnight. Pin a version in automation configs, for example @playwright/mcp@<version>, and update it on purpose. 4. Does it read untrusted content? Web fetchers, issue readers and inbox readers can carry injected instructions. Pair them with narrow allow rules (next section). 5. Is it still maintained? A recent commit history and open-issue activity matter more than star count. Also be careful with similar names: projects like steipete/claude-code-mcp run Claude Code itself as an MCP server for other agents (GitHub). That's the opposite direction from this guide. How do you run Claude Code MCP servers headlessly and safely? Give each headless job its own server file, allowlist named MCP tools, set short timeouts, and check server status in the output. A claude -p run has nobody to answer prompts. Why your interactive setup is risky in cron Without --bare, claude -p loads the MCP servers in the working directory's .mcp.json, and the headless docs note: "A -p session shows no workspace trust dialog and no per-server approval prompt." The approval that protects you interactively is skipped. Two flags close the gap: - --bare skips auto-discovery of MCP servers, hooks and CLAUDE.md, and connects only servers you pass with --mcp-config. The limitation: bare mode never reads OAuth or keychain credentials, so it needs ANTHROPIC_API_KEY and won't use your Pro or Max login (headless docs). - --strict-mcp-config with --mcp-config loads only the servers in that file and ignores other MCP configuration (CLI reference). Use this one if you run on a subscription. One open report says it does not override a project's disabledMcpServers list (issue #14490), so don't disable your automation's servers in /mcp. Permission scoping From v2.1.302, a -p run starts in Manual mode when nothing else sets a mode. Any call that needs approval is denied unless an allow rule, a PermissionRequest hook or a permission prompt tool approves it (headless docs). Use allow rules to list exactly which MCP tools the job may call: - mcp__github allows every tool from the github server. - mcp__github__get_issue allows a single tool. The rule format is described in the permissions docs. Prefer naming single tools for jobs that write anything. Also add --permission-prompts none (v2.1.259 or later). With it, Claude is told nobody can approve, so it stops retrying denied calls, and any MCP elicitation request that no hook answers is cancelled (headless docs). For a complete rule set, see the headless permissions template. Timeouts that fail fast | Variable | Default | What it bounds | |---|---|---| | MCP_TIMEOUT | 30 s | Server startup; with --mcp-config in -p, the first turn waits up to this | | CLAUDE_CODE_MCP_STARTUP_WAIT_MS | (uses the startup wait) | How long the first non-interactive turn waits for servers that are still connecting (v2.1.274+) | | CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT | 5 min network / 30 min stdio | A tool call with no response or progress | | Per-server timeout in JSON | none | Overall tool time for that server | | CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS | 120,000 ms | When a long MCP call moves to the background | Sources: headless docs, env vars, MCP reference. The last row matters most for cron. After 2 minutes, a long MCP call moves to the background. The headless docs say that when a -p run ends, for a call that automatic backgrounding moved, "the call's result is lost". Setting CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS=0 turns automatic backgrounding off (env vars). Also set CLAUDE_CODE_MCP_ALLOWLIST_ENV=1. Stdio servers then get only a safe baseline environment plus their configured env, instead of inheriting every secret in your shell (env vars). What happens when a server is unreachable The run does not crash on its own. An entry that fails validation is skipped and the run "continues and exits cleanly" (headless docs). A server that never connects means its tools are simply missing, so Claude may finish the task without them and still exit 0. Your script has to check for that. The system/init event in stream-json output lists mcp_servers (each with name and status) and mcp_server_errors. A redirected stderr shows no warning, so read these fields instead. Exit code 143 means the process got SIGTERM (headless docs). The following example wrapper adapts documented flags. Check the status strings your version emits before relying on the filter. #!/usr/bin/env bash set -euo pipefail cd /srv/myproject export MCP_TIMEOUT=15000 export CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT=120000 export CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS=0 export CLAUDE_CODE_MCP_ALLOWLIST_ENV=1 timeout 20m claude -p "Summarize issues labeled bug opened in the last 24h" \ --mcp-config ./automation.mcp.json --strict-mcp-config \ --allowedTools "mcp__github__list_issues,mcp__github__get_issue" \ --permission-prompts none \ --output-format stream-json --verbose > run.jsonl # Fail the job if any server is not connected (example filter) if jq -e 'select(.type=="system" and .subtype=="init") | (.mcp_server_errors // []) + [.mcp_servers[] | select(.status!="connected")] | length > 0' run.jsonl >/dev/null; then echo "MCP server missing; result not trusted" >&2; exit 2 fi Scheduling, retries and alerting belong in your cron layer. See the cron job setup guide and catching silent failures. For every -p flag and the auth trade-offs, see the headless mode guide. Next steps Do this today: 1. Run claude mcp add --transport http claude-code-docs https://code.claude.com/docs/mcp, then claude mcp list. That takes about two minutes and confirms your setup works. 2. Create automation.mcp.json containing only the servers your cron job needs, with pinned versions and ${VAR} tokens. 3. Run the wrapper above once by hand and inspect the system/init line in run.jsonl before you schedule it. If you'd rather not run servers yourself: use vendor-hosted remote servers from the Anthropic Directory, so the vendor handles uptime and updates. You still configure scopes and tokens. Claude Code itself requires a paid plan: Pro is $20/month billed monthly ($17/month on annual billing), and Max starts at $100/month (as of October 2026; check the official pricing page). Pro suits one person running a few scheduled jobs. It's a poor fit if you need --bare everywhere, because bare mode needs an API key billed separately through the Claude Console (headless docs). Related guides on this site: - Claude Code Headless Mode: Flags, Auth and Cost (2026) - Claude Code Permissions Best Practices: Headless Template - Claude Code Cron Job Setup: Run Automations Overnight - AI Agent Monitoring in Claude Code: Catch Silent Failures FAQ What happens if an MCP server becomes unreachable mid-run in Claude Code? For HTTP servers, Claude Code retries up to 5 times with exponential backoff starting at 1 second. For stdio servers in claude -p, it retries on the next tool call (MCP reference). If the server stays down, that server's tools fail but the run can still exit 0. Check mcp_servers in the system/init event and treat a missing server as a failed job. Can Claude Code use multiple MCP servers at once? Yes. List as many as you need under mcpServers in one file. Each server's tool names and instructions load into the context window, though, so remove ones you don't use (quickstart). Tool search reduces that cost when you have many tools (MCP reference). Where is the Claude Code MCP servers config file on each OS? Local and user scopes live in ~/.claude.json on macOS and Linux, and in %USERPROFILE%\.claude.json on Windows. Project scope is .mcp.json in the repo root. Run claude mcp get <name> to see which scope holds a server (quickstart). Is it safe to run Claude Code MCP servers in a cron job without review? It's reasonably safe only when the job loads a fixed server list (--strict-mcp-config or --bare), allows named MCP tools only, and uses narrowly scoped tokens. Without those, a -p run connects whatever .mcp.json the directory contains, with no approval prompt (headless docs). Servers that read web pages or issues can also carry prompt injection. Sources - Claude Code Docs: MCP reference - Claude Code Docs: Connect to MCP servers (quickstart) - Claude Code Docs: Run Claude Code programmatically (headless) - Claude Code Docs: Environment variables - Claude Code Docs: Configure permissions - Claude Code Docs: CLI reference - Anthropic: Remote MCP support in Claude Code - GitHub: modelcontextprotocol/servers - GitHub: modelcontextprotocol/servers-archived - MCP Registry - Anthropic Directory - GitHub issue #14490: --strict-mcp-config and disabledMcpServers - GitHub: steipete/claude-code-mcp - Claude pricing --- This article was created with the help of AI. Sources are linked inline; check the official documentation for the latest details.

    2 reactions · 2 comments
  5. A useful detail in MCP reference servers v2.2.0: the Everything server blocks non-public addresses throughout fetch redirects. Checking the starting URL alone leaves later hops unchecked. Glad to see the protection follows the redirects too. github.com/modelcontextprotocol/servers/releases/…

    2 likes · 98 views
  6. Nobody mentions the part of the official MCP repo you can run offline. 91,564 stars, and inside it: seven reference servers. filesystem, git, fetch, memory, sequential thinking, time, and one test server. modelcontextprotocol/servers

    2 likes · 75 views
  7. The MCP Transport Layer Blind Spot This was first published on my site and is cross-posted here; the version on my site is canonical and the one I keep current. It is authorized research: I ran mcp-witness, an open-source scanner I built, across Python MCP servers published to PyPI, and reproduced the most serious finding only on infrastructure I own, under responsible disclosure with a 90-day embargo observed before anything was published. If you want the tool rather than the writeup, it is at desledishant10/mcp-witness. TL;DR Between May and June 2026 I disclosed six vulnerabilities across six PyPI-published Python MCP servers, in two distinct classes. Outbound SSRF in URL-fetching tools (two packages: Anthropic's reference mcp-server-fetch and the community-published mcp-server-http-request) lets an agent coerced via prompt injection retrieve cloud-metadata-service credentials from any host where IMDS is reachable. Inbound DNS rebinding on HTTP-transport MCP servers (four packages: mcp-streamablehttp-proxy, mcp-fetch-streamablehttp-server, fastmcp-http, and mcp-server-fetch-sse) lets any web page the operator visits issue tool calls against their locally running MCP servers, with no agent prompt injection required. The worst case, mcp-server-fetch-sse, composes both: a browser-driven DNS rebind to reach the server, then an inherited pre-fix SSRF inside the wrapped fetch tool to exfiltrate IMDS credentials. The SSRF half landed a fix fast and then stalled. PR #4226 by external contributor @kgarg2468 shipped within 10 days of the original disclosure, with a fix shape more defensive than the disclosure asked for (per-redirect validation closes the redirect-bypass class), and I independently verified it on 2026-05-22. But the maintainers closed the disclosure as "not planned," and as of my last check on 2026-09-10 that PR is still open and unmerged while vulnerable releases keep shipping to PyPI. The DNS-rebind half is worse: across four independently authored packages and three maintainer teams, zero fixes shipped. All four are unchanged on PyPI and still vulnerable at the disclosed versions as of 2026-09-10. Two caveats up front. (1) On Amazon Linux 2023 instances launched with default IMDSv2-Required metadata options, the SSRF half is mitigated at the host level: the package does not send the required token header, so IMDS responds 401. The bug is still real because IMDSv2 is not required on older AMIs, operators legitimately set it to Optional for compatibility, GCP and Azure metadata services have different default postures, and RFC 1918 internal services have no IMDSv2 equivalent. (2) DNS rebinding does not depend on the agent at all. That is the point. The browser is the attack surface. The takeaway is not "six buggy packages." It is "two architectural anti-patterns at the MCP transport layer, repeating across independently authored servers." Both reduce to the same shape: external constraint, missing in-package enforcement. What MCP is and why this surface exists The Model Context Protocol, adopted by Anthropic's Claude, OpenAI's ChatGPT, Cursor, Zed, and a growing list of AI hosts, lets agents discover and invoke tools defined by external MCP servers. A typical server exposes a list of tools, each with a name, a natural-language description, and a JSON Schema for arguments. The agent sees those, decides when to call them, and feeds the results back into the conversation. MCP servers come in two transport flavors. Stdio transport is the original: the server runs as a subprocess of the agent host, communicating over stdin and stdout. HTTP transport, including the newer streamable-HTTP and the older SSE variant, has the server running as a network service with the agent host as a client. Stdio servers face one security surface: what the agent asks them to do. HTTP servers face two: what the agent asks them to do, and what anything else on the network can ask them to do. The HTTP-transport servers in this post are vulnerable on the inbound side; the stdio fetch servers are vulnerable on the outbound side. Together they cover both halves of the transport boundary, each leaking in the way it is built to function. Class 1: outbound SSRF in URL-fetching tools For HTTP-fetch tools, the most common kind of MCP server by my PyPI survey, the implementation boils down to a single function: take a URL from the agent's arguments, fetch it, return the response. There is almost always a parameter named url typed as string with format: uri. The server trusts the agent to be responsible. For most non-cloud deployments, that is fine. For cloud-hosted agent hosts, it is a credential-exfiltration primitive waiting to be activated. Discovery: a static rule on the captured tools/list I built mcp-witness (formerly mcp-scan; renamed mid-survey to avoid collision with an established tool of the same name) as a security testing toolkit for MCP servers: a static analyzer, a dynamic harness, and a capability classifier. The static-analyzer rule MCP-S-009 flags URL-fetching tools that lack any apparent allowlist: - the tool has a parameter named url / uri / endpoint or with format: uri, and - no JSON Schema pattern / const / enum constraint on that parameter, and - no validation keywords in the tool description (allowlist, scheme is, restricted to, and similar). It is a heuristic, necessary but not sufficient, and a high-precision "review this." Run against the captured tools/list from mcp-server-fetch: $ mcp-witness-capture --server-cmd python --server-arg=-m --server-arg=mcp_server_fetch -o /tmp/fetch.json $ mcp-witness-analyze /tmp/fetch.json [HIGH] MCP-S-009 <captured>:0 fetch Tool has URL parameter(s) ['url'] with no schema-level constraint and no validation keywords in the description. Likely no scheme allowlist or host denylist. Verify against SSRF to link-local / loopback / cloud- metadata addresses. Same finding against mcp-server-http-request, multiplied by five (one per HTTP method). Dynamic verification: not just a pattern, a path Static rules say "this might be vulnerable, look closer." For confidence you need a dynamic probe. MCP-D-003 connects to a running server and tries a battery of test URLs: the harness's own canary (proves arbitrary egress), AWS IMDSv1, the ECS task-role endpoint, GCP metadata.google.internal, Azure IMDS, loopback ports, file:// URIs, and non-HTTP schemes (gopher, dict). Against mcp-server-fetch on a dev machine, the result was interesting and misleading. The canary hit (it is a fetch tool, it fetched). The metadata addresses all failed with "Failed to fetch robots.txt due to a connection issue". The file:// URIs did the same. It looked like it had defenses. But the mechanism is telling. mcp-server-fetch does a politeness check: it fetches robots.txt first and proceeds to the requested URL only if robots.txt was reachable. On a dev machine, 169.254.169.254 is not routable, so the robots.txt fetch fails at the TCP layer and mcp-server-fetch bails. That is an accidental SSRF defense, not a deliberate one. It only works when the target host does not respond to TCP. On any cloud instance where the metadata service does respond to TCP, which is all of them, the robots.txt fetch succeeds with a 404 (IMDS has no robots.txt), and mcp-server-fetch proceeds to fetch the real URL. mcp-server-http-request does not even have this accidental defense. It skips robots.txt and just makes the request. EC2 demonstration: real credentials My hypothesis was that on actual EC2 the failure mode would be different and credentials would come back. I launched a t3.micro (Amazon Linux 2023, us-east-1), attached an IAM role with AmazonEC2ReadOnlyAccess, set IMDSv2 to Optional (the default is Required, which itself mitigates this bug, as discussed below), installed mcp-server-fetch, and asked it to fetch the IMDS credentials URL via an MCP client harness. Result, redacted: --- calling fetch('http://169.254.169.254/latest/meta-data/iam/security-credentials/mcp-scan-ssrf-test-role/') --- Contents of http://169.254.169.254/latest/meta-data/iam/security-credentials/mcp-scan-ssrf-test-role/: { "Code" : "Success", "AccessKeyId" : "ASIA****", "SecretAccessKey" : "wJal****", "Token" : "AQoD****", "Expiration" : "2026-05-12T..." } A working AWS IAM credential triplet, returned by an MCP tool, retrievable by anything that can convince an agent to call fetch("http://169.254.169.254/..."). The role and instance were torn down within minutes of capture; the credentials expired with their normal lifetime. The full reproduction runbook is in the mcp-witness repo. From an attacker's perspective the chain is: prompt injection, then the agent calls fetch with the IMDS URL, then credentials are returned, then the attacker pivots to whatever the IAM role permits. The fix that shipped fast and never merged The first half of this is the discovery-to-fix loop working as designed. Ten days after the coordinated disclosure was filed as modelcontextprotocol/servers#4143, external contributor @kgarg2468 opened PR #4226, "fix(fetch): block private network URL fetches", explicitly fixing the disclosed issue. The commit summary describes the fix as: - validate that fetch URLs use the http / https scheme, - resolve hostnames and reject targets that are not public IP addresses, - block localhost, private, loopback, link-local, and metadata-service targets, - follow redirects manually so each redirected target is validated before the next request. That last item, per-redirect validation, is a more rigorous defense than the original disclosure asked for. Without it, an attacker could host a public URL that 302-redirects to http://169.254.169.254/...; the in-package scheme/host check would pass on the initial request and the metadata leak would still occur. Per-redirect validation closes that bypass. On 2026-05-22 I re-ran the same demo script that previously exfiltrated IAM credentials against the fix branch (kgarg/harden-fetch-ssrf). Same request, different response: Fetching private or non-public IP addresses is not allowed Refusal at the validation layer with a clean error message, no network attempt, and independent verification posted on the PR. The second half is where it stops resembling a clean loop. Issue #4143 was closed as "not planned" on 2026-07-30 and consolidated into an older, dormant umbrella issue, #3741. As of 2026-09-10, PR #4226 is still open and unmerged: CI green, one external technical-audit review, but no approving review from anyone with write access, which the repository requires to merge, and stale since 2026-06-21. Three releases shipped after the disclosure (v2026.6.4, v2026.7.10, v2026.8.18); the current latest still runs the unguarded fetch path, runtime-verified via the poc/ssrf/ harness on 2026-09-10. Coordinated-disclosure obligations were satisfied and the 90-day embargo was observed, so I do not read intent into the routing. The outcome is still that a verified, low-risk, self-contained fix for a credential-exfiltration primitive in a reference server has sat unmerged for weeks while releases shipped without it. The full arc of that one package is its own piece: I found an SSRF in Anthropic's reference MCP server. The community companion package mcp-server-http-request v0.1.0 (published by the startup statespace) has the identical class and no accidental robots.txt defense at all. I disclosed it by email the same day, since it had no public issue tracker. After thirty days of silence, cofounder Gavin Chan replied on LinkedIn on 2026-06-11: "That's not an actively maintained package." I logged it as maintainer-confirmed-unmaintained. No fix. Class 2: inbound DNS rebinding on HTTP-transport MCP servers The SSRF half is about the server reaching out. The DNS-rebind half is about the browser reaching in. Why HTTP-transport MCP servers are a different attack surface When an MCP server runs as an HTTP service, every request to it carries an Origin header (or does not, if the request is from a non-browser client). A server intended for local-only use must validate that Origin against an allowlist; otherwise any web page the operator visits can issue requests to the server's endpoints. The web's standard defense against this is the Same-Origin Policy enforced by the browser, but SOP gates on the origin (scheme plus host plus port), and an attacker who controls a domain can defeat the host check via DNS rebinding: serve a low-TTL DNS record pointing at an attacker-controlled IP initially, then flip the record to 127.0.0.1 after the page loads. From the browser's perspective the page's origin has not changed; from the network's perspective the requests now hit localhost. A localhost bind sounds defensively reasonable but is not enough on its own. Without server-side Origin/Host validation, the rebind primitive turns any browser tab into a tool-invocation path against locally running MCP servers. Discovery: S-014 and the W1 through W4 detector evolution Static-analyzer rule MCP-S-014 flags HTTP-transport MCP servers that bind to loopback or 0.0.0.0 without Origin/Host validation. The original v0.1 detector was a string-literal pattern match plus a coarse "does the file mention Origin anywhere" suppression. That fired on none of the four disclosure targets; each of mcp-streamablehttp-proxy, mcp-fetch-streamablehttp-server, fastmcp-http, and mcp-server-fetch-sse needed one of the W1 through W4 patches (or manual source review) to surface. The survey itself taught the detector. Four patches followed: - W1: host=variable resolution. Patterns like uvicorn.run(app, host=host, port=port) where host is bound to "0.0.0.0" earlier (via module-level assignment or a function-parameter default) need a tree-wide pre-pass to track string bindings. Necessary for mcp-streamablehttp-proxy and fastmcp-http. - W2: AST-based Origin suppression. Substring-matching origin anywhere in the file silenced the rule even when the only origin mention was inside an LLM prompt string or a wildcard CORS response header. Replaced with an AST walk that looks for actual request-header reads, request.headers["Origin"] or .headers.get("Origin", …). Tightens precision substantially. - W3: aiohttp bind shapes. _SERVER_BIND_METHODS extended with run_app (keyword-host pattern: web.run_app(app, host="…")) and TCPSite (positional-host pattern: web.TCPSite(runner, "…", port)). Necessary for mcp-server-fetch-sse. - W4: env-var defaults. host = os.getenv("HOST", "0.0.0.0"), common in production-shaped code where the env-var fallback is the deployed bind. _extract_env_default resolves the literal second-arg default; _collect_string_bindings calls it for Assign nodes whose value is a Call. Necessary for mcp-fetch-streamablehttp-server. After the patches: four of four surveyed packages correctly flagged. The detector is now what it should have been from the start, and the survey work is what made it that way. The full walkthrough, each patch tied to the specific source file that motivated it, is its own piece: Detector evolution: MCP-S-014. Four packages, one pattern All four targets exhibit the same architectural assumption: "some external layer handles Origin / Host enforcement." For two of them (atrawog/mcp-oauth-gateway's mcp-streamablehttp-proxy and mcp-fetch-streamablehttp-server), the external layer is Traefik configured as part of the larger monorepo, and the in-package code has inline comments explicitly delegating to it. The problem is that the package is also distributed as a standalone PyPI release with its own console script, and the standalone path ships without Traefik. For fastmcp-http, the external layer is implicit: Flask's dev server has no auth and no middleware, and operators are presumed to put a reverse proxy in front. For mcp-server-fetch-sse, it is a fork of an internal-use-only experiment whose threat-model docs never got updated. The worst case is mcp-server-fetch-sse: a DNS-rebind primitive on the HTTP+SSE transport combined with an inherited pre-fix SSRF in the wrapped fetch tool. The attack chain compounds. Attacker-controlled DNS rebinds to 127.0.0.1; page JavaScript establishes an SSE session; the page POSTs tools/call with an IMDS URL to /message?sessionId=<id>; the server accepts (no Origin/Host check) and forwards to the fetch tool; the fetch tool retrieves IMDS (no scheme/host check); credentials return in the response body. Browser-tab-triggered, no agent prompt injection required. The full inbound writeup, including the honest split between the server-side flaw and the harder live-rebind delivery, is One assumption, four packages. Disclosure outcomes The coordinated-disclosure embargo was 2026-08-10, and it has passed. I checked all four packages against GitHub and PyPI on 2026-09-10. All four are unchanged on PyPI since disclosure, with no fix releases, and all four remain vulnerable at the disclosed versions. - mcp-streamablehttp-proxy v0.2.0 and mcp-fetch-streamablehttp-server v0.2.0 (atrawog). Coordinated disclosure emailed to atrawog@gmail.com on 2026-05-12, with a day +21 follow-up on 2026-06-02 and a third email at day +30. No response at any point. Silent. - fastmcp-http v0.1.4 (ARadRareness). Disclosed via a public GitHub issue, ARadRareness/mcp-registry#3, on 2026-06-02, as the channel of last resort after GHSA was disabled on the repo, the maintainer profile listed no contact, and PyPI showed only a GitHub-noreply email. The maintainer closed the issue as "completed" on 2026-06-21, with no comment, no commits since, and no fix release. Closed without a fix. - mcp-server-fetch-sse v0.1.1 (PyPI maintainer-of-record jadamson@anthropic.com). Disclosed by email on 2026-06-02, with a parallel courtesy notice to Anthropic Security, discussed next. The maintainer never replied. The brand-attribution problem mcp-server-fetch-sse v0.1.1 lists Author: Anthropic, PBC. and Maintainer: Jack Adamson <jadamson@anthropic.com> in its wheel METADATA. Its README is lifted near-verbatim from Anthropic's official mcp-server-fetch: same CAUTION block, same VS Code install badges pointing at the upstream package name. The wrapped fetch implementation looks like a fork of upstream code from before PR #4226 shipped the SSRF fix. From a downstream user's perspective the package presents as Anthropic-published; from outside, I cannot verify whether the attribution is active Anthropic maintenance or attribution inherited from a fork's pyproject.toml. PyPI does not verify Author claims. My disclosure approach was a primary technical disclosure to the published maintainer-of-record at jadamson@anthropic.com, with a parallel courtesy notice to Anthropic Security so they could route internally if appropriate. The parallel notice took two attempts. The HackerOne program listed in Anthropic's .well-known/security.txt returned a triage interstitial keyword-filtering the report as the documented mcp-server-fetch SSRF class, which it is not (separate package, different attack direction, plus a class of vulnerability the interstitial does not cover at all). The interstitial included a "submitting this report could impact your reputation points" warning, HackerOne's signal that the program will not engage with this report class, so I cancelled the submission and switched channels. The email to disclosure@anthropic.com (the secondary contact published on Anthropic's responsible-disclosure-policy page) returned a no-reply auto-responder routing back to HackerOne or to specialized channels, none of which fit "third-party PyPI package brand attribution." Both deflections are corporate-intake-routing artifacts, not Anthropic's position on the disclosure. No human review was reached on the Anthropic side, and the maintainer never replied. The primary technical disclosure to the listed maintainer remains the binding channel for a fix; the parallel-notice piece is documented in the disclosure record for posterity. The class-wide observation: one ecosystem norm Two attack directions, four maintainer teams, six packages, and one architectural anti-pattern. In the SSRF half: HTTP-fetch tools model the url parameter as an unconstrained string and trust the agent to be responsible. The threat model for MCP servers explicitly includes adversarially influenced tool arguments via prompt injection, so the trust assumption is wrong. The fix is small: a scheme allowlist, an IP-class denylist, per-redirect validation. Ten lines per package, or ten lines once in a shared utility. In the DNS-rebind half: HTTP-transport MCP servers defer Origin/Host enforcement to "some external layer," Traefik in some cases, an unnamed reverse proxy in others, the operator's good judgment in the rest. The standalone deployment path ships without that external layer. The fix is small: a Starlette TrustedHostMiddleware for ASGI servers, an aiohttp middleware for aiohttp servers, a Flask before_request hook for Flask servers, each validating Host and Origin against an allowlist. Five to ten lines per package. Both are the same shape: external constraint, missing in-package enforcement. The author assumed someone upstream would handle security: the cloud platform via IMDSv2, the operator via robots.txt being unreachable, the reverse proxy via header rewriting, the agent via judgment. In each case the assumption holds for the deployment the author had in mind and fails for the deployment users actually run. The shared fix is to defend in-process and treat external defenses as defense-in-depth rather than the primary boundary. The corollary is that the default pip install plus console-script flow should be safe out of the box; users who know what they are doing can opt out. def safe_fetch_url(url: str) -> str: parsed = urlparse(url) if parsed.scheme not in ("http", "https"): raise ValueError(f"Unsupported scheme: {parsed.scheme}") ip = ipaddress.ip_address(socket.gethostbyname(parsed.hostname)) if ip.is_loopback or ip.is_link_local or ip.is_private or ip.is_reserved: raise ValueError(f"Refusing to fetch reserved address: {ip}") return httpx.get(url).text Ten lines. Per package, or once across the ecosystem. Disclosure timeline - 2026-05-11: discovery via the S-009 static rule; dynamic verification of D-003 on a dev machine. - 2026-05-12: EC2 reproduction with real IAM credentials retrieved; coordinated disclosures filed: GitHub issue #4143 (mcp-server-fetch), email to maintainers (mcp-server-http-request), email to atrawog (mcp-streamablehttp-proxy and mcp-fetch-streamablehttp-server, combined). - 2026-05-22: PR #4226 (@kgarg2468) shipped against mcp-server-fetch; the same demo script that previously exfiltrated IAM credentials, re-run against the fix branch, returns "Fetching private or non-public IP addresses is not allowed"; independent verification posted on the PR. - 2026-06-02: day +21 follow-up pings on the four still-silent maintainers; three new disclosures dispatched: fastmcp-http (ARadRareness/mcp-registry#3, public-issue channel of last resort), mcp-server-fetch-sse (email to jadamson@anthropic.com), parallel notice to disclosure@anthropic.com after the HackerOne triage interstitial declined the report class. - 2026-06-11: day +30 escalation checkpoint; mcp-server-http-request confirmed maintainer-unmaintained via the statespace cofounder's LinkedIn reply. - 2026-06-21: fastmcp-http issue closed as "completed" with no fix. - 2026-07-30: mcp-server-fetch disclosure #4143 closed as "not planned" and consolidated into dormant umbrella #3741. - 2026-08-10: embargo expiration. - 2026-09-10: post-embargo verification: PR #4226 still open and unmerged, mcp-server-fetch latest (v2026.8.18) still vulnerable; all four DNS-rebind packages unchanged on PyPI and still vulnerable. - 2026-09-11: CVE requests filed with MITRE for all six packages (IDs pending assignment). What the ecosystem should do For MCP server authors building anything that touches network egress, inbound HTTP transport, or any IO sink with cloud-environment relevance: 1. Do not trust the URL parameter. Whatever the agent passes in might be attacker-controlled even if the agent itself is honest. Validate scheme and host before issuing the request. 2. Reject reserved IP ranges by default on outbound calls: link-local (169.254/16), loopback (127/8, ::1), RFC 1918 private (10/8, 172.16/12, 192.168/16), IPv6 ULA (fc00::/7). Provide an opt-in env var for users who need internal access. 3. For HTTP-transport servers, validate Origin and Host headers in-process. The standalone pip install plus console-script flow should be safe; the reverse-proxy-in-front deployment can disable in-process checks via an explicit env var. 4. Schema-constrain when possible. A pattern or enum on the URL parameter is the simplest defense, and it pushes constraints into the protocol surface where every downstream auditor can see them. 5. Do not rely on host-level mitigations. IMDSv2-Required is good, but the package should not assume the host has it enabled. A coding-assistant or browser-automation MCP host will not always be on AL2023. For MCP host operators: 1. Set IMDSv2 to Required on every cloud instance running an MCP host. It is the single highest-leverage host-level mitigation. 2. Audit your MCP server inventory. Run mcp-witness (or an equivalent) against every server you have enabled and flag any with MCP-S-009 (outbound SSRF) or MCP-S-014 (inbound DNS rebind) findings. 3. Treat MCP servers as untrusted code that runs with the agent's effective network identity and the operator's effective browser-localhost identity. They are, by design, speaking to the model on your behalf. For the MCP spec itself: The spec does not currently mandate validation patterns for HTTP-class tools. Whether that becomes a normative requirement or a recommended-practice section is a spec-track question, but the survey here makes the case: the ecosystem cannot be relied on to enforce security at the package level when the spec implies it is the application's responsibility. A normative section ("HTTP-fetch tools MUST allowlist scheme and reject reserved IP ranges by default" / "HTTP-transport servers MUST validate Origin and Host headers before processing tool calls") would shift the default. The disclosure record here would have looked very different if these were spec-mandated invariants. About the tool mcp-witness is open source under Apache 2.0. It includes the static analyzer (15 rules, 278 tests), the dynamic harness (7 scenarios, two agent drivers: a deterministic stub and a real Claude tool-use loop), the capability classifier, a calibration corpus of 11 hand-labeled MCP servers, the EC2 reproduction runbook used for the SSRF verification, and the full audit trail of all six disclosures covered in this post, including the channel-decision records for the four that needed unusual disclosure paths. If you want to audit your own MCP servers, three commands get you started: pip install mcp-server-fetch # or any MCP server you want to audit mcp-witness-capture --server-cmd python --server-arg=-m --server-arg=mcp_server_fetch -o /tmp/tools.json mcp-witness-analyze /tmp/tools.json The runbook, all findings, and the disclosure record live in the repo; contributions are welcome. Next A few follow-ups are worth their own pieces rather than another paragraph here: - AST-based vs string-pattern detection: methodology trade-offs. Triggered by a productive cross-tool discussion on the #4143 thread. Different methodologies have different blind spots, and the comparison is worth a thorough treatment. - Cross-language parity. The analyzer now carries a tree-sitter TypeScript/JavaScript front-end that brings the source rules to TS/JS at parity with Python, so the same transport-layer checks run against TS servers. How the two front-ends share a rule model is its own write-up. - MCP-spec engagement. Whether the security recommendation above gets traction is up to the spec maintainers and the broader community; the open question is where to surface the proposal, whether an issue thread, an RFC draft, or a working-group discussion. Closing The MCP ecosystem is in the early-adoption phase that every new protocol goes through: fast growth, many small packages, security treated as a follow-up concern. Two vulnerability classes are documented at scale here, and almost certainly not the last. The same scanner is finding patterns in command-execution tools, file-handling tools, and tool descriptions that contain agent-directed instructions. Each pattern deserves a careful audit; some will produce more disclosures, some will turn out to be false positives. That is the work. The question for the ecosystem is whether it adopts secure defaults or learns by repeated incident. Coordinated disclosure with a reasonable embargo is one way to nudge it toward the former. Publishing the runbook, the rules, the scanner, and the methodology in public is another. Both are open for anyone reading this to take further. Survey conducted May–June 2026; disclosure outcomes verified 2026-09-10.

    1 reaction
  8. MCP servers fixed 8 flaws. npm sees old 2026.x versions as newer than patched 2.0.1, so upgrades can miss fixes. Install filesystem/everything 2.0.1 and fetch 2026.10.24 explicitly. Windows path-race fix remains partial. github.com/modelcontextprotocol/servers/releases/…

  9. MCP servers v2.1.0 fixes 8 vulns: a symlink swap let filesystem writes escape allowed dirs, server-everything took calls from any web origin, fetch could hit internal IPs. Catch: npm ranks 2.0.1 below old 2026.x builds. Install it explicitly. github.com/modelcontextprotocol/servers/releases/…

    4 likes · 131 views
  10. MCP 공식 서버 v2.1.0, 취약점 8개 수정 filesystem·everything은 2.0.1, fetch는 2026.10.24가 수정판 릴리스 노트에 적힌 함정 2026.8.31 같은 날짜 버전을 쓰던 쪽은 npm이 2.0.1을 낮은 버전으로 봐서 업데이트로 안 뜸 버전을 2.0.1로 직접 지정해서 깔아야 함 github.com/modelcontextprotocol/servers/releases/… t.co

    3 likes · 50 views

Latest stargazers

The last 10 distinct accounts we observed this year.

Most active participants

PR, issue, comment & review events in the past year.

Packages

7
PackageVersionDownloads · 30dTrend · 30d
v2026.8.31
≥399.4K
v2026.8.31
≥951.3K
v2026.8.31
≥321.1K
v2026.8.31
≥301.5K
mcp-server-fetchv2026.10.10
v2026.10.10
≥606K
mcp-server-gitv2026.10.10
v2026.10.10
≥212.1K
mcp-server-timev2026.10.10
v2026.10.10
≥440.7K

Live mentions badge

Show how many posts mention your project in the last 30 days.

Live mentions · last 30 days
modelcontextprotocol/servers mentions in the last 30 days