# MCP Server

NREL-shift includes a [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) server that exposes the full framework to LLM-based agents. The server provides 33 tools, 3 resource templates, and 3 prompt templates that enable AI assistants to build synthetic distribution feeder models interactively.

## Installation

Install the MCP optional dependencies:

```bash
pip install -e ".[mcp]"
```

## Running the Server

```bash
# Via console script
shift-mcp-server

# Via Python module
python -m shift.mcp_server
```

The server uses **stdio** transport by default.

### Claude Desktop Configuration

Add the following to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):

```json
{
  "mcpServers": {
    "nrel-shift": {
      "command": "shift-mcp-server"
    }
  }
}
```

## Architecture

```
src/shift/mcp_server/
├── __init__.py
├── __main__.py          # Entry point
├── server.py            # FastMCP instance, lifespan, registration
├── config.py            # ServerConfig (Pydantic model)
├── state.py             # AppContext — in-memory session state
├── serializers.py       # JSON serialization helpers
├── tools/
│   ├── data_acquisition/
│   │   ├── parcels.py       # fetch_parcels, fetch_parcels_in_polygon
│   │   ├── roads.py         # fetch_road_network
│   │   └── clustering.py    # cluster_parcels
│   ├── graph/
│   │   ├── management.py    # create_graph, delete_graph, list_graphs
│   │   ├── nodes.py         # add_node, remove_node, get_node
│   │   ├── edges.py         # add_edge, remove_edge, get_edge
│   │   ├── query.py         # query_graph
│   │   └── builder.py       # build_graph_from_groups
│   ├── mapper/
│   │   ├── phase.py         # configure_phase_mapper, get_phase_mapping
│   │   ├── voltage.py       # configure_voltage_mapper, get_voltage_mapping
│   │   └── equipment.py     # configure_equipment_mapper, get_equipment_mapping
│   ├── system/
│   │   ├── builder.py       # build_system, get_system_summary, list_systems
│   │   └── export.py        # export_system_json
│   ├── utilities/
│   │   ├── geo.py           # distance_between_points, polygon_from_points
│   │   ├── network.py       # create_mesh_network, split_edges
│   │   └── nearest.py       # find_nearest_points
│   └── documentation/
│       ├── search.py        # search_docs
│       └── read.py          # list_docs, read_doc
├── resources/
│   └── docs.py              # shift://docs, shift://docs/{name}, shift://graphs
└── prompts/
    └── workflows.py         # build_feeder_from_location, inspect_network, explore_api
```

## Session State

The server is **stateful** — graphs, mappers, and systems are held in memory for the duration of a session via the `AppContext` dataclass. Each tool receives the context through FastMCP's lifespan mechanism.

Key state containers:

| Container | Type | Contents |
|-----------|------|----------|
| `graphs` | `dict[str, DistributionGraph]` | In-memory distribution graphs |
| `phase_mappers` | `dict[str, BalancedPhaseMapper]` | Phase mapper instances |
| `voltage_mappers` | `dict[str, TransformerVoltageMapper]` | Voltage mapper instances |
| `equipment_mappers` | `dict[str, EdgeEquipmentMapper]` | Equipment mapper instances |
| `systems` | `dict[str, DistributionSystem]` | Built distribution systems |
| `docs_index` | `dict[str, str]` | Indexed documentation content |

## Tools Reference

### Data Acquisition (3 tools)

| Tool | Description |
|------|-------------|
| `fetch_parcels` | Fetch building parcels from OpenStreetMap for a given location |
| `fetch_parcels_in_polygon` | Fetch building parcels within a polygon boundary |
| `fetch_road_network` | Fetch the road network from OpenStreetMap around a location |

### Graph Management (8 tools)

| Tool | Description |
|------|-------------|
| `create_graph` | Create a new empty distribution graph |
| `delete_graph` | Delete a distribution graph and its associated mappers |
| `list_graphs` | List all distribution graphs in the current session |
| `add_node` | Add a node to a distribution graph |
| `remove_node` | Remove a node from a distribution graph |
| `get_node` | Get details of a specific node |
| `add_edge` | Add an edge (line or transformer) to a distribution graph |
| `remove_edge` | Remove an edge from a distribution graph |
| `get_edge` | Get details of a specific edge |
| `query_graph` | Query information about a distribution graph (summary, nodes, edges, vsource, dfs_tree) |
| `build_graph_from_groups` | Build a complete distribution graph from parcel groups using the PRSG algorithm |

