Command Palette

Search for a command to run...

UnylyUnyly
Browse all

ICD-10-CM Medical Coding

FreeNot checked

Provides ICD-10-CM medical coding tools with a RAG pipeline for clinical note analysis.

GitHubEmbed

About

Provides ICD-10-CM medical coding tools with a RAG pipeline for clinical note analysis.

README

A Model Context Protocol (MCP) server that provides AI agents with accurate ICD-10-CM medical coding capabilities through a 3-stage RAG pipeline.


What Is This Project?

This is an ICD-10-CM MCP (Model Context Protocol) server that lets any AI agent, Claude Desktop, LangGraph agent, or web application look up and assign accurate ICD-10-CM billing codes from plain-English clinical notes or medical queries.

ICD-10-CM codes are the standardized diagnostic codes used in healthcare for insurance claims, medical records, and billing. Instead of manually searching through massive code books, AI agents can use this server to:

  • Search for codes using natural language medical terms
  • Automatically code clinical notes by extracting diagnoses and matching them to the most accurate ICD-10 codes with clinical rationale

The Two Tools

This server exposes two tools that any MCP-compatible AI agent can call:

l 3-Stage RAG Pipeline

What it does: Takes a complete clinical note (lab report, discharge summary, progress note) and returns structured ICD-10 codes with rationale.

When to use: You have a full clinical document and need accurate billing codes.

Speed: ~15-30 seconds (calls LLM twice: planner + selector)

Example:

Input: Lab report showing elevated glucose, HbA1c 9.2%, creatinine 2.1, eGFR 38
Output: 
  - E11.22 (Type 2 diabetes mellitus with diabetic chronic kidney disease)
  -ic kidney disease, stage 3)
  - Clinical rationale for each code selection

2. search_icd10 — Fast Semantic Vector Search

What it does: Takes a plain-English medical query and returns the top matching ICD-10 codes.

When to use: Quick lookups when you know the condition but need the code.

Speed: ~2-3 seconds

Example:

Query: "type 2 diabetes with chronic kidney disease"
Returns: E11.22, E11.9, N18.3, etc. with descriptions and relevance scores

lly — The RAG Pipeline

The code_clinical_note tool uses a 3-stage RAG (Retrieval-Augmented Generation) pipeline to ensure accurate, clinically-sound ICD-10-CM code assignment.

Stage 1 — Planner (planner.py)

Goal: Extract clinical problems from the note and generate optimized search queries.

How it works:

  1. Takes the raw clinical note as input
  2. Sends it to an LLM via OpenRouter (default: openai/gpt-4o-mini) with a detailed system prompt that acts as a "clinical query planner"
  3. The LLM analyzes the note and extracts a structured list of clinical problems
    • Example problem", "Anemia in CKD"
  4. For each problem, the LLM generates 2–4 semantic search queries optimized for matching ICD-10-CM code titles in a vector database
    • Example queries for "Type 2 diabetes with CKD":
      • "type 2 diabetes mellitus with diabetic chronic kidney disease"
      • "T2DM with renal complications"
      • "diabetes mellitus type 2 with nephropathy"
  5. Returns structured JSON:
    {
      "problems": [
        {
          "problem": "Type 2 diabetes mellitus with chronic kidney disease",
          "confidence": "high",
          "queries": ["...", "...", "..."]
        }
      ]
    }
    

Why this matters: The planner ensures we search for the right things. A raw clinical note might say "elevated HbA1c 9.2% with reduced eGFR 38" — the planner translates this into proper medical terminology that matches ICD-10 code descriptions.

Stage 2 — Retriever (retriever.py)

Goal: Find the best candidate ICD-10 codes for each clinical problem using vector search and ranking fusion.

it works:**

  1. Batch Embedding: Takes all queries from the planner (across all problems) and batch-embeds them in a single API call to OpenRouter (default: openai/text-embedding-3-small)

    • This is much faster than embedding queries one-by-one
  2. Vector Search: For each problem, runs multiple Pinecone vector queries (one per query generated by the planner)

    • Each query retrieves top_k=16 results from the Pinecone index
    • The Pinecone index contains pre-embedded ICD-10-CM ctadata)
  3. Reciprocal Rank Fusion (RRF): Combines results from multiple queries using RRF scoring

    • RRF formula: score = Σ(1 / (k + rank)) where k=60
    • This ensures codes that appear in multiple query results get boosted
  4. Lexical Re-ranking: Adds a lexical similarity score to catch exact term matches

    • Token overlap: measures how many words from the query appear in the code title
    • SequenceMatcher: measures character-level similarity
    • Final score: 75% RRF + 25% lexical
  5. De-duplication: Returns up to 40 unique candidate codes per problem, sorted by final score

