MCP
Let AI assistants read, configure and run a Tangle project through the Model Context Protocol, with show protection.
tangle-core includes an MCP (Model Context Protocol) server, so an AI assistant can read a project, build rooms and cue lists, control devices and check monitoring. Every tool goes through the same command layer as the REST API: the same validation, scopes and audit log, plus a protection that keeps assistants away from a running show.
Two ways to connect
| Transport | Where | For |
|---|---|---|
| Streamable HTTP | http://<host>:7340/mcp | Assistants and tools that speak MCP over HTTP. |
| stdio bridge | tangle-core mcp | Desktop assistants that only start MCP servers as local commands. |
Both need a running tangle-core and an API token. Create a token for the assistant on the machine that runs tangle-core:
tangle-core token create --name claude --scopes read,control,configure
The secret is printed once. Choose the scopes for what the assistant should be able to do (see Scopes).
Note: MCP access is part of Tangle Pro and Ultimate (and the demo mode). On other editions, MCP requests get
403 not_licensed. See Licensing.
Streamable HTTP
Send the token as a bearer token on every request:
POST /mcp HTTP/1.1
Host: 127.0.0.1:7340
Authorization: Bearer tgl_...
Accept: application/json, text/event-stream
Content-Type: application/json
A request without a valid token gets 401 with a problem-details body. Unless tangle-core runs with --allow-remote, the MCP endpoint accepts only requests addressed to this machine (a loopback Host).
stdio bridge
tangle-core mcp reads JSON-RPC messages from stdin, forwards each one to the running core's /mcp endpoint, and writes every answer and notification to stdout.
TANGLE_TOKEN=tgl_... tangle-core mcp
tangle-core mcp --url http://10.0.0.5:7340 --token tgl_...
| Option | Default | Description |
|---|---|---|
--url <url> | http://127.0.0.1:7340 | Address of the running tangle-core. |
--token <token> | TANGLE_TOKEN | API token. Reads the TANGLE_TOKEN environment variable when not given. |
If the core cannot be reached, the bridge answers each request with a JSON-RPC error that says so. When stdin closes, the bridge ends its MCP session and exits.
Configure an assistant
Most desktop assistants read a JSON file that lists MCP servers. Add Tangle with the bridge:
{
"mcpServers": {
"tangle": {
"command": "tangle-core",
"args": ["mcp"],
"env": { "TANGLE_TOKEN": "tgl_..." }
}
}
}
- If
tangle-coreis not on the assistant'sPATH, give its full path incommand. - To reach a core on another machine, add
"--url", "http://10.0.0.5:7340"toargs. That core must run with remote access (--listen 0.0.0.0:7340 --allow-remote --insecure-http, see CLI). - Restart the assistant after editing its configuration.
The server introduces itself as tangle-core and gives the assistant these instructions: read the project with get_project and live state with get_snapshot; change the configuration with the create_*, update_* and delete tools (dry run first); make things happen with send_intent; drive shows with control_timeline; never interrupt a running show without being asked.
Scopes
Each tool needs one scope (shown in its description, which the assistant reads).
| Scope | Allows |
|---|---|
read | Every tool that only reads: get_*, list*, search_fixture_library, export_project, device_references. |
control | Making things happen: send_intent, control_timeline, run_automation, stop_automation, set_variable, set_control, press_control, call_function, lighting playback and programmer, rebalance_cluster, resume_session, alerts and metrics, device log notes, positions. |
configure | Changing the project and the core: create_*, update_*, delete, remove_devices, drivers, modules, projects, templates, dashboards drafts and publishing, tests, discovery, setup assistants, community. |
admin | manage_license, accept_discovered_core, get_pairing_code. |
live | Allows changes and control while a timeline plays (see below). |
The tables below give each tool's scope.
Resources and live updates need read.
Show protection
While any timeline is playing, an assistant whose token lacks the live scope cannot change anything. Every tool that edits the project, sends an intent, drives a timeline or runs an automation is refused with code show_running:
{
"type": "https://tangle.dev/problems/show_running",
"title": "Conflict",
"status": 409,
"detail": "the show is running (timeline main-show is playing); nothing was changed",
"code": "show_running"
}
Reading always works. Give a token live only when an assistant should act during a show:
tangle-core token create --name show-assistant --scopes read,control,live
Other protections apply to every client, with or without live:
- Live timelines: editing a timeline (or its tracks and cues) while it plays is refused (
timeline_running) unless the tool call setsallowLive: true. - Network scans:
discover_devicesis refused while a timeline plays (show_mode). - Deleting:
deleteonly previews unlessconfirmis true. - Redundancy: on a backup node, changes and control are refused with
not_primary.
Results and errors
Tool results are JSON (structured content). A list is wrapped as { "result": [...] }. When a tool fails, the result is an error whose content is a problem-details object with a stable code, detail and, for validation errors, errors listing each problem. The codes are the REST API's (see REST API errors); an unknown tool name gets unknown_tool.
Configuration tools (create_*, update_*, delete) return:
| Field | Description |
|---|---|
dryRun | True when nothing was applied. |
result | The resource after the change (or as it would be). Absent after a delete. |
changes | [{ op, kind, id, parent }]: what changed. |
revision | Project revision after the change. |
note | On a delete without confirm: says nothing was deleted. |
Tools
Project and configuration
| Tool | Scope | Purpose | Arguments |
|---|---|---|---|
get_project | read | Read the whole project. | none |
list_modules | read | Installed device modules with their capabilities and the devices using each. | none |
get_cluster_status | read | Redundancy status: role, primary, every node's health, sync state and cue log. | none |
list | read | List resources of one kind, the tracks of a timeline, or the cues of a track. | kind (required), timelineId, trackId |
get | read | Read one resource by id. | kind, id (required), timelineId, trackId |
delete | configure | Delete a resource. Without confirm: true it only previews. Refused if the project would become invalid (for example a scene still uses the device). | kind, id (required), timelineId, trackId, confirm, dryRun, allowLive |
create_<kind> | configure | Create a resource. The whole project is validated first; a mistake changes nothing. | <kind> (the resource, required), timelineId, trackId, dryRun, allowLive |
update_<kind> | configure | Replace a resource with a complete new version (read it first with get). Creates it if missing. | same as create_<kind> |
kind for list, get and delete is one of: room, device, scene, timeline, track, cue, automation, alertRule, notificationChannel, maintenanceWindow, probe. Tracks need timelineId; cues need timelineId and trackId.
The create and update tools exist for every resource type; their input schema is the contract type, so the assistant sees every field:
| Tools | Argument | Contract type |
|---|---|---|
create_room, update_room | room | Room |
create_device, update_device | device | DeviceInstance |
create_scene, update_scene | scene | Scene |
create_timeline, update_timeline | timeline | Timeline |
create_track, update_track | track, timelineId | Track |
create_cue, update_cue | cue, timelineId, trackId | Cue |
create_dashboard, update_dashboard | dashboard | Dashboard |
create_automation, update_automation | automation | Automation |
create_alertRule, update_alertRule | alertRule | AlertRule |
create_notificationChannel, update_notificationChannel | notificationChannel | NotificationChannel |
create_maintenanceWindow, update_maintenanceWindow | maintenanceWindow | MaintenanceWindow |
create_probe, update_probe | probe | Probe |
create_listener, update_listener | listener | Listener |
create_map, update_map | map | Map |
create_view, update_view | view | EndUserView |
create_sequence, update_sequence | sequence | Sequence |
create_preset, update_preset | preset | LightingPreset |
create_lightingGroup, update_lightingGroup | lightingGroup | LightingGroup |
create_executor, update_executor | executor | Executor |
The fields of each type are in Project file. dryRun: true validates and returns the changes without applying them. Device settings are checked against the module's settings schema, and secret settings go to the OS credential store.
| Tool | Scope | Purpose | Arguments |
|---|---|---|---|
list_available_modules | read | Every module this core knows: installed, update available, or bundled and ready to install. | none |
install_module | configure | Install or update a module. Devices using it restart; refused while a timeline plays if any would. | moduleId |
device_references | read | What uses these devices and their sub-devices. Call it before remove_devices. | ids |
remove_devices | configure | Delete devices, their sub-devices and their references in one change. Without confirm: true it only previews. | ids, references (remove or keep), confirm, allowLive |
run_device_setup | configure | Run one step of a device's setup assistant through its module. Settings it returns are saved on the device. | deviceId, configurator (required), step, answers, config |
list_units, set_project_units | read, configure | The quantities and units Tangle converts between; the project's metric or imperial system and overrides. | system, overrides, autoScale |
set_project_controls | configure | The project's default dashboard control style and highlight colours. | controls, colors |
get_variables, set_project_variables | read, configure | Project variables with their values; declare the whole list. | name; variables |
list_project_templates, apply_project_template | read, configure | The kinds of project the wizard starts from; add one to the project (rooms, planned devices, timelines, automations, dashboards, alert rules). | templateId, answers, devices, acknowledged, dryRun |
Project library
| Tool | Scope | Purpose | Arguments |
|---|---|---|---|
list_projects | read | The open project and every project kept on this core. | none |
create_project | configure | A new, empty project; open: true opens it. | name (required), id, open |
open_project, close_project | configure | Open another project, or close the open one. Refused while a timeline plays unless force (needs live). | projectId, force |
duplicate_project | configure | Copy a project with its dashboards and plans; open: true is Save as. | projectId, name, open |
rename_project | configure | Give a project a new name; its id, dashboards and plans stay. | projectId, name |
delete_project | configure | Delete a project that is not open. Cannot be undone. | projectId |
export_project, import_project | read, configure | A .tangle file as base64, without device passwords; import one, or a Project object, as a new project. | projectId; dataBase64 or project, fileName, open |
save_project | configure | Write the open project's file now. | none |
Control and live state
| Tool | Scope | Purpose | Arguments |
|---|---|---|---|
send_intent | control | Make something happen (power.on, level.set, input.select, scene.recall with params.scene, ...) on a device, room, tag or everything. Waits for the result and returns the final IntentStatus with each device's outcome. | intent (an IntentRequest, required), waitMs (0 to 60000, default 5000) |
get_intent_status | read | Current status of an intent sent earlier. | intentId |
control_timeline | control | Play, pause, stop, seek (positionMs) or GO (cue lists). Returns the TimelineState. | control: { op, timelineId, positionMs } |
get_timeline_states | read | Transport state of every timeline (status, position, last and next cue). | none |
get_snapshot | read | Live status and state of every device, and every timeline's state. | none |
get_device_state | read | Live status and capability state of one device; use it to diagnose a device. | deviceId |
test_device | configure | Test a device's connection in a separate module process (the running device is not touched). Pass config to try settings before saving them. | deviceId (required), config |
discover_devices | configure | Scan the local network (mDNS, SSDP) for devices, with suggested modules. Refused while a timeline plays. | seconds (1 to 30, default 5) |
get_device_log | read | A device's log, newest first: actions and who sent them, their outcome, status and state changes, events. Several devices are merged. | deviceIds (required), limit, before, kinds, level |
add_device_log_note, clear_device_log | control, configure | Write a note in a device's log; empty it. | deviceId, text, level |
set_variable | control | Set a project variable; variable triggers fire. | name, value |
list_controls | read | Dashboard controls with a kept value or override. | id |
set_control | control | Set a dashboard control: value (or add), label, visible, enabled, other properties in props. Every screen follows. | id (required), value, add, label, visible, enabled, props |
press_control | control | Report a control event as if someone used it: click, press, release or hold. | id (required), event, value |
rename_control | configure | Rename a control; every reference follows. | id, to |
list_functions, call_function | read, control | Library functions for expressions and Function steps; call one to try it. | namespace; name, args |
Intents sent over MCP are recorded with source api and the token name. See REST API intents for intent names and targets.
Dashboards
| Tool | Scope | Purpose | Arguments |
|---|---|---|---|
get_dashboard_draft | configure | Read a dashboard's draft, or its published version when there is no draft. | dashboardId |
save_dashboard_draft | configure | Save a draft (contract Dashboard). Screens keep the published version until it is published. | dashboardId, dashboard |
publish_dashboard | configure | Publish the draft: every screen showing the dashboard reloads. | dashboardId |
list_dashboard_versions | read | Published versions, newest first. | dashboardId |
get_dashboard_version | read | One published version. | dashboardId, version |
restore_dashboard_version | configure | Publish an old version again, as a new version. | dashboardId, version |
create_dashboard and update_dashboard publish directly. See Dashboards.
Automations
| Tool | Scope | Purpose | Arguments |
|---|---|---|---|
run_automation | control | Run an automation now. Conditions are checked unless force is true. Returns the run: started, or skipped with the reason. | automationId (required), force |
get_automation_runs | read | Recent runs, newest first: what triggered each, and whether it completed, failed (with the reason) or was skipped. | automationId |
stop_automation | control | Stop every run of an automation in progress. | automationId |
run_macro, get_macro_runs, stop_macro | control, read | Deprecated aliases: macros are automations since contract 0.2.30. | macroId |
Listeners
| Tool | Scope | Purpose | Arguments |
|---|---|---|---|
get_listeners | read | Every listener and how it is doing; with listenerId and log: true, the messages it received with the matched rule, captures and runs. | listenerId, log, since |
get_listener_runs | read | Recent runs started by a listener's messages. | listenerId |
test_listener | configure | Check a message against a listener's rules. A dry run unless run: true. | listenerId (required), text, http, osc, run |
Device creator and community
| Tool | Scope | Purpose | Arguments |
|---|---|---|---|
list_drivers, get_driver, get_driver_files | read | Drivers made with the device creator: the list, one definition and code, or every file of its module. | driverId |
check_driver | configure | Check a definition and its code without saving: errors and warnings with paths. | definition (required), script |
save_driver | configure | Build and install a driver's module; devices using it restart. The description explains the definition format. | definition (required), script |
delete_driver | configure | Delete a driver: uninstalls its module and removes its source; refused while devices use it. | driverId |
community | read, configure | The community driver repository: status, search, install, publish, sign-in and sign-out. | action (required), and the action's arguments |
Cluster
| Tool | Scope | Purpose | Arguments |
|---|---|---|---|
list_discovered_cores | read | Other cores on the local network, not in this cluster. | none |
accept_discovered_core | admin | Keep a discovered core separate, or pair it as a backup with its pairing code (ask the user for it). | instanceId, mode (required), pairingCode |
get_pairing_code | admin | This core's pairing code. | none |
get_cluster_placement | read | Shared mode: which node runs each device. | none |
rebalance_cluster | control | Shared mode: spread the devices again. Ask the user first during a show; dryRun previews. | dryRun |
set_cluster_settings | configure | Standby or shared mode, folder placement, auto return, the cluster address, health limits. | mode, folders, autoReturn, address, limits |
Maps, plans and images
| Tool | Scope | Purpose | Arguments |
|---|---|---|---|
import_plan, get_plan | configure, read | Import a floor plan (SVG text, or a DXF, DWG, PNG or JPEG file as base64) for a map; read one back. | name, svg or dataBase64; file |
import_image, get_image | configure, read | Store a dashboard image; read one back. | name, svg or dataBase64; file |
get_map_positions | read | What is where on a map, in metres: placed devices and tracked positions. | mapId |
push_positions | control | Report positions as a tracking source does. | positions |
Lighting
| Tool | Scope | Purpose | Arguments |
|---|---|---|---|
get_lighting_state | read | Every sequence, the grand master and your programmer. | none |
lighting_command | control | Go, back, goto, off, fader, flash, pause, grand master. | command |
lighting_programmer | control | Select lights, set values, fan, blind, highlight, clear. | update |
lighting_store | configure | Store the programmer into a cue, a preset or a group. | store |
search_fixture_library | read | Search the bundled fixture library by manufacturer or model. | query, limit |
Monitoring (Sentinel)
| Tool | Scope | Purpose | Arguments |
|---|---|---|---|
get_sentinel_status | read | Overview: firing alert counts by severity, the firing alerts, the health of every device, core node and probe. | none |
list_alerts | read | Pending and firing alerts; with history: true also recent transitions, newest first. | history, limit (1 to 500) |
ack_alert | control | Acknowledge an alert: stops its repeat notifications. | alertId (required), note |
silence_alerts | control | Stop notifications for one alert or every alert of a rule until a time. | alertId or ruleId, until (ms since epoch, required) |
push_metrics | control | Record custom metric values that metric alert rules can watch. | samples: [{ name, value, labels, at }] |
get_metrics | read | Current custom and probe metric values; samples: true adds 24 h of per-minute min/max/last. | samples |
test_channel | configure | Send a test notification through a channel and return the provider's answer. | channelId |
Monitoring is part of Tangle Pro and Ultimate. See Sentinel.
License
| Tool | Scope | Purpose | Arguments |
|---|---|---|---|
get_license | read | State, mode (licensed, designer, demo), edition, features, limits, expiry dates, and whether a designer or demo session is paused. | none |
resume_session | control | Resume device connections and timelines after the hourly pause of the free designer or demo mode. | none |
manage_license | admin | Activate, refresh, deactivate, import a license file, offline mode, sign in with email, password reset, request file. | action (required), key, email, password, licenseId, name, file, offline |
Resources
| URI | Content |
|---|---|
tangle://project | The project configuration. |
tangle://snapshot | Every device's status and state, and every timeline's state. |
tangle://devices/{deviceId}/state | Live status and state of one device (one resource per device in the project). |
tangle://timelines/{timelineId}/state | Transport state of one timeline (one resource per timeline). |
All resources are application/json. The two {...} forms are also offered as resource templates.
Assistants can subscribe to resources to be notified when they change:
| Change | Resources notified |
|---|---|
| A device's state or status | tangle://devices/<id>/state, tangle://snapshot |
| A timeline's state | tangle://timelines/<id>/state, tangle://snapshot |
| The project | tangle://project |
Prompts
| Prompt | Argument | What it asks the assistant to do |
|---|---|---|
set_up_room | room (room name) | List the installed modules, ask which devices are in the room and how to reach them, create the room and each device (dry run first), propose a "presentation" and an "off" scene, then test with scene.recall and report each device's outcome. |
build_cue_list | show (what happens, in order) | Read the project for real device ids, create a cuelist timeline with one track and one cue per step, validate with a dry run, show the cue list, and apply it only after you agree. |
diagnose_device | deviceId | Read the device's status and configuration, check its module is installed, and explain the likely cause and what to check on site, without changing anything. |
Audit
Every change and control action made through MCP is recorded in audit.log (in the Tangle log folder) with surface mcp and the token name, like any other client. Use one token per assistant so the log shows who did what.