TogoMCP Tutorial

A hands-on tutorial for querying life-science databases in plain language — without writing SPARQL.

Written for life-science researchers and graduate students. No informatics background is assumed. You can work through it without knowing RDF or SPARQL.


What you will be able to do

  1. Connect TogoMCP to your own environment and query the major life-science databases in plain language
  2. Tell whether an answer came from a database or from the AI's memory
  3. Keep your results in a reproducible form — good enough to put in a paper

The third one matters most. The first one alone takes ten minutes.

Most of this tutorial is spent not on examples that work, but on examples that fail. In Chapter 4 we deliberately ask a vague question. It returns a fluent answer in about a dozen seconds — and that answer never touched a database. Nothing on the screen tells you so.

Being fast and confidently wrong is more dangerous than being slow and failing.

Learning to tell the difference is what this tutorial is for.


How to read this

About 90 minutes end to end. You do not have to read all of it.

Chapter Contents Time
00 Overview Why TogoMCP exists. What RDF and SPARQL are 8 min
★01 Setup Connect (there is a no-install route) 10 min
★02 First demo Run it. Then look at what happened underneath 10 min
03 How it works MCP and MIE — why correct SPARQL comes out 20 min
04 Harder questions Cross-database queries, and questions that fail 20 min
05 Skills Methodology packaged as a workflow 10 min
★06 How to ask a good question The chapter most worth taking away 10 min
07 Verification and reproducibility What to do before a number goes into a paper 10 min
08 Troubleshooting Read when things break as needed
Appendix Local install, KEGG 15 min
Exercises / Answers Self-check 30 min

In a hurry? The three ★ chapters alone are enough to be useful (01 → 02 → 06, about 30 minutes).

Using this page:

  • Navigate from the table of contents on the left; your position updates as you scroll
  • Prompts and queries copy with one click — hover over a code block and a Copy button appears
  • toggles dark/light, prints (save as PDF from there)
  • It reads fine on a phone

⚠️ About the numbers in this tutorial — please read this first

The text is full of actual results: accession numbers, counts, resolutions. Every one of them was measured.

Measurement conditions:

Dates 2026-08-20 (initial) / 2026-08-21 (everything re-measured and corrected)
Model Session configured as claude-opus-5 and as claude-sonnet-5, both measured
Server Hosted instance, https://togomcp.rdfportal.org/mcp
Language Prompts in Japanese

And when you run the same queries, your numbers will be different.

That is not a malfunction. It is what a living database looks like. While this tutorial was being written, the PDB counts moved within a matter of days.

So what this tutorial wants you to take away is not the numbers — it is the queries, and the reasoning that produced them. That is why it consistently gives you the query text rather than a table of results.

This is not a weakness of the material. It is the very practice the material teaches. Chapter 7 explains why "record the query and the date" is the rule.

One more thing. How long a query takes, and which tools get used, depend on the model you are using. Faced with the same question, one model ran a single search; another never touched a database at all. The values that came back agreed — but how they were reached did not.


If you want to run a workshop with this material

The complete teaching kit is on GitHub — 90- and 60-minute schedules, a verbatim instructor script, full transcripts of every demo (for when the network fails), exercises with answers, and projection slides.

https://github.com/dbcls/togomcp — see the tutorial/ directory

The Markdown sources and the build script are included, so adapt it to your own field as you see fit.


Citation

If you use TogoMCP in your research, please cite:

Kinjo, A. R., Yamamoto, Y., Bustamante-Larriet, S., Labra-Gayo, J.-E., & Fujisawa, T. (2026). TogoMCP: Natural Language Querying of Life-Science Knowledge Graphs via Schema-Guided LLMs and the Model Context Protocol. Database 2026:baag042. https://doi.org/10.1093/database/baag042

For the evidence behind the MIE file design (an ablation study):

Kinjo, A. R., & Yamamoto, Y. (2026). Measure before you rewrite: ablation-driven redesign of LLM-facing RDF schema documentation in TogoMCP. BioHackrXiv. https://doi.org/10.37044/osf.io/6v5ra_v1

Please also cite the individual databases you actually used. TogoMCP is the doorway, not the source of the data.

  • Hosted server: https://togomcp.rdfportal.org/
  • Repository: https://github.com/dbcls/togomcp
  • RDF Portal: https://rdfportal.org/

00

00. Overview — Why TogoMCP

The problem we are trying to solve

Over the past fifteen years, the major life-science databases have largely moved to RDF. UniProt, PDB, ChEMBL, Reactome — all of them are machine-readably searchable through a query language called SPARQL. In principle, a question like "which human lysosomal enzymes are targets of approved drugs?" should be answerable with a single query.

In practice it is not. There are three reasons.

1. Few researchers can write SPARQL. This is not a question of ability but of return on investment. It is not rational to keep up a skill you use a few times a year.

2. Every database has its own vocabulary and its own graph structure. In UniProt a protein is up:Protein and the relation to a gene is up:encodedBy. In PDB you traverse from pdbo:datablock through pdbo:has_entityCategory. Learning one gets you nothing for the next.

3. Cross-database questions cannot be written without knowing each endpoint's quirks. Which graph to specify, which predicates are fast, where the join breaks. This is often undocumented, and the failures are silent — you get no error, you get the wrong count.

TogoMCP is an MCP server that hands all three of these off to the AI assistant.

What RDF and SPARQL are

This section is not here to teach you to write SPARQL. Writing it is the AI's job. But to follow what the AI is doing on screen, and to doubt the numbers that come back, you do need the four words below.

RDF is a way of representing data as nothing but three-part statements — "the B of A is C." Instead of tables, you build a database by piling up a great many of these.

insulin ──  organism  ──→ human
insulin ──  length    ──→ 110
insulin ── associated ──→ diabetes
              disease

The middle part — "organism", "length" — is called a predicate. Which predicates exist differs from database to database. That is what Chapter 3 is about.

SPARQL is the query language for these three-part statements. It is to RDF what SQL is to tables.

"Insulin" and "human" are not written as English words. They are written as names shaped like URLs, such as http://purl.uniprot.org/uniprot/P01308. Such a name is called an IRI. An IRI is a name, not a link. It is not there to be clicked open; it is there so that the same thing has the same name everywhere in the world. If UniProt's "human" and PDB's "human" are the same IRI, the two connect mechanically.

Last, a graph: a compartment that holds triples inside one database. A single store often houses several datasets side by side, and forgetting to name the compartment picks up rows from the dataset next door. Chapter 3 makes this happen on purpose.

Word What it means Where it bites
RDF Data as three-part statements Background. You can forget it after this
Predicate The middle part — "length", "associated disease" Get it wrong and you get zero rows (Ch. 3)
IRI A name shaped like a URL The tool that pins down a vague word (Ch. 3, Ch. 6)
Graph A compartment of data Forget it and the count goes wrong (Ch. 3)
SPARQL The query language for triples The AI writes it. You only read it

You do not have to memorize any of this. Coming back here when a word turns up is enough.

What MCP is

MCP (Model Context Protocol) is a standard for handing "tools" to an AI assistant. It lets you bolt on connections to external data and services without rebuilding the AI itself.

    You    ──→  Claude, etc.  ──→  MCP server  ──→  external data
 (question)      (picks a tool)     (TogoMCP)      (SPARQL / REST)

What matters is the shift: from what the AI "knows" to what it can "go and fetch." Instead of recalling memories from its training data, it builds the answer by querying a database that is alive right now.

What TogoMCP is

In one line:

An MCP server that makes the roughly 37 databases of RDF Portal queryable in natural language

Field Databases
Proteins & proteomics UniProt, PDB, jPOST
Genes & genomes NCBI Gene, Ensembl, HGNC, OMA, Bgee, HCO, MCO, DDBJ, MoG+, TogoVar, GWAS Catalog
Chemistry ChEMBL, PubChem, ChEBI, Rhea, BRENDA, MassBank
Pathways Reactome
Disease & clinical ClinVar, MedGen, MONDO, NANDO
Literature PubMed, PubTator
Microbiology BacDive, MediaDive, AMR Portal, NBRC
Glycans GlyCosmos
Ontologies MeSH, GO, HP, UBERON, CL, SO, ECO, EFO, PRO, FMA …
Taxonomy NCBI Taxonomy
Materials science SuperCon

Having the Japan-originated databases (TogoVar, jPOST, GlyCosmos, NBRC, MoG+, NANDO, MediaDive) all in one place is a feature nothing else substitutes for. Chapter 4 makes that value concrete.

Not "looks useful" but "measured and effective"

Tools of this kind are often impressive in a demo and useless in real work. For TogoMCP there is a quantitative evaluation.

In a comparison where the same problem set was solved with and without TogoMCP, the effect size was Cohen's d = 1.82*, with p < 0.001* by the Wilcoxon test.

Source: Kinjo, A. R., Yamamoto, Y., Bustamante-Larriet, S., Labra-Gayo, J.-E., & Fujisawa, T. (2026). TogoMCP: Natural Language Querying of Life-Science Knowledge Graphs via Schema-Guided LLMs and the Model Context Protocol. Database 2026:baag042. https://doi.org/10.1093/database/baag042

The figures above are those reported in that paper. They are not an independent measurement made for this tutorial.

An effect size of 1.82 is far beyond the conventional benchmark for "large" (0.8) in the behavioral sciences. But this is a measurement on a benchmark problem set, and it is no guarantee that you will see the same difference on your own research topic. Chapters 4 and 7 look concretely at the cases where it does not work.

💡 Why the source is cited this carefully. This tutorial will keep telling you not to believe a number that has no provenance. The tutorial itself cannot then produce numbers without provenance. The practice taught in Chapters 6 and 7 is observed in the body text as well.

What this tutorial aims at

Three things.

  1. Get connected and be able to use it (Chapters 1–2)
  2. Understand why it works (Chapter 3) — without this, you are helpless when it does not
  3. Be able to doubt an answer and verify it (Chapters 4, 6, 7)

Let me emphasize the third. What this tutorial spends the most time on is not the examples that work, but the ones that do not.

In Chapter 4 we deliberately throw a vague question at it. It comes back in 12 seconds with a fluent answer — and that answer never once consulted a database. What is more, nothing on the screen gives you a clue that this is so.

Being fast and confidently wrong is more dangerous than being slow and failing.

Learning to tell the difference is the center of this tutorial.


Next → 01. Setup

01

01. Setup

There are three routes. Start with route A. No installation, three minutes. B and C can wait until you need them.

Route For Time Installation
A. Claude custom connector almost everyone 3 min none
B. Claude Code (CLI) people who work on the command line 5 min Claude Code only
C. Local stdio developers, and anyone using KEGG 15 min Python, uv, git

None of the routes require a TogoMCP account or an API key (only if you use the NCBI tools do you need an NCBI API key → appendix).


Route A: Claude custom connector (recommended)

Works on every plan of Claude (Web / desktop / Cowork). The Free plan allows one custom connector; paid plans have no limit.

Steps

  1. Open Claude and go to Settings → Customize → Connectors
  2. "+""Add custom connector"
  3. Enter the MCP server URL:

https://togomcp.rdfportal.org/mcp

  1. After adding it, click the "+" button in the chat screen, choose Connectors, and enable it for that conversation

On a Team / Enterprise plan

An organization owner has to take one step first. Individual members cannot add it themselves.

  1. The owner goes to Organization settings → Connectors → Add, hovers over Custom, chooses Web, and adds it for the whole organization
  2. After that, each member connects for themselves from Customize → Connectors

💡 If your organization is on a Team/Enterprise plan, you cannot add it yourself. Ask your administrator.

If custom connectors are unavailable in your environment

There is a way through a local bridge called mcp-remote. It is a community tool, not an official Anthropic procedure, but it works.

Add the following to claude_desktop_config.json:

{
  "mcpServers": {
    "togomcp": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://togomcp.rdfportal.org/mcp"]
    }
  }
}

Route B: Claude Code (CLI)

Run the following in the terminal (before you enter a Claude session).

claude mcp add --scope user --transport http togomcp https://togomcp.rdfportal.org/mcp

Adding --scope user makes it available in every project. Without it, it is only active in that one directory.

Scope Stored in Applies to
--scope local (default) the per-project entry in ~/.claude.json that project only, and only you
--scope project .mcp.json at the project root everyone who clones the repository
--scope user the top level of ~/.claude.json all of your projects ← recommended

Checking the connection

claude mcp list

If ✔ Connected appears next to togomcp, you are done.