Why this matters: A single query might miss relevant codes. By generating multiple queries and fusing results, we cast a wider net while still prioritizing the most relevant codes. The lexical re-ranking ensures we don't miss codes with exact terminology matches.

Stage 3 — Selector (selector.py)

Goal: Select the most accurate ICD-10 codes from the candidates using clinical coding rules.

How it works:

  1. Takes the original clinical note + all candidate codes per problem
  2. Sends everything to an LLM via OpenRouter (default: openai/gpt-4o-mini) with a strict system prompt that applies ICD-10-CM coding rules:
    • Prefer most-specific combination codes (e.g., E11.22 over E11.9 + N18.3)
    • Enforce etiology-manifestation coding (code the underlying cause first)
    • Drop parent codes when child codes are present (e.g., if E11.22 is selected, don't also include E11) codes across problems** (each code appears only once)
    • Avoid "unspecified" codes when specific codes are available
  3. The LLM reviews each candidate and decides whether to include it, providing clinical rationale
  4. Returns final selected codes with rationale per problem:
    {
      "results": [
        {
          "problem": "Type 2 diabetes with CKD",
          "selected_codes": [
            {
              "code": "E11.22",
              "title": "Type 2 diabetes mellitus disease",
              "rationale": "Combination code captures both diabetes and CKD relationship"
            }
          ]
        }
      ]
    }
    

Why this matters: Vector search alone can return too many codes or miss coding rules. The selector applies clinical expertise to choose the right codes and explain why, ensuring the output is billable and clinically accurate.

MCP Protocol Layer (server.py)

Goal: Expose the pipeline as MCP tools that any AI agent can call.

How it works:

  • Built on the mcp Python SDK
  • Supports two transports:
    • stdio — for local Claude Desktop use (no network, runs as subprocess)
    • HTTP/SSE — for remote use by LangGraph, web apps, agents, n8n, etc.
  • Per-request OpenRouter API key injection via X-OpenRouter-API-Key header
    • Users bring their own OpenRouter key; the server never stores it
    • This allows the hosted server to be used by anyone without exposing API keys
  • Health check endpoint at /health for monitoring and uptime checks

Architecture:

Client (Claude Desktop, LangGraph, etc.)
    │
    ▼
MCP Server (stdio or HTTP/SSE)
    │
    ├─► Planner ──► OpenRouter LLM (extract problems + queries)
    │
    ├─► Retriever ──► OpenRouter Embeddings + Pinecone (vector search + RRF)
    │
    └─► Selector ──► OpenRouter LLM (select codes + rationale)

Prerequisites

Before using this MCP server, you need:

  1. Python 3.10 or higher installed on your system

    • Index name: icd10cm-2026
    • Namespace: icd10cm_2026
    • The index should contain embedded ICD-10-CM codes with metadata (code, title, parent_code, level)
  2. An OpenRouter API key — get one at openrouter.ai

  3. Environment variables (for local/self-hosted use):

    • PINECONE_API_KEY — your Pinecone API key
    • PINECONE_INDEX_NAME — default: icd10cm-2026
    • PINECONE_NAMESPACE — default: icd10cm_2026
    • OPENROUTER_API_KEY — your OpenRouter API key

Integration Examples — How to Use This MCP Server

This server can be integrated with any MCP-compatible AI agent or application. Below are integration guides for common use cases.

A. Claude Desktop (Remote Hosted Server)

Connect Claude Desktop to the hosted server at https://icd10-mcp.onrender.com/sse using the supergateway bridge.

Step 1: Copy the configuration from claude_desktop_config_example.json:

{
  "mcpServers": {
    "icd10-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "supergateway",
        "--sse",
        "https://icd10-mcp.onrender.com/sse",
        "--header",
        "X-OpenRouter-API-Key: sk-or-v1-YOUR_OPENROUTER_API_KEY_HERE"
      ]      
    }
  }
}

Step 2: Locate your Claude Desktop config file:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json

Step 3: Add the configuration to your Claude Desktop config file (merge with existing mcpServers if you have others)

