Metadata-Version: 2.4
Name: cngstorytelling-mcp
Version: 2.13.0
Summary: MCP Server for CNG Sandbox: agent-driven story creation and data exploration
Project-URL: Homepage, https://github.com/aboydnw/cng-sandbox
Project-URL: Repository, https://github.com/aboydnw/cng-sandbox
Project-URL: Issues, https://github.com/aboydnw/cng-sandbox/issues
Author: Development Seed
License-Expression: MIT
License-File: LICENSE
Keywords: agent,api,geospatial,mcp
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.11
Requires-Dist: httpx>=0.28.1
Requires-Dist: mcp>=1.27.2
Requires-Dist: pydantic>=2.13.4
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=1.4.0; extra == 'dev'
Requires-Dist: pytest-cov>=7.1.0; extra == 'dev'
Requires-Dist: pytest-mock>=3.15.1; extra == 'dev'
Requires-Dist: pytest>=9.1.0; extra == 'dev'
Requires-Dist: ruff==0.15.20; extra == 'dev'
Description-Content-Type: text/markdown

# CNG MCP Server

Model Context Protocol (MCP) server for CNG Sandbox. Exposes datasets, stories, connections, and validation as composable tools for agents.

## What is MCP?

MCP is a protocol that lets applications (agents, CLI tools, SDKs) discover and interact with tools and resources in a standardized way. This server makes the CNG Sandbox accessible to any MCP-compatible client.

## Installation

```bash
pip install cngstorytelling-mcp
```

This installs the `cng-mcp` command. To run against your own instance, see the [self-hosting guide](../docs/self-hosting.md).

## Quick Start

### 1. Start the Server

```bash
# Default API URL (http://localhost:8086)
cng-mcp --workspace-id <8-char-id>

# Custom API URL
cng-mcp --api-url http://your-sandbox.com --workspace-id <8-char-id>
```

The server communicates over stdio. Connect with any MCP client.

The ingestion API requires an `X-Workspace-Id` header on workspace-listing endpoints (`/api/datasets`, `/api/connections`, `/api/stories`). Pass `--workspace-id` (or set `SANDBOX_WORKSPACE_ID`) so the server can forward it — without it, listing endpoints return 400.

### 2. Use with Claude Desktop / Code

Add to your MCP client config:

```json
{
  "mcpServers": {
    "cng-sandbox": {
      "command": "cng-mcp",
      "args": ["--api-url", "http://localhost:8086", "--workspace-id", "abcd1234"]
    }
  }
}
```

## Tools

### read_datasets
List all datasets in your workspace with IDs, types, and bounds.

### read_story
Get a story's metadata and chapters.
**Input**: `story_id` (string)

