# Project export: Medflix

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: TreeHacks 2026
- Tagline: Netflix for pediatric healthcare
- Devpost: https://devpost.com/software/medflix
- GitHub: https://github.com/Michael-600/MedFlix
- Video: https://www.youtube.com/embed/qyrDssfF_rI?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 6 GitHub contributor(s) — Iurii Beliaev (14 commits), Michael Hayford (9 commits), Cursor (5 commits), Claude Sonnet 4.5 (2 commits), princecharming001 (2 commits), jwgunter (1 commits)

## Devpost submission (written by the team)

### Inspiration

At five years old, Anish was diagnosed with astrocytoma, a type of brain cancer. Every day, oncologists would walk in, explain scans, treatment plans, and side effects in typical healthcare jargon and then leave before Anish understood anything. Sure, what they said was accurate, but Anish left the doctor’s office confused and feeling worse about his condition than before. And he had a right to feel that way - he did not even understand why his own body was doing this to him, and so it is no surprise he felt scared and isolated. We built Medflix to solve this problem by making medical understanding accessible at a child’s level.

### What it does

Medflix takes in real doctor-patient notes and infuses them with data grounded in medical ground truth. We then transform this complex data into short, animated episodes personalized to each child We generate these episodes using characters and visual styles inspired by the shows and media each child already loves, so the experience feels familiar and comforting rather than clinical. On top of that, children can speak with a live AI avatar trained on their medical context, allowing them to ask questions and receive simple, personalized explanations in real time.

### How we built it

We built a React frontend with Tailwind in order to make our interface feel kid-friendly and welcoming to a young child. We also build a Node/Express backend that is responsible for communicating with all of our different components. To start, we pull real medication and clinical label data from OpenFDA and DailyMed, then feed everything into Perplexity’s Sonar API to further refine what our data misses using deep medical search. We also built a strict video pipeline that enforces video generation frame-by-frame which gave us dramatically cohesive videos - rare for traditional ai video creation pipelines. We feed all healthcare data we fetch into HeyGen’s Video Generation API in order to create personalized videos for the patients. We also integrated HeyGen’s LiveAvatar API to power an interactive voice-based AI avatar that knows each child’s name, diagnosis, and medications so the child can feel comfortable talking about their diagnosis. We also added gamification (battle cards, quizzes, progression) and curated the UI specifically for children to make the application seem friendly and welcome to a child.

### Challenges we ran into

Early on, we ran into a lot of issues with AI hallucination. Our AI scripts mixed diagnoses, medications, and lifestyle advice into single episodes and confused even basic information. In order to solve this we made strict guardrail prompting and also topic-bounded scene generators. Another huge issue for us was video generation latency where each episode took a painful 1- 5 minutes. In order for us to solve this, we had to redesign the UX for asynchronous background rendering and added status tracking for each individual episode. We also ran into a lot of issues with real time AI avatar interaction from HeyGen’s LiveAvatar API which included things like WebRTC autoplay issues, audio track timing, session concurrency limits, and infinite echo loops. In addition, when trying to add Poke as our message assistant/reminder manager for the parents, it sometimes took us time to convince the stubborn guy to collaborate. Though once we went through this barries, Poke proved amazing Also, one of the hardest things for us was actually non-technical - we had to translate chemotherapy, antibiotics, and inhalers into language a young child could easily understand. We actually worked around this by using specific prompting in our AI video generation along with heavy ground sourcing in real health care data

### Accomplishments we're proud of

A newly diagnosed child can now watch seven personalized episodes explaining their condition and medications in language they actually understand. The AI Health Buddy works end-to-end so kids can ask questions in their own voice and receive contextual, grounded responses. We grounded every single episode in ground truth medical data and didn’t rely on raw AI output We built out the full features from doctor and patients for our platform, not just a demo from the patients side We made a professional looking UI during a hackathon

### What we learned

