Putting my own MCP server behind my own MCP gateway
Current Situation Analysis
Autonomous AI agents have shifted from interactive chatbots to scheduled, headless workers. This transition exposes a critical architectural blind spot: tool execution lacks financial and operational guardrails. When developers prototype locally, they manually observe tool invocations. If an agent misbehaves, they interrupt it. In production, agents run on cron schedules, webhook triggers, or continuous feedback loops without human supervision.
The Model Context Protocol (MCP) standardizes how agents discover and invoke tools, but it deliberately omits cost governance, audit trails, and policy enforcement. Most production setups connect the agent directly to the upstream MCP server via stdio or HTTP. This direct coupling means every tool call executes immediately, with zero visibility into frequency, latency, or failure states.
The financial risk is non-trivial. Specialized tool servers often wrap paid APIs. For example, financial research endpoints can cost $0.05 per invocation, while lightweight utilities like token counters cost $0.01. During a planning loop or a misconfigured retry strategy, an agent can trigger expensive tools dozens of times per minute. Without a proxy layer, there is no mechanism to pause execution, enforce approval workflows, or reconstruct what happened after a cost spike. The industry has optimized for capability discovery while neglecting execution oversight.
WOW Moment: Key Findings
Introducing a gateway layer between the agent and the upstream MCP server fundamentally changes the operational profile of autonomous workflows. The following comparison illustrates the architectural trade-offs between direct coupling and gateway-mediated execution.
| Approach | Cost Governance | Audit Visibility | Latency Overhead | Policy Enforcement | Setup Complexity |
|---|---|---|---|---|---|
| Direct MCP Connection | None (fire-and-forget) | None (black box) | ~0 ms | None | Low |
| Gateway-Proxy Architecture | Granular approval/blocking | Full JSON audit trail | ~5β15 ms | Rule-based gating & rate limits | Medium |
The gateway approach adds minimal latency but unlocks production-grade controls. It transforms tool execution from an opaque operation into a measurable, governable pipeline. This enables three critical capabilities:
- Cost containment: Expensive endpoints can be paused for human approval or blocked entirely based on dynamic rules.
- Forensic observability: Every invocation, argument payload, and round-trip duration is logged, enabling post-incident analysis and usage billing.
- Decoupled security: Bearer authentication and transport bridging (HTTP to stdio) isolate the agent from direct upstream exposure.
Core Solution
The architecture follows a three-tier pattern: Agent Client β HTTP Gateway β stdio Upstream Server. The gateway acts as a transparent proxy that intercepts JSON-RPC method calls, evaluates them against a policy engine, logs the transaction, and forwards valid requests to the upstream MCP server.
Step 1: Provision Upstream Credentials
The upstream tool server requires an API key for authentication. Generate one via the provider's registration endpoint. Store it securely in your environment. Do not hardcode it in configuration files.
curl -X POST 'https://agent-toolbelt-production.up.railway.app/api/clients/register' \
-H 'Content-Type: application/json' \
-d '{"email":"ops@yourdomain.com"}'
The response returns a key prefixed with atb_. The free tier covers 1,000 invocations monthly, which is sufficient for staging and low-volume production workloads.
Step 2: Deploy the Gateway Layer
Install the gateway CLI and initialize a TypeScript configuration file. The configuration defines the upstream server spawn command, environment injection, transport bridging, and policy rules.
// gateway.config.ts
import { defineConfig } from '@getcordon/policy';
export default defineConfig({
upstreams: [
{
identifier: 'financial-research-suite',
transport: 'stdio',
spawnCommand: 'npx',
spawnArgs: ['-y', 'agent-toolbelt-mcp'],
environment: {
UPSTREAM_API_KEY: process.env.UPSTREAM_API_KEY ?? '',
UPSTREAM_BASE_URL: 'https://agent-toolbelt-production.up.railway.app',
},
defaultAction: 'allow',
toolOverrides: {
stock_thesis: { action: 'approve', rationale: 'High-cost endpoint ($0.05/invocation)' },
moat_analysis: { action: 'approve', rationale: 'High-cost endpoint ($0.05/invocation)' },
bear_vs_bull: { action: 'approve', rationale: 'High-cost endpoint ($0.05/invocation)' },
compare_stocks: { action: 'approve', rationale: 'High-cost endpoint ($0.05/invocation)' },
},
},
],
gateway: {
transport: 'http',
listenPort: 7777,
auth: {
type: 'bearer',
token: process.env.GATEWAY_AUTH_TOKEN ?? '',
},
},
observability: {
auditEnabled: true,
logDestination: 'stdout',
format: 'json',
},
approvals: {
channel: 'terminal',
timeoutMs: 60_000,
},
});
Architecture Rationale:
stdiofor upstream: MCP servers are designed to be spawned as child processes. The gateway manages the lifecycle, ensuring clean shutdowns and stderr capture.httpfor client: HTTP is universally supported by agent clients (Claude Desktop, Cursor, custom scripts). It simplifies header injection and firewall rules.toolOverrides: Granular policies prevent blanket blocking. Cheap utilities (count_tokens,text-extractor) pass through instantly, preserving agent velocity.approvals.timeoutMs: Prevents indefinite agent hangs. If no human responds within 60 seconds, the gateway returns a structured rejection error.
Start the gateway:
export UPSTREAM_API_KEY="atb_your_key_here"
export GATEWAY_AUTH_TOKEN=$(openssl rand -hex 16)
cordon start --http
The process spawns the upstream server and binds to http://127.0.0.1:7777/mcp.
Step 3: Route the Agent Client
Update your agent client's MCP configuration to point to the gateway endpoint. Inject the Bearer token via headers. The agent sees the identical tool surface; the gateway handles interception transparently.
{
"mcpServers": {
"research-tools-proxied": {
"url": "http://127.0.0.1:7777/mcp",
"headers": {
"Authorization": "Bearer ${GATEWAY_AUTH_TOKEN}"
}
}
}
}
Step 4: Verify Observability & Policy Enforcement
Trigger a tool call from the agent. The gateway logs two JSON events per invocation: entry and completion.
{"event":"tool_call_received","ts":1747366621337,"id":"req_a8f2","server":"financial-research-suite","tool":"list_tools","payload":{}}
{"event":"tool_call_completed","ts":1747366621703,"id":"req_a8f2","tool":"list_tools","latencyMs":366}
When the agent invokes a gated tool like stock_thesis, the gateway pauses execution and prompts for approval in the terminal:
[A]pprove / [D]eny: stock_thesis
Args: {"ticker": "NVDA", "depth": "full"}
Reason: High-cost endpoint ($0.05/invocation)
Approving forwards the request. Denying returns a clean 403 Policy Rejected error that the agent's error handler can catch and recover from.
Pitfall Guide
1. Environment Variable Leakage to Child Process
Explanation: The gateway spawns the upstream server as a subprocess. If environment variables are not explicitly passed through the configuration, the upstream server fails to authenticate with its backend APIs.
Fix: Always map required keys in the environment block of the upstream config. Use process.env fallbacks to prevent undefined values from crashing the spawn.
2. Indefinite Approval Timeouts
Explanation: Setting timeoutMs too high or omitting it causes the agent to hang indefinitely while waiting for human input. In headless or scheduled environments, this blocks the entire workflow.
Fix: Set a strict timeout (e.g., 30β60 seconds). Configure the gateway to return a deterministic error payload on timeout so the agent can fallback to cheaper tools or skip the step.
3. Over-Gating Low-Cost Utilities
Explanation: Applying approval policies to every tool creates friction. Agents spend more time waiting for prompts than executing work, degrading throughput and increasing latency.
Fix: Reserve approve or block actions for endpoints with measurable cost or irreversible side effects. Keep utilities like token counters, formatters, and local file readers on allow.
4. Transport Protocol Mismatch
Explanation: Attempting to connect an HTTP-only client to a stdio-only upstream without a bridging gateway results in connection failures. Conversely, running the gateway in stdio mode while expecting HTTP logs breaks observability pipelines.
Fix: Explicitly declare transport: 'http' for the gateway listener and transport: 'stdio' for the upstream spawn. Verify the client configuration matches the gateway's listener protocol.
5. Unrotated Gateway Tokens
Explanation: Hardcoding or statically generating the Bearer token and never rotating it creates a persistent attack surface. If the token leaks, unauthorized clients can invoke paid tools. Fix: Generate tokens via secure random sources at startup. Implement a token rotation strategy in CI/CD or use a secrets manager. Log authentication failures to detect brute-force attempts.
6. Ignoring Audit Log Volume
Explanation: High-frequency tool usage generates massive JSON logs. Writing directly to stdout without rotation or filtering can fill disk space or overwhelm terminal buffers. Fix: Route audit logs to a structured sink (file with rotation, syslog, or hosted dashboard). Implement log sampling or level filtering if you only need cost-gated events for compliance.
7. Assuming Zero Latency Impact
Explanation: The gateway adds a proxy hop. While typically 5β15 ms, this compounds when agents chain multiple tool calls. Developers sometimes blame the upstream API for slowdowns that originate in the gateway's policy evaluation.
Fix: Monitor latencyMs in audit logs. If gateway overhead exceeds acceptable thresholds, consider inlining policy checks at the agent level or optimizing the policy engine's rule evaluation path.
Production Bundle
Action Checklist
- Generate upstream API key and store in environment variables
- Install gateway CLI and initialize TypeScript configuration
- Map upstream spawn command, environment, and transport protocols
- Define granular tool policies (allow/approve/block) based on cost
- Configure approval channel and timeout thresholds
- Update agent client config to route through gateway HTTP endpoint
- Validate audit log output and verify policy enforcement with test calls
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|---|---|---|
| Local prototyping | Direct MCP connection | Fastest iteration, no proxy overhead | None |
| Scheduled batch jobs | Gateway with approve on high-cost tools |
Prevents runaway loops, enables audit trails | Predictable, capped by policy |
| Real-time interactive agents | Gateway with allow + audit logging |
Maintains low latency while preserving visibility | Minimal, logging overhead only |
| Multi-agent swarm | Gateway with rate limits + hosted dashboard | Centralized governance, prevents cross-agent cost spikes | Higher setup cost, lower risk exposure |
Configuration Template
Copy this production-ready configuration. Adjust environment variables and policy rules to match your tool surface.
// proxy-gateway.config.ts
import { defineConfig } from '@getcordon/policy';
export default defineConfig({
upstreams: [
{
identifier: 'production-tool-server',
transport: 'stdio',
spawnCommand: 'npx',
spawnArgs: ['-y', 'agent-toolbelt-mcp'],
environment: {
UPSTREAM_API_KEY: process.env.UPSTREAM_API_KEY ?? '',
UPSTREAM_BASE_URL: 'https://agent-toolbelt-production.up.railway.app',
},
defaultAction: 'allow',
toolOverrides: {
stock_thesis: { action: 'approve', rationale: 'Costs $0.05 per call' },
moat_analysis: { action: 'approve', rationale: 'Costs $0.05 per call' },
bear_vs_bull: { action: 'approve', rationale: 'Costs $0.05 per call' },
compare_stocks: { action: 'approve', rationale: 'Costs $0.05 per call' },
},
},
],
gateway: {
transport: 'http',
listenPort: 7777,
auth: {
type: 'bearer',
token: process.env.GATEWAY_AUTH_TOKEN ?? '',
},
},
observability: {
auditEnabled: true,
logDestination: 'stdout',
format: 'json',
},
approvals: {
channel: 'terminal',
timeoutMs: 45_000,
},
});
Quick Start Guide
- Register & Export Key: Call the upstream registration endpoint, capture the
atb_key, and export it asUPSTREAM_API_KEY. - Install & Configure: Run
npm install -g @getcordon/cli, createproxy-gateway.config.tsusing the template above, and setGATEWAY_AUTH_TOKEN. - Launch Gateway: Execute
cordon start --http. Verify the console printsHTTP gateway listening on http://127.0.0.1:7777/mcp. - Point Agent Client: Update your editor or agent config to use the gateway URL with the Bearer token header. Restart the client and validate tool visibility.
- Test Policy Enforcement: Trigger a high-cost tool. Confirm the terminal prompts for approval and audit logs emit structured JSON.
Mid-Year Sale β Unlock Full Article
Base plan from just $4.99/mo or $49/yr
Sign in to read the full article and unlock all tutorials.
Sign In / Register β Start Free Trial7-day free trial Β· Cancel anytime Β· 30-day money-back
