Install
$ agentstack add mcp-msathiyakeerthi-gatewright Open-source listing, not yet scanned by AgentStack. Follow the source repository for install instructions.
Security review
⚠ Flagged1 finding(s); flagged for manual review. · v0.1.0 How review works →
- • Prompt-injection patterns
- • Secret / credential exfiltration
- • Dangerous shell & filesystem operations
- • Untrusted network calls
- • Known-malicious package signatures
- high Possible prompt-injection directive.
What it can access
- ● Network access Used
- ● Filesystem access Used
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →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.
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_toolsmeta-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_tiermetadata 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.
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
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:
- Create Diff — proposed YAML compared against the current active version.
- 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. - Deploy Diff — only if scan didn't return any
errorfindings. Bumps version, writes to the store. TheConfig Providerhot-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:
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: trueforPOST/PUT/PATCH/DELETE(so the mutating-verb scanner doesn't block)description_for_llmfrom the spec'ssummary/description, optionally rewritten by an LLM- a placeholder transport the operator must replace before promoting (an OpenAPI URL is REST, not MCP)
CLI
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:
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_toolsto only the patterns the key matches, and - rejects any
call_toolwith 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).
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:
// .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.
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).
# 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/mcpgwgateway/admin/) (REST API + UI), [configprovider.py](apps/gateway/mcpgwgateway/configprovider.py), [upstream.py](apps/gateway/mcpgwgateway/upstream.py), [server.py](apps/gateway/mcpgwgateway/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
- Source: msathiyakeerthi/gatewright
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.