Eclipse Ditto MCP Server
A Model Context Protocol (MCP) server for Eclipse Ditto — the open-source (EPL-2.0) digital twin framework. It exposes Ditto's HTTP API as MCP tools so an AI agent can manage devices ("things"), their attributes/features, policies, connections, and cluster admin — the full Ditto HTTP API surface.
Since Bosch IoT Things and other commercial IoT platforms are built on Ditto's open protocol, this server also works against those deployments — nothing in the code is vendor-specific.
Built with FastMCP.
Things
| Tool | Description |
|---|
list_things(namespaces, filter, fields, limit) | Search/list things. filter is a Ditto RQL expression, e.g. eq(attributes/location,"lab-1"). |
count_things(namespaces, filter) | Count things matching a filter without fetching them. |
get_things(thing_ids, fields) | Bulk-fetch multiple things by ID in one request. |
get_thing(thing_id, fields) | Fetch the full digital twin for one thing. |
put_thing(thing_id, thing, overwrite=False) | Create a thing, or replace one with overwrite=True. |
patch_thing(thing_id, patch) | Partially update a thing via RFC-7396 JSON Merge Patch. |
delete_thing(thing_id, confirm=False) | Delete a thing. Irreversible. |
get_thing_policy_id / set_thing_policy_id | Read/repoint the policy controlling a thing. |
get_thing_definition / set_thing_definition / delete_thing_definition | Manage a thing's WoT/model definition. |
whoami() | Return the identity Ditto authenticated this connector as — useful to sanity-check credentials. |
Attributes & Features
| Tool | Description |
|---|
get_thing_attributes(thing_id, path=None) | Get all attributes, or one by JSON pointer path. |
set_thing_attributes(thing_id, value, path=None, overwrite=False) | Set the whole attributes object, or one attribute. |
delete_thing_attributes(thing_id, path=None, confirm=False) | Delete attributes (whole object or one path). |
get_thing_features(thing_id) | Get all features (sensor/actuator state). |
get_feature / put_feature / delete_feature | Read/create-replace/delete one whole feature. |
get_feature_properties / set_feature_properties / delete_feature_properties | Read/write/delete feature properties or desiredProperties, whole or by path. |
get_feature_definition / set_feature_definition / delete_feature_definition | Manage a feature's definition. |
Messages
| Tool | Description |
|---|
send_message(thing_id, subject, payload, feature_id=None, direction="inbox", timeout=10) | Send a live message to/from a thing or feature (e.g. invoke a device operation). |
claim_thing(thing_id, payload=None, timeout=10) | Send a claim message to gain access to a thing. |
Policies
| Tool | Description |
|---|
get_policy / put_policy / delete_policy | Read/create-replace/delete a policy. |
get_policy_entries(policy_id, label=None) | Read all entries, or one by label. |
set_policy_entry / delete_policy_entry | Create-replace/delete one policy entry. |
Connectivity & DevOps (require DITTO_DEVOPS_USERNAME/DITTO_DEVOPS_PASSWORD)
Ditto's Connections API and DevOps API (log levels, cluster config, piggyback commands) sit behind a separate "devops" Basic-auth realm on the gateway, independent of the policy-based auth used for things/policies.
| Tool | Description |
|---|
list_connections / get_connection | List/fetch connection configs. |
create_connection(connection, dry_run=False) | Create a connection (ID generated by Ditto). |
update_connection(connection_id, connection) | Update an existing connection (404 if it doesn't exist). |
delete_connection(connection_id, confirm=False) | Delete a connection. |
get_connection_status / get_connection_metrics / get_connection_logs | Inspect a connection's live state. |
send_connection_command(connection_id, command) | Open/close/reset-metrics/enable-logs/reset-logs. |
get_logging_config / set_logging_config | Read/set cluster log levels. |
get_devops_config(path) | Read cluster-wide runtime config. |
send_devops_piggyback(command, service_name=None, instance_index=None) | Low-level admin command — Ditto's most powerful, least-guarded API. Use with care. |
Live events
| Tool | Description |
|---|
poll_thing_changes(thing_ids, namespaces, filter, fields, duration_seconds=10, max_events=50) | Open a short-lived SSE connection and collect live thing-change events. A request/response approximation of Ditto's persistent change feed — see caveat below. |
Safety conventions
- Create vs. replace: every
put_*/set_*/create_* tool defaults to overwrite=False, using Ditto's If-None-Match: * header so create-only is race-free (not a check-then-set). Pass overwrite=True to replace existing data.
- Deletes are confirm-gated: every
delete_* tool requires confirm=True. There is no bulk/cascading delete.
- Not covered (deliberately): WoT Web-of-Things descriptor endpoints (
/.well-known/wot, /devops/wot/*), and fine-grained policy sub-resources (subjects/{id}, resources/{path}, imports, token-integration actions) — whole-entry set_policy_entry/get_policy_entries covers the common case. Add these later if you need them.
Configuration
| Env var | Required | Description |
|---|
DITTO_BASE_URL | Yes | Base URL of the Ditto HTTP API, e.g. http://localhost:8080 or your gateway's public URL. |
DITTO_USERNAME / DITTO_PASSWORD | No | Basic auth for the Things/Policies/Messages/Search API. Typical for self-hosted Ditto (nginx demo setup uses ditto/ditto). |
DITTO_BEARER_TOKEN | No | Bearer token, used instead of Basic auth for the same API. Typical for OAuth2-fronted deployments (e.g. Bosch IoT Things). |
DITTO_DEVOPS_USERNAME / DITTO_DEVOPS_PASSWORD | No | Basic auth for the separate DevOps/Connections realm (self-hosted default: devops/foobar). Required only for the Connectivity and DevOps tools. |
If both DITTO_BEARER_TOKEN and Basic auth credentials are set, the bearer token takes precedence. If neither is set, requests are sent unauthenticated (only works if your Ditto instance allows anonymous/pre-authenticated access).
Running locally
uv sync
export DITTO_BASE_URL=http://localhost:8080
export DITTO_USERNAME=ditto
export DITTO_PASSWORD=ditto
export DITTO_DEVOPS_USERNAME=devops
export DITTO_DEVOPS_PASSWORD=foobar
uv run dittomcpserver.py
See mcp.json for ready-to-use client configs (stdio and Docker).
Testing against a real Ditto instance
This repo's design principles call for testing against a real instance rather than mocks. docker-compose.yml (adapted from Ditto's official example, trimmed to the services this connector needs) spins up a full local Ditto stack — MongoDB, the Ditto microservices, and an nginx front door with Basic auth.
docker compose up -d
uv run pytest -v
docker compose down
The 47 tests cover every tool group (things, attributes/features, messages, policies, connectivity, devops, live events) against this real stack, including delete/confirm gating and create/overwrite gating.
A note on the live-events approximation
poll_thing_changes opens Ditto's SSE change feed for a bounded window instead of holding a persistent subscription (which doesn't map cleanly onto a single MCP tool call). Ditto's SSE stream sends periodic empty data: keepalive lines even when nothing has changed — the tool checks its deadline on every line received, including keepalives, so it reliably returns within duration_seconds regardless of how chatty or quiet the stream is.
License notes
docker-compose.yml and the files under docker/ are adapted from the Eclipse Ditto project (EPL-2.0) and are used here only to spin up a local test instance — they are not part of the MCP server itself.