Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# roteiro-agent

MCP (Model Context Protocol) server for [Roteiro](roteiro.io) — a spatial data platform. Enables AI agents (Claude Desktop, VS Code, Cursor) to work with geospatial datasets, run geoprocessing operations, execute PostGIS queries, and more.
MCP (Model Context Protocol) server for Roteiro, a spatial data platform. Enables AI agents (Claude Desktop, VS Code, Cursor) to work with geospatial datasets, run geoprocessing operations, execute SQL, and more.

## Installation

Expand Down Expand Up @@ -41,9 +41,9 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
```json
{
"mcpServers": {
"cairn": {
"roteiro": {
"command": "roteiro-agent",
"args": ["--server-url", "https://your-cairn-instance.com", "--api-key", "cairn_abc123"]
"args": ["--server-url", "https://your-roteiro-instance.com", "--api-key", "roteiro_abc123"]
}
}
}
Expand All @@ -56,9 +56,9 @@ Add to `.vscode/mcp.json`:
```json
{
"servers": {
"cairn": {
"roteiro": {
"command": "roteiro-agent",
"args": ["--server-url", "http://localhost:8080", "--api-key", "cairn_abc123"]
"args": ["--server-url", "http://localhost:8080", "--api-key", "roteiro_abc123"]
}
}
}
Expand All @@ -71,9 +71,9 @@ Add to `.mcp.json`:
```json
{
"mcpServers": {
"cairn": {
"roteiro": {
"command": "roteiro-agent",
"args": ["--server-url", "http://localhost:8080", "--api-key", "cairn_abc123"]
"args": ["--server-url", "http://localhost:8080", "--api-key", "roteiro_abc123"]
}
}
}
Expand Down Expand Up @@ -105,6 +105,7 @@ Add to `.mcp.json`:
| `compute_route_matrix` | Origin-destination time/distance matrix |
| `compute_service_area` | Distance-based service area polygons |
| `list_operations` | Available geoprocessing operations |
| `list_analysis_operations` | Available advanced analysis operations |
| `browse_catalog` | Browse the built-in data catalog |
| `browse_catalog_enhanced` | Browse enhanced catalog with filters |
| `get_catalog_entry` | Get enhanced catalog entry by ID |
Expand Down
35 changes: 12 additions & 23 deletions SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,28 +43,17 @@ Queries must be SELECT-only (read-only).

## Geoprocessing Operations

Use `run_process` for single operations or `run_pipeline` for chains. Available operations include:

| Operation | Description | Key Parameters |
|-----------|-------------|----------------|
| `buffer` | Create buffer around features | `distance` (meters) |
| `clip` | Clip features by a mask | `clip_dataset` |
| `simplify` | Reduce geometry complexity | `tolerance` |
| `reproject` | Change coordinate system | `target_crs` (e.g. "EPSG:4326") |
| `centroid` | Calculate centroids | — |
| `convex_hull` | Convex hull of features | — |
| `intersection` | Intersect two datasets | `overlay_dataset` |
| `union` | Union two datasets | `overlay_dataset` |
| `difference` | Subtract one dataset from another | `overlay_dataset` |
| `sjoin` | Spatial join | `join_dataset`, `predicate` |
| `dissolve` | Merge features by attribute | `by` (field name) |
| `voronoi` | Voronoi polygons | — |
| `spatial_stats` | Descriptive statistics | — |
| `morans_i` | Spatial autocorrelation | `attribute` |
| `hotspot` | Getis-Ord Gi* hotspot analysis | `attribute` |
| `kernel_density` | Density estimation | `bandwidth`, `cell_size` |

Use `list_operations` to get the full list with parameter schemas.
Use `run_process` for single operations or `run_pipeline` for chains.

Always call `list_operations` first to fetch the live server operation catalog and parameter names. The current server supports a broad set including vector ops (`buffer`, `clip`, `intersect`, `difference`, `dissolve`, `sjoin`), geometry conversion (`centroid`, `convex_hull`, `feature_to_point`, `points_to_line`), stats (`spatial_stats`, `morans_i`, `hotspot`, `kernel_density`), interpolation (`interpolate_idw`, `ordinary_kriging`), validation (`validate`, `make_valid`, `validate_topology`), and optimization (`solve_vrp`, `p_median`, `mclp`).

Important parameter names for common ops:
- `clip` uses `mask`
- `sjoin` uses `right` and `predicate`
- `reproject` uses `from_crs` and `to_crs`
- `dissolve` uses `group_by`

Use `list_analysis_operations` for advanced analysis catalog endpoints under `/api/analysis/operations`.

## Data Catalog & STAC

Expand Down Expand Up @@ -115,7 +104,7 @@ GROUP BY r.name ORDER BY count DESC
{
"steps": [
{"operation": "buffer", "input": "schools", "params": {"distance": 1000}},
{"operation": "intersection", "params": {"overlay_dataset": "residential_zones"}}
{"operation": "intersect", "params": {"mask": "residential_zones"}}
]
}
```
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//
// Usage:
//
// roteiro-agent --server-url http://localhost:8080 --api-key cairn_abc123
// roteiro-agent --server-url http://localhost:8080 --api-key roteiro_abc123
package main

import (
Expand Down
12 changes: 12 additions & 0 deletions mcp/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,18 @@ func (c *Client) ListOperations() (json.RawMessage, error) {
return json.RawMessage(body), nil
}

// ListAnalysisOperations calls GET /api/analysis/operations.
func (c *Client) ListAnalysisOperations() (json.RawMessage, error) {
body, code, err := c.get("/api/analysis/operations", nil)
if err != nil {
return nil, err
}
if code != http.StatusOK {
return nil, fmt.Errorf("GET /api/analysis/operations returned %d: %s", code, truncate(body, 500))
}
return json.RawMessage(body), nil
}

// GetDatasetSchema calls GET /api/datasets/{name}/schema.
func (c *Client) GetDatasetSchema(name string) (json.RawMessage, error) {
body, code, err := c.get("/api/datasets/"+url.PathEscape(name)+"/schema", nil)
Expand Down
10 changes: 10 additions & 0 deletions mcp/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ func HandleToolCall(client *Client, name string, args json.RawMessage) (string,
return handleComputeServiceArea(client, params)
case "list_operations":
return handleListOperations(client)
case "list_analysis_operations":
return handleListAnalysisOperations(client)
case "browse_catalog":
return handleBrowseCatalog(client, params)
case "browse_catalog_enhanced":
Expand Down Expand Up @@ -514,6 +516,14 @@ func handleListOperations(client *Client) (string, error) {
return formatJSON(data), nil
}

func handleListAnalysisOperations(client *Client) (string, error) {
data, err := client.ListAnalysisOperations()
if err != nil {
return "", err
}
return formatJSON(data), nil
}

// requireString extracts a required string parameter.
func requireString(params map[string]interface{}, key string) (string, error) {
v, ok := params[key]
Expand Down
27 changes: 26 additions & 1 deletion mcp/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func TestToolsList(t *testing.T) {
"diff_datasets", "execute_sql", "list_spatial_tables", "get_duckdb_info",
"list_duckdb_datasets", "geocode", "reverse_geocode", "compute_route",
"compute_isochrone", "compute_route_matrix", "compute_service_area",
"list_operations", "browse_catalog", "browse_catalog_enhanced",
"list_operations", "list_analysis_operations", "browse_catalog", "browse_catalog_enhanced",
"get_catalog_entry", "list_catalog_categories", "list_catalog_tags", "import_from_catalog", "browse_stac_catalog",
"browse_stac_collections", "browse_stac_items", "import_stac_asset",
"search_stac",
Expand All @@ -109,6 +109,31 @@ func TestToolsList(t *testing.T) {
}
}

func TestToolsCall_ListAnalysisOperations(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("GET /api/analysis/operations", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprint(w, `{"operations":[{"id":"topology","name":"Topology Analysis"}]}`)
})
srv := testServer(t, mux)

resp := sendRequest(t, srv, "tools/call", 22, map[string]interface{}{
"name": "list_analysis_operations",
"arguments": map[string]interface{}{},
})

if resp.Error != nil {
t.Fatalf("unexpected error: %v", resp.Error)
}
result, _ := resp.Result.(map[string]interface{})
content, _ := result["content"].([]interface{})
first, _ := content[0].(map[string]interface{})
text, _ := first["text"].(string)
if !strings.Contains(text, "Topology Analysis") {
t.Errorf("response should contain operation name, got: %s", text)
}
}

func TestToolsCall_ListDatasets(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("GET /datasets", func(w http.ResponseWriter, r *http.Request) {
Expand Down
7 changes: 6 additions & 1 deletion mcp/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ func AllTools() []Tool {
},
{
Name: "run_process",
Description: "Run a single geoprocessing operation on a dataset. Operations include: buffer, clip, simplify, reproject, centroid, convex_hull, intersection, union, difference, sjoin, dissolve, voronoi, spatial_stats, morans_i, hotspot, kernel_density, and more.",
Description: "Run a single geoprocessing operation on a dataset via /api/process. Use list_operations first to discover the live operation catalog and parameter names on the connected server.",
InputSchema: InputSchema{
Type: "object",
Properties: map[string]PropertySchema{
Expand Down Expand Up @@ -303,6 +303,11 @@ func AllTools() []Tool {
Description: "List all available geoprocessing operations with their parameter schemas. Useful for discovering what operations are supported and what parameters they accept.",
InputSchema: InputSchema{Type: "object"},
},
{
Name: "list_analysis_operations",
Description: "List all available advanced analysis operations from /api/analysis/operations.",
InputSchema: InputSchema{Type: "object"},
},
{
Name: "browse_catalog",
Description: "Browse the built-in data catalog to discover available datasets for import. Supports text search and category filtering.",
Expand Down
Loading