# Architecture
> One-page overview of MCP Gateway internals.
## System Diagram
```
┌─────────────────────────────────────────────────┐
│ MCP Gateway (:39400) │
│ │
AI Client │ ┌──────────────┐ ┌───────────────────┐ │
(Claude, Cursor, etc.) │ │ HTTP Router │───>│ Meta-MCP │ │
| │ │ (axum) │ │ 4 meta-tools: │ │
| │ │ │ │ - list_servers │ │
POST /mcp │ │ /mcp ─────┼───>│ - list_tools │ │
──────────────────────> │ /mcp/{id}───┼──┐ │ - search_tools │ │
| │ │ /health ─────┼─┐│ │ - invoke │ │
GET /mcp (SSE) │ └──────────────┘ ││ └───────┬───────────┘ │
──────────────────────> ││ │ │
| │ ┌─────────────┐ ││ ┌───────v───────────┐ │
| │ │ Streaming │ ││ │ Backend Registry │ │
<──────────────│──│ (SSE Mux) │ ││ │ + Capability Sys │ │
notifications │ └─────────────┘ ││ └───────┬───────────┘ │
│ ││ │ │
│ ┌────────────────┘│ ┌───────v───────────┐ │
│ │ Health │ │ Failsafes │ │
│ │ Endpoint │ │ ┌─────────────┐ │ │
│ └────────────────┘│ │ │Circuit Break│ │ │
│ │ │ │Retry+Backoff│ │ │
│ ┌─────────────────┘ │ │Rate Limiter │ │ │
│ │ Direct Access │ └─────────────┘ │ │
│ │ /mcp/{backend} └───────┬───────────┘ │
│ └────────┬──────────────────┤ │
│ │ │ │
│ ┌────────v──────────────────v──────────┐ │
│ │ Transport Layer │ │
│ │ ┌─────────┐ ┌────────┐ ┌─────────┐ │ │
│ │ │ stdio │ │ HTTP │ │ SSE │ │ │
│ │ │(spawn) │ │(POST) │ │(stream) │ │ │
│ │ └────┬────┘ └───┬────┘ └────┬────┘ │ │
│ └───────┼──────────┼───────────┼──────┘ │
│ │ │ │ │
│ ┌───────v──┐ ┌─────v────┐ ┌───v────────┐ │
│ │ Response │ │ OAuth │ │ Secrets │ │
│ │ Cache │ │ Client │ │ (Keychain) │ │
│ └──────────┘ └──────────┘ └────────────┘ │
└─────────────────────────────────────────────────┘
| | |
┌──────────────v──┐ ┌─────v────┐ ┌───v──────┐
│ MCP Server A │ │ MCP Srv B│ │ REST API │
│ (stdio) │ │ (HTTP) │ │ (YAML) │
└─────────────────┘ └──────────┘ └──────────┘
```
## Module Map
Source files live in `src/`. Each module is split to 800 LOC or fewer.
| Module | File(s) | Responsibility |
|--------|---------|----------------|
| **gateway** | `gateway/` | Core server: axum router, request handling, session management |
| **backend** | `backend/` | Backend lifecycle: spawn, connect, health tracking, tool caching |
| **transport** | `transport/` | Wire protocols: stdio subprocess I/O, HTTP client, SSE streaming |
| **protocol** | `protocol/` | MCP JSON-RPC types, version negotiation (2024-10-07 through 2025-11-25) |
| **config** | `config.rs` | YAML + env config loading via figment, env file support, `${VAR}` expansion |
| **capability** | `capability/` | REST-to-MCP bridge: load YAML definitions, execute HTTP calls, hot-reload |
| **cache** | `cache.rs` | Response cache with TTL eviction and per-capability overrides |
| **failsafe** | `failsafe/` | Circuit breaker (closed/open/half-open), retry with exponential backoff, token-bucket rate limiter |
| **oauth** | `oauth/` | OAuth 2.0 client: authorization code flow, token refresh, dynamic registration |
| **discovery** | `discovery/` | Meta-MCP logic: list, search (ranked), invoke routing |
| **ranking** | `ranking/` | Usage-weighted search ranking, frequency persistence |
| **registry** | `registry/` | Capability registry: install, search, list from local and remote sources |
| **secrets** | `secrets/` | Keychain integration (macOS/Linux), env var resolution, session caching |
| **stats** | `stats/` | Invocation counters, cache hit rates, token savings estimation |
| **validator** | `validator/` | Input validation for tool arguments against JSON Schema |
| **cli** | `cli/` | CLI argument parsing: `serve`, `stats`, `cap` subcommands |
| **error** | `error.rs` | Unified error types with `thiserror` |
## Data Flow
### Tool Discovery (Meta-MCP)
```
1. Client sends: POST /mcp { "method": "tools/call",
"params": { "name": "gateway_search_tools",
"arguments": { "query": "weather" } } }
2. Gateway: discovery::search() -> iterate all backends -> match by name/description
3. Ranking: ranking::rank() -> sort by usage frequency
(persisted in ~/.mcp-gateway/usage.json)
4. Response: Return ranked matches with server, tool name, description, input schema
```
### Tool Invocation
```
1. Client sends: POST /mcp { "method": "tools/call",
"params": { "name": "gateway_invoke",
"arguments": { "server": "X", "tool": "Y", ... } } }
2. Gateway: Resolve backend "X" from registry
3. Cache check: cache::get(backend, tool, args) -> if hit, return cached response
4. Failsafe: circuit_breaker::check() -> retry::execute() -> rate_limiter::acquire()
5. Transport: Route to backend via stdio / HTTP / SSE
6. Cache store: cache::set(response, ttl)
7. Stats: stats::record_invocation()
8. Response: Return tool result to client
```
### Capability Execution (REST APIs)
```
1. Invocation targets a capability backend (e.g., "fulcrum")
2. capability::execute() loads the YAML definition
3. Build HTTP request: base_url + path, substitute {params},
resolve {env.VAR} / {keychain.name}
4. Send HTTP request via reqwest
5. Extract response via response_path (optional JSON pointer)
6. Return as MCP tool result
```
## Key Design Decisions
| Decision | Choice | Rationale |
|----------|--------|-----------|
| Language | Rust | Sub-10ms startup, <2ms routing overhead, zero-cost abstractions |
| HTTP framework | axum | Tokio-native, tower middleware, streaming support |
| Config format | YAML + figment | Human-readable, env override, dotenv loading |
| Default port | 39400 | Above ephemeral range, unlikely to conflict |
| Meta-MCP default | ON | Core value prop: 97% token savings |
| Capability format | Custom YAML ("fulcrum") | Simpler than OpenAPI for single-endpoint definitions |
| Cache | In-memory HashMap | Local proxy -- no need for Redis; bounded by max_entries |
| Search ranking | Usage frequency | Simple, effective, persisted, no ML overhead |
## Security Model
- **Auth** is disabled by default for local-only use. Enable for networked deployments.
- **Bearer tokens** support auto-generation, env vars, and literals.
- **API keys** support per-client rate limits and backend restrictions.
- **Secrets** use OS keychain (macOS Keychain, Linux secret-service) -- never stored in config files.
- **OAuth** supports per-backend configuration with dynamic client registration.
- **`/health`** is always public (configurable via `auth.public_paths`).