The
.mdfiles in this repo are runtime prompt artifacts, not documentation.server.pyloads them at startup and injects their content into MCP tool descriptions and prompts. Editing them changes what the agent is instructed to do.README.mdis the only human-facing documentation.
This repo uses GitHub Flow: all changes go through a branch + PR, never committed directly to main. main has branch protection enforced — direct pushes are rejected.
- Create a branch for your change (
git switch -c <branch>) before the first commit. Never commit onmainand open a PR afterward — the squash-merge will leave localmainpermanently diverged fromorigin/main. - Open a PR against
main - Merge via the GitHub UI (squash merge preferred)
This repo squash-merges, so the merged commit on origin/main has a different SHA than the local feature commits. Clean up with reset, not pull:
git switch main
git fetch origin
git reset --hard origin/main
git branch -D <feature-branch>
Do not git pull on main when local main has commits matching the just-merged PR — pull will create a merge commit because the squash changed the SHA. If you must pull, use git pull --ff-only so divergence fails loudly instead of silently merging.
The MCP server runs on the NRP Nautilus Kubernetes cluster.
- Prod:
https://duckdb-mcp.nrp-nautilus.io— 2 replicas,k8s/deployment.yaml - Dev:
https://dev-duckdb-mcp.nrp-nautilus.io— 1 replica,k8s/dev-deployment.yaml - Resources: 16 Gi RAM requested, up to 160 Gi / 16 CPU per pod
- STAC catalog:
https://s3-west.nrp-nautilus.io/public-data/stac/catalog.json(set viaSTAC_CATALOG_URLenv var) - Ingress: HAProxy with CORS enabled, 10-minute query timeout, 1-hour SSE tunnel timeout
Merge to main → redeploy dev only:
kubectl apply -f k8s/dev-deployment.yaml
kubectl rollout restart deployment/dev-duckdb-mcp -n biodiversity
Tag a release → redeploy prod:
kubectl apply -f k8s/deployment.yaml
kubectl rollout restart deployment/duckdb-mcp -n biodiversity
kubectl apply must precede rollout restart — a git push alone does not update the cluster.
This project uses two distinct, asynchronous AI agent processes. Do not confuse them.
A lightweight open-source LLM serves user requests in real time via the MCP tool. It has no memory between requests. Its only context is what is injected at call time.
Files it reads (injected as prompts):
query-setup.md— required DuckDB setup SQL, must run before every queryquery-optimization.md— short, actionable query-writing rulesdatasets.md— STAC catalog summary, dataset paths, column schemasassistant-role.md— role and response style instructions
Rules for editing these files:
- Write for a small, stateless model with limited context
- Instructions must be short, concrete, and unambiguous
- No debugging advice, no explanation of DuckDB internals, no "check X before Y"
- No meta-commentary about why rules exist — just the rules
- Every example must be a correct, copy-pasteable query
A separate, asynchronous process uses Claude to review logs from real user sessions, identify slow or incorrect queries, diagnose root causes, and update the small LLM's instructions.
Files it reads:
- Real-time query logs and timing data from production
optimization-design-notes.md— developer notes on DuckDB behavior and benchmarksAGENTS.md— this file
Files it writes:
query-optimization.md,query-setup.md— updated instructions for the small LLMoptimization-design-notes.md— updated technical findings
Rules for this process:
- When a query is slow, check whether it follows all rules in
query-optimization.mdbefore diagnosing infrastructure, DuckDB behavior, or S3 limitations - Benchmark queries used to test a rule must themselves follow that rule (e.g., a benchmark testing "h0 in join" must actually have h0 in the join)
- Do not edit
query-optimization.mdwith debugging checklists, developer notes, or explanations of DuckDB internals — that content belongs inoptimization-design-notes.md - A claim that a rule in
query-optimization.mdis wrong requires a correctly structured benchmark that violates only that rule and nothing else
In March 2026, a small LLM generated a query omitting h0 from a join (violating the
rule in query-optimization.md). The resulting slow query was diagnosed as "S3 DPP
is broken" rather than "the query violated the h0-in-join rule." Hours of investigation
followed, producing optimization-design-notes.md content that incorrectly characterized
the rule as wrong. A subsequent Claude session read those notes and tried to remove the
correct rule from query-optimization.md.
The fix is this document: keep the two processes and their files clearly separated.