Display Meaning
✔ Connected success
✘ Failed to connect the URL is not being reached. Check the trailing /mcp
! Connected · tools fetch failed connected, but the tool list did not come back
! Needs authentication waiting on authentication (should not happen with TogoMCP)

Inside a Claude session, typing /mcp shows you the same state.

Removing and inspecting

claude mcp list              # list
claude mcp remove togomcp    # remove
claude mcp remove togomcp --scope user   # remove within a specified scope

Common mistakes

  1. Typing claude mcp add inside a Claude session. This is a command you type in the terminal. It will not work once you have typed claude and entered the session.
  2. Dropping the trailing /mcp from the URL. You get a 404.
  3. Forgetting the scope. The default local only works in that one directory. Most cases of "but it worked yesterday" are this.
  4. Putting the config file in the wrong place. Claude Code reads only ~/.claude.json and .mcp.json at the project root. Files like ~/.claude/.mcp.json are not read.

The above was confirmed on Claude Code v2.1.210 and later. claude --version tells you which version you have.


Route C: Local stdio

For developers, and the only route if you want to use the KEGG tools. The steps are collected in the appendix.


Checking that you are connected (all routes)

Ask Claude this.

What databases can you use from TogoMCP?

What you should see: in about ten seconds, a list of 37 databases including UniProt, PDB, ChEMBL and TogoVar, organized by field.

If it does not work:

Symptom What to check
It never mentions TogoMCP and answers in generalities whether the connector is enabled for that conversation (on route A you have to select it from "+" in each conversation)
It tells you it has no tools available the URL, and the state shown by claude mcp list
It tries to call a tool and fails the network. Possibly a corporate proxy or VPN

For details, go to 08. Troubleshooting.


Note: using it from ChatGPT / Gemini

TogoMCP is not Claude-only. But each host has its quirks.

  • ChatGPT: Developer Mode (Web only, not supported on mobile). Pro has read/fetch only, but that is enough. Plus cannot use custom MCP connectors. ⚠️ ChatGPT records the tool list once, when the connector is added, and never re-fetches it automatically. Tools added later stay invisible. If it tells you a tool that should exist is not there, run Scan Tools again, or delete the connector and add it back. Note that adding databases is not affected (the database catalog is delivered at query time, so it is always current).
  • Gemini / Antigravity: specify it as "serverUrl" in ~/.gemini/config/mcp_config.json. Note that TogoMCP is Streamable HTTP, not SSE.

MCP servers that are strong alongside it

TogoMCP works on its own, but using it together with the following widens what you can handle. Some of the skills in Chapter 5 assume them.

Server URL Role
PubMed official Claude connector literature search and full-text retrieval
OLS4 (EMBL-EBI) https://www.ebi.ac.uk/ols4/mcp exploring ontology terms and hierarchies
PubDictionaries https://pubdictionaries.org/mcp natural-language labels → ontology IDs

The typical combination is "settle the canonical term ID in OLS4 → query with that ID in TogoMCP." Chapter 6 shows why that order works.


Next → 02. The First Demo

02

02. The First Demo

First we run it. Then we open it up and look at what happened underneath. The second part matters more.


Demo 1: a one-hop question

Paste this as it is.

Give me the UniProt entry for the human insulin (INS) gene product. Function and sequence length too.

What comes back (measured at 18–28 s; it varies by model):

Item Value
Accession P01308
Mnemonic INS_HUMAN
Name Insulin [Cleaved into: Insulin B chain; Insulin A chain]
Organism Homo sapiens (Human)
Sequence length 110 aa
Mass 11,981 Da

Function (verbatim from UniProt):

Insulin decreases blood glucose concentration. It increases cell permeability to monosaccharides, amino acids and fatty acids. It accelerates glycolysis, the pentose phosphate cycle, and glycogen synthesis in liver.

If "110 aa" stopped you

That is the right reaction. You were taught that insulin is 51 amino acids (A chain 21 + B chain 30).

110 is the length of the preproinsulin precursor. Signal peptide (24) + B chain (30) + C peptide (31) + A chain (21) + the cleavage sites. What P01308 in UniProt points at is the translation product, not the mature hormone circulating in your blood. That is why the name in the answer says [Cleaved into: Insulin B chain; Insulin A chain].

This is the first thing you learn in this tutorial.

A database is more precise than your memory. And it can be pointing at something other than what you expected.

When an answer feels "wrong," the first thing to suspect is the gap between your expectation and the database's definition. The AI did not make a mistake.


Demo 2: carrying an ID across to another database

Still in the same conversation, ask this next.

Convert that UniProt ID to an Ensembl gene ID and an HGNC ID. And the PDB structures too.

"That UniProt ID" is enough. The context of the previous exchange carries over.

What comes back (measured at 18–30 s):

Converted to ID
Ensembl Gene ENSG00000254647
HGNC HGNC:6081
PDB a list of real structure IDs

This is calling an ID conversion service called TogoID. It holds the definition of the relation — "you get from UniProt to Ensembl through is product of gene" — so this is not blind string matching.

💡 HGNC sometimes comes back as the bare number 6081. The proper form is HGNC:6081.


What happened underneath (this is the real subject)

Open the tool call log.

  • Claude desktop / Web: expand the fold that appears while the answer is being written
  • Claude Code: it is displayed as it runs

In Demo 1, this was the order of operations.

1. TogoMCP_Usage_Guide()          ← read the usage guide
2. search_uniprot_entity(...)     ← search UniProt for INS and pin down P01308
3. get_MIE_file("uniprot")        ← read UniProt's "schema documentation"
4. run_sparql("uniprot", "...")   ← build the SPARQL and run it

Not magic. A procedure.

Step What it does Why it is needed
1 read the usage guide learn which databases exist and in what order to use them
2 ambiguous word → stable ID pin the string "insulin" to an ID that does not move: P01308
3 read the MIE file learn UniProt's predicate names, graph structure, and known pitfalls
4 run the SPARQL actually fetch the data

Step 3 is the core of TogoMCP. The next chapter covers it in detail. Here, just hold on to the fact that before writing any SPARQL, it always reads that database's schema documentation.

What we want you to notice

Step 1 runs every single time you say something. Not once at the start of the session.

📖 Terminology: "turn"

One round of the conversation. You say something once, and the turn is over when the response to it is finished.

The point to hold on to: however many tools get called inside a turn, it is still one turn. Four tools ran in the example above — that is one turn. Demo 1 and Demo 2 were typed in separately, so that is two turns.

[Turn 1] You: "The human insulin…" → Usage_Guide, search_uniprot, get_MIE, run_sparql, answer ↑ once, right here [Turn 2] You: "Convert that ID to Ensembl…" → Usage_Guide, togoid_getRelation, togoid_convertId, answer ↑ and once again

This is by design — the tool description says explicitly "call this every turn, before any other tool," on the premise that nothing from the previous turn's work carries over.

The guide itself is 44,570 characters (five English markdown files concatenated; roughly 11,000–13,000 tokens). It stacks up as a conversation gets long, so if you care about your usage, keep it in mind.

💡 If you have KEGG enabled in a local installation, the KEGG section is added and it grows further.


What these two demos show

Demo 1: we landed from an ambiguous word ("insulin") on a verifiable ID (P01308). The answer has a source.

Demo 2: we took that ID and crossed to another database. The single biggest nuisance in life-science databases — the same thing being called by a different name in every DB — got automated.

Combine the two and you reach the "questions that span multiple DBs" of Chapter 4.


Try it yourself

Repeat Demo 1 with a gene or protein you actually care about.

Give me the UniProt entry for human [YOUR GENE / PROTEIN]. Function and sequence length too.

What to check:

  1. Is the accession that came back the right one (five seconds on the UniProt website settles it)
  2. Is the sequence length what you expected. If not, why not (a precursor? an isoform?)
  3. Does run_sparql appear in the tool log. If it does not, where did the answer come from?

If number 3 is where you got snagged, go straight on to the second half of Chapter 4. That is the real subject.


Next → 03. How It Works

03

03. How It Works

Why can an AI write correct SPARQL against a database nobody taught it? The answer is not "because it is smart." It is because we hand it the schema documentation.


3-1. The shape of the data — groundwork for this chapter

If Chapter 0's "What RDF and SPARQL are" was enough for you, skip this. The real subject of this chapter is the traps in its second half. This section goes just far enough to make those legible.

An RDF database is a pile of three-part statements — "the B of A is C." Each of the three positions has a name.

  subject         predicate        object
     │               │               │
     ▼               ▼               ▼
  insulin ──── organism ─────────→ human
     │
     ├──────── length ───────────→ 110
     │
     └──────── associated ───────→ diabetes
                disease

Such a statement is called a triple. A "count" is the number of triples matching your conditions — that fact does the work later in this chapter.

Names are shaped like URLs

The diagram above used English words. The actual data does not.

<http://purl.uniprot.org/uniprot/P01308>          ← subject   (insulin)
    <http://purl.uniprot.org/core/organism>       ← predicate (organism)
        <http://purl.uniprot.org/taxonomy/9606> . ← object    (human)

A name shaped like a URL is an IRI. They are long, so you give the leading part an alias — which is all the PREFIX lines at the top of a SPARQL query are.

PREFIX up: <http://purl.uniprot.org/core/>
#      ↑ from here on, up:organism means the long IRI above

📖 Why a URL shape? Not so you can click it open. So that the same thing carries the same name everywhere in the world. If UniProt's "human" and NCBI's "human" are both taxonomy/9606, the two connect mechanically. That single fact is what lets Chapter 4 walk across several databases.

The flip side: if the IRIs differ, the machine treats them as different things — however identical they look to you. Chapter 4 runs into exactly this.

Graphs — compartments for triples

One store (an endpoint) often houses several datasets side by side. So triples are kept in compartments called graphs.

endpoint (sparql.uniprot.org)
 ├─ graph <.../uniprot>        ← UniProt's own triples
 ├─ graph <.../taxonomy>       ← organism triples
 └─ graph <.../another-dataset> ← something else living here

Writing FROM <graph> in SPARQL means look only inside that compartment. Leaving it out means search across all of them.

You may be thinking that since the answer comes back either way, this is a detail. Section 3-6 demonstrates that it is not.

How to read a query (you do not have to write one)

SPARQL appears several times from here on. You do not need to be able to write it, but knowing the shape lets you follow what the AI did.

SELECT ?protein ?mass                     # what to give back
FROM <http://sparql.uniprot.org/uniprot>  # which compartment to look in
WHERE {                                   # what shape of triple to look for
  ?protein up:mass ?mass .                #  subject  predicate  object
}

Anything starting with ? is a blank. Find every triple of the form "the up:mass of ?protein is ?mass", and return the values that landed in the blanks as a table. That is all.


3-2. The overall structure

    You
     │  "For human insulin, …"
     ▼
 ┌─────────────┐
 │  Claude     │  ← picks the tools, builds the query, reads the results
 └─────────────┘
     │  MCP protocol
     ▼
 ┌─────────────┐
 │  TogoMCP    │  ← the tools themselves. Also hands out the documentation
 └─────────────┘
     │
     ├──→ SPARQL endpoints (rdfportal.org and others)
     └──→ REST APIs (UniProt, ChEMBL, PDB, NCBI, TogoID, TogoVar …)

TogoMCP is not a mere relay. On top of the ability to send SPARQL, it has the ability to hand out knowledge — "this database is shaped like this, write it this way and it is fast, here is where it fails." Without the latter, the former is useless.


3-3. The tools come in three layers

Sorted by role, the TogoMCP tools look like this. Once you understand this three-layer structure, everything else is application.

Layer 1: the guidance layer — teaches you how to use it

Tool Role
TogoMCP_Usage_Guide How to use the whole thing. Catalog of databases. Rules you must follow
get_MIE_file The schema documentation for each database (below — the most important one)
get_sparql_endpoints Which DB lives at which endpoint
get_graph_list The graphs inside an endpoint

Layer 2: the grounding layer — turns "words" into "IDs"

Tool Conversion
search_uniprot_entity protein name → UniProt accession
search_chembl_molecule / _target drug or target name → ChEMBL ID
search_pdb_entity description of a structure → PDB ID
search_mesh_descriptor disease name → MeSH descriptor
search_reactome_entity / search_rhea_entity pathway name, reaction → ID
togoid_convertId ID → ID in another DB
ncbi_esearch / ncbi_esummary / ncbi_efetch the NCBI family
togovar_search_gene / _variant / _disease genes, variants, diseases (Japanese population data)

