Graph Server
The Graph Server is an MCP server that builds a structural graph of your code and makes it queryable by any MCP client. No LLM inside — pure deterministic AST analysis.
Getting Started
No Docker and no database server. The default backend is GraphQLite — an embedded SQLite graph that persists to ~/.codesteward/graph.db between sessions. Requires uv (Python 3.12+).
1. Set up once, machine-wide
Auto-detects your installed AI tools (Claude Code, Cursor, Cline, Codex CLI, Gemini CLI), registers the MCP server globally, and merges workflow instructions into your existing config files — nothing is overwritten.
uvx --from "codesteward-mcp[graph-all,graphqlite]" codesteward-mcp setup To remove everything again, run the same command with --uninstall.
2. Open any repo and ask
No .mcp.json, no per-project config, no repeated setup. The agent builds the graph on first use and queries it from then on.
# Any repository on your machine
cd /path/to/your/project
claude # or: cursor, cline, codex, gemini
# The agent calls these for you:
graph_status(repo_id="my-project")
graph_rebuild(repo_path="/path/to/your/project", repo_id="my-project")
codebase_graph_query(query_type="referential", query="authenticate", repo_id="my-project")Manual setup
If you would rather configure it yourself, add the server to your MCP client config directly. uvx downloads and caches the package on first run.
{
"mcpServers": {
"codesteward-graph": {
"type": "stdio",
"command": "uvx",
"args": [
"--from", "codesteward-mcp[graph-all,graphqlite]",
"codesteward-mcp", "--transport", "stdio"
]
}
}
}Note: Claude Code requires "type": "stdio" in the server config. Cursor and Cline do not need it.
MCP Tools
graph_rebuildBuild or incrementally update the structural graph for any codebase. Parses 14 languages with tree-sitter. Supports full rebuild or fast incremental mode for changed files only.
repo_pathstringAbsolute path to the repository rootchanged_filesstring[] | nullIncremental mode: re-parse only these files. null = full rebuild.tenant_idstringNamespace for multi-repo isolationawait graph_rebuild({
repo_path: "/workspace",
changed_files: null // null = full rebuild
})codebase_graph_queryRun any Cypher query against the structural graph. Get call chains, inheritance trees, blast radius, or auth coverage — all deterministic, all instant.
cypherstringCypher query to execute against the graphrepo_idstringTarget repository identifiertenant_idstringTenant namespaceawait codebase_graph_query({
cypher: `
MATCH (caller)-[:CALLS*1..5]->(fn:Function)
WHERE fn.name = "processPayment"
RETURN caller.name, caller.file
ORDER BY caller.file
`,
repo_id: "payments-service"
})graph_augmentAdd agent-inferred edges to the graph — relationships you detected that the parser cannot derive statically. Always confidence < 1.0. Never used by the policy engine.
edgesAugmentEdge[]Inferred edges with confidence scores (0.0–0.99)repo_idstringTarget repositorytenant_idstringTenant namespaceawait graph_augment({
edges: [{
source_id: "fn::validatePayment",
edge_type: "calls",
target_name: "stripe.charges.create",
confidence: 0.85,
rationale: "Dynamic dispatch via SDK client"
}],
repo_id: "payments-service"
})taint_analysisTrace how untrusted input propagates to dangerous sinks across the codebase. Writes TAINT_FLOW edges to the graph. Requires codesteward-taint to be installed on PATH.
repo_idstringRepository identifierframeworksstring[] | nullFramework catalog names — omit to auto-detectlevel1 | 2 | 3Maximum analysis depth (default: 3)await taint_analysis({
repo_id: "payments-service",
frameworks: ["fastapi"], // or null to auto-detect
level: 3
})Edge Types
IMPORTSModule and package import relationshipsdeterministicCALLSFunction and method invocations, cross-file resolveddeterministicEXTENDSClass inheritance and interface implementationdeterministicGUARDED_BYAuth guards on the function itself — decorators, annotations, Depends() injectiondeterministicPROTECTED_BYRouter/middleware scope guards — FastAPI APIRouter(dependencies=[...]) and Express router.use(middleware)deterministicDEPENDS_ONPackage-level dependencies from manifest filesdeterministicTAINT_FLOWTaint paths from user-controlled sources to dangerous sinks — written by codesteward-taintdeterministicCUSTOMAgent-inferred edges with explicit confidence scoresinferredSupported Languages
Every supported language contributes the same structural edges to the same graph. Polyglot monorepos get a single queryable model.
Cypher Query Examples
MATCH (caller)-[:CALLS*1..5]->(fn:Function {name: "processPayment"})
RETURN caller.name, caller.file, caller.line_start
ORDER BY caller.fileMATCH (fn:Function)
WHERE fn.name =~ "(?i).*(route|handler|controller).*"
AND NOT (fn)-[:CALLS|GUARDED_BY]->()
RETURN fn.name, fn.file, fn.line_startMATCH (child)-[:EXTENDS*1..]->(base:Class {name: "BaseController"})
RETURN child.name, child.file, child.language
ORDER BY child.language, child.fileBackends
GRAPH_BACKEND defaults to auto: Neo4j if a password is set, JanusGraph if its URL is changed, otherwise GraphQLite. For a laptop, the default needs no configuration at all.
Embedded SQLite. No external service, no server process. Persists to ~/.codesteward/graph.db — override with GRAPHQLITE_DB_PATH.
A shared, persistent graph for teams and CI. Requires a running Neo4j 5+ server; setting the password is what activates it.
NEO4J_URI=bolt://your-host:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=your-passwordFor large-scale deployments. Requires a running JanusGraph 1.0+ server. Raw query passthrough uses Gremlin instead of Cypher.
JANUSGRAPH_URL=ws://localhost:8182/gremlinSupported MCP Clients
The setup command auto-detects and configures these. The table shows where each tool keeps its config if you are wiring it up by hand.
Claude Code~/.claude.json — under mcpServersCursor~/.cursor/mcp.jsonClinecline_mcp_settings.json in VS Code globalStorageCodex CLI~/.codex/config.yaml — under mcp_serversGemini CLI~/.gemini/settings.json — under mcpServers Any other MCP client works too — point it at the same stdio command, or run the server over sse / http on port 3000 via TRANSPORT.
Audit Proxy
A zero-config reverse proxy that intercepts LLM API traffic and writes structured audit logs to ClickHouse — thinking blocks, narration, and tool calls.
Setup
Prerequisites: Go 1.22+ and a running ClickHouse instance.
1. Apply the schema
clickhouse-client --multiquery < migrations/001_initial.sql2. Start the proxy
CLICKHOUSE_DSN=clickhouse://localhost:9000/audit \
go run ./cmd/proxy3. Point your agent at the proxy
ANTHROPIC_BASE_URL=http://localhost:8080OPENAI_BASE_URL=http://localhost:8080/v1GEMINI_API_BASE_URL=http://localhost:8080AUDIT_PROJECT=myorg/myrepo \
AUDIT_BRANCH=feature/refactor \
CLICKHOUSE_DSN=clickhouse://localhost:9000/audit \
go run ./cmd/proxyConfiguration
All configuration via environment variables. No config files required.
PROXY_ADDR127.0.0.1:8080Listen addressCLICKHOUSE_DSN(required)ClickHouse connection stringCLICKHOUSE_DBauditDatabase nameBATCH_SIZE100Events per flushBATCH_INTERVAL1sMax time between flushesAUDIT_PROJECT""Repository / project name tagged on every rowAUDIT_BRANCHauto-detectedGit branch; falls back to git rev-parse at startupUPSTREAM_PROXY(none)Upstream proxy URL (overrides HTTPS_PROXY)LOG_LEVELinfodebug | info | warn | errorOTEL_EXPORTER_OTLP_ENDPOINT(none)Activates OTel traces when set (e.g. http://localhost:4318)Proxy Chaining
Audit Proxy slots into existing proxy chains. Set UPSTREAM_PROXY and it forwards through your existing proxy while capturing the full audit trail.
Agent -> [codesteward-audit-proxy :8080] -> [Your Existing Proxy] -> LLM APIUPSTREAM_PROXY=http://proxy.corp.internal:3128 \
go run ./cmd/proxyUPSTREAM_PROXY=https://api.portkey.ai \
go run ./cmd/proxy If UPSTREAM_PROXY is not set, the proxy checks standard HTTPS_PROXY / HTTP_PROXY environment variables — companies with a system-wide proxy get chaining automatically.
ClickHouse Query Examples
SELECT session_id, ts, tool_name, tool_input
FROM audit_events
WHERE ts > now() - INTERVAL 7 DAY
AND JSONExtractString(tool_input, 'file_path') LIKE '%/.env%'
ORDER BY ts DESC;SELECT project, branch, agent, tool_name, count() AS calls
FROM audit_events
WHERE toDate(ts) = today()
GROUP BY project, branch, agent, tool_name
ORDER BY calls DESC;IDE Plugins
The Codesteward IDE plugins are the recommended way to set up the audit proxy. Install the plugin from the official marketplace and it handles everything — no manual environment variables needed.
Installation
Search "Codesteward Audit Proxy" in the Extensions Marketplace
# or
ext install bitkaio.codesteward-audit-proxySearch "Codesteward Audit Proxy" in the JetBrains Marketplace
# Settings → Plugins → MarketplaceWhat the plugin does
- Sets the proxy URL for each detected agent (
ANTHROPIC_BASE_URL,OPENAI_BASE_URL,GEMINI_API_BASE_URL) - Injects identity headers automatically: user (from git), project, branch, session ID, and team
- Tracks branch changes in real time — headers update when you switch branches
- Status bar indicator shows proxy state and health
- Header inspector panel lets you see exactly what metadata is being sent
Supported agents
Claude Code CLIANTHROPIC_BASE_URLANTHROPIC_CUSTOM_HEADERSCodex CLIOPENAI_BASE_URL~/.codex/config.tomlGemini CLIGEMINI_API_BASE_URLGEMINI_CLI_CUSTOM_HEADERSClineProvider settingsExtension settingsAiderANTHROPIC_BASE_URL--extra-headersContinueProvider configConfig headersTeam configuration
Commit a .codesteward.json to your repository root for shared team settings. The plugin reads it automatically — no per-developer setup needed.
{
"proxy": {
"url": "https://audit.corp.internal:8443",
"enabled": true
},
"identity": {
"team": "platform-engineering",
"project": "payment-service"
}
}Session Summarizer
Automatically turns raw audit proxy events into structured session summaries — key decisions, files changed, bugs resolved, and security-relevant actions. Runs locally with Ollama or via hosted LLMs (OpenAI, Anthropic).
Setup
Prerequisites: ClickHouse with audit proxy events, and Docker.
1. Apply the migrations
clickhouse-client --host <host> --query "$(cat migrations/001_session_summaries.sql)"
clickhouse-client --host <host> --query "$(cat migrations/002_session_chunk_extractions.sql)"2. Start the summarizer
# Docker Compose (includes Ollama sidecar)
docker compose up -d
# The model is auto-pulled on first startup
# Summaries appear in audit.session_summariesRunning without Docker
ollama pull phi3:mini
uv sync
CLICKHOUSE_URL=http://localhost:8123 \
uv run python -m summarizer.mainuv sync --extra openai
LLM_PROVIDER=openai \
SUMMARIZER_MODEL=gpt-4o-mini \
OPENAI_API_KEY=sk-... \
CLICKHOUSE_URL=http://localhost:8123 \
uv run python -m summarizer.mainuv sync --extra anthropic
LLM_PROVIDER=anthropic \
SUMMARIZER_MODEL=claude-haiku-4-5-20251001 \
ANTHROPIC_API_KEY=sk-ant-... \
CLICKHOUSE_URL=http://localhost:8123 \
uv run python -m summarizer.mainConfiguration
All configuration via environment variables. No config files required.
LLM_PROVIDERollamaLLM backend: ollama, openai, or anthropicSUMMARIZER_MODELphi3:miniModel name (provider-specific)CLICKHOUSE_URLhttp://localhost:8123ClickHouse HTTP interface URLCLICKHOUSE_DATABASEauditDatabase containing audit_events and session_summariesOLLAMA_URLhttp://localhost:11434Ollama API endpoint (ollama provider only)RUN_MODEpollpoll (continuous loop) or once (single cycle)POLL_INTERVAL_SECONDS300Seconds between polling cyclesSESSION_COOLDOWN_MINUTES30Minutes of inactivity before summarizationLOOKBACK_HOURS168How far back to look for unsummarized sessions (default: 7 days)BATCH_SIZE10Max sessions to process per poll cycleSUMMARIZER_VERSIONv1Bump to force re-summarization of all sessionsCONTEXT_MAX_TOKENS4096Model context window size in tokensSESSION_LANGUAGEenISO 639-1 code of session content languageLOG_LEVELinfoLogging level: debug, info, warning, errorRun modes
poll(default) — continuous loop. Processes pending sessions, sleeps, repeats. Use for Docker / systemd.once— single cycle, then exits. Use for cron jobs or Kubernetes CronJobs.
Version-gated re-summarization
Changing SUMMARIZER_VERSION (e.g., from v1 to v2) causes all sessions within the lookback window to be re-summarized. Useful after changing prompts, switching models, or adding new fact categories. Old summaries are preserved as revisions.
LLM Providers
Ollama (default)
Local inference. The model is auto-pulled on startup if not available. Recommended CPU-friendly models:
phi3:mini(3.8B params, ~2.3 GB) — good quality-to-size ratiomistral(7B params, ~4.1 GB) — better quality, needs more RAMllama3:8b(8B params, ~4.7 GB) — strong general performance
OpenAI
Set LLM_PROVIDER=openai and OPENAI_API_KEY. Install the SDK: uv sync --extra openai. Supports custom base URLs via OPENAI_BASE_URL for Azure OpenAI or local vLLM. Recommended: gpt-4o-mini, gpt-4o.
Anthropic
Set LLM_PROVIDER=anthropic and ANTHROPIC_API_KEY. Install the SDK: uv sync --extra anthropic. Recommended: claude-haiku-4-5-20251001, claude-sonnet-4-20250514.
Processing Pipeline
Every poll cycle, the summarizer discovers sessions with events in the lookback window that have been inactive for the cooldown period and haven't been summarized with the current version.
Single-pass (short sessions)
When events fit within the token budget, the summarizer compresses them into a timeline and sends them to the LLM in one call. The response is parsed as YAML containing the summary, key decisions, and tags.
Chunked extract-merge-synthesize (long sessions)
For sessions that exceed the budget, events are split into chunks at natural boundaries (time gaps > 5 minutes). Each chunk is sent to the LLM to extract 11 categories of structured facts. Facts are then merged deterministically (no LLM, no information loss) before a final synthesis pass produces the summary.
Token budget
The character budget is derived from the model's token limit with language-aware ratios (English ~4.0 chars/token, German ~3.0, Chinese ~1.5). 28 languages are supported. Set CONTEXT_MAX_CHARS to override the calculation entirely.
Security
- Secrets are stripped via regex before any text reaches the LLM
- Tool inputs (which may contain file contents) are never sent — only extracted file paths
- API response bodies are excluded from context
Prompt Optimization
A closed-loop system that evaluates session summary quality, generates improved prompts, validates them against real data, and promotes winners — automatically or with human review. Optional add-on to the Session Summarizer.
Evaluation
The prompt evaluator is a separate batch service that uses a strong LLM (Claude or GPT-4o) to score summarizer outputs. It runs periodically (daily or weekly) on a configurable sample size.
Evaluation levels
- Chunk-level — for each chunk extraction: did it capture all visible facts? Are they accurate? Did the model hallucinate? Each of the 11 fact categories is scored independently.
- Summary-level — does the final summary accurately cover key activities? Are decisions complete and well-stated? Are tags meaningful?
Prompt provenance
Every summary and extraction is tagged with the exact prompt version (prompt_id), a hash of the prompt text (prompt_hash), and a hash of the input context (input_context_hash). This creates full traceability from output to instructions.
Evaluation data collection
When enabled, the summarizer stores the exact input context sent to the LLM alongside the output. The evaluator needs this to see the same context the original model saw — not a re-rendered version that might differ due to code changes or configuration drift.
Auto-Optimization
When enabled, the evaluator goes beyond scoring. It analyzes patterns across all scored outputs, generates a revised prompt, validates it, and promotes it if it outperforms the current one.
Optimization pipeline
- Analyze — identify which fact categories consistently underperform and what high-scoring outputs have in common
- Generate — feed the analysis to the strong model with the current prompt, low-scoring examples with feedback, and high-scoring examples as references
- Validate — re-process a sample of real sessions with the candidate prompt. Score the new outputs with the same evaluator
- Promote or reject — promote only if the candidate wins by a meaningful margin. Marginal gains are flagged for human review. Worse results are rejected outright
Safety guardrails
- Validation gate — no prompt goes live without proving it's better than what it replaces
- Auto-rollback — after promotion, outputs are continuously scored. If quality drops below historical baselines, the previous prompt is restored automatically
- Lineage limits — configurable depth limit on automatic iterations. The system pauses for human review after N generations, preventing unbounded prompt drift
- Full audit trail — every prompt links to its parent. Every optimization decision is logged with scores and reasoning
Operation Modes
Automatic (hands-off)
The system manages the full loop: evaluate, generate, validate, promote, monitor, rollback. Best for teams that want continuous improvement without dedicating engineering time to prompt maintenance.
Manual (human-in-the-loop)
The evaluator runs in evaluation-only mode, providing quality scores and detailed feedback. Engineers write improved prompts informed by the data. The system handles validation and promotion. Best for teams that prefer direct control over prompt changes.
Cost considerations
- Summarization — unchanged, still uses the same small/local model
- Evaluation — strong model on configurable samples (default 20 outputs), periodic schedule. Cost is bounded
- Optimization — one candidate prompt per run (single LLM call)
- Validation — re-processes the same sample with the candidate, bounded by sample size
Taint Analysis
Traces how untrusted input propagates through your codebase to dangerous operations — SQL sinks, shell commands, file I/O — and writes findings as TAINT_FLOW edges.
Setup
Taint analysis builds on the structural graph. Build the graph first, then run the analyzer.
# 1. Build the graph
codesteward-mcp graph_rebuild --repo-path /path/to/repo
# 2. Run taint analysis
codesteward-taint \
--repo-id my-repo \
--repo-path /path/to/repo \
--frameworks fastapi \
--level 3Output edges in the graph
(source:Function)-[:TAINT_FLOW {
cwe: "CWE-89",
hops: 3,
level: 3,
sanitized: false
}]->(sink:Function)CLI Flags
--repo-idstring(required)Repository identifier--repo-pathstring(required)Absolute path to repository on disk--neo4j-uristringbolt://localhost:7687Neo4j bolt URI--neo4j-userstringneo4jNeo4j username--neo4j-passwordstringNeo4j password--tenant-idstringlocalTenant namespace--frameworksstringauto-detectComma-separated catalog names, e.g. fastapi,express--max-hopsint8Maximum call depth for Level 1 traversal--levelint3Maximum analysis level: 1, 2, or 3--persist-cfgboolfalseWrite CFG nodes to Neo4j for path-level queries--include-safeboolfalseInclude sanitized paths in output--catalog-dirstringPath to additional custom catalog YAML files--no-builtin-catalogsboolfalseDisable built-in catalogs; use only --catalog-dir--outputstringyamlOutput format: yaml | json | text--stubboolfalseAnalyze without writing results to Neo4j--log-levelstringinfodebug | info | warn | errorCustom Catalogs
Add custom source/sink definitions by creating YAML files in a catalog directory and passing --catalog-dir.
# Example custom catalog: my-catalogs/internal-api.yaml
sources:
- name: get_user_input
type: parameter
language: python
cwe: CWE-89
sinks:
- name: execute_raw_query
type: function_call
language: python
cwe: CWE-89codesteward-taint \
--repo-id my-repo \
--repo-path /path/to/repo \
--catalog-dir ./my-catalogs