Skip to content
Conduit

mcp server

mol-search-sparql-service

πŸ«† A SPARQL service for finding similar chemicals and substructures using molecular fingerprints with RDKit

sib-swiss/mol-search-sparql-servicecommunitylistedunreviewed

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`)
~/Library/Application Support/Claude/claude_desktop_config.json
{
  "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, 2026

title: 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 SERVICE clause:

    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).

  1. chem IRI: IRI of the chemical. Must be explicitly wrapped in angle brackets (e.g., <http://...>, <urn:...>) or start with http(s)://.
  2. SMILES: SMILES string. If RDKit fails to parse the string into a valid molecule, the engine will issue a warning and skip the row.
  3. 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, Rhea will 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. The pattern fingerprint 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 to server.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 to http://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 with MolFromSmiles (sanitized, aromatized, stereo perceived).
  • func:smart β€” parsed with MolFromSmarts as a query graph, matched literally (wildcards, any-bond ~, recursive SMARTS; aliphatic C does 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 pattern fingerprint. It is always computed automatically, even when using the -t/--fingerprints option 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:ListFingerprints lists every fingerprint type the code supports, but a given deployment only compiles the subset selected via -t/--fingerprints (plus pattern, always loaded for substructure search). To discover which are actually usable for similarity search on a running instance, read the func:compiled flag β€” only rows where it is true are searchable. (func:ListDatabases likewise reports only the databases actually loaded.)

[!WARNING] A Note on atom_pair and topological_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:

PredicateTypeDescription
func:fpTypestrIdentifier key for the fingerprint type (e.g. morgan_ecfp).
func:descriptionstrHuman readable description of the fingerprint.
func:mechanismstrExplainability mechanism / how bits map to substructures.
func:shortNamestrShort display name for the fingerprint (e.g. ECFP, MACCS).
func:compiledboolTrue 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:

PredicateTypeDescription
func:dbNamestrThe 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:

PredicateTypeDefaultDescription
func:smilesstrrequiredQuery SMILES string.
func:limitint10Maximum number of results to return.
func:dbNamesUnionType[str, NoneType]NoneOptional database name to filter results.
func:fpTypestr'morgan_ecfp'Fingerprint type key to use.
func:minScorefloat0.0Minimum similarity score threshold (0.0 - 1.0).

Outputs:

PredicateTypeDescription
func:resultURIRefThe URI of the matching compound.
func:scorefloatTanimoto 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:smart is parsed with MolFromSmarts as a query graph. It keeps query features (wildcards, any-bond ~, recursive SMARTS, degree/charge constraints) and matches literally β€” an aliphatic C will NOT match an aromatic carbon, and aromaticity is not re-perceived.
  • func:smiles is parsed with MolFromSmiles: sanitized, aromatized and with stereochemistry perceived, then used as a substructure query.

IRI: urn:sparql-function:SubstructureSearch

Inputs:

PredicateTypeDefaultDescription
func:smartUnionType[str, NoneType]NoneQuery SMARTS pattern to match (mutually exclusive with smiles).
func:smilesUnionType[str, NoneType]NoneQuery SMILES pattern to match (mutually exclusive with smart).
func:limitint100Maximum number of results to return (default: 100).
func:dbNamesUnionType[str, NoneType]NoneOptional database name to limit the search.
func:minMatchCountint1Minimum number of substructure matches required.
func:useChiralityboolFalseIf 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:

PredicateTypeDescription
func:resultURIRefThe URI of the matching compound.
func:matchCountintNumber of matches found (1 if boolean match).
func:matchedSmilesstrSMILES of the matched fragment, rendered from the target (stereo preserved).
func:matchedSmartsstrSMARTS of the matched fragment, rendered from the target (stereo preserved).
func:completeSmilesstrThe 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

KeyShort NameDescription
morgan_ecfpECFPExtended Connectivity Fingerprint (ECFP). Encodes atom-centered circular environments up to a given radius. Widely used for similarity search, clustering, and QSAR.
morgan_ecfp_chiralECFP_CExtended 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_fcfpFCFPFunctional-Class Fingerprint (FCFP). Morgan fingerprint using pharmacophoric atom features instead of exact atom types.
morgan_fcfp_chiralFCFP_CFunctional-Class Fingerprint (FCFP). Morgan fingerprint using pharmacophoric atom features instead of exact atom types. (Computed with stereochemistry enabled).
rdk_topologicalRDKRDKit topological (path-based) fingerprint. Encodes linear bond paths similar to Daylight fingerprints.
atom_pairAPAtom Pair fingerprint. Encodes pairs of atoms along with their topological distance.
topological_torsionTTTopological Torsion fingerprint. Encodes sequences of four bonded atoms.
maccsMACCSMACCS structural keys (166 bits). Each bit corresponds to a predefined chemical pattern.
patternPatternRDKit 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
↑↓ navigate openesc close