MCP is how agents
see the system.

Every module exposes its capabilities as typed, discoverable tools via Model Context Protocol. Agents call the same interfaces the system calls itself — no translation layer, no API shim.

  • 1

    Typed parameters in, typed responses out

    Every MCP tool is a Pydantic model pair. Agents know the exact shape of their input and output. No stringly-typed APIs, no guessing at field names.

  • 2

    Read-only by design

    MCP is the query path. All mutations go through the event bus — a single, auditable write path. An agent cannot accidentally modify state through a query.

  • 3

    Runtime introspection

    Every module server exposes introspect_tools() and introspect_events(). Agents discover what a module can do without external docs.

trades/mcp/server.py
# Every tool: typed params → typed response → lineage

class QueryTradeParams(BaseModel): trade_id: str
class TradeDetail(BaseModel): trade_id: str status: str asset_class: str counterparty: str npv: PresentValue | None
# Handler signature — lineage is explicit
async def handler( params: QueryTradeParams, lineage: LineageContext, ) → tuple[TradeDetail, LineageContext]: trade = store.get_trade(params.trade_id) return trade, lineage.extend(...)
AI Agent
or Frontend
MCP query
Gateway
FastAPI
execute_tool()
Module MCP
typed handler
read
Projection
materialised view
derived from
Event Store
source of truth
substrate/lineage.py
# Not middleware. Not a decorator. A parameter.

async def calculate_npv( trade: Trade, market_env: MarketEnvironment, valuation_date: BusinessDate, lineage: LineageContext, # ← explicit ) → tuple[PresentValue, LineageContext]:
npv = discount_cashflows(...)
updated = lineage.extend( module="valuation", operation="calculate_npv", computation_type=DETERMINISTIC, inputs_hash=hash(trade, market_env), )
return npv, updated

Lineage is a parameter,
not an afterthought.

Every function that transforms data takes a LineageContext and returns an updated one. The provenance chain is visible in every function signature, impossible to accidentally omit.

  • Structural, not bolted on

    Lineage isn't logging you add later. It's a required function parameter — code that doesn't propagate it won't compile.

  • Every record captures context

    Module, operation, timestamp, computation type, confidence score, inputs hash, config version. The chain is a complete audit trail.

  • Branches naturally

    One input can produce multiple outputs, each extending the lineage independently. Fan-out computations like batch valuation preserve full traceability per trade.

Every output tells you
how it was produced.

Three computation types. Every value in the system carries one. Downstream consumers make fitness-for-purpose decisions based on the tag — not guesswork.

D

Deterministic

Mathematically exact. Same inputs always produce the same output. NPV calculations, day count fractions, payment schedule generation.

confidence: 1.0
H

Heuristic

Rules-based with judgment. Pattern matching, threshold detection, counterparty name fuzzy matching. Reliable but not mathematical.

confidence: 0.7 – 0.95
A

Agent-Assisted

LLM in the processing loop. Intent resolution from natural language, unstructured document parsing, trade entry from plain English.

confidence: 0.5 – 0.9
substrate/computation.py — type gate
# Gates enforce consumption policy at the infrastructure level

@computation_type_gate( accepted={DETERMINISTIC}, # only exact values warn_on={HEURISTIC}, # log but allow # AGENT_ASSISTED → rejected automatically )
async def handle_margin_call(event: EventEnvelope): # This handler will never see agent-assisted values # for margin calls. Enforced by the substrate, not # by hoping the developer remembers to check. ...

Invalid states can't
be represented.

Semantic types encode business rules at construction time. You don't detect a currency mismatch downstream — you can't construct one. Agents reason about types structurally, not by reading docs.

$
CurrencyCode
ISO 4217 validated against 67-currency whitelist at construction
N
NotionalAmount
Amount + currency. Adding USD + EUR is a type error, not a bug
V
PresentValue
Carries computation type + confidence. Consumers know what to trust
T
TradeId
SCION-YYYYMMDD-NNNNNN format enforced by regex at construction
X
FxRate
Base/quote pair with type-safe convert() and invert() operations
D
BusinessDate
Logical trading day, not a timestamp. Distinct from calendar date
substrate/types.py — dimensional safety
# This is a type error. Not a runtime bug.

usd = NotionalAmount( amount=50_000_000, currency=CurrencyCode("USD"), )
eur = NotionalAmount( amount=40_000_000, currency=CurrencyCode("EUR"), )

total = usd + eur # → CurrencyMismatchError

# You must convert explicitly:
fx = FxRate("EUR", "USD", 1.085)
total = usd + fx.convert(eur) # → $93,400,000 USD
GET /api/ai/explain — response
{ "entity_id": "SCION-20260512-000001", "summary": "NPV computed through 4 steps,
  originating from gateway.book_trade,
  most recently processed by
  valuation.calculate_npv."
,
"computation_type": "deterministic", "overall_confidence": 1.0, "computation_steps": [ { "module": "gateway", "op": "book_trade" }, { "module": "trades", "op": "capture_trade" }, { "module": "market_data", "op": "get_env" }, { "module": "valuation", "op": "calculate_npv" } ], "data_sources": ["SEED-SNAPSHOT-001"] }

Every value answers
“why?”

The ExplanationRenderer traverses the lineage chain and produces a structured response — no LLM call required. Explanations are deterministic, auditable, and instant.

  • Deterministic rendering

    Explanations are derived from the lineage chain itself. No LLM interprets the result — the computation steps are the explanation.

  • ExplanationNode trees

    Recursive tree of factors: name, value, weight, children. An agent can ask "why this NPV?" and drill into each contributing factor.

  • Explainable protocol

    Any computation result that implements Explainable exposes a structured explain() method. Standard interface, no special cases.

Three ways in.
One consistent substrate.

Natural language, structured API, or direct MCP tool call. All three paths flow through the same type system, lineage propagation, and computation tagging.

Natural language query

Ask "show me all live EUR swaps" and get a structured grid response. The AI service parses intent, dispatches to MCP tools, and returns typed results.

POST /api/ai/query

Trade entry assist

"Book a 5y pay-fixed IRS, 50M USD against Goldman at 4.2%". Gemini extracts structured fields, validates against live reference data, and pre-fills the form.

POST /api/ai/assist

Lineage explanation

"Why did this trade's NPV change?" The explainability engine traces the lineage chain and returns the computation steps, data sources, and confidence level.

POST /api/ai/explain

Pluggable LLM integration

Gemini for structured extraction. Claude for full tool-use reasoning. Keyword fallback when no API key is configured. The system works at every level of AI capability — LLMs enhance, they don't enable.

Hub trade record

The single unified materialised view of a trade's complete state. Agents query the hub once instead of stitching from five modules. Identity, parties, legs, risk data, exercise terms — one query.

Data that explains itself
doesn't need a chatbot.

Every piece of data in Scion can answer three questions: where did you come from? (lineage), how were you computed? (computation type), and why this answer? (explainability). The LLM integration layers on top as an interaction surface — not a dependency.

See it live