UberGate
by 0xPsyop
README.md
# UberGate
[](https://github.com/0xPsyop/ubergate/actions/workflows/ci.yml)
Inspired by [Uber Engineering's "Designing MCP Gateway"](https://www.uber.com/gb/en/blog/designing-mcp-gateway/), UberGate is an independent implementation and is not Uber's code. It is an open, governed API-to-MCP gateway that automatically crawls existing REST APIs described by OpenAPI specifications, generates tools that are **disabled by default**, and serves them to AI agents over standard MCP (Model Context Protocol). Cross-cutting concerns—caller authentication, fine-grained RBAC/policy by actor type and risk tier, rate limiting, PII redaction, audit logging, and token-efficient progressive discovery (`search_tools`, `get_schema`, `invoke_tool`)—are enforced in a centralized gateway layer so backend engineering teams never hand-write or insecurely expose MCP wrappers.
> [!NOTE]
> Tools configuration changes are reviewed by [`@0xPsyop`](.github/CODEOWNERS).
---
## Why UberGate?
As enterprises connect LLM agents to internal infrastructure, exposing internal services presents three critical challenges described in Uber's architecture:
1. **Catalog Context Bloat**: A conventional `tools/list` response grows with the number and size of endpoint schemas. In the generated 200-endpoint scenario measured below, the initial catalog is estimated at 19,171 tokens using a character-based heuristic; the same catalog is synthetic, not a production API.
2. **Accidental Exposure & Lack of Governance**: Auto-generating tools directly to agents risks unintentional invocation of write or destructive endpoints, leaks raw PII (social security numbers, credit cards, phones) into model prompts, and lacks per-caller authorization.
3. **Control Plane / Data Plane Decoupling**: Hand-coding MCP servers per team creates maintenance sprawl and inconsistent security controls. A declarative, GitOps-driven control plane ensures that discovery does not mean exposure: tools require human review in pull requests to be enabled.
---
## Token-size measurement
Run `npm run benchmark:tokens` to reproduce these measurements. The script serializes the tool definitions and request/response payloads as JSON, then estimates tokens as `ceil(UTF-16 character count / 4)`. This is a rough estimate, not output from a model tokenizer. Full-flow totals include the initial `tools/list`, tool-call requests, and tool-call responses. The `demo.getUser` invocation executes against the local demo API; the 200-endpoint catalog is generated synthetic OpenAPI.
| Scenario | Enabled tools | Conventional catalog | Progressive catalog | Conventional full flow | Progressive full flow | Estimated full-flow change |
| :--- | ---: | ---: | ---: | ---: | ---: | ---: |
| Demo API configuration | 6 | 1,502 chars / 376 tokens | 876 chars / 219 tokens | 1,759 chars / 440 tokens | 2,413 chars / 604 tokens | 37.3% more |
| Synthetic OpenAPI catalog | 200 | 76,681 chars / 19,171 tokens | 876 chars / 219 tokens | 76,938 chars / 19,235 tokens | 3,315 chars / 829 tokens | 95.7% fewer |
The demo API catalog is small enough that the full progressive flow costs more in this estimate. The synthetic result shows the effect of catalog size; it should not be read as a measurement of a real API or a model's exact tokenization.
---
## Architecture
```mermaid
flowchart TD
subgraph ControlPlane["Control Plane (GitOps)"]
OpenAPI["OpenAPI 3.x Specs"] -->|crawl| Crawler["Crawler & Merger"]
Crawler -->|generates| ToolsYAML["config/tools.yaml (disabled by default)"]
PR["Human PR & Security Review"] -->|sets enabled: true| ToolsYAML
PolicyYAML["config/policy.yaml"]
GatewayYAML["config/gateway.yaml"]
end
subgraph DataPlane["Data Plane (Gateway Server)"]
ToolsYAML -->|hot reload| Registry["Registry Snapshot Holder"]
PolicyYAML -->|hot reload| Registry
GatewayYAML -->|hot reload| Registry
Agent["AI Agent / MCP Client"] -->|"POST /mcp (Bearer Token)"| HTTP["Streamable HTTP Transport"]
HTTP --> Auth["Caller Authentication"]
Auth --> McpServer["Per-Request McpServer"]
McpServer --> Discovery["search_tools & get_schema"]
McpServer --> Pipeline["invoke_tool Execution Pipeline"]
Pipeline --> RateLimit["Rate Limiter (Token Bucket)"]
RateLimit --> Lookup["Tool Lookup & Status Check"]
Lookup --> Policy["Policy Engine (First-Match-Wins)"]
Policy --> Validator["Ajv Argument Validator"]
Validator --> Executor["HTTP Executor"]
Executor --> Downstream["Downstream REST APIs"]
Downstream --> Redactor["PII Redactor (Regex + Luhn + Keys)"]
Redactor --> Projection["Response Projection (Dot Paths)"]
Projection --> Audit["Audit Logger (HMAC-SHA256 Args)"]
end
```
---
## Quickstart
### 1. Install & Configure
```bash
# Clone and install dependencies
git clone https://github.com/0xPsyop/ubergate.git
cd ubergate
npm install
# Setup development environment variables
cp .env.example .env
```
### 2. Start the Demo API and UberGate
In terminal 1 (Demo API with fake PII data on port 4000):
```bash
npm run demo-api
```
In terminal 2 (UberGate on port 3000):
```bash
npm run dev
```
### 3. Connect via MCP Client (or curl)
Send an authenticated JSON-RPC request to `http://localhost:3000/mcp`:
```bash
curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer dev-alice-token" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "search_tools",
"arguments": { "query": "get user" }
}
}'
```
#### Connecting from Claude Desktop / Cursor
Configure your MCP client settings with HTTP transport and authorization headers:
```json
{
"mcpServers": {
"gateway": {
"url": "http://localhost:3000/mcp",
"headers": {
"Authorization": "Bearer dev-alice-token"
}
}
}
}
```
## Caller authentication
The gateway accepts static bearer tokens in the `Authorization: Bearer <token>` header. A caller is configured with either an inline `token` value or a `token_env` name whose value is read from the process environment; the configured caller ID, type, and scopes are then used by policy checks. Tokens are SHA-256 hashed and compared with `crypto.timingSafeEqual`. This is not JWT or OAuth authentication: the gateway does not validate issuer, audience, expiry, or other claims, and it has no built-in token issuance or rotation.
The checked-in [`.env.example`](.env.example) contains `dev-alice-token`, `dev-billing-token`, and `dev-agent-token` for local development only. **Never use these development tokens outside local development.** Use deployment-managed secrets for non-local environments, and serve bearer credentials over TLS.
---
## Crawl -> PR -> Enable Workflow
1. **Crawl an OpenAPI Specification:**
Run the crawler against any OpenAPI 3.x spec file:
```bash
npm run crawl:demo -- --owner @team-demo
```
A newly discovered tool is merged with `enabled: false` and `needs_review: false`. If an existing tool has the same ID but a different `source_hash`, the merge resets it to `enabled: false` and sets `needs_review: true`; unchanged tools preserve their existing human edits and review state.
2. **Open a Pull Request:**
A human engineer reviews the endpoint and enables it explicitly in Git:
```diff
--- a/config/tools.yaml
+++ b/config/tools.yaml
- id: demo.getUser
server: demo
description: Get a user by ID
risk: read
owner: "@team-demo"
- enabled: false
+ enabled: true
```
3. **Continuous Enforcement & Change Detection:**
If upstream API contracts change, re-crawling updates the `source_hash`, automatically resets `enabled: false`, and flags `needs_review: true`, requiring re-approval before exposure.
---
## Governance: Policy & PII Redaction
- **Centralized Policy Engine (`config/policy.yaml`)**:
Rules follow first-match-wins semantics over caller type (`human`, `service`, `agent`), tool risk tier (`read`, `write`, `destructive`), glob tool patterns, and required caller scopes. For instance, `helper-agent` can read and write, but is strictly denied destructive tools like `demo.deleteUser`.
- **Automatic PII Redaction (`config/gateway.yaml`)**:
Responses are inspected prior to projection:
- **Pattern Redaction**: Emails, phone numbers (US and international), SSNs (`\d{3}-\d{2}-\d{4}`), and 13–19 digit credit cards validated against the **Luhn algorithm**. Non-card numeric identifiers (like barcodes) are safely preserved.
- **Key-Based Redaction**: Case-insensitive matching replaces sensitive values for keys like `password`, `ssn`, `token`, `secret`, `api_key`, and `cardNumber`.
- **Zero Raw PII in Audit Logs**:
Tool arguments and payloads are never logged raw. Only an HMAC-SHA256 digest of serialized arguments is stored in `logs/audit.jsonl`, keyed by `AUDIT_HMAC_KEY`. The key must be at least 32 bytes; gateway startup fails if it is missing or too short.
---
## Limitations
- Regex and Luhn-based redaction can miss names, addresses, and other PII in free text. Key-based redaction only handles configured key names.
- Rate-limit buckets are stored in process memory. Limits are not shared across instances and reset when an instance restarts.
- Downstream tool responses are not scanned for prompt injection. Attacker-controlled text in a response can influence an agent that consumes it.
- Authentication uses static bearer tokens with no built-in expiry, rotation, or identity-provider integration. TLS must be provided by the deployment environment.
- When a downstream response omits or misstates `Content-Length`, the executor reads the body before enforcing `max_response_bytes`, so a large response can consume more memory than the configured limit.
- Distributed catalog synchronization, gRPC/Protocol Buffers or Thrift translation, native MCP server proxying, and third-party token exchange are not implemented.
See [`docs/roadmap.md`](docs/roadmap.md) for additional deferred work and [`docs/design-decisions.md`](docs/design-decisions.md) for design rationale.
---
## License
MIT © 2026
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo recent community issues