Step 4: Replace sk-or-v1-YOUR_OPENROUTER_API_KEY_HERE with your actual OpenRouter API key

Step 5: Restart Claude Desktop

Step 6: Test it by asking Claude:

"Search for ICD-10 codes for hypertension with heart disease"

How it works:

  • supergateway is an NPM package that bridges SSE (Server-Sent Events) to stdio
  • Your OpenRouter API key is sent in the X-OpenRouter-API-Key header with each request
  • The hosted server at https://icd10-mcp.onrender.com/sse handles the requests
  • No need to run anything locally — the server is always available

B. LangGraph Agent

Connect a LangGraph agent to the hosted MCP server using the langchain-mcp-adapters package.

Installation:

pip install langchain-mcp-adapters

Example Code:

from langchain_mcp_adapters.client import MultiServerMCPClient
import asyncio

async def create_icd10_agent():
    """Create a LangGraph agent with ICD-10 MCP tools."""
    
    # Connect to the hosted MCP server
    client = MultiServerMCPClient({
        "icd10-mcp": {
    /sse",
            "transport": "sse",
            "headers": {
                "X-OpenRouter-API-Key": "sk-or-v1-YOUR_OPENROUTER_API_KEY_HERE"
            }
        }
    })
    
    # Get available tools
    tools = await client.get_tools()
    print(f"Available tools: {[t.name for t in tools]}")
    
    # Call a tool
    result = await client.call_tool(
        "search_icd10",
        arguments={"query": "type 2 diabetes", "top_k": 5}
    )
    print(f"Results: {result}")
    
    # Use tools in t
    # ... your LangGraph state machine code here ...
    
    return client

# Run the agent
asyncio.run(create_icd10_agent())

Using with LangGraph State Machine:

from langgraph.graph import StateGraph, END
from typing import TypedDict

class AgentState(TypedDict):
    clinical_note: str
    icd_codes: list

async def code_note_node(state: AgentState):
    """Node that calls the ICD-10 MCP server."""
    client = MultiServerMCPClient({
        "icd10-mcp": {
            "url": "https://icd10-mcp.onrender.com/sse",
            "transport": "sse",
            "headers": {"X-OpenRouter-API-Key": "sk-or-v1-YOUR_KEY"}
        }
    })
    
    result = await client.call_tool(
        "code_clinical_note",
        arguments={
            "note": state["clinical_note"],
            "max_codes_per_problem": 2
        }
    )
    
    return {"icd_codes": result}

# Build the graph
workflow = StateGraph(AgentState)
workflow.add_node("code_note", code_note_node)
workflow.set_entry_point("code_note")
dd_edge("code_note", END)

app = workflow.compile()

D. Direct HTTP / Any Web App or Backend

Call the MCP server directly via HTTP/SSE from any programming language.

Server Details:

  • Base URL: https://icd10-mcp.onrender.com
  • SSE Endpoint: GET /sse (for MCP protocol connection)
  • Messages Endpoint: POST /messages/ (for tool calls)
  • Health Check: GET /health
  • Required Header: X-OpenRouter-API-Key: sk-or-v1-YOUR_OPENROUTER_API_KEY

Python Example (using requests):

import requests
import json

MCP_SERVER_URL = "https://icd10-mcp.onrender.com"
OPENROUTER_API_KEY = "sk-or-v1-YOUR_KEY"

# Health check
response = requests.get(f"{MCP_SERVER_URL}/health")
print(response.json())

# Call search_icd10 tool
response = requests.post(
    f"{MCP_SERVER_URL}/messages/",
    json={
        "method": "tools/call",
        "params": {
            "name": "search_icd10",
            "arguments": {"query": "hypertension", "top_k": 5}
        }
    },
    headers={
        "X-OpenRouter-API-Key": OPENROUTER_API_KEY,
        "Content-Type": "application/json"
    }
)

result = response.json()
print(json.dumps(result, indent=2))

# Call code_clinical_note tool
clinical_note = """
Patient presents with elevated blood pressure 158/96 mmHg.
History of type 2 diabetes, HbA1c 9.2%.
Creatinine 2.1 mg/dL, eGFR 38 mL/min.
"""

