This project demonstrates how to use the Model Context Protocol (MCP) to connect a Jupyter notebook to the JupyterHealth Exchange server, enabling LLM-powered queries over health data using the NRP-hosted Qwen3 model.
The JHE Notebook connects:
- MCP Client → JupyterHealth Exchange MCP Server (local, port 8001)
 - OpenAI Client → NRP Qwen3 LLM (hosted at NRP)
 - Integration: LLM uses MCP tools to query health data and respond in natural language
 
┌─────────────────┐
│ Jupyter Notebook│
│  (This Project) │
└────┬────────┬───┘
     │        │
     │        └──────────────┐
     │                       │
     ▼                       ▼
┌─────────────────┐    ┌──────────────┐
│  MCP Client     │    │ OpenAI Client│
│  (Python SDK)   │    │  (NRP API)   │
└────┬────────────┘    └──────┬───────┘
     │                        │
     │                        │
     ▼                        ▼
┌─────────────────┐    ┌──────────────┐
│ JHE MCP Server  │    │ NRP Qwen3    │
│ localhost:8001  │    │   Model      │
└────┬────────────┘    └──────────────┘
     │
     ▼
┌─────────────────┐
│ JupyterHealth   │
│ Exchange (JHE)  │
│ localhost:8000  │
└─────────────────┘
Important: You need the MCP-enabled version of JupyterHealth Exchange.
Clone the JupyterHealth Exchange repository and checkout the MCP feature branch:
git clone https://github.com/jupyterhealth/jupyterhealth-exchange.git
cd jupyterhealth-exchange
# TODO: Update this when PR is merged - for now use the feature branch
git fetch origin pull/XXX/head:mcp-support  # Replace XXX with actual PR number
git checkout mcp-supportFollow the JupyterHealth Exchange setup instructions to:
- Install dependencies
 - Configure the database
 - Seed test data
 
Important: The MCP Python SDK requires Python 3.10 or later.
Check your Python version:
python3 --version  # Should be 3.10 or higherIf needed, use a specific Python version:
# macOS with Homebrew
brew install [email protected]
# Use specific Python version for venv
python3.11 -m venv .venvJupyterHealth Exchange Server must be running:
cd /path/to/jupyterhealth-exchange
.venv/bin/python manage.py runserverMCP Server must be running:
cd /path/to/jupyterhealth-exchange
./start_mcp_server.shVerify servers are healthy:
curl http://localhost:8000/admin/  # Should return 200
curl http://localhost:8001/health  # Should return {"status": "healthy"}You need an API key from the National Research Platform:
- Visit https://portal.nrp.ai/
 - Create an account or log in
 - Generate an API token
 - Note the model name (e.g., 
qwen3) 
Important: The MCP Python SDK requires nest-asyncio to work in Jupyter notebooks:
- Jupyter runs an active asyncio event loop
 - MCP SDK uses async/await extensively
 - nest-asyncio patches the loop to allow nested async operations
 
This is automatically installed via requirements.txt and applied in the notebook's first cell.
Security Note: All queries go through the MCP server which enforces OAuth authentication and permission filtering. Never query the database directly - the MCP server is the gatekeeper that ensures:
- OAuth authentication with JWT verification
 - Permission filtering by study_ids from ID token claims
 - SQL injection protection
 - Read-only operations only
 
The MCP server uses OAuth to authenticate with JHE. You should have already completed the OAuth flow (tokens cached at ~/.jhe_mcp/token_cache.json). If not, the notebook will guide you through authentication.
git clone https://github.com/the-commons-project/jhe-notebook.git
cd jhe-notebookImportant: Use Python 3.10+ (e.g., python3.11)
# Use Python 3.11 (or 3.10+)
python3.11 -m venv .venv
# Activate the environment
source .venv/bin/activate  # On Windows: .venv\Scripts\activatepip install -r requirements.txtcp .env.example .env
# Edit .env and add your NRP API keyExample .env:
JHE_MCP_URL=http://localhost:8001/sse
NRP_BASE_URL=https://ellm.nrp-nautilus.io/v1
NRP_API_KEY=your_actual_api_key_here
NRP_MODEL=qwen3
jupyter notebookNavigate to notebooks/jhe_mcp_llm_demo.ipynb and open it.
The demonstration notebook shows:
- MCP Connection: Connect to the local JHE MCP server via SSE
 - Tool Discovery: List available MCP tools (get_study_count, list_studies, execute_filtered_query, etc.)
 - LLM Integration: Use Qwen3 to interpret natural language queries
 - Function Calling: LLM decides which MCP tools to call
 - Data Retrieval: Execute SQL queries filtered by user permissions
 - Natural Language Response: LLM formats results for human consumption
 
# Simple query
"How many studies are in the system?"
# Complex health data query
"Show me all blood pressure readings for patient 40001 on January 1, 2025,
and calculate the average systolic pressure"
# Time-series aggregation
"Give me hourly averages and standard deviations for patient 40001's
blood pressure data over the first week of January"The JHE MCP Server provides these tools:
- get_study_count: Count studies accessible to authenticated user
 - list_studies: List all studies with IDs, names, and organizations
 - get_patient_demographics: Get patient demographics for a specific study
 - get_study_metadata: Get detailed metadata about a study
 - get_patient_observations: Get FHIR observations for a specific patient
 - get_database_schema: Get comprehensive database schema with examples
 - execute_filtered_query: Execute arbitrary SQL SELECT queries (auto-filtered by permissions)
 
- All MCP tool calls are authenticated via OAuth
 - SQL queries are automatically filtered based on user permissions
 - Users can only access studies/patients they have permission to view
 - Only SELECT queries allowed (no INSERT/UPDATE/DELETE)
 - Dangerous SQL operations are blocked
 
asyncio.CancelledError: Cancelled via cancel scope...
Solution: Make sure Cell 1 includes the nest_asyncio setup:
import nest_asyncio
nest_asyncio.apply()If you see this error, check that Cell 1 has been run and includes the nest_asyncio code. See NOTEBOOK_UPDATES.md for detailed instructions.
Error: Connection refused to localhost:8001
Solution: Ensure the MCP server is running:
curl http://localhost:8001/healthError: Authentication failed
Solution: Re-authenticate by deleting the token cache:
rm ~/.jhe_mcp/token_cache.jsonThen restart the notebook - it will trigger OAuth flow.
Error: 401 Unauthorized
Solution: Check your .env file has the correct NRP_API_KEY.
"row_count": 0
Solution:
- Verify you're logged in as a user with study access (e.g., [email protected])
 - Check that test data exists in the database
 - Ensure patient IDs are correct (e.g., 40001)
 
jhe-notebook/
├── README.md              # This file
├── requirements.txt       # Python dependencies
├── .env.example          # Environment variable template
├── .env                  # Your actual config (gitignored)
├── .gitignore            # Git ignore rules
└── notebooks/
    └── jhe_mcp_llm_demo.ipynb  # Main demonstration notebook
Create new .ipynb files in the notebooks/ directory. They will automatically have access to the same dependencies and configuration.
This is a demonstration project showing MCP integration patterns. Feel free to:
- Add new example notebooks
 - Create helper utilities
 - Improve error handling
 - Add data visualizations
 
This project will be published to The Commons Project GitHub organization at:
https://github.com/the-commons-project/jhe-notebook
Apache 2.0 (same as other Commons Project repositories)