A GIS-enabled conversational application with Flutter desktop client, agentic API server, and PostgreSQL/PostGIS persistence.
Magma Soup combines natural language interaction with geographic information system (GIS) capabilities. Users can ask questions about locations, distances, and geographic data through a conversational interface.
┌─────────────────┐
│ Flutter Client │ Desktop UI with map visualization
│ (macOS) │
└────────┬────────┘
│ HTTP/SSE
▼
┌─────────────────┐
│ API Server │ Agentic loop orchestration
│ (Node.js) │ Claude API integration
└────┬───────┬────┘
│ │
│ └──────┐
│ │ HTTP
▼ ▼
┌──────────┐ ┌──────────┐
│PostgreSQL│ │ MCP │ GIS tools
│ PostGIS │ │ Server │ (geocoding, distance, etc.)
└──────────┘ └──────────┘
-
Flutter Client (
flutter_client/)- Desktop application (macOS)
- Two-pane UI: chat + results/map
- BLoC state management
- Solarized Light theme
-
API Server (
api_server/)- Node.js + Express + TypeScript
- Agentic loop with tool use
- Claude Sonnet 4.5 integration
- Full conversation history sent to LLM for context
- Map-aware prompts (LLM sees current features)
- Local tools (feature removal) and MCP tools
- PostgreSQL persistence
- SSE streaming for real-time updates
-
MCP Server (
mcp_server/)- Model Context Protocol server
- GIS tool implementations
- TypeScript SDK
-
Database (PostgreSQL + PostGIS)
- Conversation history
- Unified message storage (conversation + LLM trace)
- Geographic feature persistence
Run the entire stack with one command:
# Copy and configure environment variables
cp .env.example .env
# Edit .env and add your ANTHROPIC_API_KEY
# Start all services
docker-compose up --build
# Services will be available at:
# - API Server: http://localhost:3001
# - MCP Server: http://localhost:3000
# - PostgreSQL: localhost:5432The first time you run this, Docker will:
- Pull the PostGIS image
- Build the MCP and API server images
- Run database migrations automatically
- Start all services with health checks
- Node.js 20+
- Flutter SDK (for client development)
- Docker (for PostgreSQL)
- Anthropic API key
docker run -d \
--name magma-postgis \
-e POSTGRES_DB=magma_soup \
-e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD=postgres \
-p 5432:5432 \
postgis/postgis:15-3.4cd mcp_server
npm install
npm run dev
# Server runs on http://localhost:3000cd api_server
npm install
# Configure environment
cp .env.example .env
# Edit .env and set ANTHROPIC_API_KEY
# Run database migrations
npm run migrate
# Start development server
npm run dev
# Server runs on http://localhost:3001cd flutter_client
flutter pub get
flutter runAsk natural language questions about geographic data:
- "What are the coordinates of San Francisco?"
- "What's the distance between NYC and LA?"
- "Find the address for these coordinates: 37.7749, -122.4194"
- "Add Portland to the map"
- "Remove San Francisco from the map"
Server-Sent Events (SSE) provide live updates:
- Tool execution progress
- LLM responses
- Geographic features extracted
- Error notifications
All conversations are stored in PostgreSQL:
- User/assistant conversation messages
- Complete LLM interaction trace (prompts, responses, tool calls, results, errors)
- All messages stored in single unified table with type discriminator
- Geographic features with PostGIS
Geographic features are automatically extracted and displayed on an interactive map. Features can be explicitly added or removed through natural language commands. The LLM is aware of all features currently on the map and can reference them in responses.
# Create new conversation
POST /conversations
{
"title": "My Conversation"
}
# List all conversations
GET /conversations?limit=50&offset=0
# Get conversation with full history
GET /conversations/:id
# Send message (SSE stream)
POST /conversations/:id/messages
{
"message": "What is the distance between SF and LA?"
}GET /healthCreate .env in the root directory:
# Required
ANTHROPIC_API_KEY=sk-ant-...
# Database (defaults shown)
DB_HOST=localhost
DB_PORT=5432
DB_NAME=magma_soup
DB_USER=postgres
DB_PASSWORD=postgres
# Servers (defaults shown)
MCP_SERVER_URL=http://localhost:3000
API_SERVER_PORT=3001conversations
- Stores conversation metadata
- UUID primary key
- Created/updated timestamps
- Optional title and metadata JSON
messages
- All message types in one table with type discriminator
- Types: user, assistant, user_prompt, llm_response, tool_call, tool_result, tool_error
- References conversation
- Sequential ordering across all message types
- Content stored as JSONB
- Supports both user/assistant conversation and complete LLM interaction trace
geo_features
- PostGIS geometry storage
- Extracted from tool results
- Point, Line, Polygon support
- WGS84 coordinate system (SRID 4326)
Migrations run automatically on startup. To run manually:
cd api_server
npm run migrateMigration files are in api_server/migrations/:
001_initial_schema.sql- Core tables002_geo_features.sql- PostGIS and spatial features003_unify_messages.sql- Consolidates messages and LLM history into single table
# Connect to database
docker exec -it magma-soup-postgis psql -U postgres -d magma_soup
# View tables
\dt
# Query conversations
SELECT * FROM conversations;
# Query with spatial data
SELECT id, feature_type, ST_AsText(geometry)
FROM geo_features;# Create conversation
curl -X POST http://localhost:3001/conversations \
-H "Content-Type: application/json" \
-d '{"title": "Test"}'
# Send message (save conversation ID from above)
curl -N -X POST http://localhost:3001/conversations/<UUID>/messages \
-H "Content-Type: application/json" \
-d '{"message": "What are the coordinates of Tokyo?"}'
# Get conversation history
curl http://localhost:3001/conversations/<UUID>
# List all conversations
curl http://localhost:3001/conversations# Health check
curl http://localhost:3000/health
# List available tools
curl http://localhost:3000/tools# Build specific service
docker-compose build api_server
# Rebuild without cache
docker-compose build --no-cache api_server
# View logs
docker-compose logs -f api_server
# Stop all services
docker-compose down
# Stop and remove volumes (deletes database!)
docker-compose down -vPostgreSQL data is persisted in a Docker volume:
# List volumes
docker volume ls | grep magma
# Inspect volume
docker volume inspect magma_soup_postgis_data
# Remove volume (deletes all data!)
docker volume rm magma_soup_postgis_datamagma_soup/
├── api_server/ # Node.js API server
│ ├── src/
│ │ ├── config/ # Database configuration
│ │ ├── models/ # Database models
│ │ ├── routes/ # Express routes
│ │ ├── services/ # Business logic
│ │ ├── types/ # TypeScript types
│ │ └── utils/ # Utilities
│ ├── migrations/ # SQL migrations
│ └── Dockerfile
├── mcp_server/ # MCP tool server
│ ├── src/
│ └── Dockerfile
├── flutter_client/ # Flutter desktop app
│ ├── lib/
│ └── README.md
├── context/ # Architecture docs
├── docker-compose.yml # Full stack orchestration
├── .env.example # Environment template
└── README.md
MCP Tools (remote, via MCP server):
- Implement tool in
mcp_server/src/tools/ - Register in
mcp_server/src/index.ts - Update prompt in
api_server/src/services/gis-prompt-builder.ts - Add feature extraction in
api_server/src/services/geo-feature-extractor.ts
Local Tools (API server-side):
- Implement tool in
api_server/src/tools/ - Register in
api_server/src/services/agent.ts(localTools map) - Tool has direct access to database and conversation context
- Example:
remove-feature.tsfor removing map features
Create new migration:
cd api_server/migrations
touch 003_my_feature.sqlWrite SQL schema changes, then run:
npm run migrateMigrations are tracked in the migrations table and only run once.
If you see esbuild platform errors:
# Force rebuild without cache
docker-compose build --no-cache
docker-compose up --force-recreate# Check PostgreSQL is running
docker ps | grep postgis
# Check logs
docker logs magma-soup-postgis
# Wait for database to be ready
docker exec magma-soup-postgis pg_isready -U postgres# Check what's using the port
lsof -i :3001
# Stop the process or change PORT in .env# Connect to database
docker exec -it magma-soup-postgis psql -U postgres -d magma_soup
# Check migration status
SELECT * FROM migrations;
# Manual rollback (if needed)
# DROP TABLE tablename CASCADE;
# DELETE FROM migrations WHERE filename = '001_initial_schema.sql';See individual component READMEs for detailed development instructions:
[Your License Here]