We learned how to chain AI systems together in order to produce a novel effect on patients since video generation alone is generic and seems fake, but if designed and combined properly, AI systems can complement each other instead of working against one another. We also learned a fair bit about AI LLM prompting, especially if you want to build a product that is robust, you need to get pretty creative. If you can figure out how to combine and use the instruments that, at the first glance, is impossible to combine, the products that could appear will surprise you in the best way possible. In addition, essentially, you need to focus on your customer. Children require clarity, empathy, and emotional safety. As such, we had to prompt our models and adjust the data in an intelligent way to cater to their specific needs. Most importantly, though, we got reminded that the best work is done in collaboration. By seeking feedback from each other, listening attentively, and actively trying to help each other, we built a product none of us would be able to in such a short period of time. As a matter of fact, during brainstorming everybody participated so proactively that at the end we realized that by ourselves we wouldn’t even be able to come up with such an idea. When everybody is engaged and excited, everything is within reach

### What's next

In the future, we will continue hyper-personalizing care for every unique patient–ensuring every child can clearly understand their diagnosis, treatment, and recovery. But we don’t want to just help children; we want to help everyone. As such, we are going to expand this model to adults as well, delivering personalized explainer videos that help patients better understand their care and recover more effectively. Our goal is simple: make medical understanding the default, not the exception.

## README (from the GitHub repository)

# MedFlix Prime Care

**AI-powered health education for children — transforming dense medical files into bite-sized, personalized video episodes that kids actually want to watch.**

---

## The Problem

- Children diagnosed with conditions like **asthma, ear infections, or leukemia** are scared and confused
- Medical discharge papers are written for adults — kids can't understand them
- Parents are overwhelmed and forget 40–80% of what doctors tell them
- There's no engaging, age-appropriate way for kids to learn about their own health

## The Solution

MedFlix takes a child's real medical data and turns it into a **7-episode animated health show** — personalized to their name, diagnosis, medications, and care team. Kids also get a **real-time AI Health Buddy** they can talk to, **collectible battle cards** for completing episodes, and **picture-based quizzes** to reinforce learning.

---

## System Architecture

```mermaid
graph TB
    subgraph "👨‍⚕️ Doctor Portal"
        DOC[Doctor Selects Patient]
        CREATE[Create Health Episodes]
        SEND[Send to Patient Portal]
    end

    subgraph "📋 Patient Data Layer"
        PD[(Patient Profiles<br/>Lily · Noah · Zara)]
        DIAG[Diagnosis + Medications<br/>+ Care Team + Goals]
    end

    subgraph "🔬 Clinical Data Pipeline"
        FDA[OpenFDA API<br/>Drug Labels & Safety]
        DM[DailyMed API<br/>NLM Clinical Data]
        GATHER[gatherClinicalData<br/>Parallel Fetch + Cache]
    end

    subgraph "🧠 AI Context Engine"
        PPLX[Perplexity Sonar AI<br/>LLM Context Layer]
        CTX[contextEngine.js<br/>Build Episode Context]
        SCRIPTS[episodeScripts.js<br/>7 Hardcoded Builders<br/>per Condition]
    end

    subgraph "🎬 Video Generation Pipeline"
        PROMPT[heygenPrompt.js<br/>Scene-by-Scene Directives]
        HEYGEN[HeyGen Video API<br/>Structured Avatar Video]
        POLL[Async Status Polling<br/>Background Generation]
        VIDEO[(Generated Videos<br/>Per Episode)]
    end

    subgraph "🗣️ Real-Time AI Avatar"
        LA_TOKEN[LiveAvatar Token API<br/>Session + Avatar Config]
        LA_SESSION[LiveAvatar Session<br/>FULL Interactive Mode]
        LIVEKIT[LiveKit WebRTC<br/>Audio/Video Streams]
        LLM[LiveAvatar LLM<br/>Patient Context Injected]
    end

    subgraph "💊 Medication Reminders"
        POKE[Poke SDK<br/>Model Context Protocol]
        MCP[MCP Server<br/>Tools + Resources]
        REMIND[Medication Schedules<br/>+ Reminders]
    end

    subgraph "👧 Patient Portal"
        LOGIN[Kid Login<br/>Emoji Avatar Selection]
        PLAN[Recovery Plan<br/>7-Episode Adventure]
        CARDS[Battle Card Collection<br/>Quiz + Rewards]
        LIVE[Live Health Buddy<br/>Voice Conversation]
        MEDS[Medication Reminders<br/>Daily Schedule]
        AI_CHAT[AI Assistant<br/>Text Q&A]
    end

    DOC --> PD
    PD --> DIAG
    CREATE --> DIAG

    DIAG --> GATHER
    GATHER --> FDA
    GATHER --> DM
    FDA --> CTX
    DM --> CTX
    DIAG --> CTX

    CTX --> PPLX
    PPLX --> CTX
    CTX --> SCRIPTS
    SCRIPTS --> PROMPT

    PROMPT --> HEYGEN
    HEYGEN --> POLL
    POLL --> VIDEO
    VIDEO --> PLAN

    SEND --> PLAN

    DIAG --> LLM
    LA_TOKEN --> LA_SESSION
    LA_SESSION --> LIVEKIT
    LIVEKIT --> LLM
    LLM --> LIVE

    POKE --> MCP
    MCP --> REMIND
    REMIND --> MEDS

    LOGIN --> PLAN
    PLAN --> CARDS
    LIVE --> LIVEKIT
```