response = requests.post(
    f"{MCP_SERVER_URL}/messages/",
    json={
        "method": "tools/call",
        "params": {
            "name": "code_clinical_note",
            "arguments": {
                "note": clinical_note,
                "max_codes_per_problem": 2
            }
        }
    },
    headers={
        "X-OpenRouter-API-Key": OPENROUTER_API_KEY,
        "Content-Type": "application/json"
    }
)

result = response.json()
print(json.dumps(result, indent=2))

JavaScript Example (using fetch):

const MCP_SERVER_URL = "https://icd10-mcp.onrender.com";
const OPENROUTER_API_KEY = "sk-or-v1-YOUR_KEY";

// Health check
fetch(`${MCP_SERVER_URL}/health`)
  .then(res => res.json())
  .then(data => console.log(data));

// Call search_icd10 tool
fetch(`${MCP_SERVER_URL}/messages/`, {
  method: "POST",
  headers: {
    "X-OpenRouter-API-Key": OPENROUTER_API_KEY,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    method: "tools/call",
    params: {
      name: "search_icd10",
      arguments: { query: "diabetes", top_k: 5 }
    }
  })
})
  .then(res => res.json())
  .then(data => console.log(data));

// Call code_clinical_note tool
const clinicalNote = `
Patient presents with elevated blood pressure 158/96 mmHg.
History of type 2 diabetes, HbA1c 9.2%.
Creatinine 2.1 mg/dL, eGFR 38 mL/min.
`;

fetch(`${MCP_SERVER_URL}/messages/`, {
  method: "POST",
  headers: {
    "X-OpenRouter-API-Key": OPENROUTER_API_KEY,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    method: "tools/call",
    params: {
      name: "code_clinical_note",
      arguments: {
        note: clinicalNote,
        max_codes_per_problem: 2
      }
    }
  })
})
  .then(res => res.json())
  .then(data => console.log(data));

E. Any AI Agent (Generic MCP Client)

Any MCP-compatible client can connect to this server using SSE (Server-Sent Events) transport.

Connection Details:

  • URL: https://icd10-mcp.onrender.com/sse
  • Transport: SSE (Server-Sent Events)
  • Required Header: X-OpenRouter-API-Key: sk-or-v1-YOUR_OPENROUTER_API_KEY
  • Tools Available: code_clinical_note, search_icd10

Generic Pattern:

import requests

# Connect to SSE endpoint
response = requests.get(
    "https://icd10-mcp.onrender.com/sse",
    headers={"X-OpenRouter-API-Key": "sk-or-v1-YOUR_KEY"},
    stream=True
)

# The server will send MCP protocol messages via SSE
# Your client should parse SSE events and handle MCP JSON-RPC messages

Environment Variables Reference

Variable Required Default Description
PINECONE_API_KEY Yes Your Pinecone API key
PINECONE_INDEX_NAME No icd10cm-2026 Name of your Pinecone index
PINECONE_NAMESPACE No icd10cm_2026 Namespace within the Pinecone index
OPENROUTER_API_KEY Yes* Your OpenRouter API key
PLANNER_MODEL No openai/gpt-4o-mini LLM model for the planner stage
SELECTOR_MODEL No openai/gpt-4o-mini LLM model for the selector stage
EMBED_MODEL No openai/text-embedding-3-small Embedding model for vector search
PORT No 8000 Port for HTTP/SSE server (HTTP mode only)

*Note: OPENROUTER_API_KEY is required for HTTP/SSE mode. Users provide their OpenRouter key via the X-OpenRouter-API-Key header with each request.

from github.com/ansh-bitontree/icd10-mcp

Installing ICD-10-CM Medical Coding

This server has no published package — it is built from source. Open the repository and follow its README.

▸ github.com/ansh-bitontree/icd10-mcp

FAQ

Is ICD-10-CM Medical Coding MCP free?

Yes, ICD-10-CM Medical Coding MCP is free — one-click install via Unyly at no cost.

Does ICD-10-CM Medical Coding need an API key?

No, ICD-10-CM Medical Coding runs without API keys or environment variables.

Is ICD-10-CM Medical Coding hosted or self-hosted?

Self-hosted: the server runs locally on your machine via the install command above.

How do I install ICD-10-CM Medical Coding in Claude Desktop, Claude Code or Cursor?

Open ICD-10-CM Medical Coding on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.

Related MCPs

Compare ICD-10-CM Medical Coding with

Not sure what to pick?

Find your stack in 60 seconds

Author?

Embed badge for your README

Browse similar

All ai MCPs