Why this layer is called "grounding": it pins wobbly words like "insulin" or "pancreatic cancer" to immovable identifiers like P01308 or D010190. Skip this layer and every step after it becomes guesswork. The failure demo in Chapter 4 is exactly what happens when this layer gets bypassed.

Layer 3: the execution layer

Tool Role
run_sparql Execute SPARQL

Just one. But you must not arrive here without reading Layer 1 — that is TogoMCP's single most important rule.


3-4. The MIE file — the core of the mechanism

A MIE (Metadata Interoperability Exchange) file is a YAML documentation file, one per database. The design goal is plain.

Give the LLM exactly enough information to write correct, fast SPARQL on the first attempt — no more, no less.

"No more, no less" is the crux. Handing over the entire schema would be accurate, but it is far too large to be practical. An outline alone is not enough to write with. MIE files are built on the policy of carrying only what the model cannot reconstruct on its own.

What is in a MIE

At the center of a MIE are verified worked examples. A single example does three jobs at once.

examples:
  - id: sequence_mass
    description: Retrieve a protein's sequence and mass
    sparql: |
      PREFIX up: <http://purl.uniprot.org/core/>
      SELECT ?sequence ?mass
      FROM <http://sparql.uniprot.org/uniprot>
      WHERE {
        ?protein up:sequence ?seq .
        ?seq rdf:value ?sequence ; up:mass ?mass .
      }
    traps_avoided:
      - union_inflation: without pinning the graph in a FROM clause,
        rows from other co-resident datasets get picked up and the count inflates
    verified: 2026-07-29
Element The job it does
sparql the shape of the schema itself (which predicates connect to what)
the execution result sample real data (what actually comes back)
traps_avoided a warning (the pitfall specific to this database)

Instead of writing the same content three times as "schema description," "sample," and "note," it is condensed into one example that runs.

See it for yourself

You can check this yourself. Ask Claude:

Show me the UniProt MIE file. Just the examples section is fine.

3-5. The rule: "always read the MIE before SPARQL"

The TogoMCP usage guide carries several mandatory rules. This is the most important one.

Before calling run_sparql, always call get_MIE_file for that database.

The rule is not there to be difficult. There are two reasons.

Reason 1: it prevents IRI hallucination. Without reading the MIE, the AI guesses — "the predicate is probably called something like this." Write a nonexistent predicate such as up:hasSequence and SPARQL will not raise an error. It returns 0 rows. And the AI reports "no matches." This is an extremely hard failure to detect.

Reason 2: it prevents timeouts. Two queries that return the same answer can differ in runtime by orders of magnitude depending on how they are written. The guide spells out a speed hierarchy.

specific IRI  ≫  narrowing by type  ≫  FILTER(CONTAINS(...))
    fast                              slow (effectively impossible on large graphs)

A measured example: a query that fetched insulin's sequence using FILTER(CONTAINS(STR(?seq), "/P01308-1")) died at the 60-second timeout. Rewritten to name the isoform IRI directly, it came back in about 5 seconds.


3-6. The traps — not "the wrong answer" but "the wrong count"

Most failures in life-science RDF happen silently. No error appears. A plausible-looking table comes back. The numbers are just wrong.

(a) Federation (SERVICE) is not available

The SERVICE clause, which joins multiple endpoints in a single query, is disabled on rdfportal.org. The correct approach is either to join within a single endpoint using GRAPH clauses, or to carry IDs and walk across manually (the Chapter 4 approach).

(b) Row inflation at co-resident endpoints

A single SPARQL endpoint may host several datasets side by side. When multiple datasets each declare predicates on a shared node (an organism IRI, say), a query that does not specify a graph picks up all of them, and the row count inflates silently.

The countermeasure is to pin the graph with FROM <graph name>, exactly as the MIE instructs.

FROM <http://sparql.uniprot.org/uniprot>     ← write this

🔬 Try it: see the trap with your own eyes

As long as you follow the MIE, this trap never fires. The graph is pinned from the start. Which means that if you quietly obey, you will never even learn the trap was there.

So let us break the rule on purpose.

Take a query that counts human lysosomal lumen enzymes in UniProt and run it
both ways — one version with the graph pinned in a FROM clause, one without —
then compare the counts. Give both COUNT(*) and COUNT(DISTINCT).

That last sentence matters. Without it, this trap stays invisible.

Measured (2026-08-21):

Version COUNT(*) COUNT(DISTINCT ?protein)
FROM pinned 98 98
FROM not pinned 196 98

Here is the crux. The row count doubled, but COUNT(DISTINCT ?protein) is 98 in both cases — a perfect match.

So this is not the simple story of "leave the graph unpinned and the answer changes." As long as you are using COUNT(DISTINCT), the answer in this example comes out right anyway. The dangerous one is COUNT(*): use that and the wrong number, 196, comes back with no error and no warning.

Worse still, the inflation factor is not fixed. Depending on the target it may double or it may not. If you are using AVG or SUM, or stacking joins on re-declared predicates (2^k for k of them), DISTINCT cannot absorb it and the answer itself goes wrong.

Track down where it came from

You can find out where the duplication came from by asking:

Which graph are those doubled rows coming from? Check with GRAPH ?g.

In the measurement, the a up:Protein typing was supplied by two graphs — UniProt itself, and another dataset co-resident at the same endpoint. Both declare the type on the same IRI, so without specifying a graph you get two rows per protein.

Learn this diagnostic move itself. "When a count looks suspicious, count the suppliers with GRAPH ?g" transfers directly to real work.

(c) Duplication from multi-valued predicates — this one does happen

Even after preventing (b), things can still inflate. That is the case where one entity holds the same predicate more than once.

A measured example. Counting the set of lysosomal lumen enzymes used in Chapter 4 gives this:

COUNT(*)                    = 62
COUNT(DISTINCT ?protein)    = 52      ← a 19% difference

The cause was that a single protein carries several EC numbers. Seven proteins were affected — for example —

Gene Number of EC numbers Breakdown
GBA1 4 2.4.1.-, 3.2.1.-, 3.2.1.45, 3.2.1.46
ASAH1 3 3.5.1.-, 3.5.1.109, 3.5.1.23
SMPD1 2 3.1.4.12, 3.1.4.3

Naively counting rows, you would have reported "62 lysosomal lumen enzymes." The correct figure is 52.

The lesson: before reporting a count, compare COUNT(*) against COUNT(DISTINCT ...). If they differ, do not report until you can explain what is being duplicated.

Chapter 7 organizes this verification procedure.


3-7. Summary

  1. TogoMCP is not "a tool for sending SPARQL" but "a mechanism for handing out the knowledge of how it should be sent"
  2. The tools form three layers: guidance / grounding / execution
  3. The MIE file is the core. Taking a verified worked example as its atomic unit, it conveys schema, real data, and traps all at once
  4. The rules (read the MIE first, pin the graph) exist to prevent silent failure
  5. Failures in life-science RDF surface not as errors but as counts that are off

Next → 04. Harder Questions

04

04. Harder Questions

This is where it gets real. We cover three things.

  1. Demo 4 — a question that search alone cannot answer (PDB)
  2. Demo 3' — a question that spans several databases (UniProt × ChEMBL)
  3. The failure demo — ★ the most important part of this chapter

Demo 4: a question that search alone cannot answer

Give me the top 10 PDB structures of the SARS-CoV-2 main protease
(3CL protease), ordered by best resolution.

Measured at 39–51 s. The most robust of the five demos (measured on two different models, zero failures and zero retries in both).

What to watch for is that it hits a wall partway through

search_pdb_entity runs first. The result: 1,872 hits, in no particular order.

This search tool cannot answer the question. Producing "the top 10 by best resolution" requires sorting on resolution, a numeric field, and that is a job for SPARQL, not for a search API.

So it proceeds to get_MIE_file("pdb")run_sparql. This is one of the few demos where the reason SPARQL is needed is visible on screen.

Results (measured 2026-08-21)

# PDB Å Title (excerpt)
1 9ZNL 1.16 Mpro covalently bound to inhibitor GRL-050-22
2 7GEF 1.18 COVID Moonshot — BEN-DND-93268d01-11
3 7K3T 1.20 possible zinc-binding intermediate
4 9HJH 1.20 compound 1 bound to Mpro
5 7GBE 1.224 COVID Moonshot — JAG-UCB-a3ef7265-20
6 7GEH 1.23 COVID Moonshot — EDJ-MED-06d94977-2
7 9HAK 1.25 compound 119 bound to Mpro
8 9RJ5 1.25 SARS-CoV-2 with a bound inhibitor
9 6YB7 1.25 unliganded active site
10 7GBT 1.25 COVID Moonshot — BEN-DND-7e92b6ca-2

You can also get the breakdown by method: X-ray 1,799 / cryo-EM 25 / neutron 4 / solution NMR 3 / electron crystallography 1 (1,832 total, measured 2026-08-21).

💡 These numbers move. Three weeks earlier the measurement totalled 1,822. Ten more since. The databases are alive.

The query that ran

PREFIX pdbo: <http://rdf.wwpdb.org/schema/pdbx-with-vrptx-v50.owl#>
PREFIX dc:   <http://purl.org/dc/elements/1.1/>
SELECT ?entry_id ?res (SAMPLE(?title) AS ?title)
FROM <http://rdfportal.org/dataset/pdb>
WHERE {
  ?entry a pdbo:datablock .
  FILTER(STRSTARTS(STR(?entry), "http://rdf.wwpdb.org/pdb/"))
  BIND(STRAFTER(STR(?entry), "http://rdf.wwpdb.org/pdb/") AS ?entry_id)
  ?entry pdbo:has_entityCategory/pdbo:has_entity ?ent .
  ?ent pdbo:link_to_enzyme <http://purl.uniprot.org/enzyme/3.4.22.69> .
  ?entry pdbo:has_entity_src_genCategory/pdbo:has_entity_src_gen/pdbo:link_to_taxonomy_source
         <http://purl.uniprot.org/taxonomy/2697049> .
  ?entry pdbo:has_exptlCategory/pdbo:has_exptl/pdbo:exptl.method "X-RAY DIFFRACTION" .
  ?entry pdbo:has_refineCategory/pdbo:has_refine/pdbo:refine.ls_d_res_high ?res .
  OPTIONAL { ?entry dc:title ?title }
}
GROUP BY ?entry_id ?res
ORDER BY ?res
LIMIT 10

Three things to look at in this query

1. The target is not narrowed by a keyword search over titles. It is narrowed by the EC-number IRI (enzyme/3.4.22.69) and the taxon IRI (taxonomy/2697049). Narrow by strings in the title and you drop entries that do not spell out "Mpro" while picking up unrelated ones. If you can narrow by IRI, do not narrow by string.

2. The resolution is X-ray only. Cryo-EM resolution lives in a different predicate (em_3d_reconstruction.resolution). This ranking does not mix methods. That is the right thing to do, but it is something you should say out loud.

3. The results move. 9ZNL is a relatively new structure. If a 1.1 Å structure is deposited tomorrow, the ranking changes. So save the query, not the table.


Demo 3': across several databases

Among human lysosomal enzymes, tell me which ones are targets of approved drugs.

This requires connecting UniProt (function and localization annotations) with ChEMBL (drug–target relationships). Measured at 79 s.

Results

Of 52 human lysosomal lumen enzymes, 3 are mechanism-of-action targets of an approved drug.

Gene Approved drug Action What it means
GLA migalastat STABILISER A pharmacological chaperone for Fabry disease. Within these 52, the only case that targets the causative enzyme of a lysosomal disease itself
GAA miglitol, voglibose INHIBITOR The intent is not Pompe disease but type 2 diabetes (inhibition of intestinal α-glucosidase)
PDGFRB imatinib, sunitinib and 11 others INHIBITOR A receptor tyrosine kinase. Not a lysosomal enzyme

💡 An easy confusion. In ChEMBL, the drug carrying an indication for Pompe disease (Glycogen Storage Disease Type II) is miglustat, not miglitol. The names are similar.

The third one, PDGFRB, dominates the result by row count (11 of 14 drugs). But it is merely annotated to the lysosomal lumen in the context of receptor uptake and degradation — it is not a lysosomal enzyme in the usual sense. This is a case where the scope definition fed straight through into the result — taken up in the very next section.

Set PDGFRB aside and here is what can be read off.

Within these 52, the only approved small molecule aimed directly at a lysosomal enzyme was migalastat for GLA. The two GAA drugs are diabetes drugs with a different indication.

That is not surprising. Lysosomal diseases are treated by "replacing" the enzyme, not by "inhibiting" it.

