4Q Tokens — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited 4Q Tokens (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
A context-aware MCP proxy that reduces token usage by exposing only 3 tools (mcp_search, mcp_call, mcp_schema) to LLMs instead of the full catalog.
When you connect multiple MCP servers to an LLM, every tool from every server is listed in the LLM's context window. For a typical workspace with 50-100 tools across multiple MCP servers, that's thousands of tokens of schema documentation on every request.
MCP Proxy Gateway sits between your LLM and your MCP servers, offering:
┌─────────────────────────────────────────────────────────────────┐
│ Your LLM │
│ (sees only: mcp_search, mcp_call, mcp_schema) │
└────────────────────┬────────────────────────────────────────────┘
│
┌───────────▼──────────────┐
│ MCP Proxy Gateway │
│ ┌──────────────────────┐ │
│ │ Tool Registry │ │
│ │ (BM25 lexical) │ │
│ └──────────────────────┘ │
│ ┌──────────────────────┐ │
│ │ Connector Manager │ │
│ │ (Idle timeout reap) │ │
│ └──────────────────────┘ │
└────────────┬─────────────┘
│
┌─────────────┼─────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│Google │ │MailerLite│ │Your Svc │
│Gmail │ │ Campaigns│ │ Custom │
│Calendar │ │ │ │ Tools │
│Drive │ │ │ │ │
└─────────┘ └─────────┘ └─────────┘git clone https://github.com/steveweltman/4q-tokenz.git
cd 4q-tokenz
pnpm install
pnpm build
# Install to ~/.local/bin and configure
./install.shnpm install -g 4q-tokenzHere's a concrete walkthrough to connect Google Workspace (Gmail, Calendar, Drive) to your LLM through the proxy:
You need an MCP server that wraps Google APIs. Options:
npm install @antidrift/mcp-google
# or
npx @antidrift/mcp-google --helptoken.json: GOOGLE_CREDENTIAL_FILE=~/Downloads/credentials.json \
npx @antidrift/mcp-googleThis opens a browser for you to authorize. Once done, it saves token.json locally.
Create ~/.config/4q-tokens/config.json:
{
"upstreams": [
{
"name": "google-workspace",
"transport": "stdio",
"command": "npx",
"args": ["@antidrift/mcp-google"],
"env": {
"GOOGLE_TOKEN_FILE": "~/.local/share/google-mcp/token.json",
"GOOGLE_CONNECTORS": "gmail,calendar,drive"
}
}
],
"searchLimit": 5,
"callItemLimit": 30,
"maxTextLength": 800,
"maxOutputTokens": 10000,
"idleTimeoutMs": 600000
}mcp-proxy
# Or via systemd if installed:
systemctl --user start mcp-proxyConfigure your LLM to use http://127.0.0.1:9200/mcp as its MCP server. It will see:
mcp_search — find tools by natural languagemcp_call — invoke a toolmcp_schema — see tool detailsExample query:
mcp_search("send an email")
# Returns: google_send_email (Gmail)
mcp_call(ref="google_send_email", args={"to": "[email protected]", "subject": "Hello", "body": "Test"})export MCP_PROXY_UPSTREAMS='[
{
"name": "google",
"transport": "stdio",
"command": "node",
"args": ["/path/to/google/server.mjs"],
"env": {
"GOOGLE_TOKEN_FILE": "token.json"
}
}
]'
export MCP_PROXY_SINGLETON_PORT=9200
export MCP_PROXY_DASHBOARD_PORT=9100
node dist/index.jsCreate ~/.config/4q-tokens/config.json:
{
"upstreams": [
{
"name": "google-workspace",
"transport": "stdio",
"command": "npx",
"args": ["@antidrift/mcp-google"],
"env": {
"GOOGLE_TOKEN_FILE": "token.json",
"GOOGLE_CONNECTORS": "gmail,calendar,drive"
}
},
{
"name": "external-api",
"transport": "http",
"url": "https://mcp.example.com/",
"auth": {
"apiKey": "MY_API_KEY_ENV_VAR"
}
}
],
"searchLimit": 3,
"callItemLimit": 20,
"maxTextLength": 500,
"maxOutputTokens": 8000,
"idleTimeoutMs": 300000
}Then run:
node dist/index.jsThe proxy will load the config from ~/.config/4q-tokens/config.json if it exists, otherwise fall back to the MCP_PROXY_UPSTREAMS environment variable.
#### Upstream Server Config
{
"name": "unique-id",
"transport": "stdio" | "http",
// For stdio transport:
"command": "node",
"args": ["path/to/server.mjs"],
"cwd": "/working/dir", // optional
"env": { "KEY": "value" }, // optional
// For http transport:
"url": "https://example.com/mcp",
"auth": {
"apiKey": "ENV_VAR_NAME" // reads from process.env[ENV_VAR_NAME]
}
}#### Proxy Options
| Option | Default | Description |
|---|---|---|
searchLimit | 3 | Max tools returned by mcp_search |
callItemLimit | 20 | Max items in mcp_call response |
maxTextLength | 500 | Truncate text fields to N chars (detail=false: 500, detail=true: 1500) |
maxOutputTokens | 8000 | Hard cap on response size |
idleTimeoutMs | 300000 | Disconnect upstream servers after N ms of inactivity (0 = disabled) |
Environment variable overrides:
export MCP_PROXY_SEARCH_LIMIT=5
export MCP_PROXY_CALL_ITEM_LIMIT=30
export MCP_PROXY_MAX_TEXT_LENGTH=800
export MCP_PROXY_MAX_OUTPUT_TOKENS=10000
export MCP_PROXY_IDLE_TIMEOUT_MS=600000When several agents share one proxy, each agent should only be able to see and call its own tools. 4Q-Tokenz does this with lanes. A lane is a named view of the tool registry that is served at its own URL, /mcp/<agentId>. An agent connected to its lane cannot discover or call tools from any other lane, even if it knows the ref.
agent hope -> entry "tokens-hope" -> /mcp/hope -> google-hope, hope-memory
agent clark -> entry "tokens-clark" -> /mcp/clark -> google-clark, clark-memory
(no lane) -> /mcp -> refused (HTTP 403)Add an agents block next to upstreams in ~/.config/4q-tokens/config.json (or set MCP_PROXY_AGENTS as JSON). Each lane lists the upstream providers it may use and, optionally, individual tools to deny:
{
"upstreams": [ ... ],
"agents": {
"hope": {
"allow": ["google-hope", "hope-memory"],
"deny": ["google-hope.drive_share", "google-hope.share_doc"]
},
"clark": { "allow": ["google-clark", "clark-memory"] }
}
}| Field | Description |
|---|---|
allow | Upstream provider names this lane may use (required, at least one) |
deny | Optional. provider.tool or *.tool entries removed from the lane after allow is applied. Deny always wins. A deny entry that matches no tool logs a warning at startup, so typos do not fail open silently |
Agent ids must match ^[a-z0-9_][a-z0-9_-]{0,63}$. The plain array form ("hope": ["google-hope"]) is still accepted and means allow-only.
/mcp is refused with 403. A client that is not bound to a lane never sees the whole tool pool. For local development only, set MCP_PROXY_ALLOW_UNSCOPED=1 to re-enable it./mcp/nobody) returns 404.MCP_PROXY_ALLOW_UNSCOPED=1. Run the proxy as a service instead (see Singleton and Bridge Mode).MCP_PROXY_AGENT.One primary instance owns the upstream connections and listens on MCP_PROXY_SINGLETON_PORT (default 9200). Run it as a service with MCP_PROXY_SERVICE_MODE=1 (HTTP only, no stdio). Any additional instance launched as a stdio MCP server detects the live primary through the lock file and becomes a bridge. A bridge forwards the client's stdio traffic to the primary on its own lane:
MCP_PROXY_AGENT=hope node dist/index.js # bridge: forwards to http://127.0.0.1:9200/mcp/hopeThe lock file lives in MCP_PROXY_LOCK_DIR, else $XDG_RUNTIME_DIR, else /run/user/<uid>, else the temp dir. Hosts that launch the proxy with a minimal environment (for example the OpenClaw gateway) do not set XDG_RUNTIME_DIR, which is why the /run/user/<uid> fallback exists. If an instance wins a lock but finds the port already served, it releases the lock and bridges instead of crashing.
OpenClaw has no per-agent MCP setting and does not tell a stdio server which agent launched it. Isolation therefore needs two layers:
<serverName>__<tool>:"mcp": {
"servers": {
"tokens-hope": { "command": "/path/to/start-4q-tokens.sh", "env": { "MCP_PROXY_AGENT": "hope" } },
"tokens-clark": { "command": "/path/to/start-4q-tokens.sh", "env": { "MCP_PROXY_AGENT": "clark" } }
}
}"agents": { "entries": { "hope": { "tools": { "deny": ["tokens-clark__*"] } } } }The proxy lane is the enforcing boundary: whatever an agent does, a bridge bound to hope can only reach /mcp/hope. The OpenClaw deny list is defense in depth that keeps one agent from being handed another agent's entry.
HTTP session created: ... [hope]. There should be no [open] sessions and no Refused unscoped lines in normal operation.mcp_search and confirm only that lane's providers appear.Tool not found.node dist/index.jsThe proxy connects via stdio to your LLM. Use it with Claude or other MCP clients.
The install script can set this up for you (see below), or manually:
~/.config/systemd/user/mcp-proxy.service:[Unit]
Description=MCP Proxy Gateway
After=network.target
[Service]
Type=simple
ExecStart=%h/.local/bin/mcp-proxy
Restart=on-failure
RestartSec=5s
Environment="PATH=%h/.local/bin:/usr/local/bin:/usr/bin"
[Install]
WantedBy=default.targetsystemctl --user daemon-reload
systemctl --user enable mcp-proxy
systemctl --user start mcp-proxyjournalctl --user -u mcp-proxy -fThe dashboard exposes a Prometheus-compatible /metrics endpoint:
curl http://localhost:9100/metricsMetrics exposed:
| Metric | Type | Description |
|---|---|---|
mcp_proxy_uptime_seconds | gauge | Seconds since process started |
mcp_proxy_registered_tools | gauge | Tools in the registry |
mcp_proxy_upstream_up | gauge | 1 if upstream is connected/idle, 0 if error |
mcp_proxy_upstream_tools | gauge | Tools discovered per upstream |
mcp_proxy_calls_total | counter | Calls by tool, provider, status |
mcp_proxy_call_duration_ms_total | counter | Cumulative call duration (ms) |
mcp_proxy_output_bytes_total | counter | Cumulative output bytes |
Add a scrape job in your Prometheus/Alloy config:
scrape_configs:
- job_name: mcp-proxy
static_configs:
- targets: ['localhost:9100']
metrics_path: /metricsThe proxy always starts an HTTP transport on port 9200 by default. Set MCP_PROXY_SINGLETON_PORT to use a different port. This allows multiple clients to connect to a single proxy instance. Clients connect to a lane at /mcp/<agentId> (see Multi-Agent Lanes). The unscoped /mcp route is refused unless MCP_PROXY_ALLOW_UNSCOPED=1 is set.
export MCP_PROXY_SINGLETON_PORT=9200
export MCP_PROXY_SERVICE_MODE=1
node dist/index.js &
# From another process, on a lane:
curl -X POST http://127.0.0.1:9200/mcp/hope -H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2025-06-18", "capabilities": {}, "clientInfo": {"name": "curl", "version": "1"}}}'Check the server logs in the dashboard (port 9100 by default) or daemon logs:
journalctl --user -u mcp-proxy -eThe proxy logs:
The proxy has comprehensive error handling to gracefully degrade on upstream failures:
NO_UPSTREAMSFor unhandled errors, check:
journalctl --user -u mcp-proxy -n 50 # Last 50 linesWhen a tool returns null or malformed data, the output shaper handles it gracefully:
[]{value: string}_rawContentIf a tool response looks truncated, retry with detail=true in mcp_call to disable output shaping:
mcp_call(ref="google_send_email", args={...}, detail=true)The proxy binds to `127.0.0.1` only for security — it's not accessible from the network by default. To access remotely:
127.0.0.1:9200 ssh -L 9200:127.0.0.1:9200 user@remote-hostpnpm test runs regression tests for the singleton lock, bridge startup, lane isolation and deny lists (Node's built-in test runner, no extra dependencies). Upstream connector behavior is not coveredPer-agent isolation is now enforced rather than optional:
/mcp route is refused with 403 unless MCP_PROXY_ALLOW_UNSCOPED=1. Clients must use /mcp/<agentId>MCP_PROXY_AGENT, connects to its lane, and refuses to start without a valid agent id. A non-service stdio primary refuses to start unless MCP_PROXY_ALLOW_UNSCOPED=1deny list (provider.tool or *.tool) in the agents config, with a startup warning for entries that match no tool@modelcontextprotocol/sdk 1.31.0 to 1.32.1, clearing all pnpm audit --prod findings (including proxy-addr via express)XDG_RUNTIME_DIR, for example by the OpenClaw gateway) looked for its lock in the temp dir, did not find the running primary, tried to become a second primary and crashed with EADDRINUSE, which the client saw as MCP error -32000: Connection closed. The lock directory now falls back to /run/user/<uid>, and an instance that wins a lock but finds the port served bridges instead of crashingpnpm test)Security hardening from the pre-release assessment (OWASP Top 10 2025, OSV.dev, GCVE):
Access-Control-Allow-Origin: * from /api/status, and stopped returning tool-call args in the audit log data — previously any website could read recent call arguments from 127.0.0.1:9100src/host-guard.ts: /mcp and the dashboard reject requests whose Host or Origin is not loopback (403), blocking DNS-rebinding and cross-site browser access$XDG_RUNTIME_DIR when set (falls back to the temp dir) and is created with mode 0600pnpm audit cleanzod 3.25 → 4.6, typescript 6.0 → 7.0, @types/node 25 → 26z.record calls now pass an explicit key schema (src/types.ts)@modelcontextprotocol/sdk from ^1.30.0 to ^1.31.0hono >=4.13.5, fast-uri >=4.1.4, ip-address >=10.5.1; add qs >=6.16.0 — clears 2 high and 7 moderate advisories (all transitive via the MCP SDK)pnpm audithono pnpm override (>=4.12.25) still resolved to a vulnerable 4.12.x release; tightened to >=4.12.34@hono/node-server >=1.19.15, fast-uri >=3.1.5, ip-address >=10.3.1, body-parser >=2.3.0 — all transitive via @modelcontextprotocol/sdk, resolves 3 high and 6 medium/low CVEs (DoS, ReDoS, SSRF-adjacent parsing bugs)@modelcontextprotocol/sdk from ^1.26.0 to ^1.30.0pnpm audit and OSV.dev for all resolved dependency versionspnpm override: hono >=4.12.25) — override was too loose and did not actually clear the vulnerable range; fully resolved in v1.21.2 abovedetail=true no longer truncates text fields in tool output/mcp/{agentId}, filtered tool registry views, MCP_PROXY_AGENTS config/metrics endpoint on the dashboard port (9100 by default)mcp_proxy_uptime_seconds, mcp_proxy_registered_tools, mcp_proxy_upstream_up, mcp_proxy_upstream_tools, mcp_proxy_calls_total, mcp_proxy_call_duration_ms_total, mcp_proxy_output_bytes_total@xenova/transformers entirely — eliminates the protobufjs/ONNX runtime supply chain@modelcontextprotocol/sdk from ~1.22.0 to ^1.26.0 — resolves 3 high-severity CVEs: ReDoS, cross-client data leak, DNS rebindingprotobufjs >=7.5.8 — resolves critical arbitrary code execution and multiple high CVEs in @xenova/transformers transitive dependency chainqs >=6.15.2 — resolves moderate DoS vulnerability in express transitive dependencyMCP Proxy Gateway is a fork of @arvoretech/mcp-proxy, originally created by João Augusto and Árvore Educação.
Forked and extended with:
MIT. See LICENSE.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.