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
- Connect TogoMCP to your own environment and query the major life-science databases in plain language
- Tell whether an answer came from a database or from the AI's memory
- 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.
- Get connected and be able to use it (Chapters 1–2)
- Understand why it works (Chapter 3) — without this, you are helpless when it does not
- 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
- Open Claude and go to Settings → Customize → Connectors
- "+" → "Add custom connector"
- Enter the MCP server URL:
https://togomcp.rdfportal.org/mcp
- 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.
- The owner goes to Organization settings → Connectors → Add, hovers over Custom, chooses Web, and adds it for the whole organization
- 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
- Typing
claude mcp addinside a Claude session. This is a command you type in the terminal. It will not work once you have typedclaudeand entered the session. - Dropping the trailing
/mcpfrom the URL. You get a 404. - Forgetting the scope. The default
localonly works in that one directory. Most cases of "but it worked yesterday" are this. - Putting the config file in the wrong place. Claude Code reads only
~/.claude.jsonand.mcp.jsonat the project root. Files like~/.claude/.mcp.jsonare not read.
The above was confirmed on Claude Code v2.1.210 and later.
claude --versiontells 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 isHGNC: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:
- Is the accession that came back the right one (five seconds on the UniProt website settles it)
- Is the sequence length what you expected. If not, why not (a precursor? an isoform?)
- Does
run_sparqlappear 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 callget_MIE_filefor 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(*)againstCOUNT(DISTINCT ...). If they differ, do not report until you can explain what is being duplicated.
Chapter 7 organizes this verification procedure.
3-7. Summary
- TogoMCP is not "a tool for sending SPARQL" but "a mechanism for handing out the knowledge of how it should be sent"
- The tools form three layers: guidance / grounding / execution
- 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
- The rules (read the MIE first, pin the graph) exist to prevent silent failure
- 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.
- Demo 4 — a question that search alone cannot answer (PDB)
- Demo 3' — a question that spans several databases (UniProt × ChEMBL)
- 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
- Some questions cannot be answered by search tools. That is where SPARQL becomes necessary (Demo 4)
- 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')
- A vague question fails plausibly, in 12 seconds, without consulting a database (the failure demo)
- Specifying it costs 15× more, but the provenance can be stated and errors can be found by yourself
- 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 startclaudeinside 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.
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.
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 decisions — never 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
- Failure shows up not as an error, but as a number that looks correct
- The four verification steps — keep the query / cross-check with COUNT / check by eye in the source DB / record the date
- What you must save is the query, not the table of results
- Cite at two levels: TogoMCP + the individual databases
- 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:
- 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 - Is the graph name right? Graph names do get renamed. Specifying an old one gives you a silent zero rows
- 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 - 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:
- Wait. There is nothing to do but wait for recovery
- 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
uvwithwhich uv(macOS/Linux) orwhere uv(Windows). A relative path, or justuv, 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.
- You are running it through the local stdio entry point
togo-mcp-local - 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 this — TOGOMCP_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"inrun_sparqlis invalid - To connect it to the RDF databases, use
kegg_convto 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. SettingTOGOMCP_LOG_RAW_IP=1records 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, seelog_file_specs.mdin 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.
togo_mcp/data/resources/endpoints.csv— the registration row (this alone determines the validdatabase=values)togo_mcp/data/mie/<db>.yaml— the MIE file (the specification is intogo_mcp/data/docs/)uv run python scripts/generate_usage_guide_catalog.py— regenerate the database catalogue in the usage guidetogo_mcp/data/resources/usage_guide_v6/02_budgets_and_discovery.md— a hand-written copy the generator does not touch. Update both the counts and the keystogo_mcp/data/docs/togomcp-intro.html— the landing-page card (not generated)
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_typethat 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:
- No structure has genuinely been solved
- A structure exists, but that correspondence is not registered along the ID-conversion route
- 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 carriesNotHighConfidenceRegion. 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.