TL;DR: MCP servers run with significant privileges inside AI agent pipelines, and most teams ship them without any security review. mcp-security-scan is an open-source CLI and GitHub Action that checks for credential theft patterns, data exfiltration, unsafe execution, and code obfuscation — and outputs a 0-100 trust score that integrates with AgentGraph's identity layer.


The Moltbook breach last year is still the clearest example of what happens when you scale agent infrastructure without thinking about trust. 770,000 agents, zero identity verification, and when it went down it exposed 35,000 emails and 1.5 million API tokens. The tokens were the real problem — many of them were credentials passed through MCP servers that nobody had audited.

MCP (Model Context Protocol) servers are the connective tissue of modern agent systems. They sit between your LLM and the outside world, handling tool calls, filesystem access, API requests. That position gives them a lot of power. It also makes them an obvious target.

And yet most teams treat MCP servers like they treat npm packages circa 2015: install and trust.


What Actually Goes Wrong

Before getting into the scanner, it's worth being specific about the threat categories. There are four that show up most often in real codebases:

Credential theft — MCP servers that read environment variables indiscriminately, log request/response payloads, or forward tool call arguments to external endpoints. This one is subtle because the server might be doing legitimate work and exfiltrating credentials.

Data exfiltration — Outbound HTTP calls to domains that weren't declared in the server's manifest, or calls that happen inside tool handlers where the LLM can influence the destination URL. Prompt injection into tool parameters is the attack vector here.

Unsafe executioneval(), exec(), subprocess calls, or dynamic require()/import() where the argument comes from tool call input. If an LLM can influence what gets executed, you have a problem.

Filesystem access — Path traversal risks, reading outside declared directories, writing to sensitive locations. Especially bad in servers that accept filename parameters from the model.

OpenClaw's skills marketplace has 512 CVEs at last count, and roughly 12% of skills contain what their own security team classifies as malware. That's a marketplace that grew fast and audited slowly.


Introducing mcp-security-scan

mcp-security-scan is a CLI tool and GitHub Action for scanning MCP server source code and runtime behavior. It's MIT licensed, lives at github.com/agentgraph-co/mcp-security-scan, and produces a structured JSON report plus a 0-100 trust score.

Install it:

npm install -g mcp-security-scan

Enter fullscreen mode Exit fullscreen mode

Basic scan against a local server directory:

mcp-security-scan scan ./my-mcp-server

Enter fullscreen mode Exit fullscreen mode

Output:

{
  "score": 74,
  "findings": [
    {
      "severity": "HIGH",
      "category": "credential_theft",
      "rule": "env-wildcard-read",
      "file": "src/tools/search.ts",
      "line": 43,
      "message": "process.env spread into tool response object",
      "snippet": "return { ...process.env, results }"
    },
    {
      "severity": "MEDIUM",
      "category": "unsafe_execution",
      "rule": "dynamic-require",
      "file": "src/index.ts",
      "line": 112,
      "message": "require() called with non-literal argument",
      "snippet": "const mod = require(toolName)"
    }
  ],
  "passed": 18,
  "failed": 2,
  "agentgraph_trust_badge": "https://agentgraph.co/badge/mcp/..."
}

Enter fullscreen mode Exit fullscreen mode

The score drops from 100 based on finding severity: HIGH findings cost 15 points each, MEDIUM costs 5, LOW costs 1. That's configurable via a .mcp-scan.json file if your threat model weights things differently.


Running It in CI

The GitHub Action is the more useful integration for teams that ship MCP servers regularly:

name: MCP Security Scan

on: [push, pull_request]

jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Scan MCP Server
        uses: agentgraph-co/mcp-security-scan@v1
        with:
          path: ./server
          fail-on-severity: HIGH
          min-score: 70
          agentgraph-api-key: ${{ secrets.AGENTGRAPH_API_KEY }}

Enter fullscreen mode Exit fullscreen mode

The agentgraph-api-key is optional. Without it, you get the local scan results. With it, the score gets pushed to your agent's trust profile on AgentGraph, and your README badge updates automatically after each passing scan.

fail-on-severity: HIGH will exit with code 1 on any HIGH finding, blocking the PR merge. min-score: 70 does the same if the aggregate score drops below threshold. You can use one, both, or neither depending on how strict you want to be.


How the Scanner Works

The scan runs in two phases: static analysis and (optionally) dynamic analysis.

Static Analysis

Static analysis uses AST parsing via @typescript-eslint/parser for TypeScript/JavaScript servers and ast module for Python servers. The rules are pattern-based, not ML-based. That was a deliberate call.

ML-based detection would catch more subtle patterns, but it would also produce false positives that developers learn to ignore. A rule that says "flag process.env spread into any object that gets returned from a tool handler" is specific, auditable, and easy to suppress with a comment when you have a legitimate reason.

Every rule has a rule ID, a description, and a link to the rationale. If a finding doesn't make sense for your codebase, you suppress it explicitly:

// mcp-scan-disable-next-line env-wildcard-read
return { ...process.env, results }

Enter fullscreen mode Exit fullscreen mode

That suppression gets logged in the report so reviewers can see what was intentionally skipped.

Current rule count: 34 rules across the four categories. The full list is in the repo's docs/rules.md.

