Metadata-Version: 2.4
Name: mcp-postgis
Version: 0.3.0
Summary: Model Context Protocol server for PostGIS
Project-URL: Homepage, https://github.com/psychonaut0/mcp-postgis
Project-URL: Repository, https://github.com/psychonaut0/mcp-postgis
Project-URL: Issues, https://github.com/psychonaut0/mcp-postgis/issues
Project-URL: Changelog, https://github.com/psychonaut0/mcp-postgis/blob/main/CHANGELOG.md
Author-email: psychonaut0 <psychonaut0@users.noreply.github.com>
License: MIT
License-File: LICENSE
Keywords: claude,gis,mcp,postgis,postgresql
Classifier: Development Status :: 3 - Alpha
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Database
Classifier: Topic :: Scientific/Engineering :: GIS
Requires-Python: >=3.11
Requires-Dist: mcp[cli]>=1.2.0
Requires-Dist: pglast<8.0,>=6.0
Requires-Dist: psycopg[binary,pool]>=3.2
Requires-Dist: shapely>=2.0
Provides-Extra: dev
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Requires-Dist: testcontainers[postgres]>=4.0; extra == 'dev'
Requires-Dist: types-shapely; extra == 'dev'
Description-Content-Type: text/markdown

# mcp-postgis

[![CI](https://github.com/psychonaut0/mcp-postgis/actions/workflows/ci.yml/badge.svg)](https://github.com/psychonaut0/mcp-postgis/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/mcp-postgis.svg)](https://pypi.org/project/mcp-postgis/)
[![Python](https://img.shields.io/pypi/pyversions/mcp-postgis.svg)](https://pypi.org/project/mcp-postgis/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

A [Model Context Protocol](https://modelcontextprotocol.io) server that exposes PostGIS to MCP-aware clients (Claude Desktop, Claude Code).

Lets Claude introspect your spatial database, run safe spatial queries, and **publish results as views that QGIS picks up automatically** — so an analyst can describe a layer in plain language and have it land in their QGIS browser.

## Install

```bash
pip install mcp-postgis     # or `uv tool install mcp-postgis`
```

## Quick start (Docker PostGIS + Claude Desktop)

1. Run PostGIS:
   ```bash
   docker run --name postgis -e POSTGRES_PASSWORD=postgres \
     -p 5432:5432 -d postgis/postgis:16-3.4
   ```
2. Create a dedicated read-only role (see [docs/security.md](docs/security.md) for read_write and admin variants):
   ```sql
   CREATE ROLE mcp_postgis_ro LOGIN PASSWORD 'change-me';
   GRANT CONNECT ON DATABASE mydb TO mcp_postgis_ro;
   GRANT USAGE ON SCHEMA public, app TO mcp_postgis_ro;
   GRANT SELECT ON ALL TABLES IN SCHEMA public, app TO mcp_postgis_ro;
   ALTER DEFAULT PRIVILEGES IN SCHEMA public, app
       GRANT SELECT ON TABLES TO mcp_postgis_ro;
   ```
3. Add to your `claude_desktop_config.json`:
   ```json
   {
     "mcpServers": {
       "postgis": {
         "command": "uvx",
         "args": ["mcp-postgis"],
         "env": {
           "MCP_POSTGIS_DATABASE_URL": "postgresql://mcp_postgis_ro:change-me@localhost:5432/mydb",
           "MCP_POSTGIS_MODE": "read_only"
         }
       }
     }
   }
   ```
4. Restart Claude Desktop. The server's tools appear in the model picker.

## Using with Claude Code (Linux/macOS/Windows)

Claude Desktop is macOS/Windows only — on Linux (or anywhere you use the CLI),
register the server with Claude Code instead:

```bash
claude mcp add --transport stdio \
  --env MCP_POSTGIS_DATABASE_URL="postgresql://mcp_postgis_ro:change-me@localhost:5432/mydb" \
  --env MCP_POSTGIS_MODE=read_only \
  --scope user \
  postgis \
  -- uvx mcp-postgis
```

Restart `claude`, then `/mcp` lists the server and its tools. Use `--scope user`
(not `project`) so your connection string isn't committed to a repo.

## Modes

| Mode         | What it can do                                                                                  |
| ------------ | ----------------------------------------------------------------------------------------------- |
| `read_only`  | Default. Introspection + SELECT + spatial analysis. No writes, no DDL.                          |
| `read_write` | Above + create/refresh/drop views in `MCP_POSTGIS_LAYER_SCHEMA` (`mcp_layers` by default).      |
| `admin`      | Anything the connected role can do. Use only for one-off admin tasks; the role still gates.     |

## Tools

**Introspection:** `list_schemas`, `list_tables`, `describe_table`,
`list_geometry_columns`, `list_spatial_indexes`, `list_extensions`

**Query:** `execute_sql`, `explain`, `sample_table`

**Spatial analysis:** `features_in_bbox`, `features_in_polygon`,
`nearest_features`, `within_distance`, `buffer`, `intersect_layers`

**Geometry operations:** `transform_srid`, `centroid`, `point_on_surface`,
`area`, `length`, `simplify`, `is_valid`, `make_valid`, `bbox`

**Data quality:** `check_geometry_validity`

**Import / export:** `import_geojson` (load a GeoJSON FeatureCollection into a
table), `export_geojson`, `export_wkt`

**Layer publishing (QGIS bridge):** `create_layer` (with `geometry_type`
filter), `refresh_layer`, `list_layers`, `describe_layer`, `drop_layer`

**Resources:** `postgis://schemas`, `postgis://schema/{schema}/{table}`,
`postgis://layers` · **Prompts:** `analyze-layer`, `nearest-things`,
`within-radius`, `compare-layers`

## QGIS integration

After running the server in `read_write` mode and asking Claude to "publish that as a layer named `hotels_near_coast`", point QGIS at the same database (or use the same role). In the QGIS Browser → PostGIS → your connection, right-click → Refresh; the `mcp_layers` schema appears with `hotels_near_coast` inside it.

See [docs/qgis-setup.md](docs/qgis-setup.md) for screenshots.

## License

MIT. Contributions welcome — please open an issue first to discuss bigger changes.
