MCP Tool Gateway
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@MCP Tool GatewaySend an email to the team about the deployment status."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
HEAD
MCP Tool Gateway
A minimal control plane between AI agents and organizational tools, with a human-in-the-loop approval gate for high-risk actions.
Low-risk tools (read-only SQL) run instantly. High-risk tools (sending email) pause and wait for a human to approve or deny from an admin dashboard before the agent is allowed to proceed. Every call is written to an audit log.
Design goal: keep it linear and synchronous. The approval "pause" is a plain
await on an in-memory asyncio.Event under a timeout — not a distributed
pause/resume state machine. No Docker sandbox, no arbitrary code execution, no
multi-tenancy, no Redis. Those are deliberate v1 exclusions.
How it works
Agent --POST /call--> Gateway
low risk -> run tool -> audit(auto) -> result
high risk -> PendingApproval(pending)
await decision (APPROVAL_TIMEOUT)
approved -> run tool -> audit(approved) -> result
denied -> audit(denied) -> {"status":"denied"}
timeout -> audit(timeout) -> {"status":"timeout"}The core is one function, gateway.run_tool: check the tool's risk flag, then
either run it now or create a pending row and await a decision.
Related MCP server: border-patrol-mcp
Tech stack
FastAPI · SQLAlchemy (async) + asyncpg · PostgreSQL · mcp SDK (Streamable HTTP) · uvicorn
File layout
mcp-gateway/
├── app/
│ ├── main.py # FastAPI app + routes wired together
│ ├── db.py # Postgres connection/session (rw + ro engines)
│ ├── models.py # PendingApproval, AuditLog
│ ├── registry.py # tool name -> {fn, risk}
│ ├── tools.py # sql_read (low), send_email (high)
│ ├── gateway.py # core: run a tool + enforce the gate
│ ├── approvals.py # in-memory events + wait/approve/deny
│ └── mcp_server.py # Step 6: registry -> MCP tools, mounted at /mcp
├── static/
│ └── dashboard.html # admin page (polls for pending requests every 2s)
├── .env
├── requirements.txt
└── README.mdSetup
Prerequisites: Python 3.11+, PostgreSQL running locally.
Install
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txtConfigure — .env (already present):
DATABASE_URL=postgresql+asyncpg://gateway:password@localhost:5432/mcp_gateway
READONLY_DATABASE_URL=postgresql+asyncpg://gateway_ro:password@localhost:5432/mcp_gateway
APPROVAL_TIMEOUT=300Create the database and the read-only role. sql_read uses gateway_ro —
SELECT-only is enforced by the database and by a READ ONLY transaction, never
by parsing the query string.
Option A — Docker (what this checkout is set up with):
docker run -d --name mcp_gateway_postgres \
-e POSTGRES_USER=gateway -e POSTGRES_PASSWORD=password -e POSTGRES_DB=mcp_gateway \
-p 127.0.0.1:5432:5432 postgres:15-alpine
docker exec -i mcp_gateway_postgres psql -U gateway -d mcp_gateway <<'SQL'
CREATE ROLE gateway_ro LOGIN PASSWORD 'password';
GRANT CONNECT ON DATABASE mcp_gateway TO gateway_ro;
GRANT USAGE ON SCHEMA public TO gateway_ro;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO gateway_ro;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO gateway_ro;
SQLStop/start it later with docker stop mcp_gateway_postgres / docker start mcp_gateway_postgres.
Option B — local Postgres install:
createdb mcp_gateway
psql mcp_gateway -c "CREATE ROLE gateway_ro LOGIN PASSWORD 'password';
GRANT CONNECT ON DATABASE mcp_gateway TO gateway_ro;
GRANT USAGE ON SCHEMA public TO gateway_ro;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO gateway_ro;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO gateway_ro;"The ALTER DEFAULT PRIVILEGES line matters: the gateway creates its tables on
startup, after the GRANT ... ON ALL TABLES above has already run, so without
it gateway_ro would be granted nothing.
Run — from inside mcp-gateway/, not your home directory. app.main:app is
resolved relative to the current directory:
cd mcp-gateway
.venv/bin/uvicorn app.main:app --reload # or: source .venv/bin/activate firstIf you see
externally-managed-environmentfrom pip, the venv isn't active — use.venv/bin/pipexplicitly. If you seeModuleNotFoundError: No module named 'app', you're in the wrong directory.
Dashboard: http://localhost:8000/ · API: http://localhost:8000/call
Build order
Each step runs before you start the next.
1. Skeleton + DB — FastAPI app, async SQLAlchemy engine,
AuditLog+PendingApprovalcreated on startup.2. Registry + tools —
sql_read(risklow) andsend_email(riskhigh) registered by name.3. Gateway + gate —
run_tool()runs low-risk now, blocks high-risk; exposed viaPOST /call.4. Approval store — in-memory
asyncio.Eventper pending id;wait_for_decision()/resolve().5. Dashboard —
GET /approvals, approve/deny endpoints,dashboard.htmlpolling every 2s.6. MCP endpoint — the registry exposed over MCP via the
mcpSDK (Streamable HTTP) at/mcp.
API
Method | Path | Purpose |
POST |
|
|
GET |
| List pending approvals (dashboard feed) |
POST |
| Approve a pending call |
POST |
| Deny a pending call |
GET |
| Liveness + registered tool names |
GET |
| Admin dashboard |
ALL |
| MCP endpoint (Streamable HTTP) |
Demo / acceptance test
Low-risk tool → returns instantly, AuditLog.decision = "auto":
curl -X POST localhost:8000/call -H "content-type: application/json" \
-d '{"tool":"sql_read","args":{"query":"select 1 as ok"}}'High-risk tool → the request hangs:
curl -X POST localhost:8000/call -H "content-type: application/json" \
-d '{"tool":"send_email","args":{"to":"a@b.com","subject":"hi","body":"test"}}'Open http://localhost:8000/ → the request is listed → click Approve → the
curl call returns its result and AuditLog.decision = "approved". Repeat with
Deny, and leave one sitting for APPROVAL_TIMEOUT seconds to see the
timeout path.
SELECT tool_name, risk, decision, created_at FROM audit_log ORDER BY id DESC LIMIT 5;Notes and known edges
Single process only. The event lives in one worker's memory, so don't run
--workers 2— the approver and the waiter must share an event loop. Redis Pub/Sub is the fix when that changes.Restart loses waiters. A pending row survives a restart, but the request awaiting it does not. Resolving an orphaned row updates the DB and returns cleanly; nothing is left hanging.
Timeout closes the row. On timeout the
PendingApprovalrow is markeddenied(the schema only haspending|approved|denied) so it leaves the dashboard, while theAuditLogrow records the more precisedecision="timeout".Failed tools are audited too. If a tool raises (bad args, or the DB refusing a write), the gateway writes an
AuditLogrow withdecision="error"and returns HTTP 400 with the message — a refused call is exactly what the audit log is for.No auth on the dashboard. Anyone who can reach the port can approve. Put it behind your own auth before it leaves localhost.
MCP endpoint
The same registry is served over MCP at http://localhost:8000/mcp (Streamable
HTTP). Each MCP tool handler is a thin wrapper around gateway.run_tool, so the
approval gate applies automatically — an agent calling send_email simply sees a
tool that takes a while to return.
Connect MCP Inspector:
npx @modelcontextprotocol/inspector
# transport: Streamable HTTP URL: http://localhost:8000/mcpInitialize → list tools shows sql_read and send_email → calling sql_read
returns rows immediately → calling send_email hangs until you click Approve on
the dashboard.
SDK note: this uses mcp 2.x, where FastMCP was renamed to
MCPServer (from mcp.server.mcpserver import MCPServer) and the wire models
moved to snake_case (result.is_error, tool.input_schema). Most tutorials
online still show the v1 names. The handler signatures are generated from the
real tool functions via functools.wraps + __signature__, so the JSON schema
stays in sync with tools.py automatically.
A denied call returns {"status": "denied"} as a successful tool result, not
an MCP error — the agent is told the action was refused rather than that
something broke. Flip this in gateway.run_tool if you'd rather it raise.
Out of scope (v1, on purpose)
Excluded | Why / when to add |
Docker sandbox | Real isolation is hard (gVisor/Firecracker territory); add only with arbitrary code exec. |
Arbitrary code exec | Highest-risk, easiest to do insecurely — keep to a fixed tool set first. |
Multi-tenancy | Add a |
Redis Pub/Sub | Only needed when approver and waiter live in different processes. |
Roadmap
Approval token expiry + idempotency keys.
WebSocket push to the dashboard (replace polling).
Redis Pub/Sub so the gate works across multiple workers.
Sandboxed code-execution tool with real isolation (gVisor/Firecracker, no network, non-root).
Per-tenant policies and role-based approvals.
MCP-Tool-Gateway
The MCP Tool Gateway is a centralized proxy and orchestration service that connects LLM clients seamlessly to multiple Model Context Protocol (MCP) servers. It provides unified tool discovery, authentication, and secure request routing across distributed tools and external APIs.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.
No tool schema history has been recorded yet.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Governed MCP gateway: one endpoint for your tools, with credential custody and audit log.
Security & DLP proxy for MCP: tool-poisoning scans, PII redaction on tool args/results. Beta.
- gatewayOAuthai.sealgate
MCP gateway with runtime security policy, tool-call-level control, and audit of agent actions.
Zero-secret MCP gateway for AI agents: risk-scored, audited calls with human-in-the-loop approval.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceA centralized gateway and router that integrates multiple MCP servers into a single endpoint with built-in policy enforcement and secret management. It features a Web GUI for managing tool access, audit logs, and multi-environment configurations across various sub-servers.-
- AlicenseNot gradedqualityBmaintenanceA policy-enforcing MCP gateway that intercepts all tool calls to downstream MCP servers, applying allow/deny/ask rules with human approval and audit logging for safe access to dangerous tools.8MIT
- AlicenseNot gradedqualityBmaintenanceA least-privilege enforcement proxy for MCP servers. It sits between MCP clients and upstream servers, enforcing tool policies, hiding denied tools, requiring human approval for risky actions, and providing a structured audit trail.MIT
- AlicenseNot gradedqualityAmaintenanceProvides a governance proxy layer for MCP servers, enforcing per-tool allowlists, human approval for write operations, quotas, secret redaction, and a hash-chained audit log of all calls.MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/smileyDDx1/MCP-Tool-Gateway'
If you have feedback or need assistance with the MCP directory API, please join our Discord server