IMPLEMENTATION_PLAN.mdโข23.6 kB
# ๐ Medplum MCP Server Implementation Plan
This plan outlines the steps to build a Model Context Protocol (MCP) server for interacting with a Medplum FHIR server via a chat-based interface powered by an LLM. The MCP server will provide standardized tools for creating, reading, updating, and searching FHIR resources.
**โ ๏ธ Important Note:** The current implementation includes FHIR resource tools and an OpenAI test harness, but **lacks the actual MCP server protocol implementation**. The existing `llm-test-harness.ts` is a direct OpenAI integration, not a standardized MCP server that can be used by MCP clients.
**General Workflow Note:** After completing each phase or significant step that results in runnable code or configuration changes, we will identify and execute relevant test or verification commands. For commands requiring interaction with the local Medplum instance or user-specific setup, I will prompt you to run them and then analyze the output you provide. For self-contained checks (e.g., linting), I may run the command directly.
## Phase 1: Project Setup & Foundation
- [x] **Initialize Node.js Project & Install Dependencies**
- Notes:
- Ran `npm init -y`.
- Installed `@medplum/core`, `@medplum/fhirtypes`.
- Installed dev dependencies: `typescript`, `@types/node`, `ts-node-dev`, `nodemon`, `eslint`, `prettier`, `@typescript-eslint/*`, `eslint-config-prettier`, `eslint-plugin-prettier`, `jest`, `@types/jest`, `ts-jest`.
- Installed `dotenv` to manage environment variables.
- Installed `node-fetch@^2` and `@types/node-fetch@^2` for explicit fetch in MedplumClient.
- [x] **Configure TypeScript**
- Notes:
- Created `tsconfig.json` using `npx tsc --init`.
- Configured `rootDir`, `outDir`, `target`, `module`, `esModuleInterop`, `strict`, `skipLibCheck`, `resolveJsonModule`, `include`, `exclude` as per plan.
- [x] **Setup Project Structure**
- Notes:
- Created directories: `src`, `src/tools`, `src/config`, `src/lib`, `tests`.
- Created `src/index.ts` as the main entry point.
- [x] **Implement ESLint and Prettier**
- Notes:
- Created `.eslintrc.js`, `.prettierrc.js`, and `.prettierignore`.
- Added `lint`, `format`, `build`, `dev`, `start` scripts to `package.json`.
- [x] **Medplum Client Configuration**
- Notes:
- Created `src/config/medplumClient.ts`.
- Initialized Medplum client to connect to `http://localhost:8103/` (or `MEDPLUM_BASE_URL` from `.env`).
- Implemented `ensureAuthenticated` function using Client ID/Secret from `.env` (via `dotenv`).
- Explicitly configured `fetch` using `node-fetch`.
- [x] **Basic Connection Test**
- Notes:
- Created `src/test-connection.ts`.
- Script successfully authenticates using client credentials from `.env`.
- Script successfully fetches server capabilities using `medplum.get('fhir/R4/metadata')`.
- Script successfully fetches the client application's profile using `medplum.getProfile()`.
- Added `test:connection` script to `package.json` and verified it runs.
- Runtime successful, though linter shows type errors for `get` and `getProfile` (using `@ts-ignore` for now).
## Phase 1.5: Initial Tool & LLM Test Harness
- [x] **Implement a Single Simple MCP Tool**
- Notes:
- Choose one straightforward tool from Phase 2, e.g., `searchPractitioners` or a simplified version of `createPatient`.
- Implement this tool in `src/tools/` using the Medplum SDK. (Implemented `searchPractitionersByName` in `src/tools/practitionerSearch.ts`)
- [x] **Develop Basic LLM Test Harness (Simulated Chat)**
- Notes:
- Created a script (`src/llm-test-harness.ts`) that takes natural language strings as input.
- This harness will send the input to an LLM (via its API, configured with the schema of the implemented tool).
- The purpose is to observe and verify the tool call generated by the LLM, not to build a user-facing chat UI at this stage. (Implemented `src/llm-test-harness.ts` using OpenAI GPT-4o)
- [x] **Define Schema and Configure LLM for the Initial Tool**
- Notes:
- Create the JSON schema for the first implemented tool (as described in Phase 3).
- Configure your chosen LLM to use this tool. (Created `src/tools/toolSchemas.ts` and used it in the test harness with GPT-4o)
- [x] **Write Initial LLM Integration Test Case**
- Notes:
- Formulate a natural language query that should trigger the implemented tool.
- Send this query through the test harness.
- Verify that the LLM attempts to call the correct tool with the correct parameters.
- This forms the first test in our iterative LLM integration suite. (Included several test queries in `llm-test-harness.ts` `main` function, verified LLM tool selection and parameter extraction)
## Phase 2: Core FHIR Resource Tools (MCP Server Tools)
For each resource, we need tools for `create`, `readById`, `update`, and `search`. Each tool should be a well-defined function in `src/tools/`.
- [x] **Patient Tools (`src/tools/patientUtils.ts`)**
- Description: Tools for managing Patient resources.
- [x] **`createPatient(patientDetails: CreatePatientArgs)`**: Creates a new patient.
- SDK: `medplum.createResource<Patient>(patientResource)`
- LLM Integration: Successfully integrated; LLM correctly extracts details from natural language.
- [x] **`getPatientById(patientId: string)`**: Retrieves a patient by their ID.
- SDK: `medplum.readResource<Patient>('Patient', patientId)`
- LLM Integration: Successfully integrated; LLM correctly extracts `patientId`.
- [x] **`updatePatient(patientId: string, updates: Omit<Partial<Patient>, 'resourceType' | 'id'>)`**: Updates an existing patient.
- SDK: `medplum.updateResource<Patient>(patientToUpdate)`
- LLM Integration: Operationally successful. LLM doesn't perfectly adhere to nested `updates` schema, but a workaround in `llm-test-harness.ts` reconstructs the arguments correctly.
- [x] **`searchPatients(searchArgs: PatientSearchArgs)`**: Searches for patients based on criteria.
- SDK: `medplum.searchResources<Patient>('Patient', medplumSearchCriteria)`
- LLM Integration: Successfully integrated. Schema uses direct parameters (e.g., `family`, `given`), and the LLM correctly provides these. `patientUtils.ts` handles conversion to Medplum SDK format and prevents empty searches.
- Notes:
- Schemas defined in `src/tools/toolSchemas.ts`.
- Integrated into `availableTools` in `src/llm-test-harness.ts`.
- Iterative refinement of schemas and harness logic was key to successful LLM interaction.
- Jest integration tests created and passing (`tests/integration/patient.integration.test.ts`).
- [x] **Practitioner Tools (`src/tools/practitionerUtils.ts`)**
- Description: Tools for managing Practitioner resources.
- [x] **`searchPractitionersByName(nameArgs: PractitionerNameSearchParams)`**
- SDK: `medplum.searchResources<Practitioner>('Practitioner', searchCriteria)`
- LLM Integration: Successfully integrated; LLM correctly extracts name parameters.
- Notes: Schema defined in `src/tools/toolSchemas.ts`.
- [x] **`createPractitioner(args: CreatePractitionerArgs)`**
- SDK: `medplum.createResource<Practitioner>(practitionerResource)`
- [x] **`getPractitionerById(practitionerId: string)`**
- SDK: `medplum.readResource<Practitioner>('Practitioner', practitionerId)`
- [x] **`updatePractitioner(practitionerId: string, updates: UpdatePractitionerArgs | Omit<Partial<Practitioner>, 'resourceType' | 'id'>)`**
- SDK: `medplum.updateResource<Practitioner>(practitionerToUpdate)`
- [x] **`searchPractitioners(criteria: PractitionerSearchCriteria)`**
- SDK example: `medplum.searchResources<Practitioner>('Practitioner', criteria)`
- Notes: For "Dr. Tom", criteria would be `{ name: 'Tom' }` or `{ family: 'Stevens' }`. LLM needs to map titles appropriately.
- Notes:
- Schemas defined in `src/tools/toolSchemas.ts`.
- Integrated into `availableTools` and tested in `src/llm-test-harness.ts`.
- Jest integration tests created and passing (`tests/integration/practitioner.integration.test.ts`).
- [x] **Tool: `manageOrganization`**
- Description: Handles Organization resources (e.g., hospitals, clinics).
- Sub-actions:
- [x] `createOrganization(args: CreateOrganizationArgs)`
- [x] `getOrganizationById(organizationId: string)`
- [x] `updateOrganization(organizationId: string, updates: UpdateOrganizationArgs)`
- [x] `searchOrganizations(criteria: OrganizationSearchCriteria)`
- Notes: Useful for linking practitioners and encounters to specific facilities.
- Schemas to be defined in `src/tools/toolSchemas.ts`.
- To be integrated into `availableTools` in `src/llm-test-harness.ts`.
- Jest integration tests created and passing (`tests/integration/organization.integration.test.ts`).
- [x] **Tool: `manageEncounter`**
- Description: Handles Encounter resources (e.g., doctor visits, hospital stays).
- Sub-actions:
- [x] `createEncounter(details: CreateEncounterArgs)`: Links to Patient, Practitioner(s), Organization.
- [x] `getEncounterById(id: string)`
- [x] `updateEncounter(id: string, updates: UpdateEncounterArgs)`
- [x] `searchEncounters(criteria: EncounterSearchArgs)`
- Notes: Encounters are central to tracking patient interactions. `status`, `class`, `type`, `subject`, `participant`, `serviceProvider` are key. Implemented with corresponding utils, schemas, test harness updates, and integration tests (`tests/integration/encounter.integration.test.ts`).
- [x] **Tool: `manageObservation`**
- Description: Handles Observation resources (e.g., lab results, vital signs, X-ray findings).
- Sub-actions:
- `createObservation(details: CreateObservationArgs)`: Links to Encounter, Patient.
- `getObservationById(id: string)`
- `updateObservation(id: string, updates: UpdateObservationArgs)`
- `searchObservations(criteria: ObservationSearchArgs)`
- Notes: `code` (LOINC, SNOMED CT), `value[x]`, `subject`, `encounter` are key fields. "Sed test" would map to a specific LOINC code. "X-ray" also.
- [x] **Tool: `manageMedicationRequest`**
- Description: Handles MedicationRequest resources (prescriptions).
- Sub-actions:
- `createMedicationRequest(details: MedicationRequest)`: Links to Patient, Practitioner, Medication.
- `getMedicationRequestById(id: string)`
- `updateMedicationRequest(id: string, updates: Partial<MedicationRequest>)`
- `searchMedicationRequests(criteria: { patientId?: string, medicationCode?: string, prescriberId?: string })`
- Notes: Differentiate between MedicationRequest and Medication (the actual drug).
- [x] **Tool: `manageMedication`**
- Description: Handles Medication resources (details about a specific drug).
- Sub-actions:
- [x] `createMedication(details: CreateMedicationArgs)` (less common, often Medications are pre-loaded or referenced)
- [x] `getMedicationById(id: string)`
- `searchMedications(criteria: { code?: string, identifier?: string, status?: string })`
- Notes: Used by MedicationRequest. Might involve searching a drug database or pre-loaded list.
- Schemas defined in `src/tools/toolSchemas.ts`.
- Utility functions in `src/tools/medicationUtils.ts`.
- Integration tests in `tests/integration/medication.integration.test.ts` passed.
- [x] **Tool: `manageEpisodeOfCare`**
- Description: Handles EpisodeOfCare resources to group related encounters and conditions.
- Sub-actions:
- [x] `createEpisodeOfCare(details: CreateEpisodeOfCareArgs)`: Links to Patient, managingOrganization.
- [x] `getEpisodeOfCareById(id: string)`
- [x] `updateEpisodeOfCare(id: string, updates: UpdateEpisodeOfCareArgs)` (e.g., change status, add encounters)
- [x] `searchEpisodesOfCare(criteria: EpisodeOfCareSearchArgs)`
- Notes: Crucial for the "ongoing health episode" concept. Encounters, conditions, procedures can be linked.
- Schemas defined in `src/tools/toolSchemas.ts`.
- Utility functions in `src/tools/episodeOfCareUtils.ts`.
- Integration tests in `tests/integration/episodeOfCare.integration.test.ts` passed.
- [x] **Tool: `manageCondition`**
- Description: Handles Condition resources (diagnoses, problems, health concerns, clinical findings).
- Sub-actions:
- [x] `createCondition(details: CreateConditionArgs)`: Creates new diagnosis/condition for a patient.
- [x] `getConditionById(id: string)`: Retrieves condition details by ID.
- [x] `updateCondition(id: string, updates: UpdateConditionArgs)`: Updates condition status, severity, etc.
- [x] `searchConditions(criteria: ConditionSearchArgs)`: Searches conditions by patient, code, category, clinical status.
- Notes: Central to clinical workflows. Key fields include `code` (ICD-10, SNOMED CT), `clinicalStatus` (active, inactive, resolved), `verificationStatus` (confirmed, provisional), `category` (problem-list-item, encounter-diagnosis), `subject`, `encounter`, `onsetDateTime`, `recordedDate`.
- Examples: "Type 2 Diabetes", "Hypertension", "Broken leg", "Major depressive disorder"
- Links to Patient, Encounter, and can reference supporting Observations
- Essential for care planning, quality metrics, and clinical decision support
- Schemas defined in `src/tools/toolSchemas.ts`.
- Utility functions in `src/tools/conditionUtils.ts`.
- Integration tests in `tests/integration/condition.integration.test.ts` passed.
- Fully integrated into MCP server with argument transformation logic.
- [ ] **Tool: `manageDiagnosticReport`**
- Description: Handles DiagnosticReport resources (findings and interpretations of diagnostic tests).
- Sub-actions:
- `createDiagnosticReport(details: CreateDiagnosticReportArgs)`: Creates new diagnostic report with findings.
- `getDiagnosticReportById(id: string)`: Retrieves diagnostic report details by ID.
- `updateDiagnosticReport(id: string, updates: UpdateDiagnosticReportArgs)`: Updates report status, conclusions, etc.
- `searchDiagnosticReports(criteria: DiagnosticReportSearchArgs)`: Searches reports by patient, code, category, date.
- Notes: Aggregates and interprets diagnostic findings. Key fields include `code` (LOINC for lab panels, SNOMED for imaging), `status` (registered, partial, preliminary, final), `category` (LAB, RAD, PATH, etc.), `subject`, `encounter`, `result` (references to Observations), `conclusion`, `conclusionCode`.
- Examples: "Complete Blood Count", "Chest X-Ray Report", "Pathology Report", "Cardiac Stress Test"
- Links individual Observations into comprehensive diagnostic findings
- Critical for clinical interpretation and medical decision-making
- [ ] **Tool: `manageProcedure`**
- Description: Handles Procedure resources (actions performed on patients).
- Sub-actions:
- `createProcedure(details: CreateProcedureArgs)`: Records new procedure performed on patient.
- `getProcedureById(id: string)`: Retrieves procedure details by ID.
- `updateProcedure(id: string, updates: UpdateProcedureArgs)`: Updates procedure status, outcome, etc.
- `searchProcedures(criteria: ProcedureSearchArgs)`: Searches procedures by patient, code, category, date.
- Notes: Documents interventions and actions. Key fields include `code` (CPT, SNOMED CT, ICD-10-PCS), `status` (preparation, in-progress, completed, stopped), `category` (surgical, diagnostic, therapeutic), `subject`, `encounter`, `performedDateTime`, `performer`, `reasonCode`, `reasonReference`, `outcome`, `bodySite`.
- Examples: "Appendectomy", "Colonoscopy", "Physical Therapy", "Blood Draw", "Surgical Wound Cleaning"
- Links to Patient, Encounter, Practitioner, and can reference Conditions as reasons
- Essential for billing, care coordination, and clinical documentation
- [x] **Tool: `GeneralFhirSearch`**
- Description: A more generic search tool if specific resource type is unknown or for broader queries.
- Action: `searchResource(resourceType: FHIR_RESOURCE_TYPE, queryParams: Record<string, string | number | boolean | string[]>)`
- SDK: `medplum.search(resourceType, queryParams)`
- Notes: Provides flexibility but requires careful query construction.
- Schema defined in `src/tools/toolSchemas.ts`.
- Utility function in `src/tools/generalFhirSearchUtils.ts`.
- Integration tests in `tests/integration/generalFhirSearch.integration.test.ts` passed.
## Phase 3: MCP Server Protocol Implementation
**Status: โ
COMPLETE**
- [x] **Install MCP SDK Dependencies** โ Add the official MCP TypeScript SDK and remove OpenAI-specific dependencies
**Notes:**
- Installed `@modelcontextprotocol/sdk` version 1.0.0
- Added `zod` for schema validation (required by MCP SDK)
- Removed `openai` dependency
- Updated package.json with bin entry for `medplum-mcp-server`
- Temporarily disabled `llm-test-harness.ts` (will be replaced with MCP testing)
- Build and install completed successfully
- [x] **Create MCP Server Infrastructure** โ Build the core MCP server using the official SDK pattern with stdio transport
**Notes:**
- Created MCP server with stdio transport using official SDK
- Implemented basic tool list handler and tool execution handler
- Set up proper MCP protocol initialization and response handling
- Added graceful shutdown handlers
- Imported all existing FHIR tool functions
- Started with subset of tool schemas (Patient and Practitioner tools)
- Successfully tested MCP protocol initialization and tools/list endpoint
- Server builds and runs correctly, responding to MCP protocol messages
- [x] **Convert Tool Schemas from OpenAI to MCP Format** โ Transform existing tool schemas in toolSchemas.ts to MCP-compatible format
**Notes:**
- Converted all 22 FHIR tools from OpenAI function calling format to MCP inputSchema format
- Added comprehensive tool schemas for Patient, Practitioner, Organization, Encounter, Observation, and GeneralFhirSearch tools
- All tools now properly registered with MCP server and returning correct tool counts
- Successfully tested tool execution with searchPatients - server authenticates with Medplum and executes FHIR operations
- MCP JSON-RPC responses are properly formatted and working
- Tool argument handling works correctly for different argument patterns (ID-based, update, search, etc.)
## Phase 4: LLM Interaction & Orchestration
- [x] **Define Tool Schema for LLM**
- Notes: JSON schemas defined in `src/tools/toolSchemas.ts` for all implemented Patient and Practitioner tools. Iteratively refined based on LLM performance (e.g., `searchPatients` uses direct parameters).
- [x] **LLM Request Handling in MCP (via Chat Harness)**
- Notes: `src/llm-test-harness.ts` serves as the current entry point. It processes natural language queries, invokes the LLM with tool schemas, and executes the chosen tool by calling the corresponding TypeScript function.
- It parses the LLM's tool call, validates (implicitly by checking tool name in `availableTools` map), calls the MCP server tool, and logs the result.
- [ ] **Natural Language to Tool Parameter Mapping Strategy**
- Notes: This is largely an LLM task (using the tool schemas), but the MCP tool design should facilitate this.
- The LLM needs to extract entities like "Dr. Stevens", "PCP", "x-ray", "sed test", "steroids", "rheumatologist" and map them to the correct parameters for the MCP tools.
- For "PCP", the LLM might first call `searchPractitioners` with name "Stevens" and then the application logic (or LLM itself) might need to confirm if this is the PCP, potentially by checking `Encounter` history or `EpisodeOfCare.careManager`.
- [ ] **Clarifying Questions Logic (MCP Server Support)**
- Notes: If the LLM cannot fill all required parameters for a tool, or if there's ambiguity, it needs to ask the user.
- The MCP server might need to support this by:
- Tools returning information that helps the LLM formulate a question (e.g., if a search returns multiple practitioners).
- Potentially, tools that can present options to the user via the LLM (e.g., "Did you mean Dr. John Stevens or Dr. Jane Stevens?").
- [ ] **Context Management & Resource Linking**
- Notes:
- The LLM needs to maintain conversation context to understand references like "his PCP", "the osteodoctor from last week".
- When creating new resources (e.g., an `Observation` for the sed test), the MCP server tools must accept parameters to link them to existing resources (e.g., `encounterId`, `patientId`).
- Example flow for "Dr. Stevens ordered a blood panel":
1. LLM identifies "Dr. Stevens" -> `searchPractitioners(name: 'Stevens')` -> gets Practitioner ID.
2. LLM identifies "blood panel", "sed test" -> maps to Observation codes.
3. LLM identifies the current Encounter (or creates one if necessary, linking to Patient and Dr. Stevens).
4. LLM calls `createObservation`
## Phase 5: Advanced Features & Production Readiness
- [ ] **Implement Advanced Features**
- Notes:
- Add new tools or enhance existing ones
- Implement advanced features like multi-step workflows, conditional logic, and complex decision-making
- Ensure compatibility with existing tools and integration points
- [ ] **Production Readiness**
- Notes:
- Perform thorough testing and validation
- Ensure stability and reliability
- Prepare for production deployment
- [x] **Test with MCP Inspector** โ Verify the server works correctly using the official MCP Inspector before testing with actual clients
**Notes:**
- Successfully launched MCP Inspector with our server using `npx @modelcontextprotocol/inspector node dist/index.js`
- Inspector is accessible at http://localhost:6274 for visual testing and debugging
- Can test all 22 FHIR tools through the Inspector's web interface
- Inspector provides proper protocol testing, tool discovery, and execution testing
- Server successfully responds to MCP protocol messages and tool execution requests
- Ready for integration testing with actual MCP clients like Claude Desktop
- [x] **Interactive Chat Harness** โ Build a comprehensive chat interface for testing the MCP server
**Notes:**
- Created a full MCP client chat harness in `src/llm-test-harness.ts`
- Integrates OpenAI GPT-4o for natural language processing
- Automatically discovers and uses all 22 MCP tools
- Provides interactive command-line interface with help system
- Maintains conversation context and handles tool execution
- Successfully tested with real FHIR operations (patient creation, search, etc.)
- Provides the best testing experience for development and validation
---
*For detailed implementation notes and technical details, see the commit history and individual tool documentation.*