https://github.com/p4st4s/mcp-audit
A transparent Go proxy that intercepts, signs, and audits all MCP tool calls between any client and server — without modifying either.
https://github.com/p4st4s/mcp-audit
ai-agents anthropic audit-log golang mcp model proxy security
Last synced: about 2 months ago
JSON representation
A transparent Go proxy that intercepts, signs, and audits all MCP tool calls between any client and server — without modifying either.
- Host: GitHub
- URL: https://github.com/p4st4s/mcp-audit
- Owner: P4ST4S
- License: apache-2.0
- Created: 2026-05-23T16:43:32.000Z (2 months ago)
- Default Branch: main
- Last Pushed: 2026-05-23T17:22:54.000Z (2 months ago)
- Last Synced: 2026-05-23T19:17:31.935Z (2 months ago)
- Topics: ai-agents, anthropic, audit-log, golang, mcp, model, proxy, security
- Language: Go
- Homepage:
- Size: 32.2 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# mcp-audit

[](https://github.com/P4ST4S/mcp-audit/actions/workflows/ci.yml)
[](https://glama.ai/mcp/servers/P4ST4S/mcp-audit)


A drop-in security and observability proxy for MCP servers. `mcp-audit` sits between an MCP client and any upstream MCP server to produce signed audit trails, redact sensitive payloads, enforce allow/deny policies and per-tool rate limits, and expose a local read-only dashboard.
## Why mcp-audit?
[The MCP 2026 roadmap](https://modelcontextprotocol.io/development/roadmap) calls out enterprise needs around audit trails, gateway patterns, and operational visibility. `mcp-audit` fills that gap as a deployable sidecar or local wrapper: it sits between any MCP client and server, preserves protocol traffic, and records signed audit entries for tool calls, resource reads, prompt requests, and all other JSON-RPC methods.
```text
+-------------+ JSON-RPC / MCP +-----------+ JSON-RPC / MCP +-------------+
| MCP client | <-------------------> | mcp-audit | <---------------------> | MCP server |
+-------------+ +-----------+ +-------------+
|
v
JSONL or SQLite audit log
|
v
Read-only dashboard
```
## What This Is / Is Not
`mcp-audit` is not a domain-specific MCP server. It is a transparent security and observability proxy that wraps any MCP server and audits the JSON-RPC traffic passing through it.
Directories may show the tools exposed by the upstream server, not tools implemented by `mcp-audit` itself.
## Supported Transports
- `stdio` for local MCP clients such as Claude Desktop
- `http` for MCP servers exposed over HTTP
## Use Cases
- Audit tool calls made by AI agents in regulated environments
- Detect unexpected or dangerous MCP tool usage
- Keep signed JSONL or SQLite logs for incident review
- Redact sensitive fields before storing requests and responses
- Block disallowed tools and apply per-tool rate limits without modifying the upstream MCP server
## Demo

## Quick Start
Install Go, then build from source:
```bash
brew install go
go install github.com/P4ST4S/mcp-audit/cmd/mcp-audit@v0.2.0
```
Run in stdio mode:
```bash
AUDIT_SECRET="$(openssl rand -hex 32)" \
mcp-audit --transport stdio --upstream "npx @modelcontextprotocol/server-filesystem /tmp"
```
Run in HTTP mode:
```bash
mcp-audit --transport http --upstream http://localhost:8080 --port 4422
```
Run with Docker Compose:
```bash
docker compose up --build
```
The dashboard is available at `http://localhost:9090` by default.
Prometheus metrics are available at `http://localhost:9091/metrics` by default.
## Configuration
`mcp-audit` loads `config.yaml` from the current directory by default. CLI flags override config values, and `AUDIT_SECRET` overrides `audit.secret`.
| Key | Default | Description |
| --- | --- | --- |
| `proxy.transport` | `stdio` | Proxy transport: `stdio` or `http`. |
| `proxy.upstream` | required | Stdio command or HTTP upstream URL. |
| `proxy.port` | `4422` | HTTP listen port. |
| `proxy.client_id` | `claude-desktop` | Client identifier written to audit entries. |
| `proxy.server_id` | `filesystem` | Server identifier written to audit entries. |
| `audit.storage` | `jsonl` | Storage backend: `jsonl` or `sqlite`. |
| `audit.path` | `./audit.jsonl` | JSONL audit log path. |
| `audit.sqlite_path` | `./audit.db` | SQLite database path. |
| `audit.sign` | `true` | Enable HMAC-SHA256 signatures when a secret is set. |
| `audit.secret` | empty | HMAC secret. Prefer `AUDIT_SECRET`. |
| `audit.async.enabled` | `false` | Enable asynchronous batched audit writes through a bounded ring buffer. |
| `audit.async.queue_size` | `4096` | Maximum queued audit entries before backpressure blocks writers. |
| `audit.async.batch_size` | `128` | Maximum entries written per storage batch. |
| `audit.async.flush_interval_ms` | `1000` | Maximum time before a partial batch is flushed. |
| `middleware.rate_limit.enabled` | `true` | Enable per-client, per-tool token buckets. |
| `middleware.rate_limit.requests_per_minute` | `60` | Allowed requests per minute per `(client_id, tool_name)`. |
| `middleware.redact.enabled` | `true` | Enable JSON key-based PII redaction. |
| `middleware.redact.patterns` | sensitive keys | Case-insensitive key fragments to redact. |
| `policy.enabled` | `false` | Enable synchronous allow/deny policy checks for `tools/call`. |
| `policy.default_action` | `allow` | Fallback action when no policy rule matches: `allow` or `deny`. |
| `policy.rules` | empty | Ordered first-match allow/deny rules for tool calls. |
| `dashboard.enabled` | `true` | Serve the dashboard. |
| `dashboard.port` | `9090` | Dashboard listen port. |
| `metrics.enabled` | `true` | Serve Prometheus metrics on a separate HTTP endpoint. |
| `metrics.port` | `9091` | Metrics listen port. |
| `metrics.path` | `/metrics` | Metrics HTTP path. |
| `metrics.include_go_metrics` | `true` | Include Go runtime metrics. |
| `metrics.include_process_metrics` | `true` | Include process metrics. |
| `metrics.tool_labels` | `true` | Include `tool_name` and `client_id` labels for tool-level metrics. Disable to minimize label cardinality. |
CLI flags:
```text
--transport stdio | http
--upstream upstream server command or URL
--port proxy port for http mode
--config path to config.yaml
--storage jsonl | sqlite
--no-dashboard disable the web dashboard
--no-metrics disable Prometheus metrics
--log-level debug | info | warn | error
```
## Claude Desktop
Configure Claude Desktop to spawn `mcp-audit` instead of the upstream MCP server:
```json
{
"mcpServers": {
"filesystem-audited": {
"command": "mcp-audit",
"args": [
"--transport",
"stdio",
"--upstream",
"npx @modelcontextprotocol/server-filesystem /tmp"
],
"env": {
"AUDIT_SECRET": "replace-with-a-long-random-secret"
}
}
}
}
```
## Dashboard
The dashboard shows recent entries, filters, expandable request/result JSON, top tools, calls today, and error rate. It refreshes every five seconds.
## Prometheus Metrics
`mcp-audit` exposes Prometheus metrics on a separate endpoint so platform teams can scrape operational data without exposing the dashboard.
```yaml
scrape_configs:
- job_name: mcp-audit
static_configs:
- targets: ["localhost:9091"]
```
Application metrics use the `mcp_audit_` prefix and avoid unbounded labels. Tool-level labels can be disabled with `metrics.tool_labels: false` for stricter cardinality control. Policy decisions are exposed as `mcp_audit_policy_decisions_total{action="allow|deny"}`.
## Policy Engine
`mcp-audit` can enforce synchronous allow/deny rules before a `tools/call` reaches the upstream MCP server. Denied calls return a JSON-RPC error and are still written to the audit log.
```yaml
policy:
enabled: true
default_action: allow
rules:
- action: deny
client_id: claude-desktop
server_id: filesystem
tool_name: delete_file
reason: "Destructive filesystem operations are blocked"
```
Rules are evaluated in order. Empty fields and `*` match any value, so `default_action: deny` can be used with explicit allow rules for stricter deployments.
## Audit Entries
Each stored entry includes a ULID, timestamp, direction, transport, JSON-RPC method, tool name when present, redacted params/result, JSON-RPC error when present, duration, client/server identifiers, and an optional HMAC-SHA256 signature.
Example JSONL entry:
```json
{
"id": "01HY8G6Y8S6W9K6ZD7VJ4Q8X4R",
"timestamp": "2026-05-25T12:34:56Z",
"direction": "client_to_server",
"transport": "stdio",
"method": "tools/call",
"tool_name": "read_file",
"params": {
"name": "read_file",
"arguments": {
"path": "/tmp/example.txt",
"token": "[REDACTED]"
}
},
"duration_ms": 18,
"client_id": "claude-desktop",
"server_id": "filesystem",
"signature": "hmac-sha256:..."
}
```
The signature covers:
```text
id + timestamp + method + tool_name + raw_params
```
## Roadmap
- OpenTelemetry export
- SIEM-friendly exports
## Contributing
This project is experimental. Keep changes small, run `go build ./...` and `go vet ./...`, and prefer standard library behavior over new dependencies.
See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, PR expectations, and project principles. See [CHANGELOG.md](CHANGELOG.md) for release history.
## License
Apache-2.0. See [LICENSE](LICENSE).