mcp server
mol-search-sparql-service
π« A SPARQL service for finding similar chemicals and substructures using molecular fingerprints with RDKit
Set up
Runs on your computer. Pick your agent and operating system.
Before you start
- uv (`curl -LsSf https://astral.sh/uv/install.sh | sh`)
{
"mcpServers": {
"mol-search-sparql-service": {
"command": "uvx",
"args": [
"mol_search_sparql_service"
]
}
}
}same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12
Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs
README
synced Oct 25, 2026title: Chemistry Search SPARQL Service emoji: π« colorFrom: indigo colorTo: blue sdk: docker app_port: 8010 pinned: false
π« Chemistry Search SPARQL Service
A service for finding similar chemicals and substructures using molecular fingerprints. It provides both a SPARQL endpoint and an MCP (Model Context Protocol) server interface.
Built using RDKit and rdflib-endpoint.
π§© Features
-
SPARQL endpoint: Query for similarity and substructure matches using standard SPARQL syntax. You can also query this service from another SPARQL endpoint using the
SERVICEclause:PREFIX func: <urn:sparql-function:> SELECT ?result ?score WHERE { SERVICE <http://localhost:8010/sparql> { [] a func:SimilaritySearch ; func:smiles "c1ccccc1" ; func:result ?result ; func:score ?score . } }Federated query to get more information about molecule in rhea:
#+ endpoint: https://sparql.rhea-db.org/sparql/ PREFIX func: <urn:sparql-function:> PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> PREFIX rh: <http://rdf.rhea-db.org/> SELECT DISTINCT ?chebi ?chebiLabel ?score ?rhea ?equation WHERE { # 1) Run RDKit similarity search, returning ChEBI IRIs in ?chebi SERVICE <https://biosoda.unil.ch/mol-search/sparql> { [] a func:SimilaritySearch ; func:smiles "c1ccccc1" ; # benzene func:limit 20 ; func:result ?chebi ; func:score ?score . } # 2) Join the similar hits to Rhea reactions via the ChEBI class used by each Rhea participant ?rhea rdfs:subClassOf rh:Reaction ; rh:status rh:Approved ; rh:equation ?equation ; rh:side/rh:contains/rh:compound/rdfs:subClassOf ?chebi . ?chebi rdfs:label ?chebiLabel . } ORDER BY DESC(?score) ?rhea LIMIT 50 -
MCP Server: Exposes SPARQL functions documentation through resource and prompt to help LLMs to perform searches (experimental).
π₯ Installation
You can install this tool directly from GitHub using uv:
uv tool install git+https://github.com/sib-swiss/mol-search-sparql-service.git
π Usage
Start the service by pointing it to a compounds data file (TSV):
mol-search-sparql-service -f compounds.tsv
[!IMPORTANT]
The input TSV file is processed strictly by column order (headers are automatically detected and skipped). The engine will stop and raise an error if any row is structurally malformed (e.g. missing columns or invalid IRI syntax).
- chem IRI: IRI of the chemical. Must be explicitly wrapped in angle brackets (e.g.,
<http://...>,<urn:...>) or start withhttp(s)://.- SMILES: SMILES string. If RDKit fails to parse the string into a valid molecule, the engine will issue a warning and skip the row.
- db: (Optional) Database source name. Note on URIs vs Literals: The engine preserves the exact string formatting provided. To specify a URI for accurate SPARQL matching later, you must wrap it in angle brackets (e.g.,
<http://rhea>). Otherwise,Rheawill be treated as a literal string.
Alternatively, you can dynamically fetch the compounds data on startup using a SPARQL query against a remote endpoint:
mol-search-sparql-service -s fetch_rhea.rq -e https://sparql.rhea-db.org/sparql
Other available optional flags include:
-t,--fingerprints: Comma-separated list of fingerprint types to compute (e.g.morgan_ecfp,pattern). If omitted, all types are computed. Thepatternfingerprint is always computed regardless of this option, as it is required for substructure search.-p,--port: Port to run the server on (default:8010).-w,--workers: Number of Uvicorn workers (default:1).-d,--daemon: Run the server in the background (logs toserver.log).-u,--public-url: Public URL of the SPARQL endpoint exposed to clients (e.g.https://api.example.com/sparql). Used by the MCP server to advertise the correct endpoint address. Useful when running behind a reverse proxy. Defaults tohttp://localhost:<port>/sparql.
π Default Endpoints
Once the service is running (default port 8010), you can access:
- SPARQL Endpoint:
http://localhost:8010/sparqlβ Standard SPARQL Protocol endpoint for querying - MCP Server:
http://localhost:8010/mcpβ Model Context Protocol server endpoint for LLM integration
π Query Examples
Once the service is running, you can query it using SPARQL. Here are some common examples:
Similarity Search β Find compounds similar to a query molecule:
PREFIX func: <urn:sparql-function:>
SELECT ?result ?score WHERE {
[] a func:SimilaritySearch ;
func:smiles "c1ccccc1" ;
func:limit 5 ;
func:result ?result ;
func:score ?score .
}
Substructure Search β Find all compounds containing a specific substructure (e.g., benzene ring). Supply the query with exactly one of two properties, which RDKit parses differently:
func:smilesβ parsed withMolFromSmiles(sanitized, aromatized, stereo perceived).func:smartβ parsed withMolFromSmartsas a query graph, matched literally (wildcards, any-bond~, recursive SMARTS; aliphaticCdoes not match aromatic carbon).
Each result also reports the matched fragment as SMILES and SMARTS, rendered from the target so its stereochemistry is preserved (one row per distinct match):
PREFIX func: <urn:sparql-function:>
SELECT ?result ?matchCount ?matchedSmiles ?matchedSmarts WHERE {
[] a func:SubstructureSearch ;
func:smiles "c1ccccc1" ;
func:limit 100 ;
func:result ?result ;
func:matchCount ?matchCount ;
func:matchedSmiles ?matchedSmiles ;
func:matchedSmarts ?matchedSmarts .
}
Or query with a SMARTS pattern using query features, e.g. any carbon bonded to any nitrogen:
PREFIX func: <urn:sparql-function:>
SELECT ?result ?matchCount WHERE {
[] a func:SubstructureSearch ;
func:smart "[#6]~[#7]" ;
func:result ?result ;
func:matchCount ?matchCount .
}
[!NOTE] Substructure search requires the
patternfingerprint. It is always computed automatically, even when using the-t/--fingerprintsoption to restrict which fingerprints are loaded.
π§ Memory Profile & Optimization
By default, the service computes all 9 fingerprint types (including chiral variants) for every loaded compound. Because they are hashed into ExplicitBitVect (2048-bits), each fingerprint consumes roughly 256 bytes per molecule. This equates to a total footprint of approximately ~2.5 GB of RAM per 1,000,000 compounds.
If you do not need all fingerprints, you can drastically reduce memory usage by explicitly passing the --fingerprints flag to specify only the ones you intend to search against.
[!NOTE] Supported vs. compiled fingerprints.
func:ListFingerprintslists every fingerprint type the code supports, but a given deployment only compiles the subset selected via-t/--fingerprints(pluspattern, always loaded for substructure search). To discover which are actually usable for similarity search on a running instance, read thefunc:compiledflag β only rows where it istrueare searchable. (func:ListDatabaseslikewise reports only the databases actually loaded.)
[!WARNING] A Note on
atom_pairandtopological_torsion: Previous versions of this service computed these fingerprints as exact, unhashed sparse vectors. For complex molecules, these sparse maps grew exponentially, causing memory usage to skyrocket. They are now forced into fixed-size 2048-bit dense vectors to cap memory and dramatically accelerate similarity speeds. While this is the industry standard for fast searching, minor bit-collisions may occur compared to exact sparse pairwise matching.
π Functions
func:ListFingerprints
List all supported fingerprint types and flag which are compiled here.
Returns every fingerprint the code supports (the static registry). A given
deployment only compiles the subset selected via the -t/--fingerprints
CLI option, so use func:compiled to tell which ones are actually usable
for search on this instance. Filter with FILTER(?compiled) to list only
the active ones.
IRI: urn:sparql-function:ListFingerprints
Outputs:
| Predicate | Type | Description |
|---|---|---|
func:fpType | str | Identifier key for the fingerprint type (e.g. morgan_ecfp). |
func:description | str | Human readable description of the fingerprint. |
func:mechanism | str | Explainability mechanism / how bits map to substructures. |
func:shortName | str | Short display name for the fingerprint (e.g. ECFP, MACCS). |
func:compiled | bool | True if this fingerprint is actually compiled/loaded in this instance (i.e. usable for search), as opposed to merely supported by the code. |
Example:
PREFIX func: <urn:sparql-function:>
SELECT ?fpType ?shortName ?compiled WHERE {
[] a func:ListFingerprints ;
func:fpType ?fpType ;
func:shortName ?shortName ;
func:compiled ?compiled .
}
func:ListDatabases
List available database names loaded in the service.
IRI: urn:sparql-function:ListDatabases
Outputs:
| Predicate | Type | Description |
|---|---|---|
func:dbName | str | The name or URI of the database stored in the service. |
Example:
PREFIX func: <urn:sparql-function:>
SELECT ?dbName WHERE {
[] a func:ListDatabases ;
func:dbName ?dbName .
}
func:SimilaritySearch
Perform similarity search using precomputed fingerprints.
IRI: urn:sparql-function:SimilaritySearch
Inputs:
| Predicate | Type | Default | Description |
|---|---|---|---|
func:smiles | str | required | Query SMILES string. |
func:limit | int | 10 | Maximum number of results to return. |
func:dbNames | UnionType[str, NoneType] | None | Optional database name to filter results. |
func:fpType | str | 'morgan_ecfp' | Fingerprint type key to use. |
func:minScore | float | 0.0 | Minimum similarity score threshold (0.0 - 1.0). |
Outputs:
| Predicate | Type | Description |
|---|---|---|
func:result | URIRef | The URI of the matching compound. |
func:score | float | Tanimoto similarity score (0-1). |
Example:
PREFIX func: <urn:sparql-function:>
SELECT ?result ?score WHERE {
[] a func:SimilaritySearch ;
func:smiles "[NH3+][C@@H](Cc1ccccc1)C(=O)[O-]" ;
func:limit 3 ;
func:result ?result ;
func:score ?score .
}
func:SubstructureSearch
Perform substructure search using a SMARTS or SMILES query pattern.
Provide exactly one of func:smart or func:smiles; the two are parsed
differently by RDKit:
func:smartis parsed withMolFromSmartsas a query graph. It keeps query features (wildcards, any-bond~, recursive SMARTS, degree/charge constraints) and matches literally β an aliphaticCwill NOT match an aromatic carbon, and aromaticity is not re-perceived.func:smilesis parsed withMolFromSmiles: sanitized, aromatized and with stereochemistry perceived, then used as a substructure query.
IRI: urn:sparql-function:SubstructureSearch
Inputs:
| Predicate | Type | Default | Description |
|---|---|---|---|
func:smart | UnionType[str, NoneType] | None | Query SMARTS pattern to match (mutually exclusive with smiles). |
func:smiles | UnionType[str, NoneType] | None | Query SMILES pattern to match (mutually exclusive with smart). |
func:limit | int | 100 | Maximum number of results to return (default: 100). |
func:dbNames | UnionType[str, NoneType] | None | Optional database name to limit the search. |
func:minMatchCount | int | 1 | Minimum number of substructure matches required. |
func:useChirality | bool | False | If true, both tetrahedral (R/S) and double-bond (E/Z) stereochemistry are enforced during matching. Defaults to false. Most meaningful for SMILES queries; SMARTS encodes its own stereo in the pattern. |
Outputs:
| Predicate | Type | Description |
|---|---|---|
func:result | URIRef | The URI of the matching compound. |
func:matchCount | int | Number of matches found (1 if boolean match). |
func:matchedSmiles | str | SMILES of the matched fragment, rendered from the target (stereo preserved). |
func:matchedSmarts | str | SMARTS of the matched fragment, rendered from the target (stereo preserved). |
func:completeSmiles | str | The original, complete SMILES string of the matching compound as stored in the service. |
Example 1:
PREFIX func: <urn:sparql-function:>
SELECT ?result ?matchCount ?matchedSmiles ?matchedSmarts WHERE {
[] a func:SubstructureSearch ;
func:smart "[#6]~[#7]" ;
func:result ?result ;
func:matchCount ?matchCount ;
func:matchedSmiles ?matchedSmiles ;
func:matchedSmarts ?matchedSmarts .
}
Example 2:
PREFIX func: <urn:sparql-function:>
SELECT ?result ?matchCount ?matchedSmiles ?completeSmiles WHERE {
[] a func:SubstructureSearch ;
func:smiles "c1ccccc1" ;
func:result ?result ;
func:matchCount ?matchCount ;
func:matchedSmiles ?matchedSmiles ;
func:completeSmiles ?completeSmiles .
}
π« Available Fingerprint Types
| Key | Short Name | Description |
|---|---|---|
morgan_ecfp | ECFP | Extended Connectivity Fingerprint (ECFP). Encodes atom-centered circular environments up to a given radius. Widely used for similarity search, clustering, and QSAR. |
morgan_ecfp_chiral | ECFP_C | Extended Connectivity Fingerprint (ECFP). Encodes atom-centered circular environments up to a given radius. Widely used for similarity search, clustering, and QSAR. (Computed with stereochemistry enabled). |
morgan_fcfp | FCFP | Functional-Class Fingerprint (FCFP). Morgan fingerprint using pharmacophoric atom features instead of exact atom types. |
morgan_fcfp_chiral | FCFP_C | Functional-Class Fingerprint (FCFP). Morgan fingerprint using pharmacophoric atom features instead of exact atom types. (Computed with stereochemistry enabled). |
rdk_topological | RDK | RDKit topological (path-based) fingerprint. Encodes linear bond paths similar to Daylight fingerprints. |
atom_pair | AP | Atom Pair fingerprint. Encodes pairs of atoms along with their topological distance. |
topological_torsion | TT | Topological Torsion fingerprint. Encodes sequences of four bonded atoms. |
maccs | MACCS | MACCS structural keys (166 bits). Each bit corresponds to a predefined chemical pattern. |
pattern | Pattern | RDKit Pattern fingerprint. Designed for substructure screening. |
π οΈ Development
Run tests:
uv run pytest
Format and lint:
uvx ruff format && uvx ruff check --fix
Auto-generate docs from functions docstrings and update the README.md:
uv run src/mol_search_sparql_service/gen_docs.py
Deploy with docker:
docker compose up -d --build
To release a new version run the release script providing the version bump: fix, minor, or major:
.github/release.sh fix
Generate slides:
cd slides
npm i
npm run dev
npm run build
Push changes to HuggingFace Spaces:
git remote add space https://huggingface.co/spaces/vemonet/mol-search-sparql-service
git push --force https://$HF_USER:$HF_TOKEN@huggingface.co/spaces/vemonet/mol-search-sparql-service main