GraphRAG How It Works User Guide - TerrenceMcGuinness-NOAA/global-workflow GitHub Wiki

How the MCP-RAG System Traverses Code: A User-Level Guide

Date: July 22, 2026 Audience: Developers, researchers, and operators new to the MDC MCP-RAG platform Context: Explains item #6 from the Platform-Parity-Check-AWS-vs-COTS-2026-07-22 β€” why setuprad returns 55 direct neighbors identically on both AWS and COTS, and what "GraphRAG" actually means in this system.


The Big Picture

When you ask "what calls setuprad?" or "trace the execution chain from JGLOBAL_FORECAST", the system doesn't just search text β€” it walks a knowledge graph. Think of it like asking for directions in a city: the graph is the street map (functions connected by "calls" relationships), and the traversal is the act of following those streets to find every path that leads to or from your destination.

The result: 55 direct neighbors of setuprad on both platforms β€” qc_amsua, init_crtm, call_crtm, nc_diag_write, setup_sst_retrieval, etc. These are the actual call-graph relationships extracted from the Fortran source code, not keyword matches. The system knows setuprad calls init_crtm because it parsed the CALL init_crtm(...) statement in the source.


Three Layers Make This "GraphRAG"

The system combines three distinct capabilities:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Layer 1: GRAPH CONSTRUCTION (offline, at ingest time)          β”‚
β”‚  "Building the map from source code"                            β”‚
β”‚                                                                 β”‚
β”‚  Fortran parser  β†’ CALLS / USES / CONTAINS edges                β”‚
β”‚  Shell parser    β†’ SOURCES / INVOKES / EXPORTS / DEPENDS_ON_ENV β”‚
β”‚  Bridge builder  β†’ Shell-to-Fortran EXECUTES edges              β”‚
β”‚  Rocoto parser   β†’ Workflow DAG (task dependencies)             β”‚
β”‚  Python parser   β†’ IMPORTS / DEFINES edges                      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                     β”‚ writes to
                                     β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Layer 2: GRAPH STORAGE + TRAVERSAL (runtime queries)           β”‚
β”‚  "The map, and how you walk it"                                 β”‚
β”‚                                                                 β”‚
β”‚  COTS: Neo4j Community + Graph Data Science (GDS 2.13.7)        β”‚
β”‚  AWS:  Amazon Neptune (managed, openCypher)                     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                     β”‚ queried by
                                     β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Layer 3: GRAPH-GUIDED RETRIEVAL (the "RAG" part)               β”‚
β”‚  "Using the map to answer your question"                        β”‚
β”‚                                                                 β”‚
β”‚  MCP tools combine graph walks with vector search               β”‚
β”‚  to give you context-aware, relationship-aware answers          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Layer 1: How the Graph Gets Built

Before you can traverse code, someone has to read every source file and extract the relationships. This happens offline via ingestion scripts:

Parser What it reads Edges it creates Example
Fortran (ingest_fortran_graph_v8.py) .f90, .F90 files under sorc/ CALLS, USES, CONTAINS, DEFINES setuprad CALLS init_crtm
Shell (ingest_shell_graph_v8.py) .sh, .ksh under ush/, scripts/, jobs/ SOURCES, INVOKES, EXPORTS, DEPENDS_ON_ENV exgfs_atmos_analysis.sh SOURCES parsing_namelists_FV3.sh
Bridge (create_shell_fortran_bridge.py) Cross-references shell→Fortran EXECUTES JGLOBAL_FORECAST EXECUTES ufs_model
Python (ingest_python_graph.py) .py under ush/, workflow/ IMPORTS, DEFINES wxflow.task IMPORTS datetime
Rocoto (ingest_rocoto_xml_v8.py) *.xml workflow definitions DEPENDS_ON, SETS_ENV gdas_analysis DEPENDS_ON gdas_prep
Config (ingest_config_files_v8.py) parm/config/config.* SETS_ENV, READS_CONFIG config.fcst SETS_ENV FHMAX

The result for the gw (develop) tenant today:

Platform Nodes Relationships Fortran subroutines Shell scripts
COTS (Neo4j) 225,836 4,051,374 80,745 589
AWS (Neptune) 148,976 4,555,408 27,941 315

