mcp-audit-gateway
Wraps the GitHub MCP server to log, sign, and hash-chain all tool calls, providing a tamper-evident audit trail for GitHub operations.
Exports trace and metric data from the audit gateway to OpenTelemetry collectors for observability.
mcp-audit
Your AI agent made 847 tool calls yesterday. Can you verify what it did?
Tamper-evident audit trail for AI agent tool calls.
Not to be confused with
amin-ale/mcp-audit-gateway, an unrelated Python project with the same name.
Setup (10 seconds)
Before:
{
"command": "npx",
"args": ["@modelcontextprotocol/server-github"]
}After:
{
"command": "npx",
"args": ["mcp-audit", "wrap", "--", "npx", "@modelcontextprotocol/server-github"]
}Every tool call is now cryptographically signed and hash-chained. Nothing else changes. The MCP server works exactly as before.
Related MCP server: DCL Evaluator
What it does
$ mcp-audit tail
✓ 14:32:01 github/create_pr 234ms bf7a2f62
✓ 14:32:03 github/list_issues 89ms a1c4e890
✗ 14:32:05 fs/delete_file 12ms c3d9f012
✓ 14:32:08 github/merge_pr 456ms e5f6a7b8Every entry is signed with HMAC-SHA256 and chained to the previous record. Tamper with any entry and verification fails. Delete an entry and the chain breaks.
Verify integrity
$ mcp-audit verify ~/.mcp-audit/audit.jsonl
Results:
Total records: 847
Valid: 847
Invalid: 0
All records verified successfully.How it works
┌────────────┐ ┌───────────┐ ┌────────────┐
│ MCP Client │──────▶│ mcp-audit │──────▶│ MCP Server │
│ (Claude, │◀──────│ wrap │◀──────│ (any) │
│ Cursor) │ └─────┬─────┘ └────────────┘
└────────────┘ │
▼
~/.mcp-audit/
audit.jsonlThe wrap command spawns your MCP server as a child process and sits between the client and server on stdio. It forwards ALL messages transparently. Only tools/call responses get signed and logged. Everything else passes through untouched.
On first run, a signing key is auto-generated in ~/.mcp-audit/key.hex. No configuration needed.
Audit record format
{
"id": "bf7a2f62-4d0f-4cce-afd2-cbfbf7bca2a5",
"timestamp": "2026-08-16T14:32:01.000Z",
"method": "tools/call",
"toolName": "github/create_pr",
"args": {"title": "Fix bug", "body": "..."},
"durationMs": 234,
"success": true,
"previousHash": "8a3f2b...",
"attestation": "7c4d9e..."
}The attestation is an HMAC-SHA256 signature over the record's canonical fields. The previousHash is SHA-256 of the preceding record. Together they detect tampering, ordering, and completeness.
Use with Claude Desktop
claude_desktop_config.json:
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["mcp-audit", "wrap", "--", "npx", "@modelcontextprotocol/server-github"]
},
"filesystem": {
"command": "npx",
"args": ["mcp-audit", "wrap", "--", "npx", "@modelcontextprotocol/server-filesystem", "/tmp"]
}
}
}Use with Claude Code
.claude/hooks/mcp-servers.json or directly in your MCP server command — prefix with mcp-audit wrap --.
CLI
mcp-audit wrap -- <cmd> [args] # Wrap any MCP server
mcp-audit tail # Live stream of tool calls
mcp-audit verify <log> # Verify chain integrity
mcp-audit serve [config] # Full gateway (policy + OTel)
mcp-audit keygen [dir] # Generate Ed25519 key pairFull gateway mode
For teams that also need access control, rate limiting, and multi-server routing:
mcp-audit serve gateway.config.jsonThe full gateway adds:
Policy engine (glob-based ACLs, per-principal rate limits)
Tool namespacing across multiple upstream MCP servers
OpenTelemetry traces and metrics export
Upstream health management with automatic reconnection
Ed25519 signatures (stronger than HMAC, portable verification)
See gateway configuration for the full schema.
Install
npm install -g @mcp-audit-gateway/coreThis installs the mcp-audit CLI globally. Or use without installing:
npx @mcp-audit-gateway/core wrap -- <your mcp server command>Attestation layer
The signing and verification subsystem goes beyond per-record HMAC. Gateway mode signs each record with Ed25519 (asymmetric, verifiable with a public key) by default; HMAC-SHA256 is available for symmetric-secret deployments. Wrap mode uses HMAC-SHA256 with a per-user key auto-generated on first run. Records are hash-chained across log rotation, crash recovery, and multi-file chains.
Checkpoint records let a consumer detect tail truncation by stashing a single hash externally. The chain carries forward across file rotations (no silent resets). Forced restarts emit signed chain_break records instead of quietly starting fresh.
The canonical form is type-tagged and injective, avoids JCS's float-formatting problem by rejecting unsafe numbers entirely, and has proven cross-language parity via 46 conformance vectors (JS + Python). See SECURITY-DESIGN.md for the full specification and threat model.
Cross-SDK differential testing (10 SDKs, 26 divergences)
MCP has 10 official SDKs and no cross-SDK conformance testing. We built a Wycheproof-style differential harness that runs 40 serialization edge-case tests across all 10 SDKs and reports where they disagree.
Result: 26 wire-level divergences across 8 distinct serializers.
Headline findings
Category | What we found |
Float formatting | 6 SDKs produce 6 different wire representations of |
Key ordering | 3 incompatible algorithms (insertion, lexicographic, numeric-aware) |
Integer precision | TypeScript silently loses precision at 2^53+1 |
String encoding | C# HTML-escapes |
Null handling | Kotlin drops null fields entirely, Swift preserves them |
Float divergence example (1e20)
TypeScript: 100000000000000000000
Python: 1e20
Swift: 1e+20
Java: 1.0E20
C#: 1E+20
PHP: 1.0e+20Six SDKs, six different bytes on the wire. If your hash-chain implementation assumes consistent serialization across SDKs, it breaks silently.
SDKs tested
SDK | Serializer | Version tested |
TypeScript |
| Node 22.20.0 |
Python | pydantic-core (Rust serde) | pydantic 2.10.3 |
Go |
| Go 1.24.1 |
Swift | Foundation | Swift 6.1.2 |
Java | Jackson 2 | OpenJDK 21, Jackson 2.18.2 |
Kotlin |
| Kotlin 2.0.21 |
C# |
| .NET 8.0 |
PHP |
| PHP 8.3 |
Ruby | stdlib | Ruby 3.3 |
Rust |
| rmcp 3.1.4, Rust 1.88 |
Run it yourself
# Clone and run the full matrix
git clone https://github.com/elang2/mcp-audit-gateway.git
cd mcp-audit-gateway && npm ci
./test/vectors/cross-sdk-diff.sh # full matrix (stdlib + SDK layers)
./test/vectors/cross-sdk-diff.sh --layer sdk # SDK wire-level only
./test/vectors/cross-sdk-diff.sh --json # machine-readable outputOutput shows per-test agreement/divergence across all SDKs with exact byte representations.
Use in your CI
Drop this into .github/workflows/cross-sdk-conformance.yml to catch serialization regressions in your own MCP server or client:
name: Cross-SDK Conformance
on: [push, pull_request]
jobs:
conformance:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
- name: Install mcp-audit-gateway
run: npm install @mcp-audit-gateway/core
- name: Run cross-SDK differential tests
run: |
npx cross-sdk-diff --json > results.json
npx cross-sdk-diff
- name: Fail on new divergences
run: |
DIVS=$(python3 -c "
import json
with open('results.json') as f:
d = json.load(f)
print(sum(1 for x in d if not x['agree']))
")
echo "Divergences found: $DIVS"
if [ "$DIVS" -gt 26 ]; then
echo "ERROR: New divergences detected (was 26, now $DIVS)"
exit 1
fiOr run just the conformance vectors (no language SDKs required, only Node.js):
name: Canonicalization Conformance
on: [push, pull_request]
jobs:
vectors:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm test
- run: node test/vectors/verify-checkpoint.mjs
- run: node test/vectors/aps-action-ref-v1.mjsMethodology
The harness uses a Wycheproof-inspired approach: define edge-case inputs (floats at precision boundaries, non-ASCII strings, nested key orders), serialize them through each SDK's actual JSON encoder, and compare the raw bytes. No mocking. Each SDK runner imports the real serialization library that the official MCP SDK uses in production.
Full divergence table with per-test byte comparisons: SDK-AUDIT.md
The audit gateway's canonicalization was designed to be immune to all 26 divergence classes: safe integers only (eliminates float formatting), explicit field order (eliminates sort disagreements), surrogate rejection (eliminates encoding divergences). See SECURITY-DESIGN.md for the threat model.
Examples
Verify your canonicalization against ours
import { canonicalize } from '@mcp-audit-gateway/core';
const record = {
method: 'tools/call',
toolName: 'github/create_pr',
args: { title: 'Fix bug', body: '...' },
timestamp: '2026-08-16T14:32:01.000Z'
};
const canonical = canonicalize(record);
// Deterministic bytes regardless of key insertion order,
// float formatting, or platform JSON encoderPython verification (cross-language parity)
from mcp_audit_gateway import verify_chain
results = verify_chain("/path/to/audit.jsonl")
assert results.valid == results.total
assert results.chain_breaks == 0Run the conformance vectors against your own implementation
# 46 cross-language vectors (JS + Python must agree on every hash)
node test/vectors/verify-checkpoint.mjs
python3 test/vectors/verify-checkpoint.py
# 51 APS action-ref-v1 vectors
node test/vectors/aps-action-ref-v1.mjs
# Full 10-SDK matrix
./test/vectors/cross-sdk-diff.shDocker (self-contained, no host dependencies)
docker build -f test/vectors/Dockerfile -t cross-sdk-diff .
docker run --rm cross-sdk-diffConformance
This implementation satisfies the following properties (verified by cross-language conformance vectors and unit tests):
Injective canonical form (no cross-type digest collisions)
Cross-language sort equivalence (UTF-16 code-unit order)
Unpaired surrogate rejection
Hash chain continuity across log rotation
Planted state detection on startup
No false-positive after legitimate chain break
Fail-closed on corrupt or oversized input
Segmented monotonicity at chain_break boundaries
Consumer-anchored completeness via checkpoint records
Memory-bounded init (1MB cap)
APS action-ref-v1 conformance: 51/51 vectors passing (JCS recomputation + fail-closed digest comparison).
Testing
npm test # unit tests
node test/vectors/verify-checkpoint.mjs # JS conformance vectors
python3 test/vectors/verify-checkpoint.py # Python conformance vectors
node test/vectors/aps-action-ref-v1.mjs # 51 APS vectors
./test/vectors/cross-sdk-diff.sh # 10-SDK differential testLicense
MIT
The APS conformance fixtures (test/vectors/aps-action-ref-v1-vectors.json)
are adapted from upstream Apache-2.0 sources. See
test/vectors/SOURCE.md for provenance and terms.
Available Tools
3 toolsaudit_statusA
Report current audit chain health: total records, last verified timestamp, chain integrity.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden of behavioral disclosure. It usefully indicates the tool reports total records, last verified timestamp, and chain integrity, and 'Report' implies a read-only snapshot. However, it does not explicitly state side-effect-freedom, failure behavior, or what 'chain integrity' means operationally.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single sentence that front-loads the action and resource, then concisely lists the three report contents. There is no redundant or wasted text.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a parameterless status tool, the description is nearly complete: it names the purpose and the expected output fields. It lacks sibling-selection guidance, but the low complexity and zero-parameter signature make that a relatively small gap.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters, so the baseline is 4. The description adds value by clarifying what the status report contains, which is the only semantic information needed for a parameterless tool.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: 'Report current audit chain health.' It also enumerates the report contents, making the tool's purpose unambiguous. However, it does not explicitly distinguish this tool from the sibling tools audit_verify and audit_tail.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No usage guidance is provided. The description does not mention when to use audit_status versus audit_verify or audit_tail, nor does it state any exclusions or prerequisites. Selection among siblings is left entirely to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
audit_tailA
Show the most recent audit log entries with verification status.
| Name | Required | Description | Default |
|---|---|---|---|
| count | No | Number of entries to return (default 10) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. The verb 'Show' implies a non-destructive read operation, but the description does not explicitly state read-only behavior, permissions, or any other side effects or limitations. Minimal behavioral context is disclosed.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single sentence that front-loads the core action and resource with no filler or redundancy. Every word earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with one optional parameter and no output schema, the description sufficiently communicates what is returned (recent audit log entries with verification status). It lacks sibling routing and caveats, but is functionally complete for the simple use case.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% for the single 'count' parameter, which already includes its default value. The tool description itself adds no additional meaning about the parameter, so the baseline score of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb ('Show'), a clear resource ('audit log entries'), and a scope qualifier ('most recent'), while adding a distinctive output feature ('verification status'). This differentiates it from siblings like audit_verify and audit_status.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies usage for viewing recent audit logs but provides no explicit guidance on when to use this tool over audit_verify or audit_status. There are no alternatives or exclusions mentioned.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
audit_verifyA
Verify integrity of an MCP audit log. Returns chain status and any broken links.
| Name | Required | Description | Default |
|---|---|---|---|
| logPath | Yes | Path to the audit log file (JSONL) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
There are no annotations, so the description carries the full burden of behavioral disclosure. It does disclose that the tool returns chain status and broken links, which is valuable, but it does not mention whether the operation is read-only, if there are side effects, or how failures are reported.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two concise sentences with no filler. The core action is front-loaded, and the return behavior is stated immediately after, making it easy to scan.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a single-parameter tool with no output schema, the description gives the essential purpose and return information. It is slightly incomplete on when to choose this over sibling tools, but the overall definition is sufficient for basic invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, and the only parameter, logPath, is already described in the schema as 'Path to the audit log file (JSONL)'. The tool description adds no additional parameter-level detail, so the schema provides the necessary semantics.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb ('Verify integrity') with a clear resource ('MCP audit log') and states the outcome ('Returns chain status and any broken links'). It clearly distinguishes this from sibling tools like audit_tail and audit_status, which have different operational purposes.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No explicit guidance is provided on when to use this tool versus audit_tail or audit_status. The description only states what the tool does, leaving the agent to infer the appropriate selection context without any exclusions or alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
3 tool updates
- First observed
audit_status - First observed
audit_tail - First observed
audit_verify
This server cannot be deployed
TDQS
Scored across 3 tools
audit_tail is clearly distinct, showing recent entries. audit_verify and audit_status overlap somewhat in reporting integrity, but verify focuses on running a chain verification while status provides a summary snapshot.
All tools share the consistent audit_ prefix and lowercase single-word suffixes. The suffixes mix a verb (verify), a command-style word (tail), and a noun (status), but the pattern remains predictable.
Three tools is well-scoped for an audit gateway surface. Each tool covers a distinct aspect of interacting with the audit log without unnecessary bloat.
The set covers the core audit operations: verifying integrity, tailing recent entries, and reporting chain health. It lacks operations like fetching a single entry or exporting records, but those are reasonable minor gaps.
Maintenance
Related MCP Connectors
Bitcoin-anchored, tamper-evident audit log for AI agents — record, disclose and verify actions.
Hash-chained HMAC-signed audit log MCP for A2A (agent-to-agent) calls. Every tool-call, agent-ha...
Etch is a signed audit chain for AI agent decisions, offline-verifiable against pinned public keys.
Verified memory for AI agents. Signed assertions, billing attestation, session continuity.
Related MCP Servers
- AlicenseAqualityCmaintenanceEnables AI agents to sign decisions with post-quantum cryptographic proofs and maintain secure audit trails for compliance. It provides tools for stamping events, verifying chain integrity, and exporting audit data across industries like finance and healthcare.431 npmMIT
- FlicenseNot gradedqualityCmaintenanceTamper-evident cryptographic audit trail for LLM outputs. Compliance logging for AI agent decisions.-
- AlicenseNot gradedqualityFmaintenanceProvides tamper-proof audit logging for AI agents using SHA-256 hash chains, integrity verification, and compliance reporting for the EU AI Act.1MIT
- AlicenseNot gradedqualityCmaintenanceProvides permission gates and tamper-evident audit logging for AI agent tool executions, with declarative policies, consent ladders, and hash-chained verification.MIT