Skip to main content
This guide covers reading compiled patient data from your backend or agent runtime (after logs have been ingested) via the MCP Patient State server, the Python SDK state-read APIs, or REST with the same auth rules.

State views vs state modules (what to call, what you get)

Downstream workflows usually need either rendered views (summaries your templates generate) or raw module payloads (structured regions of patient state).
ConceptWhat it isHow you access it (MCP)What it returns
State viewA generated snapshot for a view key (view_type, often aligned with template summary_type): blocks of text/JSON produced on a cadence from templates (e.g. symptom snapshot, medication snapshot).get_view, list_views_and_blocks, get_view_block, get_view_recent_eventsMarkdown (human-readable) or raw JSON: block content, segments (week / long_term / temp), provenance-style refs depending on tool. Use this when an agent or workflow should consume the same narrative or structured blocks your product shows in the Console.
Stable modulesSlow-changing facts: demographics, condition/diagnosis, medications, preferences, etc.get_stable_data (optional modules filter).Module-keyed payloads (structured fields), not free-form summaries. Ideal for deterministic steps: eligibility, dosing context, identifiers.
Event-state modulesRolling, event-driven regions (symptoms, labs, adherence, etc.).list_event_state_modules, get_event_state_module.Module-typed rolling state (e.g. lists of symptoms, recent events): structured payloads aligned with Patient State module keys.
Logs & eventsRaw ingestion history and event audit (get_events).get_logs, get_events.Rows you can filter by time, log type, or trace for lineage.
For a workflow that must branch on “what the clinician sees in the chart summary,” start from views (get_view / blocks). For rules that depend on structured facts (medication list, diagnosis codes), use stable or event-state modules. Combine both when an agent needs narrative plus verifiable fields.

Choose an access path

ApproachTypical use
MCP Patient StateRuntimes that discover tools (get_stable_data, get_view, get_event_state_module, …) over JSON-RPC.
Python SDK Patient State readBackend jobs already using olira; same semantics as MCP with typed helpers.
REST APINon-Python services; mirror auth and scopes from the SDK.

Endpoint and credentials (MCP)

Your organization’s MCP URL is in Console → Settings → MCP:
https://mcp-patient-state.prod.olira.ai/mcp
Send Authorization: Bearer <token> on every request:
  1. Opaque API key (YOUR_API_KEY) with mcp:patient-state: server-side agents and automation.
  2. Short-lived JWT from POST /auth/token (exchange API key).
  3. Patient Token: see below; best practice when the agent must only ever see one patient.
  4. Auth0 JWT from olira login: interactive developer sessions.

Agent frameworks

OpenAI Agents SDK: attach the MCP base URL and headers so tools load from Olira:
from agents import Agent

agent = Agent(
    name="clinical-assistant",
    mcp_servers=[{
        "url": "https://mcp-patient-state.prod.olira.ai/mcp",
        "headers": {"Authorization": "Bearer YOUR_API_KEY"},
    }],
    instructions=(
        "Use get_stable_data and get_view before clinical answers. "
        "Do not invent facts."
    ),
)
LangChain / LangGraph: point an MCP adapter at the same URL with streamable_http and the same Authorization header.

Using MCP resources

Resources carry patient state (and related organization) context as URI-addressable snapshots (constitution-backed sections, population-view material, or other canonical blobs) over the same MCP base URL as tools. Design choice: decide whether each resource’s content should land directly in the prompt or stay behind runtime fetch.
  • Inject into the prompt: Call resources/list, pick the URIs that matter for this workflow, then resources/read before the model runs and paste the returned text into your system prompt, developer message, or user preamble. Best when you want deterministic, fixed context every turn and accept the token cost up front.
  • Expose at runtime: Register resources with your agent runtime (when it supports MCP resources) so the model can request resources/read when it chooses, similar to tools. Best when context is large or optional and you want the agent to pull only what it needs, trading a round-trip for a smaller default context.
Flow:
  1. resources/list: JSON-RPC method: resources/list, params: {}. Inspect each entry’s uri and name to decide what to fetch.
  2. resources/templates/list: Optional; lists resource templates when the server advertises them (resourceTemplates in the result).
  3. resources/read: Pass params.uri from the list response. The result includes contents with text (often JSON) for that URI.
Provider and organization API keys may see additional organization-scoped resources merged into resources/list; behavior depends on auth context. Same Authorization header as tools. If your stack does not surface MCP resources in the agent UI, call these JSON-RPC methods from your integration layer and inject or bridge the text yourself.

Using MCP prompts

Prompts are reusable templates for different use cases (for example missing-data elicitation or provider alerts). They guide behavior in two ways:
  • In-session: Resolve with prompts/get and apply the messages as system / developer instructions, or prepend to the user turn, so the same agent follows Olira’s intended tone and structure.
  • Across channels: The resolved text can be sent directly to other parties or surfaces: a patient-facing message to probe for missing information, a provider alert, or any template meant to be delivered as-is outside the main chat. You still obtain the canonical wording via prompts/listprompts/get so it stays aligned with what Olira ships.
Typical flow:
  1. prompts/list: Discover name, description, and expected arguments schema for each prompt.
  2. prompts/get: Pass params.name and, when required, params.arguments (key/value object matching that prompt’s schema).
