Enterprise Bitcoin Intelligence with RBAC, Rate Limiting & Audit Trails
Hackathon Submission: Secure & Govern MCP category Built with agentgateway + bitcoin-mcp
AI agents accessing financial data without guardrails is a disaster waiting to happen.
bitcoin-mcp exposes 49 tools for querying the Bitcoin network: block analysis, fee estimation, mempool inspection, address lookups, and more. Two of those tools are write operations -- send_raw_transaction (broadcasts a signed transaction to the network) and generate_keypair (creates private key material).
In a production environment, you need answers to hard questions:
- Who is calling these tools?
- What are they allowed to do?
- How often can they call?
- Where is the audit trail?
Bitcoin Gateway Guard answers all four by placing agentgateway in front of bitcoin-mcp, enforcing JWT authentication, role-based access control, per-consumer rate limiting, and full OpenTelemetry tracing to Jaeger.
agentgateway (port 3000)
+---------------------------------+
| |
AI Agent -------->| JWT Auth |
(Claude, | | |
GPT, | v |
custom) | RBAC Policy Engine |-------> Jaeger (port 16686)
| | | OTLP
| v |
| Rate Limiter (10 req/min) |
| | |
| v |
| bitcoin-mcp (49 tools) |
| |
+---------------------------------+
Roles:
reader --> 47 read-only tools (blocks, fees, mempool, addresses...)
admin --> all 49 tools (includes send_raw_transaction, generate_keypair)
Every request flows through four security layers before reaching bitcoin-mcp. No exceptions, no bypasses.
| Layer | What It Does | Config |
|---|---|---|
| JWT Authentication | Validates signed tokens, rejects anonymous access | RSA-256, custom issuer/audience |
| RBAC | reader blocked from write tools, admin gets full access |
CEL expressions on jwt.role + mcp.tool.name |
| Rate Limiting | 10 requests/minute per consumer, token bucket algorithm | localRateLimit policy |
| Audit Trail | Every tool call traced with full context | OpenTelemetry --> Jaeger |
- 47 read-only tools:
get_block_count,get_blockchain_info,get_fee_estimates,analyze_mempool,get_address_balance,validate_address,decode_raw_transaction, and 40 more - 2 write/sensitive tools:
send_raw_transaction(broadcasts transactions),generate_keypair(creates private keys)
See SECURITY_MODEL.md for the full breakdown.
pip install pyjwt cryptography
python scripts/generate_keys.pyThis creates keys/pub-key.pem, keys/priv-key.pem, keys/reader.jwt, and keys/admin.jwt.
docker compose up -dThis starts three services:
- bitcoin-mcp -- the MCP server with 49 Bitcoin tools
- agentgateway -- the security gateway on port 3000
- jaeger -- the tracing UI on port 16686
# Reader can query blocks (ALLOWED)
python scripts/test_rbac.py --user reader --tool get_block_count
# Reader cannot broadcast transactions (DENIED)
python scripts/test_rbac.py --user reader --tool send_raw_transaction
# Admin can do everything (ALLOWED)
python scripts/test_rbac.py --user admin --tool send_raw_transaction
# Rate limiting kicks in after 10 rapid requests
python scripts/test_rbac.py --user reader --tool get_block_count --burst 15$ python scripts/test_rbac.py --user reader --tool get_block_count
[Bitcoin Gateway Guard] Testing RBAC
User: reader
Tool: get_block_count
Token: keys/reader.jwt
Result: ALLOWED
Response: {"block_count": 889241}$ python scripts/test_rbac.py --user reader --tool send_raw_transaction
[Bitcoin Gateway Guard] Testing RBAC
User: reader
Tool: send_raw_transaction
Token: keys/reader.jwt
Result: DENIED
Error: Authorization denied: role "reader" cannot access tool "send_raw_transaction"$ python scripts/test_rbac.py --user reader --tool get_fee_estimates --burst 15
[Bitcoin Gateway Guard] Burst test: 15 requests
Request 1: 200 OK (2ms)
Request 2: 200 OK (1ms)
...
Request 10: 200 OK (2ms)
Request 11: 429 Rate Limited (0ms)
Request 12: 429 Rate Limited (0ms)
...
Request 15: 429 Rate Limited (0ms)
Summary: 10 allowed, 5 rate-limitedThe entire security policy lives in config.yaml:
config:
tracing:
otlpEndpoint: http://jaeger:4317
randomSampling: trueAll tool invocations are exported as OpenTelemetry spans to Jaeger. Every span includes the tool name, the JWT claims (role, subject), the response status, and timing data.
localRateLimit:
- maxTokens: 10
tokensPerFill: 1
fillInterval: 60sToken bucket algorithm: 10 tokens max, refills 1 token per second, bucket resets every 60 seconds. This gives a sustained rate of 1 req/sec with burst capacity up to 10.
jwtAuth:
issuer: bitcoin-gateway-guard
audiences: [bitcoin-mcp]
jwks:
file: ./keys/pub-key.pemEvery request must carry a valid JWT signed with the RSA private key. The gateway validates the signature, issuer, audience, and expiration. No token = no access.
mcpAuthorization:
rules:
- 'jwt.role == "admin"'
- 'jwt.role == "reader" && mcp.tool.name != "send_raw_transaction" && mcp.tool.name != "generate_keypair"'Rules are evaluated as CEL expressions. A request is authorized if any rule evaluates to true. The admin rule matches all tools. The reader rule explicitly excludes the two write operations.
After running some test queries, open http://localhost:16686 to see the full audit trail:
- Select service agentgateway from the dropdown
- Click Find Traces
- Each trace shows:
- Tool name called
- JWT subject (who called it)
- JWT role (reader or admin)
- Authorization result (allowed or denied)
- Duration (latency through the gateway)
- Response status
This gives security teams a complete, searchable, time-ordered record of every AI agent interaction with Bitcoin data. Feed it into your SIEM, set up alerts on denied requests, detect anomalous access patterns.
The generate_keys.py script is for development. In production:
- Use a proper secrets manager (HashiCorp Vault, AWS KMS) for JWT signing keys
- Rotate keys on a schedule and update the gateway config
- Use JWKS endpoints instead of file-based keys for zero-downtime rotation
- Run bitcoin-mcp and agentgateway in the same private network
- Only expose agentgateway's port (3000) to AI agents
- Never expose bitcoin-mcp directly
The default 10 req/min is conservative. Adjust based on your use case:
- Dashboard refresh: 30 req/min may be appropriate
- Batch analytics: Use a separate admin-tier rate limit
- Real-time monitoring: Consider per-tool rate limits
agentgateway is stateless. For high availability:
- Run multiple gateway instances behind a load balancer
- Use Redis-backed distributed rate limiting
- Export traces to a production-grade Jaeger or Grafana Tempo cluster
Set up alerts for:
- Authorization denied events (potential privilege escalation attempts)
- Rate limit violations (runaway agents)
- Elevated error rates from bitcoin-mcp
- JWT validation failures (token theft or forgery attempts)
submissions/security/
README.md # This file
SECURITY_MODEL.md # Detailed security model documentation
blog.md # dev.to-ready blog post
config.yaml # agentgateway configuration
docker-compose.yml # Full stack: bitcoin-mcp + agentgateway + jaeger
Dockerfile.bitcoin-mcp # Bitcoin MCP server container
scripts/
generate_keys.py # Generate RSA keys and JWT tokens
test_rbac.py # Test RBAC, rate limiting, auth
keys/ # Generated keys and tokens (gitignored in production)
- bitcoin-mcp -- 49-tool MCP server for Bitcoin network intelligence
- agentgateway -- Open-source gateway for securing MCP servers
- Jaeger -- Distributed tracing platform
Built for the Secure & Govern MCP Hackathon. Because AI agents with access to financial infrastructure deserve the same security posture as the humans they assist.