Getting started (backend)
stacgis-ai is the Python agent harness. Requires Python ≥ 3.11.
Prerequisites
- Python ≥ 3.11
- uv (recommended) or
pip - An LLM endpoint — the default is Ollama
(
ollama serve+ a model such asqwen3.5:9borllama3.2)
Install
cd backend
uv sync --extra mcp # core + MCP support (needed for the example app)
# extras: --extra all (mcp + redis + prometheus)
# --extra dev (pytest, ruff)
# --extra examples
Or with pip, from a published wheel:
pip install stacgis-ai[mcp]
Run the example application
The example (entry point backend/examples/app.py) is a complete geospatial
agent harness: a FastMCP server with geospatial tools, an Ollama provider and
the SSE streaming router. The code is split into app.py (FastAPI entry
point), agent.py (loop factory + system prompt) and tools/ (one tool
module per use case).
uv run --extra mcp python run_example.py
- Swagger UI — http://localhost:8000/docs
- Root — http://localhost:8000/
Point it at your Ollama instance via environment variables:
export OLLAMA_BASE_URL=http://localhost:11434 # ← your Ollama host
export OLLAMA_MODEL=qwen3.5:9b # ← any tool-capable model
Try it
# Blocking
curl -X POST http://localhost:8000/api/v1/agent/chat \
-H 'Content-Type: application/json' \
-d '{"message": "Find 5 cafes near London and buffer them by 200 m"}'
# Streaming (Server-Sent Events)
curl -N -X POST http://localhost:8000/api/v1/agent/stream \
-F 'message=Calculate the route from Paris to Lyon'
The example tools you can ask about:
| Tool | Kind | Purpose |
|---|---|---|
geocode_address | MCP (Nominatim/Photon) | Address → lat/lon |
reverse_geocode | MCP (Nominatim/Photon) | lat/lon → address |
calculate_route | MCP (OSRM) | Driving distance/duration + GeoJSON geometry |
query_osm_features | MCP (Overpass) | POIs (cafés, schools, …) in a radius |
generate_buffer_geojson | MCP | Circular buffer polygon (GeoJSON) around a point |
calculate_area | MCP | Rectangle area (length × width) |
get_weather | MCP (demo stub) | Placeholder weather tool |
list_map_layers | MCP | List the demo map project's layers |
delete_map_layer | MCP — approval-gated | Delete a demo layer; the loop pauses until the user approves/rejects |
zoom_to_location | @frontend_tool (zoom_to) | Fly the map to a coordinate |
draw_geometry | @frontend_tool (draw_geometry) | Draw a GeoJSON overlay on the map |
show_toast | @frontend_tool (show_toast) | Toast notification in the UI |
select_location_on_map | @frontend_tool (select_location) | Open the map location picker; the user's pick is sent back to the agent |
The example also demonstrates:
- Human-in-the-loop —
MCPClient(server=mcp_server, approval_tools={"delete_map_layer"})gates a destructive tool; the frontend shows the approval modal with an auto-reject countdown. - Location pick round-trip — the agent calls
select_location_on_map, the map picker opens, and the user's confirmed selection is sent back as a new chat message. - Suggested prompts — the demo welcome screen's starter prompts are tuned
to the tool flows (see
frontend/example/src/data/prompts.ts).
Build your own agent (minimal)
import asyncio
from stacgis_ai import AgentLoop, AgentLoopConfig, MCPClient, OllamaProvider, OllamaConfig
from mcp.server.fastmcp import FastMCP
server = FastMCP("my-tools")
@server.tool()
def distance_km(lat1: float, lon1: float, lat2: float, lon2: float) -> str:
"""Great-circle distance between two WGS-84 points in km."""
import math
# ... your haversine implementation ...
async def main():
client = MCPClient(server=server)
await client.connect()
llm = OllamaProvider(OllamaConfig(base_url="http://localhost:11434",
model="qwen3.5:9b"))
loop = AgentLoop(mcp_client=client, llm=llm,
config=AgentLoopConfig(max_steps=8))
await loop.initialize()
result = await loop.run("How far is it from Berlin to Prague?")
print(result.response)
print(result.tools_used)
asyncio.run(main())
Run the tests
uv run --extra dev python -m pytest