
Tooluniverse Sdk
- 352 installs
- 1.6k repo stars
- Updated August 4, 2026
- mims-harvard/tooluniverse
tooluniverse-sdk is an agent integration skill that wires the Harvard ToolUniverse Python SDK and SMCP MCP server into coding agents to register, discover, validate, and invoke 1000+ scientific tools from production rese
About
tooluniverse-sdk is an agent skill from mims-harvard/ToolUniverse that teaches developers to wire the Harvard ToolUniverse SDK and SMCP MCP server into agent or MCP runtimes. ToolUniverse is an ecosystem integrating 1000+ machine learning models, datasets, APIs, and scientific packages behind a standardized AI-Tool Interaction Protocol, with 68 pre-built research workflows for drug discovery and related domains. The skill covers registering tools, loading catalogs, validating schemas, and calling scientific utilities from production pipelines via Python SDK or MCP transports (stdio, HTTP, SSE). Developers reach for tooluniverse-sdk when building AI scientist agents that need programmatic access to biomedical databases, analysis tools, and remote MCP servers. SMCP exposes 350+ scientific tools through FastMCP with tools/find and tools/search discovery methods.
- Harvard MIMS ToolUniverse SDK setup
- Tool catalog registration and discovery
- Schema validation for callable tools
- Agent and MCP runtime integration patterns
Tooluniverse Sdk by the numbers
- 352 all-time installs (skills.sh)
- +5 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #2,128 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/mims-harvard/tooluniverse --skill tooluniverse-sdkAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 352 |
|---|---|
| repo stars | ★ 1.6k |
| Last updated | August 4, 2026 |
| Repository | mims-harvard/tooluniverse ↗ |
How do you integrate ToolUniverse SDK with agents?
Wire the Harvard ToolUniverse SDK into agent or MCP runtimes to register tools, load catalogs, validate schemas, and call scientific or general utilities from production pipelines.
Who is it for?
ML engineers and research developers building AI scientist agents that need programmatic access to Harvard ToolUniverse's scientific tool catalog via SDK or MCP.
Skip if: Developers building generic CRUD APIs without scientific tool discovery, schema validation, or research workflow orchestration needs.
When should I use this skill?
User asks to set up ToolUniverse, wire SMCP MCP server, register scientific tools, or call ToolUniverse SDK from an agent pipeline.
What you get
MCP server configs, registered tool catalogs, validated tool schemas, and callable scientific utility pipelines.
- MCP server configuration
- Registered tool catalogs
- Validated tool call pipelines
By the numbers
- Integrates 1000+ machine learning models, datasets, APIs, and scientific packages
- SMCP MCP server exposes 350+ scientific tools
- Repository includes 68 pre-built research workflows
Files
ToolUniverse Python SDK
3 calling patterns -- start with pattern 1: 1. tu.run({"name": ..., "arguments": ...}) -- single tool call, dict API (most portable) 2. tu.tools.ToolName(param=value) -- function API (recommended for interactive use) 3. Direct class instantiation -- advanced, bypasses caching/hooks
Installation
pip install tooluniverse # Standard
pip install tooluniverse[embedding] # Embedding search (GPU)
pip install tooluniverse[all] # All featuresexport OPENAI_API_KEY="sk-..." # Required for LLM tool search
export NCBI_API_KEY="..." # OptionalQuick Start
from tooluniverse import ToolUniverse
tu = ToolUniverse()
tu.load_tools() # REQUIRED before any tool call
# Find tools
tools = tu.run({"name": "Tool_Finder_Keyword", "arguments": {"description": "protein structure", "limit": 10}})
# Execute (dict API)
result = tu.run({"name": "UniProt_get_entry_by_accession", "arguments": {"accession": "P05067"}})
# Execute (function API)
result = tu.tools.UniProt_get_entry_by_accession(accession="P05067")Core Patterns
Batch Execution
calls = [
{"name": "UniProt_get_entry_by_accession", "arguments": {"accession": "P05067"}},
{"name": "UniProt_get_entry_by_accession", "arguments": {"accession": "P12345"}},
]
results = tu.run_batch(calls)Scientific Workflow
def drug_discovery_pipeline(disease_id):
tu = ToolUniverse(use_cache=True)
tu.load_tools()
try:
targets = tu.tools.OpenTargets_get_associated_targets_by_disease_efoId(efoId=disease_id)
compound_calls = [
{"name": "ChEMBL_search_molecule_by_target",
"arguments": {"target_id": t['id'], "limit": 10}}
for t in targets['data'][:5]
]
compounds = tu.run_batch(compound_calls)
return {"targets": targets, "compounds": compounds}
finally:
tu.close()Configuration
# Caching
tu = ToolUniverse(use_cache=True)
stats = tu.get_cache_stats()
tu.clear_cache()
# Hooks (auto-summarization of large outputs)
tu = ToolUniverse(hooks_enabled=True)
# Load specific categories
tu.load_tools(categories=["proteins", "drugs"])Critical Notes
1. Always call `load_tools()` before using any tools 2. Tool Finder returns nested structure: access via tools['tools'] after isinstance(tools, dict) check 3. Tool names are case-sensitive: UniProt_get_entry_by_accession not uniprot_get_... 4. Check required params: tu.all_tool_dict["ToolName"]['parameter'].get('required', []) 5. Cache deterministic calls (ML predictions, DB queries); don't cache real-time data
Error Handling
from tooluniverse.exceptions import ToolError, ToolUnavailableError, ToolValidationError
try:
result = tu.tools.some_tool(param="value")
except ToolUnavailableError:
... # Tool service down
except ToolValidationError as e:
tool_info = tu.all_tool_dict["some_tool"]
print(f"Required: {tool_info['parameter'].get('required', [])}")Tool Categories
| Category | Tools | Use Cases |
|---|---|---|
| Proteins | UniProt, RCSB PDB, AlphaFold | Protein analysis, structure |
| Drugs | DrugBank, ChEMBL, PubChem | Drug discovery, compounds |
| Genomics | Ensembl, NCBI Gene, gnomAD | Gene analysis, variants |
| Diseases | OpenTargets, ClinVar | Disease-target associations |
| Literature | PubMed, Europe PMC | Literature search |
| ML Models | ADMET-AI, AlphaFold | Predictions, modeling |
| Pathways | KEGG, Reactome | Pathway analysis |
Resources
- Docs: https://zitniklab.hms.harvard.edu/ToolUniverse/
- GitHub: https://github.com/mims-harvard/ToolUniverse
- See REFERENCE.md for detailed guides.
ToolUniverse SDK Reference
Detailed reference for ToolUniverse Python SDK features and advanced usage.
Installation Details
Package Options
# Base installation (~200-300 MB)
pip install tooluniverse
# With embedding search (+1-2 GB for model weights)
pip install tooluniverse[embedding]
# With ML tools (+2-5 GB for model weights)
pip install tooluniverse[ml]
# All features
pip install tooluniverse[all]
# Minimal client for HTTP API access
pip install tooluniverse[client]Performance Metrics
- First
load_tools(): 5-10 seconds (1000++ tools) - Subsequent loads: 2-3 seconds (cached)
- Specific categories: <2 seconds
- Memory: 200-300 MB base, +1-2 GB with embeddings
Initialization Options
tu = ToolUniverse(
use_cache=True, # Global caching
hooks_enabled=True, # Auto-summarization
log_level="INFO", # Logging level
tool_files={}, # Custom tool configs
keep_default_tools=True # Include defaults
)
# Load options
tu.load_tools(
categories=["proteins", "drugs"], # Specific categories
tool_config_files={...} # Custom configs
)Tool Discovery Methods
Method 1: Keyword Search
Best for: Fast searches, exact matches, no API key
tools = tu.run({
"name": "Tool_Finder_Keyword",
"arguments": {
"description": "protein structure",
"limit": 10
}
})
# Always check structure
if isinstance(tools, dict) and 'tools' in tools:
for tool in tools['tools']:
print(f"{tool['name']}: {tool['description']}")Method 2: LLM Search
Best for: Complex queries, natural language, best matches Requires: OPENAI_API_KEY
tools = tu.run({
"name": "Tool_Finder_LLM",
"arguments": {
"description": "find genetic variants associated with Alzheimer's",
"limit": 5
}
})Method 3: Embedding Search
Best for: Semantic similarity, concept matching Requires: GPU for embedding model
tools = tu.run({
"name": "Tool_Finder",
"arguments": {
"description": "protein-protein interaction networks",
"limit": 10,
"return_call_result": False # Only return info
}
})Advanced Caching
Cache Configuration
import os
from pathlib import Path
cache_dir = Path.home() / ".tooluniverse" / "cache"
cache_dir.mkdir(parents=True, exist_ok=True)
os.environ["TOOLUNIVERSE_CACHE_PATH"] = str(cache_dir / "cache.sqlite")
os.environ["TOOLUNIVERSE_CACHE_ENABLED"] = "true"
os.environ["TOOLUNIVERSE_CACHE_PERSIST"] = "true"
tu = ToolUniverse(use_cache=True)
tu.load_tools()Cache Management
# Get statistics
stats = tu.get_cache_stats()
print(f"Hits: {stats['hits']}, Misses: {stats['misses']}")
# Inspect cache
for entry in tu.dump_cache():
print(f"Tool: {entry['tool_name']}")
print(f"Hit count: {entry['hit_count']}")
# Clear cache
tu.clear_cache()
# Always close connections
tu.close()When to Cache
✅ Good candidates:
- ML model predictions (deterministic)
- Database queries (stable data)
- Protein structure predictions
- Literature searches (stable results)
❌ Avoid:
- Real-time data
- Time-sensitive queries
- User-specific data
- Rapidly changing data
Hooks System
Basic Hooks
# Enable default summarization hook
tu = ToolUniverse(hooks_enabled=True)
tu.load_tools()
result = tu.tools.OpenTargets_get_target_gene_ontology_by_ensemblID(
ensemblId="ENSG00000012048"
)
# Check if hook applied
if isinstance(result, dict) and "summary" in result:
print(f"Original: {result['original_length']} chars")
print(f"Summary: {len(result['summary'])} chars")Custom Hook Configuration
hook_config = {
"exclude_tools": [
"Tool_RAG",
"ToolFinderEmbedding",
"CustomTool_*" # Wildcard pattern
],
"hooks": [{
"name": "summarization_hook",
"type": "SummarizationHook",
"enabled": True,
"conditions": {
"output_length": {"operator": ">", "threshold": 5000}
},
"hook_config": {
"max_tokens": 2000,
"summary_style": "concise"
}
}]
}
tu = ToolUniverse(hooks_enabled=True, hook_config=hook_config)
tu.load_tools()File Save Hook
import tempfile
hook_config = {
"hooks": [{
"name": "file_save_hook",
"type": "FileSaveHook",
"enabled": True,
"conditions": {
"output_length": {"operator": ">", "threshold": 10000}
},
"hook_config": {
"temp_dir": tempfile.gettempdir(),
"file_prefix": "tool_output",
"include_metadata": True
}
}]
}
tu = ToolUniverse(hooks_enabled=True, hook_config=hook_config)
tu.load_tools()
result = tu.tools.some_large_output_tool(param="value")
if isinstance(result, dict) and "file_path" in result:
print(f"Saved to: {result['file_path']}")Remote HTTP API
Deploy ToolUniverse as a server for remote access.
Server Setup
# Install on server
pip install tooluniverse
# Start server
tooluniverse-http-api --host 0.0.0.0 --port 8080Client Usage
# Install minimal client
pip install tooluniverse[client]from tooluniverse import ToolUniverseClient
client = ToolUniverseClient("http://server:8080")
# Use like local ToolUniverse
client.load_tools(tool_type=['uniprot', 'ChEMBL'])
result = client.run_one_function({
"name": "UniProt_get_entry_by_accession",
"arguments": {"accession": "P05067"}
})Benefits:
- Minimal client dependencies (
requests+pydantic) - All computation on server
- Automatic method updates
- Shared instance across users
Complex Workflows
Multi-Step Pipeline
def analyze_disease_targets(disease_efo_id):
"""Complete disease-to-drug pipeline"""
tu = ToolUniverse(use_cache=True, hooks_enabled=True)
tu.load_tools()
try:
# Step 1: Get targets
targets = tu.tools.OpenTargets_get_associated_targets_by_disease_efoId(
efoId=disease_efo_id
)
if not targets or 'data' not in targets:
return {"error": "No targets found"}
# Step 2: Get target info (batch)
target_ids = [t['target']['id'] for t in targets['data'][:5]]
target_calls = [
{"name": "UniProt_get_entry_by_accession",
"arguments": {"accession": tid}}
for tid in target_ids
]
target_info = tu.run_batch(target_calls)
# Step 3: Find compounds (batch)
compound_calls = [
{"name": "ChEMBL_search_molecule_by_target",
"arguments": {"target_id": tid, "limit": 10}}
for tid in target_ids
]
compounds = tu.run_batch(compound_calls)
# Step 4: ADMET predictions
all_smiles = []
for comp_list in compounds:
if comp_list and 'molecules' in comp_list:
all_smiles.extend([m['smiles'] for m in comp_list['molecules'][:3]])
admet_calls = [
{"name": "ADMETAI_predict_admet", "arguments": {"smiles": s}}
for s in all_smiles
]
admet_results = tu.run_batch(admet_calls)
# Step 5: Literature search
gene_names = [t.get('gene_name', '') for t in target_info if t]
lit_calls = [
{"name": "PubMed_search_articles",
"arguments": {"query": f"{gene} drug therapy", "max_results": 10}}
for gene in gene_names[:3] if gene
]
literature = tu.run_batch(lit_calls)
return {
"targets": targets,
"target_info": target_info,
"compounds": compounds,
"admet": admet_results,
"literature": literature
}
except Exception as e:
return {"error": str(e)}
finally:
tu.close()Error Handling Wrapper
from tooluniverse.exceptions import ToolError, ToolUnavailableError
def safe_tool_executor(tu, tool_name, arguments, fallback_tool=None):
"""Execute tool with comprehensive error handling"""
try:
return tu.run({
"name": tool_name,
"arguments": arguments
})
except ToolUnavailableError:
if fallback_tool:
print(f"⚠️ {tool_name} unavailable, trying {fallback_tool}")
return tu.run({
"name": fallback_tool,
"arguments": arguments
})
return {"error": f"Tool {tool_name} unavailable"}
except ToolError as e:
print(f"❌ Tool error: {e}")
return {"error": str(e)}
except Exception as e:
print(f"❌ Unexpected error: {e}")
return {"error": str(e)}
# Usage
result = safe_tool_executor(
tu,
"UniProt_get_entry_by_accession",
{"accession": "P05067"},
fallback_tool="AlternativeTool"
)Environment Variables
API Keys
export OPENAI_API_KEY="sk-..." # Required for LLM tools
export NCBI_API_KEY="..." # Optional, higher rate limits
export USPTO_API_KEY="..." # For patent toolsCache Configuration
export TOOLUNIVERSE_CACHE_ENABLED="true"
export TOOLUNIVERSE_CACHE_PERSIST="true"
export TOOLUNIVERSE_CACHE_PATH="/path/to/cache.sqlite"Performance Tuning
export TOOLUNIVERSE_LIGHT_IMPORT="1" # Minimal imports
export TOOLUNIVERSE_LOAD_TIMEOUT="30" # Load timeout (seconds)
export TOOLUNIVERSE_TIMEOUT="120" # Execution timeout (seconds)Common Issues & Solutions
Issue: Tool Not Loading
# Check tool registry
from tooluniverse.tool_registry import get_tool_registry
registry = get_tool_registry()
print(f"Registered: {len(registry)}")
# Enable debug logging
import logging
logging.basicConfig(level=logging.DEBUG)
tu = ToolUniverse()
tu.load_tools()Issue: Slow Performance
# Solution 1: Load specific categories
tu = ToolUniverse()
tu.load_tools(categories=["proteins", "drugs"])
# Solution 2: Enable caching
tu = ToolUniverse(use_cache=True)
# Solution 3: Use batch execution
results = tu.run_batch(calls)
# Solution 4: Disable validation (after testing)
result = tu.tools.tool_name(param="value", validate=False)Issue: High Memory Usage
# Use light import mode
import os
os.environ["TOOLUNIVERSE_LIGHT_IMPORT"] = "1"
from tooluniverse import ToolUniverse
tu = ToolUniverse()
tu.load_tools(categories=["proteins"])
# Clear cache periodically
tu.clear_cache()
# Always close
tu.close()Issue: API Key Errors
import os
# Check if set
api_key = os.environ.get("OPENAI_API_KEY")
if not api_key:
print("⚠️ Set: export OPENAI_API_KEY='sk-...'")
else:
print(f"✅ Key set: {api_key[:10]}...")
# Test key
try:
tools = tu.run({
"name": "Tool_Finder_LLM",
"arguments": {"description": "test", "limit": 1}
})
print("✅ API key valid")
except Exception as e:
print(f"❌ API key error: {e}")Best Practices Summary
1. Always call `load_tools()` after initialization 2. Check result structures (tool finders return nested dicts) 3. Use caching for expensive, deterministic operations 4. Use batch execution for parallel tasks 5. Handle errors with try/except blocks 6. Close connections with tu.close() when done 7. Validate parameters before execution (check tool schema) 8. Use appropriate tool finder (keyword for speed, LLM for accuracy) 9. Enable hooks for large outputs 10. Document workflows clearly
Additional Resources
- Main Documentation: https://zitniklab.hms.harvard.edu/ToolUniverse/
- API Reference: https://zitniklab.hms.harvard.edu/ToolUniverse/api/modules.html
- Tutorials: https://zitniklab.hms.harvard.edu/ToolUniverse/guide/scientific_workflows.html
- GitHub Examples: https://github.com/mims-harvard/ToolUniverse/tree/main/examples
- Community Slack: https://join.slack.com/t/tooluniversehq/shared_invite/zt-3dic3eoio-5xxoJch7TLNibNQn5_AREQ
Related skills
How it compares
Pick tooluniverse-sdk for scientific research tool catalogs; use generic MCP wrappers when you only need a single API without schema-validated tool discovery.
FAQ
How do you install ToolUniverse for agents?
ToolUniverse installs via uvx tooluniverse for MCP config or uv pip install tooluniverse for Python SDK access. Agent skills install with npx skills add mims-harvard/ToolUniverse.
How many tools does ToolUniverse expose?
ToolUniverse integrates 1000+ machine learning models, datasets, APIs, and scientific packages. The SMCP MCP server bridges 350+ scientific tools with discovery via tools/find and tools/search.