Spaces:
Running
Running
Commit
·
10e234c
1
Parent(s):
c114650
feat: enhance Gradio UI with MCP server support and new tool interfaces
Browse files- Updated the Gradio app to include support for the MCP server, enabling integration with Claude Desktop.
- Added new interfaces for PubMed, Clinical Trials, bioRxiv, and a comprehensive search tool, enhancing the research capabilities.
- Modified the demo creation function to reflect these updates and ensure a seamless user experience.
Files modified:
- src/app.py
- README.md +28 -5
- src/app.py +63 -6
- tests/integration/test_mcp_server.py +24 -0
- tests/unit/test_mcp_tools.py +6 -6
README.md
CHANGED
|
@@ -30,11 +30,35 @@ uv sync
|
|
| 30 |
|
| 31 |
```bash
|
| 32 |
# Start the Gradio app
|
| 33 |
-
uv run python
|
| 34 |
```
|
| 35 |
|
| 36 |
Open your browser to `http://localhost:7860`.
|
| 37 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
## Development
|
| 39 |
|
| 40 |
### Run Tests
|
|
@@ -53,13 +77,12 @@ make check
|
|
| 53 |
|
| 54 |
DeepCritical uses a Vertical Slice Architecture:
|
| 55 |
|
| 56 |
-
1. **Search Slice**: Retrieving evidence from PubMed and
|
| 57 |
2. **Judge Slice**: Evaluating evidence quality using LLMs.
|
| 58 |
3. **Orchestrator Slice**: Managing the research loop and UI.
|
| 59 |
|
| 60 |
Built with:
|
| 61 |
- **PydanticAI**: For robust agent interactions.
|
| 62 |
- **Gradio**: For the streaming user interface.
|
| 63 |
-
- **PubMed**: For biomedical
|
| 64 |
-
- **
|
| 65 |
-
|
|
|
|
| 30 |
|
| 31 |
```bash
|
| 32 |
# Start the Gradio app
|
| 33 |
+
uv run python src/app.py
|
| 34 |
```
|
| 35 |
|
| 36 |
Open your browser to `http://localhost:7860`.
|
| 37 |
|
| 38 |
+
### 3. Connect via MCP
|
| 39 |
+
|
| 40 |
+
This application exposes a Model Context Protocol (MCP) server, allowing you to use its search tools directly from Claude Desktop or other MCP clients.
|
| 41 |
+
|
| 42 |
+
**MCP Server URL**: `http://localhost:7860/gradio_api/mcp/`
|
| 43 |
+
|
| 44 |
+
**Claude Desktop Configuration**:
|
| 45 |
+
Add this to your `claude_desktop_config.json`:
|
| 46 |
+
```json
|
| 47 |
+
{
|
| 48 |
+
"mcpServers": {
|
| 49 |
+
"deepcritical": {
|
| 50 |
+
"url": "http://localhost:7860/gradio_api/mcp/"
|
| 51 |
+
}
|
| 52 |
+
}
|
| 53 |
+
}
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
**Available Tools**:
|
| 57 |
+
- `search_pubmed`: Search peer-reviewed biomedical literature.
|
| 58 |
+
- `search_clinical_trials`: Search ClinicalTrials.gov.
|
| 59 |
+
- `search_biorxiv`: Search bioRxiv/medRxiv preprints.
|
| 60 |
+
- `search_all`: Search all sources simultaneously.
|
| 61 |
+
|
| 62 |
## Development
|
| 63 |
|
| 64 |
### Run Tests
|
|
|
|
| 77 |
|
| 78 |
DeepCritical uses a Vertical Slice Architecture:
|
| 79 |
|
| 80 |
+
1. **Search Slice**: Retrieving evidence from PubMed, ClinicalTrials.gov, and bioRxiv.
|
| 81 |
2. **Judge Slice**: Evaluating evidence quality using LLMs.
|
| 82 |
3. **Orchestrator Slice**: Managing the research loop and UI.
|
| 83 |
|
| 84 |
Built with:
|
| 85 |
- **PydanticAI**: For robust agent interactions.
|
| 86 |
- **Gradio**: For the streaming user interface.
|
| 87 |
+
- **PubMed, ClinicalTrials.gov, bioRxiv**: For biomedical data.
|
| 88 |
+
- **MCP**: For universal tool access.
|
|
|
src/app.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
"""Gradio UI for DeepCritical agent."""
|
| 2 |
|
| 3 |
import os
|
| 4 |
from collections.abc import AsyncGenerator
|
|
@@ -7,6 +7,12 @@ from typing import Any
|
|
| 7 |
import gradio as gr
|
| 8 |
|
| 9 |
from src.agent_factory.judges import JudgeHandler, MockJudgeHandler
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
from src.orchestrator_factory import create_orchestrator
|
| 11 |
from src.tools.biorxiv import BioRxivTool
|
| 12 |
from src.tools.clinicaltrials import ClinicalTrialsTool
|
|
@@ -115,10 +121,10 @@ async def research_agent(
|
|
| 115 |
|
| 116 |
def create_demo() -> Any:
|
| 117 |
"""
|
| 118 |
-
Create the Gradio demo interface.
|
| 119 |
|
| 120 |
Returns:
|
| 121 |
-
Configured Gradio Blocks interface
|
| 122 |
"""
|
| 123 |
with gr.Blocks(
|
| 124 |
title="DeepCritical - Drug Repurposing Research Agent",
|
|
@@ -137,9 +143,10 @@ def create_demo() -> Any:
|
|
| 137 |
- "What existing medications show promise for Long COVID?"
|
| 138 |
""")
|
| 139 |
|
|
|
|
| 140 |
gr.ChatInterface(
|
| 141 |
fn=research_agent,
|
| 142 |
-
type="messages",
|
| 143 |
title="",
|
| 144 |
examples=[
|
| 145 |
"What drugs could be repurposed for Alzheimer's disease?",
|
|
@@ -157,24 +164,74 @@ def create_demo() -> Any:
|
|
| 157 |
],
|
| 158 |
)
|
| 159 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
gr.Markdown("""
|
| 161 |
---
|
| 162 |
**Note**: This is a research tool and should not be used for medical decisions.
|
| 163 |
Always consult healthcare professionals for medical advice.
|
| 164 |
|
| 165 |
-
Built with
|
|
|
|
|
|
|
| 166 |
""")
|
| 167 |
|
| 168 |
return demo
|
| 169 |
|
| 170 |
|
| 171 |
def main() -> None:
|
| 172 |
-
"""Run the Gradio app."""
|
| 173 |
demo = create_demo()
|
| 174 |
demo.launch(
|
| 175 |
server_name="0.0.0.0",
|
| 176 |
server_port=7860,
|
| 177 |
share=False,
|
|
|
|
| 178 |
)
|
| 179 |
|
| 180 |
|
|
|
|
| 1 |
+
"""Gradio UI for DeepCritical agent with MCP server support."""
|
| 2 |
|
| 3 |
import os
|
| 4 |
from collections.abc import AsyncGenerator
|
|
|
|
| 7 |
import gradio as gr
|
| 8 |
|
| 9 |
from src.agent_factory.judges import JudgeHandler, MockJudgeHandler
|
| 10 |
+
from src.mcp_tools import (
|
| 11 |
+
search_all_sources,
|
| 12 |
+
search_biorxiv,
|
| 13 |
+
search_clinical_trials,
|
| 14 |
+
search_pubmed,
|
| 15 |
+
)
|
| 16 |
from src.orchestrator_factory import create_orchestrator
|
| 17 |
from src.tools.biorxiv import BioRxivTool
|
| 18 |
from src.tools.clinicaltrials import ClinicalTrialsTool
|
|
|
|
| 121 |
|
| 122 |
def create_demo() -> Any:
|
| 123 |
"""
|
| 124 |
+
Create the Gradio demo interface with MCP support.
|
| 125 |
|
| 126 |
Returns:
|
| 127 |
+
Configured Gradio Blocks interface with MCP server enabled
|
| 128 |
"""
|
| 129 |
with gr.Blocks(
|
| 130 |
title="DeepCritical - Drug Repurposing Research Agent",
|
|
|
|
| 143 |
- "What existing medications show promise for Long COVID?"
|
| 144 |
""")
|
| 145 |
|
| 146 |
+
# Main chat interface (existing)
|
| 147 |
gr.ChatInterface(
|
| 148 |
fn=research_agent,
|
| 149 |
+
type="messages", # type: ignore
|
| 150 |
title="",
|
| 151 |
examples=[
|
| 152 |
"What drugs could be repurposed for Alzheimer's disease?",
|
|
|
|
| 164 |
],
|
| 165 |
)
|
| 166 |
|
| 167 |
+
# MCP Tool Interfaces (exposed via MCP protocol)
|
| 168 |
+
gr.Markdown("---\n## MCP Tools (Also Available via Claude Desktop)")
|
| 169 |
+
|
| 170 |
+
with gr.Tab("PubMed Search"):
|
| 171 |
+
gr.Interface(
|
| 172 |
+
fn=search_pubmed,
|
| 173 |
+
inputs=[
|
| 174 |
+
gr.Textbox(label="Query", placeholder="metformin alzheimer"),
|
| 175 |
+
gr.Slider(1, 50, value=10, step=1, label="Max Results"),
|
| 176 |
+
],
|
| 177 |
+
outputs=gr.Markdown(label="Results"),
|
| 178 |
+
api_name="search_pubmed",
|
| 179 |
+
)
|
| 180 |
+
|
| 181 |
+
with gr.Tab("Clinical Trials"):
|
| 182 |
+
gr.Interface(
|
| 183 |
+
fn=search_clinical_trials,
|
| 184 |
+
inputs=[
|
| 185 |
+
gr.Textbox(label="Query", placeholder="diabetes phase 3"),
|
| 186 |
+
gr.Slider(1, 50, value=10, step=1, label="Max Results"),
|
| 187 |
+
],
|
| 188 |
+
outputs=gr.Markdown(label="Results"),
|
| 189 |
+
api_name="search_clinical_trials",
|
| 190 |
+
)
|
| 191 |
+
|
| 192 |
+
with gr.Tab("Preprints"):
|
| 193 |
+
gr.Interface(
|
| 194 |
+
fn=search_biorxiv,
|
| 195 |
+
inputs=[
|
| 196 |
+
gr.Textbox(label="Query", placeholder="long covid treatment"),
|
| 197 |
+
gr.Slider(1, 50, value=10, step=1, label="Max Results"),
|
| 198 |
+
],
|
| 199 |
+
outputs=gr.Markdown(label="Results"),
|
| 200 |
+
api_name="search_biorxiv",
|
| 201 |
+
)
|
| 202 |
+
|
| 203 |
+
with gr.Tab("Search All"):
|
| 204 |
+
gr.Interface(
|
| 205 |
+
fn=search_all_sources,
|
| 206 |
+
inputs=[
|
| 207 |
+
gr.Textbox(label="Query", placeholder="metformin cancer"),
|
| 208 |
+
gr.Slider(1, 20, value=5, step=1, label="Max Per Source"),
|
| 209 |
+
],
|
| 210 |
+
outputs=gr.Markdown(label="Results"),
|
| 211 |
+
api_name="search_all",
|
| 212 |
+
)
|
| 213 |
+
|
| 214 |
gr.Markdown("""
|
| 215 |
---
|
| 216 |
**Note**: This is a research tool and should not be used for medical decisions.
|
| 217 |
Always consult healthcare professionals for medical advice.
|
| 218 |
|
| 219 |
+
Built with PydanticAI + PubMed, ClinicalTrials.gov & bioRxiv
|
| 220 |
+
|
| 221 |
+
**MCP Server**: Available at `/gradio_api/mcp/` for Claude Desktop integration
|
| 222 |
""")
|
| 223 |
|
| 224 |
return demo
|
| 225 |
|
| 226 |
|
| 227 |
def main() -> None:
|
| 228 |
+
"""Run the Gradio app with MCP server enabled."""
|
| 229 |
demo = create_demo()
|
| 230 |
demo.launch(
|
| 231 |
server_name="0.0.0.0",
|
| 232 |
server_port=7860,
|
| 233 |
share=False,
|
| 234 |
+
mcp_server=True, # Enable MCP server
|
| 235 |
)
|
| 236 |
|
| 237 |
|
tests/integration/test_mcp_server.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Integration tests for MCP server functionality."""
|
| 2 |
+
|
| 3 |
+
import pytest
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class TestMCPServerIntegration:
|
| 7 |
+
"""Integration tests for MCP server (requires running app)."""
|
| 8 |
+
|
| 9 |
+
@pytest.mark.integration
|
| 10 |
+
@pytest.mark.asyncio
|
| 11 |
+
async def test_mcp_tools_work_end_to_end(self) -> None:
|
| 12 |
+
"""Test that MCP tools execute real searches."""
|
| 13 |
+
from src.mcp_tools import search_pubmed
|
| 14 |
+
|
| 15 |
+
result = await search_pubmed("metformin diabetes", 3)
|
| 16 |
+
|
| 17 |
+
assert isinstance(result, str)
|
| 18 |
+
assert "PubMed Results" in result
|
| 19 |
+
# Should have actual content (not just "no results")
|
| 20 |
+
# Typical queries should return something.
|
| 21 |
+
# The wrapper returns "No PubMed results found" string if empty.
|
| 22 |
+
|
| 23 |
+
if "No PubMed results found" not in result:
|
| 24 |
+
assert len(result) > 10
|
tests/unit/test_mcp_tools.py
CHANGED
|
@@ -184,17 +184,17 @@ class TestMCPTypeHints:
|
|
| 184 |
sig = inspect.signature(search_pubmed)
|
| 185 |
|
| 186 |
# Check parameter hints
|
| 187 |
-
assert sig.parameters["query"].annotation
|
| 188 |
-
assert sig.parameters["max_results"].annotation
|
| 189 |
|
| 190 |
# Check return hint
|
| 191 |
-
assert sig.return_annotation
|
| 192 |
|
| 193 |
def test_search_clinical_trials_type_hints(self) -> None:
|
| 194 |
"""All parameters and return must have type hints."""
|
| 195 |
import inspect
|
| 196 |
|
| 197 |
sig = inspect.signature(search_clinical_trials)
|
| 198 |
-
assert sig.parameters["query"].annotation
|
| 199 |
-
assert sig.parameters["max_results"].annotation
|
| 200 |
-
assert sig.return_annotation
|
|
|
|
| 184 |
sig = inspect.signature(search_pubmed)
|
| 185 |
|
| 186 |
# Check parameter hints
|
| 187 |
+
assert sig.parameters["query"].annotation is str
|
| 188 |
+
assert sig.parameters["max_results"].annotation is int
|
| 189 |
|
| 190 |
# Check return hint
|
| 191 |
+
assert sig.return_annotation is str
|
| 192 |
|
| 193 |
def test_search_clinical_trials_type_hints(self) -> None:
|
| 194 |
"""All parameters and return must have type hints."""
|
| 195 |
import inspect
|
| 196 |
|
| 197 |
sig = inspect.signature(search_clinical_trials)
|
| 198 |
+
assert sig.parameters["query"].annotation is str
|
| 199 |
+
assert sig.parameters["max_results"].annotation is int
|
| 200 |
+
assert sig.return_annotation is str
|