Skip to main content

mcp-behaviour-guard workflow showing MCP server discovery, human-reviewed contracts, active security checks, and evidence reporting

mcp-behaviour-guard

Contract-driven security and behavioural regression testing for MCP servers.

PyPI CI Python 3.11-3.14 License: MIT

Discover an MCP server, define what it is allowed to do, test its actual runtime behaviour, and generate reproducible evidence when the observed behaviour violates the contract.

Quick startSecurity checksGenerate a contractReportsLimitations

PyPI releases are published from the protected pypi GitHub environment using GitHub OIDC Trusted Publishing, with digital attestations for uploaded distributions. These attestations provide provenance for the published files; they are not a claim that the package or every tested MCP integration is vulnerability-free.


Why this project exists

MCP servers can expose files, commands, databases, APIs, email systems and other sensitive capabilities to coding assistants and AI agents.

Tool descriptions and input schemas explain what a tool claims to do, but they do not prove that the server:

  • Enforces user and tenant boundaries.
  • Keeps sessions isolated.
  • Avoids undeclared filesystem or network activity.
  • Prevents duplicate state-changing operations.
  • Preserves the same security behaviour between releases.

mcp-behaviour-guard tests these properties against an explicit YAML security contract.

Define the permitted behaviour, execute controlled tests, observe the result, and report deviations with reproducible evidence.

Workflow

  1. Connect to an HTTP or STDIO MCP server.
  2. Discover its tools and generate a draft security contract.
  3. Review identities, permissions, tenant rules and permitted side effects.
  4. Run deterministic authorization and behavioural checks.
  5. Optionally re-test the same MCP session and fingerprint tool/prompt/resource metadata for runtime drift.
  6. Repeat scans at a configured interval when monitoring is enabled.
  7. Generate severity-ordered HTML, JSON, JUnit and SARIF evidence.

Contract generation produces a starting point, not an automatically trusted security policy. Authorization rules and permitted side effects must be reviewed by someone who understands the target system.

What it tests

When explicitly configured in the security contract, the current MVP supports:

  • Tool discovery and capability inventory
  • Anonymous and invalid-token access
  • Identity-based tool authorization, with reusable role policies for tenant expansion
  • Configured cross-tenant resource probes
  • Session and context isolation
  • Undeclared network requests visible to configured observers
  • Undeclared filesystem writes inside configured observation paths
  • Replay and duplicate-execution tests behind the existing safety gate
  • Tool-inventory and behavioural baseline drift
  • Runtime-gated tool/prompt/resource metadata drift across repeated calls
  • MCP host-configuration provenance and drift checks
  • Scheduled scans and severity-filtered alerts

Evidence-first reporting

Findings are ordered by severity, with critical findings displayed first. Each report row can be expanded to show:

  • The identity and tool used
  • Test input
  • Expected contract decision
  • Observed runtime behaviour
  • Evidence and trace locations
  • Remediation guidance

HTTP MCP security report generated by mcp-behaviour-guard STDIO MCP security report generated by mcp-behaviour-guard

HTTP MCP demonstration execution STDIO MCP demonstration execution

Quick start

Requirements

  • CPython 3.11-3.14; Docker, primary/integration CI, and release builds remain pinned to 3.11.14
  • macOS or Ubuntu
  • Docker Desktop/Engine with Compose for the HTTP lab
  • No Docker requirement for the basic STDIO lab

Install from PyPI

For normal CLI use:

python -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install mcp-behaviour-guard

Use your own reviewed contract with the installed mcp-guard CLI.

Source/development setup for the included labs

The demo commands below expect a repository checkout because the demo contracts, Compose file and lab fixtures live in the source tree.

pyenv install 3.11.14
pyenv local 3.11.14

python -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e '.[dev]'
cp .env.example .env

Run the HTTP lab

docker compose up --build -d
mcp-guard doctor contracts/http-demo.yaml
mcp-guard run contracts/http-demo.yaml --lab-mode --no-fail

Run the STDIO lab

export DEMO_AGENT_TOKEN=local-demo-token
mcp-guard doctor contracts/stdio-demo.yaml
mcp-guard run contracts/stdio-demo.yaml --lab-mode --no-fail

Run the temporal-integrity demo

This harmless demo keeps one STDIO session open, makes repeated calls, and changes its own tool/prompt metadata after the third call. It never reads credentials, files or network data.

mcp-guard doctor contracts/temporal-demo.yaml
mcp-guard run contracts/temporal-demo.yaml --no-fail

The expected result is a high TEMPORAL-METADATA-001 finding with the first drift recorded after call 3.

Reports are written to reports/<run-id>/index.html.

The evidence status guide explains why a denied result needs a positive control and why an observer outage is inconclusive. For direct Python use:

import asyncio
from mcp_behaviour_guard import run_contract

result = asyncio.run(run_contract("contracts/stdio-demo.yaml", lab_mode=True))
print(result.summary.assessment.value, result.run_dir)

For a runner image separate from the deliberately vulnerable Docker demo:

docker build -f Dockerfile.runner -t mcp-guard-runner:dev .
docker run --rm -v "$PWD:/work" mcp-guard-runner:dev run contracts/stdio-demo.yaml --lab-mode --no-fail

The container can reach only mounted files and networks available from its runtime. This demo mounts the checkout read-write because the STDIO lab writes controlled fixtures under demo_runtime/stdio; do not use an unrestricted read-write source checkout mount for production runner jobs. The demo's original Dockerfile and Compose setup remain unchanged.

Architecture and evidence flow

MCP server definition
        |
        v
Tool/schema discovery  --->  conservative draft contract
                                   |
                                   v
                       human-reviewed security policy
                                   |
                  +----------------+----------------+
                  |                                 |
          Streamable HTTP                      STDIO process
                  |                                 |
                  +----------------+----------------+
                                   |
                 identities + tenant/session/replay probes
                                   |
                  declared and independent observers
                                   |
                                   v
              deterministic findings + reproducible evidence
                                   |
          SQLite | HTML | JSON | JSONL | JUnit | SARIF | alerts

The YAML contract is the expected policy. The MCP server's own tool descriptions are discovery input, not the source of authorization truth.

Generate a draft contract

The generator connects to the underlying MCP server, discovers its tools and schemas, and creates a conservative YAML draft. It cannot safely infer who should be authorized, which tenant owns an object, which test data is safe, or which side effects are approved.

After generation, review all REVIEW_REQUIRED values, identities, probes and side-effect rules before running a scan.

From VS Code with GitHub Copilot

VS Code stores MCP definitions in a workspace .vscode/mcp.json or a user-profile mcp.json. Copy the server's command, args, cwd and required environment mapping.

Example VS Code definition:

{
  "servers": {
    "memory": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-memory"]
    }
  }
}

Generate the draft:

mcp-guard contract generate \
  --transport stdio \
  --name memory \
  --command npx \
  --arg=-y \
  --arg=@modelcontextprotocol/server-memory \
  --output contracts/memory-draft.yaml

For a remote server from mcp.json:

export MCP_ACCESS_TOKEN='replace-me'

mcp-guard contract generate \
  --transport streamable-http \
  --name remote-tools \
  --url https://mcp.example.com/mcp \
  --bearer-token-env MCP_ACCESS_TOKEN \
  --output contracts/remote-tools-draft.yaml

From Claude Code

Inspect the configured server:

claude mcp list
claude mcp get <server-name>

Use the displayed command and arguments with the same STDIO generator. For example, a server added as:

claude mcp add --transport stdio local-tools -- python -m my_server.app

can be discovered with:

mcp-guard contract generate \
  --transport stdio \
  --name local-tools \
  --command python \
  --arg=-m \
  --arg=my_server.app \
  --output contracts/local-tools-draft.yaml

From OpenClaw

OpenClaw can run as a STDIO MCP server using openclaw mcp serve. Generate a draft directly from that server process:

mcp-guard contract generate \
  --transport stdio \
  --name openclaw \
  --command openclaw \
  --arg=mcp \
  --arg=serve \
  --output contracts/openclaw-draft.yaml

The OpenClaw gateway and any required authentication must already be configured. Use --server-env CHILD_ENV=SOURCE_ENV when the child process requires environment variables without storing secret values in the contract.

OpenAI Codex, Cursor, Windsurf, Gemini CLI, and other MCP-capable hosts

