# Where the AI Gateway and MCP Gateway fit — target architecture
> Cat-3 (Tool-Level Access Control & Policy) deliverable: the V4 seam map, extended into a
> concrete integration design. **Goal:** place an **AI Gateway** (LLM/model proxy) and an
> **MCP Gateway** (Arcade) into the existing `Agent Platform → Tool Hub → Automation Hub`
> stack **without major work on the Tool Hub or Automation Hub applications.**
>
> Grounded in: `servicetitan/tool-hub` @ master, `servicetitan/automation-hub` @ master,
> arcade-eval LIVE-POC (all read 2026-06-22).
## The thesis in one paragraph
Both Tool Hub and Automation Hub were built with the exact seams this needs, and neither does
the one thing Arcade is for. **Tool Hub** already has a data-driven `IExecutionAdapter` registry
with a **`mcp_proxy` SourceType named in the contract** — adding Arcade is the *intended*
extension, not surgery. **Automation Hub** explicitly scopes per-user OAuth / connector
infrastructure as a **non-goal** and names per-user OAuth brokering as the gap an external
platform fills. So the minimal-work design is: **(1) AI Gateway = pure configuration** (repoint
the model/embedding base URLs every component already calls); **(2) MCP Gateway (Arcade) = one
adapter pair behind Tool Hub's existing `mcp_proxy` seam**, with all per-user third-party OAuth
living *inside Arcade* (so Tool Hub needs no credential vault and no new OBO authority).
Automation Hub is untouched. Tool Hub remains the single authority/policy/audit plane over
**both** execution backends.
## Design constraints — what "no major work" means here
| App | Allowed | Explicitly avoided |
|---|---|---|
| **Tool Hub** | Implement one `ICatalogSource` + one `IExecutionAdapter` (`type='arcade'`/`mcp_proxy`) — the designed extension point. Config: model base URLs → AI Gateway. | No change to discovery hot path, permission model, idempotency, audit, or the OBO core. Per-user SaaS OAuth is **not** added to Tool Hub. |
| **Automation Hub** | Nothing. | No new executor, no connector framework, no OAuth store. AH stays one of Tool Hub's catalog sources. |
| **Agent Platform** | Config: inference endpoint → AI Gateway; identity = per-user Entra SSO. | No re-architecture. |
## 1. Target topology
```mermaid
flowchart TB
subgraph IDP["Identity"]
Entra["Entra ID SSO
per-user login / IUM"]
end
subgraph AGENT["Agent plane"]
Agent["LLM Agent
(AgentOS / sidecar)"]
end
subgraph GW["Gateways — inserted, no app surgery"]
AIGW["AI Gateway
LiteLLM-class LLM/model proxy
keys · routing · rate-limit · cost · audit"]
MCPGW["MCP Gateway — Arcade
MCP transport + per-user OAuth broker"]
end
subgraph TH["Tool Hub — authority / data plane (core UNCHANGED)"]
MCPHost["MCP surface
search_tools · get_tool_details · execute_tool"]
Policy["Stage0-6: permission re-check ·
idempotency · rate-limit · audit/outbox"]
Reg["IExecutionAdapter registry
(catalog_source.type → adapter)"]
AHAdapter["automation_hub adapter
(exists)"]
ArcAdapter["arcade adapter
(NEW — mcp_proxy seam)"]
end
subgraph AH["Automation Hub — UNCHANGED"]
AHCat["Catalog API
GET /api/catalog/actions (ETag, cursor)"]
AHExec["POST /actions/{id}/execute
st.automation_hub.execute"]
AHDown["ST Core API v2 / Internal API
IUM bot-user impersonation"]
end
subgraph EXT["Third-party + custom capability"]
SaaS["GitHub · Slack · Google · ..."]
Custom["Custom / partner MCP servers"]
end
subgraph MODELS["Model providers"]
LLMs["Anthropic · Voyage · OpenAI · internal"]
end
Entra -. "per-user token" .-> Agent
Agent -- "inference" --> AIGW
Agent -- "MCP meta-tools (carries user identity)" --> MCPHost
MCPHost --> Policy --> Reg
Reg --> AHAdapter
Reg --> ArcAdapter
AHAdapter -- "catalog sync" --> AHCat
AHAdapter -- "IUM OBO execute" --> AHExec
AHExec --> AHDown
ArcAdapter -- "MCP tools/call + user identity" --> MCPGW
MCPGW -- "resolve per-user OAuth token" --> SaaS
MCPGW --> Custom
AIGW --> LLMs
TH -. "enrichment · query rewrite · embeddings · rerank" .-> AIGW
classDef new fill:#ffe8cc,stroke:#e8860c,stroke-width:2px,color:#000;
class AIGW,MCPGW,ArcAdapter new;
```
Highlighted (orange) = the only new pieces: the **AI Gateway**, the **MCP Gateway (Arcade)**,
and the thin **arcade adapter** that slots into Tool Hub's existing registry.
## 2. Two execution paths through one authority plane
Tool Hub stays the single point of policy, idempotency, and audit. The *only* difference
between an internal action and a third-party action is which adapter the registry resolves — and
that the Arcade path adds per-user OAuth that neither Tool Hub nor AH can do today.
```mermaid
sequenceDiagram
autonumber
participant U as User / Agent
participant TH as Tool Hub
participant AR as Arcade (MCP GW)
participant SaaS as Third-party SaaS
participant AH as Automation Hub
participant ST as ServiceTitan APIs
Note over U,ST: A. Internal ServiceTitan action — existing path, unchanged
U->>TH: execute_tool(automation_hub://crm.create_job, input)
TH->>TH: permission re-check · idempotency · rate-limit · audit
TH->>AH: POST /actions/{id}/execute (IUM OBO, bot-user)
AH->>ST: call Core / Internal API
ST-->>AH: result
AH-->>TH: ActionExecutionResult
TH-->>U: CallToolResult
Note over U,SaaS: B. Third-party action — NEW path via Arcade
U->>TH: execute_tool(arcade://github.create_issue, input)
TH->>TH: SAME permission re-check · idempotency · rate-limit · audit
TH->>AR: MCP tools/call + user identity (Entra SSO)
AR->>AR: resolve this user's stored GitHub OAuth token
AR->>SaaS: call GitHub API AS THE USER
SaaS-->>AR: result
AR-->>TH: MCP CallToolResult
TH-->>U: CallToolResult
```
The critical property: **the per-user OAuth complexity lives entirely in Arcade.** Tool Hub only
authenticates the *user* to Arcade and passes identity — so it needs no third-party token vault
and no change to its Entra/IUM OBO core (the arcade adapter sets `RequiresObo=false` for the
third-party-OAuth case; Arcade does the brokering). That is what keeps this out of "major work."
## 3. The AI Gateway is a configuration change, not a build
Every model/embedding call in the stack already goes through a pinned SDK with a configurable
endpoint. Point those endpoints at one AI Gateway and you get unified keys, routing, rate-limit,
cost control, and audit across all AI traffic — with zero application code change.
```mermaid
flowchart LR
A["Agent inference"] --> AIGW
B["Tool Hub — enrichment (Claude)"] --> AIGW
C["Tool Hub — query rewrite (Claude Haiku)"] --> AIGW
D["Tool Hub — embeddings + rerank (Voyage)"] --> AIGW
E["Arcade engine — LLM / embeddings"] --> AIGW
AIGW["AI Gateway (LiteLLM-class)
keys · routing · rate-limit · cost · audit"] --> P["Anthropic · Voyage · OpenAI · internal"]
classDef new fill:#ffe8cc,stroke:#e8860c,stroke-width:2px,color:#000;
class AIGW new;
```
The Arcade POC already routes its engine LLM + embeddings through in-cluster LiteLLM
(LIVE-POC), so this consolidates an existing pattern rather than inventing one.
## 4. Change surface — component by component
| Component | Role in target | Change required | Evidence it's minimal |
|---|---|---|---|
| **AI Gateway** (LiteLLM-class) | Single egress for all LLM/embedding traffic | **Config only** — repoint base URLs | Tool Hub model providers are DI seams with configurable endpoints (`IEmbeddingProvider`, `IEnrichmentProvider`, `IQueryRewriter`, `IReranker`); Arcade already uses in-cluster LiteLLM |
| **MCP Gateway (Arcade)** | MCP transport + **per-user OAuth broker** for SaaS / custom MCP | **Deploy + register** as Tool Hub catalog source | Arcade is a running self-hosted POC (`api.arcade.st.dev`) |
| **Tool Hub** | Authority: discovery, policy, idempotency, audit over both backends | **One adapter pair** in the `mcp_proxy` slot + endpoint config | `ICatalogSource` docstring already names `"mcp_proxy"`; adapter selection is `catalog_source.type → registry`, dispatch site unchanged |
| **Automation Hub** | One of Tool Hub's catalog sources (internal ST actions) | **None** | AH's catalog + `/actions/{id}/execute` contract already matches Tool Hub 1:1 (same 4 execution modes, JSON-Schema I/O, `namespace:name@semver`) |
| **Agent Platform** | Caller | **Config** — inference → AI Gateway; identity → per-user Entra SSO | — |
## 5. Why this is the right seam (and the one open decision)
- **It fills a real, documented gap.** Per-user third-party OAuth is explicitly absent from
*both* apps: AH lists "OAuth token management / connector marketplace" as a **V1 non-goal** and
its own platform research names per-user OAuth brokering as what an external platform must add;
Tool Hub's downstream auth is Entra/IUM-only. Arcade is precisely that missing layer.
- **It uses the designed extension point.** Tool Hub's `mcp_proxy` SourceType and data-driven
adapter registry exist *for this*. No core path changes.
- **It preserves the authority model (cat-3 criterion 5).** Tool Hub remains the single Engine
for permission re-check, idempotency, rate-limit, and audit over *both* AH and Arcade calls —
so the policy/enforcement story is unchanged and now covers third-party tools too.
- **One decision to confirm with Platform (chump/tahmad):** Tool Hub's ADR-009 currently intends
partner/MCP capabilities to arrive *through AH as actions*. Routing Arcade **direct into Tool
Hub** as a peer catalog source is a conscious deviation (ADR-009 even lists "BYO MCP outside
AH's onboarding flow" as a trigger to reconsider). The recommendation here is the direct path,
because AH has no plugin model and explicitly defers third-party connectivity — so going
through AH would push *more* net-new work into AH, violating the "no major work" constraint.
## Evidence index
- **Tool Hub:** `src/ToolHub.Contracts/Catalog/ICatalogSource.cs` (`mcp_proxy` named);
`src/ToolHub.Contracts/Execution/IExecutionAdapter.cs` (`RequiresObo`, `GetOboAuthority`);
`src/ToolHub.Execution/Dispatch/ExecutionAdapterRegistry.cs` (data-driven dispatch);
`Stage3_OboAcquisitionStage.cs` (Entra/IUM-only OBO); ADR-009, ADR-007.
Full seam map: `architecture/toolhub-arcade-integration.md` (outer repo).
- **Automation Hub:** `src/server/Host.Api/Controllers/ActionExecutionController.cs`
(`POST /actions/{id}/execute`); `Host.CatalogApi/Controllers/CatalogActionsController.cs`
(catalog sync contract); `Domain/Catalog/Actions/DownstreamApiAuthType.cs`
(`{ApiAccessToken, TokenServer, None}` — no per-user OAuth);
`crap/blueprint/system/context/v1-roadmap.md` (external integration = non-goal);
`docs/research/platform-selection/paragon.md` (per-user OAuth named as the external gap).
- **Arcade POC:** arcade-eval `LIVE-POC.md` (self-hosted, Entra IdP, in-cluster LiteLLM);
`criteria-section-3.md` (enforcement-at-Engine + bypass findings).