Skip to content

Latest commit

 

History

91 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MCP-Trust

MCP-Trust pipeline

Research scaffold for studying how LLM agents should decide whether to trust an external MCP (Model Context Protocol) server before calling it, in educational settings.

The repo has three layers:

  1. Live path — a persistent MCP proxy (trustguard_live.py) that intercepts a real MCP connection, collects observable evidence, and runs an LLM panel validator to reach an allow / deny verdict before the agent trusts the server.
  2. Offline data generation + simulator — the original Week-1 scaffold (generate_tasks.py, generate_servers.py, simulator.py) for synthetic tasks, servers, and evidence. No real MCP servers, no real student data.
  3. Benchmark + experiments — a corpus MCP server, a two-container benchmark runner, and additive research harnesses (rug-pull hash detection, validator-coercion ablation).

Python 3.9+ required.


Quick start

cd MCP-Trust
pip install -r requirements.txt

# Credentials (a provider is required for any LLM-backed step)
cp .env.example .env
# edit .env — set GOOGLE_CLOUD_PROJECT (Vertex) or OPENROUTER_API_KEY

# --- offline data generation (no LLM) ---
python3 generate_tasks.py       # data/tasks_100.json
python3 generate_servers.py     # data/servers.json + data/evidence.json

# --- simulator demo ---
PROVIDER=vertex python3 simulator.py     # LLM-backed
python3 simulator.py --no-llm            # offline templates (no credentials)

# --- live proxy (sits between an agent and a downstream MCP server) ---
python3 trustguard_live.py --transport sse \
  --downstream-url http://localhost:9090/sse \
  --provider openrouter --validator panel --panel-models panel6

The panel validator runs on OpenRouter (--provider openrouter); single-model and committee modes are also available (--validator single|committee).


Decision model (live path)

The validator emits exactly two decisions and an integer risk score 0–100:

risk_score decision
0–49 allow
50–100 deny

DECISIONS = ("allow", "deny") in validator.py; the threshold is applied at _score_to_decision. There is no separate "sandbox" verdict in the live path.

Panel validator (panel6) — six models, aggregated by majority or optimal-weight: google/gemini-2.5-flash, anthropic/claude-sonnet-5, openai/gpt-4o, meta-llama/llama-4-maverick, qwen/qwen3.7-plus, moonshotai/kimi-k2.7-code.


Scenarios

Scenario (server) Tools Sensitive data
ai_tutoring (trustguard-ai-tutoring) explain_concept, generate_practice, record_attempt, get_progress Student questions, learning history
lms (trustguard-lms) list_assignments, get_assignment, get_submission, get_grade, submit_assignment Grades, submissions
plagiarism_detection (trustguard-plagiarism) check_similarity, list_matches, get_report, flag_integrity Essays, drafts

Attack types

attack_type ground_truth_label expected decision
none safe allow
prompt_injection malicious deny
tool_poisoning malicious deny
permission_abuse malicious deny
rug_pull safe (v1.0.0) / malicious (v1.1.0) allow / deny
on_off malicious deny

Ground-truth labels are set deterministically in attacks.py / the benchmark corpus — never inferred by the LLM.


File structure

MCP-Trust/
├── Live path
│   ├── trustguard_live.py     # Persistent MCP proxy — evidence → panel validator → allow/deny
│   ├── validator.py           # LLM validator (single / committee / panel); prompt + scoring
│   ├── evidence_collector.py  # EvidenceRecord: hashes, phrase scan, permission inference, trace
│   ├── ledger.py              # Append-only, hash-chained post-consensus audit ledger (no PII)
│   └── schema.py              # Dataclasses: EvidenceRecord, validator_view, …
├── Offline scaffold
│   ├── attacks.py             # ATTACK_TEMPLATES + apply_* transforms (deterministic labels)
│   ├── simulator.py           # MockMCPServer (list_tools, call_tool, make_evidence)
│   ├── generate_tasks.py      # data/tasks_100.json  (fixed seed)
│   └── generate_servers.py    # data/servers.json + data/evidence.json
├── Benchmark + experiments
│   ├── corpus_mcp_server.py   # SSE MCP server serving benchmark corpus entries
│   ├── benchmark_runner.py    # Drives the two-container live benchmark
│   ├── benchmark/             # Corpus generation + evaluation/calibration sets
│   ├── rp_hash_experiment.py  # Rug-pull v1→v2 schema-hash detection (record-only ledger seam)
│   └── coercion_ablation.py   # Validator-coercion ablation (2×2, description/trace channels)
├── llm_client.py              # Provider abstraction: Vertex AI / OpenRouter / offline
├── .env.example
├── requirements.txt
└── README.md

Audit ledger

ledger.py is an append-only, hash-chained (prev_entry_hash) log written after consensus. Each entry carries only hashes, the per-validator {weight, accuracy, risk_score, decision} map, the consensus verdict, and a human-readable server_idnever tool-response text, rationales, or the evidence record. Enable it on the live path with --ledger (or LEDGER_ENABLED=1); it is off by default so calibration/eval sweeps write nothing.


Validator-coercion ablation

coercion_ablation.py is a standalone, additive harness (it does not modify the live scoring path, corpus, or ledger). It measures how often a prompt injection can coerce the validator itself into an attacker-chosen risk score + allow, across a 2×2 matrix:

  • Modescurrent (with system preamble) vs naked (preamble stripped).
  • Armsbaseline (inject, do nothing), pre-screen (oracle — removes the exact injected span; theoretical ceiling), regex-prescreen (pattern detector, no access to the recorded span, evaluated on held-out variants), plus an opt-in heuristic-prescreen (validator-target-aware non-LLM detector).
  • Channels (--channel) — description (payload in the tool schema; malicious tool author) and trace (payload in captured tool output; compromised server at runtime). Two distinct attack surfaces.

LLM provider configuration

PROVIDER value Backend Credentials needed
vertex (default) Google Gemini / Claude via Vertex AI GOOGLE_CLOUD_PROJECT in .env
openrouter Any model via OpenRouter (panel validator) OPENROUTER_API_KEY in .env
none Offline templates / heuristic none

--no-llm is equivalent to PROVIDER=none. Data-generation scripts are always offline — they never call an LLM regardless of env vars.


Reproducibility & design notes

  • Deterministic labels. The behavior path (which prompt, which flags, which status) is selected by Python code from attack_type / version. The LLM cannot change ground_truth_label, risk_label, or the expected decision. Generators call random.seed(42) and produce byte-identical JSON on re-run.
  • No raw PII in outputs. sanitized_trace fields carry only short neutral summaries; the ledger stores only hashes + numeric per-validator metrics.
  • Inferred permissions. permission_scope (what the server claims) is inferred by a deterministic rule from tool names + parameter names (evidence_collector._infer_permission_scope), required_permissions is caller/task-supplied, and excessive_permissions = permission_scope − required_permissions. The validator reasons over these lists but does not compute them.
  • Hashes. metadata_hash and schema_hash are SHA-256 digests, making version changes in rug_pull detectable by hash comparison.

About

No description, website, or topics provided.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages