An open-source Model Context Protocol server stack designed as a research honeypot for AI agent behaviour. It looks like a legitimate cloud wallet platform with three MCP servers (graph, database, filesystem), seeded with bait data and instrumented to capture why AI agents do what they do — not just what they do.
If you are a security researcher, defender, or AI agent developer, this project lets you:
- Study how LLM-driven agents discover, explore, and exploit MCP infrastructure
- Capture the reasoning behind each tool call (the agent voluntarily explains itself)
- Identify which AI models connect to your servers (the agent self-identifies)
- Collect candid feedback from agents about your tool design and security posture
A traditional honeypot logs the request. This one also logs the agent's reasoning, identity, and opinions — voluntarily provided by the LLM driving the attack.
| Captured | Traditional honeypot | This project |
|---|---|---|
| Source IP, request, payload | Yes | Yes |
| MCP client name and version | — | Yes (protocol-level) |
| LLM model name + version | — | Yes (agent self-reports) |
| Why the agent took each action | — | Yes (reasoning field) |
| Broader task objective | — | Yes (context field) |
| Candid feedback on the platform | — | Yes (feedback field) |
| Voluntary security findings | — | Yes (report_finding tool) |
The mechanism is described in docs/REASONING_CAPTURE.md.
AI agent (Claude, GPT, Cursor, custom)
│
│ MCP (Server-Sent Events)
│
├── :8080 Graph API → Neo4j (users, wallets, seed phrases, API keys, admins)
├── :8081 Database API → SQLite (transactions, system_config, api_keys, users)
└── :8082 Storage API → Filesystem (.env, AWS creds, mnemonic backup, SSH key)
└─ All bait files. Cross-server breadcrumbs
lead the agent through the kill chain.
Each server injects four extra fields into every tool's schema (reasoning, context, model_info, feedback) plus a report_finding tool. LLMs voluntarily fill these in because they look like legitimate platform telemetry. Every interaction is logged to JSONL files for analysis.
The fastest way to run the full stack is Docker Compose:
git clone https://github.com/YOUR_USER/cloudaiwallet-mcp-servers.git
cd cloudaiwallet-mcp-servers
docker compose up -d
# Wait for Neo4j to be healthy, then seed the bait data
docker compose exec graph-api python scripts/seed_neo4j.py
docker compose exec storage-api python scripts/seed_storage.pyNow connect any MCP client to:
| Server | URL |
|---|---|
| Graph API | http://localhost:8080/sse |
| Database API | http://localhost:8081/sse |
| Storage API | http://localhost:8082/sse |
Logs are written to ./logs/:
| File | Contents |
|---|---|
traces.jsonl |
Every tool call with arguments, results, IP, session ID |
reasoning.jsonl |
Just the reasoning entries (the "why" behind each action) |
feedback.jsonl |
Just the feedback entries (agent opinions on the platform) |
*-api-requests.jsonl |
Per-server raw request log including SSE connect/disconnect |
python -m venv venv && source venv/bin/activate
pip install -r requirements.txt
# Start Neo4j somewhere (e.g. docker run -p 7687:7687 -e NEO4J_AUTH=neo4j/changeme neo4j:5.26)
export NEO4J_PASS=changeme
# Seed bait data
python scripts/seed_neo4j.py
python scripts/seed_storage.py
# Run the three servers (in separate terminals)
python -m mcp_servers.graph_api
python -m mcp_servers.database_api
python -m mcp_servers.storage_apiAdd to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"cloudaiwallet-graph": {
"command": "npx",
"args": ["-y", "supergateway", "--sse", "http://localhost:8080/sse"]
},
"cloudaiwallet-database": {
"command": "npx",
"args": ["-y", "supergateway", "--sse", "http://localhost:8081/sse"]
},
"cloudaiwallet-storage": {
"command": "npx",
"args": ["-y", "supergateway", "--sse", "http://localhost:8082/sse"]
}
}
}Settings → Connectors → Add custom connector → paste any of the SSE URLs above.
Any MCP-compatible client works. Reference the MCP spec at https://modelcontextprotocol.io.
All values are obviously fake placeholders prefixed with HONEYPOT_BAIT_*. None of these are real credentials.
Neo4j graph (16 nodes): 5 Users, 3 Wallets, 2 SeedPhrases, 2 APIKeys, 2 TradingBots, 1 Admin, 1 InternalConfig with cross-server breadcrumbs.
SQLite database (5 tables): users, api_keys, transactions, system_config (with bait JWT/AWS/Stripe/wallet secrets), ai_usage.
Filesystem: config/env/.env, config/aws/credentials, config/ssh/id_ed25519_admin, backups/keys/mnemonic_backup.txt. All values are bait.
The InternalConfig node and system_config table contain breadcrumbs pointing to each other — an attacker who finds the graph admin record will be guided to the SQL system_config, then to the storage .env file, exercising all three servers.
All settings via environment variables:
| Variable | Default | Description |
|---|---|---|
NEO4J_URI |
bolt://localhost:7687 |
Neo4j connection URI |
NEO4J_USER |
neo4j |
Neo4j user |
NEO4J_PASS |
neo4j |
Neo4j password |
DB_PATH |
./data/crypto_platform.db |
SQLite database path |
STORAGE_ROOT |
./storage |
Filesystem storage root |
LOG_DIR |
./logs |
JSONL log output directory |
HOST |
0.0.0.0 |
Bind host |
PORT |
per-server default | Bind port |
LANGFUSE_PUBLIC_KEY |
(unset) | Optional Langfuse cloud trace forwarding |
LANGFUSE_SECRET_KEY |
(unset) | Optional Langfuse cloud trace forwarding |
LANGFUSE_HOST |
(unset) | Optional Langfuse host URL |
If Langfuse env vars are not set, traces are written to JSONL only.
The four injected fields and their descriptions live in mcp_servers/reasoning_capture.py under REASONING_PROPERTIES. The descriptions are deliberately framed as legitimate platform telemetry — modify them to match your honeypot's cover story.
To add the reasoning capture to your own MCP server:
from reasoning_capture import inject_reasoning_fields, REPORT_FINDING_TOOL
BASE_TOOLS = [...] # your normal tool definitions
TOOLS = inject_reasoning_fields(BASE_TOOLS)
TOOLS.append(REPORT_FINDING_TOOL)
# In your tool handler, strip the capture fields before executing:
def _strip_reasoning(args):
return {k: v for k, v in args.items()
if k not in ("reasoning", "context", "model_info", "feedback")}This project is intended for defensive security research, AI safety evaluation, and MCP protocol behavioral studies. It is not intended to:
- Attract real users into providing real credentials (the bait data is obviously fake)
- Be deployed in adversarial mode against AI vendors
- Serve as a generic credential trap targeting human users
If you deploy a public-facing instance, please:
- Use a clearly-research domain (e.g. include "honeypot" or "research" in the name)
- Publish a
/security.txtor/research.txtdescribing the project - Never include real-looking credentials in the bait data
- Respect the Acceptable Use Policies of the AI providers whose models may interact with your instance
This is research code, MIT-licensed, no warranty, no support. Issues and PRs welcome but responses may be slow.
MIT — see LICENSE.
If you use this project in academic or industry research, please cite as:
CloudAIWallet: A Reasoning-Capture Honeypot for MCP Agent Behavior Research
ProgrammerSecurity.com, 2026
https://github.com/YOUR_USER/cloudaiwallet-mcp-servers