Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.agentguardian.io/llms.txt

Use this file to discover all available pages before exploring further.

Declare your MCP endpoint in an agentguardian.yaml contract, then run agent-guardian scan --contract. The transport speaks JSON-RPC 2.0 over MCP Streamable HTTP, discovers tools via tools/list, and drives each adversarial prompt through tools/call.

What this example tests

  • The 10 ASI categories against the MCP server itself — tool poisoning, schema confusion, destructive-tool gating, session bleed — rather than a specific LLM client that happens to use the server.
  • Supply-chain attacks specific to MCP: asi04.mcp-server-poison payloads try to register an untrusted MCP server (https://tools.attacker.test/mcp, etc.) and check whether the target acknowledged or began using it.
  • Rules of Engagement enforcement: declare an allowlist/blocklist in YAML and the live RoE chokepoint blocks the call before contacting the server.
The MCP transport is contract-only — there is no --mcp-url CLI flag. MCP servers carry session state, tool surfaces, and (often) OAuth-protected resources; the contract is the right place to declare all three. Source: src/agent_guardian/transports/mcp.py, src/agent_guardian/contract/schema.py (McpTransport).

Prerequisites

  • AgentGuardian installed — pip install agent-guardian.
  • An MCP server reachable over Streamable HTTP (default) or legacy SSE.
  • For OAuth-protected servers: client credentials available via env vars (the contract dereferences ${env:NAME} references).
  • A model spec — --model stub for an offline dry-run, or a real model spec for a graded assessment.

Run target

Create agentguardian.yaml next to your project. This is a minimal MCP target with no auth and a stateless session, plus a Rules-of-Engagement block that allow-lists one tool and blocks a destructive one:
agentguardian.yaml
version: 1
target:
  name: mcp-search
  environment: staging
  transport:
    kind: mcp
    url: https://mcp.example.com/rpc
    transport_type: streamable_http
    entry_tool: search
    prompt_argument: input
    init_timeout_ms: 30000
  response:
    output_path: $.output.text
  session:
    mode: stateless
roe:
  data_egress:
    allow_external: true
  tools:
    allowlist: [search]
    blocklist: [delete_everything]
Every transport field is verified against McpTransport in src/agent_guardian/contract/schema.py:
FieldTypeDefaultNotes
kind"mcp"requiredDiscriminator
urlURLrequiredThe MCP JSON-RPC endpoint
transport_type"streamable_http" | "sse""streamable_http"Streamable HTTP is the modern default
entry_toolstring | nullnullTool to invoke each turn. When null, the first discovered tool is used
prompt_argumentstring"input"Argument name the adversarial prompt is mapped onto
init_timeout_msint30000Timeout for the initialize handshake (and downstream RPCs)
The roe.tools.blocklist is the live RoE chokepoint: before any tools/call the transport asks the gate whether the tool name is allowed; a blocked tool never contacts the server (the transport returns a benign blocked note and a recorded ToolCall instead — see McpTransport.send in transports/mcp.py). Pre-flight will fail if your allow/blocklist names a tool the server did not advertise — the discovered set from tools/list is reconciled against the RoE. For OAuth-protected servers, swap the auth block (every field maps 1:1 to McpOAuthAuth in the schema):
target:
  # ... transport block as above ...
  auth:
    kind: mcp_oauth
    client_id: ${env:MCP_CLIENT_ID}
    client_secret: ${env:MCP_CLIENT_SECRET}   # omit for public clients
    scopes: [tools.read, tools.call]
McpOAuthProvider runs the full MCP authorization flow: fetches {resource}/.well-known/oauth-protected-resource (RFC 9728 Protected-Resource-Metadata) to discover the authorization_servers, performs authorization-code + PKCE (S256), and applies the bearer token in the Authorization header.

Run AgentGuardian

agent-guardian scan \
  --contract ./agentguardian.yaml \
  --model stub \
  --mode fast \
  --output sarif \
  --output-path mcp-scan.sarif
--contract is mutually exclusive with the positional target / --system-prompt / --endpoint / --framework modes. The contract supplies the transport, auth, session, and RoE; budgets in the contract map onto the swarm config, and a provenance audit is attached to the report. Flag-by-flag, every option below is verified against src/agent_guardian/cli.py:
  • --contract PATH — drive the scan from a target contract (agentguardian.yaml).
  • --model stub — offline default. Swap for a real model spec for a graded run.
  • --mode fastfast / smart / full (default).
  • --output sarif --output-path mcp-scan.sarif — SARIF report, ready to upload to GitHub Code Scanning. Other formats: json, md, junit, pdf.

Expected output

The scan emits a standard AgentGuardian report. The target block reflects the contract-driven path and the discovered MCP transport:
{
  "scan_id": "scan_2026...",
  "target": {
    "mode": "contract",
    "name": "mcp-search",
    "environment": "staging",
    "transport": {
      "kind": "mcp",
      "endpoint": "https://mcp.example.com/rpc",
      "supports_tools": true,
      "session_modes": ["stateless", "server_session"],
      "discovered_tools": ["search", "delete_everything"]
    }
  },
  "aivss_score": 5.8,
  "findings": [
    { "id": "F-001", "probe": "asi04.mcp-server-poison", "severity": "MEDIUM" }
  ]
}
If the server returned an Mcp-Session-Id response header, the transport captures it and replays it as a request header on every later call so the server can resume the same session (this is MCP’s server_session mode, advertised in session_modes). A finding under asi04.mcp-server-poison (the in-tree MCP probe at src/agent_guardian/probes/asi04/mcp-server-poison.yaml) means an attacker payload tried to register an untrusted MCP server and the target acknowledged or began using it. That is a supply-chain failure; the remediation is the RoE allow-list.

Common errors

  • tool 'X' is not advertised by the server. The discovery step (tools/list) did not return a tool named in your entry_tool or RoE allow/blocklist. Run the MCP server’s own tools/list against your endpoint to see what it actually exposes.
  • 401 Unauthorized on every call. The server is OAuth-protected and you used auth: kind: none (or omitted the block). Switch to auth: kind: mcp_oauth with the client_id / scopes block above.
  • Scan finishes with error.category: PROTOCOL. The server returned a JSON-RPC error member that didn’t match a refusal hint (forbidden, denied, not allowed, blocked, unauthorized). Check the server logs — a malformed inputSchema or a missing required argument is the usual cause.
  • EXIT_CONTRACT_INVALID on startup. Your agentguardian.yaml failed Pydantic validation. Run agent-guardian contract validate agentguardian.yaml to see the exact field error.

Next step