Natural language agent that answers questions about camera feeds, encoder parameters, and decoder parameters.
Built with:
- Typed DataStore for CSV and JSON inputs
- MCP tools for all data operations
- LangGraph plan that always calls tools
- FastAPI endpoint that returns answer plus evidence
- Zero LLM usage by default to keep cost near zero
- Base: NL query app for feeds, encoder, decoder ✅
- Improvement 1: LangGraph for orchestration ✅
- Improvement 2: Data operations as MCP tools ✅
- Improvement 3: Equivalent run inside a code-gen IDE ✅ Cursor MCP instructions included
canyoncode_agent_step1_scaffold/
├─ app/
│ ├─ graph.py # LangGraph plan
│ └─ main.py # FastAPI app, POST /query
├─ datastore/
│ ├─ loader.py # DataStore, schema validation, typed loading
│ └─ models.py
├─ scripts/
│ ├─ smoke_test.py # Step 1 sanity checks
│ ├─ tool_smoke.py # Call tools without MCP
│ └─ demo_scenarios.py # Prints 4 demo answers in one run
├─ tools_mcp/
│ ├─ schemas.py # Pydantic request and response models
│ ├─ tools.py # MCP-style tools
│ └─ mcp_server.py # MCP stdio server for IDEs like Cursor
├─ util/
│ └─ ranking.py # Scoring with configurable weights
├─ encoder_schema.json
├─ decoder_schema.json
├─ encoder_params.json
├─ decoder_params.json
├─ Table_defs_v2.csv
└─ Table_feeds_v2.csv
macOS or Linux:
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtpython scripts/smoke_test.pyExpected:
- Loaded feeds rows: 100
- Unique FEED_ID: 100
- Encoder and decoder params printed
python scripts/tool_smoke.pyuvicorn app.main:app --reloadHealth:
curl -s http://127.0.0.1:8000/healthRun these in a second terminal while the server is running.
- Ranking with clarity
curl -s -X POST http://127.0.0.1:8000/query -H "Content-Type: application/json" \
-d '{"question":"Top 5 feeds with best clarity in PAC"}' | jq- Filtered listing
curl -s -X POST http://127.0.0.1:8000/query -H "Content-Type: application/json" \
-d '{"question":"List feeds at least 1080p and 30 fps using H265 in EUR"}' | jq- Parameters introspection
curl -s -X POST http://127.0.0.1:8000/query -H "Content-Type: application/json" \
-d '{"question":"Show decoder parameters"}' | jq- Constraint check
curl -s -X POST http://127.0.0.1:8000/query -H "Content-Type: application/json" \
-d '{"question":"Check top feeds in PAC for constraints"}' | jqOptional smoothness demo:
curl -s -X POST http://127.0.0.1:8000/query -H "Content-Type: application/json" \
-d '{"question":"Top 5 feeds with smooth video in CONUS"}' | jq- POST /query
Request:
{"question":"Top 5 feeds with best clarity in PAC"}Response:
{
"answer": "Top feeds by clarity matching {...}\n- FD-... | ...",
"evidence": {
"filters": {"theater":"PAC"},
"weights": {"resolution":0.6,"fps":0.2,"codec":0.2},
"feed_ids": ["FD-...","FD-..."],
"scores": [{"feed_id":"FD-...","score":1.0}]
}
}- GET /health returns {"status":"ok"}
Use tools inside Cursor via MCP.
Create .cursor/mcp.json in the repo root with paths:
{
"mcpServers": {
"canyoncode-tools": {
"command": ".venv/bin/python",
"args": ["-m", "tools_mcp.mcp_server"],
"cwd": "."
}
}
}Example tool inputs in Cursor chat:
{"theater":"PAC","limit":3}{"theater":"EUR","top_k":5}{}{"phrase":"best clarity"}{"feed_ids":["FD-LLF3SB","FD-8D150S"]}Minimal pytest examples in tests/test_basic.py:
pip install pytest
pytest -q- DataStore loads CSV and JSON, validates against the provided schemas, normalizes types.
- MCP tools provide a narrow surface: list, rank, params, summarize, explain term, sanity check.
- LangGraph parses intent and filters, optionally uses explain_term to set weights for clarity and smooth, then calls tools and formats the answer.
- FastAPI exposes POST /query that returns answer plus evidence for traceability.
- FEED_ID is unique
- FRRATE is frames per second
- RES_W and RES_H are pixel counts
- CODEC values normalized to upper case
- Clarity score combines resolution, frame rate, and a codec bonus
- Constraint checker uses decoder caps and a conservative codec allowlist
- Latency mapping is a placeholder weight shift