MCP Enterprise 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 Enterprise Tool Gatewayget account information for account number 1234567890"
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.
MCP Enterprise Tool Gateway: A Governed Model Context Protocol Server for Bank-Grade Agent Tool Access
Fictional bank notice: "Northbridge Financial Group" is a wholly invented bank, created solely as a synthetic demo brand for this project. It does not exist. Every account, customer, transaction, and general ledger entry referenced anywhere in this repository is fabricated for illustration -- no real institution, customer, or employer is represented here.
Why this exists
A regulated bank cannot let every agent framework its teams adopt -- LangGraph here, CrewAI there, an in-house AutoGen pilot somewhere else -- reimplement its own access control, PII handling, and audit logging around "who gets to call the account-lookup tool." That inconsistency is where incidents come from. This repository shows the alternative: one governed MCP server that every agent framework talks to identically, where RBAC, PII redaction, rate limiting, and append-only audit logging are enforced exactly once, in the tool-serving layer itself, instead of trusted to each calling agent's own good behavior. It is deliberately built as zero-paid-API-key infrastructure -- this repo serves tools, it does not call an LLM -- so a reviewer can clone it and see the whole governance pipeline run locally in seconds.
Related MCP server: production-grade-mcp-agentic-system
Architecture
flowchart LR
subgraph Callers["Agent Frameworks (examples/)"]
LG["LangGraph<br/>ToolNode"]
CA["CrewAI<br/>BaseTool"]
GC["Generic MCP client<br/>(any framework)"]
end
subgraph Gateway["MCP Enterprise Tool Gateway"]
MCP["mcp_server.py<br/>FastMCP tool handlers"]
MW["ToolGateway.invoke (gateway.py)<br/>governance pipeline"]
RBAC["RBAC scope check<br/>(rbac.py)"]
RL["Rate limiter<br/>(ratelimit.py)"]
RED["PII redaction<br/>(redaction.py)"]
AUD["Append-only audit log<br/>(audit.py, SQLite)"]
CB["Circuit breaker<br/>(resilience.py)"]
end
subgraph Backend["Mock Enterprise Systems"]
DS["MockDatastore (store.py)<br/>accounts / transactions / KYC /<br/>product catalog / general ledger"]
end
subgraph Ops["Admin / Audit API (admin_api.py)"]
H["/healthz /readyz"]
M["/metrics (Prometheus)"]
A["/audit /scopes /traces"]
end
LG -->|MCP: stdio / streamable-http| MCP
CA -->|MCP: stdio / streamable-http| MCP
GC -->|MCP: stdio / streamable-http| MCP
MCP --> MW
MW --> RBAC
RBAC -->|allowed| RL
RL -->|under limit| CB
CB --> DS
DS --> RED
RED --> AUD
AUD -->|redacted result| MCP
RBAC -.->|denied| AUD
RL -.->|rate limited| AUD
Ops -.->|reads| AUD
Ops -.->|reads| RBACEvery arrow into "Gateway" is the same regardless of which box in "Callers" it came from -- that uniformity is the entire point. See GOVERNANCE.md for the detailed per-control walkthrough.
Key Design Decisions
agent_identityis an explicit tool argument, not transport metadata. MCP's stdio transport -- the most portable, zero-install option, and what every "generic MCP client" example uses -- has no standard out-of-band header mechanism the way HTTP does. Rather than build governance on a feature only some transports support, every tool acceptsagent_identitydirectly (seemodels.AgentContext). Tradeoff: the identity is currently self-asserted by the caller. In a production deployment behind streamable-HTTP, this string would be replaced, not merely supplemented, by an identity derived from mTLS or a verified OAuth token at the transport boundary.SQLite for the audit log and RBAC table, not Postgres. This is a demo/portfolio project: SQLite requires no separate service to run, ships in the standard library, and is genuinely sufficient for an append-mostly, single-writer audit log. Tradeoff: a single SQLite file does not support multiple concurrent writer processes across nodes, which is why the Kubernetes manifests intentionally ship at 1 replica per component (see the comment in
deploy/k8s/deployment.yaml). Migratingsqlite_backend.pyto Postgres is a self-contained, scoped change precisely because storage access is centralized in one module.Redaction is uniform and pattern-based, with no per-field allowlist. Every string matching an SSN or account-number-shaped digit run is masked to its last 4 characters -- including primary-key fields like
account_idin the response for the very account the caller asked about. Tradeoff: this occasionally masks a value that arguably "should" be visible (mild usability cost), in exchange for never requiring a human to maintain a growing "these fields are safe" exception list as new tools are added, which is exactly the kind of list that quietly rots.In-memory rate limiting, not a shared store. The token-bucket limiter in
ratelimit.pyis process-local state. Tradeoff: a horizontally scaled deployment would need a shared backend (Redis, most likely) for limits to be enforced consistently across replicas; that is flagged explicitly in the module rather than silently glossed over.A real (if simple) circuit breaker around a datastore that cannot actually fail. The mock datastore is an in-memory Python dict -- it cannot time out or 500.
resilience.pywraps it in a genuine three-state circuit breaker anyway, because the pattern a production version of this gateway needs (fail fast and stop hammering a struggling downstream system during an incident) should exist and be tested now, not bolted on later under pressure./readyzreports the breaker's state, which is what a Kubernetes readiness probe should key off in production.
Governance & Guardrails
This is the centerpiece of the repo -- see GOVERNANCE.md
for the full walkthrough. In summary, every tool call passes through the
same five controls, enforced once in ToolGateway.invoke regardless of
caller:
Identity -- every call carries an explicit
agent_identity.RBAC (
rbac.py) -- default-deny scope table, SQLite-backed. Acustomer-service-agentidentity can calllookup_account_summarybut is deniedget_general_ledger_entry; seetests/test_rbac.py.Rate limiting (
ratelimit.py) -- per-identity token bucket; trips raise a clear MCP tool error with a retry hint.PII redaction (
redaction.py) -- SSN/account-number-shaped values masked to their last 4 characters in every response and before anything is written to the audit log.Append-only audit log (
audit.py) -- one row per call (including denials/failures) with redacted params, a result hash, latency, and status. No update/delete code path exists.
This gateway is the enforcement point that keeps raw sensitive data out of an LLM's context window, no matter which agent framework is calling it. Redaction happens inside the gateway, before a result is ever serialized back over MCP -- an agent framework cannot opt out of it, and does not need to know it is happening.
Getting Started
Requires Python 3.11+. No paid API keys, no external services -- the gateway serves tools backed by an in-memory mock datastore.
git clone <this-repo-url>
cd mcp-enterprise-tool-gateway
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
make install # pip install -e . plus dev tooling
cp .env.example .env # optional -- sane defaults work as-is
make test # 39 tests: RBAC, audit, redaction, rate limiting, ...
make lint # ruff + mypyRun the MCP server
make run # python -m mcp_gateway (stdio transport by default)Point a generic MCP client at it
examples/generic_mcp_client.py is genuinely runnable end-to-end -- it
spawns the server over stdio, lists the five governed tools, calls
lookup_account_summary as customer-service-agent (showing PII
redaction in the response), then calls get_general_ledger_entry with the
same identity to show an RBAC denial surfacing as a clean MCP tool error:
python examples/generic_mcp_client.pyTo point any other MCP-compatible client (Claude Desktop, mcp dev, a
hand-rolled ClientSession, LangGraph's langchain-mcp-adapters, ...) at
this server, the shape is the same in every case: spawn
python -m mcp_gateway as a stdio subprocess (or connect to
GATEWAY_MCP_TRANSPORT=streamable-http over HTTP once deployed), then call
any of the five tools with an agent_identity string plus that tool's
declared parameters. See examples/README.md for the LangGraph and
CrewAI integration sketches.
Run the admin/audit API
make run-admin-api # http://localhost:8000
curl localhost:8000/healthz
curl localhost:8000/readyz
curl localhost:8000/scopes # current RBAC table
curl localhost:8000/audit # recent audit log entries (PII-redacted)
curl localhost:8000/metrics # Prometheus exposition formatOne-command local stack
docker compose up --build # admin API on :8000, MCP server (streamable-http) on :8765Observability
Four Prometheus series (mcp_gateway/metrics.py), scraped from
/metrics on the admin API, are the headline governance signals for a
Grafana dashboard:
mcp_gateway_tool_calls_total{tool_name, agent_identity, status}-- call volume broken down by who is calling what, and whether it succeeded, was denied, rate-limited, timed out, or errored.mcp_gateway_redaction_hits_total{tool_name}-- how often PII redaction actually fires, per tool.mcp_gateway_rbac_denials_total{tool_name, agent_identity}-- RBAC denial rate, per identity.mcp_gateway_rate_limit_trips_total{agent_identity}-- rate-limit trip rate, per identity.mcp_gateway_tool_call_latency_seconds{tool_name}-- end-to-end latency histogram, including governance overhead.
Every call also gets a lightweight tracing span (tracing.py) with a
correlation ID shared with its structured JSON log line and audit row, and
the most recent 200 spans are queryable at /traces for local debugging.
In production, these spans are structured enough to ship into an
OpenTelemetry collector / Jaeger without changing any call sites; wire
Prometheus to scrape /metrics and build a Grafana dashboard on the four
series above as the natural next step (see Roadmap).
Production Deployment
Docker:
Dockerfileis a multi-stage build (builder stage installs pinned dependencies into a venv; runtime stage is a slim, non-compiler-bearing image) running as a non-rootgatewayuser (uidon a pinned
python:3.12.8-slim-bookwormbase.
docker-compose:
docker-compose.ymlbrings up the admin API and the MCP server (streamable-HTTP transport) as two containers from one image, sharing a named volume for the SQLite-backed audit log.Kubernetes:
deploy/k8s/has a Deployment + Service + PVC per component, a ConfigMap for the (all non-secret) environment variables, a HorizontalPodAutoscaler for the stateless admin API, and a default-deny NetworkPolicy with explicit allow rules. See the comments indeployment.yamlfor why replica count is intentionally 1 until the Postgres migration described above lands.OpenShift:
deploy/OPENSHIFT.mdcovers SCC/UID handling, Routes vs. Ingress, ImageStreams, and NetworkPolicy support on OpenShift's default SDN.CI:
.github/workflows/ci.ymlruns ruff, mypy, pytest, and a Docker build on every push/PR, in that order, failing fast on the cheapest check first.
Tech Stack
Layer | Technology | Why |
MCP protocol |
| The standard this whole portfolio's tool-access layer is built on. |
Admin/audit HTTP API | FastAPI + Uvicorn | Async, typed, first-class OpenAPI docs, minimal boilerplate for health/metrics/audit endpoints. |
Validation | Pydantic v2 | Boundary validation on every tool input/output and audit record. |
Configuration | pydantic-settings | Typed, validated env-var configuration; no hardcoded secrets. |
Audit log / RBAC storage | SQLite (stdlib | Zero-install, sufficient for an append-mostly audit log; see Key Design Decisions. |
Metrics |
| Standard |
Logging |
| Structured JSON logs with correlation IDs, safe for a real log aggregator. |
Testing | pytest, pytest-asyncio, httpx | 39 unit/integration tests covering every governance control. |
Lint / types | ruff, mypy | Fast style+correctness checks and static typing, enforced in CI. |
Containerization | Docker (multi-stage, non-root), docker-compose | One-command local spin-up; production-shaped image. |
Orchestration | Kubernetes manifests, OpenShift notes | Deployment/Service/PVC/HPA/NetworkPolicy for a real cluster. |
Repository Structure
mcp-enterprise-tool-gateway/
├── src/mcp_gateway/
│ ├── mcp_server.py # MCP protocol adapter (FastMCP tool handlers)
│ ├── gateway.py # ToolGateway.invoke -- the governance pipeline
│ ├── container.py # Wires config -> storage -> rbac -> gateway
│ ├── config.py # pydantic-settings, GATEWAY_* env vars
│ ├── models.py # Pydantic models for every boundary
│ ├── rbac.py # Default-deny scope table (SQLite-backed)
│ ├── ratelimit.py # Per-agent-identity token bucket
│ ├── redaction.py # PII masking middleware
│ ├── audit.py # Append-only audit log (SQLite-backed)
│ ├── resilience.py # Circuit breaker around the mock datastore
│ ├── tracing.py # Lightweight per-call tracing spans
│ ├── metrics.py # Prometheus counters/histograms
│ ├── logging_setup.py # Structured JSON logging + correlation IDs
│ ├── correlation.py # contextvars-based correlation ID propagation
│ ├── sqlite_backend.py # Shared SQLite connection/schema plumbing
│ ├── admin_api.py # FastAPI: /healthz /readyz /metrics /audit /scopes /traces
│ ├── datastore/
│ │ ├── seed_data.py # Synthetic Northbridge Financial Group fixtures
│ │ └── store.py # In-memory mock enterprise datastore
│ └── tools/
│ └── banking_tools.py # The 5 tool implementations
├── tests/ # RBAC, audit, redaction, rate limiting, gateway, admin API
├── examples/
│ ├── generic_mcp_client.py # Actually runnable end-to-end
│ ├── langgraph_tool_node.py # Illustrative LangGraph integration sketch
│ └── crewai_custom_tool.py # Illustrative CrewAI integration sketch
├── deploy/
│ ├── k8s/ # Deployment, Service, ConfigMap, HPA, NetworkPolicy, PVC
│ └── OPENSHIFT.md
├── .github/workflows/ci.yml # lint -> typecheck -> test -> docker build
├── Dockerfile # Multi-stage, non-root, pinned base image
├── docker-compose.yml
├── Makefile
├── GOVERNANCE.md # Governance & guardrails deep dive
├── SECURITY.md
└── CONTRIBUTING.mdOne governed tool surface, many agent frameworks
This gateway is designed to be consumed identically from LangGraph,
CrewAI, AutoGen, or any other MCP-compatible client -- examples/ proves
the shape for three of them. None of them get special treatment, and none
of them can bypass RBAC, redaction, rate limiting, or audit logging,
because those controls live in the gateway, not in any particular
framework's integration code. That is the connective tissue across a
larger personal portfolio of agent-framework demos: whichever framework a
given repo showcases, the tool access layer underneath it is the same
governed pattern shown here. (This repo stands alone -- it does not assume
or require any of those other demos to be present or wired in.)
Roadmap / What I'd Build Next
Postgres-backed audit log to remove the single-writer/single-replica constraint noted in
sqlite_backend.pyanddeploy/k8s/deployment.yaml, enabling true horizontal scaling of the MCP server.Verified identity at the transport layer -- replace the self-asserted
agent_identityargument with an identity derived from mTLS client certificates or a validated OAuth token, terminated at the streamable-HTTP boundary, so RBAC decisions are keyed off something a caller cannot spoof.OpenTelemetry exporter wired directly into
tracing.py's spans, plus a checked-in Grafana dashboard JSON built on the four Prometheus series already exposed.Shared (Redis-backed) rate limiter so limits hold consistently once the MCP server runs as more than one replica.
Dependabot / SCA scanning wired into CI, now that dependencies are pinned to exact versions.
A real langchain-mcp-adapters-based LangGraph integration test, run in CI against this server, once that dependency's install footprint is acceptable for a lightweight infra repo.
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 Servers
- Alicense-qualityCmaintenanceMCP server for AI agent security guardrails. Provides input validation, prompt injection detection, PII redaction, output filtering, policy enforcement, rate limiting, and comprehensive audit logging.Last updated501MIT
- Alicense-qualityDmaintenanceA production-grade MCP server designed for multi-tenant, authenticated, and observable AI agent systems, enabling secure tool execution across heterogeneous data sources.Last updated52MIT
- Alicense-qualityCmaintenanceEnables AI agents to discover and execute tools via a secure MCP server with JWT authentication, RBAC, rate limiting, and audit logging.Last updated1MIT
- Flicense-qualityCmaintenanceA governed MCP server for integrating AI agents with customer data, featuring role-based access control, field redaction, and human-in-the-loop approval for secure support operations.Last updated1
Related MCP Connectors
A paid remote MCP for CLI tool MCP, built to return verdicts, receipts, usage logs, and audit-ready
MCP server for AI agents to plan, verify, and deploy Cloudflare-native apps.
A paid remote MCP for agent memory MCP, built to return verdicts, receipts, usage logs, and audit-re
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/manuelbomi/MCP-Enterprise-Tool-Gateway-A-Governed-Model-Context-Protocol-Server-for-Bank-Grade-Agent-Tool-Acce'
If you have feedback or need assistance with the MCP directory API, please join our Discord server