MCP (Model Context Protocol) is how AI agents talk to tools. OpenClaw, the open source agent framework with 300K+ GitHub stars, uses it for every tool call -- get weather, query a database, transfer funds.
Every one of those messages travels unsigned.
No signature. No replay protection. No way to verify the response came from the server you actually called. In production, with multiple agents sharing server pools across trust boundaries, that's an attack surface.
We fixed it in 100 lines.
The Problem
When an OpenClaw agent calls a tool via MCP, the JSON-RPC message goes over stdio to the tool server. The response comes back the same way. At no point does either side prove its identity.
This means:
- Replay attacks -- an intercepted tool call can be captured and replayed
- Response forgery -- an attacker can send a fake response and the agent will trust it
- Signature stripping -- if someone strips security headers, the agent has no way to notice
- Tool poisoning -- a tool's description can be modified in transit without detection
These are documented in the OWASP MCP Top 10 and the OWASP Agentic AI Top 10.
The Fix
We built mcps-openclaw -- a transport wrapper that adds ECDSA P-256 message signing to OpenClaw's MCP layer.
It wraps the MCP SDK's StdioClientTransport:
OpenClaw Agent
↓
McpsSigningTransport (signs outgoing, verifies incoming)
↓
StdioClientTransport (unchanged)
↓
MCP Tool Server
Every outgoing message gets:
- An ECDSA P-256 digital signature
- A unique nonce (replay protection)
- A timestamp
- A passport ID (agent identity)
Every incoming response is verified against the server's public key. Replayed messages are rejected. Forged signatures are caught. Stripped envelopes are blocked in strict mode.
User Experience
For an OpenClaw user, enabling this is three lines of config:
{
"mcpServers": {
"my-server": {
"command": "node",
"args": ["server.js"],
"mcps": { "enabled": true }
}
}
}
Then install the signing library:
npm i mcp-secure
That's it. All tool calls are now signed. Everything else works exactly as before. If mcps is not in the config, nothing changes -- fully backward compatible.
What We Tested
15 tests covering every attack vector, plus a 5-scenario demo:
- Legitimate tool call -- signed request, verified response, end-to-end
- Replay attack -- captured response replayed → blocked (nonce already consumed)
- Forged response -- attacker signs with wrong key → blocked (signature mismatch)
- Signature stripping -- MCPS envelope removed → blocked in strict mode
- Tool poisoning -- tool description modified → detected via hash comparison
All crypto is real ECDSA P-256 -- no mocks, no stubs.
Design Decisions
No hard dependency. The mcp-secure library is lazy-loaded. If it's not installed, signing is skipped with a warning. Existing OpenClaw installations are unaffected.
Ephemeral keys. A fresh key pair is generated per session. No key files to manage, no PKI to set up. For production deployments that need persistent identity, the config accepts a server public key for verification.
Verify before nonce. The signature is verified before the nonce is consumed. This prevents an attacker from burning valid nonces by sending messages with valid nonces but invalid signatures.
Fail closed. If signing fails, the message is not sent. No silent fallback to unsigned.
The Bigger Picture
This is part of MCPS (MCP Secure) -- a cryptographic security layer for the Model Context Protocol. We filed an IETF Internet-Draft for the specification and have been contributing to the OWASP MCP Top 10 and OWASP AI Security Verification Standard.
The OpenClaw integration is one piece. The same signing layer works with any MCP implementation -- we also have integrations for LangChain (Python) and enterprise HA deployments (mcps-ha).
Links
- OpenClaw integration: razashariff/mcps-openclaw
- Core library: mcp-secure on npm (zero dependencies)
- IETF Internet-Draft: draft-sharif-mcps-secure-mcp
- OWASP MCP Top 10: owasp.org/www-project-mcp-top-10
- OpenClaw issue: openclaw/openclaw#49010
If you're running MCP servers in production, unsigned tool calls are an open attack surface. This closes it.
Top comments (0)