mirror of
https://github.com/mims-harvard/ToolUniverse.git
synced 2026-09-19 07:31:47 +08:00
Release tooluniverse 1.4.0 (#385)
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
},
|
||||
"metadata": {
|
||||
"description": "ToolUniverse marketplace \u2014 1000+ scientific research tools and 115 specialized skills for biology, chemistry, medicine, and data science.",
|
||||
"version": "1.3.2"
|
||||
"version": "1.4.0"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ sys.path.insert(0, os.path.abspath("../src"))
|
||||
project = "ToolUniverse"
|
||||
copyright = "2025, Shanghua Gao"
|
||||
author = "Shanghua Gao"
|
||||
release = "1.3.1"
|
||||
release = "1.4.0"
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
extensions = [
|
||||
|
||||
@@ -99,24 +99,18 @@ def example_get_variant_details():
|
||||
|
||||
print(f"Status: {result.get('status')}")
|
||||
if result.get('status') == 'success':
|
||||
if 'formatted_data' in result:
|
||||
formatted = result['formatted_data']
|
||||
print(f"Variant ID: {formatted['variant_id']}")
|
||||
print(f"Accession: {formatted['accession']}")
|
||||
print(f"Title: {formatted['title']}")
|
||||
print(f"Object Type: {formatted['obj_type']}")
|
||||
print(f"Genes: {formatted['genes'][:5]}") # Show first 5 genes
|
||||
print(f"Clinical Significance: {formatted['clinical_significance']}")
|
||||
print(f"Review Status: {formatted['review_status']}")
|
||||
print(f"Chromosome: {formatted['chromosome']}")
|
||||
print(f"Location: {formatted['location']}")
|
||||
else:
|
||||
# Fallback to original format
|
||||
data = result.get('data', {})
|
||||
print(f"Variant ID: {result.get('variant_id')}")
|
||||
print(f"Data type: {type(data)}")
|
||||
if isinstance(data, dict):
|
||||
print(f"Data keys: {list(data.keys())}")
|
||||
# The formatted payload is returned under 'data'; the unprocessed
|
||||
# esummary record stays available at data['raw_data'].
|
||||
formatted = result.get('data', {})
|
||||
print(f"Variant ID: {formatted['variant_id']}")
|
||||
print(f"Accession: {formatted['accession']}")
|
||||
print(f"Title: {formatted['title']}")
|
||||
print(f"Object Type: {formatted['obj_type']}")
|
||||
print(f"Genes: {formatted['genes'][:5]}") # Show first 5 genes
|
||||
print(f"Clinical Significance: {formatted['clinical_significance']}")
|
||||
print(f"Review Status: {formatted['review_status']}")
|
||||
print(f"Chromosome: {formatted['chromosome']}")
|
||||
print(f"Location: {formatted['location']}")
|
||||
else:
|
||||
print(f"Error: {result.get('error')}")
|
||||
|
||||
@@ -138,28 +132,22 @@ def example_get_clinical_significance():
|
||||
|
||||
print(f"Status: {result.get('status')}")
|
||||
if result.get('status') == 'success':
|
||||
if 'formatted_data' in result:
|
||||
formatted = result['formatted_data']
|
||||
print(f"Variant ID: {formatted['variant_id']}")
|
||||
print("Germline Classification:")
|
||||
print(f" Description: {formatted['germline_classification']['description']}")
|
||||
print(f" Review Status: {formatted['germline_classification']['review_status']}")
|
||||
print(f" Last Evaluated: {formatted['germline_classification']['last_evaluated']}")
|
||||
print(f" FDA Recognized: {formatted['germline_classification']['fda_recognized']}")
|
||||
print(f" Traits: {formatted['germline_classification']['traits']}")
|
||||
print("Clinical Impact:")
|
||||
print(f" Description: {formatted['clinical_impact']['description']}")
|
||||
print(f" Review Status: {formatted['clinical_impact']['review_status']}")
|
||||
print("Oncogenicity:")
|
||||
print(f" Description: {formatted['oncogenicity']['description']}")
|
||||
print(f" Review Status: {formatted['oncogenicity']['review_status']}")
|
||||
else:
|
||||
# Fallback to original format
|
||||
data = result.get('data', {})
|
||||
print(f"Variant ID: {result.get('variant_id')}")
|
||||
print(f"Data type: {type(data)}")
|
||||
if isinstance(data, dict):
|
||||
print(f"Data keys: {list(data.keys())}")
|
||||
# The formatted payload is returned under 'data'; the unprocessed
|
||||
# esummary record stays available at data['raw_data'].
|
||||
formatted = result.get('data', {})
|
||||
print(f"Variant ID: {formatted['variant_id']}")
|
||||
print("Germline Classification:")
|
||||
print(f" Description: {formatted['germline_classification']['description']}")
|
||||
print(f" Review Status: {formatted['germline_classification']['review_status']}")
|
||||
print(f" Last Evaluated: {formatted['germline_classification']['last_evaluated']}")
|
||||
print(f" FDA Recognized: {formatted['germline_classification']['fda_recognized']}")
|
||||
print(f" Traits: {formatted['germline_classification']['traits']}")
|
||||
print("Clinical Impact:")
|
||||
print(f" Description: {formatted['clinical_impact']['description']}")
|
||||
print(f" Review Status: {formatted['clinical_impact']['review_status']}")
|
||||
print("Oncogenicity:")
|
||||
print(f" Description: {formatted['oncogenicity']['description']}")
|
||||
print(f" Review Status: {formatted['oncogenicity']['review_status']}")
|
||||
else:
|
||||
print(f"Error: {result.get('error')}")
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"manifest_version": "0.4",
|
||||
"name": "tooluniverse",
|
||||
"display_name": "ToolUniverse",
|
||||
"version": "1.3.1",
|
||||
"version": "1.4.0",
|
||||
"description": "ToolUniverse: an ecosystem for democratizing AI scientists with 2,500+ scientific tools.",
|
||||
"long_description": "ToolUniverse is an ecosystem for creating AI scientist systems from any large language model (LLM). It standardizes how LLMs interact with tools, integrating thousands of machine learning models, datasets, APIs, and scientific packages for data analysis, knowledge retrieval, and experimental design. This bundle exposes ToolUniverse capability via the Model Context Protocol (MCP), enabling AI assistants to perform complex scientific tasks.",
|
||||
"author": {
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "tooluniverse-mcpb-native"
|
||||
version = "1.3.1"
|
||||
version = "1.4.0"
|
||||
description = "ToolUniverse MCP Server (Native MCPB bundle)"
|
||||
requires-python = ">=3.10,<3.14"
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "tooluniverse",
|
||||
"description": "1000+ scientific research tools for biology, chemistry, medicine, and data science. Access PubMed, UniProt, PubChem, TCGA, NHANES, GWAS Catalog, and hundreds more databases through a unified MCP interface. Includes 120+ specialized research skills for genomics, drug discovery, clinical analysis, protein variant interpretation, and more.",
|
||||
"version": "1.3.2",
|
||||
"version": "1.4.0",
|
||||
"author": {
|
||||
"name": "Shanghua Gao",
|
||||
"url": "https://shgao.site"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "tooluniverse",
|
||||
"description": "1000+ scientific research tools for biology, chemistry, medicine, and data science. Access PubMed, UniProt, PubChem, TCGA, NHANES, GWAS Catalog, and hundreds more databases through a unified MCP interface. Includes specialized research skills for genomics, drug discovery, clinical analysis, protein variant interpretation, and more.",
|
||||
"version": "1.3.2",
|
||||
"version": "1.4.0",
|
||||
"author": {
|
||||
"name": "Shanghua Gao",
|
||||
"url": "https://shgao.site"
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "tooluniverse"
|
||||
version = "1.3.1"
|
||||
version = "1.4.0"
|
||||
description = "A comprehensive collection of scientific tools for Agentic AI, offering integration with the ToolUniverse SDK and MCP Server to support advanced scientific workflows."
|
||||
authors = [
|
||||
{ name = "Shanghua Gao", email = "shanghuagao@gmail.com" }
|
||||
|
||||
+2
-2
@@ -7,13 +7,13 @@
|
||||
"url": "https://github.com/mims-harvard/ToolUniverse",
|
||||
"source": "github"
|
||||
},
|
||||
"version": "1.3.1",
|
||||
"version": "1.4.0",
|
||||
"packages": [
|
||||
{
|
||||
"registryType": "pypi",
|
||||
"registryBaseUrl": "https://pypi.org",
|
||||
"identifier": "tooluniverse",
|
||||
"version": "1.3.1",
|
||||
"version": "1.4.0",
|
||||
"runtimeHint": "uvx",
|
||||
"transport": {
|
||||
"type": "stdio"
|
||||
|
||||
@@ -559,7 +559,16 @@ class ClinVarGetVariantDetails(ClinVarRESTTool):
|
||||
variant_data = fetch["variant_data"]
|
||||
result = fetch["result"]
|
||||
result["variant_id"] = variant_id
|
||||
result["formatted_data"] = {
|
||||
# Fix-R8E-1/R6C-2: assign the formatted payload *over* `result["data"]`
|
||||
# (the full, unprocessed esummary envelope from _make_request) instead
|
||||
# of publishing it alongside under a second key. Keeping both roughly
|
||||
# tripled payload size for no informational gain and made this tool's
|
||||
# output nearly indistinguishable from ClinVarGetClinicalSignificance's,
|
||||
# which had the identical duplication. Overwriting preserves that
|
||||
# de-duplication -- raw access remains at data["raw_data"] -- while
|
||||
# delivering the payload under the `data` key the return_schema
|
||||
# declares and every other tool in the registry uses.
|
||||
result["data"] = {
|
||||
"variant_id": variant_id,
|
||||
"accession": variant_data.get("accession", ""),
|
||||
"obj_type": variant_data.get("obj_type", ""),
|
||||
@@ -573,15 +582,6 @@ class ClinVarGetVariantDetails(ClinVarRESTTool):
|
||||
**self._parse_variant_summary(variant_data),
|
||||
"raw_data": variant_data,
|
||||
}
|
||||
# Fix-R8E-1/R6C-2: `result["data"]` (the full, unprocessed esummary
|
||||
# envelope from _make_request) duplicates the same content already
|
||||
# exposed at formatted_data["raw_data"] -- keeping both roughly
|
||||
# tripled payload size for no informational gain and made this
|
||||
# tool's output nearly indistinguishable from
|
||||
# ClinVarGetClinicalSignificance's, which has the identical
|
||||
# duplication. Drop the redundant top-level copy; raw access is
|
||||
# still available via formatted_data.raw_data.
|
||||
result.pop("data", None)
|
||||
|
||||
return result
|
||||
|
||||
@@ -613,7 +613,11 @@ class ClinVarGetClinicalSignificance(ClinVarRESTTool):
|
||||
clinical_impact = variant_data.get("clinical_impact_classification", {})
|
||||
oncogenicity = variant_data.get("oncogenicity_classification", {})
|
||||
|
||||
result["formatted_data"] = {
|
||||
# Fix-R8E-1/R6C-2: see the matching fix in ClinVarGetVariantDetails --
|
||||
# assigning over `result["data"]` (the raw esummary envelope) both
|
||||
# de-duplicates the payload and honours the `data` key declared in the
|
||||
# return_schema. Raw access remains at data["raw_data"].
|
||||
result["data"] = {
|
||||
"variant_id": variant_id,
|
||||
"germline_classification": {
|
||||
"description": germline_class.get("description", ""),
|
||||
@@ -637,8 +641,5 @@ class ClinVarGetClinicalSignificance(ClinVarRESTTool):
|
||||
},
|
||||
"raw_data": variant_data,
|
||||
}
|
||||
# Fix-R8E-1/R6C-2: see the matching fix in ClinVarGetVariantDetails
|
||||
# -- result["data"] duplicates formatted_data["raw_data"].
|
||||
result.pop("data", None)
|
||||
|
||||
return result
|
||||
|
||||
@@ -186,11 +186,54 @@
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"description": "Detailed variant information from ClinVar",
|
||||
"description": "Formatted variant details. Replaces the raw esummary envelope; the unprocessed record for this variant is preserved at raw_data.",
|
||||
"properties": {
|
||||
"result": {
|
||||
"variant_id": {
|
||||
"type": "string",
|
||||
"description": "ClinVar Variation ID that was requested"
|
||||
},
|
||||
"accession": {
|
||||
"type": "string",
|
||||
"description": "ClinVar accession (e.g. VCV000012345)"
|
||||
},
|
||||
"obj_type": {
|
||||
"type": "string",
|
||||
"description": "Variant object type (e.g. single nucleotide variant, Deletion)"
|
||||
},
|
||||
"chromosome": {
|
||||
"type": "string",
|
||||
"description": "Chromosome the variant maps to"
|
||||
},
|
||||
"location": {
|
||||
"type": "string",
|
||||
"description": "Cytogenetic band of the variant's first variation location"
|
||||
},
|
||||
"variation_name": {
|
||||
"type": "string",
|
||||
"description": "HGVS-style variation name (e.g. NM_000059.4(BRCA2):c.1234A>G)"
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "ClinVar record title"
|
||||
},
|
||||
"genes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "HGNC symbols of genes overlapping the variant"
|
||||
},
|
||||
"clinical_significance": {
|
||||
"type": "string",
|
||||
"description": "Germline classification description (e.g. Pathogenic, Likely benign, Uncertain significance)"
|
||||
},
|
||||
"review_status": {
|
||||
"type": "string",
|
||||
"description": "ClinVar review status for the germline classification"
|
||||
},
|
||||
"raw_data": {
|
||||
"type": "object",
|
||||
"description": "Variant data structure keyed by variant ID"
|
||||
"description": "Unprocessed NCBI esummary record for this variant"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -257,11 +300,80 @@
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"description": "Clinical significance information from ClinVar",
|
||||
"description": "Formatted clinical significance. Replaces the raw esummary envelope; the unprocessed record for this variant is preserved at raw_data.",
|
||||
"properties": {
|
||||
"result": {
|
||||
"variant_id": {
|
||||
"type": "string",
|
||||
"description": "ClinVar Variation ID that was requested"
|
||||
},
|
||||
"germline_classification": {
|
||||
"type": "object",
|
||||
"description": "Summary results for the variant"
|
||||
"description": "Germline classification for the variant",
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "Classification text"
|
||||
},
|
||||
"review_status": {
|
||||
"type": "string",
|
||||
"description": "ClinVar review status for this classification"
|
||||
},
|
||||
"last_evaluated": {
|
||||
"type": "string",
|
||||
"description": "Date the classification was last evaluated"
|
||||
},
|
||||
"fda_recognized": {
|
||||
"type": "string",
|
||||
"description": "FDA-recognized database designation, when present"
|
||||
},
|
||||
"traits": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Trait/condition names associated with the classification"
|
||||
}
|
||||
}
|
||||
},
|
||||
"clinical_impact": {
|
||||
"type": "object",
|
||||
"description": "Clinical impact classification (somatic variants)",
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "Classification text"
|
||||
},
|
||||
"review_status": {
|
||||
"type": "string",
|
||||
"description": "ClinVar review status for this classification"
|
||||
},
|
||||
"last_evaluated": {
|
||||
"type": "string",
|
||||
"description": "Date the classification was last evaluated"
|
||||
}
|
||||
}
|
||||
},
|
||||
"oncogenicity": {
|
||||
"type": "object",
|
||||
"description": "Oncogenicity classification",
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "Classification text"
|
||||
},
|
||||
"review_status": {
|
||||
"type": "string",
|
||||
"description": "ClinVar review status for this classification"
|
||||
},
|
||||
"last_evaluated": {
|
||||
"type": "string",
|
||||
"description": "Date the classification was last evaluated"
|
||||
}
|
||||
}
|
||||
},
|
||||
"raw_data": {
|
||||
"type": "object",
|
||||
"description": "Unprocessed NCBI esummary record for this variant"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
"""Regression guard for Fix-R8E-1/R6C-2: ClinVarGetVariantDetails and
|
||||
ClinVarGetClinicalSignificance both returned the full raw esummary blob
|
||||
twice -- once as the top-level "data" key (from the underlying
|
||||
_make_request envelope) and again inside formatted_data["raw_data"] --
|
||||
roughly tripling payload size for no informational gain and making the
|
||||
two tools' outputs nearly indistinguishable from each other. The
|
||||
top-level duplicate is now dropped; raw access remains available via
|
||||
formatted_data.raw_data.
|
||||
_make_request envelope) and again inside the formatted payload's
|
||||
"raw_data" -- roughly tripling payload size for no informational gain and
|
||||
making the two tools' outputs nearly indistinguishable from each other.
|
||||
|
||||
The formatted payload is now assigned *over* the raw envelope, so the
|
||||
duplication stays gone and the payload is delivered under the "data" key
|
||||
that both tools' return_schema declares (it was previously published under
|
||||
a non-standard "formatted_data" key that no other tool in the registry
|
||||
uses, so callers reading result["data"] by convention silently saw
|
||||
nothing). Raw access remains available at data["raw_data"].
|
||||
"""
|
||||
|
||||
from unittest.mock import patch
|
||||
@@ -23,10 +28,18 @@ _VARIANT_DATA = {
|
||||
"accession": "VCV000000009",
|
||||
"obj_type": "single nucleotide variant",
|
||||
"chr_sort": "13",
|
||||
"variation_set": [{"variation_loc": [{"band": "13q13.1"}], "variation_name": "NM_007294.4(BRCA1):c.68_69delAG"}],
|
||||
"variation_set": [
|
||||
{
|
||||
"variation_loc": [{"band": "13q13.1"}],
|
||||
"variation_name": "NM_007294.4(BRCA1):c.68_69delAG",
|
||||
}
|
||||
],
|
||||
"title": "NM_007294.4(BRCA1):c.68_69delAG",
|
||||
"genes": [{"symbol": "BRCA1"}],
|
||||
"germline_classification": {"description": "Pathogenic", "review_status": "reviewed"},
|
||||
"germline_classification": {
|
||||
"description": "Pathogenic",
|
||||
"review_status": "reviewed",
|
||||
},
|
||||
"clinical_impact_classification": {},
|
||||
"oncogenicity_classification": {},
|
||||
}
|
||||
@@ -38,31 +51,54 @@ _FETCH_RESULT = {
|
||||
}
|
||||
|
||||
|
||||
def _fetch_variant_patch():
|
||||
def _count_variant_copies(node):
|
||||
"""Count how many times the raw variant record appears anywhere in the
|
||||
response, so duplication is caught no matter where it is reintroduced."""
|
||||
if isinstance(node, dict):
|
||||
found = 1 if node == _VARIANT_DATA else 0
|
||||
return found + sum(_count_variant_copies(v) for v in node.values())
|
||||
if isinstance(node, list):
|
||||
return sum(_count_variant_copies(v) for v in node)
|
||||
return 0
|
||||
|
||||
|
||||
def _fetch_variant_patch(tool_cls):
|
||||
return patch.object(
|
||||
ClinVarGetVariantDetails.__bases__[0],
|
||||
tool_cls.__bases__[0],
|
||||
"_fetch_variant",
|
||||
return_value={"variant_data": _VARIANT_DATA, "result": dict(_FETCH_RESULT)},
|
||||
)
|
||||
|
||||
|
||||
def test_get_variant_details_has_no_duplicate_top_level_data():
|
||||
def _assert_payload_contract(result):
|
||||
# Payload is delivered under the schema-declared key, not the old one.
|
||||
assert "data" in result
|
||||
assert "formatted_data" not in result
|
||||
# Raw access is preserved...
|
||||
assert result["data"]["raw_data"] == _VARIANT_DATA
|
||||
# ...but the raw esummary envelope no longer sits at the top level, and
|
||||
# the variant record is present exactly once in the whole response.
|
||||
assert "result" not in result["data"]
|
||||
assert result["data"] != _FETCH_RESULT["data"]
|
||||
assert _count_variant_copies(result) == 1
|
||||
|
||||
|
||||
def test_get_variant_details_has_no_duplicate_raw_data():
|
||||
tool = ClinVarGetVariantDetails({"name": "ClinVar_get_variant_details"})
|
||||
with _fetch_variant_patch():
|
||||
with _fetch_variant_patch(ClinVarGetVariantDetails):
|
||||
result = tool.run({"variant_id": "9"})
|
||||
|
||||
assert "data" not in result
|
||||
assert result["formatted_data"]["raw_data"] == _VARIANT_DATA
|
||||
_assert_payload_contract(result)
|
||||
assert result["data"]["variant_id"] == "9"
|
||||
assert result["data"]["accession"] == "VCV000000009"
|
||||
assert result["data"]["clinical_significance"] == "Pathogenic"
|
||||
|
||||
|
||||
def test_get_clinical_significance_has_no_duplicate_top_level_data():
|
||||
def test_get_clinical_significance_has_no_duplicate_raw_data():
|
||||
tool = ClinVarGetClinicalSignificance({"name": "ClinVar_get_clinical_significance"})
|
||||
with patch.object(
|
||||
ClinVarGetClinicalSignificance.__bases__[0],
|
||||
"_fetch_variant",
|
||||
return_value={"variant_data": _VARIANT_DATA, "result": dict(_FETCH_RESULT)},
|
||||
):
|
||||
with _fetch_variant_patch(ClinVarGetClinicalSignificance):
|
||||
result = tool.run({"variant_id": "9"})
|
||||
|
||||
assert "data" not in result
|
||||
assert result["formatted_data"]["raw_data"] == _VARIANT_DATA
|
||||
_assert_payload_contract(result)
|
||||
assert result["data"]["variant_id"] == "9"
|
||||
assert result["data"]["germline_classification"]["description"] == "Pathogenic"
|
||||
|
||||
Reference in New Issue
Block a user