Architecture
This page is mainly about one practical question:
If you want to use groundworkers, which layer should you call?
The short answer
- Use MCP tools when you want a remote tool interface.
- Use
app.services.mappingwhen you want higher-level mapping workflows from Python. - Use
app.adapters.*when you want lower-level dependency-shaped operations.
Layers
flowchart TD
A1[Python app] --> S[services/]
A2[MCP client] --> T[tools/]
T --> S
S --> AD[adapters/]
AD --> OG[omop-graph]
AD --> OE[omop-emb]
AD --> DB[(OMOP DB / index)]
What each layer is for
adapters/
Adapters handle dependency-specific work:
- calling
omop-graph - calling
omop-emb - running vocabulary-table queries
- smoothing over backend-specific behavior
If you need exact control over those integrations, this is the layer to use.
services/
Services handle workflow logic that is useful across applications:
- assembling candidate bundles
- combining lexical, graph, and embedding evidence
- building mapping context packets
- resolving simple mapping expressions
- evaluating predicted mappings against references
If you are building a Python application and want to reuse groundworkers logic,
this is the layer you usually want.
tools/
Tools expose the same capabilities over MCP. They mostly do three things:
- validate and clamp inputs
- call a service or adapter method
- return an MCP-friendly result shape
If you are building an MCP client, this is the layer you see.
app.py and server.py
build_application(config)creates the shared Python object graphcreate_server(config)uses that graph and registers MCP tools on top
Why the service layer exists
Without a service layer, a Python application has to choose between two awkward options:
- reimplement the workflow logic itself
- or import MCP tool wrappers that are shaped for transport, not for library use
services/ gives Python consumers a cleaner place to start.
Request flow
sequenceDiagram
participant C as Consumer
participant T as tools/
participant S as services/
participant A as adapters/
participant D as OMOP deps
alt MCP
C->>T: call tool
T->>S: service method
else Direct Python
C->>S: service method
end
S->>A: dependency calls
A->>D: query
D-->>A: raw results
A-->>S: normalized adapter results
S-->>T: domain result
S-->>C: domain result
T-->>C: MCP-safe dict
Composition
flowchart LR
CFG[AppConfig] --> APP[build_application]
APP --> ADP[Adapters]
APP --> SRV[Services]
SRV --> MAP[MappingService]
SERVER[create_server] --> APP
SERVER --> MCP[registered MCP tools]
Which layer should you pick?
| If you need... | Use... |
|---|---|
| Remote tool calls, discovery, or agent interoperability | MCP tools |
| High-level mapping workflows from Python | app.services.mapping |
| Exact lower-level OMOP or embedding operations | app.adapters.* |
Practical rule
- If the code coordinates multiple data sources or encodes mapping policy, put it in
services/. - If the code mainly deals with dependency APIs or database details, put it in
adapters/. - If the code mainly validates inputs and shapes transport responses, put it in
tools/.