Skip to main content
Glama
raheemmohammeddev

Enterprise MCP Gateway

Enterprise MCP Gateway

Multi-Tenant Model Context Protocol Gateway with Dynamic RBAC, Multi-Backend Integration, and Defense-in-Depth Observability

An enterprise-grade MCP server that mediates AI agent and IDE copilot access to backend systems through a unified, governed interface with authentication, authorization, rate limiting, audit logging, and encryption.


Key Metrics

Metric

Value

MCP Tools

15 registered (1 built-in, 4 Jira, 4 BRE stubs, 3 Neo4j, 3 Teradata)

Backend Systems

4 backend modules (Jira, BRE/RDS, Neo4j, Teradata) plus DocumentDB as the config/audit store

Authentication Modes

3 (API Key + JWT, Kiro RBAC, Legacy opt-in loopback only)

Security Guardrails

7 composable checks per request

Audit Pipelines

3 parallel (Jira, Generic, Access)

Rate Limiting

2-layer sliding window (per-consumer + per-group)

Encryption

AES-256-GCM with AWS KMS envelope encryption

RBAC Roles

5 AD-group-mapped tiers


Related MCP server: mcp-gateway

Architecture Overview

Client (AI Agent / Kiro IDE / REST)
    |
    v
AuthMiddleware (3-mode: API Key+JWT, Kiro RBAC, Legacy)
    |
    v
Guardrail Pipeline (7 composable security checks)
    |
    v
Two-Layer Rate Limiter (per-consumer + per-tool-group)
    |
    v
Backend Dispatcher
    |
    +--- Jira REST API / LLM Orchestrator
    +--- Business Rule Engine (RDS API)
    +--- Neo4j (Bolt driver)
    +--- Teradata (MCP Sidecar)
    |
    v
Audit Pipelines (Jira | Generic | Access) --> DocumentDB

MCP Capabilities

Exactly what the server advertises over /mcp, and nothing more:

Capability

Served

Methods

Tools

15

tools/list, tools/call

Resources