---

## Data Flow — Video Generation

```mermaid
sequenceDiagram
    participant Doc as 👨‍⚕️ Doctor
    participant FE as React Frontend
    participant BE as Express Backend
    participant FDA as OpenFDA API
    participant DM as DailyMed API
    participant PPLX as Perplexity Sonar
    participant HG as HeyGen API

    Doc->>FE: Select patient + episode
    FE->>BE: POST /api/context/build
    BE->>FDA: GET drug labels (parallel)
    BE->>DM: GET clinical data (parallel)
    FDA-->>BE: Drug safety, indications, warnings
    DM-->>BE: Dosage, interactions, contraindications

    BE->>PPLX: Build context layer<br/>(clinical data + patient info)
    PPLX-->>BE: Enriched medical context

    BE->>BE: episodeScripts.js<br/>Build scenes (6-8 per episode)<br/>Script + Visual per scene

    BE-->>FE: Episode context + scenes

    FE->>BE: POST /api/heygen/video-agent
    BE->>HG: Create video (script + visuals)
    HG-->>BE: video_id (async)

    loop Poll every 5s
        FE->>BE: GET /api/heygen/status/:id
        BE->>HG: Check video status
        HG-->>BE: pending / completed + URL
    end

    HG-->>FE: Video URL ready
    FE->>FE: Display in Recovery Plan
```

---

## Data Flow — LiveAvatar Conversation

```mermaid
sequenceDiagram
    participant Kid as 👧 Child
    participant FE as React + LiveKit
    participant BE as Express Backend
    participant LA as LiveAvatar API
    participant LK as LiveKit Server
    participant LLM as Avatar LLM

    Kid->>FE: Click "Start Call"
    FE->>BE: POST /api/liveavatar/stop-all
    BE->>LA: Close stale sessions
    FE->>BE: POST /api/liveavatar/token
    BE->>LA: Create session (avatar, voice, quality, language)
    LA-->>BE: Session token + LiveKit URL
    BE-->>FE: Token + URL

    FE->>BE: POST /api/liveavatar/start
    BE->>LA: Start session
    LA-->>BE: Session active

    FE->>LK: Connect WebRTC room
    LK-->>FE: Audio/Video tracks attached

    FE->>LLM: Inject patient context prompt<br/>(name, age, diagnosis, meds, goals)
    LLM-->>FE: Initial greeting<br/>"Hi Lily! I'm your Health Buddy!"

    loop Conversation
        Kid->>FE: Speaks (microphone)
        FE->>LK: Audio stream
        LK->>LLM: user.transcription
        FE->>LLM: Forward with patient context
        LLM-->>FE: avatar.speak_response
        FE->>Kid: Avatar speaks + captions
    end
```

---

## Episode Pipeline — Per Condition