Dynamic Analysis

Dynamic analysis is opt-in and requires Docker. It spins up the MCP server in a sandboxed container with network monitoring enabled, runs a set of synthetic tool calls, and records outbound connections.

mcp-security-scan scan ./my-mcp-server --dynamic

Enter fullscreen mode Exit fullscreen mode

This catches things static analysis misses: obfuscated code that decodes at runtime, dependencies that phone home, servers that behave differently under specific input patterns.

The trade-off is obvious: it's slower (adds 30-90 seconds depending on server startup time), requires Docker, and can't catch everything — a server could behave differently with real LLM-generated inputs than with synthetic ones. But it catches the low-hanging fruit reliably.

The Trust Score

The 0-100 score is designed to integrate with AgentGraph's trust infrastructure. AgentGraph assigns W3C DIDs to AI agents and maintains auditable trust scores based on verifiable signals — scan results, deployment history, operator reputation. The scanner is one input into that system.

When you push scan results via the API key, they get recorded on-chain as part of the agent's evolution trail. That means you can show users of your MCP server a verifiable history of security scans, not just a static badge that could be faked.

This is the piece that distinguishes it from running ESLint with some custom rules. ESLint output lives in your CI logs. The trust score lives in a verifiable record that third parties can check before deciding whether to use your server.


Architecture Decisions and What We Got Wrong

A few honest notes on choices that didn't go perfectly:

The scoring formula is too simple. Flat point deductions per severity don't capture the difference between a HIGH finding in a core authentication handler versus a HIGH finding in a rarely-called utility function. We're working on a weighted scoring model that factors in code path reachability. The current version errs toward simplicity — a score you can explain beats a score that's accurate but opaque.

Python support is incomplete. The TypeScript/JavaScript rules are more mature. Python servers get about 60% of the rule coverage. If you're building Python MCP servers, the scanner will still catch the most common issues, but don't treat a passing score as a clean bill of health.

Dynamic analysis doesn't handle auth flows well. If your MCP server requires OAuth or API key setup before it will respond to tool calls, the dynamic scanner will time out waiting for initialization. There's a --dynamic-init-script flag that lets you provide a setup script, but it's clunky. This is on the roadmap.

The false positive rate on obfuscation detection is ~8%. Some legitimate minification patterns trigger the obfuscation rules. The fix is usually adding the file to .mcp-scan-ignore, but it's annoying when it hits a vendored dependency you can't control.


Integrating with AgentGraph Trust Badges

If you're publishing an MCP server publicly — on npm, GitHub, or through a marketplace — the trust badge is the user-facing output of all this.

After your first successful scan with an API key, you get a badge URL:

[![MCP Security Score](https://agentgraph.co/badge/mcp/your-server-id)](https://agentgraph.co/agent/your-server-id)

Enter fullscreen mode Exit fullscreen mode

The badge shows the current score and links to the full scan history. It updates after each scan that pushes results to the API. If a scan fails or the score drops below 70, the badge goes red.

This is the same trust infrastructure that AgentGraph uses for AI agents more broadly — verifiable DIDs, on-chain audit trails, social graph trust scoring. The scanner is a way into that system for teams who build tooling rather than agents directly.


What It Doesn't Catch

Being clear about limitations:

  • Supply chain attacks in dependencies. The scanner checks your code, not your node_modules. Use something like socket.dev or npm audit alongside it for dependency scanning.

  • Logic-level vulnerabilities. If your server correctly implements a tool that does something dangerous by design, the scanner won't flag it. It checks implementation patterns, not intent.

  • Runtime prompt injection. The dynamic analysis catches some injection patterns, but a sophisticated attack that only triggers under specific LLM-generated inputs won't show up in synthetic testing.

  • Configuration security. How you deploy the server, what permissions it runs with, network policies — none of that is in scope.

Think of it as one layer in a defense-in-depth approach, not a complete security solution.


Getting Started

# Install
npm install -g mcp-security-scan

# Scan a local server
mcp-security-scan scan ./path/to/server

# Scan with dynamic analysis
mcp-security-scan scan ./path/to/server --dynamic

# Output JSON for CI integration
mcp-security-scan scan ./path/to/server --format json --output report.json

# Check a specific rule
mcp-security-scan rules list
mcp-security-scan rules explain env-wildcard-read

Enter fullscreen mode Exit fullscreen mode

The full documentation is in the repo. Issues and PRs are open — the rule set in particular benefits from real-world examples of patterns people have seen in the wild.


Conclusion

MCP servers are infrastructure. They deserve the same security review you'd give any other piece of infrastructure that touches credentials and runs code on behalf of users. Most teams aren't doing that review today because there wasn't a fast, automated way to do it.

mcp-security-scan is the starting point. It won't catch everything, and the trust score is a signal, not a guarantee. But a score of 45 with three HIGH findings is a concrete thing you can act on before shipping.

The broader project — verifiable agent identity, auditable trust trails, a trust layer that scales across the agent ecosystem — is what AgentGraph is building. The scanner is how a lot of developers find their way into it.

Repo: github.com/agentgraph-co/mcp-security-scan

Disclosure: This post was generated with AI assistance as part of AgentGraph's content pipeline. The technical details reflect the actual tool's behavior.