⚠️ Be careful how you say this. It is not a claim that migalastat is the only such drug in the world. It is a measurement: "cross-referencing the 52 human reviewed enzymes carrying GO:0043202 against approved drugs in ChEMBL produced this." Generalize past that scope and you are doing to yourself exactly what this tutorial warns against in Chapter 7.

★ The most interesting landing point — GBA1 returns 0 rows

GBA1 (P04062), the causative enzyme of Gaucher disease, is among the 52 — yet the approved-drug side came back with 0 rows.

Miglustat, a Gaucher disease drug, does not target GBA1; its target is the substrate-synthesizing enzyme UGCG (ceramide glucosyltransferase). And imiglucerase, the enzyme replacement therapy, has — as the next section shows — its target registered on the substrate side.

That "causative enzyme = drug target" does not hold becomes visible at the level of the data structure.

And along the way, another good teaching case appears

Even among drugs for the same Gaucher disease, miglustat and eliglustat look different in the DB.

Drug How it appears in ChEMBL
Miglustat Mechanism-of-action record present → UGCG (Q16739) can be retrieved directly as INHIBITOR
Eliglustat Mechanism-of-action record does not exist. Only the indication "Gaucher Disease, phase 4" can be retrieved

Eliglustat is a UGCG inhibitor too, but you have to cross-check that in the literature.

The lesson: "not in the database" is not "not a fact."

When 0 rows come back, that is not evidence of nonexistence — it is evidence that it cannot be found by this route. Chapter 7 takes this up again.

⚠️ The most important thing here is that the original question was bad

How you define "lysosomal enzyme" makes the answer an entirely different thing.

Take UniProt's keyword KW-0458 "Lysosome" at face value and 161 proteins match. But that is a subcellular-localization keyword, not "lysosomal enzyme." In come INSR (the insulin receptor), PDGFRB, MTOR, PCSK9, LRRK2, and about 20 RAB GTPases.

The approved-drug side then looks like this:

22 insulin preparations and 12 PDGFR kinase inhibitors

Put that under the heading "approved drugs targeting lysosomal enzymes" and in a room of biologists you would be called out within seconds.

Narrowing by EC number to 3.* (hydrolases) does not fix it either. 113 of the 161 survive. RAB GTPases are EC 3.6.5.2 — respectable hydrolases.

The fix that worked was switching to a GO term.

up:classifiedWith obo:GO_0043202     # lysosomal lumen

Candidates narrow from 161 → 52, and the set comes to center on genuine lumenal hydrolases: GBA1, GLA, HEXA/HEXB, GAA, IDUA, IDS, ARSA/ARSB.

Lesson 1 of this chapter: when the answer looks strange, what is wrong is usually not the query's syntax but how you defined the target.

Anticipated questions

Q. Why do enzyme replacement therapies like imiglucerase not show up?

Imiglucerase does exist in ChEMBL (CHEMBL1201632, approved phase 4, mechanism of action registered as well). Follow it and here is what you get.

Item Value
substanceType Enzyme
Target CHEMBL2364176 "Glucocerebroside"
Target type SMALL MOLECULE
Links to UniProt 0
Action type HYDROLYTIC ENZYME

The target is a "substrate," not a protein. Enzyme replacement therapy is not "a drug that inhibits an enzyme" but "a drug that supplies the enzyme itself," so in ChEMBL the target stands on the substrate side. A join going through a UniProt accession can never catch it, no matter what you do. As a database representation, this is correct behavior.

Q. Why is PDGFRB mixed in?

PDGFRB genuinely carries GO:0043202. A localization annotation ≠ a functional classification. This is not an error; your question simply lacked resolution.

Q. It looks like the same drug appears twice?

Because salt forms are registered as separate molecules (migalastat / its hydrochloride, sunitinib / its malate). Row count ≠ drug count. There are 16 rows and 14 drugs, but effectively 12 active ingredients.

⚠️ The query you must not write

You will be tempted to restrict the ChEMBL side to "single proteins only." Do not.

?target a cco:SingleProtein .    # ← add this and

Measured: 16 rows → 8, drugs 14 → 7, with no error. Voglibose is tied to a protein family called "Alpha glucosidase," and PDGFRB's 7 drugs to a complex called "PDGF receptor." One UniProt accession corresponds to several target entities of differing types.

This is the textbook case of "silent failure." No error appears; only the count drops.


★ The failure demo: the center of this chapter

From here on is the most important part of this tutorial.

First, throw a bad question at it

Tell me about genes involved in cancer

Measured at 12–22 s. And the tool calls numbered 1, or 0. What comes back is a fluent answer like this:

The main genes involved in cancer include the tumor suppressors TP53, RB1, PTEN, APC, BRCA1/2, and the oncogenes KRAS, MYC, EGFR, ERBB2 (HER2), PIK3CA, ALK, BRAF. TP53 is mutated in roughly half of all human cancers…

Plausible. Fast. And it consulted no database.

Open the tool log

What you see here depends on the model you are using. In our measurements there were two patterns.

Tool calls
Pattern ① 0 — the databases were never touched at all
Pattern ② exactly 1 — a search was issued, but its results were not used

Pattern ① — the log is empty

Nothing was called. Not even the usage guide (which itself says "call me first, every time").

Twenty-seven genes came back in 20 seconds, sorted neatly into oncogenes and tumor suppressors. Not one of them was looked up just now.

Here is the model's own account of it:

"I had TogoMCP loaded — and its usage guide says, in its own words, 'call me first on every turn' — and I touched none of it. I treated this as recall of textbook knowledge and answered exactly as I would with zero tools connected."

The conclusion is plain, so feel free to skip Pattern ② and move to the next section.


Pattern ② — a single search

The one search that ran was this:

search_uniprot_entity(query="cancer AND organism_id:9606 AND reviewed:true", limit=20)

What came back:

Q9Y238  Deleted in lung and esophageal cancer protein 1
P51587  Breast cancer type 2 susceptibility protein
Q5HYN5  Cancer/testis antigen family 45 member A1
O00559  Receptor-binding cancer antigen expressed on SiSo cells
P35243  Recoverin (Cancer-associated retinopathy protein)
P78358  Cancer/testis antigen 1 (NY-ESO-1)
...

TP53, KRAS, MYC, PTEN, RB1 and APC — the genes lined up in the answer — are not among these. Not one of them.

That is no accident. This search only looks at whether the protein's name contains the string "cancer." TP53's UniProt name is "Cellular tumor antigen p53"; KRAS is "GTPase KRas". Neither contains the string "cancer," so they are structurally unreachable.

There are traces of a database being touched, and the answer comes from memory.

This is the failure mode this tutorial most wants to convey.

★ Either pattern, the same conclusion

Pattern ① is "never touched it"; Pattern ② is "only traces of touching it." Different phrasings — but either way, the answer came from memory.

And here is what matters — from what is on screen, you cannot tell which one it was. The fluency is the same, the speed is the same. You only find out by opening the log.

💡 This is model-dependent behavior. When you try it yourself, something different from what is written here may happen. What matters is not "which pattern you got" but the fact that you opened the log and checked.

Why this is dangerous

On screen there is nothing at all to tip you off. The answer is fast, it is fluent, and as content it is broadly correct (TP53 really is a tumor suppressor).

The problem is not correctness. It is that the provenance is unknown, and the result can be neither verified nor refuted nor reproduced. You cannot put it in a paper.

Being slow and failing is far safer than being fast and confidently wrong.

Three axes were left vague

Axis What went unspecified What happened
Species "cancer" with no species given organism_id:9606 was added on its own. The user never said "human" — a specification fabricated where the user cannot see it
Cancer type "cancer" = all several hundred MeSH descriptors Genes from breast, lung and colorectal cancer mixed together at random
Type of evidence "involved in" undefined Germline susceptibility / somatic driver / expression biomarker / therapeutic target / mere literature co-occurrence — none of them chosen, all of them blended

Next, throw the same intent at it, properly specified

In MeSH, first identify the descriptor for 'Pancreatic Neoplasms', then give me the
human genes associated with that disease as a table — the top 20, ordered by strength of
association — stating the source database and field names. Cross-check the counts with COUNT.

Measured at 182 s, with 8 tool calls. Roughly 15× the cost.

What changed

1. The target became grounded in a verified ID. search_mesh_descriptor ran and MeSH D010190 was settled. Every subsequent query is anchored to that IRI, so string matching disappeared entirely.

2. The provenance became sayable.

Item Value
Endpoint https://rdfportal.org/ncbi/sparql
Main graph http://rdfportal.org/dataset/pubtator_central
Disease side dcterms:subject "Disease" + oa:hasBody <identifiers.org/mesh/D010190>
Join key oa:hasTarget (a shared PubMed article IRI = the definition of co-occurrence)
Species restriction ncbigene:taxid <identifiers.org/taxonomy/9606>
Definition of "strength" COUNT(DISTINCT ?article)

3. Results came out.

Rank Gene NCBI Gene Co-occurring papers
1 TP53 7157 1581
2 AKT1 207 1200
3 EGFR 1956 1151
4 VEGFA 7422 1129
5 KRAS 3845 1076
6 INS 3630 986
7 NFKB1 4790 889
16 GAPDH 2597 604

4. And the COUNT cross-check exposed a methodological flaw.

Total papers annotated with D010190                    = 236,144
Of those, papers where TP53 co-occurs (strict, all)    =  32,597

The value on the sample (1,581) and the strict value (32,597) are off by about 20×. To avoid a timeout, an inner LIMIT 20000 had been applied — and those 20,000 rows were not a uniform random sample.

And it was not only the absolute numbers that were off.

The sample is 20,000 out of 236,144 = 8.47%. Under a uniform sample the full-set value should be 11.81× the sample value. The measured ratios ran 14.97× to 32.33×, all 20 genes exceeded the expected value, and the spread between genes was 2.2×.

As a result, the ranking actually changed.

Gene Rank on the sample Rank on all rows
MTOR 20th 9th ← up 11 places
INS 6th 16th ← down 10 places
IL6 17th 12th
EGF 13th 20th

Had the cross-check not been demanded, a table with the wrong ranking would have gone straight through. Not just the absolute numbers — the ordering itself could not be trusted.

Note that trying to count all 20 genes strictly in a single query timed out at 60 seconds. Only by splitting into batches of 3–6 genes did it run to completion.

Had the cross-check not been demanded, a plausible-looking table would have gone straight through.

That single line, "cross-check the counts with COUNT as well," is the highest-return request in this tutorial.


★ And even with a good question, limits remain

Do not stop here. The specified version's answer has clear flaws too.

(a) Things that are not biological have crept into the top ranks

  • INS (insulin) at 6th — co-occurrence via the organ, the pancreas. Insulin turning up in pancreatic cancer papers is a matter of course, not a causal relationship
  • GAPDH at 16th — it appears in the Methods section of a great many papers simply because it has served for years as a housekeeping gene used as an internal control
  • POTEF at 14th — a primate-specific chimeric gene formed by the fusion of an actin retrogene (UniProt A5A3E0 even names it "Chimeric POTE-actin protein"). Its C-terminal region is highly similar to ACTB, and in mass spectrometry some peptides assigned to ACTB are reported to be shared with POTEF/POTEE/POTEI/POTEJ
  • ⚠️ But whether that is "the reason it lands at 14th in pancreatic cancer papers" has not been confirmed. All we have is a suspicion of identification ambiguity from sequence similarity. Do not promote a guess into an assertion — do not do here the very thing this tutorial warns against in Chapter 7

(b) The genuine drivers are missing

SMAD4 and CDKN2A, the principal drivers of pancreatic cancer, fall outside the top 20.

Why. Because co-occurring paper count measures "how famous the gene is and how many papers have been written about it" — it does not measure disease specificity. TP53 is studied across all cancers, so it turns up in pancreatic cancer papers in bulk.

The question said "ordered by strength of association," but it never specified which association. If you want specificity, you need an additional specification such as "normalize by co-occurrence counts across all cancers."

Lesson 2 of this chapter: a good question makes the answer better. But limits remain even after you make the question good.

Verification does not end with improving the question. On to Chapter 7.