```mermaid
graph LR
    subgraph "Episode 1: Hi There!"
        E1[Welcome + Meet Health Buddy]
    end
    subgraph "Episode 2: What's Happening?"
        E2[Condition Explanation<br/>Age-Appropriate]
    end
    subgraph "Episode 3: Super Medicine!"
        E3[Each Med = Superpower<br/>How-To + Fun Facts]
    end
    subgraph "Episode 4: What's Next?"
        E4[Treatment Timeline<br/>Day-by-Day Expectations]
    end
    subgraph "Episode 5: Healthy Habits!"
        E5[Diet + Exercise + Sleep<br/>Daily Routines]
    end
    subgraph "Episode 6: Uh Oh Moments!"
        E6[Warning Signs<br/>When to Tell a Grown-Up]
    end
    subgraph "Episode 7: You Did It!"
        E7[Celebration + Recap<br/>Health Hero Certificate]
    end

    E1 --> E2 --> E3 --> E4 --> E5 --> E6 --> E7

    E1 -.- C1[🦸 Captain Welcome]
    E2 -.- C2[🦉 Dr. Owl]
    E3 -.- C3[🛡️ Super Shield]
    E4 -.- C4[🐢 Time Turtle]
    E5 -.- C5[🦊 Veggie Fox]
    E6 -.- C6[🦅 Alert Eagle]
    E7 -.- C7[⭐ Star Champion]
```

Each episode unlocks a **collectible Battle Card** + **picture-based quiz**.

---

## Supported Conditions

| Condition | Patient | Age | Key Medications |
|-----------|---------|-----|-----------------|
| Childhood Asthma | Lily Chen | 4 | Albuterol (rescue), Flovent (daily controller) |
| Ear Infection (Otitis Media) | Noah Martinez | 3 | Amoxicillin (antibiotic), Children's Tylenol |
| Acute Lymphoblastic Leukemia | Zara Thompson | 10 | Vincristine (chemo), Prednisone, Ondansetron |

---

## Tech Stack

