Metadata-Version: 2.5
Name: spatialflow-mcp
Version: 0.2.0
Summary: Local read-only MCP server for the SpatialFlow API
Project-URL: Homepage, https://spatialflow.io
Project-URL: Documentation, https://docs.spatialflow.io/api/
Project-URL: Repository, https://github.com/spatialflow-io/spatialflow-mcp
License-Expression: MIT
License-File: LICENSE
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: mcp<3,>=2
Description-Content-Type: text/markdown

# spatialflow-mcp

A local, read-only [MCP](https://modelcontextprotocol.io) server for SpatialFlow. It lets an AI
assistant such as Claude Desktop, Claude Code or Cursor answer questions about your own
workspace: where devices are, which geofences they entered, how long trucks sat at a facility,
and how workflows ran. It talks to
the public SpatialFlow API with your API key and runs on your machine over stdio.

It only issues `GET` requests. There are no tools that create, change or delete anything.

## Run

You need [uv](https://docs.astral.sh/uv/). `uvx` fetches the package from PyPI and runs it on
Python 3.10 or newer.

```bash
SPATIALFLOW_API_KEY=<your-api-key> uvx spatialflow-mcp
```

`pip install spatialflow-mcp` works too and puts a `spatialflow-mcp` command on your path.

| Variable | Required | Default |
|----------|----------|---------|
| `SPATIALFLOW_API_KEY` | yes | none |
| `SPATIALFLOW_API_URL` | no | `https://api.spatialflow.io` |

Logs go to stderr. The API key is never logged or returned in tool output.

## Create a read-only key

In the dashboard, open Settings, API Keys, then Create API Key. Turn on **Read-only mode** so
the API rejects anything but `GET` for this key, even if a bug or a prompt injection asked for
more. Give it only the permissions below. Set an expiry if you can. The key acts inside the
workspace it was created in.

| Tool | Key permission |
|------|----------------|
| `list_devices`, `get_device`, `get_recent_locations`, `list_geofence_events`, `detention_report`, `fleet_report` | `devices:read` |
| `list_geofences` | `geofences:read` |
| `list_workflows`, `list_workflow_executions` | `workflows:read`, and the key's owner must be a workspace owner or manager |

A tool whose permission the key lacks returns a short permission error; the others still work.

## Claude Desktop

Add this to `claude_desktop_config.json` and restart Claude Desktop:

```json
{
  "mcpServers": {
    "spatialflow": {
      "command": "uvx",
      "args": ["spatialflow-mcp"],
      "env": { "SPATIALFLOW_API_KEY": "<your-api-key>" }
    }
  }
}
```

## Claude Code

```bash
claude mcp add spatialflow --env SPATIALFLOW_API_KEY=<your-api-key> -- uvx spatialflow-mcp
```

## Tools

Every tool is marked read-only. List tools return `total_count`, `returned_count`,
`truncated`, a `hint` for narrowing, and `items`. `limit` defaults to 100 and is capped at 500.

| Tool | Returns |
|------|---------|
| `list_devices` | Devices with status, last location, shift state and the geofences they are inside |
| `get_device` | One device, including its open shift |
| `get_recent_locations` | The newest GPS points for a device |
| `list_geofences` | Geofences without polygon coordinates; filter by tag |
| `list_geofence_events` | Entry and exit events; filter by device, geofence, type and time |
| `list_workflows` | Workflows with status and run statistics |
| `list_workflow_executions` | Workflow runs; filter by workflow and status |
| `detention_report` | One row per facility visit with time over the free window and the amount owed |
| `fleet_report` | Time on site, trips, idle stops, mileage or attendance (`kind`) over a date range |

Report dates are `YYYY-MM-DD` days in the workspace timezone, and the default range is the last
7 days. Report summaries come back unchanged, and `limit` caps only the rows.

### Detention report

`rate_per_hour` is in dollars. The report separates what the data proves from what it can only
bound:

- A recorded exit is fully evidenced. An open visit counts while the device keeps reporting.
- If the device re-entered the geofence without an exit event, the departure is inferred and the
  time and money are a ceiling ("at most"). They are not added to the total.
- If the device went silent during the visit, the time is a floor ("at least") and the stop is
  unresolved. It is reported in `contact_lost_unresolved_cents`, not in the total.

The report models only per-visit free time counted from geofence arrival and an hourly rate
prorated per minute. Caps, billing increments, free time shared across stops and
appointment-based clocks need manual handling. GPS timestamps back up a claim; the rate
confirmation and signed bill of lading decide what is owed.

Timestamps are UTC. Distances are meters and speeds are meters per second.

## Privacy

Whatever a tool returns is sent to your AI provider as part of the conversation, including
device names, driver names, GPS positions and addresses. Use a key for a workspace whose data
you are allowed to share with that provider, and prefer narrow questions.

## Development

```bash
uv sync
uv run pytest
uv run ruff check src tests && uv run black --check src tests
```