The host application is not the scan target; its configured MCP server is. This applies to OpenAI Codex and other MCP-capable AI assistants or co-workers: Behaviour Guard tests the configured MCP server boundary, not the assistant's reasoning or the host as a whole. Map the server's command, arguments, working directory and environment, or its remote endpoint and credential settings, into the same contract-generation flow. Native host-configuration imports and multi-agent or cross-server workflow assurance remain future work.

Find the server entry in the host's MCP settings and map it as follows:

Host field mcp-guard contract generate
Local command --command
Each local argument repeated --arg
Working directory --cwd
Child-process environment repeated --server-env CHILD_ENV=SOURCE_ENV
Remote URL --url with --transport streamable-http
Bearer credential --bearer-token-env

Generic local example:

mcp-guard contract generate \
  --transport stdio \
  --name project-tools \
  --command uvx \
  --arg=my-mcp-package \
  --cwd /path/to/project \
  --output contracts/project-tools-draft.yaml

The generated YAML is intentionally editable. Add or remove identities, permitted tools, tenant probes, session tests, replay checks, filesystem paths, network destinations and process allowlists. See docs/contract-reference.md.

Temporal integrity: catch sleeper-style metadata changes

A server can look harmless during discovery and change the metadata presented to an agent later. temporal_integrity keeps a real MCP session open, executes a reviewed driver tool repeatedly, and compares canonical fingerprints of the metadata observed before and after those calls.

temporal_integrity:
  enabled: true
  identity: reviewer
  driver_tool: format_text
  driver_arguments:
    text: behaviour-guard-canary
  sessions: 2
  retests_per_session: 5
  rediscover_after_each_call: true
  monitor_tools: true
  monitor_prompts: true
  monitor_resources: false
  probe_argumentless_prompts: true
  prompt_probes:
    assistant_guidance: {}
  severity: high

retests_per_session is user-configurable from 1 to 50. The driver must already exist in the reviewed contract, be permitted for the selected identity, and be marked read_only: true; configuration validation rejects a state-changing driver before the scan starts. Tool/prompt list-change notifications are captured as evidence when the server emits them, but detection does not depend on the notification: Behaviour Guard actively re-discovers metadata at the configured checkpoints.

Real-world pattern: Deadbugz, August 2026

Pillar Security documented Deadbugz in August 2026, an active MCP supply-chain campaign delivered through public GitHub pull requests.

The reviewed campaign combined two useful security lessons:

  • MCP configuration changes can introduce a new remote or local server into a developer environment.
  • A server that looks harmless during initial discovery can change the metadata presented to the agent only after normal use.

Pillar reported 23 campaign-related pull requests: 17 introduced a remote MCP configuration, four referenced a hidden local Python artifact, and two were directory or listing submissions. The productivity-suite MCP server initially exposed ordinary formatting and summarization behaviour. After three normal tool calls, subsequent tools/list and prompts/get responses changed and began presenting credential-seeking instructions to the attached agent.

MCP Behaviour Guard v0.3 addresses the observable security boundaries in that pattern:

Deadbugz behaviour Behaviour Guard v0.3
PR adds or changes an MCP server definition Approved host-configuration snapshots can flag added, removed or structurally changed MCP servers
Server initially exposes benign metadata Initial tool, prompt and resource metadata is fingerprinted
Trigger depends on repeated normal use sessions and retests_per_session control repeated same-session testing
tools/list changes after the trigger Tool metadata drift is detected deterministically
prompts/get content changes after the trigger Configured prompt payloads are fingerprinted and compared
MCP advertises list-change events Tool/prompt list-change notifications are retained as supporting evidence
Changed metadata appears only after call three The included inert demo raises TEMPORAL-METADATA-001 after the third call

The validated local demonstration deliberately reproduces the timing and metadata-drift technique, not Deadbugz's credential-seeking payload. It uses a harmless format_text call. After call three, Behaviour Guard observed a changed format_text tool definition and changed assistant_guidance prompt payload, captured both tool and prompt list-change notifications, and raised a high TEMPORAL-METADATA-001 finding with no execution errors.

MCP Behaviour Guard temporal integrity report detecting metadata drift after the third call

Harmless temporal demo: tool and prompt metadata changed after call three and was raised as a High finding.

Terminal evidence for MCP Behaviour Guard temporal metadata integrity test

The host-configuration provenance check was also validated independently: an unchanged approved configuration produced no drift, while adding a second MCP server and changing a host sandbox control produced a deterministic configuration-drift result. Credential-like test values were not copied into the generated baseline or diff evidence.