| Layer | Technology | Purpose |
|-------|-----------|---------|
| **Frontend** | React 18, Vite, Tailwind CSS | Kid-friendly responsive UI |
| **Backend** | Node.js, Express.js | API orchestration server |
| **Video AI** | HeyGen API | Personalized avatar video generation |
| **Live AI** | HeyGen LiveAvatar + LiveKit | Real-time voice AI conversation |
| **Context AI** | Perplexity Sonar API | LLM-powered medical context layer |
| **Drug Data** | OpenFDA API | FDA drug labels, safety, indications |
| **Clinical Data** | DailyMed / NLM API | Clinical dosage, interactions |
| **Reminders** | Poke SDK (MCP) | Medication reminders via Model Context Protocol |
| **Real-Time** 

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 49 recognized source files, 430 KB.
- CSS (language) — detected in the code
- Express (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- Node.js (technology) — claimed on Devpost, not found in the code
- AI coding agent: Claude Code — evidence: commit authorship or trailers
- AI coding agent: Cursor — evidence: commit authorship or trailers

## Codebase structure (from repository index)

### Files (53 of 53)

```
.gitignore
index.html
package.json
postcss.config.js
README.md
server/.gitignore
server/clinicalSearch.js
server/contextEngine.js
server/dailymedClient.js
server/episodeScripts.js
server/heygenPrompt.js
server/index.js
server/mcp/api.js
server/mcp/createServer.js
server/mcp/main.js
server/mcp/tools/getMedicationSchedule.js
server/mcp/tools/getPatientContext.js
server/mcp/tools/index.js
server/mcp/tools/logMedicationTaken.js
server/mcp/tools/researchMedicalTopic.js
server/mcp/tools/searchClinicalEvidence.js
server/mcp/tools/sendReminder.js
server/openfdaClient.js
server/package.json
server/perplexitySonar.js
server/pokeMcp.js
server/README.md
src/api/clinicalDataTool.js
src/App.jsx
src/components/AIAssistant.jsx
src/components/CreateContent.jsx
src/components/DayCard.jsx
src/components/Header.jsx
src/components/LiveAvatar.jsx
src/components/Logo.jsx
src/components/MedicationReminders.jsx
src/components/RecoveryPlan.jsx
src/components/VideoPlayer.jsx
src/contexts/AuthContext.jsx
src/data/mockData.js
src/data/patientData.js
src/data/quizData.js
src/index.css
src/main.jsx
src/pages/DoctorPortal.jsx
src/pages/Landing.jsx
src/pages/Login.jsx
src/pages/PatientPortal.jsx
src/README.md
src/utils/storage.js
tailwind.config.js
VISUAL_STYLE_IMAGES_ADDED.md
vite.config.js
```

### Dependencies

- package.json: @vitejs/plugin-react@^4.3.4, autoprefixer@^10.4.20, livekit-client@^2.17.1, lucide-react@^0.460.0, postcss@^8.4.49, react@^18.3.1, react-dom@^18.3.1, react-router-dom@^6.28.0, tailwindcss@^3.4.16, vite@^6.0.3
- server/package.json: @modelcontextprotocol/sdk@^1.26.0, cors@^2.8.5, dotenv@^16.4.7, express@^4.21.2, poke@^0.2.5, zod@^3.22.0

### Recent commits (newest first)

- Enhance landing page hero section with professional design updates
- Updated README.md
- Changed README
- Merge pull request #6 from Michael-600/feat/poke3
- Merge remote-tracking branch 'origin/main' into feat/poke3
- Finished mcp with poke
- Added some changes
- Updated READMEs
- Merge branch 'main' of github.com:Michael-600/MedFlix
- Added new patients
- code
- Updated new logic for children with added functionalities
- Merge feat/poke into main, keeping main versions of conflicted files
- Merge remote main, resolve conflicts keeping local changes
- Add avatar selection for LiveAvatar, hardcoded episode scripts, key takeaways
- Refactor server and enhance medication reminders component
- Complete UI overhaul with gradient effects and improved accessibility
- Working version of Poke + ngrok + Twilio
- Added requirement to not use personal history to Poke
- Added improved prompting

## Key source files (fetched from GitHub, selected and truncated for size)

### VISUAL_STYLE_IMAGES_ADDED.md

```markdown
# Visual Style Images Added ✅

## Overview
Updated the visual style selector in the content creation workflow to display representative images for each animation/video style instead of just icons.

---

## Changes Made

### File Modified:
- ✅ `/src/data/mockData.js` - Added image URLs to `visualStyles` array

### Image URLs Added:

1. **Friends** (Live-action sitcom)
   - Image: Central Perk café scene from Wikimedia Commons
   - URL: `https://upload.wikimedia.org/wikipedia/commons/thumb/b/bc/Central_Perk_%289641298406%29.jpg/1280px-Central_Perk_%289641298406%29.jpg`

2. **Zootopia** (Disney 3D animation)
   - Image: Colorful animated style from Unsplash
   - URL: `https://images.unsplash.com/photo-1578632767115-351597cf2477?w=400&h=300&fit=crop`

3. **Anime** (Japanese 2D animation)
   - Image: Anime-style artwork from Unsplash
   - URL: `https://images.unsplash.com/photo-1578632292335-df3abbb0d586?w=400&h=300&fit=crop`

4. **The Office** (Mockumentary)
   - Image: Office environment from Unsplash
   - URL: `https://images.unsplash.com/photo-1497366216548-37526070297c?w=400&h=300&fit=crop`

5. **Pixar** (3D animation)
   - Image: 3D animated character style from Unsplash
   - URL: `https://images.unsplash.com/photo-1536440136628-849c177e76a1?w=400&h=300&fit=crop`

6. **Spider-Verse** (Comic book animation)
   - Image: Comic/pop-art style from Unsplash
   - URL: `https://images.unsplash.com/photo-1612036782180-6f0b6cd846fe?w=400&h=300&fit=crop`

7. **South Park** (Cutout animation)
   - Image: Colorful paper-craft style from Unsplash
   - URL: `https://images.unsplash.com/photo-1513364776144-60967b0f800f?w=400&h=300&fit=crop`

8. **Custom** (Your own style)
   - Image: None (remains icon-based with Settings icon)

---

## Technical Implementation

### Component Support:
The `CreateContent.jsx` component already had the logic to display images:

```jsx
{style.image ? (
  <img 
    src={style.image} 
    alt={style.name}
    className="w-24 h-24 rounded-2xl object-cover shadow-md"
  />
) : (
  <div className={`w-24 h-24 rounded-2xl flex items-center justify-center...`}>
    {/* Icon fallback */}
  </div>
)}
```

### Image Specifications:
- **Size**: 96×96px (w-24 h-24)
- **Border Radius**: rounded-2xl (16px)
- **Object Fit**: cover (maintains aspect ratio, fills container)
- **Shadow**: shadow-md (medium drop shadow)
- **Source**: Mix of Wikimedia Commons (Public Domain) and Unsplash (Free to use)

---

## Visual Impact

### Before:
- ❌ Generic icons with colored backgrounds
- ❌ Text-only descriptions ("Add image in mockData.js")
- ❌ Less engaging visual selection

### After:
- ✅ **Representative images** showing the actual visual style
- ✅ **Professional appearance** with high-quality photography
- ✅ **Easier selection** - users can see what the style looks like
- ✅ **Better UX** - visual recognition vs. reading descriptions

---

## Image Sources

### Wikimedia Commons:
- **Friends**: Public domain image of Central Perk set
- License: Public Doma
[truncated — 2357 more characters]
```

### package.json

```
{
  "name": "medflix",
  "private": true,
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "livekit-client": "^2.17.1",
    "lucide-react": "^0.460.0",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "react-router-dom": "^6.28.0"
  },
  "devDependencies": {
    "@vitejs/plugin-react": "^4.3.4",
    "autoprefixer": "^10.4.20",
    "postcss": "^8.4.49",
    "tailwindcss": "^3.4.16",
    "vite": "^6.0.3"
  }
}