1 (graph://neo4j/schema)

resources/list, resources/read

Prompts

1 (cypher_query_guidance)

prompts/list, prompts/get

  • graph://neo4j/schema (application/json) returns node labels, relationship types, property keys and connected (:Label)-[:REL]->(:Label) relationship patterns read from CALL db.schema.visualization(). When that procedure is unavailable the pattern list is empty and a notes field explains why; patterns are never fabricated.

  • cypher_query_guidance(question) returns Cypher composition guidance that embeds the live schema and the constraints the gateway actually enforces (read-only keyword blocking and the auto-injected LIMIT, capped at 500 rows).

  • Both are registered only when NEO4J_ENABLED=true. With the backend disabled resources/list and prompts/list return empty and a resources/read of graph://neo4j/schema fails with Resource not found. The resources and prompts capability flags themselves are advertised unconditionally: the FastMCP layer builds the initialize capability object from its own server features, not from what the backends registered. The registration guard controls the lists, which is what a discovering client enumerates.

  • Both pass through the same guardrail pipeline as the tools before any database work: the resource authorizes against neo4j_get_schema and the prompt against neo4j_validate_query, and the prompt withholds the live schema unless the caller is also authorized for neo4j_get_schema. A caller denied the tool cannot read the data through the resource or the prompt, and a caller with no identity is denied outright.

  • A guardrail denial is delivered in the body, not as a JSON-RPC error, which follows the gateway convention that handler errors are returned at HTTP 200: a denied resources/read returns contents[0].text holding the {"error": "forbidden", "guardrail": "tool_authorization", ...} document, and a denied prompts/get returns that same document as the prompt text. A client has to inspect the payload; it gets no protocol-level signal.

  • No other resources or prompts are registered.


Quick Start

Prerequisites

  • Python 3.11+

  • Docker and Docker Compose (for local infrastructure)

Local Development

# Clone the repository
git clone <repo-url> && cd enterprise-mcp-gateway-core

# Copy environment config
cp .env.example .env

# Start infrastructure (MongoDB + Redis)
docker compose up -d mongodb redis

# Create a virtual environment
python -m venv .venv
source .venv/bin/activate  # Linux/Mac
# .venv\Scripts\Activate.ps1  # Windows PowerShell

# Install dependencies
pip install -r requirements.txt
pip install -e ".[dev]"

# Run the gateway
python src/gateway/app.py --transport=streamable-http --port=8100

# In another terminal, run tests
pytest tests/ -v

Docker Compose (Full Stack)

docker compose up -d
# Gateway at http://localhost:8100
# MongoDB at localhost:27017
# Redis at localhost:6379

Project Structure

enterprise-mcp-gateway-core/
  src/gateway/
    app.py                      # ASGI entry point (Starlette + FastMCP)
    config/
      settings.py               # All env-var configuration with defaults
      db.py                     # DocumentDB/MongoDB connection
    middleware/
      auth.py                   # AuthMiddleware (3-mode authentication)
    security/
      jwt_validator.py          # IdP-agnostic JWT validation
      consumer_registry.py      # Consumer profile management
      kms_envelope.py           # AES-256-GCM envelope encryption
    guardrails/
      pipeline.py               # 7-step composable security pipeline
    ratelimit/
      limiter.py                # Two-layer sliding-window rate limiter
    audit/
      loggers.py                # 3 audit pipelines + sanitization
    backends/
      jira_backend.py           # Jira tools (read + write)
      bre_backend.py            # Business Rule Engine tools (stubs, not implemented)
      neo4j_backend.py          # Neo4j Knowledge Graph tools
      teradata_backend.py       # Teradata via MCP sidecar
  tests/                        # Unit test suite
  docs/                         # Architecture docs and whitepaper
  Dockerfile                    # Production container image
  docker-compose.yml            # Local dev environment
  pyproject.toml                # Build configuration
  requirements.txt              # Pinned dependencies

Environment Variables

Server

Variable

Default

Description

MCP_SERVER_PORT

8100

Gateway HTTP port

LOG_LEVEL

INFO

Logging level

Authentication

Variable

Default

Description

JWT_AUTH_ENABLED

false

Enable JWT bearer token validation

JWT_SECRET

(empty)

Shared secret for JWT signing/validation

JWT_ALGORITHMS

HS512

Accepted JWT signing algorithms. Must stay within one key family (HMAC, RSA or EC); a mixed list is refused

JWT_VERIFY_AUD

true

Verify the aud claim. Requires JWT_AUDIENCE to be set

JWT_AUDIENCE

(empty)

Expected audience; required when JWT_VERIFY_AUD=true

JWT_ISSUER

(empty)

Expected issuer; always required, see the fail-closed note below

JWT_CLAIM_USER_ID

sAMAccountName,sub

Comma-separated candidate claim keys for user ID

JWT_CLAIM_GROUPS

groups,roles

Candidate claim keys for group membership

KIRO_RBAC_ENABLED

true

Require a gateway-signed token for IDE consumers

GATEWAY_ALLOW_INSECURE_LOCAL

false

Opt-in escape hatch for the legacy auto-whitelist. Also requires KIRO_RBAC_ENABLED=false and a loopback client

GATEWAY_STDIO_TRUST_LOCAL

false

Grant a local stdio caller (no AuthMiddleware, therefore no identity) unrestricted tool and project scope. Keep false for any HTTP deployment

KIRO_TOKEN_TTL_HOURS

24

Kiro token expiry in hours

The token validator is fail-closed and the configuration check runs on every validation, independently of JWT_AUTH_ENABLED. With KIRO_RBAC_ENABLED=true (the default) the Mode 2 gateway-signed token is validated by the same code path, so that mode requires JWT_SECRET, JWT_ISSUER, and either JWT_AUDIENCE or JWT_VERIFY_AUD=false. Any other combination makes the gateway refuse to validate tokens at all. This documents today's effective behaviour rather than changing it: validation already failed on the empty JWT_SECRET default.

Rate Limiting

Variable

Default

Description

RATE_LIMIT_WINDOW_SECONDS

60

Sliding window duration

RATE_LIMIT_JIRA

30

Global Jira calls per window

RATE_LIMIT_DEFAULT

59

Default group limit, enforced per pod (not a global cap)

WRITE_RATE_LIMIT

5

Max writes per consumer per write window

Backends

Variable

Default

Description

JIRA_BASE_URL

https://jira.example.com

Jira REST API base URL

JIRA_BACKEND

pat

Backend mode: pat, basic, or llm-orchestrator

NEO4J_ENABLED

true

Enable Neo4j backend

TERADATA_MCP_ENABLED

true

Enable Teradata sidecar

TERADATA_MCP_URL

http://localhost:8001/mcp

Teradata sidecar URL

Encryption

Variable

Default

Description

ENCRYPTION_ENABLED

false

Enable KMS envelope encryption

KMS_KEY_ID

alias/enterprise-mcp-gateway-tokens

AWS KMS CMK ARN or alias

DocumentDB

Variable

Default

Description

DOCDB_HOST

localhost

MongoDB/DocumentDB host

DOCDB_DATABASE

ENTERPRISE_MCP_GATEWAY

Database name

DOCDB_TLS_ENABLED

true

Connect with TLS and full certificate validation. The DocumentDB/RDS CA bundle must be in the host trust store


Authentication Modes

Mode 1: API Key + JWT Bearer Token

For service-to-service consumers (AI agents, REST clients):

X-MCP-API-Key: <consumer-api-key>
Authorization: Bearer <jwt-token>

Mode 2: Kiro IDE RBAC (Gateway-Signed Token)

For IDE copilots authenticated via browser-based flow:

X-MCP-App-Name: kiro-username
X-MCP-Auth-Token: <gateway-signed-jwt>

Mode 3: Legacy (Auto-Whitelist) - local development only, off by default

Disabled unless all three conditions hold: GATEWAY_ALLOW_INSECURE_LOCAL=true, KIRO_RBAC_ENABLED=false, and the request arrives from a loopback peer with no forwarding headers (X-Forwarded-For, X-Forwarded-Host, X-Real-IP, Forwarded). The peer address is the immediate TCP peer, so in a deployment where a reverse proxy or sidecar on the same host fronts the gateway and strips forwarding headers, a remote caller would appear loopback - keep this flag off anywhere behind a proxy. Any other request without a credential receives HTTP 401.

X-MCP-App-Name: kiro-local

stdio transport

AuthMiddleware only runs on the HTTP transport, so a tool call arriving over stdio carries no caller identity. The absence of an identity is not a grant: such calls are denied by the tool authorization guardrail (forbidden / tool_authorization) unless GATEWAY_STDIO_TRUST_LOCAL=true is set, which grants the local caller unrestricted tool and project scope and logs a warning on every call.

JSON-RPC error codes

An authentication failure on the MCP JSON-RPC endpoint (/mcp and /mcp/<...>, excluding the public /mcp/portal, /mcp/dashboard and /mcp/api/ prefixes) is returned as HTTP 401 with a well-formed JSON-RPC 2.0 error object. The inbound request id is echoed when it is readable and is null otherwise (batch array, notification, or an unreadable, oversized or non-JSON body). The pre-existing machine-readable error key is preserved at error.data.error, so consumers that already branch on it keep working.

{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32001,
    "message": "An API key is required. Set the X-MCP-API-Key header.",
    "data": {
      "error": "unauthorized",
      "message": "An API key is required. Set the X-MCP-API-Key header."
    }
  }
}