### create_story
Create a new story.
**Input**:
- `title` (string)
- `description` (string)
- `chapters` (array): each with `title`, `narrative`, `map_state`, and `layer_config` (containing `dataset_id` or `connection_id`)
  - `map_state`: `{ center: [lng, lat], zoom, bearing, pitch, basemap }` plus optional 3D fields:
    - `terrain`: `{ enabled: bool, exaggeration: number }` — 3D terrain relief. Ignored when the chapter has a bound data layer (data can't drape on terrain in v1); use terrain on scene-setting chapters only.
    - `globe`: `bool` — globe projection with atmosphere; works with data layers.
    - `buildings`: `bool` — extruded 3D buildings (OpenFreeMap).
    - Omit these for a flat 2D map. Portable/archival HTML exports ignore 3D fields and render flat.
- Chapter `type` may be `"flyover"`: a scroll-scrubbed 3D camera flight. Flyover chapters add:
  - `keyframes` (array, >= 2 to fly): `{ center: [lng, lat], zoom, bearing, pitch, caption? }` — the camera path, interpolated smoothly in reader scroll order. `caption` is markdown shown as the camera passes that keyframe.
  - `scroll_length` (number, default 1): scroll distance multiplier (~one viewport height per keyframe segment at 1).
  - `layer_config` is optional; as with other chapters, terrain is ignored when a data layer is bound. The best flyovers are terrain/buildings scene-setters with no data layer.
  - `map_state` pose fields are ignored — keyframe 0 is the entry camera. Compute keyframes from dataset bounds for an easy orbit: fix `center`/`zoom`/`pitch`, sweep `bearing` across ~180° in 5 steps.

### update_story
Modify an existing story.
**Input**: `story_id`, `updates` (object with changed fields)

### read_connections
List external tile source connections.

### create_connection
Create a new external tile source connection.
**Input**:
- `name` (string)
- `url` (string)
- `connection_type` (string): one of `zarr`, `cog`, `pmtiles`, `xyz_raster`, `xyz_vector`, `geoparquet`, `copc`
- `bounds` (array, optional): `[west, south, east, north]`
- `min_zoom` / `max_zoom` (integer, optional)
- `tile_type` (string, optional): tile image format (e.g. `png`, `webp`)
- `band_count` (integer, optional)
- `rescale` (string, optional): range as `"min,max"`
- `config` (object, optional): additional tiler config
- `geozarr_attrs` (object, optional): GeoZarr-spec attributes

### validate_layer_config
Check if a layer configuration is valid before creating a chapter.
**Input**: `dataset_id`, optional `colormap`, `rescale_min`/`rescale_max`, and `color_mode` (`elevation`/`intensity`/`classification`/`rgb`) for point-cloud (copc) layers. Provide either a `colormap` (raster) or a `color_mode` (copc).

## Ingestion & export

### get_job_status
Get the status of a conversion job by job ID (pending/converting/ready/failed).
**Input**: `job_id` (string)

### ingest_url
Ingest a remote geospatial file (GeoTIFF, GeoJSON, Shapefile `.zip`, NetCDF, HDF5) into a dataset. Waits for conversion to finish and returns the dataset ID.
**Input**: `url` (string); optional `wait` (boolean, default true), `timeout` (number, default 600)

### discover_remote
List geospatial files at a remote URL or S3 prefix before connecting them.
**Input**: `url` (string)

### connect_remote_temporal
Register remote COGs as a temporal (date-stepped, time-slider) or mosaic dataset. Discovers files at the URL if an explicit file list is not given; waits for the job to finish.
**Input**: `url` (string); optional `mode` (`temporal` or `mosaic`, default `temporal`), `files` (array of `{url, filename}`), `timeout` (number)

### upload_story_asset
Upload a local image or CSV file as a story asset (for image chapters and CSV chart chapters). Returns the asset ID and URL.
**Input**: `file_path` (string), `kind` (`image` or `csv`); optional `story_id` (string)

### update_connection_colormap
Set the preferred colormap for a raster connection (cog/xyz_raster/raster pmtiles).
**Input**: `connection_id` (string), `colormap` (string); optional `reversed` (boolean)

### update_connection_categories
Update category labels/colors for a categorical connection.
**Input**: `connection_id` (string), `categories` (array of `{value:int, label?:str, color?:'#RRGGBB'}`)

### delete_connection
Delete an external tile source connection by ID.
**Input**: `connection_id` (string)

### export_story_interactive
Build and download a story's self-contained interactive HTML bundle (`.zip`) to a local path. Stories with zarr layers, flyover chapters, or scrolly chapters needing snapshots return an error.
**Input**: `story_id` (string), `output_path` (string)

## Resources

- `cng://datasets` — Catalog of available datasets
- `cng://story-templates` — Pre-built templates agents can reference
- `cng://colormaps` — Valid colormap names

## Development

```bash
cd mcp
uv sync --extra dev
uv run pytest tests/ -v
```

See [CONTRIBUTING.md](CONTRIBUTING.md) and [ARCHITECTURE.md](ARCHITECTURE.md).

## Troubleshooting

### "Connection refused" on localhost:8086
Verify sandbox stack is running: `docker compose ps`. If not, start it: `docker compose up -d`.

### "Unknown colormap" validation error
List valid colormaps via the `cng://colormaps` resource and pick one.

### Server doesn't discover tools
Run with debug logging: `cng-mcp --log-level DEBUG`. Check for tool registration messages.

### "ModuleNotFoundError: No module named 'cng_mcp'"
Install in editable mode from the `mcp/` directory: `pip install -e mcp/`.
