{"slug": "my-mcp-server-only-talks-to-apis-i-trust-that-doesn-t-mean-the-data-coming-back", "title": "My MCP Server Only Talks to APIs I Trust. That Doesn't Mean the Data Coming Back Is Trustworthy.", "summary": "A developer discovered a security blind spot in their MCP server: even a fully vetted, self-built server can return untrusted data from external APIs. The server's tools fetch free-text fields like GitHub repo descriptions and DEV.to article titles, which could contain adversarial content that an AI agent might treat as instructions. The developer now advocates checking not just server and API trustworthiness, but also whether response content could be authored by strangers and misused as directives.", "body_md": "I built a small MCP server a while back — `developer-presence`\n\n, seven tools wrapping the GitHub REST API and the DEV.to API so an agent can check my repo stats, list my articles, or draft a new post without me leaving the chat. It's mine, I wrote every line, there's no third-party package doing anything sketchy under the hood. By the usual \"vet your MCP servers before installing them\" checklist, it passes clean. I've written that checklist article before.\n\nWhat I hadn't thought carefully about until recently is that vetting the server doesn't vet the data. Two of its tools go straight to the point:\n\n``` php\n@mcp.tool()\ndef get_repo_stats(repo: str) -> dict:\n    \"\"\"Get stars, forks, watchers, open issues for enjoykumawat/<repo>.\"\"\"\n    r = _gh(f\"/repos/{GITHUB_USERNAME}/{repo}\")\n    return {\n        \"name\": r[\"name\"],\n        \"stars\": r[\"stargazers_count\"],\n        \"forks\": r[\"forks_count\"],\n        \"watchers\": r[\"watchers_count\"],\n        \"open_issues\": r[\"open_issues_count\"],\n        \"language\": r.get(\"language\"),\n        \"description\": r.get(\"description\"),\n    }\n```\n\n`description`\n\nis free text. Any repo owner can put anything in it. If I ever point this tool at a repo I don't control — someone else's fork, a dependency, anything — that field lands in my agent's context exactly the same way a trusted instruction would: as text in a tool result, with no marker distinguishing \"this came from GitHub's database, unfiltered\" from \"this is something I told the agent to do.\" The server is safe. The channel is safe. The payload was never vetted at all, because there was nothing to vet — it's just whatever a stranger typed into a form.\n\nI only really felt this because of a task I run on a schedule: check dev.to for trending posts in a few tags, score them, and use the highest scorers as source material for what to write about next. Step one of that job is a loop over tag pages:\n\n```\nfor tag in [\"ai\", \"llm\", \"mcp\", \"claudecode\", \"agents\", \"productivity\"]:\n    url = f\"https://dev.to/api/articles?tag={tag}&top=7&per_page=20\"\n    ...\n    for a in r:\n        score = a.get(\"positive_reactions_count\", 0) + 3 * a.get(\"comments_count\", 0)\n```\n\nEvery title, every tag list, every body snippet in that response was written by an arbitrary dev.to user, unmoderated, and I feed the highest-scoring ones directly into an agent whose job is to decide what to write about and then write it. Nothing stops a post titled to look like an operator instruction rather than an article — something engineered to read as a directive if it lands in an agent's context — from scoring well enough to make the cut. I haven't hit a real attempt at this yet. That's exactly the property that makes it easy to miss: the failure mode isn't \"the server got compromised,\" it's \"a normal day's data included something adversarial and nothing downstream noticed.\"\n\nThis is the piece I'd skipped in both of my earlier MCP security posts. One was about hardening a server you're building against misuse. The other was about vetting a server before you install it — provenance, permissions, what it's allowed to touch. Both treat \"the server\" as the trust boundary. Neither one covers what happens once a legitimately safe, correctly-scoped, fully-vetted server hands back a string that a total stranger wrote, and that string walks straight into the same context window as the actual instructions.\n\nThe fix isn't code in my case yet — it's a change in what I check for. Before this, my mental model of \"is this tool call safe\" stopped at \"do I trust this server and do I trust the API it's calling.\" Now there's a second question after that: \"is the *content* of the response something a stranger could have authored, and if so, would anything downstream treat it as an instruction rather than as data.\" For `get_repo_stats`\n\n, the honest answer is that `description`\n\nshould be handled as inert text — displayed, logged, maybe summarized — but never a source of directives, and if I ever let an agent act on \"instructions\" found inside a fetched description or article body, that's the bug, not the API response itself. For the trending-topics job, it means the scoring and theme-picking step should only ever produce a *topic to write about*, never something that changes what tools get called or what files get touched — which, as it happens, is exactly how I built it, if by accident rather than by threat-modeling it up front.\n\nThe general shape, once you see it: any tool result that contains text authored outside your control — a bio, a comment, a PR description, a trending post, a filename someone else chose — is untrusted input, full stop, regardless of how much you trust the API serving it to you. Vetting the server answers \"can I trust the pipe.\" It never answers \"can I trust what's flowing through it.\" Those are two different questions, and I'd only ever been asking the first one.", "url": "https://wpnews.pro/news/my-mcp-server-only-talks-to-apis-i-trust-that-doesn-t-mean-the-data-coming-back", "canonical_source": "https://dev.to/enjoy_kumawat/my-mcp-server-only-talks-to-apis-i-trust-that-doesnt-mean-the-data-coming-back-is-trustworthy-1m0e", "published_at": "2026-07-15 12:36:21+00:00", "updated_at": "2026-07-15 12:59:55.108628+00:00", "lang": "en", "topics": ["ai-safety", "ai-agents", "developer-tools"], "entities": ["GitHub", "DEV.to", "MCP"], "alternates": {"html": "https://wpnews.pro/news/my-mcp-server-only-talks-to-apis-i-trust-that-doesn-t-mean-the-data-coming-back", "markdown": "https://wpnews.pro/news/my-mcp-server-only-talks-to-apis-i-trust-that-doesn-t-mean-the-data-coming-back.md", "text": "https://wpnews.pro/news/my-mcp-server-only-talks-to-apis-i-trust-that-doesn-t-mean-the-data-coming-back.txt", "jsonld": "https://wpnews.pro/news/my-mcp-server-only-talks-to-apis-i-trust-that-doesn-t-mean-the-data-coming-back.jsonld"}}