```

### server/package.json

```
{
  "name": "medflix-server",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "node --watch index.js",
    "start": "node index.js",
    "mcp": "node pokeMcp.js"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.26.0",
    "cors": "^2.8.5",
    "dotenv": "^16.4.7",
    "express": "^4.21.2",
    "poke": "^0.2.5",
    "zod": "^3.22.0"
  }
}

```

### src/main.jsx

```javascript
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)

```

### src/App.jsx

```javascript
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
import { AuthProvider, useAuth } from './contexts/AuthContext'
import Login from './pages/Login'
import PatientPortal from './pages/PatientPortal'
import DoctorPortal from './pages/DoctorPortal'
import Landing from './pages/Landing'

function ProtectedRoute({ children, requiredRole }) {
  const { user, loading } = useAuth()
  if (loading) return <div className="flex items-center justify-center h-screen">Loading...</div>
  if (!user) return <Navigate to="/login" replace />
  // If requiredRole is specified and doesn't match, redirect to correct portal
  if (requiredRole && user.role !== requiredRole) {
    return <Navigate to={user.role === 'doctor' ? '/doctor' : '/portal'} replace />
  }
  return children
}

function AppRoutes() {
  const { user } = useAuth()

  const getDefaultRedirect = () => {
    if (!user) return <Landing />
    return <Navigate to={user.role === 'doctor' ? '/doctor' : '/portal'} replace />
  }

  return (
    <Routes>
      <Route path="/" element={getDefaultRedirect()} />
      <Route path="/login" element={user ? <Navigate to={user.role === 'doctor' ? '/doctor' : '/portal'} replace /> : <Login />} />
      <Route
        path="/portal"
        element={
          <ProtectedRoute>
            <PatientPortal />
          </ProtectedRoute>
        }
      />
      <Route
        path="/doctor"
        element={
          <ProtectedRoute requiredRole="doctor">
            <DoctorPortal />
          </ProtectedRoute>
        }
      />
      <Route path="*" element={<Navigate to="/" replace />} />
    </Routes>
  )
}

export default function App() {
  return (
    <BrowserRouter future={{ v7_startTransition: true, v7_relativeSplatPath: true }}>
      <AuthProvider>
        <AppRoutes />
      </AuthProvider>
    </BrowserRouter>
  )
}