Enable prompts/list and prompts/get when your framework supports them. If the runtime only exposes tools, call these methods over the same POST /mcp JSON-RPC transport yourself, or copy template text into your own prompts until wiring is complete; see MCP → Resources and prompts for method names.

Querying logs (filter / project / aggregate)

get_logs() is a simple time-cursor fetch. When you need richer filtering, field projection, or server-side aggregation, use the log query builder (sdk:state-read scope).

Single-patient queries

import olira
from olira import F

olira.init(api_key="YOUR_API_KEY")

# Filter + order + limit: returns LogQueryResult
rows = (
    olira.logs("PATIENT_ID")
        .eq("type", "symptom_report")
        .gt("payload.score", 4)
        .ilike("payload.metric_type", "%pain%")
        .order("timestamp", desc=True)
        .limit(25)
        .execute()
)
for row in rows:
    print(row["timestamp"], row["payload"])

# IN filter
rows = (
    olira.logs("PATIENT_ID")
        .in_("type", ["symptom_report", "mood_report"])
        .limit(50)
        .execute()
)

# OR boolean group via F()
rows = (
    olira.logs("PATIENT_ID")
        .or_(F("payload.score").gt(7), F("type").eq("mood_report"))
        .limit(10)
        .execute()
)

# Projection: rows become dicts with only the selected keys
rows = (
    olira.logs("PATIENT_ID")
        .eq("type", "health_metric")
        .select("timestamp", score="payload.score")   # alias=path kwarg
        .limit(10)
        .execute()
)

# Count only: no rows returned
n = olira.logs("PATIENT_ID").eq("type", "symptom_report").count()

# Aggregation: group by type, count per type, average score
agg = (
    olira.logs("PATIENT_ID")
        .group_by("type")
        .count_agg("n")
        .avg("payload.score", "avg_score")
        .execute()
)

Organization / cohort queries

population_logs() posts to POST /v1/state/logs/query (no patient_id in the path). Omit patient_ids to span the whole org; pass a list for a cohort.
# Whole org: last 50 health_metric events
rows = (
    olira.population_logs()
        .eq("type", "health_metric")
        .order("timestamp", desc=True)
        .limit(50)
        .execute()
)

# Explicit cohort
rows = (
    olira.population_logs(patient_ids=["p_1", "p_2"])
        .gt("payload.score", 6)
        .limit(100)
        .execute()
)

# Org-wide event counts by type
agg = (
    olira.population_logs()
        .group_by("type")
        .count_agg("n")
        .execute()
)

Terminals at a glance

TerminalReturnsBehavior
.execute()LogQueryResultAll matching rows (or aggregated rows)
.count()intServer-side count only; no rows returned
.single()dictExactly one row or ValidationError
.maybe_single()dict | NoneZero or one row; ValidationError if > 1
.as_logs()list[LogEntry]Parse rows into typed LogEntry; valid when no .select() was used
LogQueryResult is iterable, indexable, and len()-able. The async client (AsyncOliraClient) returns AsyncLogQuery with identical interface and async def terminals.

Allowed field roots

The server only allows type, timestamp, trace, and payload as filter/projection root paths. Any other root (e.g. id, user_id) returns HTTP 422, which the SDK surfaces as ValidationError.

Patient-scoped tokens (single-patient agents)

Best practice: when an agent or client must only access one patient’s data (for example a patient-facing assistant), mint a Patient Token on your server and pass token.access_token as the Bearer credential. The token binds to that patient_id; callers cannot substitute another patient, which reduces cross-context contamination (no accidental mixing of patients in one session).
import olira

olira.init(api_key="YOUR_API_KEY")  # API key must include sdk:patient-token
token = olira.get_patient_token(patient_id="<patient-id>")
# Client sends: Authorization: Bearer <token.access_token>
# Omit patient_id from MCP tool arguments when using a Patient Token
Fields and expiry are documented under Patient token in the Python SDK reference. For provider or backend agents that legitimately need to switch patients, use an API key with mcp:patient-state and pass patient_id explicitly per call instead.

Prompting

Use only the tools your workflow needs. MCP exposes many tools (get_stable_data, get_view, get_event_state_module, get_logs, …). Registering or describing the smallest set that satisfies each agent or step keeps context windows smaller, cuts redundant round-trips, and usually improves latency and cost. Prefer narrow calls: e.g. get_stable_data with a modules filter instead of pulling every stable module when you only need medications; request raw format when the model must parse JSON, markdown when a short human-readable summary is enough. For resources and prompts, follow Using MCP resources and Using MCP prompts above; they use the same MCP endpoint as tools with resources/list, resources/read, prompts/list, and prompts/get. Match tool choice to the task (see the State views vs state modules table above): narrative or chart-aligned answers → views (get_view / blocks); rules, scoring, or integrations → stable or event-state modules; audits or lineage → logs / events (get_events). Avoid adding unrelated state to the prompt; retrieve in steps and only escalate to broader tools if the first pass is insufficient. Scope credentials the same way: Patient Tokens for a single patient session; broader API keys only where the workflow must switch patients. Tighter scope reduces risk of cross-patient mistakes and keeps prompts aligned with one person’s data. Choose get_view when you need template-driven output; get_stable_data / get_event_state_module when you need authoritative structured slices for logic or validation.