Why the difference? COTS ran the regex-fallback parser (catches 99.9% of Fortran files including ones that trip fparser2's strict AST). AWS ran fparser2 only (fewer nodes but richer AST-level DEFINES edges β€” hence more relationships). Both are correct; they're different parser depths on the same source code.


Layer 2: How the Graph Gets Traversed

Once the graph exists, the MCP tools walk it at runtime. This is where the Neo4j vs Neptune gap matters.

What both do identically (openCypher)

Both Neo4j and Neptune speak openCypher β€” a graph query language. When you call find_callers_callees("setuprad"), the system runs a query like:

MATCH (target:FortranSubroutine {name: "setuprad"})
MATCH (target)-[r]-(neighbor)
RETURN neighbor.name, type(r)

This is why setuprad returns the same 55 neighbors on both β€” the query is identical, the data is equivalent, and the traversal logic is the same.

Where they diverge: algorithms

Capability Neo4j (COTS) Neptune (AWS)
Basic traversal (MATCH paths, shortest path) Yes Yes
Variable-length paths (-[:CALLS*1..5]->) Yes Yes
Fan-out protection (degree > 100 β†’ one-hop only) Yes (our code) Yes (our code)
Depth-bounded traversal (max_depth parameter) Yes Yes
Graph Data Science (GDS) β€” community detection (Leiden), PageRank, centrality, similarity Yes (GDS 2.13.7) No
Statement timeout backstop (30s kill) N/A (local, fast) Yes (prevents Neptune OOMs)

The GDS gap is the one we discovered a few weeks ago (documented as Gap J in the multi-tenant gap tracker). It means:

  • On COTS, we can run Leiden community detection to group related functions/modules into "subsystems" (e.g. "the GSI EnKF subsystem", "the atmospheric forecast model subsystem") β€” these power search_architecture and get_code_context(..., include_community=True).
  • On AWS, community detection doesn't exist as a server-side algorithm. Community summaries were migrated from a prior Neo4j run (2,113 entries) but can't be regenerated natively. The workaround (planned for Q3) is to export the graph, run Leiden in Python (leidenalg + igraph), and write the results back.

The traversal tools you can use

MCP Tool What it does Graph operation
find_callers_callees Who calls this function? What does it call? 1-hop neighbor walk
find_dependencies What files/modules does this file import? Who imports it? Multi-hop IMPORTS/USES walk
trace_execution_path Follow the call chain from a function N levels deep Recursive depth-bounded walk
trace_full_execution_chain Cross Shell β†’ Fortran β†’ Python boundaries Multi-label path walk (EXECUTES + CALLS + SOURCES)
find_env_dependencies What scripts depend on env var $X? DEPENDS_ON_ENV / EXPORTS edge walk
get_change_impact If I change this function, what breaks? Reverse-direction fan-out (blast radius)
search_architecture What subsystem does this belong to? Community membership lookup (GDS-derived, COTS only for new ones)

Layer 3: Graph-Guided Retrieval (the "RAG" fusion)

The "RAG" in GraphRAG means the system combines graph traversal with vector (semantic) search to answer questions. Here's the flow when you ask something:

Your question: "How does setuprad handle quality control for AMSU-A?"
                          β”‚
          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
          β”‚               β”‚                   β”‚
          β–Ό               β–Ό                   β–Ό
   Vector Search     Graph Walk         Code Lookup
   (embeddings)      (relationships)    (file content)
          β”‚               β”‚                   β”‚
   "docs about       "setuprad calls    "the actual
    AMSU-A QC"        qc_amsua"          source lines"
          β”‚               β”‚                   β”‚
          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                          β”‚
                          β–Ό
              Combined, context-rich answer
              with documentation + call graph + code

This is more powerful than either approach alone:

  • Vector-only (plain RAG) would find docs mentioning "AMSU-A" but wouldn't know the call relationship.
  • Graph-only would know setuprad calls qc_amsua but couldn't explain what the QC does.
  • GraphRAG gives you both: the structural relationship AND the semantic context, so the answer includes "setuprad calls qc_amsua (line 847), which performs channel-by-channel quality control for AMSU-A brightness temperatures using a gross-check + cloud-detection algorithm."

The setuprad Example β€” What the Graph Reveals

When you asked find_callers_callees("setuprad"), here's what the graph showed:

                    β”Œβ”€β”€β”€ qc_amsua (AMSU-A QC)
                    β”œβ”€β”€β”€ qc_mhs (MHS QC)
                    β”œβ”€β”€β”€ qc_atms (ATMS QC)
                    β”œβ”€β”€β”€ qc_irsnd (IR sounder QC)
                    β”œβ”€β”€β”€ qc_avhrr (AVHRR QC)
                    β”œβ”€β”€β”€ qc_ssmi (SSM/I QC)
                    β”œβ”€β”€β”€ qc_gmi (GMI QC)
     setuprad β”€β”€β”€β”€β”€β”€β”œβ”€β”€β”€ init_crtm (radiative transfer init)
     (degree 174    β”œβ”€β”€β”€ call_crtm (forward model)
      on COTS,      β”œβ”€β”€β”€ destroy_crtm (cleanup)
      193 on AWS)   β”œβ”€β”€β”€ nc_diag_init (diagnostic output)
                    β”œβ”€β”€β”€ nc_diag_write (write diagnostics)
                    β”œβ”€β”€β”€ setup_sst_retrieval (SST)
                    β”œβ”€β”€β”€ radiance_obstype_search (obs lookup)
                    β”œβ”€β”€β”€ comp_fact10 (10m wind factor)
                    β”œβ”€β”€β”€ dtime_setup / dtime_check (timing)
                    └─── ... (55 total direct neighbors)

This tells you that setuprad is the central radiance observation processing routine in GSI β€” it orchestrates quality control for every satellite instrument type, manages the CRTM (Community Radiative Transfer Model) lifecycle, and writes observation diagnostics. A change to setuprad has a blast radius of 55+ direct dependents across QC, radiative transfer, and I/O.

The degree difference (174 on COTS vs 193 on AWS) reflects the parser depth: AWS Neptune has additional DEFINES edges from fparser2's AST containment analysis (functions defined within modules), which adds to the degree count without changing the set of called/calling functions.


The Neo4j vs Neptune Gap β€” What It Means for You

As a user, you mostly won't notice the difference β€” the same tools, the same queries, the same setuprad neighbors. The gap matters in two specific scenarios:

Scenario COTS (Neo4j + GDS) AWS (Neptune)
"What subsystem does this function belong to?" Full answer β€” Leiden community detection groups functions into subsystems Partial β€” uses migrated community summaries (2,113 from Q1); can't regenerate
"Show me the most central functions in the codebase" Full answer β€” PageRank / betweenness centrality via GDS Not available β€” no server-side algorithm catalog
"Find functions similar to X by graph structure" Full answer β€” node similarity via GDS Not available
"What calls setuprad?" Identical Identical
"Trace the full execution chain from JGLOBAL_FORECAST" Identical Identical (with 30s timeout backstop)
"What depends on $FHMAX?" Identical Identical

Bottom line: For day-to-day code analysis (call graphs, dependencies, env vars, execution chains, change impact), both platforms are equivalent. For advanced graph analytics (community detection, centrality, structural similarity), COTS has capabilities that AWS currently lacks β€” planned for Q3 via an external Leiden runner.


Quick Reference: Tools to Try

What you want to know Tool to use Example
What calls this function? find_callers_callees find_callers_callees("setuprad")
What does this file import? find_dependencies find_dependencies("ush/forecast_predet.sh")
Full execution chain across languages trace_full_execution_chain trace_full_execution_chain("JGLOBAL_FORECAST")
What uses this env variable? find_env_dependencies find_env_dependencies("FHMAX")
What breaks if I change this? get_change_impact get_change_impact("setuprad")
What subsystem is this part of? search_architecture search_architecture("radiance processing")
Explain this component with full context get_code_context get_code_context("setuprad")

All tools accept tenant_id to query specific branches: tenant_id="gw_v17" for the GFS v17 development branch, or omit it for the develop baseline.


Further Reading

  • Platform-Parity-Check-AWS-vs-COTS-2026-07-22 β€” the parity check that confirmed setuprad traversal equivalence
  • Gap J in .kiro/steering/12-multi-tenant-gap-tracker.md β€” the Neo4j GDS vs Neptune community-detection gap and the Q3 remediation plan
  • docs/development/graph_node_count_scopes.md β€” why three tools report three different node counts (they're different scopes, all correct)
  • The ingestion scripts under mcp_server_python/scripts/ (ingest_fortran_graph_v8.py, ingest_shell_graph_v8.py, create_shell_fortran_bridge.py) β€” the Layer 1 parsers

Written July 22, 2026 by Kiro AI in collaboration with Terry McGuinness. Verified against live graph queries on both AWS (Neptune) and COTS (Neo4j).