An MCP server for querying the federated SPARQL endpoint
(https://apps.okn.us/federation/sparql) over the
Proto-OKN knowledge graphs.
It lets an LLM discover which knowledge graphs are relevant (from the
okn-registry descriptions), then run
SPARQL queries scoped to one or more named graphs of the form
https://purl.org/okn/frink/kg/{shortname}.
Proto-OKN — the Prototype Open Knowledge Network — is a National Science Foundation initiative (with NASA, NIH, the National Institute of Justice, NOAA, and the U.S. Geological Survey) that funds research teams to build a publicly accessible, interconnected set of data repositories and knowledge graphs. The graphs span domains such as health, the environment, criminal justice, space exploration, and supply-chain security, and are served together over the OKN federated SPARQL endpoint that this server queries. The okn-registry catalogs the participating knowledge graphs.
This repository ships one MCP server, two Agent Skills, and relies on two external literature MCPs — each with a distinct role:
| Component | Role |
|---|---|
![]() mcp-okn |
Federated OKN query service: discovers the relevant Proto-OKN graphs, writes SPARQL scoped to named graphs, aligns across KGs via crosswalks, and returns grounded rows with provenance and transcripts. |
![]() okn-report-style |
Report & reproducibility skill: turns an OKN analysis into a polished, reproducible deliverable (Markdown / HTML / Excel / figures / maps), tracking sources, versions, queries, and caveats. |
![]() okn-bioanalysis |
Biomedical workflow skill: cross-KG analysis of genes, diseases, chemicals, and drugs — enrichment, ortholog projection, mechanistic maps — ranking hypotheses by evidence and calling the literature MCPs to validate. |
![]() PubMed & Paperclip |
Literature evidence validation: search PubMed and full-text collections, corroborate OKN-derived claims against sources, add citations, and flag conflicts and uncertainty. |
Complementary by design: the skills augment mcp-okn, which queries the OKN;
okn-bioanalysis can call PubMed & Paperclip for literature evidence validation, and
okn-report-style turns the results into reproducible deliverables.
Once the server is configured in your MCP client (see Connecting your client), just ask in natural language — the assistant picks the graphs, writes the SPARQL, and combines the results for you. Some prompts to try:
- "List all Proto-OKN knowledge graphs as a table of shortname and description." — Result
- "List all verified crosswalks, grouped by domain, with an example of what each answers." — Result
- "For each crosswalk, list the join key and the SPARQL skeleton." — Result
- "Give a high-level overview of the spoke-genelab knowledge graph — its main classes and relationships — and draw the schema diagram." — Result
- "Which genes does rdkg associate with autism spectrum disorder?" — Result
- "What is the maximum PFAS measurement in each county?" — Result
- "How do I join spoke-okn and prokn? Show the verified recipe and shared identifier." — Result
- "Which knowledge graphs supply GO, pathway, or trait annotations for a gene I can join on Entrez?" — uses
find_context_sourcesto list every supplier with its join key and size - "What version of prokn is loaded, and when was it last updated?" — reads the
okn-voidprovenance viaget_kg_version - "Create a chat transcript of this analysis." — create a transcript in a downloadable Markdown file
- "Create a chat transcript of this analysis in PDF format." — the server returns Markdown and the client converts the
.mdto a.pdffile (Claude Desktop / claude.ai)
A crosswalk is a verified way to join two (or three) Proto-OKN knowledge graphs on a shared identifier — for example linking a disease in one graph to the genes another graph associates with it via a common MONDO or DOID id. Because the graphs are built by different teams on different ontologies, the value of the federation is in these connections: a crosswalk is an integration opportunity where a question one graph can't answer alone becomes answerable by combining two. This section catalogs the verified crosswalks and shows the queries that exercise them.
A visual map of the whole network — all 162 crosswalks across 35 graphs, drawn as
direct KG-to-KG edges (edge width ∝ log of the verified join count). Each crosswalk is
its own edge, so multiple crosswalks between the same pair of graphs fan out as parallel
arcs. Identifier-bridged joins (e.g. DOID↔MONDO via ubergraph, HGNC→Entrez via
wikidata) are shown as direct edges with the bridge noted in the label and the line
styled (dashed for an ubergraph bridge, dotted for wikidata, solid for a direct join).
▶ Click the image to open the interactive, zoomable network.
Two resources, each backed by live federated SPARQL joins verified to actually answer the question (biomedical claims checked against PubMed / Paperclip; geospatial and industrial joins against their authoritative shared standard):
- Proto-OKN Crosswalk Inventory — a single-page map of the verified crosswalks: the joined KGs, shared key, row count, and a one-line note on what each answers. Start here to see which graphs connect and on what identifier.
- Cross-KG crosswalk catalog — 328 example questions worked end-to-end, each with a full transcript (the live SPARQL and its results), across 15 domains (Anatomy & Cell Type, Chemicals, Disease & Phenotype, Earth Observation, Environmental Toxicology, Function & Pathways, Genes, Geospatial, Hydrology, Industry & Supply Chain, Justice & Public Safety, Proteins, Publications, Social Determinants & Services, Taxonomy). Every crosswalk is now worked twice — the inventory carries 324 questions — two for every one of the 162 crosswalks — and the catalog has a transcript behind each, plus four questions on two extra stems (a second example on the spoke-genelab×spoke-okn Entrez axis, and the three-way gene dossier whose clique row was retired).
Every catalog row links to a standalone, replayable transcript — the prompt, the answer, and every verbatim SPARQL query with its result.
These transcripts are produced by create_chat_transcript and can be re-run
against the endpoint with scripts/replay_transcript.py.
The catalog was generated by driving the model with the
crosswalk generation prompt — list every
list_crosswalks recipe, write two research questions per crosswalk, run and
verify each as live SPARQL, and validate the findings against the literature.
Ten end-to-end analyses that federate many Proto-OKN graphs into a single evidence-backed map — five of a disease's biology (genes, variants, pathways/gene sets, drugs, altered-activity signatures, and clinical/biomarker features), four of environmental exposure and justice (PFAS source attribution, the bisphenol chemical exposome, cumulative environmental-justice burden across U.S. counties, and flood-mobilised contamination routed downstream through the stream network), and one of urban scaling (how disease, mortality and crime scale with settlement size) — each finding tagged with its source(s) and evidence kind, then ranked by cross-source agreement. Every case study ships an interactive HTML report, a reproducibility record preserving every verbatim SPARQL query, and an Excel workbook.
Prerequisites for re-running a case study:
- The two Skills —
okn-bioanalysis(analysis) andokn-report-style(report format). - The PubMed and Paperclip MCP connectors — for the literature-comparison step only.
| Case study | Report | Literature Comparison | Data | Reproducibility | Folder |
|---|---|---|---|---|---|
| Type 2 diabetes — 16 KGs, 2,117 genes (381 multi-source) | HTML | md | xlsx | md | files |
| Alzheimer's disease — 8 KGs, 2,662 genes (318 multi-source) | HTML | md | xlsx | md | files |
| Multiple sclerosis — 14 KGs, 2,397 genes (52 Tier A) | HTML | md | xlsx | md | files |
| Spaceflight-induced bone loss — 8 KGs, 2,686-gene flight signature (777 core) | HTML | md | xlsx | md | files |
| Spaceflight-associated neuro-ocular syndrome (SANS) — 6 KGs, 169-gene cross-species core | HTML | md | xlsx | md | files |
| PFAS source prioritization — 5 KGs, 2,949 S2 cells, 12,714 co-located facilities | HTML | md | xlsx | md | files |
| Bisphenol chemical exposome — 7 KGs, 13 bisphenol analogues, 183 target genes (186 consensus pairs) | HTML | md | xlsx | md | files |
| Cumulative environmental-justice burden — 8 KGs, 3,134 counties, 6 burden domains (31 indicators), 495 high-burden | HTML | md | xlsx | md | files |
| Urban scaling of disease, mortality & crime — 3 KGs, 18 outcomes, 26,343 census places and up to 3,072 counties | HTML | md | xlsx | md | files |
| Flood-mobilised contamination cascade — 6 KGs, 11,085 flood-exposed facilities routed to 519 counties (179 downstream-only) | HTML | md | xlsx | md | files |
Each case study is a self-contained folder under docs/examples/{Name}/ — the
report and its deliverables sit alongside the figures/, scripts/, and data/
that produced them, so every analysis is inspectable and re-runnable:
docs/examples/MS/
├── MS_report.html / .md # interactive report + Markdown source
├── MS_literature_comparison.md # per-finding PubMed / Paperclip check
├── MS_reproducibility.md # every verbatim SPARQL query
├── MS_results.xlsx # multi-sheet data workbook
├── figures/ # publication figures (PNG)
├── scripts/ # figure / report / analysis build code
└── data/ # intermediate result tables (CSV)
The server is hosted at https://apps.okn.us/okn-mcp-dev/mcp — point any MCP
client at that URL, no local install required. (For a local install instead, see
Local installation.)
- Open Settings → Connectors → Add custom connector.
- Name it
mcp-okn-devand enter the URLhttps://apps.okn.us/okn-mcp-dev/mcp. - Click Configure and set the tool permissions to Always allow.
- In a new chat, click the
+icon and enable themcp-okn-devtoggle.
A Claude Pro or Max subscription is required for MCP connectors in Claude Desktop.
Register it from the CLI:
claude mcp add --transport http mcp-okn-dev https://apps.okn.us/okn-mcp-dev/mcpOr add it to .mcp.json in your project root (or ~/.claude/settings.json for
universal access):
{
"mcpServers": {
"mcp-okn-dev": {
"type": "url",
"url": "https://apps.okn.us/okn-mcp-dev/mcp"
}
}
}Verify with /mcp — you should see mcp-okn-dev listed as connected.
Supported in the ChatGPT web app at https://chatgpt.com.
- Sign in to https://chatgpt.com.
- Click your profile name/avatar.
- Open Settings.
- Go to Apps.
- Open Advanced settings.
- Turn on Developer mode (required for custom MCP apps in ChatGPT).
- Return to Apps.
- Click Create app.
- Enter the MCP app details:
- Name:
mcp-okn-dev - URL:
https://apps.okn.us/okn-mcp-dev/mcp
- Name:
- Save or create the app.
- Start a new chat.
- Click the + button in the message box.
- Select mcp-okn-dev from the list of available apps/tools.
- Turn Web search off before testing, so ChatGPT uses the MCP app rather than web search.
- Run the verification prompt. If ChatGPT returns a graph list, the MCP app is working.
A subscription is required for MCP connectors in ChatGPT.
Use MCP in Agent mode with the GitHub Copilot extension, with the same URL:
https://apps.okn.us/okn-mcp-dev/mcp.
The case studies' Comparison with prior work step checks each finding against the primary literature, which needs two additional MCP connectors beyond the mcp-okn server:
- PubMed —
https://pubmed.mcp.claude.com/mcp - Paperclip —
https://paperclip.gxl.ai/mcp
Add them the same way as the mcp-okn server — in Claude Desktop / claude.ai via Settings → Connectors → Add custom connector, or from Claude Code:
claude mcp add --transport http pubmed https://pubmed.mcp.claude.com/mcp
claude mcp add --transport http paperclip https://paperclip.gxl.ai/mcpThese are needed only to reproduce that one step; the rest of each case study runs on the mcp-okn server alone.
Two optional Agent Skills ship
in the skills/ directory. They teach a client the repeatable
methodology for working with the mcp-okn tools, so you get consistent analyses and
report deliverables without re-explaining conventions each time:
okn-report-style— layout, figure, and style conventions for turning any OKN case study into a polished, reproducible report deliverable (interactive HTML + Markdown + multi-sheet Excel + figures + maps).okn-bioanalysis— methodology for biomedical knowledge-graph analysis and cross-KG hypothesis generation over the OKN federation's bio graphs (genes, proteins, diseases, phenotypes, pathways, chemicals, drugs, enrichment, ortholog projection, and linking bio entities to place-based data via geography).
Each skill is a self-contained folder: a SKILL.md plus references/ and
scripts/. A ready-to-upload zip of each is also committed for convenience:
Agent Skills work in these clients:
| Client | Install method | Notes |
|---|---|---|
| Claude Desktop (macOS / Windows) | Upload the zip under Settings → Capabilities → Skills | Pro, Max, Team, or Enterprise |
| claude.ai (web) | Upload the zip under Settings → Capabilities → Skills | Pro, Max, Team, or Enterprise |
| ChatGPT Desktop | Type the prompt: Install skill from GitHub: https://raw.githubusercontent.com/sbl-sdsc/mcp-okn/main/skills/{skill}.zip |
Replace {skill} with okn-report-style or okn-bioanalysis |
| Claude Code (CLI + IDE extensions) | Copy the folder into .claude/skills/ or ~/.claude/skills/ (see below) |
Free tier and up |
| Claude Agent SDK | Point the SDK at the skill folder | For building custom agents |
| Claude Developer Platform (API) | Load via the code-execution / Skills API | For programmatic use |
Download the skills straight from this repo into your personal skills directory (available in every project on this machine) — no clone required. Run the block for whichever skills you want:
mkdir -p ~/.claude/skills && cd ~/.claude/skills
# okn-report-style
curl -sL "https://raw.githubusercontent.com/sbl-sdsc/mcp-okn/main/skills/okn-report-style.zip" -o okn-report-style.zip
unzip -oq okn-report-style.zip && rm okn-report-style.zip
# okn-bioanalysis
curl -sL "https://raw.githubusercontent.com/sbl-sdsc/mcp-okn/main/skills/okn-bioanalysis.zip" -o okn-bioanalysis.zip
unzip -oq okn-bioanalysis.zip && rm okn-bioanalysis.zipFor a project-scoped install (shared with a repo), swap ~/.claude/skills for
.claude/skills in your project root.
Verify with /skills — you should see okn-report-style and okn-bioanalysis
listed. Claude invokes a skill automatically when a task matches its description.
To run the server yourself instead of using the hosted service:
Install uv (see the installation guide), then clone this repo:
git clone https://github.com/sbl-sdsc/mcp-okn.gitWorking inside this repo? It ships a project-scoped .mcp.json, so
Claude Code offers the local server automatically — no registration needed. Use it
when developing: it serves the crosswalk catalog from your working tree, whereas the
hosted mcp-okn-dev endpoint serves whatever was last deployed and will not reflect
local edits to crosswalks.json until it is redeployed.
For Claude Code, register it from the CLI:
claude mcp add mcp-okn -- uv --directory /path/to/mcp-okn run mcp-oknOr add it to any MCP client's config (e.g. Claude Desktop claude_desktop_config.json):
{
"mcpServers": {
"mcp-okn": {
"command": "uv",
"args": ["--directory", "/path/to/mcp-okn", "run", "mcp-okn"]
}
}
}Replace /path/to/mcp-okn with the absolute path to your checkout.
The tools follow a typical analysis arc — discover → inspect → plan a join → query → record. The single table below is grouped in that order.
| Tool | Purpose |
|---|---|
| 1. Discover graphs | |
list_kgs |
List all KGs with shortname, title, description, homepage, named_graph, and a payload list — the curated context types each graph supplies (e.g. digcfdekg → gene, gene_set, trait, disease), so you judge a graph by what it carries, not its name. Served from a bundled snapshot for instant cold start. |
describe_kg(shortname, long_description=False) |
Full registry doc (frontmatter + prose) for one KG, for deeper context. Set long_description=True for the registry's ~150-word prose body — useful for picking among near-overlapping KGs. For spoke-genelab, also appends its spaceflight assay-comparison rules. |
get_kg_version(shortname=None) |
A KG's release version and last_updated (ISO-8601 timestamp) — read live from the okn-void meta-graph's VoID provenance (pav:version, pav:lastUpdatedOn). Omit shortname for every KG that records provenance (39 of 42), sorted by shortname. Use it to check how current a graph is or cite the exact version behind an analysis. |
| 2. Inspect a graph's schema and identifiers | |
get_schema(shortname, compact=True) |
Schema for one KG — classes, predicates, edge properties (with reification query templates), and node properties. Uses curated metadata when available, else probes the endpoint for distinct classes/predicates. Call before writing a query. Returns usage_notes (guidance + a reusable SPARQL snippet) for KGs with query-time domain rules, e.g. spoke-genelab. |
visualize_schema(shortname) |
Deterministic Mermaid classDiagram of a KG's schema, built server-side from get_schema — class boxes, labeled edges, and edge-property predicates as intermediary classes with typed fields (node classes light blue, edge classes orange, with a legend). When the curated metadata names predicates but not their endpoints, edges are recovered from the graph's rdfs:domain/rdfs:range scoped to the curated classes. Returns mermaid_block (already wrapped in a ```mermaid fence) — output it verbatim; don't redraw it as SVG/an image. Rendered examples: spoke-genelab, dreamkg, rdkg (details). |
probe_namespaces(shortname, predicate, sample=0) |
Report which identifier/ontology namespaces populate a predicate's objects. get_schema lists a KG's predicates but not which controlled vocabularies fill their values — call this before the main query whenever a predicate's objects are ontology terms (diseases, chemicals, genes, anatomy) to see the actual namespace distribution and pick the best identifier to join on. Exploratory — not logged. |
find_crosswalks(shortname, sample=0) |
Find ontology/database ids in a KG however they are encoded, profiling all three places at once: mapping predicates (rdfs:seeAlso, owl:sameAs, SKOS *Match, oboInOwl:hasDbXref), node IRIs that are the ontology term (role="subject"), and domain-specific predicates carrying an id (role="object"). The latter two are invisible to a mapping-predicate-only scan. Use whenever a KG seems to lack the identifier you need on its obvious predicates. |
| 3. Plan a cross-graph join | |
list_crosswalks(include_examples=True) |
List every verified cross-KG integration point in one call — a global map of which graphs connect and on what shared key. Rows are grouped by domain (Genes, Geospatial, Disease & phenotype, …) and sorted by ontology, ready to render as a table. Each row is a compact summary (domain, connected kgs in join order by official shortname, shared_key, bridge_kg, verified_count, and an example_question by default; set include_examples=False for a terser list). Use get_join_strategy(kg_a, kg_b) for a single pair's full recipe. |
get_join_strategy(kg_a, kg_b=None) |
Look up a precomputed, hand-verified recipe for joining two KGs — predicates, roles, shared identifier, bridge graph, verified count, and a runnable skeleton_query (the example SPARQL to copy and build on; it already encodes the IRI rewrites). Call before writing a federated join. Returns verified / known_non_join / unknown; with kg_b omitted, lists every join touching kg_a. |
find_context_sources(want=None, join_key=None) |
Reverse capability index — the inverse of get_join_strategy. Answers "which KGs supply pathway / GO / trait / disease … for an entity I can join on join_key?" by combining the per-KG payload tags with the verified crosswalk table. Returns, per requested context type, the supplier KGs with predicate + shared key + verified join size, sorted biggest-join-first, plus a payload_only bucket (KGs that carry the type but key it differently, e.g. Ensembl vs Entrez). A requested type that yields an empty list is positive evidence nothing supplies it on that key — so you never conclude a context is "unavailable" without checking. |
taxon_overlap(kg_a, kg_b) |
Compose the NCBITaxon overlap between two hub KGs through ubergraph. Returns two runnable skeletons — exact_id (same taxon id) and clade_membership (kg_b taxa under kg_a's clades via subClassOf*, which can be far larger when one side is coarser-grained) — plus, for a pair with a precomputed non-zero overlap, the materialized counts under materialized_overlap (the same per-pair counts list_crosswalks surfaces in the NCBITaxon hub row). Run a skeleton with sparql_query. |
point_to_s2(lat, lng, level=13) |
Convert a lat/long point to its spatialkg/KWG S2 cell IRI (Level-13 default) — the deterministic primitive behind the spatial bridge. Use when a KG carries POINT coordinates but no S2 key and you need the cell IRI spatialkg stores. |
spatial_bridge(point_query, target_pattern, select_vars="*", extra_prefixes="", limit=500) |
Generic point→S2 bridge for any point-bearing graph that lacks a stored S2 key (sudokn is the first such graph). point_query must SELECT ?site ?lat ?lng; the server computes each cell in Python and injects (?site ?cell) as a VALUES block into a federated query whose target_pattern joins ?cell to spatialkg/fiokg/sawgraph (e.g. county/FIPS). Nothing is persisted — the computed key lives only inside the request. |
| 4. Run queries | |
sparql_query(query, format="json", exploratory=False, compact=False) |
Run a SPARQL query on the federation endpoint. Substantive results are logged for the transcript unless exploratory=True. Pass compact=True for a token-efficient json shape — {"columns", "data", "count"} with positional rows — instead of the default repeated-key {"vars", "rows", "row_count"} (affects only the returned payload, not the transcript). A bracketed <https://schema.org/…> IRI is canonicalized to the http:// form most KGs store (string literals and IRI(CONCAT(…)) are left as written). A few KGs store the https:// form (nikg, ruralkg, ufokn); reach those predicates by binding the predicate as a variable and matching scheme-free, e.g. FILTER(STRENDS(STR(?p),'schema.org/location')). |
expand_ontology_term(term, relation="subClassOf", direction="descendants", include_self=True, limit=1000) |
Expand an ontology term to its full subtree/closure via the ubergraph graph. |
| 5. Record a reproducible transcript | |
reset_query_log() |
Clear the session query log. Call at the start of an analysis to scope a transcript. |
get_query_log() |
Return the queries logged so far this session (only those that returned rows and weren't exploratory). |
create_chat_transcript(model, exchanges, ...) |
Emit a reproducible markdown (or JSON) record of a session — prompts, answers, the verbatim queries + results that produced findings, and any visualize_schema diagrams. Call at the end of an analysis. |
create_reproducibility_record(model, supporting, ...) |
Emit a lean reproducibility record — header + the verbatim supporting queries + row counts + per-query diagrams (gated by size), no conversation prose or result tables. Small enough to return inline so it saves directly; use it for the reproducibility deliverable. supporting optionally curates the log to the queries that underpin the findings. |
| Resource | Purpose |
|---|---|
transcript://session/latest (text/markdown) |
The most recent record rendered by create_chat_transcript or create_reproducibility_record, so a client can fetch/save the document directly (transport-agnostic; works for remote servers). Cleared by reset_query_log. |
- mcp-okn redesign (also as a slide deck, PDF)
— a side-by-side comparison of the previous
mcp-proto-oknserver and thismcp-oknredesign across query model, KG coverage, schema/registry handling, cross-graph joins, and reproducibility.
The package is organized by concern. server.py is a thin assembly point: it
imports the tool modules to trigger their @mcp.tool() registration and
re-exports their public symbols (so from mcp_okn.server import ... keeps
working). The shared mcp application instance lives in app.py, separate from
server.py, so the tool modules can import it without a circular dependency.
src/mcp_okn/
├── app.py # the shared FastMCP `mcp` instance + INSTRUCTIONS
├── server.py # assembly point: registers tools, re-exports, main()
├── registry.py # KG discovery from the okn-registry (+ bundled snapshot)
├── schema.py # get_schema / visualize_schema logic
├── sparql.py # federation endpoint client + schema.org normalization
├── crosswalks.py # curated cross-KG join table (data/crosswalks.json)
├── payloads.py # curated per-KG payload tags (data/kg_payloads.json)
├── void.py # per-KG version / last-updated from the okn-void graph
├── taxon.py # NCBITaxon hub: taxon-overlap skeleton composition
├── session.py # in-memory query/diagram log for transcripts
├── data/ # bundled snapshots: kgs.json, crosswalks.json, kg_payloads.json
└── tools/ # one module per concern; each registers via @mcp.tool()
├── _shared.py # helpers used by >1 tool module (_to_uri, …)
├── discovery.py # list_kgs, describe_kg, get_kg_version
├── schema_tools.py # get_schema, visualize_schema
├── probe.py # probe_namespaces, find_crosswalks
├── joins.py # get_join_strategy, taxon_overlap, list_crosswalks, find_context_sources
├── query.py # sparql_query, expand_ontology_term
└── transcript.py # reset/get_query_log, create_chat_transcript, create_reproducibility_record, resource
uv run python -m pytest # unit tests (offline)
uv run ruff check . # lint
uv run ruff format . # auto-format (use --check in CI)
uv run mypy # type-check src/mcp_okn
# live smoke test:
uv run python -c "import asyncio; from mcp_okn.sparql import run_sparql; \
print(asyncio.run(run_sparql('SELECT ?s WHERE { ?s ?p ?o } LIMIT 3')))"CI (.github/workflows/ci.yml) runs ruff lint, ruff format-check, and mypy on
every push/PR, plus the offline test suite on Python 3.10 and 3.12. A skills
job validates both Agent Skills against the
Agent Skills spec with the official
agentskills validator and asserts each committed .zip is in sync with its
skill folder (so an edited SKILL.md can't ship without a rebuilt zip).
ruff is pinned in uv.lock, and CI lints/format-checks the whole tree, so a
ruff version bump can surface new findings across the repo — reconcile them in one
pass when bumping. Scope (see [tool.ruff] in pyproject.toml):
- Excluded (
extend-exclude):docs/reproduction/anddocs/examples/— one-off, throwaway figure/PDF/reproduction scripts, not maintained tooling. - Docstrings not required (
Dignored viaper-file-ignores):tests/,scripts/,benchmark/, and the bundledskills/*/scripts/helpers. Everything else — chieflysrc/mcp_okn/— is held to the full rule set.
When committing, stage explicit paths (git add <path> …), not git add -A: the
working tree often carries untracked worked-example artifacts under docs/reproduction/
that should not be swept into an unrelated commit.
Deferred improvements are tracked as GitHub issues.
Reproducible checks of behaviors that aren't covered by the offline unit tests:
- schema.org http/https normalization
— a bracketed
<https://schema.org/…>IRI is canonicalized tohttp, so a query written with thehttpsform still hitshttp-stored data (dreamkgschema:Rating→ 3762), while string literals /IRI(CONCAT(…))are left intact (see below). - visualize_schema rendering — the
generated Mermaid renders cleanly as a class diagram via
mermaid-cliacross all three schema paths (curated, class-only, probe fallback), and survives thecreate_chat_transcriptround-trip. - transcript MCP resource — the
transcript://session/latestresource serves the full document via the resource API, with its embedded diagram still rendering.
A few graphs store schema.org terms under the https:// form. The canonicalization
hits only bracketed IRIs, so those predicates are unreachable by IRI (the bracketed
https form is rewritten to http, which the data isn't), but match when the predicate
is bound as a variable or the IRI is rebuilt from a string literal (now preserved).
Verified live against the federation:
| Graph (predicate) | <https://schema.org/X> (bracketed) |
IRI(CONCAT('https://schema.org/','X')) |
STRENDS(STR(?p),'schema.org/X') |
|---|---|---|---|
nikg (location) |
0 | 296,189 | 296,189 |
ruralkg (postalCode) |
0 | 9,037 | 9,037 |
ufokn (value, level-13 sample) |
0 | 5/5 (LIMIT 5) |
5/5 |
In each case the IRI(CONCAT) and STRENDS forms agree (same rows / same decimal S2
ids for ufokn) while the bracketed IRI returns 0 — confirming the literal-preservation
fix and the variable-predicate workaround documented in the ruralkg/ufokn crosswalk
notes.
spoke-genelab (NASA GeneLab) models each differential measurement as an Assay
with two arms — factor_space_1/factor_space_2 (the condition labels) and
factors_1/factors_2 (lists bundling the condition label plus extra factors
like dose, time, sex, strain). Reading any assay as a "spaceflight effect" is
wrong; two domain rules apply:
- Direction — keep only
factor_space_1 = "Space Flight"andfactor_space_2 = "Ground Control". Drop the reverse and every other pairing. Verified live: 680 assays are SF→GC vs 664 the reverse (plus SF/SF 1244, GC/GC 424, and Basal/Vivarium pairings). With this orientation group 1 = Space Flight, solog2fc/methylation_diff/lnfc> 0 means up in spaceflight. - Comparability — two separate
Assayrecords are comparable only if they share the same materials (prefermaterial_id_1/material_id_2) and the samefactors_1/factors_2after the experimental-condition labels are stripped, then test the remaining factors for equality (a shared extra factor is allowed if present on both sides).
Stripping removes exactly 15 distinct values, verified live: the spelled-out
labels (Space Flight, Ground Control, Basal Control, Vivarium Control,
Cell Culture Control; case-insensitive, so Ground control/Vivarium control
match too) and the short group codes via the anchored regex
^(GC|FLT|VIV|BSL|CC)(_C[0-9]+)?$ (GC, FLT, VIV_C2, BSL_C1, CC_C1, …).
The anchoring is deliberate: real factors that merely contain a control word —
Hardware 1G Ground Control, Ground Control Rerun, HLU_IR
(hindlimb-unloading) — stay in the list as legitimate distinguishing factors.
The rules live once in src/mcp_okn/contrasts.py (guidance prose + a reusable
comparability-signature SPARQL snippet) and are surfaced in the server
INSTRUCTIONS, as usage_notes on get_schema("spoke-genelab"), and appended to
describe_kg("spoke-genelab").
list_kgs serves a static snapshot bundled at src/mcp_okn/data/kgs.json (~42
KGs), so the first call returns instantly without fetching the individual
registry files. The live registry is only contacted when the snapshot is missing
(or when an internal refresh=True is passed). To refresh the snapshot after the
registry changes:
uv run python scripts/refresh_snapshot.pyKGs that are in the registry but not actually loaded under their expected
federation named graph (currently semopenalex) are filtered
out, so list_kgs only returns graphs that are queryable.
The curated crosswalk table is edited at metadata/crosswalks.json and bundled to
src/mcp_okn/data/crosswalks.json by the same refresh_snapshot.py run. Two helpers
recompute its live-verified counts against the federation and write them back:
scripts/verify_skeletons.py (per-crosswalk skeleton_query counts) and
scripts/refresh_taxon_overlaps.py (the NCBITaxon hub's pairwise exact_id + clade
counts — --inject to write, --pair A B for one pair). Run refresh_snapshot.py
afterwards to sync the bundled copy.
Each KG carries curated payload tags — the context types it supplies —
edited at metadata/kg_payloads.json and bundled to
src/mcp_okn/data/kg_payloads.json by refresh_snapshot.py (which validates that
every tag is a defined vocabulary term and every servable KG is tagged). These tags
are partly derived from each KG's entity schema, which get_schema fetches live
from the upstream *_entities.csv files — so a KG's schema can change under the
tags. scripts/check_payload_drift.py guards against that: it fingerprints each
KG's live class + predicate labels and diffs them against a committed baseline
(metadata/schema_fingerprints.json), flagging KGs whose schema moved so their
tags can be re-reviewed. Run it after an upstream schema update; re-ground any
affected tags, then --update to accept the new baseline.
uv run python scripts/check_payload_drift.py # report drift; exit 1 if any
uv run python scripts/check_payload_drift.py --update # accept current schemas as baselinemcp-okn is the next-generation successor to
mcp-proto-okn. If you use this
software, please cite the paper describing that predecessor:
Rose, P. W., Good, B. M., Saravia-Butler, A. M., Nelson, C. A., Balhoff, J. P., Kebede, Y., Whetzel, P. L., Bizon, C., Su, A. I., & Baranzini, S. E. (2026). mcp-proto-okn: Natural-language access to open scientific knowledge graphs through the Model Context Protocol. arXiv:2605.30283. https://arxiv.org/abs/2605.30283
@misc{rose2026mcpprotookn,
title = {mcp-proto-okn: Natural-language access to open scientific knowledge graphs through the Model Context Protocol},
author = {Rose, Peter W. and Good, Benjamin M. and Saravia-Butler, Amanda M. and Nelson, Charlotte A. and Balhoff, James P. and Kebede, Yaphet and Whetzel, Patricia L. and Bizon, Christopher and Su, Andrew I. and Baranzini, Sergio E.},
year = {2026},
eprint = {2605.30283},
archivePrefix = {arXiv},
primaryClass = {cs.AI},
url = {https://arxiv.org/abs/2605.30283}
}- National Science Foundation Award #2333819: "Proto-OKN Theme 1: Connecting Biomedical information on Earth and in Space via the SPOKE knowledge graph"
- National Science Foundation Award #2535091: "Proto-OKN Theme 2: OKN-Fabric"
This project is licensed under the BSD 3-Clause License.




