occulytics
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@occulyticsWhat are the top five operators by investment share and how many facilities do they run?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Occulytics MCP Server
An MCP server that lets an AI assistant answer portfolio questions for a healthcare-REIT asset-management team (Omega Healthcare Investors), grounded in two public sources: Omega's SEC 10-K filings and the CMS Nursing Home Provider Information file.
The design goal, per the brief: the server must be able to say an answer is complete, uncertain, or unsupported — and why — instead of producing a confident number nothing supports. Every tool returns deterministic data inside an envelope that carries a computed status, caveats, and provenance.
Quickstart
Everything runs offline — the data artifacts are committed.
npm install
npm run build
npm test # 41 tests: curated-data checksums, domain units, full e2e over MCPTry it in a UI (MCP Inspector opens in your browser):
npm run inspectConnect to Claude Code: a project-scoped .mcp.json is included — open
this repo in Claude Code after npm run build and the occulytics server is available.
Or register it globally:
claude mcp add occulytics -- node /absolute/path/to/occulytics-mcp/dist/src/server/index.jsConnect to Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"occulytics": {
"command": "node",
"args": ["/absolute/path/to/occulytics-mcp/dist/src/server/index.js"]
}
}
}Pre-demo check that the compiled server works over real stdio: npm run smoke.
To refresh data from the live sources: npm run ingest (see Data pipeline).
Related MCP server: Medical Billing MCP
What you can ask
The five target questions, and what the server actually does:
Question | Answer path | Honest outcome |
Top five operators by % of investment, and how many facilities each runs? |
| Partial by design: Omega discontinued the full operator table after its FY2020 10-K. You get the complete FY2020 ranking (with lease/mortgage decomposition — including that Ciena, not Consulate, was actually #1 with mortgages included) and the FY2025 named disclosures (Maplewood ≥10%, CommuniCare 7.2%), each dated, never blended. "Actually runs" = live CMS chain counts. |
Share of top operators' facilities below the national staffing average? |
| Computed per operator and pooled server-side against the national mean (3.86 reported nurse HPRD). Unmappable operators are named and excluded, not silently dropped. |
Largest operator's average star rating and two-year direction? |
| Unsupported for Maplewood (largest by investment): it runs senior-living communities, which are not CMS-certified nursing homes — the server says so and why. For CommuniCare (largest by revenue): avg 3.05 stars, improved 2.26 → 3.04 on a 117-facility constant panel (Jul 2024 → Jul 2026). |
Portfolio occupancy? |
| A labeled proxy: Omega discloses neither occupancy nor a facility list. Bed-weighted occupancy across mapped operator chains (83.5% vs 80.5% national), with coverage accounting — what share of the portfolio the proxy actually represents and who's excluded (UK operators, Maplewood, low-confidence maps). |
One-paragraph exposure briefing on the largest operator? |
| The model writes the paragraph; the server supplies only deterministic facts: ≥10% of investments, 6.6%/5.2%/5.4% revenue trend, the $12.5M termination-fee note, and the CMS coverage gap. |
Architecture
Three layers, one direction of dependency, no database, no runtime network:
scripts/ingest.ts CMS download → validate → project → data/processed/*.json (committed)
data/curated/*.json Hand-transcribed 10-K facts + operator→CMS map, per-fact citations
│
src/domain/ Pure, deterministic, unit-tested: store, resolve, metrics
│
src/server/ MCP wiring: 9 tools + 1 resource → envelope responses (stdio)data/curated/omega-10k.json— FY2025 portfolio summary + concentration note, FY2020 operator investment table. Every block cites its filing/section.data/curated/operator-map.json— the honesty backbone: each Omega operator's CMS mapping withmethod(chain-exact / legal-name-pattern / curated-alias),confidence(high/medium/low), and caveats; unmappable operators carry the reason.src/domain/metrics.ts— all arithmetic: rankings, occupancy, benchmark comparisons, constant-panel star trends. Nothing numeric is left to the model.src/server/tools.ts— thin: validate input (zod), call domain, wrap in envelope.
The answer envelope
Every tool returns:
{
"status": "complete" | "partial" | "unsupported", // brief's complete / uncertain / unsupported
"data": { /* deterministic numbers & records, never prose */ },
"caveats": [ /* why partial; staleness; method notes — computed, not decorative */ ],
"provenance": [ { "source", "asOf", "detail", "url" } ],
"cost": { "chars", "estTokens", "basis" } // self-reported payload size, labeled estimate
}status is computed from the data path, not hardcoded: an unmapped operator yields
unsupported with the mapping entry's recorded reason; anything touching the FY2020
table is partial with the staleness caveat; the occupancy proxy is always partial.
Tool surface
Tool | Returns | Raw or resolved? |
| FY2025 totals, mix, geo + named operator concentration | resolved facts, as filed |
| Two dated ranking blocks (FY2020 full / FY2025 named) | resolved; % computed from filed dollars |
| name → canonical operator + CMS mapping + confidence + 10-K context (FY2020 rank/%, FY2025 disclosed %) | metadata |
| paged facility rows + full-population summary | raw rows + resolved summary |
| stars (mean + per-star distribution), staffing-vs-national, occupancy, and 2-yr constant-panel trends for all three; pooled block for multi-operator | resolved (all arithmetic server-side) |
| facility drill-down by CCN/name: current metrics, per-snapshot history, reverse Omega-operator affiliation | raw detail + resolved affiliation |
| proxy occupancy + 2-yr trend + coverage accounting | resolved, explicitly labeled proxy |
| national staffing/star/occupancy references + methods | resolved |
| sources, vintages, mappings, known gaps (also | metadata |
Granularity rationale: tools are question-shaped but composable — deterministic
aggregation (where LLM arithmetic over 100+ rows is a correctness risk) is a tool
responsibility; narrative synthesis is the model's. Every operator-taking tool accepts
free text and resolves internally, so a client never needs a two-step protocol; a failed
resolution is an unsupported answer (with candidates and the known universe), not an error.
Cross-source questions (10-K piece ↔ CMS piece) are first-class: the operator identity is
the join key, verified round-trip (every name in the 10-K ranking resolves in every
CMS-backed tool — e2e-tested), and every resolved operator block embeds its 10-K context
(omegaContext: FY2020 rank and % of portfolio, FY2025 disclosed concentration), so
"how good is our biggest operator?" style questions resolve without a second call.
Key decisions & trade-offs
1. Two vintages, never blended. The decisive research finding: Omega's 10-Ks after
FY2020 do not contain a per-operator investment table — the FY2025 filing names only
Maplewood (≥10% of investments) and CommuniCare (7.2%). So a current "top five" is not
fully supportable from the named sources, and the server says exactly that: rankings come
as two separately-dated blocks, and the status is partial with the reason. Trade-off:
less satisfying than one clean list; chosen because a blended list would be numerically
incoherent (2020 dollars vs 2025 percentages on different denominators).
2. Hand-transcribed SEC facts, machine-ingested CMS data. The Omega facts are ~30 numbers across two tables in two differently-formatted filings. A generic 10-K parser at this scope has the worst failure mode possible for this brief — silently wrong extraction. Instead: curated JSON with per-fact citations, guarded by checksum tests (every summable column must reproduce the filing's own subtotals and totals — a mistyped digit fails the build). The CMS side (14,693 rows × 3 monthly vintages) is fully automated with validation, because there scale makes automation the safer option. Trade-off: refreshing for a new 10-K is a manual edit; accepted for a annually-filed document.
3. The operator→CMS join is a curated, confidence-tagged artifact. Neither dataset references the other. The join (10-K operator name → CMS chain) is the riskiest inference in the system, so it is data, not code: each mapping records how it was made and how much to trust it, and unmappable operators record why (Maplewood: senior living, outside CMS; Healthcare Homes: UK). Low-confidence mappings (Agemo → Signature) are excluded from pooled aggregates by default and surfaced when included. Trade-off: doesn't scale to hundreds of REITs; correct for one REIT's ~11 named operators, and the mechanism (method/confidence/caveat per mapping) is what would scale.
4. Chain metrics are supersets, and say so. Omega's facility-level portfolio is not public (verified: Schedule III aggregates by state). CMS metrics therefore describe an operator's whole operation, not just Omega's buildings — every affected response carries that caveat, and the occupancy proxy reports what share of the (FY2020) portfolio its coverage represents (~40%). Trade-off: a facility-level reconstruction from the CMS Ownership file was possible but is multi-day fuzzy-matching work; the honest proxy with coverage accounting is the four-hour answer. That reconstruction is the natural next step.
5. Methodology is part of the answer. Star trend = constant panel (facilities rated in both endpoint snapshots), with panel size, exclusions, and the known bias (chain membership is current-only) in the response. Staffing benchmark = reported total nurse HPRD, facility-mean (what the question asks, unadjusted; case-mix-adjusted exists and is noted). Occupancy = avg residents/day ÷ certified beds, which understates operational occupancy (certified > in-service beds). All stated in the payloads, not just here.
6. In-memory JSON, no database, artifacts committed. 15k rows load in milliseconds; a DB adds operational surface for zero query need. Committed artifacts (~6MB) mean install → build → demo works with no network — the live demo cannot be broken by a CMS outage or a changed download URL. Cost: repo carries data; ingestion re-derives it from sources at any time.
7. Bounded outputs. Facility lists are paged (default 25) with an always-complete summary block and total count — a 185-facility chain never floods the client context.
Testing
tests/curated.test.ts— transcription checksums against the filings' own totals.tests/metrics.test.ts,tests/resolve.test.ts— domain units on fixtures (exact values).tests/e2e.test.ts— a real MCP client over an in-memory transport against the real data: one test per demo question, including the unsupported paths.npm run smoke— the compiled server over real stdio from a foreign cwd.
Efficiency & token cost
npm run cost measures what an LLM client pays in context per demo question (tool-result
text + one-time tool schemas), fully offline. Token figures are estimates (chars ÷ 4;
real tokenizers vary ±20%) — the value is relative cost and regression tracking.
Current measurements (committed artifacts):
Question | Calls | Est. tokens |
Q1 top-5 + facility counts | 2 | ~4.1k |
Q2 staffing below national | 1 | ~3.0k |
Q3 largest operator stars + trend | 2 | ~1.9k |
Q4 portfolio occupancy | 1 | ~1.0k |
Q5 exposure briefing | 2 | ~1.4k |
Five-question session | 8 | ~11.4k (+ ~3.2k one-time schemas) |
Every response also stamps its own cost block ({chars, estTokens, basis}) so the
assistant can cite what an answer cost in context — labeled an estimate, because real
tokenization happens client-side and the server never sees it (in Claude Code, /cost
and /context remain the ground truth at session level).
Two deliberate optimizations keep this lean (a 31% reduction vs the naive version, measured): the model-facing text mirror is compact JSON (pretty-print whitespace alone was ~26% of payload), and repeated methodology strings live once per response in the envelope caveats rather than on every trend block. Facility lists are paged; summaries are always full-population. The cost stamp itself adds ~21 tokens per response — measured, and worth it for the visibility.
Data pipeline
npm run ingest downloads and rebuilds data/processed/:
Resolves the current Provider Information CSV URL from the CMS PDC metastore API (the file URL changes monthly), downloads it plus two archived snapshots (Jul 2024, Jul 2025) for the trend.
Validates (row counts, required columns with header aliasing across CMS's 2024→2025 column renames, rating ranges, null rates) — fails loudly, never writes partial artifacts.
Projects to three artifacts: per-facility slice, CCN→rating history, national benchmarks (with methods recorded in the file).
Raw downloads cache in data/raw/ (gitignored); --force re-downloads.
Repo layout
data/curated/ hand-verified 10-K facts + operator map (source-cited, checksummed)
data/processed/ generated CMS artifacts (committed; rebuild with npm run ingest)
scripts/ ingest.ts, stdio-smoke.mjs
src/domain/ types, store, resolve, metrics — pure & unit-tested
src/server/ MCP tools + entry (stdio)
tests/ checksums, units, e2e
docs/ PLAN.md (build plan + audit trail), DEMO.md (presentation script)Known limitations & next steps
Omega-owned facilities are not individually identifiable → operator-chain proxy (next: cross-walk the CMS Ownership file's property-company records).
Current-year operator ranking is inherently incomplete (disclosure stopped in FY2020); Omega's quarterly supplementals could narrow this but are outside the brief's sources.
Trends (stars, staffing, occupancy) use two endpoint snapshots + a midpoint; more monthly snapshots would smooth them.
UK facilities (17.7% of real estate) have no CMS-equivalent ingestion (CQC would be the analogous UK source).
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceEnables AI-powered analysis of healthcare market segments, product comparisons, and sales data insights using natural language processing and retrieval-augmented generation.2
- AlicenseAqualityCmaintenanceEnables AI assistants to look up medical billing codes, denial reasons, and payer rules for faster claim resolution.66MIT
- FlicenseNot gradedqualityCmaintenanceEnables document search, grounded question answering, summarization, patient timeline extraction, and PHI redaction for healthcare documents using retrieval-augmented generation.
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to query organizational architecture and governance constraints, returning evidence-grounded answers from documented structures.MIT
Related MCP Connectors
Certified SEC EDGAR fact memory for AI agents with zero hallucination and filing provenance.
Provide AI assistants with real-time access to official SEC EDGAR filings and financial data. Enab…
Deterministic compliance and vertical knowledge bases for autonomous agents. Free 24hr trial.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/siddak1234/occulytics-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server