```

### server/mcp/main.js

```javascript
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { api } from './api.js'
import { createMcpServer } from './createServer.js'
import { registerTools, TOOL_NAMES } from './tools/index.js'

export async function main() {
  const transport = new StdioServerTransport()
  const server = createMcpServer()

  registerTools(server, { api })
  await server.connect(transport)

  console.error('MedFlix MCP server running on stdio')
  console.error(`Tools: ${TOOL_NAMES.join(', ')}`)
}


```

### server/index.js

```javascript
import 'dotenv/config'
import express from 'express'
import cors from 'cors'
import { Poke } from 'poke'
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'
import { z } from 'zod'
import { getPerplexityKey, perplexitySonarChat } from './perplexitySonar.js'
import { buildHeyGenPrompt, buildPerplexityDeepResearchPrompt, safePreview } from './heygenPrompt.js'
import { buildEpisodeContext, gatherClinicalData, clearContextCache } from './contextEngine.js'
import { searchClinicalData } from './clinicalSearch.js'

const app = express()
app.use(cors())
app.use(express.json())
app.use(express.urlencoded({ extended: false }))

const HEYGEN_API_KEY = process.env.HEYGEN_API_KEY
const LIVEAVATAR_API_KEY = process.env.LIVEAVATAR_API_KEY
const PERPLEXITY_API_KEY = getPerplexityKey()
const POKE_API_KEY = process.env.POKE_API_KEY
const HEYGEN_DISABLED =
  String(process.env.HEYGEN_DISABLED || '').toLowerCase() === 'true' ||
  String(process.env.DISABLE_HEYGEN || '').toLowerCase() === 'true'
const HEYGEN_BASE = 'https://api.heygen.com'
const LIVEAVATAR_BASE = 'https://api.liveavatar.com'

// ─── Poke client ────────────────────────────────────────
const pokeClient = POKE_API_KEY ? new Poke({ apiKey: POKE_API_KEY }) : null

// ─── Helper ────────────────────────────────────────────
async function heygenFetch(path, opts = {}) {
  const res = await fetch(`${HEYGEN_BASE}${path}`, {
    ...opts,
    headers: {
      'Content-Type': 'application/json',
      'X-Api-Key': HEYGEN_API_KEY,
      ...opts.headers,
    },
  })
  return res.json()
}

async function liveAvatarFetch(path, opts = {}) {
  const res = await fetch(`${LIVEAVATAR_BASE}${path}`, {
    ...opts,
    headers: {
      'Content-Type': 'application/json',
      'X-Api-Key': LIVEAVATAR_API_KEY,
      ...opts.headers,
    },
  })
  return res.json()
}

// Simple in-memory cache to avoid repeat Sonar calls during bulk generation
const sonarCache = new Map()
const SONAR_CACHE_TTL_MS = 10 * 60 * 1000

function getCachedSonar(key) {
  const item = sonarCache.get(key)
  if (!item) return null
  if (Date.now() > item.expiresAt) {
    sonarCache.delete(key)
    return null
  }
  return item.value
}

function setCachedSonar(key, value) {
  sonarCache.set(key, { value, expiresAt: Date.now() + SONAR_CACHE_TTL_MS })
}

// ═══════════════════════════════════════════════════════
//  HEYGEN  –  Care Video Generation
// ═══════════════════════════════════════════════════════

// List available avatars
app.get('/api/heygen/avatars', async (_req, res) => {
  try {
    const data = await heygenFetch('/v2/avatars')
    res.json(data)
  } catch (e) {
    console.error('avatars error:', e)
    res.status(500).json({ error: e.message })
  }
})

// List available voices
app.get('/api/heygen/voices', async (_req, res) => {
  try {
    const data = await heygenFetch('/v2/voices')
    res.json(data)
  } catch (e) {
    console.error('voices error:', e)
    res.status(500).json({ error: e.message })
  }
})

