# IBM/mcp-context-forge

> Python/FastAPI gateway that federates MCP servers, REST endpoints and A2A agents behind one RBAC-governed MCP endpoint.

- Category: [API layer & connectors](https://llms-technical-reviews.com/connectors/)
- Repository: https://github.com/IBM/mcp-context-forge (reviewed at commit `a10c655b9fc04d42d2029a565a25bad059409c77`, 2026-10-06)
- Stars: 4579 · Language: Python · License: Apache-2.0
- Canonical page: https://llms-technical-reviews.com/p/mcp-context-forge/

## Overview

ContextForge (`mcp-contextforge-gateway` on PyPI) is IBM's MCP gateway. It is a single FastAPI application that sits between MCP clients and many upstream tool sources, and it presents all of them as one governed MCP endpoint. It has no integration code of its own. It **federates**: you register upstream MCP servers ("gateways"), individual REST endpoints (REST tools) or A2A agents. ContextForge stores their tools, resources and prompts in its database, groups them into **virtual servers**, and serves them over Streamable HTTP, SSE or WebSocket. Authentication, team-scoped RBAC, OAuth to upstreams, SSRF protection, plugin hooks, metrics and an admin UI sit around that core.

So in this category it is the "bring your own connectors" option. The value is in governance and protocol plumbing, not in a catalog of hand-written API wrappers. The closest thing to a catalog is `mcp-catalog.yml`, a curated list of 135 public remote MCP servers (Asana, Linear, Notion and others) with pre-seeded OAuth discovery metadata, which an admin can register in a few clicks.

The code base is large and mature for its age: `main.py` alone is about 13,600 lines and `tool_service.py` about 9,000. Its comments often cite issue numbers and performance fixes.

## Architecture

```mermaid
flowchart LR
  C["MCP client / agent"] --> GATE["MCP endpoint: /mcp or /servers/ID/mcp"]
  GATE --> MW["Auth + path rewrite middleware"]
  MW --> T["Streamable HTTP handlers"]
  T --> TS["ToolService.invoke_tool"]
  TS --> PL["Plugin hooks (cpex)"]
  TS --> OA["OAuthManager"]
  TS --> REST["REST tool: httpx + SSRF pinning"]
  TS --> MCP["Upstream MCP: SSE / Streamable HTTP"]
  TS --> A2A["A2A agent"]
  GS["GatewayService"] --> MCP
  GS --> DB["SQLite / Postgres"]
  TS --> DB
  EV["EventService (Redis pub/sub)"] --> T
```

| Component | Path | Role |
|---|---|---|
| App and routing | `mcpgateway/main.py` | FastAPI app, routers, `/mcp` mount, path-rewrite middleware |
| MCP transport | `mcpgateway/transports/streamablehttp_transport.py` | MCP server object, `tools/list`, `tools/call`, resources, prompts, session handling |
| Ingress mount | `mcpgateway/transports/mcp_ingress_mount.py` | Swappable ASGI dispatcher for `/mcp` (Python or the Rust sidecar) |
| Tool service | `mcpgateway/services/tool_service.py` | Tool lookup, RBAC, schema validation, invocation for REST/MCP/A2A |
| Gateway service | `mcpgateway/services/gateway_service.py` | Register upstream MCP servers, handshake, refresh, health checks |
| OAuth | `mcpgateway/services/oauth_manager.py`, `token_backends/` | Client credentials, password, auth code + PKCE, RFC 8693 token exchange; DB or Vault token storage |
| Schemas and models | `mcpgateway/schemas.py`, `mcpgateway/db.py` | Pydantic create/read schemas; SQLAlchemy `Tool`, `Server`, `Gateway`, `A2AAgent` |
| Translate | `mcpgateway/translate.py`, `translate_grpc.py` | Bridge stdio MCP servers to SSE/Streamable HTTP and back; gRPC bridge |
| Plugins | `plugins/`, `mcpgateway/plugins/` | About 40 bundled plugins on the external `cpex` framework (PII filter, rate limiter, Vault, and others) |
| Rust runtime | `crates/mcp_runtime/` | Optional Rust MCP sidecar, now deprecated |

## How a request flows

Take a `tools/call` from an MCP client against a virtual server:

1. **Path and auth.** `MCPPathRewriteMiddleware` authenticates first, then rewrites `/servers/<id>/mcp` (and `/v1/virtual-servers/<id>/mcp`) to `/mcp/` so a single transport serves every virtual server ([main.py](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/main.py#L3158-L3175)). `/mcp` is mounted behind an Origin/Host gate ([main.py](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/main.py#L13540-L13550)).
2. **MCP dispatch.** The transport registers MCP SDK v2 handlers for `tools/list`, `tools/call`, prompts, resources, completion and logging ([streamablehttp_transport.py](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/transports/streamablehttp_transport.py#L3575-L3583)). `call_tool` turns off the SDK's own schema validation, because older JSON Schema drafts fail it, and leaves validation to the tool service ([L1885-L1900](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/transports/streamablehttp_transport.py#L1885-L1900)).
3. **Resolve.** `invoke_tool` calls `_resolve_tool_for_invocation` with the user, token teams and server id. That step does lookup, visibility/RBAC and input-schema validation in one place, shared with other call paths, and a schema failure becomes a `ToolInvocationError` ([tool_service.py](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/services/tool_service.py#L5785-L5830), [L5416-L5430](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/services/tool_service.py#L5416-L5430)).
4. **Release the DB.** Everything needed for the call is copied into local variables, and the session is committed and closed before any network I/O, so slow upstreams do not pin pool connections.
5. **Authenticate upstream.** For a REST tool with `auth_type == "oauth"`, `OAuthManager.get_access_token` fetches a token, with mTLS material taken from the gateway ([tool_service.py](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/services/tool_service.py#L6110-L6125)). The manager dispatches by grant type. `authorization_code` cannot be used here, because it needs prior user consent through `/oauth/authorize` ([oauth_manager.py](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/services/oauth_manager.py#L255-L286)).
6. **Plugins.** If any plugin registers `TOOL_PRE_INVOKE`, it can rewrite headers or arguments or block the call. `TOOL_POST_INVOKE` runs on the result.
7. **Call out.** For REST, the final URL goes through `validate_url_for_connection_pinning`. The URL is pinned to the resolved IP with the original `Host` and SNI kept, which closes DNS-rebinding gaps ([tool_service.py](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/services/tool_service.py#L6255-L6300)). For MCP tools, the gateway opens an SSE or Streamable HTTP client session to the upstream. For `authorization_code` gateways it uses the calling user's stored token ([L6519-L6545](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/services/tool_service.py#L6519-L6545)).
8. **Return.** The result is mapped back to MCP content, metrics and spans are recorded, and failures surface as `ToolInvocationError`, `ToolTimeoutError` or plugin violations.

## Key components

### Three kinds of tools

`ToolCreate.integration_type` is `REST`, `MCP` or `A2A` ([schemas.py](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/schemas.py#L790-L830)). MCP tools come from gateway discovery. REST tools are hand-registered endpoints with an HTTP method, `input_schema`, header/query mapping, `path_template`, a host `allowlist`, a `jsonpath_filter` and per-tool plugin chains. That is how ContextForge turns a plain HTTP API into an MCP tool. A helper endpoint can pull input/output schemas from an OpenAPI document for a single operation. The repo does not bulk-generate tools from OpenAPI.

### Gateways (upstream MCP servers)

`register_gateway` stores the upstream and calls `_initialize_gateway`. That function connects over SSE or Streamable HTTP with the configured auth (basic, bearer, custom headers, query param, OAuth, optional mTLS and a custom CA), runs the MCP handshake, and returns capabilities plus tool, resource and prompt definitions ([gateway_service.py](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/services/gateway_service.py#L1811-L1830), [L5510-L5530](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/services/gateway_service.py#L5510-L5530)). Each gateway has a `gateway_mode`: `cache`, where tools come from the database (the default), or `direct_proxy`, a pass-through with no caching. `direct_proxy` also needs a global flag that is off by default ([schemas.py](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/schemas.py#L3340-L3356)). Health checks run every `health_check_interval` seconds: 60 by default, set to 300 in the shipped compose file ([config.py](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/config.py#L2974-L2978)).

### SSRF defaults

SSRF protection is on by default. Cloud metadata ranges, link-local and CGNAT ranges are always blocked. Localhost and RFC 1918 networks are blocked unless explicitly allowed or listed in `SSRF_ALLOWED_NETWORKS` ([config.py](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/config.py#L780-L825)). The deny-by-default rule for private networks is right for a gateway, but it surprises people whose upstream MCP servers run on the same Docker network.

### Ingress and the Rust sidecar

`MCPIngressMount` holds a registry of named ASGI apps plus a selector, so the public `/mcp` can switch between the Python transport and Rust ingresses at runtime ([mcp_ingress_mount.py](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/transports/mcp_ingress_mount.py#L1-L40)). The Rust crate it was built for is marked deprecated, with a sunset date that has already passed. Its README tells users to stay on the Python path. Python remains the authority for authentication, token scoping and RBAC ([README.md](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/crates/mcp_runtime/README.md#L1-L30)).

### Translate bridge

`mcpgateway.translate` wraps a local stdio MCP server (for example `uvx mcp-server-git`) and exposes it over SSE or Streamable HTTP, or does the reverse ([translate.py](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/translate.py#L1-L40)). This is how stdio-only servers get onto the gateway. The gateway itself only connects to network transports.

## Extending it

- **Register upstreams.** Use the admin UI or REST API to add MCP servers, REST tools or A2A agents, then compose virtual servers from the tools you want to expose. Visibility is `private`, `team` or `public`.
- **Catalog.** Turn on `mcpgateway_catalog_enabled` (default true) and edit `mcp-catalog.yml` to offer your own curated server list.
- **Plugins.** Write a `cpex` plugin with tool, prompt or resource pre/post hooks, or start from one of the bundled ones (`deny_filter`, `vault`, `circuit_breaker`, `schema_guard` and others). The `mcpplugins` CLI comes from `cpex`.
- **Stdio servers.** Front them with `mcpgateway.translate` and register the resulting URL.

## Running it

- **Package.** `pip install mcp-contextforge-gateway` (Python 3.12-3.13). The `mcpgateway` CLI starts the server ([pyproject.toml](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/pyproject.toml#L320-L325)).
- **Make.** `make dev` runs Uvicorn with `--reload` on port 8000. `make serve` runs Gunicorn with Uvicorn workers ([Makefile](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/Makefile#L459-L460)).
- **Compose.** `docker-compose.yml` puts an Nginx caching proxy in front of gateway replicas, with Postgres, PgBouncer and Redis. Profiles add Keycloak SSO, a monitoring stack (Prometheus, Grafana, Loki, Tempo), Locust load tests, TLS and more ([docker-compose.yml](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/docker-compose.yml#L73-L80), [L122-L140](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/docker-compose.yml#L122-L140)). Helm charts are in `charts/`.
- **Minimum.** SQLite and a single process work for development. Redis is needed for multi-instance event fan-out and shared caches.

## Strengths and caveats

- **Strength: governance in one place.** Team-scoped RBAC, scoped API tokens, SSO, per-user OAuth tokens to upstreams, token exchange and audit logging cover what an enterprise asks for before letting agents reach internal tools.
- **Strength: careful outbound security.** IP-pinned SSRF checks, schema validation with multi-draft support, and encrypted stored credentials (Argon2id-derived Fernet keys).
- **Strength: protocol breadth.** MCP over three transports, REST-to-MCP, A2A and stdio bridging all go through one invocation path, with plugin hooks at every step.
- **Caveat: no connectors of its own.** Every integration is either an upstream MCP server someone else maintains or a REST tool you describe by hand. ContextForge adds no API-specific logic, pagination or webhook triggers.
- **Caveat: size and churn.** The core modules run to thousands of lines, there are hundreds of settings, and the code carries compatibility layers (MCP SDK v1 to v2, legacy encryption formats, a deprecated Rust runtime). Expect real operational effort.
- **Caveat: no trigger model.** Events are internal (Redis pub/sub, `tools/list_changed`). Nothing turns an inbound third-party webhook into an agent trigger.

*Sources: code at a10c655, verified Q&A.*

## How IBM/mcp-context-forge answers the API layer & connectors questions

### How is third-party authentication implemented? (answered)

**Third-party authentication is implemented through multiple mechanisms, layered on top of internal JWT-based auth.**

*OAuth2 / SSO Providers:* Seven SSO providers are supported via OAuth2/OIDC: GitHub, Google, Microsoft Entra ID, Okta, Keycloak, ADFS, and a generic OIDC provider (Auth0, Authentik, etc.). The SSO router exposes `POST /auth/sso/{provider}` to initiate the authorization-code flow. Each provider is configured through environment variables with `client_id`, `client_secret`, `authorization_url`, `token_url`, `scope`, and group/role mappings. Provider configs are also manageable at runtime via CRUD endpoints on `SSOService`.

*OAuth 2.0 Token Management:* The `OAuthManager` handles Client Credentials (M2M), Password, and Authorization Code grant types. It implements PKCE (RFC 7636) and supports `refresh_token` flow. The `token-exchange` grant (RFC 8693) is also implemented for gateway-based on-behalf-of flows. OAuth tokens for upstream MCP servers are stored encrypted — either in the database or in HashiCorp Vault (pluggable backend via `oauth_token_backend` config).

*API Keys / JWT Tokens:* The tokens router provides CRUD for scoped API tokens with named token creation, team scoping, and revocation via blocklist. Token teams are decoded via `normalize_token_teams()`. Token creation uses `TokenCatalogService` for scope derivation and `PermissionService` for RBAC containment checks.

*Credential Encryption:* The `EncryptionService` encrypts stored client secrets using an Argon2id-derived Fernet key with a `v2:` format marker, supporting both strict and idempotent modes.

*Dynamic Client Registration:* The `DcrService` implements RFC 7591 for automatic OAuth client registration, and RFC 8414 for AS metadata discovery.

*Multi-tenant connected accounts:* Team-based RBAC and token scoping control what each authenticated user can see and do. The `SSOService` supports auto-creating users from SSO, trusted-domain filtering, and team/role mapping from IdP groups.

*Email Auth:* The `EmailAuthService` and email auth router implement Argon2id-password-based login as an alternative, with lockout, password expiry, and reset workflows.


Citations: [mcpgateway/routers/sso.py:1-120](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/routers/sso.py#L1-L120) · [mcpgateway/services/oauth_manager.py:93-290](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/services/oauth_manager.py#L93-L290) · [mcpgateway/services/encryption_service.py:1-150](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/services/encryption_service.py#L1-L150) · [mcpgateway/services/dcr_service.py:1-60](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/services/dcr_service.py#L1-L60) · [mcpgateway/routers/tokens.py:1-80](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/routers/tokens.py#L1-L80) · [mcpgateway/config.py:316-710](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/config.py#L316-L710)

### How is an integration / connector defined? (answered)

**Integrations/connectors are defined as Gateways and Servers, both database-backed models with Pydantic schemas.**

*Gateway model* (`db.py` class `Gateway`): Represents an upstream MCP server endpoint. Registered via `GatewayService.register_gateway()` which accepts a `GatewayCreate` schema. The schema defines: `name` (unique), `url`, `transport` (SSE, STREAMABLEHTTP), `auth_type` (basic, bearer, authheaders, oauth, query_param), OAuth config, mTLS certificates, refresh interval, gateway mode (cache or direct_proxy), team scoping, and visibility. During registration the gateway is contacted for capability negotiation and tool/resource/prompt aggregation.

*Virtual Server model* (`db.py` class `Server`): A logical grouping that associates a subset of tools/resources/prompts into a named server, exposed as a unified MCP endpoint.

*Schema format:* Tools, Resources, and Prompts are stored as database rows derived from gateway capability discovery, not from a manifest file. Each tool carries `name`, `description`, `input_schema` (JSON Schema), `output_schema`, `auth_type`/`auth_value`, OAuth config, URL, `integration_type` (REST, A2A, MCP), and metadata like tags, team_id, and visibility.

*Code vs. config:* All configuration is driven through the REST API at runtime. There is no pipeline that code-generates from OpenAPI specs. The `GatewayCreate` schema has ~40 configurable fields covering transport, auth, TLS, OAuth, passthrough headers, caching, and identity propagation. Team scoping and RBAC govern visibility.

*Catalog:* An MCP server catalog (`mcpgateway_catalog_enabled`) can bulk-import known servers from a YAML file (`mcp-catalog.yml`) with auto health-checking.

*Versioning:* There is no formal integration versioning. The `protocol_version` config field tracks the MCP protocol version. Gateways have a `mcp_protocol_version` field.

The project ships tens of built-in integration support mechanisms (7+ SSO providers, 4+ OAuth grant types, mTLS, query-param auth, custom CA certs) but defines no pre-packaged connector definitions for specific third-party APIs.

> **Editor's note.** Correction: besides gateway-discovered MCP tools, `ToolCreate.integration_type` defaults to `REST` (also `A2A`), so individual HTTP endpoints are registered by hand with method, input schema, path template and header/query mappings (mcpgateway/schemas.py L790-L830). The repo also ships `mcp-catalog.yml`, a curated list of 135 public remote MCP servers with seeded OAuth metadata.

Citations: [mcpgateway/schemas.py:3236-3360](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/schemas.py#L3236-L3360) · [mcpgateway/services/gateway_service.py:749-850](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/services/gateway_service.py#L749-L850) · [mcpgateway/services/gateway_service.py:1811-1920](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/services/gateway_service.py#L1811-L1920) · [mcpgateway/db.py:3270-3275](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/db.py#L3270-L3275) · [mcpgateway/db.py:4412-4417](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/db.py#L4412-L4417) · [mcpgateway/db.py:4712-4717](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/db.py#L4712-L4717)

### How are integrations exposed to LLM agents? (answered)

**Integrations are exposed to LLM agents via the MCP protocol through multiple transport mechanisms.**

*MCP Server:* The gateway creates an MCP server instance (`mcp_app` in the Streamable HTTP transport) and registers request handlers for `tools/list`, `tools/call`, `resources/list`, `prompts/list`, and more. Each handler is an `_adapt_*` function that bridges between the typed MCP types library and the internal service layer.

*Transport protocols:* Three MCP transports expose the server: **Streamable HTTP** (primary, at `POST /mcp`), **SSE** (at `/mcp/sse` + `/mcp/message`), and **WebSocket**. The `MCPIngressMount` provides a swappable mount point for `/mcp`, supporting multiple ingress backends including an experimental Rust proxy.

*Tool listing:* The `list_tools()` function returns all tools available through the gateway. It supports two modes: `cache` (returns tools from the database, previously discovered during gateway registration) and `direct_proxy` (forwards the request to the remote MCP server using the MCP SDK client).

*Dynamic tool loading:* Tools are discovered during gateway registration via `_initialize_gateway_with_timeout()` which performs MCP capability handshake and stores results in the database. `ToolService.list_tools()` supports cursor-based pagination, tag filtering, gateway filtering, and token-team RBAC enforcement.

*Function-calling schemas:* Each tool stores a JSON Schema `input_schema` from the upstream MCP server, exposed through `tools/list` as MCP Tool objects with `name`, `description`, and `inputSchema` — directly consumable by MCP-compatible LLM clients.

There are no separate SDKs per framework — the gateway IS the API surface. Plugin hooks via CPEX can intercept and modify tool calls before execution.


Citations: [mcpgateway/transports/streamablehttp_transport.py:3460-3490](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/transports/streamablehttp_transport.py#L3460-L3490) · [mcpgateway/transports/streamablehttp_transport.py:2686-2780](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/transports/streamablehttp_transport.py#L2686-L2780) · [mcpgateway/transports/streamablehttp_transport.py:3575-3585](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/transports/streamablehttp_transport.py#L3575-L3585) · [mcpgateway/transports/mcp_ingress_mount.py:1-80](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/transports/mcp_ingress_mount.py#L1-L80) · [mcpgateway/transports/streamablehttp_transport.py:1603-1656](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/transports/streamablehttp_transport.py#L1603-L1656) · [mcpgateway/services/tool_service.py:3046-3110](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/services/tool_service.py#L3046-L3110)

### How is a tool call executed? (answered)

**Tool calls are executed through a multi-phase pipeline in `ToolService.invoke_tool()`.**

*Phase 0 – Request context:* Sets the `request_headers_var` ContextVar for downstream session-affinity logic.

*Phase 1 – Tool resolution:* `_resolve_tool_for_invocations()` looks up the tool by name, checks RBAC, validates input schema against supplied arguments, and extracts tool/gateway payloads with auth, OAuth config, URL, and headers.

*Phase 2 – Data extraction:* All DB-backed data is extracted to local variables. The DB session is then committed and closed before any HTTP call — preventing connection-pool exhaustion during slow upstream requests.

*OAuth token acquisition:* For OAuth-protected tools, `OAuthManager.get_access_token()` obtains an access token via client_credentials, password, or token-exchange grant, with automatic decryption of stored credentials and up to `max_retries` with exponential backoff. Token exchange retry (`_send_with_token_exchange_retry`) handles 401-driven re-exchange-and-retry.

*Plugin hooks:* CPEX framework plugins intercept the call via `TOOL_PRE_INVOKE` hooks, modifying headers, arguments, or blocking execution.

*SSRF protection:* The target URL is validated by `SecurityValidator.validate_url_for_connection_pinning()` which resolves DNS, checks against blocked networks/hostnames, and pins the connection to the resolved IP. Localhost and private networks are blocked by default.

*HTTP invocation:* Depending on content type (JSON, form-data, multipart), httpx sends the request with connection pinning and timeout. The method defaults to POST (or the tool's `request_type`). Path/query parameter substitution and header/query mapping are applied before the call.

*Direct proxy mode:* For MCP-protocol upstreams, `tools/call` can be forwarded directly via the MCP SDK client session.

*Rate limiting & retries:* Configurable via `retry_max_attempts` (3), `retry_base_delay` with exponential backoff. Rate limiting is enforced at the middleware layer.

*Error mapping:* Tool errors map to specific exception types: `ToolNotFoundError`, `ToolInvocationError`, `ToolTimeoutError`, `PluginViolationError`. Observability spans track start/end, success/failure, and timing.

*Sandboxing:* There is no execution sandbox — tools proxy HTTP requests to upstream servers. SSRF protection and URL validation are the primary safety mechanisms.


Citations: [mcpgateway/services/tool_service.py:5695-5800](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/services/tool_service.py#L5695-L5800) · [mcpgateway/services/tool_service.py:5794-5950](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/services/tool_service.py#L5794-L5950) · [mcpgateway/services/tool_service.py:6092-6300](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/services/tool_service.py#L6092-L6300) · [mcpgateway/services/oauth_manager.py:208-290](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/services/oauth_manager.py#L208-L290) · [mcpgateway/services/oauth_manager.py:546-622](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/services/oauth_manager.py#L546-L622)

### How are data sync, webhooks and triggers implemented? (answered)

**Data sync is primarily event-driven through Redis Pub/Sub, with incremental background sync via the dataplane publisher.**

*Event Service:* `EventService` implements a centralized pub/sub event bus using Redis Pub/Sub for distributed broadcasting across multiple gateway instances, falling back to `asyncio.Queue` for single-node development. Each event channel has a unique name (e.g., `mcpgateway:tool_events`). The `publish_event()` method sends dict payloads. `GatewayService` and `ToolService` each create their own `EventService` instance for lifecycle events (added, updated, removed).

*Dataplane Publisher:* The dataplane publisher periodically exports user configuration (gateways, tools, resources, prompts, virtual hosts) from the database to Redis using `msgpack` serialization. It runs on a configurable interval and is disabled by default. The publisher snaps full state (not incremental cursors) to Redis under the `UserConfig` key.

*Incremental sync:* `ToolService.list_tools()` supports cursor-based pagination (opaque base64 cursor token), enabling clients to page through tools incrementally. There is no built-in incremental cursor tracking for external data sources.

*Webhook ingestion:* A2A push notification configs support registering webhooks for task events. `A2AService.upsert_push_config()` stores webhook URLs with encrypted bearer tokens. The Rust MCP runtime handles push dispatch with AES-GCM decryption of stored webhook credentials.

*Health checks:* `GatewayService` runs periodic health checks at `GW_HEALTH_CHECK_INTERVAL` (300s) with an in-memory failure count tracker for active/inactive gateway management.

*MCP notifications:* The gateway sends `notifications/tools/list_changed` when tool availability changes, enabling clients to refresh their tool list. There is no cron-based scheduler, no incremental cursor for third-party API sync, and no webhook receiver that maps incoming webhooks to tool calls or agent triggers.


Citations: [mcpgateway/services/event_service.py:1-100](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/services/event_service.py#L1-L100) · [mcpgateway/services/dataplane_publisher.py:1-100](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/services/dataplane_publisher.py#L1-L100) · [mcpgateway/services/a2a_service.py:3486-3540](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/services/a2a_service.py#L3486-L3540) · [mcpgateway/transports/streamablehttp_transport.py:4065-4075](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/transports/streamablehttp_transport.py#L4065-L4075)

### How is it self-hosted and what is open vs proprietary? (answered)

**ContextForge is fully self-hostable under the Apache 2.0 license.**

*License:* Apache License 2.0. The entire codebase is open-source — no proprietary components or hosted-cloud dependencies.

*Required services:* The gateway needs a database (SQLite for dev, PostgreSQL for production) and optionally Redis for caching, federation, and distributed event broadcasting. The `docker-compose.yml` file defines the full stack: Nginx (caching reverse proxy), Postgres, Redis, and the gateway application itself. Multiple compose profiles add optional services (SSO/Keycloak, monitoring/Prometheus+Grafana, testing/Locust, TLS, MCP Inspector, web UI).

*What's in the repo:* All source code is in this repository: Core gateway with services, routers, middleware, transports, and plugins; SQLAlchemy ORM models and Alembic migrations; Admin UI (HTMX + Alpine.js); Helm charts for Kubernetes; Rust crates under `crates/mcp_runtime/` for experimental performance-sensitive paths; Full test suite with unit, integration, and live-gateway E2E tests; Documentation including ADRs and deployment guides; Dockerfiles.

*What the hosted cloud provides:* There is no hosted cloud offering. The project is entirely self-contained — you run `docker compose up` (or `make dev` for bare-metal) and have a fully functional gateway. The `client_mode` config flag exists but relates to running as a sidecar, not a cloud dependency.

*Deployment options:* `make dev` for local development (uv + uvicorn reload); `make serve` for production gunicorn; `docker compose up` for containerized stack; Helm charts for Kubernetes. The `.env.example` provides all configuration variables. The `Makefile` orchestrates setup, testing, linting, and builds. The `mcpgateway_ui_airgapped` flag enables use of local CDN assets for airgapped deployments.


Citations: [LICENSE:1-25](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/LICENSE#L1-L25) · [docker-compose.yml:1-80](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/docker-compose.yml#L1-L80) · [mcpgateway/config.py:1-20](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/mcpgateway/config.py#L1-L20) · [pyproject.toml:34-42](https://github.com/IBM/mcp-context-forge/blob/a10c655b9fc04d42d2029a565a25bad059409c77/pyproject.toml#L34-L42)