MCP Behaviour Guard detecting an added MCP server and changed host controls

Configuration provenance: the reviewed baseline stayed clean; adding another MCP server and changing a host control produced drift.

This is detection of observed configuration or metadata integrity violations, not a claim that Behaviour Guard universally prevents Deadbugz or proves a server is safe. A new MCP server is a review event rather than automatic proof of malware, and downstream agent actions still depend on the host application's own permission and runtime controls.

A finite retest count is not proof that a sleeper server is clean. Activation could depend on a larger call count, elapsed time, client fingerprint, identity or probability. Scheduled scans and independent runtime controls still matter. See docs/temporal-integrity.md for the evidence model and limits.

MCP host-configuration provenance

Behaviour Guard can also baseline MCP server definitions in JSON/JSONC/common JSON5-style host configuration and flag added, removed or changed entries. The snapshot covers the launch fields it understands directly (endpoint, command, arguments, working directory, environment and headers) and also fingerprints additional per-server controls such as enablement, sandbox flags, OAuth/TLS settings, timeouts and tool filters. VS Code-style top-level sandbox and inputs controls are fingerprinted too. Credential-like values are redacted, while other non-structural strings in those extra controls are hashed rather than copied verbatim.

# Keep the reviewed snapshot in a tracked, review-protected path for CI.
mkdir -p policy
mcp-guard config snapshot .vscode/mcp.json \
  --host "VS Code" \
  --output policy/vscode-mcp.json

# Later, or in CI. Exit code 1 means the MCP configuration drifted.
mcp-guard config check .vscode/mcp.json policy/vscode-mcp.json \
  --output host-config-diff.json

The configuration check is provenance/drift detection, not malware analysis. A new server is a review event, not automatically a malicious finding. In CI, protect the approved snapshot with normal code-review controls (for example CODEOWNERS or branch protection); a drift check cannot help if the same untrusted change can silently replace both the MCP configuration and its approved baseline.

Large multi-tenant environments

Compile many identities from reusable roles instead of duplicating YAML:

mcp-guard contract expand-tenants \
  contracts/http-demo.yaml \
  examples/tenants.csv \
  examples/role-policy.yaml \
  --output contracts/generated-tenants.yaml

The CSV supports per-tenant allow_tools and deny_tools exceptions. Application-specific object IDs and safe probes remain in the reviewed base contract.

Reports, history and monitoring

Each run can produce:

  • interactive HTML with critical-first expandable rows;
  • JSON findings and JSONL MCP traces;
  • JUnit XML and SARIF for CI workflows;
  • tool inventory and baseline artifacts;
  • SQLite run, invocation, finding and alert history.

Run once from cron, launchd or CI:

mcp-guard monitor contracts/production.yaml \
  --once \
  --minimum-severity high \
  --webhook-url "$MCP_GUARD_WEBHOOK"

Or run on an interval:

mcp-guard monitor contracts/production.yaml --interval 3600

Finding fingerprints suppress repeated alerts unless a configured repeat interval is reached. Interval monitoring reloads the reviewed contract and reruns its checks; it does not automatically approve a newly generated contract.

Similar solutions and where this fits

The tools below have overlapping but different published goals. This table is a scope comparison, not a claim that one tool is universally stronger.

Project Documented primary focus Relationship to MCP Behaviour Guard
Snyk Agent Scan Inventory and scanning of agent configurations, MCP servers and skills for threats including prompt injection, tool poisoning, toxic flows and malware payloads. Useful for discovering installed components and content/configuration risk. Behaviour Guard adds a human-defined contract and controlled identity, tenant, session, side-effect and replay checks.
Trail of Bits MCP Context Protector Runtime wrapper with configuration pinning, tool-response guardrails/quarantine and control-character sanitization. Useful as a live protective wrapper. Behaviour Guard is an on-demand/CI regression harness and is not a runtime enforcement gateway.
MCP Behaviour Guard Contract-driven tests for authorization, tenant/session isolation, declared side effects, replay/idempotency and behavioural drift. Intended to complement scanners, wrappers, protocol tests and evaluation frameworks rather than replace them.

Detailed source notes and the comparison date are maintained in docs/comparison-sources.md. This is a scope comparison, not a detection benchmark.

