Skip to main content

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 as qwen3.5:9b or llama3.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

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:

ToolKindPurpose
geocode_addressMCP (Nominatim/Photon)Address → lat/lon
reverse_geocodeMCP (Nominatim/Photon)lat/lon → address
calculate_routeMCP (OSRM)Driving distance/duration + GeoJSON geometry
query_osm_featuresMCP (Overpass)POIs (cafés, schools, …) in a radius
generate_buffer_geojsonMCPCircular buffer polygon (GeoJSON) around a point
calculate_areaMCPRectangle area (length × width)
get_weatherMCP (demo stub)Placeholder weather tool
list_map_layersMCPList the demo map project's layers
delete_map_layerMCP — approval-gatedDelete 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-loopMCPClient(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