# MCP Server
The Enterprise Edition plugin `plugin-mcp` provides a [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server for open.WARE. MCP clients such as Claude, IDE assistants or custom agents can use it to explore and analyse open.WARE data. They can find sources and items, read live and historical values, and aggregate and analyse time series. They can also write values, configure items, manage virtual sensors and trigger actuators.
- **Endpoint:** `POST /api/mcp`. The server uses Streamable HTTP in stateless mode with JSON responses (no SSE stream, no sessions).
- **Authentication:** The regular open.WARE authentication of `/api/*` applies. Send either the header `OD-SESSION: <session>` or `Authorization: Bearer <jwt>`. Requests without a valid user are rejected with 403.
- **Permissions:** Every tool runs as the authenticated user. Read, write and delete permissions are checked per item with the same rules as the REST API, including `accessControl=false`.
## Deployment
Add the plugin as a dependency of your open.WARE instance, e.g. in the `pom.xml` of the instance:
```xml
<dependency>
<groupId>de.openinc.openware</groupId>
<artifactId>plugin-mcp</artifactId>
<version>1.1-SNAPSHOT</version>
</dependency>
```
The MCP SDK and its dependencies are resolved transitively. The server is disabled by default. Enable it via the configuration, e.g. with the environment variable:
```bash
OW_DE_OPENINC_OW_PLUGIN_MCP_MCPPLUGIN={"enabled":true}
```
## Configuration
Options are read from `conf/de.openinc.ow.plugin.mcp.McpPlugin.json` or from the environment variable `OW_DE_OPENINC_OW_PLUGIN_MCP_MCPPLUGIN` (JSON). The server only starts with `"enabled": true`. All other fields are optional:
```json
{
"enabled": true,
"path": "/mcp",
"readOnly": false,
"disabledTools": ["trigger_actuator"],
"maxItems": 100,
"maxValues": 1000,
"allowedActuators": ["email", "webhook"],
"instructions": null
}
```
| Option | Default | Meaning |
|--------|---------|---------|
| `enabled` | `false` | Starts the MCP server. Without it, the plugin only logs that it is disabled |
| `path` | `/mcp` | Endpoint below `/api` |
| `readOnly` | `false` | Only offers tools that do not change anything |
| `disabledTools` | `[]` | Tool names that are not offered |
| `maxItems` | `100` | Maximum number of items per listing call |
| `maxValues` | `1000` | Maximum number of values per item in a result |
| `allowedActuators` | all | Actuator types that may be triggered |
| `instructions` | built-in | Server instructions sent to the client on `initialize` |
| `maxRequestBytes` | 10 MB | Maximum size of a request |
Like any other service, the plugin can be deactivated via `state.json`.
## Connecting a client
Clients authenticate with a session token or a JWT:
- **Session token:** Call `GET /api/users/login?username=..&password=..` and use the field `result.session`.
- **JWT:** Configure `OW_JWT_SECRET` and `OW_JWT_ISSUER` (see [Environment Variables](EnvVariables.md)). The token must contain one of the claims `username`, `usermail` or `uid`.
Claude Code:
```bash
claude mcp add --transport http openware https://<host>/api/mcp --header "OD-SESSION: <session>"
```
Generic `mcp.json`:
```json
{
"mcpServers": {
"openware": {
"type": "http",
"url": "https://<host>/api/mcp",
"headers": { "Authorization": "Bearer <jwt>" }
}
}
}
```
## Tools
Tools are annotated for clients:
- **read** tools do not change anything (`readOnlyHint`).
- **destructive** tools overwrite or remove state.
- **external** tools reach systems outside of open.WARE, such as mail servers or webhooks.
| Tool | Kind | Purpose |
|------|------|---------|
| `whoami` | read | Current user, roles, admin flag |
| `list_sources` | read | Readable sources with number of items |
| `list_items` | read | Items with value dimensions and last value; filter by source, text, unit; paginated |
| `list_units` | read | Units of all readable items, by frequency |
| `get_item` | read | Full item incl. meta data and last value |
| `check_access` | read | Read/write/delete permission for an item |
| `get_source_hierarchy` | read | Source tree of open.DASH; only offered with the `ParseUserAdapter`, see [Source hierarchy](#source-hierarchy) |
| `get_live_values` | read | Current value or last N values before a point in time |
| `get_historical_data` | read | Values of a time range, sorted; reduced to `maxValues` (minmax/sample/first/last or even sampling) |
| `count_values` | read | Number of values in a time range |
| `list_transformations` | read | Available transformation operations with aliases, parameters and dedicated tool |
| `aggregate` | read | avg/min/max/sum/count/first/last/stdd/variance/diffminmax/difffirstlast per calendar interval or equal splits (`DBAggregation` on MongoDB, in-memory otherwise) |
| `run_pipeline` | read | Arbitrary transformation pipeline (`historical`, `math`, `sync_merge`, ...) |
| `statistics` | read | Histogram, PCA or multiple linear regression (`StatisticTransformers`) |
| `pattern_search` | read | Sections similar to a reference pattern (dynamic time warping) |
| `seasonal_decomposition` | read | Seasonal, trend and residual component with seasonal profile and strengths (`STLTransformer`) |
| `moving_average` | read | Moving average over the last N values of one dimension (`MovingAvgTransformer`) |
| `differences` | read | Differences between consecutive values (`DiffTransformer`) |
| `calculate` | read | mXparser expressions over the dimensions, filters, optionally across merged items (`MathOperation`) |
| `map_values` | read | Map values to categories/other values by conditions (`MapTransformer`) |
| `merge_items` | read | Several items aligned by timestamp in one series (`SyncMerge`) |
| `values_at_intervals` | read | Value at every calendar interval boundary, e.g. reading at midnight (`LiveDataSource`) |
| `meter_consumption` | read | Consumption per calendar interval from meter readings, incl. meter changes (`MeteringConsumption`) |
| `geo_analysis` | read | Coordinates, k-means or DBSCAN clusters of GeoJSON positions (`GeoTransformer`) |
| `push_values` | write | Store new values (new items need `valueTypes`) |
| `update_values` | destructive | Overwrite stored values |
| `list_item_configs` | read | Item configurations the user may change |
| `configure_item` | destructive | Rename, move, rescale, (de)activate an item |
| `delete_item_config` | destructive | Remove an item configuration |
| `list_virtual_sensors` | read | Virtual sensor providers and sensors |
| `create_virtual_sensor` | write | Create a virtual sensor (for `vkpi` owner and ACL are set automatically) |
| `delete_virtual_sensor` | destructive | Delete a virtual sensor (`vkpi`: write access in the ACL, or the owner if there is no ACL) |
| `create_olap_view` | write | Pre-aggregated MongoDB view; only offered if `plugin-OlapMongoViews` is deployed |
| `list_actuators` | read | Actuators that may be triggered |
| `trigger_actuator` | external | Send mail, call webhook, publish to AMQP, ... |
| `get_instance_state` | read | Services, item statistics, ingest backlog; `OW_ADMIN_ROLE` only |
### Transformation operations
Every registered transformation operation can be used in `run_pipeline`. The operations and their parameters are described in [Data Transformation](DataTransformation.md), and `list_transformations` returns the same information to the client.
The dedicated tools (e.g. `moving_average`, `differences`, `seasonal_decomposition`) load the values sorted chronologically, since some operations such as `DiffTransformer` and `MovingAvgTransformer` expect ascending order. `values_at_intervals` and `meter_consumption` query every interval boundary separately. They are therefore limited to `maxValues` intervals.
`DimensionReduceTransformer` and `TemplateTransformer` are only offered through `run_pipeline`. The core operation `FilterTransformer` is intentionally not available: it evaluates arbitrary SpEL expressions, which would allow code execution. Use `calculate` with `filters` instead, which evaluates filters with the sandboxed mXparser.
### Times
Times accept ISO-8601 (`2026-09-01T12:00:00+02:00`, `2026-09-01`), epoch milliseconds and relative values (`now`, `-15m`, `-24h`, `-7d`, `-3mo`). Local times use `OW_TIMEZONE` (default `Europe/Berlin`). Results contain epoch milliseconds (`date`) and the formatted time (`time`).
## Source hierarchy
If `ParseUserAdapter` is the user adapter, `get_source_hierarchy` returns the source tree of open.DASH (Parse class `OD3_Source`). Nodes are linked by the pointer `parent`. The field `tag` is the `source` of open.WARE items, and one tag can appear on several nodes. Each node has `id`, `name`, `tag`, `tenant` (label), `items` (readable items with that source), `childCount` and `children`.
- **No arguments:** returns the whole tree from its roots. Nodes whose parent the user cannot see become roots.
- **`tag` / `query`:** returns the matching nodes, each with its `path` from the root and its subtree.
- **`id`:** returns one node with its path and subtree. `tag` and `query` are then searched only below that node.
- **`depth` / `limit`** (default 200, max 1000 nodes): cut the tree breadth-first. `truncated` is then set, and nodes can be expanded with `id`.
The tool checks the Parse ACL of every source, including roles, so users see the same sources as in open.DASH. With `accessControl=false` all sources are visible.
## Limitations
- The transport is stateless, so the server cannot send messages on its own: there are no live subscriptions or progress notifications. `GET` and `DELETE` on `/api/mcp` return 405.
- Missing authentication is answered with 403 by the open.WARE auth handler, not with 401 plus OAuth metadata. Clients therefore need a pre-configured session header or JWT.
- Reports, alarms, scheduled jobs and data deletion are intentionally not exposed yet.