Safety and limitations

  • Run only against MCP servers you own or are explicitly authorized to test.
  • State-changing and replay probes require contract permission and --lab-mode.
  • The project is an MVP, not a complete MCP security platform.
  • Contract generation cannot infer the organisation's true authorization policy.
  • Side-effect detection is limited to configured observers; it is not arbitrary OS-wide syscall monitoring.
  • On macOS and Ubuntu, concurrent Behaviour Guard processes running as the same OS user serialize access when they resolve to the same configured target or observer ownership key. This reduces cross-run attribution of uncorrelated evidence, but it does not coordinate other OS users, other hosts, isolated container /tmp namespaces, unrelated external writers, or distinct aliases for the same backend.
  • Temporal integrity uses a finite number of calls and sessions; it can detect observed drift but cannot prove that no delayed or conditional activation exists.
  • Host-config provenance detects definition changes; it does not determine whether a newly added server or pull request is malicious.
  • The harness tests MCP servers directly. It does not claim to security-test all behaviour or reasoning of OpenAI Codex, VS Code, GitHub Copilot, Claude Code, OpenClaw, Cursor, Windsurf, Gemini CLI or other MCP-capable hosts themselves.
  • No comparative detection benchmark against the projects above has been performed.
  • This release line remains pinned to mcp==1.28.1; MCP Python SDK 2.x / protocol 2026-07-28 has not been integration-tested. The 2026-07-28 protocol removes protocol-level HTTP sessions, so the temporal state model needs compatibility testing before migration.

Future work

  • Local contract editor UI with field explanations, validation and safe defaults
  • Automatic import from common agent MCP configuration files into draft contracts
  • OAuth and delegated-authorization test flows
  • MCP Python SDK 2.x / protocol 2026-07-28 migration, including subscriptions/listen handling for list-change notifications
  • Legacy HTTP+SSE compatibility where required, plus additional transports supported by specific hosts or SDKs
  • OpenTelemetry, proxy, DNS and portable OS-level observers
  • Approval-token replay and argument-after-approval tests
  • Property-based schema-valid probes and stronger fixture management
  • Multi-server and multi-agent workflow contracts
  • Taint/provenance tracking and richer behavioural baselines
  • Cross-server/tool-shadowing tests that include host/agent context
  • GitHub/GitLab templates and vendor-specific alert adapters
  • Historical evidence search and dashboards
  • Agent-to-MCP proxy monitoring

Development

make format
make lint
make test
make demo-stdio
make demo-temporal

See CONTRIBUTING.md, SECURITY.md, and docs/threat-model.md.

AI-assisted development disclosure

AI assistance was used during design, implementation review, test generation and documentation drafting. Deterministic checks, safety gates and evidence requirements are represented explicitly in code and tests. Review the code and dependencies before using it in a real environment.

License

MIT. See LICENSE.

Release files for mcp-behaviour-guard 0.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mcp-behaviour-guard 0.5.0
File Size Uploaded
mcp_behaviour_guard-0.5.0.tar.gz 8.1 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for mcp-behaviour-guard 0.5.0
File Interpreter ABI Platform
mcp_behaviour_guard-0.5.0-py3-none-any.whl Python 3 none any Details

Total release size: 8.2 MB

Release files / mcp_behaviour_guard-0.5.0.tar.gz

Download URL mcp_behaviour_guard-0.5.0.tar.gz
Size 8.1 MB
Tags Source
SHA-256 checksum
How to use checksums
2a60e092b97299088ffc485ff4b82a610378d142eed7c0d4368900ec777cc544
BLAKE2b-256 checksum
How to use checksums
47b229e419901b32aca748bf87de46a130f30fd5c0daa3bb2c606406c1043be0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.

Transparency log

Release files / mcp_behaviour_guard-0.5.0-py3-none-any.whl

Download URL mcp_behaviour_guard-0.5.0-py3-none-any.whl
Size 72.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
aca69e2d44aeeff627839a66234fbe46f7e12eaecd72000dddcc1687e49bfe37
BLAKE2b-256 checksum
How to use checksums
3b1ce54e2057b70b7cf9d847dca30b0e05bf86d03ce98d2116f629c857c4bf69
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.5.0 This release

2 release files

0.4.3

2 release files

0.4.2

2 release files

0.4.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page