### Mappers (6 tools)

| Tool | Description |
|------|-------------|
| `configure_phase_mapper` | Configure balanced phase mapping for a distribution graph |
| `get_phase_mapping` | Get phase assignments (nodes, assets, or transformers) |
| `configure_voltage_mapper` | Configure voltage mapping for a distribution graph |
| `get_voltage_mapping` | Get voltage assignments for all nodes |
| `configure_equipment_mapper` | Configure equipment mapping using an equipment catalog |
| `get_equipment_mapping` | Get equipment assignments for all edges |

### System (3 tools)

| Tool | Description |
|------|-------------|
| `build_system` | Build a complete distribution system from a configured graph |
| `get_system_summary` | Get a summary of a built distribution system |
| `list_systems` | List all built distribution systems in the current session |
| `export_system_json` | Export a distribution system to JSON format |

### Utilities (5 tools)

| Tool | Description |
|------|-------------|
| `cluster_parcels` | Cluster geographic points into groups using K-means |
| `distance_between_points` | Calculate geodesic distance between two points |
| `polygon_from_points` | Create a polygon boundary from a set of points |
| `create_mesh_network` | Create a regular 2D mesh/grid network |
| `split_edges` | Split long edges into shorter segments |
| `find_nearest_points` | Find the nearest target point for each source point |

### Documentation (3 tools)

| Tool | Description |
|------|-------------|
| `list_docs` | List all available documentation files |
| `search_docs` | Search across all documentation for a keyword or phrase |
| `read_doc` | Read a specific documentation file, optionally by section |

## Resource Templates

| URI | Description |
|-----|-------------|
| `shift://docs` | List all indexed documentation files |
| `shift://docs/{doc_name}` | Read a specific documentation file by name |
| `shift://graphs` | List all in-memory distribution graphs |

## Prompt Templates

| Prompt | Description |
|--------|-------------|
| `build_feeder_from_location` | Guides through the full pipeline: fetch → cluster → build graph → map phases → map voltages → map equipment → build system → export |
| `inspect_network` | Inspect an existing distribution graph and summarize its topology |
| `explore_api` | Explore the NREL-shift API documentation on a given topic |

## Configuration

The server loads configuration from a `ServerConfig` Pydantic model. Defaults can be overridden by placing a `shift_mcp_config.yaml` file in the working directory.

| Setting | Default | Description |
|---------|---------|-------------|
| `server_name` | `"nrel-shift"` | Server display name |
| `server_version` | `"0.1.0"` | Server version |
| `default_search_distance_m` | `500.0` | Default parcel/road search radius (meters) |
| `max_search_distance_m` | `5000.0` | Maximum allowed search radius (meters) |
| `default_cluster_count` | `5` | Default number of K-means clusters |
| `docs_path` | `""` | Override path to documentation directory |
| `log_level` | `"INFO"` | Logging level |

## Example Workflow

A typical agent-driven workflow:

```
User: Build a small distribution feeder for Golden, CO

Agent:
1. fetch_parcels(location="Golden, CO", distance_meters=300)
   → 24 parcels found

2. cluster_parcels(points=[...], num_clusters=6)
   → 6 groups created

3. build_graph_from_groups(groups=[...], source_longitude=-105.22, source_latitude=39.75)
   → graph "graph-a1b2" created with 31 nodes, 30 edges

4. configure_phase_mapper(graph_id="graph-a1b2", transformer_configs=[...])
   → phase mapper configured

5. configure_voltage_mapper(graph_id="graph-a1b2", transformer_voltages=[...])
   → voltage mapper configured

6. configure_equipment_mapper(graph_id="graph-a1b2", catalog_path="...")
   → equipment mapper configured

7. build_system(system_name="golden_feeder", graph_id="graph-a1b2")
   → system built

8. export_system_json(system_name="golden_feeder", output_path="./golden_feeder.json")
   → exported to ./golden_feeder.json
```

## Known Limitations

- **Pydantic version conflict**: `grid-data-models==2.2.1` pins `pydantic~=2.10`, while `mcp[cli]` may require `pydantic>=2.12`. You may need to relax the pin or install in a separate environment.
- **Network-dependent tools**: `fetch_parcels`, `fetch_parcels_in_polygon`, and `fetch_road_network` require internet access to query OpenStreetMap.
- **Single session**: The stdio transport serves one client at a time. For multi-client scenarios, wrap with a proxy or use the SSE transport.