Chapter summary

  1. Some questions cannot be answered by search tools. That is where SPARQL becomes necessary (Demo 4)
  2. When the answer looks strange, suspect the definition of the target, not the syntax. KW-0458 and GO:0043202 gave completely different answers (Demo 3')
  3. A vague question fails plausibly, in 12 seconds, without consulting a database (the failure demo)
  4. Specifying it costs 15× more, but the provenance can be stated and errors can be found by yourself
  5. Limits remain even after specification. Co-occurrence in text mining is not causation

Next → 05. Skills — or feel free to skip ahead to 06. How to Ask a Good Question

05

05. Workflows with Skills

MCP and skills are not the same thing

The MCP we have dealt with so far is "capability" — what can be done. Look something up in UniProt, send a SPARQL query, convert an ID.

A skill is "methodology" — by what procedure, in what order.

Two people can hold the same tools, and the quality of what they get out still depends on whether there is a written procedure. We saw exactly this in Chapter 4. Using the same set of tools, the vague question answered in 12 seconds without touching a database, while the specified question found its own error. A skill is that difference, packaged so it can be reproduced.

MCP  = the toolbox (what can be done)
Skill = the written procedure (how to use it, in what order, what to verify)

Think of a skill as what saves you from writing out by hand, every single time, the things Chapters 6 and 7 of this tutorial ask of you.


The three skills

The TogoMCP repository ships with the following skills.

Where to get them: https://github.com/dbcls/togomcp (under .claude/skills/)

To use them in Claude Code, put the skill's directory in ~/.claude/skills/<name>/ to enable it for every project, or in .claude/skills/<name>/ at the root of a project to enable it for that project. The easiest route is to clone the repository and start claude inside it.

research-article-analysis — verify a paper's claims against databases

What it does: hand it a paper, and it will refuse to take the text at its word, checking the claims against databases one by one.

Molecular formulas, reaction equations, pathways, protein function, GO definitions — for each of them it builds an evidence chain of ChEBI → Rhea → UniProt → Reactome → GO, and returns a verification result per claim.

Why it works: because it hits independent provenance, not the fragments a keyword search returns ("this paper says so"). Whether a paper's statements are correct is not something you can determine by reading that paper.

When to use it: peer review, background work before attempting a replication, checking a paper you are about to cite, fact-checking your own manuscript.

Verify the biological claims in this paper against the databases.
(attach the PDF, or give the DOI / PMID)

disease-analysis — map a disease across scales

What it does: takes a single disease and describes it at each level — molecule → pathway → cell → tissue → clinical → treatment. It combines TogoID, OLS4 and PubMed on top of TogoMCP.

Why it works: information about a disease is scattered across a different database at every level. Molecular defects in UniProt, pathways in Reactome, phenotypes in HP, disease concepts in MONDO/MeSH, treatments in ChEMBL. Joining them by hand is half a day's work.

When to use it: getting a grip on an unfamiliar disease quickly, early scoping for a research plan, understanding the field a collaborator works in.

Analyze the pathophysiology of Fabry disease across scales, from the molecular level to the clinical symptoms.

PRISM — take the intersection of several conditions

What it does: finds entities that are "both A and B". The name is the initials of Predicate-defined, Reproducible, Identifier-bridged, Set-intersection Mining.

  • "targets associated with disease X that are also druggable"
  • "genes involved in pathway P that are also modulated by an existing drug"
  • "compounds associated with phenotype Q that are also substrates of enzyme A"

Why it works, and the most important point: PRISM forces each condition (each axis) to be defined as a reproducible predicate. It expands along the ontology hierarchy, triangulates across multiple sources of evidence, takes the intersection on stable IDs, and leaves behind a provenance ledger.

This is the machinery for doing the four verification steps of Chapter 7 automatically, with nothing missed.

The skill's own description contains a line like this — "If you catch yourself about to list candidate genes from memory, stop and use PRISM."

That is the failure demo of Chapter 4, precisely.

When to use it: drug repositioning, drug-target identification, any question of the form "what do these two sets have in common?"

Use PRISM to find genes associated with [DISEASE] that are also modulated by an existing approved drug.

Applications with a track record include lipid transport in age-related macular degeneration, and the analysis of Pompe disease.


To try them

The skills are in the repository. If you are trying one first, we recommend research-article-analysis — the input is a single paper, which is easy to follow, and the output is a "verification result per claim" table, which makes it easy to read what happened.

Hand it a paper from your own field, and see how far the claims can be backed up by databases. You will see the verification discipline of Chapter 7 automated as it stands.


Deciding whether to use a skill

Situation Recommendation
A one-off, simple query No skill needed. The templates in Chapter 6 are enough
The same kind of work, repeated Use a skill. The variation in procedure disappears
Results going into a paper Use a skill. The provenance is kept automatically
You want to share or review the procedure itself Use a skill. The procedure exists as a file
Exploring, feeling your way A skill can get in the way. Ask plainly

The essential value of a skill is that the same procedure is followed even when you are tired, even when you are in a hurry. As the failure demo in Chapter 4 showed, skipping the procedure gets you an answer in 12 seconds. And nothing on the screen tells you that anything was skipped.


Writing your own skill

A skill is a Markdown file. In SKILL.md you write when to use it (description), and by what procedure the work is to be done.

If your lab has procedures of its own — "in our analyses we always hit these three DBs and tabulate them in this format" — you can make that a skill. The existing skills in the TogoMCP repository serve as worked examples.


Next → 06. How to Ask a Good Question

06

06. How to Ask a Good Question

For researchers outside informatics, this is the chapter most worth taking away.

The five elements below are not general advice. They are the causes we actually observed when we ran two versions of the same question against live databases in Chapter 4.


The contrast, at a glance

Vague question Specified question
Wording "Tell me about genes involved in cancer" "First identify the MeSH descriptor for 'Pancreatic Neoplasms', then…"
Time 12 s 182 s (~15× longer)
Tool calls 1 8
SPARQL executions 0 4
MIE consulted no yes
Identifiers verified none MeSH D010190, resolved live
COUNT cross-check no yes — and it found a flaw
Provenance stated no database, graph, and predicates, all of it
Reproducible no yes, from the full query text

Twelve seconds is not fast. It is fast because nothing happened.


The five elements

1. Name the target with a controlled vocabulary and a way to resolve it

"In MeSH, first identify the descriptor for 'Pancreatic Neoplasms'"

What the vague version did: the word "cancer" went straight into a full-text search, where it silently became a completely different question — "does the protein's name contain the string 'cancer'?" What came back was NY-ESO-1 and recoverin.

What changed: search_mesh_descriptor ran and produced D010190, a verified IRI. Every subsequent query was anchored to that single IRI, so the match became a strict structured lookup (oa:hasBody <mesh/D010190>) and the string matching disappeared.

Naming a vocabulary is enough to switch the tool selection from "full-text search" to "resolve an ID, then match on structure."

Vocabularies worth naming: MeSH (diseases, medical concepts), GO (function, localization, process), MONDO / NANDO (diseases), HP (phenotypes), UBERON (anatomy), ChEBI (compounds), NCBI Taxonomy (organisms).

2. Demand the provenance — database and field names

"state the source database and field names"

The vague version: nothing asked for sources, so there was no reason to read the MIE. No SPARQL meant no chance to pin a graph either. The structure of the request let an answer from memory pass as complete.

The specified version: the moment provenance became an obligation, get_MIE_file(pubtator) became a mandatory prerequisite. That MIE then disclosed three traps in advance — the predicate values are case-fixed, the target graph is a single named graph, and, critically, the gene annotations do not distinguish species.

"Cite your sources" is not a formatting request. It functions as a trigger that forces the schema to be read.

3. State the species and the scope

"human genes"

The vague version: nobody said anything about species, yet organism_id:9606 was silently added. That is a specification invented on your behalf, where you cannot see it. Had the subject actually been mouse, a wrong answer would have come back and nobody would have noticed.

The specified version: the MIE's warning — this literature-annotation data does not separate human genes from model-organism orthologs — triggered a concrete countermeasure, a join that filters on taxon. Without it, identically named mouse and rat genes would have crept in and inflated the numbers with no error and no empty result.

Naming the species is not a filter. It is a switch that causes work to happen which prevents silent contamination.

4. Fix the count and the ordering criterion as numbers

"the top 20, ordered by strength of association, as a table"

The vague version: with no count specified, "about ten" was chosen unilaterally, and the ordering became "by fame" — a criterion that cannot be defined, and therefore cannot be verified or refuted.

The specified version: "top N, ordered" was translated into an executable definition: GROUP BY … ORDER BY DESC(COUNT(DISTINCT ?article)) LIMIT 20. At the same time, an operational definition — strength of association = number of co-occurring papers — was made explicit. That is what made the criticism in Chapter 4 sayable at all: co-occurrence is not causation. In the vague version there was no definition to criticize.

Demanding an ordering criterion forces "strength" to be reduced to a measurable quantity. Whether that reduction is valid can only be argued once the reduction is on the table.

5. Demand an independent cross-check (COUNT)

"and cross-check the counts with COUNT"

Of the five, this one did the most work.

Without it, a table reading "TP53 = 1,581" would have gone out unchallenged. The COUNT produced two numbers — 236,144 papers annotated with the disease, and 32,597 in which TP53 genuinely co-occurs — and the fact that the ratio did not add up exposed a methodological flaw: the 20,000 rows taken by the inner LIMIT were not a uniform sample.

And when the counts were redone exactly, the ranking itself changed — MTOR moved from 20th to 9th, INS from 6th to 16th. Not just the absolute numbers: the ordering could not be trusted either.

"Cross-check with COUNT" is not proofreading. It is a detector that exposes sampling bias and counting traps. No other single line you can add returns as much.


Templates you can use as-is

General template

First identify [TARGET] using [VOCABULARY] ([ID or term]),
then list the [UNIT OF OUTPUT] that satisfy [RELATION],
top [N], ordered by [CRITERION], as a table,
stating the source database and field names for each.
Cross-check the counts with COUNT as well.

By purpose

Look up a protein

Identify human [PROTEIN] in UniProt (give me the accession),
then summarize its function, sequence length, subcellular localization,
and associated diseases — indicating which UniProt field each came from.

Enumerate proteins with a given function

List human proteins annotated with GO [TERM] (GO:XXXXXXX),
restricted to reviewed entries.
Give both COUNT(DISTINCT) and COUNT(*), and if they differ,
explain what is being duplicated.

Find molecules associated with a disease

First identify the MeSH (or MONDO) ID for [DISEASE],
then list the top N associated human genes,
stating explicitly how you are defining "strength of association" —
and what that definition does and does not measure.

Search structures with conditions

Find PDB structures of [TARGET], restricted to [EXPERIMENTAL METHOD],
top N by best resolution.
Also give the breakdown of entry counts per experimental method.

Cross identifiers

Convert these IDs to [TARGET NAMESPACE].
If any could not be converted, say so explicitly.

That last line matters. A count that shrank silently is the most dangerous kind.


And the ways of asking that you should avoid

Why it fails
"Tell me about X" "About" is undefined, so it can be answered from memory "Give me [SPECIFIC ATTRIBUTE] of X from [DATABASE]"
"What are the important genes?" "Important" is not measurable "Top N by [METRIC], descending"
"Everything that's related" "Everything" explodes at runtime; most of these time out "Top N. Give the total separately with COUNT"
"What's the latest?" A database does not know what "latest" means "Entries deposited or updated since [YEAR]"
"Is this right?" An AI tends to agree with you "Find evidence in the databases that contradicts this claim"

The last row is especially effective. Do not ask "is this right?" — ask for the counter-evidence.


About the cost

Satisfying all five elements takes roughly 15× longer. You do not need to do it every time.

Situation What to use
Exploring, getting a rough sense Element 1 (identify the target) is enough
For discussion, or a slide Elements 1, 3, 4
For a paper or a formal report All five. No exceptions.

For any number that goes into a paper, satisfy all five. If you put a number that came back in twelve seconds into a manuscript, you will not be able to answer for it at review.


Next → 07. Verification and Reproducibility

07

07. Believing the Result / Doubting It — Verification and Reproducibility

As we saw in Chapter 6, a good question makes the answer better. But there are limits that remain even after you have made the question a good one. This chapter deals with those.

One thing first, as a premise.

You cannot put the output of an AI into a paper as it stands.

Not because AI cannot be trusted, but because without a record of what you verified, you cannot answer at review. It is the same as not reporting an experimental result without having kept a lab notebook.


7-1. Five failure modes we actually observed

Every one of them occurred in the measured runs of Chapter 4. None of them raises an error.

(a) Inflated counts

COUNT(*)                 = 62
COUNT(DISTINCT ?protein) = 52      ← a 19% gap

This happened because a single protein can carry several EC numbers (GBA1 has 4, ASAH1 has 3). Counting rows naively, we would have reported "62 lysosomal lumen enzymes."

(b) Sampling bias

Having limited the inner query to 20,000 rows to avoid a timeout:

TP53 co-occurrences in the sample =  1,581
TP53 co-occurrences over all rows = 32,597      ← about 20×

Writing "TP53 appears in 1,581 pancreatic cancer papers" would have been wrong.

Worse still, the ranking itself changed — MTOR went from 20th in the sample to 9th over all rows, INS from 6th to 16th. There was not even the escape route of "the absolute numbers may be off, but the ordering can be trusted."

(c) Mistaking the definition

UniProt's keyword KW-0458 "Lysosome" is a subcellular localization, not "lysosomal enzyme." Using it, the approved-drug side of the result was dominated by 22 insulin preparations and 12 PDGFR inhibitors.

The syntax was perfectly correct, execution succeeded, and the answer was meaningless.

(d) Misreading the unit of aggregation

When we looked into variants in TogoVar:

Breakdown Total Matches the variant count of 182?
type (SNV 157 / deletion 23 / insertion 2) 182 ✅ matches
significance (Pathogenic 277 / Likely pathogenic 134 / others) 457 2.5×
consequence 2,613 about 14×

457 is not a number of variants. It is a number of "variant × condition" pairs — because one variant can be tied to several diseases. consequence is "variant × transcript" on top of that, so it swells to 14×.

And something counterintuitive happens. We filtered on "pathogenic," yet the breakdown contains 35 Uncertain significance and 1 Likely benign. This is not a contradiction — the filter is per variant, the breakdown is per variant-condition record. In fact, a single variant (rs421016) alone accounts for 13 records.

The tool itself returns a warning in statistics_caveats. Read it.

"significance": "... counted PER VARIANT-CONDITION classification record ... Do NOT compare the sum to \filtered`."`

(e) Misunderstanding what the metric measures

INS, GAPDH and POTEF came out at the top as genes associated with pancreatic cancer. None of them is a driver of pancreatic cancer.

  • INS — organ-level co-occurrence, because the pancreas is an endocrine organ
  • GAPDH — a housekeeping gene, merely mentioned as an internal control in experiments
  • POTEF — a chimeric gene formed from a fused actin retrogene, highly similar in sequence to ACTB

⚠️ Here we confess a failure of this tutorial itself. The first draft described POTEF as "a known false-positive source for gene normalization." It was plausible, and it very nearly went through as it stood. When we checked, PubMed had a total of only 17 papers on POTEF, and nowhere in them was there any support for that claim.

All we could confirm was a different fact — that it shares peptides with ACTB — and whether that is the cause of its ranking in PubTator is not known.

This is the subject of this chapter, exactly. Without verification, your own conjecture gets promoted to fact inside your own prose.

Conversely, the true drivers — SMAD4 and CDKN2A — were nowhere in the top 20. The number of co-occurring papers measures "fame × publication volume," and it does not measure disease specificity.


7-2. The four verification steps

For any number that goes into a paper or a report, do these without exception.

Step 1: Make it output the query that was executed, and save it

Give me the full text of the SPARQL you just executed, exactly as it ran. And the endpoint you used.

Do not let it summarize. You need the full text. This is what corresponds to a lab notebook.

Step 2: Cross-check the counts with COUNT

Give me the count of that result with both COUNT(DISTINCT ...) and COUNT(*).
If they differ, explain what is being duplicated.

A difference is not an anomaly. Reporting it without being able to explain it is the anomaly.

If sampling was used (an inner LIMIT), then also:

If you ran the same aggregation over all rows with no LIMIT, what would this number become?
If that is too heavy, recount just the top few rows exactly and compare.

This is how you detect the sampling bias of (b).

Step 3: Check one or two entries by eye in the source database

Do not skip this. It takes 30 seconds.

DB Where to check
UniProt https://www.uniprot.org/uniprotkb/[accession]
PDB https://www.rcsb.org/structure/[PDB ID]
ChEMBL https://www.ebi.ac.uk/chembl/
NCBI Gene https://www.ncbi.nlm.nih.gov/gene/[ID]
TogoVar https://togovar.org/
MeSH https://meshb.nlm.nih.gov/

Look at the top entry, and at one from the bottom — or one that surprises you. The surprising one carries more information — that PDGFRB was in a list of lysosomal enzymes is something you can notice by eye.

Step 4: Record the date of execution and the endpoint

Databases get updated. A result with no "when" cannot be reproduced.


7-3. What to save for reproducibility

For each query, keep the following.

├─ query.rq          the full text of the SPARQL that was executed
├─ endpoint.txt      the endpoint URL and the graph name
├─ result.csv        the result itself
├─ counts.txt        the result of the COUNT cross-check
└─ meta.txt          date and time of execution, the original question, ★the model name,
                     ★the client, the tools used and their versions

Do not forget to put "the original question" into meta.txt. Six months later it is the only clue you have as to why you narrowed things the way you did.

★ Record the model name — a requirement specific to querying through an LLM

With ordinary SPARQL, the query, the endpoint and the date are enough to reproduce a result. When an LLM is in the loop, that is not enough.

The same question behaves differently on a different model. Even in this tutorial's measured runs, we saw differences like these.

One model Another model
Tool calls for "tell me about genes involved in cancer" 1 0
Read the usage guide yes no
Time for the same demo 8–18 s 21–30 s

Even when the content of the answer is the same, the process that got there is not. To verify after the fact why the answer came out the way it did, you need a record of the model.

So meta.txt needs at minimum these:

  • The model name (e.g. claude-opus-5 / claude-sonnet-5)
  • The client (Claude desktop / Web / Claude Code / API)
  • The MCP server (the hosted one, or a local install. Note the version too if you know it)

⚠️ Careful: "the model you configured" and "the model that actually did the processing" are not necessarily the same. Fallbacks and switching can happen. Strictly, the accurate thing to write is "configured as …", and this tutorial's own measurement records are written that way.

You can also have Claude produce the whole set for you.

For the query just run, give me
(1) the full text of the SPARQL executed (2) the endpoint and graph name
(3) the results as CSV (4) the COUNT cross-check (5) the date and time and the original question,
in a form I can save to files as-is.

💡 The PRISM skill in Chapter 5 has machinery for structuring this record and keeping it automatically (a provenance ledger). Consider it once doing this by hand becomes tiresome.


7-4. How to write it up in a paper

Cite at two levels

Cite both TogoMCP and each individual database you actually used. TogoMCP is the entrance; it is not where the data came from.

Kinjo, A. R., Yamamoto, Y., Bustamante-Larriet, S., Labra-Gayo, J.-E., & Fujisawa, T. (2026). TogoMCP: Natural Language Querying of Life-Science Knowledge Graphs via Schema-Guided LLMs and the Model Context Protocol. Database 2026:baag042. https://doi.org/10.1093/database/baag042

Plus a citation for each database you actually consulted — UniProt, PDB, ChEMBL, and so on.

What belongs in the methods section

  • The databases used and their version/release, and the date of access
  • The full text of the queries executed (in supplementary material)
  • The identifiers used to narrow the target (GO:0043202, MeSH D010190 and so on. By ID, not by name)
  • If sampling or a LIMIT was used, that fact itself, and its consequences

Not many people can write that last item. Being able to write it is a strength.

What must not be written

  • "We asked an AI" — that is not a method
  • Numbers you have not verified
  • Claims whose source you cannot state

7-5. What this is good for / what it is not good for

Honestly.

Good for

  • Exploration and hypothesis generation — "which proteins with this function have no drug yet?"
  • Cross-database checking — "what is this ID called in the other DBs?"
  • Organizing the known — pulling scattered information into a single table
  • Drafting SPARQL — as a starting point you intend to rewrite yourself (this is extremely effective)
  • Finding what you missed — picking up connections that lie outside your own field

Not good for

  • Analyses where completeness is a requirement — "all of them" cannot be guaranteed. Not suitable for building the population for a phylogenetic analysis or a meta-analysis
  • Statistical inference — it will return counts, but designing the test and the effect size is your job
  • Clinical decisionsnever use it for this. TogoVar's pathogenicity classifications are the contents of ClinVar submissions; they are neither a diagnosis nor advice
  • Producing primary data — it only queries existing databases. It makes no new measurements
  • Substituting for expert judgment — the one who judged whether PDGFRB is a lysosomal enzyme, in this tutorial, was you

7-6. Other cautions

Licensing. KEGG is restricted to those affiliated with academic institutions, and offering it in a public service requires a separate licence (appendix). Every database also has terms of use. Check them before any commercial use.

Personal information. What TogoVar returns is aggregate values (allele frequencies, submission counts), not individual-level data. Controlled-access datasets appear only as cohort counts. That said, ClinVar's condition descriptions can contain case-derived phenotypes. It is public information, but do not speak of it as "the patient we found."

Handling disease information. A single variant can be tied to several diseases (one variant carries both Gaucher disease and Parkinson's disease). This is something to state as a ClinVar classification, not as advice about risk. Be especially careful when showing it in a presentation.


Summary of this chapter

  1. Failure shows up not as an error, but as a number that looks correct
  2. The four verification steps — keep the query / cross-check with COUNT / check by eye in the source DB / record the date
  3. What you must save is the query, not the table of results
  4. Cite at two levels: TogoMCP + the individual databases
  5. There are things it is not good for. Guaranteeing completeness, and clinical decisions, above all

Next → 08. Troubleshooting

08

08. Troubleshooting

It will not connect

Symptom What to check
It says nothing at all about TogoMCP and answers in generalities Whether the connector is enabled in that conversation. In Claude you have to select it from the "+" in every conversation. This is the most common cause
On Team/Enterprise, "Add custom connector" cannot be clicked The organization's owner has to add it organization-wide first (→ Chapter 01). An individual cannot add it
claude mcp list shows ✘ Failed to connect Check the URL. Did you drop the trailing /mcp?
It worked yesterday but not today (Claude Code) Scope. The default --scope local only takes effect in that directory. Re-add it with --scope user
claude mcp add comes back with "no such command" Are you typing it inside a Claude session? This is a command for the terminal
A tool call fails Network. Possibly a corporate proxy or a VPN
ChatGPT says "there is no such tool" Re-run Scan Tools, or delete the connector and add it again. ChatGPT does not re-fetch the tool list on its own

The query fails, or is slow

It times out at 60 seconds

SPARQL has an upper limit on execution time. Do not re-send the same query. You will get the same result.

Remedies, in descending order of effect:

1. Narrow the target by IRI. FILTER(CONTAINS(...)) and FILTER(regex(...)) are effectively unusable on large graphs. Consider whether you can specify the IRI directly.

Measured: FILTER(CONTAINS(STR(?seq), "/P01308-1"))  → timeout at 60 s
          rewritten to specify the IRI directly     → about 5 s

2. Cut down the OPTIONALs. Each one added makes it heavier. Get it working with the mandatory part first, and add the rest afterwards.

3. Apply a LIMIT. But ⚠️ an inner LIMIT is not a uniform sample. Read Chapter 7 — this is not optional. If you are going to report counts, you need a separate COUNT.

4. Split it into stages. Getting the IDs and then sending the next query is faster than one enormous query, and you can see what is happening on the way.

You can ask Claude for this:

That query is too heavy. Can you rewrite it to narrow by IRI?
Also propose a version with fewer OPTIONALs, or split into two stages.

It returns zero rows

Zero rows with no error is the most dangerous pattern of all. SPARQL does not raise an error when a predicate name or a graph name is wrong.

The order in which to check:

  1. Did you read the MIE? The predicate names in a query written without reading it are guesses Check the MIE file for [DATABASE] and confirm that the predicate names used actually exist
  2. Is the graph name right? Graph names do get renamed. Specifying an old one gives you a silent zero rows
  3. Remove the filters one at a time. Identify which condition drives it to zero Drop the conditions one at a time and find out where the results disappear
  4. Doubt the ID itself. The source ID may not exist, or may have been renamed

The counts look wrong

Go to Chapter 3 and Chapter 7. The essentials only:

Give me both COUNT(DISTINCT ...) and COUNT(*). If they differ, explain what is being duplicated.

There are two main reasons for inflation — the graph is not pinned, and the predicate is multi-valued (one entity carries the same predicate several times).


The answer is wrong, or strange

What to doubt first is not the syntax — it is the definition of the target

The real case from Chapter 4: define "lysosomal enzyme" by UniProt's keyword KW-0458, and the insulin receptor and mTOR come in, and the approved-drug results end up dominated by insulin preparations. The syntax was perfectly correct, and execution succeeded.

I suspect this result contains things that should not be in it.
Explain how you defined the target, and what that definition does and does not include.

When you suspect no database was consulted

Symptoms: the answer is too fast (in the ten-second range), the tool log shows no run_sparql or search tool, there are no accessions or IDs attached.

List the tool calls that this answer was based on.
Separate the values you retrieved from a database from the ones you did not.

That flushes out "it was actually answering from memory." → the failure demo in Chapter 4

The AI agrees with you

Ask "is this right?" and it tends to agree. Demand the counter-evidence.

Find evidence in the databases that contradicts this claim.

The endpoint is down

This actually happens. In August 2026 there was an occasion when every SPARQL endpoint at rdfportal.org was unreachable.

How to tell: it is not one particular query — run_sparql fails against every database. Meanwhile get_MIE_file and the usage guide respond normally (these are files inside the server, so they do not depend on the external endpoints).

What to do:

  1. Wait. There is nothing to do but wait for recovery
  2. Switch to the REST-based tools. togovar_*, ncbi_*, search_chembl_* and others are a separate route, so they can still work while SPARQL is down
The SPARQL endpoint is not responding. Can you answer the same question using only the REST-based tools?

If that still does not solve it

  • Issues on the repository: https://github.com/dbcls/togomcp
  • Status of the hosted version: https://togomcp.rdfportal.org/

When you report, attach the full text of the query that was executed, the endpoint, the date and time of execution, and the error message (→ the save format in Chapter 7 works as-is).

App.

Appendix: Local Installation (Route C)

For developers, and the only route if you want to use the KEGG tools.

It is not needed for ordinary use. Route A (the custom connector) is fully functional. You need what follows only if:

  • you want to use the KEGG tools (restricted to those affiliated with an academic institution)
  • you are writing or fixing MIE files
  • you are developing the server itself
  • you are hosting it yourself inside your organization

Prerequisites

  • Python >= 3.11
  • The uv package manager

Installing uv

# macOS / Linux
curl -LsSf https://astral.sh/uv/install.sh | sh

# Windows (PowerShell)
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

Installation

git clone https://github.com/dbcls/togomcp.git
cd togomcp
uv sync

NCBI API key (mandatory if you use the NCBI tools)

Get a key from the NCBI documentation, then:

export NCBI_API_KEY="your-key-here"

Configuring Claude Desktop

Where the configuration file lives:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: ~\AppData\Roaming\Claude\claude_desktop_config.json
{
    "mcpServers": {
        "togomcp": {
            "command": "/path/to/uv",
            "args": [
                "--directory",
                "/path/to/togomcp",
                "run",
                "togo-mcp-local"
            ],
            "env": {
                "NCBI_API_KEY": "your-key-here"
            }
        }
    }
}

💡 You can find the absolute path to uv with which uv (macOS/Linux) or where uv (Windows). A relative path, or just uv, will not work.

After configuring, quit Claude Desktop completely and restart it.


KEGG (opt-in, local stdio only)

KEGG is off by default. You do not need it. TogoMCP is fully functional without KEGG. This section is relevant only if you are eligible and you want to use it.

The eight tools kegg_find / kegg_get_entry / kegg_conv / kegg_link / kegg_pathway_graph / kegg_pathway_neighborhood / kegg_pathway_paths / kegg_pathway_cycles become available only when both of the following hold.

  1. You are running it through the local stdio entry point togo-mcp-local
  2. You have set TOGOMCP_ENABLE_KEGG=1

Why there are two gates — the reasons are separate

(1) The restriction on the transport route is structural, and configuration cannot change it.

The KEGG API is provided "for academic use by academic users belonging to academic institutions," and providing a service that uses KEGG requires a separate academic service-provider licence (KEGG's terms of use).

A public server cannot verify the affiliation of the caller. Therefore no HTTP deployment reaches rest.kegg.jp, including togomcp.rdfportal.org. No environment variable changes thisTOGOMCP_ENABLE_KEGG has no effect whatsoever on the HTTP path.

(2) The opt-in exists because the claim of eligibility is yours.

Under stdio, you yourself are the caller. But whether your institution's access rights cover you is something only you can know.

Enabling KEGG by default would mean placing an API call you may not be entitled to make on the path of least resistance. An AI assistant uses the tools it can see. For a user whose use is not academic, leaving the variable unset is the correct configuration, and it has no other consequences.

Enabling it

"env": {
    "NCBI_API_KEY": "your-key-here",
    "TOGOMCP_ENABLE_KEGG": "1"
}

Constraints

  • Calls are limited to 3 requests per second (enforced across the whole process. There is no retry on 403/429)
  • KEGG is not part of RDF Portal. It has no SPARQL endpoint, so database="kegg" in run_sparql is invalid
  • To connect it to the RDF databases, use kegg_conv to convert KEGG identifiers into UniProt / NCBI Gene / NCBI Protein / ChEBI / PubChem first

Docker

cp .env.example .env                                # fill in NCBI_API_KEY
docker build -t localhost/togo-mcp:latest .
docker compose up -d togomcp-main                   # port 8000

compose.yaml defines two services, togomcp-main (8000) and togomcp-test (8001), so you can run production and testing side by side from the same image.

docker compose logs -f togomcp-main    # logs
docker compose down                    # stop and remove

Putting it behind a reverse proxy

Two environment variables come into play. Both break in ways that are easy to misdiagnose.

TOGOMCP_ALLOWED_HOSTS — the Host header is validated (a defence against DNS rebinding), and any host not on the allow list gets a 421. The default is localhost and the DBCLS public vhost only. Unless you add your own hostname, every request through the proxy is rejected.

TOGOMCP_FORWARDED_ALLOW_IPS — which peer addresses are allowed to set X-Forwarded-Proto / -For. uvicorn trusts only 127.0.0.1 by default, and a container reached through a published port does not arrive as loopback. Get this wrong and the headers are not rejected — they are silently discarded. The app then believes it is serving plain HTTP, and emits redirects that downgrade https:// to http://.

The proxy side has to send X-Forwarded-Proto as well. nginx does not send it by default:

proxy_set_header X-Forwarded-Proto $scheme;

Caddy and Traefik do send it. It only works when both are in place. One alone will not do.


Tool-call logging (optional)

TogoMCP can log every tool call as one JSON object per line (timestamp, tool name, arguments, status, elapsed milliseconds, session/request/client ID, transport, client IP). SPARQL calls additionally carry the endpoint URL, the HTTP code, the row and byte counts, and the SHA-256 of the query.

It is useful for benchmarking, for improving the MIEs, and for reconstructing procedures that span several tools. You can also use it to automate the reproducibility records of Chapter 7.

On and off is a single environment variable, TOGOMCP_QUERY_LOG. Unset = disabled (zero overhead). Set it to a writable file path and it is enabled.

For Claude Desktop (local stdio), add it to the env block. Use an absolute path (the working directory of the launched process is unpredictable) and create the parent directory first:

"env": {
    "NCBI_API_KEY": "your-key-here",
    "TOGOMCP_QUERY_LOG": "/Users/you/togomcp-logs/togomcp.jsonl"
}
mkdir -p ~/togomcp-logs

Then restart Claude Desktop completely.

⚠️ Privacy: IPs are recorded as a salted hash (ip_hash) by default. Setting TOGOMCP_LOG_RAW_IP=1 records them in the clear as well, which makes it possible to identify and block abusers — but it makes the log personal data. For the details of each field, see log_file_specs.md in the repository.


Adding a database (for developers)

There are five places. Not two. Only the first two affect the server's validation; the rest are documentation surfaces that drift out of sync silently. The tests catch that.

  1. togo_mcp/data/resources/endpoints.csv — the registration row (this alone determines the valid database= values)
  2. togo_mcp/data/mie/<db>.yaml — the MIE file (the specification is in togo_mcp/data/docs/)
  3. uv run python scripts/generate_usage_guide_catalog.py — regenerate the database catalogue in the usage guide
  4. togo_mcp/data/resources/usage_guide_v6/02_budgets_and_discovery.mda hand-written copy the generator does not touch. Update both the counts and the keys
  5. togo_mcp/data/docs/togomcp-intro.html — the landing-page card (not generated)

← Back to the table of contents

Ex.

Exercises for Self-Assessment

Worked solutions are in solutions-en.md. Do them yourself first, then look.

Difficulty: ★ basic / ★★ applied / ★★★ advanced

⚠️ The numbers you get change from day to day. If your figures differ from the worked solutions, that alone is not an error. Check the reasoning, and the steps that led there.


Exercise 1 ★ — Check the connection and ask your first question

(a) Confirm that TogoMCP is connected.

(b) Pick one human protein you care about and look up its UniProt entry. Get the accession, the sequence length, and the function.

(c) Check the accession you got back on the UniProt website. Was it right?

(d) Was the sequence length what you expected? If not, find out why.

Hint: precursor? isoform? does it include the signal peptide?


Exercise 2 ★ — Travel across databases carrying an ID

Use the accession from Exercise 1.

(a) Convert that UniProt ID into an Ensembl gene ID, an HGNC ID, and PDB IDs.

(b) How many PDB structures were there? If the answer was 0, does that mean "there is no structure"? Check.

(c) If anything failed to convert, make it say so explicitly.

💡 A count that shrank silently is the most dangerous kind. Get into the habit of making conversion failures be reported.


Exercise 3 ★★ — TogoVar: variants in the Japanese population

How many pathogenic variants are there in GBA1?
Which of them are frequent in the Japanese population?

(a) Run it.

(b) Search with both "GBA1" and "GBA" and compare the match_type. What is different?

⚠️ Both put GBA1 on the first row. There is still a difference. Make sure you look at the match_type that came back.

(b2) Get the HGVS notation (NM_... / p....) out. It does not come back on the first call. What do you have to add?

(c) Does the number of pathogenic + likely pathogenic variants match the total number of significance classification records? If not, what is each of them counting?

(d) How much did the frequencies differ between the Japanese cohorts and the international cohorts?

⚠️ These results include disease names as registered by ClinVar. Treat them as ClinVar classifications. They are not medical advice.


Exercise 4 ★★ — Experience how changing the definition changes the answer

Take Demo 3' from Chapter 4 and break it yourself.

(a) Look up "human lysosomal enzymes that are targets of approved drugs" using the UniProt keyword KW-0458.

(b) Ask the same question using GO GO:0043202 (lysosomal lumen).

(c) Compare the two answers. Why are they this different?

(d) Name one term in your own field where the same trap could occur.

This is the most important exercise here. When an answer looks strange, what to suspect is not the syntax — it is the definition of the target.


Exercise 5 ★★ — Run through the four verification steps

Take the results from Exercise 3 or 4 and actually walk through the verification procedure in Chapter 7.

(a) Get the executed SPARQL printed in full and save it (do not let it be summarized)

(b) Get both COUNT(DISTINCT ...) and COUNT(*). If they differ, make it explain what is being duplicated

(c) On the original database's website, eyeball the top hit and one "surprising" hit

(d) Record the date of execution, the endpoint, and the original text of your question

(e) Save all of this together in one folder

Is it in a form that you, six months from now, could reproduce?


Exercise 6 ★★★ — Write a bad question and a good question yourself

(a) For your own research topic, write a deliberately vague question and send it.

(b) Open the tool log and check: - Were run_sparql or the search tools called? - Did the specific names in the answer (gene names, compound names, etc.) actually exist in the tool output? - Were accessions or IDs attached?

(c) Using the five elements from Chapter 6, specify the same intent and ask again.

(d) What changed? The time taken, the number and kind of tool calls, the content of the answer.

(e) Does the specified version still have limitations left in it?

(e) is the real point. A good question makes the answer better; it does not make the limitations disappear.


Exercise 7 ★★★ — Make it look for counter-evidence

Pick one claim in your field that you believe is correct.

(a) Ask "is this claim correct?"

(b) Then ask "find evidence in the databases that contradicts this claim."

(c) Did the answer change? Which was more useful?

An AI tends to agree with you. In practice, "find the counter-evidence" works, and "is this right?" does not.


Finally — with your own topic

Ask one thing you actually want to know, from your own research topic.

It is fine if it does not work. When it does not work is exactly when you should isolate what happened.

Worth recording:

  • What you asked (the original wording, verbatim)
  • Which tools were called
  • What came back
  • Where it differed from what you expected. Was that the AI's problem, the database's problem, or the question's problem?

That last line is what you should take away from this tutorial.

Ans.

Worked Solutions and Where People Get Stuck

⚠️ Every number here is measured (re-measured 2026-08-20 / 08-21). Databases get updated, so it is normal not to get the same numbers. Check the steps and the reasoning instead.


Exercise 1 — Check the connection and ask your first question

(a)(b) Omitted (depends on your protein).

(c) Where people get stuck: no accession comes back, or several do.

Protein names are not unique. "Amylase" covers AMY1A/AMY1B/AMY1C/AMY2A/AMY2B. When it is ambiguous, the AI picks one on its own. Specify it again by gene symbol.

(d) The sequence length is not what you expected — nearly always one of these three.

Cause Example
You are looking at the precursor Insulin: expected 51 aa → actually 110 aa (preproinsulin)
There are multiple isoforms The default is the canonical one (-1). Other isoforms have different lengths
The signal peptide or propeptide is included Common with secreted proteins

How to check:

Is that sequence length for the precursor or for the mature protein?
If the signal peptide or propeptide regions are annotated in UniProt, show me those too.

The lesson: when a number differs from what you expected, the first thing to suspect is a mismatch between your expectation and the database's definition. Not an error by the AI.


Exercise 2 — Travel across databases carrying an ID

(a) For insulin, P01308: Ensembl ENSG00000254647 / HGNC 6081 (formally HGNC:6081).

💡 HGNC sometimes comes back as a bare number. Fix the notation.

(b) Interpreting 0 PDB hits — this is the real subject.

Zero hits does not mean "there is no structure." The possibilities are:

  1. No structure has genuinely been solved
  2. A structure exists, but that correspondence is not registered along the ID-conversion route
  3. It was solved as part of a complex, and there is no entry for that protein on its own

How to check — come at it by another route:

Find PDB structures for this protein by searching PDB directly,
not through ID conversion.
If the counts differ, explain why.

Searching the RCSB PDB website directly also takes 30 seconds.

The lesson: when a single route returns 0 hits, that is not evidence that the thing does not exist. It is evidence that it cannot be found by that route.

(c) Making the failed conversions explicit:

Convert these IDs to XXX. Also state explicitly which ones could not be converted.

A count that shrank silently is the most dangerous kind. You send 20 and 12 come back, and you report "there were 12" without ever noticing that 8 vanished.


Exercise 3 — TogoVar

(a) Measured (2026-08-21): pathogenic + likely pathogenic = 182 variants.

(b) Resolving the gene name — "GBA1" is an exact match, "GBA" is not.

Search term First row match_type
GBA1 GBA1 (HGNC:4177) exact
GBA GBA1 (HGNC:4177) prefix ⚠️

HGNC renamed GBA → GBA1 in 2022, so the old symbol "GBA" is not in the current set of approved symbols. Search for "GBA" and there is not a single exact match — GBA1, GBA2, GBA3, GBAT2 and GBA1LP all line up as prefix matches, five of them.

★ If you did not get caught by this. GBA1 comes up on the first row, so you can sail straight past. That is the result of re-ranking, and it is luck.

match_type: prefix means "the symbol you asked about does not exist." Look at GBAT2, sitting right there in the same list — its official name is "RFX5 antisense RNA 1," and it is not even in the GBA family. Make "take the first hit" a habit and it will get you eventually.

Was that gene symbol an exact match or a prefix match?
If there are other candidates, explain why it is not one of those.

(c) They do not match. ★This is the single biggest point

Aggregation Unit being counted Total Matches 182?
type (SNV 157 / deletion 23 / insertion 2) variants 182 ✅ matches
significance variant × condition 457 ❌ 2.5×
consequence variant × transcript 2,613 ❌ about 14×

Saying "277 pathogenic variants" is wrong (277 is the number of Pathogenic records, and the total is larger still, 457).

One more thing that runs against intuition. You filtered on "pathogenic," yet the breakdown contains 35 Uncertain significance and 1 Likely benign. That is not a contradiction: the filter works on variants, the breakdown works on variant × condition records. rs421016 alone has 13 records.

The tool itself returns statistics_caveats. Read them.

(b2) HGVS does not come out — the place everyone gets stuck live

HGVS notation is not included in the default output. It takes an additional call with include_transcripts=True.

Give me the HGVS notation for that variant, including the transcript information.

What you get: NM_000157.4:c.1448T>C / NP_000148.2:p.Leu483Pro

💡 The old conventional name is L444P (the old numbering, with the 39-residue signal peptide excluded). The literature mostly writes L444P, so keep it in mind as a bridge.

⚠️ But the include_transcripts output is large — for GBA1 it comes back for 12 transcripts. Do the same thing on something BRCA1-sized (over 400) and your screen falls apart.

(d) Enrichment in the Japanese population — the highlight of this exercise

Take pathogenic + likely pathogenic and apply a ToMMo frequency ≥0.0005, and only one is left — rs421016 (L444P in the old notation, NP_000148.2:p.Leu483Pro in the current one).

Cohort Allele frequency
ToMMo (Japanese, n≈54,000) 0.000801
NCBN (Japanese) 0.000807
GEM-J WGA (Japanese) 0.001326
gnomAD exomes 0.0000842
gnomAD genomes 0.000237

Roughly a 10-fold enrichment in the Japanese population (ToMMo / gnomAD exomes ≈ 9.5×, and ≈ 15.7× for GEM-J WGA). All three Japanese cohorts are high together, so it reads as a population difference rather than an artifact of a single cohort. This is information you would miss if you only looked at the international databases. That is TogoVar's reason for existing, and it is the value of TogoMCP carrying Japanese databases.

⚠️ But do not assert it. ToMMo and NCBN carry the quality flag VQSRTrancheSNP99.95to100.00, and GEM-J WGA carries NotHighConfidenceRegion. GBA1 is highly homologous to the pseudogene GBAP1, which makes short-read mapping hard, and the possibility that part of the frequency difference is a technical false positive cannot be excluded.

This connects directly to "what it is not suited for" in Chapter 7. Check the quality flags before you make a claim about population differences.

⚠️ Do not say "heterozygous carriers are unaffected, so there is nothing to worry about."

Gaucher disease is autosomal recessive, and heterozygous carriers do not develop Gaucher disease itself. But that does not mean "unrelated" — heterozygous GBA1 variants substantially increase the risk of Parkinson's disease, as multiple reports have found (Sanyal et al., Mov Disord 2020, PMID 32034799).

That is exactly why Parkinson disease and dementia with Lewy bodies are sitting there on your screen. A confident reassurance is inaccurate in this situation.

One more trap (sharp of you if you caught it): many of the pathogenic variants have tgv_id: null. They exist on the REST side, but they are not in the subset on TogoVar's SPARQL side. Chase this through SPARQL and you will silently lose them.


Exercise 4 — Changing the definition changes the answer ★most important

(a) Using KW-0458 (the UniProt keyword "Lysosome")

161 proteins as candidates. On the approved-drug side, 48 rows.

UniProt Gene Approved drugs
P06213 INSR 22 insulin preparations
P09619 PDGFRB imatinib, sunitinib, sorafenib and others, 12
P06280 GLA migalastat
P10253 GAA miglitol, voglibose

34 of the 48 rows are insulin and PDGFR inhibitors. Put that out under the heading "approved drugs targeting lysosomal enzymes" and you will be called on it immediately.

(b) Using GO:0043202 (lysosomal lumen)

52 proteins as candidates. On the approved-drug side, 16 rows / 3 proteins / 14 drugs. The candidates now center on the genuine luminal hydrolases — GBA1, GLA, HEXA/HEXB, GAA, IDUA, IDS, ARSA/ARSB, SGSH, NAGLU, GALC, SMPD1, TPP1, PPT1.

The 22 insulin preparations are gone. PDGFRB stays (because GO:0043202 really is annotated on it), but the result is now at a scale you can read.

The answer:

Of the 52 human lysosomal luminal enzymes, 3 are targets of approved drugs — GLA (migalastat, a pharmacological chaperone), GAA (miglitol / voglibose, though the indication is type 2 diabetes), and PDGFRB (11 drugs; a receptor tyrosine kinase, not a lysosomal enzyme).

Take PDGFRB out and there is effectively one approved small-molecule line that targets a lysosomal enzyme directly: migalastat. Because lysosomal diseases are treated by replacing the enzyme, not by inhibiting it.

(c) Why they are this different

KW-0458 is a subcellular-localization keyword, not a classification of function. It means "a protein that localizes (at least sometimes) to the lysosome," so INSR, PDGFRB, MTOR, PCSK9, LRRK2, PSEN2, and around 20 RAB GTPases come in with it.

Narrowing by EC number to 3.* (hydrolases) does not fix it either. 113 of the 161 survive. Because RAB GTPases are EC 3.6.5.2 — perfectly respectable hydrolases.

And PDGFRB still survives into the GO:0043202 version. Because that annotation genuinely is on it. A localization annotation ≠ a functional classification. It is not an error; it is a question of the resolution of your question.

(d) Analogues in your own field — what to look for:

  • A localization word used as a function word ("mitochondrial protein" — works in the mitochondrion? localizes there? is encoded there?)
  • Speaking at a higher-level concept ("kinase," "transcription factor" — where do you draw the line?)
  • Clinical vocabulary mixed with molecular vocabulary ("cancer gene" — somatic driver? germline susceptibility?)
  • A conventional name that differs from the official one (as with GBA / GBA1)

Exercise 5 — The four verification steps

Common failures:

Where people get stuck What to do
The SPARQL gets summarized Say explicitly: "print it in full, verbatim." A summary is useless for reproduction
The COUNT ends up with different conditions than the main query Specify: "use the same WHERE clause as the main query and change only the COUNT"
The inner LIMIT goes unnoticed Ask directly: "did you use sampling or a LIMIT?"
Eyeballing gets skipped It takes 30 seconds. Do not skip it

(b) How to ask for an explanation when they differ:

COUNT(*) and COUNT(DISTINCT) differ.
Show me which predicate has multiple values, and give one concrete example.

"One concrete example" is what does the work. An abstract explanation cannot be verified.

(c) Eyeballing means looking at the "surprising" hit. The top hit is usually correct, so it carries little information. An oddity like PDGFRB sitting in a list of lysosomal enzymes is something you only notice by looking at the surprising hit.


Exercise 6 — Bad questions and good questions

(b) Checklist:

Check Danger sign
Were run_sparql or the search tools called Not called = answering from memory
Do the specific names in the answer exist in the tool output They do not = it came from memory (most important)
Are accessions / IDs attached Not attached = unverifiable
Time taken Around 10 seconds = possibly nothing happened

The second one is decisive. In the worked example in Chapter 4, TP53, KRAS and MYC lined up in the answer and not one of them existed in the tool output.

(d) The change you should expect (measured, Chapter 4):

Vague version Specified version
Time 12 s 182 s (~15×)
Tool calls 1 8
SPARQL 0 4
IDs verified none yes
Reproducible no yes

(e) The limitations that remain — this is the real point. Even the specified version in Chapter 4:

  • Has INS (co-occurrence via the pancreas as an organ), GAPDH (an experimental internal standard), and POTEF (a chimeric gene whose sequence is highly similar to ACTB; whether that is the cause of its rank is unconfirmed) contaminating the top of the list
  • Leaves the true drivers SMAD4 and CDKN2A outside the top 20
  • The reason: number of co-occurring papers measures "fame × paper count" and does not measure disease specificity

Your answer to (c) should have limitations of the same kind. Can you write, in one line, what this metric measures and what it does not? If you can, this tutorial has achieved its purpose.


Exercise 7 — Making it look for counter-evidence

The difference you should expect:

"Is this claim correct?" → the AI tends to agree. It tends to selectively gather the evidence that supports you.

"Find evidence that contradicts this claim" → the direction of the search changes, and different data comes out.

Which is more useful: almost always the latter. You already have the evidence on the supporting side. What you do not have is the other side.

Applying it in practice:

Is there a hypothesis other than this interpretation that explains the same data?
Find evidence in the databases that supports that hypothesis.

This works when you are writing the discussion of a paper, and when you are anticipating reviewer comments.


Free exercise — What to look at in the retrospective

When it does not work, the cause falls into three kinds. What matters is being able to tell which one it is.

Cause Sign What to do
The question's problem An answer comes back but misses the point. The tools were called Specify it again with the five elements from Chapter 6
The database's problem 0 hits. Or plainly incomplete Check what is and is not in that data. Try another DB
The tool's problem Errors, timeouts, cannot connect Go to Chapter 8

The first is the most common. And the first is also the hardest to notice — because an answer comes back, so it does not look like a failure.