{"id":49652389,"url":"https://github.com/baranzinilab/cdwagent","last_synced_at":"2026-05-06T05:01:32.412Z","repository":{"id":353125525,"uuid":"1217427416","full_name":"BaranziniLab/CDWAgent","owner":"BaranziniLab","description":"MCP server for a de-identified Epic Caboodle Clinical Data Warehouse (SQL Server, read-only). BioRouter extension, sibling of UCSFOMOPAgent.","archived":false,"fork":false,"pushed_at":"2026-04-30T14:50:11.000Z","size":1379,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-30T16:20:58.198Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/BaranziniLab.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-04-21T21:53:41.000Z","updated_at":"2026-04-30T14:55:09.000Z","dependencies_parsed_at":null,"dependency_job_id":"12aff73e-a0aa-4e1a-b2c3-60d36c9a3baf","html_url":"https://github.com/BaranziniLab/CDWAgent","commit_stats":null,"previous_names":["neurogb/cdwagent"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/BaranziniLab/CDWAgent","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BaranziniLab%2FCDWAgent","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BaranziniLab%2FCDWAgent/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BaranziniLab%2FCDWAgent/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BaranziniLab%2FCDWAgent/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/BaranziniLab","download_url":"https://codeload.github.com/BaranziniLab/CDWAgent/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BaranziniLab%2FCDWAgent/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32679444,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-06T02:33:58.958Z","status":"ssl_error","status_checked_at":"2026-05-06T02:33:39.611Z","response_time":117,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-06T05:00:58.438Z","updated_at":"2026-05-06T05:01:32.399Z","avatar_url":"https://github.com/BaranziniLab.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# CDWAgent\n\nAn MCP (Model Context Protocol) server that exposes a de-identified **Epic Caboodle Clinical Data Warehouse** (SQL Server) to [**BioRouter**](https://github.com/BaranziniLab/BioRouter).\n\nBuilt for clinical researchers who need natural-language access to EHR data without writing SQL. Designed as a sibling of [UCSFOMOPAgent](https://github.com/BaranziniLab/UCSFOMOPAgent): CDWAgent targets the UF Epic Caboodle schema while OMOPAgent targets the OHDSI/OMOP common data model. Both can be enabled in the same BioRouter session — tool names are namespace-prefixed to prevent collision, and CDWAgent includes a `crossmap_patient` tool that resolves OMOP `person_id` values to CDW `PatientDurableKey`.\n\nArchitecture is based on the [MedCP](https://github.com/BaranziniLab/MedCP) template by the UCSF Baranzini Lab, with a modular tool registry, expanded clinical tools, and no knowledge graph dependency.\n\n## BioRouter Extension\n\n**[Download cdwagent.brxt](https://github.com/BaranziniLab/CDWAgent/releases/latest/download/cdwagent.brxt)**\n\nDrag the `.brxt` file into BioRouter's **Extensions → Add extension** dialog. BioRouter will install the virtual environment automatically and prompt for required credentials.\n\n| Variable | Required | Default | Description |\n|----------|----------|---------|-------------|\n| `CLINICAL_RECORDS_USERNAME` | ✅ | — | UCSF network username (e.g. `CAMPUS\\youruser`) |\n| `CLINICAL_RECORDS_PASSWORD` | ✅ | — | UCSF network password |\n| `CLINICAL_RECORDS_SERVER` | optional | `QCDIDDWDB001.ucsfmedicalcenter.org` | SQL Server hostname |\n| `CLINICAL_RECORDS_DATABASE` | optional | `CDW_NEW` | Database name |\n| `CDW_NAMESPACE` | optional | `CDW` | Tool namespace prefix |\n| `CDW_SCHEMA` | optional | `deid_uf` | SQL schema name |\n| `CDW_LOG_LEVEL` | optional | `INFO` | Logging level |\n\n## Authors\n\n- **Gianmarco Bellucci**\n- **Wanjun Gu**\n\n## Features\n\n- 21 MCP tools organized into 6 domain modules\n- 3 guided workflow prompts for common research tasks\n- OMOP → CDW patient crossmapping with birth-date sanity check\n- Read-only SQL enforcement with comprehensive write-blocking\n- Schema discovery from a pre-parsed data dictionary (no DB connection needed)\n- Clinical notes search and retrieval\n- Cohort building with aggregate demographics\n- CSV export for large result sets\n- Configurable tool namespace and database schema\n\n## Tools\n\nAll tool names are namespace-prefixed with `CDW-` at runtime so they coexist with sibling agents (e.g., `UCSFOMOPAgent`) inside a single BioRouter session. The descriptions below are the canonical entry points each tool exposes; for the per-tool flow diagrams see [`docs/agent-flows/02-tool-flows/`](docs/agent-flows/02-tool-flows/).\n\n### Schema Discovery (3)\n\nThese tools read from the bundled `schema_reference.json` and require no database connection — they work offline for exploratory research.\n\n| Tool | Description |\n|------|-------------|\n| `get_database_overview` | List every CDW table with one-line description, patient/encounter linkage flags, and column counts. The agent uses this as its first move when a research question lacks an obvious target table. |\n| `describe_table` | Return the column list for a named table — names, data types, descriptions, and foreign-key relationships. Used to construct schema-aware SQL after a candidate table has been identified. |\n| `search_schema` | Keyword search across table and column names plus their descriptions. Useful when the user describes a clinical concept (e.g. \"lab results\") rather than a table name. |\n\n### Clinical Queries (7)\n\nThese tools execute SELECT-only SQL against the de-identified Epic Caboodle warehouse. Every executed statement is validated by `ClinicalQueryValidator` (read-only enforcement, no semicolon chaining, blocked write verbs) and appended to the SQL audit log at `$TMPDIR/cdwagent_sql.log`.\n\n| Tool | Description |\n|------|-------------|\n| `query` | Execute a read-only SQL `SELECT` query (or `WITH ... SELECT`) and return the rows as CSV. The validator blocks every write verb. The cohort subquery pattern (`WHERE PatientDurableKey IN (...)`) is the recommended composition primitive for cross-fact queries. |\n| `get_patient_demographics` | Return the most recent demographic record for a `PatientDurableKey` from `PatientDim` (filtered by `IsCurrent = 1`). Sex, birth date, race, ethnicity, language, status. |\n| `get_encounters` | Encounter history from `EncounterFact` for one patient, ordered by `DateKey` descending. Includes department specialty, encounter type, and visit type. |\n| `get_medications` | Medication orders from `MedicationOrderFact` for one patient, with `OrderedDateKey`/`StartDateKey`/`EndDateKey` so the agent can reconstruct treatment duration. |\n| `get_diagnoses` | Diagnosis history from `DiagnosisEventFact` for one patient, ordered by `StartDateKey`. Joined to `DiagnosisDim` for human-readable names and to `DiagnosisTerminologyDim` for the originating code system. |\n| `get_labs` | Lab results from `LabComponentResultFact` for one patient. Returns the `Value` string field rather than `NumericValue` (de-identified and unreliable for analysis). |\n| `crossmap_patient` | Resolve an OMOP `person_id` to a CDW `PatientDurableKey` via `OMOP_DEID.dbo.person.person_source_value = CDW_NEW.deid_uf.PatientDim.PatientEpicId` with `IsCurrent = 1`. Returns demographics plus a `birth_date_match` boolean for sanity-checking the join. The bridge tool when a study starts on the OMOP side and needs CDW depth. |\n\n### Clinical Notes (4)\n\nA two-tier retrieval surface: an NLP concept layer (cTAKES) for fast semantic search, and a verbatim layer for chart review or exact-phrase matching. The cTAKES layer is the preferred entry point for clinical concepts; verbatim retrieval is reserved for cases where the NLP layer would not normalise the phrase (specific provider names, exact dose phrasing, idiosyncratic wording).\n\n| Tool | Description |\n|------|-------------|\n| `search_note_concepts` | Search the NLP-extracted concept layer (`note_concepts`, populated by cTAKES) by canonical text or UMLS CUI, optionally restricted to a cohort of one or more `PatientDurableKey` values. Defaults exclude negated mentions and family-history mentions; historical mentions are kept (commonly relevant for retrospective research). Population-mode (no cohort) applies an early-termination optimisation and emits a `[NOTICE: ...]` banner that the agent must surface to the user. |\n| `search_note_sdoh` | Search Social Determinants of Health concepts (`note_concepts_sdoh`, populated by the cTAKES SDOH module) — housing instability, food insecurity, employment, transportation barriers, substance use, social isolation, financial strain. Use for equity and vulnerability research where structured fields rarely capture the signal. Same population-mode notice convention as `search_note_concepts`. |\n| `search_notes` | Verbatim text retrieval over `note_text` and `note_metadata`, scoped to a cohort of one or more `PatientDurableKey` values. Supports an optional keyword filter; without a keyword the call performs a chronological chart review. SQL Server `IN`-clause cap of 2000 patients. |\n| `get_note` | Retrieve the full text of one clinical note by its `deid_note_key`, typically discovered via `search_note_concepts` or `search_notes`. |\n\n### Concept Search (4)\n\nThese tools resolve human-language concept names or terminology codes into the surrogate keys used by fact tables. The agent uses them as the first step in any cohort-building workflow: it finds the relevant `*Key` values and then composes a `... IN (...)` filter on the corresponding fact table.\n\n| Tool | Description |\n|------|-------------|\n| `search_diagnoses_by_code` | Resolve ICD/SNOMED codes or diagnosis names against `DiagnosisTerminologyDim` joined to `DiagnosisDim`. Returns `DiagnosisKey` values for use in `DiagnosisEventFact.DiagnosisKey IN (...)`. |\n| `search_medications_by_code` | Resolve NDC/RxNorm codes, brand names, or generic names against `MedicationCodeDim`. Returns `MedicationKey` values for use in `MedicationOrderFact.MedicationKey IN (...)`. |\n| `search_labs_by_code` | Resolve LOINC codes or lab component names (e.g. \"hemoglobin a1c\", \"creatinine\") against `LabComponentDim`. Returns `LabComponentKey` values for use in `LabComponentResultFact.LabComponentKey IN (...)`. Note the LOINC column is `LoincCode`, not `Loinc`. |\n| `search_procedures_by_code` | Resolve CPT/HCPCS codes or procedure names against `ProcedureTerminologyDim`. Returns `ProcedureTerminologyKey` values for use in `ProcedureEventFact.ProcedureTerminologyKey IN (...)`. |\n\n### Data Export (1)\n\n| Tool | Description |\n|------|-------------|\n| `export_query_to_csv` | Execute a read-only SQL query and write the rows to a CSV file at a caller-specified path. Validator and audit log apply identically to `query`. The target directory must exist. |\n\n### Statistics (2)\n\n| Tool | Description |\n|------|-------------|\n| `summarize_table` | Per-table descriptive statistics: row count, per-column null rates, and sample value distributions for low-cardinality categorical columns. |\n| `cohort_summary` | Aggregate demographics (age statistics, sex, race, ethnicity) for a cohort defined by a SQL subquery returning `PatientDurableKey`. Used as the closing summary at the end of a cohort-building workflow. |\n\n## Guided Prompts\n\nThe server includes three MCP prompts that guide the LLM through common workflows:\n\n- **clinical_data_exploration** — Step-by-step CDW exploration: schema overview, table discovery, query building\n- **cohort_building** — Cohort identification workflow with correct patient identifier patterns and query optimization tips\n- **notes_analysis** — Clinical notes investigation from patient identification through note retrieval and summarization\n\n## Validation\n\nCDWAgent has been end-to-end validated against the two BAA-covered LLM providers supported at UCSF:\n\n- **Azure OpenAI GPT-5.2** via the UCSF unified-api endpoint\n- **AWS Bedrock — Sonnet 4.6**\n\nThe eval suite covers cohort identification by structured codes, multi-criteria intersection, longitudinal lab and medication trajectories, NLP-based phenotype extraction over the cTAKES `note_concepts` and `note_concepts_sdoh` layers, OMOP↔CDW patient crossmapping, ambiguity disambiguation, and read-only enforcement. All cases pass against both providers under the v0.4.3 release. The eval harness lives in [`neuroGB/CDWAgent_testing`](https://github.com/neuroGB/CDWAgent_testing) (private).\n\n## Installation\n\n### Requirements\n\n- Python \u003e= 3.11\n- Access to a SQL Server Clinical Data Warehouse\n- [uv](https://github.com/astral-sh/uv) package manager (recommended)\n\n### Quick install (uvx)\n\n```bash\nuvx --from git+https://github.com/BaranziniLab/CDWAgent cdwagent\n```\n\n### From source\n\n```bash\ngit clone https://github.com/BaranziniLab/CDWAgent.git\ncd CDWAgent\nuv sync\ncp .env.example .env\n# Edit .env with your database connection details\nuv run cdwagent\n```\n\n### Run as a module\n\n```bash\npython -m cdwagent\n```\n\n## Configuration\n\nAll configuration is via environment variables (see `.env.example`):\n\n| Variable | Required | Description |\n|----------|----------|-------------|\n| `CLINICAL_RECORDS_USERNAME` | Yes | SQL Server username |\n| `CLINICAL_RECORDS_PASSWORD` | Yes | SQL Server password |\n| `CLINICAL_RECORDS_SERVER` | No | SQL Server hostname (default: `QCDIDDWDB001.ucsfmedicalcenter.org`) |\n| `CLINICAL_RECORDS_DATABASE` | No | Database name (default: `CDW_NEW`) |\n| `CDW_NAMESPACE` | No | Tool name prefix (default: `CDW`) |\n| `CDW_SCHEMA` | No | Database schema for table qualification (default: `deid_uf`) |\n| `CDW_LOG_LEVEL` | No | Logging level (default: `INFO`) |\n\nThe server and database default to the UCSF CDW deployment. Set the env vars only to override (e.g. a different host or a development database).\n\n## Use with BioRouter\n\nCDWAgent is a standard stdio MCP server, so it registers as a BioRouter **Extension** exactly like UCSFOMOPAgent does — no BioRouter-specific code needed.\n\nAdd this block to `~/.config/biorouter/config.yaml`:\n\n```yaml\nextensions:\n  cdwagent:\n    type: stdio\n    name: CDWAgent\n    description: UF Epic Caboodle de-identified Clinical Data Warehouse (SQL Server, read-only)\n    enabled: true\n    cmd: uvx\n    args: [\"--from\", \"git+https://github.com/BaranziniLab/CDWAgent\", \"cdwagent\"]\n    timeout: 600\n    envs:\n      CLINICAL_RECORDS_USERNAME: \"your-username\"\n      CLINICAL_RECORDS_PASSWORD: \"your-password\"\n      CDW_SCHEMA: \"deid_uf\"\n```\n\nServer and database are hard-coded to the UCSF CDW deployment; override with `CLINICAL_RECORDS_SERVER` / `CLINICAL_RECORDS_DATABASE` only if needed.\n\nOr via CLI:\n\n```bash\nbiorouter session --with-extension \"CLINICAL_RECORDS_USERNAME=... CLINICAL_RECORDS_PASSWORD=... uvx --from git+https://github.com/BaranziniLab/CDWAgent cdwagent\"\n```\n\n**Tip — pairing with OMOPAgent:** enable both extensions to translate between the two clinical data representations. Ask BioRouter *\"for OMOP person_id 12345, pull lab trends from the CDW side\"* and it will call `CDW-crossmap_patient` then `CDW-get_labs`. See [`docs/BIOROUTER.md`](docs/BIOROUTER.md) for operational details (timeouts, malware check, tool-name disambiguation).\n\n## Context Strategy (LLM dispatch optimization)\n\nCDW Epic Caboodle uses a proprietary schema the LLM does not know from its training data (unlike OMOP CDM, where OHDSI terms are well-known). To minimize roundtrips and context usage, CDWAgent ships schema context at **two layers**:\n\n1. **MCP `server instructions`** — a concise overview of the 14 most-used tables, patient identifier rules, date-column mapping per fact table, and the cohort subquery pattern. Sent once at session init via `InitializeResult.instructions` (FastMCP feature). BioRouter and other MCP clients fold this into the LLM's system prompt. Net effect: the LLM knows the schema the moment it picks any CDW tool, without a `get_database_overview` roundtrip.\n\n2. **Tool descriptions** — kept short (~150 words each). Only the single most common failure mode (schema-qualification with `deid_uf.`) is repeated in the `query` tool description as a banner, since it is the top error source. Everything else lives in the server instructions.\n\nLong tail: 139 total tables, ~5000 columns. Full listing is available on-demand via `get_database_overview` and `describe_table` — not pushed into the system prompt.\n\nThis is the generic pattern for MCPs targeting non-standard schemas. Thin tool descriptions + rich server instructions keeps turn-by-turn context small (tool descriptions are sent on every LLM turn; instructions are sent once) while still providing the context the LLM needs up front.\n\n## Schema Reference\n\nSchema discovery tools (`get_database_overview`, `describe_table`, `search_schema`) read from a pre-parsed JSON at [`src/cdwagent/data/schema_reference.json`](src/cdwagent/data/schema_reference.json) (bundled inside the Python package so `uvx` installs work out of the box) — **no database connection is required** for schema exploration. The JSON contains only structural metadata: table names, column names, data types, and descriptions. No patient data, no institutional identifiers.\n\n**The source Epic Caboodle data dictionary (`.xlsx`) is intentionally NOT bundled with this repository.** It is a local governance artifact of each institution. The committed JSON is a derived representation — everything CDWAgent needs at runtime — but the original xlsx stays under institutional control.\n\nIf you need to regenerate `src/cdwagent/data/schema_reference.json` from an updated dictionary, obtain the xlsx through your institution's CDW governance channel and run:\n\n```bash\nuv run python scripts/parse_data_dictionary.py /path/to/deid_uf_data_dictionary.xlsx\n```\n\n## Project Structure\n\n```\nsrc/cdwagent/\n├── __init__.py          # Package exports\n├── __main__.py          # python -m cdwagent\n├── cli.py               # CLI entry point\n├── server.py            # FastMCP instance, tool registration, prompts\n├── config.py            # Pydantic configuration models\n├── db.py                # Per-query pymssql connection management\n├── validation.py        # SQL read-only validation\n└── tools/\n    ├── schema.py        # Schema discovery tools\n    ├── queries.py       # Query execution, clinical record retrieval, OMOP→CDW crossmap\n    ├── notes.py         # Clinical notes search and retrieval\n    ├── export.py        # CSV export\n    ├── concepts.py      # Diagnosis/medication/procedure code search\n    └── stats.py         # Table and cohort summary statistics\n```\n\n## Security Policy\n\n### Read-Only Enforcement\n\nAll SQL queries are validated before execution by `ClinicalQueryValidator`:\n\n- Only `SELECT`, `WITH`, and `DECLARE` statements are allowed\n- Write operations (`INSERT`, `UPDATE`, `DELETE`, `DROP`, `ALTER`, `TRUNCATE`, `EXEC`, `MERGE`, `CREATE`) are blocked\n- Semicolons are rejected to prevent statement chaining\n- Queries are validated after stripping SQL comments\n\n### Credential Handling\n\n- Database credentials are passed via environment variables, never hardcoded\n- BioRouter stores credentials via `envs` (inline) or `env_keys` (OS keyring) in its config\n- No credentials are logged or included in tool responses\n\n## Disclaimer\n\n**THIS SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED.** The authors (Gianmarco Bellucci and Wanjun Gu) make no representations or warranties regarding the accuracy, completeness, or reliability of the software or its outputs.\n\n**Important notices:**\n\n- This tool is designed for **research purposes only** and is **not intended for clinical decision-making** or direct patient care.\n- The authors are **not responsible** for any consequences arising from the use or misuse of this software, including but not limited to: incorrect query results, data misinterpretation, security incidents, or regulatory non-compliance.\n- Users are solely responsible for ensuring their use of this software complies with all applicable **institutional policies**, **data use agreements**, **IRB protocols**, and **privacy regulations** (including HIPAA where applicable).\n- The read-only SQL validation provides a defense-in-depth layer but should **not be the sole security control**. Database-level permissions and network controls should be configured independently.\n- Clinical data accessed through this tool is **de-identified** per the source data warehouse configuration. Users must not attempt to re-identify patients.\n\n## License\n\nMIT\n\n## Acknowledgments\n\n- [**MedCP**](https://github.com/BaranziniLab/MedCP) — architecture template by the UCSF Baranzini Lab.\n- [**UCSFOMOPAgent**](https://github.com/BaranziniLab/UCSFOMOPAgent) — sibling agent for the OMOP CDM, which CDWAgent is designed to pair with inside BioRouter.\n- [**BioRouter**](https://github.com/BaranziniLab/BioRouter) — agent framework (a fork of Block's Goose) that coordinates clinical MCP agents.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbaranzinilab%2Fcdwagent","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbaranzinilab%2Fcdwagent","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbaranzinilab%2Fcdwagent/lists"}