Skip to content

Commit 54bcca4

Browse files
authored
Merge pull request #5 from genomoncology/server-hanging
Update package version to 0.1.1. Add example scripts for MCP integration and Python SDK. Update troubleshooting documentation for macOS. Modify trial search example to remove --size argument. Update dependencies in pyproject.toml. Add script to run the MCP server. Enable deptry checks on example scripts.
2 parents a39fa33 + bc63b15 commit 54bcca4

6 files changed

Lines changed: 211 additions & 26 deletions

File tree

docs/troubleshooting/macos.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ You can run `biomcp` commands directly without a full installation using `uvx`.
2323
```
2424
- Test a search command (e.g., trial search):
2525
```bash
26-
uvx --from biomcp-python biomcp trial search --condition NSCLC --size 1 | head -n 5
26+
uvx --from biomcp-python biomcp trial search --condition NSCLC | head -n 5
2727
# Expected Output (NCT ID and Title will vary):
2828
# # Record 1
2929
# Nct Number: NCT0XXXXXXX

example_scripts/mcp_integration.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#!/usr/bin/env -S uv --quiet run --script
2+
# /// script
3+
# requires-python = ">=3.11"
4+
# dependencies = [
5+
# "mcp",
6+
# ]
7+
# ///
8+
9+
# Scripts to reproduce this page:
10+
# https://biomcp.org/mcp_integration/
11+
12+
import asyncio
13+
14+
from mcp.client.session import ClientSession
15+
from mcp.client.stdio import StdioServerParameters, stdio_client
16+
from mcp.types import TextContent
17+
18+
19+
async def check_server():
20+
# Run with pypi package
21+
# server_params = StdioServerParameters(
22+
# command="uvx",
23+
# args=["--from", "biomcp-python", "biomcp", "run"],
24+
# )
25+
26+
# Run with local code
27+
server_params = StdioServerParameters(
28+
command="python",
29+
args=["-m", "biomcp", "run"],
30+
)
31+
32+
async with (
33+
stdio_client(server_params) as (read, write),
34+
ClientSession(read, write) as session,
35+
):
36+
await session.initialize()
37+
38+
# list prompts
39+
prompts = await session.list_prompts()
40+
print("Available prompts:", prompts)
41+
42+
# list resources
43+
resources = await session.list_resources()
44+
print("Available resources:", resources)
45+
46+
# list tools
47+
tool_result = await session.list_tools()
48+
tools = tool_result.tools
49+
print("Available tools:", tools)
50+
assert len(tools) >= 9
51+
52+
# run tool
53+
tool_name = "variant_details"
54+
tool_args = {"variant_id": "rs113488022"}
55+
result = await session.call_tool(tool_name, tool_args)
56+
assert result.isError is False, f"Error: {result.content}"
57+
58+
# --- Assertions ---
59+
# 1. Check the call was successful (not an error)
60+
assert (
61+
result.isError is False
62+
), f"Tool call resulted in error: {result.content}"
63+
64+
# 2. Check there is content
65+
assert result.content is not None
66+
assert len(result.content) >= 1
67+
68+
# 3. Check the type of the first content block
69+
content_block = result.content[0]
70+
assert isinstance(content_block, TextContent)
71+
72+
markdown_output = content_block.text
73+
# print(markdown_output)
74+
assert isinstance(markdown_output, str)
75+
assert "rs113488022" in markdown_output
76+
assert "BRAF" in markdown_output
77+
assert "Pathogenic" in markdown_output
78+
print(f"Successfully called tool '{tool_name}' with args {tool_args}")
79+
80+
81+
if __name__ == "__main__":
82+
asyncio.run(check_server())

