Metadata-Version: 2.5
Name: biothings-search
Version: 0.2.0
Summary: Search and understand any biological entity (gene, protein, variant, disease, drug, chemical, pathway)
Project-URL: Homepage, https://github.com/biothings/biothings_search
Project-URL: Repository, https://github.com/biothings/biothings_search
Project-URL: Issues, https://github.com/biothings/biothings_search/issues
Project-URL: Changelog, https://github.com/biothings/biothings_search/blob/main/CHANGELOG.md
Project-URL: BioThings APIs, https://biothings.io
Author-email: BioThings Team <biothings@scripps.edu>
Maintainer-email: BioThings Team <biothings@scripps.edu>
License-Expression: Apache-2.0
License-File: LICENSE
License-File: NOTICE
Keywords: ai-tools,bioinformatics,biothings,drug,gene,mcp,variant
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: anyio>=4
Requires-Dist: httpx>=0.27
Requires-Dist: mcp[cli]<2,>=1.0
Requires-Dist: pydantic>=2
Requires-Dist: rich>=13
Requires-Dist: typer>=0.12
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: pyyaml>=6; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Description-Content-Type: text/markdown

# biothings_search

Search and understand any biological entity — gene, protein, variant, disease, drug, chemical, or pathway — from within AI coding agents.