// Generate video via Video Agent (prompt → video) — legacy endpoint
app.post('/api/heygen/generate-video', async (req, res) => {
  try {
    const {
      prompt: basePrompt,
      avatar_id,
      duration_sec,
      orientation,
      patient,
      episode,
      clinicalContext,
      // Legacy support: accept openEvidence too
      openEvidence,
      use_sonar,
      sonar_model,
      sonar_search_recency_filter,
    } = req.body

    const contextData = clinicalContext || openEvidence || null

    let sonar = null
    if (use_sonar && PERPLEXITY_API_KEY) {
      const sonarMessages = buildPerplexityDeepResearchPrompt({
        patient,
        episode,
        clinicalContext: contextData,
      })
      const cacheKey = JSON.stringify({
        model: sonar_model || 'sonar',
        search_recency_filter: sonar_search_recency_filter || 'month',
        messages: sonarMessages,
      })
      const cached = getCachedSonar(cacheKey)
      if (cached) {
        sonar = cached
      } else {
        const sonarResult = await perplexitySonarChat({
          apiKey: PERPLEXITY_API_KEY,
          model: sonar_model || 'sonar',
          messages: sonarMessages,
          search_recency_filter: sonar_search_recency_filter || 'month',
        })
        if (sonarResult.ok) {
          sonar = sonarResult.data
          setCachedSonar(cacheKey, sonar)
        } else {
          console.warn('[Perplexity] Sonar call failed:', sonarResult.error, safePreview(sonarResult.detail))
        }
      }
    }

    const prompt = buildHeyGenPrompt({
      basePrompt: basePrompt,
      patient,
      episode,
      clinicalContext: contextData,
      sonarResearch: sonar,
    })

    if (HEYGEN_DISABLED || !HEYGEN_API_KEY) {
      return res.json({
        data: {
          video_id: null,
        },
        error: HEYGEN_DISABLED ? 'heygen_disabled' : 'missing_heygen_api_key',
        medflix: {
          prompt,
          used_sonar: Boolean(use_sonar && PERPLEXITY_API_KEY && sonar?.content),
          sonar_model: use_sonar ? (sonar_model || 'sonar') : null,
          sonar_content: sonar?.content || null,
          sonar_preview: sonar?.content ? safePreview(sonar.content, 800) : null,
        },
      })
    }

    const body = { prompt }
    if (avatar_id || duration_sec || orientation) {
      body.config = {}
      if (avatar_id) body.config.avatar_id = avatar_id
      if (duration_sec) body.config.duration_sec = duration_sec
      if (orientation) body.config.orientation = orientation
    }
    const data = await heygenFetch('/v1/video_agent/generate', {
      method: 'POST',
      body: JSON.stringify(body),
    })
    res.json({
      ...data,
      medflix: {
        prompt,
        used_sonar: Boolean(use_sonar && PERPLEXITY_API_KEY && sonar?.content),
        sonar_model: use_sonar ? (sonar_model || 'sonar') : null,
        sonar_content: sonar?.content || nu
[truncated — 28872 more characters]
```

### server/mcp/tools/index.js

```javascript
import { registerGetMedicationScheduleTool } from './getMedicationSchedule.js'
import { registerGetPatientContextTool } from './getPatientContext.js'
import { registerLogMedicationTakenTool } from './logMedicationTaken.js'
import { registerResearchMedicalTopicTool } from './researchMedicalTopic.js'
import { registerSearchClinicalEvidenceTool } from './searchClinicalEvidence.js'
import { registerSendReminderTool } from './sendReminder.js'

export const TOOL_NAMES = [
  'get_patient_context',
  'get_medication_schedule',
  'search_clinical_evidence',
  'research_medical_topic',
  'log_medication_taken',
  'send_reminder',
]

export function registerTools(server, deps) {
  registerGetPatientContextTool(server, deps)
  registerGetMedicationScheduleTool(server, deps)
  registerSearchClinicalEvidenceTool(server, deps)
  registerResearchMedicalTopicTool(server, deps)
  registerLogMedicationTakenTool(server, deps)
  registerSendReminderTool(server, deps)
}


```

### postcss.config.js

```javascript
export default {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

```

### vite.config.js

```javascript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  server: {
    port: 5173,
    open: true,
    proxy: {
      '/api': {
        target: 'http://localhost:3001',
        changeOrigin: true,
      },
    },
  },
})

```

[37 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]