Gateway-defined codes, from the -32000 to -32099 range the specification reserves for server-defined errors:

Code

Condition

error.data.error

-32001

Authentication required: no credential presented

unauthorized, kiro_auth_required

-32002

Authentication failed: credential presented but rejected

unauthorized (unregistered key), kiro_token_invalid

-32003

Authorization precondition not met

jwt_required

-32002 overlaps a code MCP itself defines: the FastMCP layer returns -32002 with the message Resource not found on this same /mcp endpoint, for example on a resources/read of an unregistered URI. The gateway keeps its three codes in one contiguous block rather than renumbering around the overlap, so do not branch on the code alone. The discriminators for a gateway authentication failure are the HTTP 401 status, which the protocol-level errors never carry, and the error.data.error key.

The standard codes -32700 (parse error), -32600 (invalid request), -32601 (method not found), -32602 (invalid params) and -32603 (internal error) are produced by the FastMCP JSON-RPC layer, as are MCP-defined codes such as the -32002 above, so that list is not exhaustive; the gateway does not override any of them and does not invent codes for conditions it never detects.

Requests to any other path keep their plain {"error": ..., "message": ...} body: wrapping a health check or a dashboard API response in a jsonrpc object would misrepresent the protocol in use on that path. No error body ever carries a stack trace, exception text, token or key material.


Security Guardrails

Every tool invocation passes through 7 composable checks:

#

Guardrail

Fail Mode

Description

0

Tool Active

Fail-open

Registry lookup; disabled tools blocked

1

Tool Authorization

Fail-closed

Consumer's allowed_tools whitelist

2

User Write Auth

Fail-closed

AD group gate for write operations

3

Write Attribution

Fail-closed

Personal PAT required for writes

4

JQL Validation

Fail-closed

Injection pattern blocking

5

SQL Read-Only

Fail-closed

DDL/DML blocking for Teradata

6

Project Scope

Fail-closed

Consumer project whitelist

7

Write Rate Limit

Fail-closed

Per-consumer mutation throttle


Testing

# Run all tests
pytest tests/ -v

# Run with coverage
pytest tests/ --cov=gateway --cov-report=term-missing

# Run specific test module
pytest tests/test_guardrails.py -v

requirements.txt declares starlette>=1.0,<2 and the suite is exercised locally against starlette 1.0.0, where it reports zero warnings. A clean CI install resolves a newer release in the same 1.x line, which emits one StarletteDeprecationWarning from starlette.testclient about its httpx dependency. The warning comes from the test client, not from gateway code, and no gate turns warnings into errors, so the suite passes either way; the zero-warning property is a property of the local pin, not of the declared range. The range is kept deliberately: the pure-ASGI middleware is written against stable ASGI primitives and is expected to hold across 1.x, and pinning an upper bound the project has never needed would claim more than the evidence supports.


License

MIT


Documentation

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables autonomous AI agents to securely access and execute external tools, such as GitHub REST API operations, with per-user authentication, authorization, audit logging, and observability.
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables secure exposure of internal tools to LLM clients with API-key authentication, tenant isolation, per-tool guardrails, PII scrubbing, rate limiting, audit logging, and telemetry over stdio and HTTP/SSE.
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables secure enterprise integration by bridging SQL, financial, SCADA, and document backends to LLM agents with RBAC, DLP redaction, human-in-the-loop approvals, and OpenTelemetry tracing.
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables LLM agents to access third-party APIs through a least-privilege set of audited tools with allowlists, per-caller scopes, label gates, write confirmation, limits, and PII-masked logging. It restricts reads to labeled data, prevents sending or destructive actions, and records every allowed or denied call.
    MIT