example_scripts/python_sdk.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
#!/usr/bin/env -S uv --quiet run --script
2+
# /// script
3+
# requires-python = ">=3.11"
4+
# dependencies = [
5+
# "biomcp-python",
6+
# ]
7+
# ///
8+
9+
# Scripts to reproduce this page:
10+
# https://biomcp.org/python_sdk/
11+
12+
import asyncio
13+
import json
14+
15+
from biomcp.trials.search import (
16+
RecruitingStatus,
17+
TrialPhase,
18+
TrialQuery,
19+
search_trials,
20+
)
21+
from biomcp.variants.getter import get_variant
22+
from biomcp.variants.search import VariantQuery, search_variants
23+
24+
25+
async def find_pathogenic_tp53():
26+
# noinspection PyTypeChecker
27+
query = VariantQuery(gene="TP53", significance="pathogenic", size=5)
28+
# Get results as Markdown (default)
29+
json_output_str = await search_variants(query, output_json=True)
30+
data = json.loads(json_output_str)
31+
assert len(data) == 5
32+
for item in data:
33+
clinvar = item.get("clinvar")
34+
for rcv in clinvar.get("rcv", []):
35+
assert "pathogenic" in rcv["clinical_significance"].lower()
36+
37+
38+
async def get_braf_v600e_details():
39+
variant_id = "chr7:g.140453136A>T" # BRAF V600E variant
40+
41+
# Get results as JSON string
42+
json_output_str = await get_variant(variant_id, output_json=True)
43+
data = json.loads(json_output_str)
44+
45+
# Process the variant data
46+
assert data, "No data returned for BRAF V600E variant"
47+
variant = data[0]
48+
clinvar = variant.get("clinvar", {})
49+
cosmic = variant.get("cosmic", {})
50+
docm = variant.get("docm", {})
51+
52+
# Verify key variant details
53+
assert clinvar.get("gene", {}).get("symbol") == "BRAF"
54+
assert clinvar.get("chrom") == "7"
55+
assert clinvar.get("cytogenic") == "7q34"
56+
assert cosmic.get("cosmic_id") == "COSM476"
57+
assert docm.get("aa_change") == "p.V600E"
58+
59+
# Verify HGVS coding variants
60+
hgvs_coding = clinvar.get("hgvs", {}).get("coding", [])
61+
assert len(hgvs_coding) >= 13
62+
assert "NM_004333.6:c.1799T>A" in hgvs_coding
63+
64+
65+
async def find_melanoma_trials():
66+
query = TrialQuery(
67+
conditions=["Melanoma"],
68+
interventions=["Pembrolizumab"],
69+
recruiting_status=RecruitingStatus.OPEN,
70+
phase=TrialPhase.PHASE3,
71+
)
72+
73+
# Get results as JSON string
74+
json_output_str = await search_trials(query, output_json=True)
75+
data = json.loads(json_output_str)
76+
77+
# Verify we got results
78+
assert data, "No trials found"
79+
assert len(data) >= 2, "Expected at least 2 melanoma trials"
80+
81+
# Verify first trial details (NCT05727904)
82+
trial1 = data[0]
83+
assert trial1["NCT Number"] == "NCT05727904"
84+
assert "lifileucel" in trial1["Study Title"].lower()
85+
assert trial1["Study Status"] == "RECRUITING"
86+
assert trial1["Phases"] == "PHASE3"
87+
assert int(trial1["Enrollment"]) == 670
88+
assert "Melanoma" in trial1["Conditions"]
89+
assert "Pembrolizumab" in trial1["Interventions"]
90+
91+
# Verify second trial details (NCT06697301)
92+
trial2 = data[1]
93+
assert trial2["NCT Number"] == "NCT06697301"
94+
assert "EIK1001" in trial2["Study Title"]
95+
assert trial2["Study Status"] == "RECRUITING"
96+
assert "PHASE3" in trial2["Phases"]
97+
assert int(trial2["Enrollment"]) == 740
98+
assert trial2["Conditions"] == "Advanced Melanoma"
99+
100+
101+
def run():
102+
asyncio.run(find_pathogenic_tp53())
103+
asyncio.run(get_braf_v600e_details())
104+
asyncio.run(find_melanoma_trials())
105+
106+
107+
if __name__ == "__main__":
108+
run()

pyproject.toml

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "biomcp-python"
3-
version = "0.1.0"
3+
version = "0.1.1"
44
description = "Biomedical Model Context Protocol Server"
55
authors = [{ name = "Ian Maurer", email = "imaurer@gmail.com" }]
66
readme = "README.md"
@@ -16,7 +16,6 @@ classifiers = [
1616
"Topic :: Software Development :: Libraries :: Python Modules",
1717
]
1818
dependencies = [
19-
"anyio>=4.8.0",
2019
"certifi>=2025.1.31",
2120
"diskcache>=5.6.3",
2221
"httpx>=0.28.1",
@@ -61,8 +60,6 @@ build-backend = "setuptools.build_meta"
6160
[tool.setuptools.package-data]
6261
biomcp = ["resources/*.md"]
6362

64-
65-
6663
[project.scripts]
6764
biomcp = "biomcp.__main__:main"
6865

@@ -164,3 +161,15 @@ omit = [
164161
"src/*/server.py",
165162
"src/*/http_client.py",
166163
]
164+
165+
[tool.deptry]
166+
exclude = [
167+
"example_scripts/python_sdk.py",
168+
"venv",
169+
".venv",
170+
".direnv",
171+
"tests",
172+
".git",
173+
"build",
174+
"dist",
175+
]

src/biomcp/cli/server.py

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import signal
2-
import sys
1+
import asyncio
32

4-
import anyio
53
import typer
64

75
from .. import logger, mcp_app
@@ -13,19 +11,9 @@
1311
def run_server():
1412
"""Run the BioMCP server with STDIO transport."""
1513

16-
# Use a simpler approach - just force exit on SIGINT
17-
def handle_sigint(sig, frame):
18-
typer.echo("\nShutting down server...")
19-
sys.exit(0)
14+
tools = asyncio.run(mcp_app.list_tools())
15+
tools_str = ",".join(t.name for t in tools)
16+
logger.info(f"Tools loaded: {tools_str}")
2017

21-
# Register only for SIGINT
22-
signal.signal(signal.SIGINT, handle_sigint)
23-
24-
logger.info("Starting MCP server... (v0.0.1)")
25-
try:
26-
anyio.run(mcp_app.run_stdio_async)
27-
logger.info("MCP server stopped gracefully.")
28-
return 0
29-
except Exception as e:
30-
logger.error(f"Error running MCP server: {e}")
31-
return 1
18+
logger.info("Starting MCP server:")
19+
mcp_app.run()

uv.lock

Lines changed: 1 addition & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)