# Gatewright

> Self-hostable control plane for AI agents. One URL, diff/scan/deploy pipeline, per-agent allowlists, OpenAPI orchestrator.

- **Type:** MCP server
- **Install:** `agentstack add mcp-msathiyakeerthi-gatewright`
- **Verified:** Pending review
- **Seller:** [msathiyakeerthi](https://agentstack.voostack.com/s/msathiyakeerthi)
- **Installs:** 0
- **Category:** [AI & ML](https://agentstack.voostack.com/c/ai-and-ml)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [msathiyakeerthi](https://github.com/msathiyakeerthi)
- **Source:** https://github.com/msathiyakeerthi/gatewright

## Install

```sh
agentstack add mcp-msathiyakeerthi-gatewright
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# AgentGate

[](https://github.com/msathiyakeerthi/agentgate/actions/workflows/ci.yml)
[](LICENSE)
[](https://www.python.org/downloads/)
[](.github/CONTRIBUTING.md)

**A self-hostable control plane for MCP (Model Context Protocol) servers.** One URL for every agent, a diff/scan/deploy pipeline for every change, per-agent allowlists, and an OpenAPI-to-MCP orchestrator. Open-source generic implementation of the architecture Uber presented at [MCP Dev Summit 2026](https://www.youtube.com/watch?v=yVqMxBahjfA).

```
 agent clients (Claude Code, Cursor, …)  ─► one URL: /mcp/  ─►  10s of upstream MCPs
                                              │                    │
                                  per-agent allowlist          stdio / http / sse
                                  PII redaction
                                  OpenTelemetry spans
```

### What's inside

- **Diff workflow.** Every change goes through `Create Diff → Security Scan → Deploy Diff`. Four built-in scanners (mutating-verb, prompt-injection, secret-leakage, PII-schema) block bad changes before they reach agents.
- **Orchestrator.** Point it at an OpenAPI spec; get a draft MCP definition with LLM-tuned descriptions, mutating-verb flags, and full provenance. Promote drafts through the same diff workflow.
- **Per-agent API keys.** Each key carries a glob allowlist (`jira__*`, `*`). Hot-revocable. Open-mode when no keys exist for backwards compatibility.
- **Server-rendered admin UI.** Starlette + Jinja2 + HTMX. No Node toolchain. Login, browse the registry, edit YAML, preview diff/scan, deploy, audit.
- **Tool-search "omni-MCP."** Optional `search_tools` meta-tool that lets agents discover tools by query instead of loading the full catalog into context.
- **Observability.** OpenTelemetry spans + metrics out of the box (no-op until you wire an exporter). `sla_tier` metadata per server.

**Status:** all four roadmap phases shipped. 181 tests, lint clean, ~150 MB Docker image. MIT licensed.

### Read more

| Doc | Use it for |
|---|---|
| [docs/architecture-comparison.md](docs/architecture-comparison.md) | Side-by-side with Uber's architecture — what's the same, what we generalized, what we added, what we're behind on. |
| [docs/quickstart.md](docs/quickstart.md) | 10-minute hands-on tour — boot, smoke-check, walk through each phase. |
| [docs/use-cases.md](docs/use-cases.md) | What this platform is *for* — concrete operator scenarios. |
| [Design Decisions](#design-decisions) (below) | Why each non-obvious call was made. |

## Architecture

```
 OpenAPI specs ──► ┌──────────────┐
 (file/dir/URL)    │ Orchestrator │── LLM (litellm, optional) ──► tuned descriptions
                   └──────┬───────┘
                          │ writes drafts
                          ▼
                   ┌──────────────┐         (drafts NOT loaded by gateway —
                   │ Drafts store │          staged until operator promotes)
                   └──────┬───────┘
                          │ promote → standard diff/scan/deploy pipeline
                          ▼
                   ┌──────────────────────┐
                   │  Definitions store   │   versioned YAML files
                   │  (filesystem / S3)   │   on disk or in object storage
                   └──────────┬───────────┘
                              │ polled (Config Provider)
       ┌──────────────────────┼──────────────────────┐
       │ writes               │                      │
       ▼ (admin API)          ▼                      │
┌──────────────┐    ┌──────────────────────┐         │
│  Admin       │    │  Upstream Registry   │── stdio/http/sse ──► real MCPs
│  API + UI    │    └──────────┬───────────┘         │ (PII redaction
│  /admin/...  │               │ tool views          │  applied to
│              │               ▼                     │  responses)
│  diff →      │    ┌──────────────────────┐         │
│  scan →      │    │  Northbound MCP      │         │  spans/metrics
│  audit       │    │  Server + search_    │         │  via OpenTelemetry
│              │    │  tools meta-tool     │         │
└──────┬───────┘    └──────────┬───────────┘         │
       │ writes                ▲                     │
       ▼                       │ filter by allowlist │
┌──────────────┐    ┌──────────┴───────────┐         │
│  Audit log   │    │  NorthboundAuth      │         │
│  (SQLite)    │    │  (Bearer token)      │         │
└──────────────┘    └──────────┬───────────┘         │
                               │                     │
                               ▼                     │
                         agent clients ◄─────────────┘
                         (Claude Code, Cursor, …)

       ┌──────────────┐
       │  Keys store  │ ── allowlists ─► NorthboundAuth + list_tools/call_tool gating
       │  (SQLite)    │
       └──────────────┘
```

## Quickstart

Requires Python 3.11+ and [uv](https://docs.astral.sh/uv/).

```sh
uv sync --all-packages

# Run the data plane only (no admin endpoints).
uv run mcpgw-gateway --port 8080

# Or run with the admin API + UI enabled. Pick any token you like.
MCPGW_ADMIN_TOKEN=change-me uv run mcpgw-gateway --port 8080
```

The repo ships with one sample definition at [definitions/echo/v1.yaml](definitions/echo/v1.yaml) pointing at [examples/echo-mcp/server.py](examples/echo-mcp/server.py). Once the gateway is up, point any MCP client at `http://127.0.0.1:8080/mcp/` — you'll see two tools: `echo__echo` and `echo__add`.

### Smoke check

```sh
uv run python scripts/smoke_check.py
```

Expected output:

```
tools advertised by gateway: ['echo__echo', 'echo__add']
echo__echo result: 'hello from smoke_check'
echo__add result: '42.0'
OK
```

### Connecting Claude Code

Add a streamable-HTTP server pointing at `http://127.0.0.1:8080/mcp/`. Tools appear namespaced as `__`.

## The diff workflow (Phase 2)

When `MCPGW_ADMIN_TOKEN` is set, the gateway exposes an admin surface for editing the registry. Every change is:

1. **Create Diff** — proposed YAML compared against the current active version.
2. **Security Scan** — pluggable scanners flag mutating verbs without `mutates: true`, prompt-injection patterns in tool descriptions, hard-coded secrets in static parameter overrides, and PII-shaped fields without redaction.
3. **Deploy Diff** — only if scan didn't return any `error` findings. Bumps version, writes to the store. The `Config Provider` hot-reloads within a few seconds.

Every step is recorded in the SQLite audit log.

### Web UI

```
http://127.0.0.1:8080/admin/ui/
```

Log in with the value of `MCPGW_ADMIN_TOKEN`. The dashboard lists registered servers; the server-detail page is a YAML editor with a "Preview diff & scan" button (HTMX, no full-page reload). When the preview is clean, hit **Deploy** to commit the new version.

The UI is deliberately minimal (Starlette + Jinja2 + HTMX, no Node toolchain). A richer Next.js admin app is on the roadmap.

### REST API

All routes require `Authorization: Bearer $MCPGW_ADMIN_TOKEN`.

| Method | Path | Purpose |
|---|---|---|
| `GET`    | `/admin/api/servers` | List registered servers + active versions. |
| `GET`    | `/admin/api/servers/{name}` | Active definition. |
| `GET`    | `/admin/api/servers/{name}/versions` | All versions. |
| `GET`    | `/admin/api/servers/{name}/versions/{n}` | Specific version (rollback / audit). |
| `POST`   | `/admin/api/servers/proposals` | Submit a definition. Body: `{"definition_yaml": "...", "dry_run": bool}`. Returns diff + scan + outcome. |
| `DELETE` | `/admin/api/servers/{name}` | Remove a server entirely. |
| `GET`    | `/admin/api/audit?server=&limit=` | Recent audit entries. |

A `dry_run: true` proposal returns the diff and scan report without writing — for previewing in CI or in the UI. `dry_run: false` deploys if the scan passes; if the scan blocks, the rejection is itself audited.

Example:

```sh
curl -H "Authorization: Bearer $MCPGW_ADMIN_TOKEN" \
     -H "Content-Type: application/json" \
     -X POST http://127.0.0.1:8080/admin/api/servers/proposals \
     -d '{"definition_yaml":"name: jira\ntier: third-party\ntransport:\n  type: stdio\n  command: python\ntools:\n  - name: search","dry_run":true}'
```

## Auto-generating from OpenAPI specs (Phase 3)

The orchestrator parses OpenAPI 3.x specs and writes **draft** definitions to a separate drafts store. Drafts are NOT loaded by the gateway — they sit in staging until an operator promotes them through the same diff/scan/deploy workflow.

The generated draft has:
- one tool per HTTP operation, named from `operationId` (or synthesized from method+path)
- `policies.mutates: true` for `POST/PUT/PATCH/DELETE` (so the mutating-verb scanner doesn't block)
- `description_for_llm` from the spec's `summary`/`description`, optionally rewritten by an LLM
- a **placeholder transport** the operator must replace before promoting (an OpenAPI URL is REST, not MCP)

### CLI

```sh
uv run mcpgw-orchestrate \
    --specs examples/petstore-openapi/petstore.yaml \
    --drafts-dir ./drafts
```

Sources can be files, directories (picks up `*.yaml`/`*.yml`/`*.json`), or `http(s)://` URLs. Repeat `--specs` for multiple sources.

### LLM descriptions (optional)

Without `--model`, descriptions come from the spec's own metadata — no network calls, works offline. To get LLM-tuned descriptions, install the `litellm` extra and set a model:

```sh
uv pip install 'mcpgw-llm[litellm]'
export ANTHROPIC_API_KEY=...    # or OPENAI_API_KEY, etc.
uv run mcpgw-orchestrate --specs ... --model anthropic/claude-3-5-haiku-latest
```

Any litellm-supported provider works (Anthropic, OpenAI, Bedrock, local Ollama, etc.). On any LLM failure the orchestrator silently falls back to heuristic descriptions for that batch — generation never blocks on a flaky API.

### From the admin UI

Visit `/admin/ui/drafts`, paste one source per line into the form, hit **Generate drafts**. Each draft gets a row with provenance (source spec, generator version, timestamp). Click **review & promote** to load the draft YAML into the standard editor — fix the transport, hit **Preview diff & scan**, then **Deploy v1 (consumes draft)**. On a successful deploy the draft is removed from the staging store automatically.

### REST API

| Method | Path | Purpose |
|---|---|---|
| `GET`    | `/admin/api/drafts` | List pending drafts. |
| `POST`   | `/admin/api/drafts/generate` | Body `{"sources": ["..."]}`. Crawls each source and writes drafts. |
| `GET`    | `/admin/api/drafts/{name}` | Get a single draft (YAML + provenance). |
| `DELETE` | `/admin/api/drafts/{name}` | Discard. |

To "promote" via API: `GET /admin/api/drafts/{name}` to fetch the YAML, edit the transport, then `POST /admin/api/servers/proposals` with the fixed YAML — the standard diff/scan/deploy pipeline. Then `DELETE /admin/api/drafts/{name}` to clean up.

## Per-agent API keys (Phase 4)

When at least one active agent key exists, the data plane (`/mcp/`) requires `Authorization: Bearer `. Each key carries an **allowlist** of glob patterns (e.g. `jira__*`, `echo__echo`, `*`). The gateway:

- filters `list_tools` to only the patterns the key matches, and
- rejects any `call_tool` with a tool outside the allowlist.

Keys are minted via the admin API (or the **Keys** tab in the UI) — the plaintext token is shown exactly once, only the SHA-256 hash is persisted. With zero keys, the data plane stays open (backwards compatible).

```sh
curl -H "Authorization: Bearer $MCPGW_ADMIN_TOKEN" \
     -H "Content-Type: application/json" \
     -X POST http://127.0.0.1:8080/admin/api/keys \
     -d '{"name":"claude-code-platform","allowlist":["jira__*","echo__echo"]}'
# => {"key": {...}, "token": "mcpgw_...."}    ← copy the token now
```

Then point the agent at the gateway:

```jsonc
// .claude/mcp.json
{
  "mcpServers": {
    "gateway": {
      "type": "http",
      "url": "http://127.0.0.1:8080/mcp/",
      "headers": { "Authorization": "Bearer mcpgw_..." }
    }
  }
}
```

Revoke a key from the UI (or `POST /admin/api/keys/{name}/revoke`); revocation is immediate.

## PII redaction at runtime

Tools with `policies.pii_redact: ["reporter.email", "users[*].ssn", ...]` get the named paths scrubbed in their responses **before** the agent sees them. JSONPath-ish syntax (dotted, `[*]` list wildcards, trailing `*` for "every key one level under"). Plain-text content is left alone (prose-level PII detection is out of scope for v1; a Presidio-backed scanner is the natural follow-up).

## OpenTelemetry

Each upstream tool call is wrapped in a span (`mcpgw.call_tool`) with attributes for server, tool, agent, and SLA tier, and contributes to three metrics: `mcpgw.tool_calls` (counter), `mcpgw.tool_call_errors` (counter), `mcpgw.tool_call_latency_ms` (histogram). All instrumentation is no-op unless `opentelemetry-sdk` is installed and an exporter is configured — typically by running with `opentelemetry-instrument` and setting `OTEL_EXPORTER_OTLP_ENDPOINT` / `OTEL_SERVICE_NAME`.

Definitions can declare `sla_tier: high|medium|low|unknown`, surfaced both as a span attribute and as a tie-breaker boost in the tool-search ranking below.

## Tool-search "omni-MCP"

Set `MCPGW_OMNI_MCP=1` and the gateway exposes a single meta-tool, `search_tools(query, limit)`, alongside the normal namespaced catalog. Agents can call it first to discover relevant tools by natural-language query — the pattern Uber's roadmap calls out for context-bloat reduction at high tool counts.

```python
result = await session.call_tool("search_tools", {"query": "echo a message back"})
# result.structuredContent =>
# {"matches": [{"name": "echo__echo", "server": "echo", "description": "...", "score": 3.81}, ...],
#  "query": "echo a message back"}
```

Ranking is TF-IDF over each tool's ` ` corpus with a small SLA-tier boost (`high` floats up ~10% over `low`). Deterministic, no external deps, no LLM call. Results respect the agent's allowlist.

## Adding a definition (manual / GitOps)

You can also drop YAML directly into the definitions store. The Config Provider picks it up within `MCPGW_POLL_INTERVAL` seconds (default 2s).

```yaml
# definitions/jira/v1.yaml
name: jira
version: 1
tier: third-party
owner: platform-team@example.com
transport:
  type: http
  endpoint: https://mcp-jira.example.com
auth:
  type: bearer
  secret_ref: env:JIRA_TOKEN          # resolves to os.environ["JIRA_TOKEN"]
tools:
  - name: search_issues
    enabled: true
    description_for_llm: |
      Search Jira issues. Use JQL syntax.
    parameter_overrides:
      project: ENG                     # statically pinned — LLM cannot override
  - name: delete_issue
    enabled: false                     # soft-disabled, never exposed to agents
```

To deploy a change, write a new file at `v2.yaml`. Old versions are retained for rollback.

## Repo layout

| Path | Purpose |
|---|---|
| [apps/gateway/](apps/gateway/) | The gateway service. Includes [admin/](apps/gateway/mcpgw_gateway/admin/) (REST API + UI), [config_provider.py](apps/gateway/mcpgw_gateway/config_provider.py), [upstream.py](apps/gateway/mcpgw_gateway/upstream.py), [server.py](apps/gateway/mcpgw_gateway/server.py). |
| [packages/definitions/](packages/definitions/) | Pydantic schema for the YAML definition format. |
| [packages/store/](packages/store/) | Definitions store abstraction + filesystem backend. |
| [packages/diff/](packages/diff/) | Structured diff between two definitions. |
| [packages/scanner/](packages/scanner/) | Pluggable scanner pipeline + 4 built-in scanners. |
| [packages/audit/](packages/audit/) | Audit log abstraction + SQLite backend. |
| [packages/llm-client/](packages/llm-client/) | Pluggable LLM client (heuristic + litellm) for the orchestrator. |
| [packages/agent-keys/](packages/agent-keys/) | Per-agent API keys with allowlists, SQLite-backed. |
| [apps/orchestrator/](apps/orchestrator/) | OpenAPI crawler + adapter + definition generator + `mcpgw-orchestrate` CLI. |
| [apps/gateway/..

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [msathiyakeerthi](https://github.com/msathiyakeerthi)
- **Source:** [msathiyakeerthi/gatewright](https://github.com/msathiyakeerthi/gatewright)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** yes
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: flagged — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-msathiyakeerthi-gatewright
- Seller: https://agentstack.voostack.com/s/msathiyakeerthi
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
