MCP server for AI-assisted cBioPortal navigation. Supports stdio mode for Claude Desktop and HTTP mode (Streamable HTTP transport) for remote MCP clients.
Six tools: resolve_and_route, get_studyviewfilter_options, navigate_to_study_view, navigate_to_group_comparison, navigate_to_results_view, navigate_to_patient_view. Prompts loaded from src/prompts/*.md at startup. Tool files use factory functions (createXxxTool()) so loadPrompt() runs after initPrompts().
-
Plain API Endpoints — cbioportal.org now serves all StudyView/study/sample data under regular
/api/paths (column-store was merged into the main API; the/api/column-store/prefix and the oldallSampleCountbug are gone). No URL rewriting needed;apiClientcallscbioportal-ts-api-clientdirectly. -
Two-Tier Filter Metadata — Router returns only attribute IDs (~300 tokens).
get_studyviewfilter_optionsprovides details on-demand (clinical + generic assay). Avoids ~1,500 tokens per query when filters aren't needed. -
Tiered Study Metadata — Keyword search: top 5 get full metadata (clinicalAttributes, molecularProfiles, treatments); rest get basic info. Direct studyIds: all get full metadata.
-
Manual Schema Maintenance —
src/tools/studyView/schemas/is manual, not auto-generated. Source types have known issues; only ~20 of 121 schemas used; API is stable. -
Clinical Attribute Bins & Unified Group Comparison —
get_studyviewfilter_optionsreturns quartilebinsfor NUMBER clinical attributes viafetchClinicalDataBinCountsUsingPOST(binMethod: QUARTILE), same{start?, end?, count}shape as gene-expression bins fromgetGeneSpecificCounts.navigate_to_group_comparisonhas a single mode — customgroups(studyViewFilterorisUnselected: true); the formerclinicalAttributeId/clinicalAttributeValues/includeNAauto-grouping mode was removed. Clinical-attribute splits (merged values, quartile ranges, and an"NA"/missing-data group viaclinicalDataFilters: [{value: "NA"}], which correctly returns samples with no recorded value) are built by the AI fromget_studyviewfilter_optionsvalues/bins. -
StudyView→ResultsView via Session —
navigate_to_results_viewwithstudyViewFilterfetches filtered samples, createsPOST /api/session/main_session, returns?session_id=...URL. -
StudyView→PatientView via navCaseIds —
navigate_to_patient_viewwithstudyViewFilter: ≤20 patients → PatientView URL withnavCaseIdsin hash (frontend'shandleLongUrls()strips towindow.navCaseIdsCacheonly at >60000 chars); >20 patients → StudyView URL withfilterJson. -
Companion URLs — Navigation tools return a
studyViewUrlalongside the primaryurlwhen a filtered subset is involved:navigate_to_results_viewwithstudyViewFilter: returnsstudyViewUrl(StudyView with same filter) so users can explore the cohort.navigate_to_group_comparison: always returnsstudyViewUrl(base study or with pre-filter) andgroupUrls(one per filter-based group, omitted for anisUnselectedgroup).system.mdinstructs the LLM to present both links, and allows parallel navigation tool calls when a query spans multiple views.
-
No targetPage Constraint —
resolve_and_routeonly resolves studies and returns metadata. The LLM decides which navigation tool(s) to call based on the selection guide in the tool description. This allows multi-tool calls and flexible routing in multi-turn conversations. -
Unselected Group (Wildtype/Complement) — In
navigate_to_group_comparison, one group may use{ name, isUnselected: true }instead of astudyViewFilter. This group receives all cohort samples NOT matched by any other group (complement). Implemented innavigateToGroupComparison: fetches full cohort (with globalstudyViewFilterif provided), subtracts union of all filter-group samples. At most one unselected group allowed. NogroupUrlis generated for the unselected group (no simple StudyView filter can express a complement). -
ResultsView Per-Gene Comparison Groups —
navigate_to_results_viewacceptscomparisonSelectedGroups: string[]to pre-select which groups appear in the comparison tab. With default OQL (no custom OQL), each queried gene gets its own group named after the gene symbol. Passing["IDH1", "EGFR"]compares IDH1-altered vs EGFR-altered samples (true altered = mutation + CNA + SV via OQL). Omit for default Altered vs Unaltered aggregate groups. Group name = gene symbol;comparison_selectedGroupsis JSON-stringified in the URL. -
Plots Pre-Configuration — Both
navigate_to_study_viewandnavigate_to_results_viewacceptplotsHorzSelection/plotsVertSelection(tab must be"plots").selectedGeneOptionaccepts Hugo symbol — resolved to Entrez ID automatically viageneResolver.resolvePlotsGene().selectedDataSourceOption= profile suffix (strip{studyId}_prefix from molecular profile ID — e.g.luad_tcga_pan_can_atlas_2018_rna_seq_v2_mrna→rna_seq_v2_mrna); frontend matches by suffix across studies. FordataType: "clinical_attribute",selectedDataSourceOption= clinical attribute ID (e.g.CANCER_TYPE_DETAILED). ValiddataTypevalues:MRNA_EXPRESSION,MUTATION_EXTENDED,COPY_NUMBER_ALTERATION,METHYLATION,PROTEIN_LEVEL,STRUCTURAL_VARIANT,clinical_attribute. Default OQL coloring applied by frontend;plotsColoringSelectionnot exposed. For ResultsView plots, all genes referenced in either axis must be included ingenes(frontend populates gene dropdowns only from queried genes). -
Tab-Level Page Descriptions — All four navigation tools return a
pageDescriptionfield when atabis specified. Descriptions are curated strings sourced from the cBioPortal frontend (not LLM-generated), stored insrc/tools/shared/pageDescriptions.ts. Coverage: PatientView (summary,clinicalData,pathways), StudyView (summary,clinicalData,cnSegments,plots), ResultsView (19 tabs including comparison subtabs), GroupComparison (9 tabs includinggeneric_assay_*). The LLM is instructed insystem.mdto use this field verbatim and not supplement it — the sanctioned way to describe what a page shows without hallucinating UI features. -
Oncoprint Annotation Tracks —
navigate_to_results_viewacceptsoncoprintClinicalTracks: string[],oncoprintHeatmapTracks/oncoprintGenericAssayTracks: Array<{molecularProfileId, entities}>to add annotation rows below the genomic tracks (maps to the frontend'sclinicallist/heatmap_track_groups/generic_assay_groupsURL params). UnlikeprofileFilter/plotsselectedDataSourceOption, these two params require the exact fullmolecularProfileId— the frontend resolves them via a direct dictionary lookup (molecularProfileIdToMolecularProfile) with no suffix-matching fallback; an unrecognized ID is silently dropped. IDs come fromresolve_and_routemetadata:heatmapProfileIds(new field — mRNA/protein profiles withshowProfileInAnalysisTab, plus all methylation profiles) andgenericAssayProfiles.oncoprintHeatmapTracks[].entities(gene symbols) are validated viageneResolver.validateBatch; invalid genes are dropped silently by cBioPortal's own URL parsing, so the validated result is echoed back in the response'soncoprintHeatmapTracksfield for the LLM to verify.oncoprintGenericAssayTracks[].entities(entity stableIds) are passed through unvalidated — obtained viaget_studyviewfilter_options(genericAssayProfileIds). Wired into both URL-construction paths (default case-set andstudyViewFilter/session).
Key enums/types referenced by this project:
- PatientViewPageTabs (
PatientViewPageTabs.tsx):summary,genomicEvolution,clinicalData,filesAndLinks,pathologyReport,tissueImage,MSKTissueImage,trialMatchTab,mutationalSignatures,pathways- We expose only always-visible:
summary,clinicalData,pathways
- We expose only always-visible:
- ResultsViewTab (
ResultsViewPageHelpers.tsx):oncoprint,survival(redirect→comparison),cancerTypesSummary,mutualExclusivity,plots,mutations,structuralVariants,coexpression,comparison,cnSegments,network,pathways,expression(redirect),downloadcnSegments: runtime-validated viavalidateTabAvailability(shared with StudyView); ~57% of studies have segment datastructuralVariants: inferred from profile IDs (look for_sv/_fusion/_structural_variants)coexpression: inferred from profile IDs (look for_mrna/_rna_seq/_rppa); single study only
- ResultsViewComparisonSubTab (
ResultsViewPageHelpers.tsx):overlap,survival,clinical,mrna,protein,dna_methylation,alterations,generic_assay(prefix). URL pattern:/results/comparison/{subtab}. Our tool supports these via composite tab values likecomparison/protein. - ResultsView comparison groups (
ResultsViewComparisonUtils.ts,ResultsViewPageStore.ts): two types — (1) aggregate"Altered group"/"Unaltered group"(default selection); (2) per-OQL-track groups, one per queried gene named after the gene symbol when using default OQL (e.g."IDH1","EGFR"). Selected viacomparison_selectedGroupsURL param (JSON array of group names). Default OQL covers mutation + CNA + SV — per-gene groups therefore represent true "altered" for each gene. Our tool exposes this ascomparisonSelectedGroups: string[]. - GroupComparisonTab (
GroupComparisonTabs.ts,ComparisonStore.ts,EnrichmentsUtil.tsx): Tab availability computed from molecular profiles + clinical attributes inresolveAndRoute→availableComparisonTabs. Always:overlap,clinical. Conditional:survival(paired_STATUS+_MONTHSattrs),alterations(MUTATION_EXTENDEDorCOPY_NUMBER_ALTERATION+DISCRETE),mutations(MUTATION_EXTENDED). Single-study only:mrna(MRNA_EXPRESSION),protein(PROTEIN_LEVEL),dna_methylation(METHYLATION),generic_assay_{type_lowercase}(one pergenericAssayType). Default tab when none specified:overlap(frontend fallback inGroupComparisonURLWrapper). - ALTERATION_FILTER_DEFAULTS (
StudyViewUtils.tsx):copyNumberAlterationEventTypesonly supportsAMP/HOMDEL; forGAIN/HETLOSS/DIPLOIDusegeneFilters - navCaseIds (
PatientViewUrlWrapper.ts,handleLongUrls.ts): hashed URL param for cohort navigation;handleLongUrls()moves towindow.navCaseIdsCachewhen >60000 chars - Column-store (
proxyColumnStore.ts): rewrites$domainto/api/column-storefor whitelisted endpoints (ClinicalDataCounts, FilteredSamples, etc.)
Hash params (filterJson) are URL-encoded via encodeURIComponent in cbioportalUrlBuilder.ts to ensure URLs are safe for embedding in markdown links. The LLM is instructed in system.md to always use the exact url field from tool responses verbatim.
- StudyView URL params not implemented:
sharedGroups,sharedCustomData,geneset_list - Treatment tier data (AgentClass/AgentTarget) — identical to base data on public cBioPortal
- Methylation profiles (hm27/hm450) have tens of thousands of probes — use
entitySearchinget_studyviewfilter_optionsto filter by gene symbol or probe ID before returning results