Backed by [BioThings APIs](https://biothings.io) (MyGene.info, MyVariant.info, MyChem.info, MyDisease.info, and more) and UniProt.

---

## Install

Requires Python 3.11 or newer.

```bash
pip install biothings-search
# or
uv add biothings-search
```

Then install the agent skills for your editor (they ship with the package):

```bash
biothings-search install-skills            # this project
biothings-search install-skills --global   # user-wide, all projects
biothings-search providers                 # list install targets
```

By default this writes the three locations named by the
[Agent Skills specification](https://agentskills.io) — `.agents/skills`,
`.claude/skills` and `.github/skills`. Between them they cover every client below.

Naming a client that reads `.agents/skills` installs there rather than creating
a directory for it — saying `--provider cursor` tells us which agent you use, it
does not ask for a `.cursor/` folder. Only Codex needs its own.

| Provider | Installs to | Notes |
|----------|-------------|-------|
| `agents` | `.agents/skills` | **Standard.** Any conforming agent reads this |
| `claude` | `.claude/skills` | **Standard.** Claude Code |
| `github` | `.github/skills`, `~/.copilot/skills` | **Standard.** GitHub Copilot, VS Code |
| `codex` | `.codex/skills` | Codex CLI — the only client needing its own directory |
| `cursor` | `.agents/skills` | Reads the standard path |
| `gemini` | `.agents/skills` | Reads the standard path |
| `pi` | `.agents/skills` | Reads the standard path |
| `opencode` | `.agents/skills` | Reads the standard path |
| `openclaw` | `.agents/skills` | Reads the standard path |

## Quick start (CLI)

```bash
biothings-search BRCA1                        # auto-detect type
biothings-search NCBIGene:695                 # CURIE ID lookup
biothings-search BRCA1 TP53 aspirin           # batch: multiple queries at once
biothings-search gene TP53
biothings-search variant rs28934578
biothings-search chemical aspirin
biothings-search disease "breast cancer"
biothings-search protein P04637
biothings-search taxon "Homo sapiens"
biothings-search geneset "p53 pathway"
biothings-search phenotype seizure
biothings-search paper "BRCA1 breast cancer"
biothings-search trial "BRCA1 mutation"
biothings-search anatomy liver
biothings-search cell "T cell"
biothings-search gwas "breast cancer"
biothings-search drug ibuprofen
biothings-search chebi "CHEBI:15365"
biothings-search get 672 --type gene                 # fetch by specific ID
biothings-search get 695 7157 672 --type gene        # batch: one request, not three
biothings-search prefixes                            # list all supported CURIE prefixes
biothings-search sources                             # list the databases searched
biothings-search fields gene                         # list the fields a source offers
```

All commands accept `--json` / `-j` for machine-readable output and `--size` / `-n`.
The default of 5 suits most lookups; raise it when you need to enumerate or
post-process a set with `--json`. The ceiling is 1000, though each source clamps
to what it will actually serve — UniProt tops out at 500.

### Asking for more than the summary

Results are normalized down to the fields most questions need — name, canonical
ID, taxon, summary. When you need more, name the source's own fields with
`--fields` / `-f` and they come back under `extra`, nested the way the source
returns them:

```bash
biothings-search gene BRCA1 -f genomic_pos,go.BP
biothings-search get 1017 --type gene -f genomic_pos
biothings-search gene BRCA1 -f all --json          # the whole record
```

`biothings-search fields <type>` lists what a source offers, straight from the
source, so it is never out of date; `--match` narrows a long catalogue:

```bash
biothings-search fields gene --match genomic
biothings-search fields chemical --match drugbank
```

Requested fields are *added* to the ones the parser needs, never substituted —
`-f genomic_pos` still returns a named, identified hit. Fields the record does
not carry are simply absent from `extra` rather than reported as empty.

This applies to the BioThings sources. UniProt, PubMed and ClinicalTrials use
their own field vocabularies; they accept `--fields` and ignore it, so a
fan-out search never fails on the source that happens to answer.

### A shorter command

`biothings-search` is the only console script installed. If you want a short
form, add an alias to your shell profile:

```bash
alias bts='biothings-search'   # or biothings, bt, whatever you like
```

An alias only applies to what *you* type. It is not expanded inside scripts, or
when another program invokes the command — so if you write a script, a Makefile
or your own agent skill that calls the short name, use a symlink instead:

```bash
ln -s "$(command -v biothings-search)" ~/.local/bin/bts
```

(The skills shipped with this package call `biothings-search` directly, so they
work either way.)

`bts` is deliberately *not* installed as a script: Debian's `devscripts`
package already ships a [`/usr/bin/bts`](https://manpages.debian.org/testing/devscripts/bts.1.en.html)
(the Bug Tracking System CLI), and shadowing it on every Debian and Ubuntu
machine would be a poor trade for four saved keystrokes.

---

## MCP server (AI agent integration)

Start the server:

```bash
biothings-search serve
# or directly:
biothings-search-server
```

### Claude Code

```bash
claude mcp add biothings biothings-search-server
```

### Claude Desktop

Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS):

```json
{
  "mcpServers": {
    "biothings": {
      "command": "biothings-search-server"
    }
  }
}
```

### Cursor

Add to `.cursor/mcp.json` in your project (already included in this repo):

```json
{
  "mcpServers": {
    "biothings": {
      "command": "biothings-search-server"
    }
  }
}
```

Or add to Cursor's global MCP settings under **Settings → MCP**.

### Windsurf

Add to Windsurf MCP settings (**Settings → MCP Servers**):

```json
{
  "mcpServers": {
    "biothings": {
      "command": "biothings-search-server"
    }
  }
}
```

### VS Code (Continue.dev)

Add to `.continue/config.json`:

```json
{
  "mcpServers": [
    {
      "name": "biothings",
      "command": "biothings-search-server"
    }
  ]
}
```

### Available MCP tools (22)

| Tool | Description |
|------|-------------|
| `search_biothing` | Universal search — auto-detects entity type |
| `search_biothing_batch` | Search multiple entities concurrently |
| `search_gene` | Genes via MyGene.info |
| `search_variant` | Variants via MyVariant.info |
| `search_chemical` | Chemicals/drugs via MyChem.info |
| `search_disease` | Diseases via MyDisease.info |
| `search_protein` | Proteins via UniProt |
| `search_taxon` | Taxonomy via MyTaxon.info |
| `search_geneset` | Gene sets/pathways via MyGeneset.info |
| `search_phenotype` | Phenotypes via Human Phenotype Ontology |
| `search_paper` | Publications via PubMed |
| `search_trial` | Clinical trials via ClinicalTrials.gov |
| `search_anatomy` | Anatomy via Uberon |
| `search_cell_type` | Cell types via Cell Ontology |
| `search_gwas` | GWAS associations via GWAS Catalog |
| `search_drug` | FDA-approved drugs |
| `search_chebi` | Chemical ontology via ChEBI |
| `get_entity` | Fetch entity by specific ID |
| `get_entities` | Fetch several entities by ID in one batched request |
| `list_entity_types` | List all supported types and CURIE prefixes |
| `list_sources` | List the databases searched and how to address each |
| `list_fields` | List the extra source fields available to `fields` |

---

## Agent skills (slash commands)

Install skills so you can invoke them with `/biothings`, `/biothings-gene`, etc.:

```bash
biothings-search install-skills                      # standard locations, this project
biothings-search install-skills --global             # under your home directory
biothings-search install-skills --provider cursor    # add a vendor directory
biothings-search install-skills --dry-run            # show what would be written
```

`.agents/skills` is the cross-client location: agents that follow the
[Agent Skills standard](https://agentskills.io) discover skills there with no
vendor-specific setup. `.claude/skills` and `.github/skills` are the other two
locations the specification names, for Claude Code and GitHub Copilot / VS Code
respectively. Everything else is a vendor convention — the files are identical,
so an extra copy costs nothing but a directory.

Note the home locations differ for two clients: Copilot reads `~/.copilot/skills`
rather than `~/.github/skills`, and pi reads `~/.pi/agent/skills`. `--global`
handles both.

Contributors regenerating the copies committed in this repo use
`python scripts/build_skills.py` instead; the source of truth is
`src/biothings_search/skills/`.

| Skill | Command |
|-------|---------|
| Universal search | `/biothings BRCA1` |
| Gene search | `/biothings-gene TP53` |
| Variant search | `/biothings-variant rs28934578` |
| Chemical search | `/biothings-chemical aspirin` |
| Disease search | `/biothings-disease "breast cancer"` |
| Protein search | `/biothings-protein P04637` |
| Taxon search | `/biothings-taxon "Homo sapiens"` |
| Geneset search | `/biothings-geneset "p53 pathway"` |
| Phenotype search | `/biothings-phenotype seizure` |
| Paper search | `/biothings-paper "BRCA1 2023"` |
| Trial search | `/biothings-trial "BRCA1 mutation"` |
| Anatomy search | `/biothings-anatomy liver` |
| Cell type search | `/biothings-cell "T cell"` |
| GWAS search | `/biothings-gwas "breast cancer"` |
| Drug search | `/biothings-drug ibuprofen` |
| ChEBI search | `/biothings-chebi "CHEBI:15365"` |
| Help | `/biothings-help` |

---

## CURIE ID support

Queries in CURIE format (`prefix:id`) are resolved directly to the correct entity and API — no text search needed. Prefixes follow the [Biolink Model](https://biolink.github.io/biolink-model/) standard (validated against 4.4.4) and are case-insensitive.

```bash
biothings-search NCBIGene:695         # → gene BTK
biothings-search UniProtKB:P04637     # → protein TP53
biothings-search DBSNP:rs28934578     # → variant
biothings-search MONDO:0007254        # → disease breast cancer
biothings-search CHEBI:15365          # → aspirin
biothings-search CL:0000084           # → T cell
biothings-search NCT:NCT00000125      # → clinical trial
biothings-search NDC:66715-6526       # → an FDA-listed drug product
```

Run `biothings-search prefixes` to see all 51 supported prefixes with their canonical ID priority.
Every prefix listed there is verified to resolve against the live API — a namespace
with no reachable backend is left out rather than advertised and silently dead.

Input is deliberately more forgiving than output. Eleven of those prefixes
(`pubmed:`, `ensg:`, `chembl:`, `nct:`, …) are conveniences that are not in the
Biolink Model; they are accepted because people type them, and never emitted.
Everything in `canonical_id` uses the model's own spelling.

`biothings-search chebi` and `biothings-search gwas` search those databases
specifically, but their results are typed by what they are — a ChEBI entry is a
`chemical`, a GWAS record is a `variant` — with the database in each hit's
`source`. That keeps `canonical_id` usable for linking: the same compound has
the same type and ID whether it came from ChEBI or MyChem.

Each result includes a `canonical_id` field — the highest-priority Biolink-standard CURIE available for that entity (e.g. `NCBIGene:672` for BRCA1, `UniProtKB:P04637` for TP53), following that class's `id_prefixes` order in the model.

One consequence worth knowing: a MyChem record contributed only by the NDC
directory is a marketed drug product rather than a small molecule, so it comes
back typed `drug` with an `NDC:` ID even from a `chemical` search. Biolink draws
the same line — `Drug` carries NDC in its `id_prefixes` and `SmallMolecule` does
not — and it is the difference between having a canonical ID and having none.

---

## Entity type auto-detection

Bare identifiers are recognized automatically without a CURIE prefix:

| Pattern | Detected as |
|---------|-------------|
| `rs28934578` | Variant (dbSNP rsID) |
| `chr1:g.123A>T` | Variant (HGVS) |
| `ENSG00000141510` | Gene (Ensembl) |
| `7157` (integer) | Gene (Entrez ID) |
| `P04637` | Protein (UniProt) |
| `CHEMBL25` | Chemical (ChEMBL) |
| 27-char InChIKey | Chemical |
| `DB00945` | Chemical (DrugBank) |
| `DOID:1612` | Disease (DO) |
| `MONDO:0007254` | Disease (MONDO) |
| `HP:0001250` | Phenotype (HPO) |
| `GO:0006915` | Gene set (GO) |
| `R-HSA-109582` | Gene set (Reactome) |
| `UBERON:0002107` | Anatomy (Uberon) |
| `CL:0000084` | Cell type (CL) |
| `CHEBI:15365` | ChEBI chemical |
| `PMID:12345678` | Publication |
| `NCT00000125` | Clinical trial |
| `GCST90428116` | GWAS study |

Unknown patterns trigger a concurrent fan-out search across genes, chemicals, diseases, and proteins.

---

## Scoping to a species

Gene, protein and gene-set searches cover every organism by default, so a symbol
query returns the same gene from dozens of species. Scope it once and forget it:

```bash
export BIOTHINGS_SEARCH_SPECIES=human        # or human,mouse — or a taxid
biothings-search gene BTK                    # human only
biothings-search gene BTK --species mouse    # per-command override
biothings-search gene BTK --species all      # opt back out
```

For an agent client, set it in the MCP server config — there is no per-call flag
to reach for, and it applies to every tool:

```json
{
  "mcpServers": {
    "biothings": {
      "command": "biothings-search-server",
      "env": { "BIOTHINGS_SEARCH_SPECIES": "human" }
    }
  }
}
```

The MCP tools also take a `species` argument that overrides it for one call.

Accepts a name (`human`, `mouse`, `zebrafish`, `fruitfly`, …), an NCBI taxid
(`9606`), a comma-separated combination, or `all`. An unrecognised value fails
immediately and lists the alternatives, rather than reaching the API as an
opaque error.

**Direct ID lookups are never scoped.** `biothings-search NCBIGene:12229`
resolves the mouse record even under `BIOTHINGS_SEARCH_SPECIES=human` — a CURIE
names one entity, so filtering it could only ever return nothing. Only searches
are filtered. Variants, chemicals, diseases and the ontologies have no species
concept and are unaffected.

## Configuration

All optional; sensible defaults apply when unset.

| Variable | Default | Purpose |
|----------|---------|---------|
| `BIOTHINGS_SEARCH_SPECIES` | `all` | Restrict gene/protein/geneset searches to one or more organisms (name, NCBI taxid, or `all`). Never applied to ID lookups. |
| `BIOTHINGS_TRANSLATOR_HOST` | `https://biothings.transltr.io` | Host for the Translator-hosted sources (ChEBI, HPO, Uberon, Cell Ontology, GWAS, FDA, trials). Point at the CI deployment to test pre-release data. |
| `NCBI_API_KEY` | — | Raises the PubMed rate limit from 3 to 10 requests/second. [Get one here](https://account.ncbi.nlm.nih.gov/settings/). |
| `BIOTHINGS_SEARCH_EMAIL` | `help@biothings.io` | Contact address sent to NCBI E-utilities. |
| `BIOTHINGS_SEARCH_CACHE_TTL` | `300` | Seconds a response stays cached. `0` disables caching. |
| `BIOTHINGS_SEARCH_CACHE_SIZE` | `512` | Maximum cached responses. |

Requests are rate-limited per host, retried on transient failures (429 and 5xx)
with exponential backoff, and cached in-process, so a batch of concurrent
searches will not trip an upstream limit or re-fetch what it just read.

---

## Data sources

| Entity type | API | Coverage |
|-------------|-----|----------|
| Gene | [MyGene.info](https://mygene.info) | 40M+ annotations, 20K+ species |
| Variant | [MyVariant.info](https://myvariant.info) | 900M+ human variants |
| Chemical | [MyChem.info](https://mychem.info) | ChEMBL, DrugBank, PubChem, FDA NDC |
| Disease | [MyDisease.info](https://mydisease.info) | MONDO, OMIM, DO, NCIT |
| Protein | [UniProt](https://www.uniprot.org) | Swiss-Prot / TrEMBL |
| Taxon | [MyTaxon.info](https://mytaxon.info) | NCBI Taxonomy |
| Gene set / Pathway | [MyGeneset.info](https://mygeneset.info) | GO, MSigDB, Reactome, WikiPathways, SMPDB |
| Phenotype | Human Phenotype Ontology | BioThings HPO API |
| Paper | [PubMed](https://pubmed.ncbi.nlm.nih.gov) | NCBI E-utilities |
| Clinical trial | [ClinicalTrials.gov](https://clinicaltrials.gov) | BioThings CT API |
| Anatomy | Uberon | BioThings Uberon API |
| Cell type | Cell Ontology | BioThings CL API |
| GWAS | [GWAS Catalog](https://www.ebi.ac.uk/gwas/) | BioThings GWAS API |
| Drug | FDA Drugs@FDA | BioThings FDA API |
| ChEBI | [ChEBI](https://www.ebi.ac.uk/chebi/) | BioThings ChEBI API |

---

## Extending with new data sources

1. Subclass `BioThingsAPI` in `src/biothings_search/apis/biothings.py` (or `BaseAPI` for a
   non-BioThings source) and implement `_parse_hit()`, setting `canonical_id` on each `SearchHit`
2. Register in `_TYPE_TO_API` in `search.py`
3. Add entries to `_CURIE_MAP` and `CANONICAL_PREFIXES` in `models.py`, verifying each prefix
   against the live API first
4. Add a CLI command in `cli.py` and an MCP tool in `server.py`
5. Create a skill in `src/biothings_search/skills/` and run `python scripts/build_skills.py`
6. Add a fixture case in `tests/fixtures/_capture.py` and a parser test

See [CONTRIBUTING.md](https://github.com/biothings/biothings_search/blob/main/CONTRIBUTING.md) for details.

---

## License

[Apache License 2.0](LICENSE).

Commercial use is unrestricted. The licence covers copyright only —
[section 6](https://www.apache.org/licenses/LICENSE-2.0#trademarks) grants no
trademark rights, so "BioThings" and the service names are governed separately
by [TRADEMARKS.md](TRADEMARKS.md). In short: say your software *works with*
BioThings as much as you like; don't *name* your software BioThings.
