Skip to main content
Glama
singular-mcp-server

singular-mcp-server

by dqbuilds

singular-mcp-server

A comprehensive Model Context Protocol server for reading, controlling, and orchestrating Singular.live live broadcast graphics compositions from Claude Desktop or any MCP client.

It lets an agent discover a control app's structure, fill its control nodes (text, images, colors…), animate sub-compositions in and out, push low-latency data, and run a newsroom rundown → graphics workflow over templates you build once in Composer.

What this is (and isn't)

Singular's public API controls compositions; it does not author them. You build the graphics (lower-thirds, full-frames, tickers, bugs) once in the browser Composer, and this server lets agents fill and sequence that kit of parts in real time. Authoring composition structure, account listing, token minting, media upload, and webhooks are not exposed by any public Singular API. See docs/SINGULAR_API.md for the API reference.

Installation

Prerequisites: Node.js ≥ 18 (developed on Node 22) and npm.

# 1. Clone
git clone https://github.com/dqbuilds/singular-mcp-server.git
cd singular-mcp-server

# 2. Install dependencies
npm install

# 3. Build (compiles TypeScript to dist/)
npm run build

# 4. (optional) Verify — runs the mock-API integration test; no Singular account needed
npm test

This produces the runnable server at dist/index.js. Next, configure a Singular app token and point your MCP client at it (below).

Install globally (optional). From the repo, npm link puts a singular-mcp-server command on your PATH; then your MCP client can use "command": "singular-mcp-server" instead of an absolute node dist/index.js path.

Related MCP server: OpenCut Controller

Configuration

Secrets come only from the environment. Copy .env.example → .env (the server itself reads real environment variables; use your process manager or the MCP client config to set them).

Variable

Required

Default

Purpose

SINGULAR_APP_TOKEN

no

—

Default control-app token when a tool omits app/appToken.

SINGULAR_API_BASE

no

https://app.singular.live/apiv2/controlapps

Control REST base.

SINGULAR_DATASTREAM_BASE

no

https://datastream.singular.live/datastreams

Data Stream base.

SINGULAR_DEFAULT_SUBCOMPOSITION

no

—

Optional default sub-composition name.

SINGULAR_REGISTRY_PATH

no

~/.singular-mcp/registry.json

On-disk alias→token registry (owner-only perms).

SINGULAR_HTTP_TIMEOUT_MS

no

30000

Per-request timeout.

LOG_LEVEL

no

info

debug|info|warn|error (logs go to stderr).

Where do tokens come from? Copy each app token from the Singular Dashboard (inspector "i" → URLs and Token; UNO </>; or Studio → Manage Access → App Token). The API cannot mint them.

Claude Desktop

Add to claude_desktop_config.json:

{
  "mcpServers": {
    "singular": {
      "command": "node",
      "args": ["/absolute/path/to/singular-mcp-server/dist/index.js"],
      "env": { "SINGULAR_APP_TOKEN": "your-token-here" }
    }
  }
}

You can omit SINGULAR_APP_TOKEN and instead register apps by alias at runtime with singular_register_app.

Tools (25)

Every tool takes response_format (markdown default, or json) and returns both human text and structuredContent. App-targeting tools accept an app alias (from the registry) or a raw appToken, falling back to the env default.

Discovery & read

  • singular_get_app_metadata — ids, name, output/preview URLs.

  • singular_get_model — structural model: sub-compositions + typed control nodes.

  • singular_get_control_state — current live state + values.

  • singular_list_subcompositions — model + live values joined (best overview).

  • singular_find_nodes — filter nodes by type (e.g. image) / title.

Content control

  • singular_update_content — fill nodes across one or more sub-comps in one PATCH.

  • singular_set_image — set an image node by URL (validates it's an image node + URL reachability).

  • singular_reset_nodes — reset nodes to their reset values (clear a template).

Animation & playout

  • singular_animate_state — take sub-comps In/Out (multiple targets per call).

  • singular_update_and_animate — fill + take to air atomically (no stale flash).

  • singular_take_out_all — clear the whole app (TakeOutAllOutput).

Data streams

  • singular_register_data_stream — store a stream's private token by alias.

  • singular_list_data_streams — list registered stream aliases.

  • singular_push_datastream — PUT low-latency JSON (≤ 60 KB) to a stream.

Media & output

  • singular_get_output_urls — live output / broadcast / thumbnail URLs.

  • singular_check_image_url — pre-flight an image URL.

Orchestration & rundown

  • singular_register_app / singular_list_apps / singular_remove_app — the alias registry.

  • singular_map_rundown_template — bind an item type → sub-comp + field map.

  • singular_list_rundown_templates — list mappings.

  • singular_prepare_item — dry-run: resolve a story item to a payload, no air.

  • singular_play_rundown_item — take an item to air (optional server-side auto-out).

  • singular_poll_onair_state — poll what's on air (+ diff since last poll).

  • singular_server_info — server config/health (no secrets).

For agents

The server sends usage guidance to clients as its MCP instructions at connect time, and every tool has a self-contained description with its args and return shape. The full playbook — the discover-before-write rule, the canonical flow, value formats, and gotchas — is in AGENTS.md.

Newsroom workflow example

1. singular_register_app  { alias: "evening-news", token: "…" }
2. singular_find_nodes    { app: "evening-news", type: "image" }      # discover the kit
3. singular_map_rundown_template {
     name: "lower-third", appAlias: "evening-news",
     subCompositionName: "LowerThird",
     fieldMap: { headline: "titleNode", photo: "bannerImage" } }
4. singular_prepare_item  { template: "lower-third",
     fields: { headline: "Markets rally", photo: "https://cdn/…jpg" } }  # verify
5. singular_play_rundown_item { template: "lower-third",
     fields: { headline: "Markets rally", photo: "https://cdn/…jpg" },
     auto_out_ms: 8000 }                                                 # to air
6. singular_poll_onair_state { app: "evening-news" }

An agent can drive this from a rundown/PDF: extract stories + image URLs, map them to templates, and play them to air.

Security

  • App tokens are bearer-equivalent secrets. They are stored server-side in the registry (file mode 0600, dir 0700), never returned to the model, and redacted from logs (including inside request URLs).

  • Host images on your own reachable URL; there is no upload API.

  • stdio logging is on stderr only (stdout is the JSON-RPC channel).

Limitations (from Singular's API, not this server)

Authoring compositions, listing an account's assets, minting tokens, uploading media, server-side rendering, and webhooks are all unavailable via public API. On-air awareness is by polling. Auto-out scheduling is in-memory (not restart-safe). See docs/SINGULAR_API.md.

Roadmap

  • Optional Streamable HTTP transport.

  • Data Node tools over the apiv1 Data Node API.

  • Batch rundown ingestion helpers (PDF/rundown → items).

  • In-memory control-model cache with TTL.

Development

npm run dev        # tsx watch
npm run typecheck  # tsc --noEmit
npm run inspector  # MCP Inspector against the built server

Available Tools

25 tools
singular_animate_stateAnimate sub-compositions In/OutA

Trigger animation state transitions to take sub-compositions on or off air. Accepts multiple targets for a coordinated transition in one call. This does NOT change content — combine with update_content first, or use update_and_animate to do both atomically.

Args: app/appToken; targets: [{ subCompositionName | subCompositionId, state: In|Out|Out1|Out2 }]; response_format. Returns { success, count }.

Inputs: targets (required), app, appToken, response_format.

singular_check_image_urlValidate an image URLA
Read-onlyIdempotent

Check that an image URL is well-formed, reachable, and looks like an image (by Content-Type or extension) before using it in set_image. Useful for pre-flighting rundown graphics.

Args: imageUrl; response_format. Returns { ok, status, contentType, reason }.

Inputs: imageUrl (required), response_format.

singular_find_nodesFind control nodes by type or titleA
Read-onlyIdempotent

Search all control nodes across the app's compositions, filtered by type and/or title substring. E.g. type='image' locates every fillable image node (the common newsroom case), type='text' for headlines. Saves hand-parsing the model tree.

Args: app/appToken; type (optional, e.g. 'image'); title_contains (optional); response_format. Returns { count, nodes: [{ subCompositionId, subCompositionName, id, title, type }] }.

Inputs: app, type, appToken, title_contains, +1 more.

singular_get_app_metadataGet control-app metadataA
Read-onlyIdempotent

Get an app instance's metadata: numeric ids, name, folder, linked composition id, thumbnail preview URL, live output URLs, and the derived control/command/model API URLs.

Args: app (alias) or appToken; response_format. Returns { id, name, accountId, compositionId, folder, thumbnail, outputUrl, broadcastOutputUrl, ...URLs }. Use this to confirm a token points at the intended app and to grab a preview/output URL.

Inputs: app, appToken, response_format.

singular_get_control_stateGet current live control stateA
Read-onlyIdempotent

Get the current on-air/staged state per sub-composition: its animation state (In/Out/Out1/Out2) and the current payload values keyed by control-node id. Use to see what is live right now.

Args: app/appToken; response_format. Returns { entries: [{ subCompositionId, subCompositionName, state, mainComposition, payload }] }.

Inputs: app, appToken, response_format.

singular_get_modelGet control model (structure + node types)A
Read-onlyIdempotent

Get the structural model of an app: every sub-composition and its control nodes with id, title, type (lowercase: text, textarea, number, image, color, checkbox, audio, json, timecontrol, button), default/reset values, and index. This is the schema-discovery call to run BEFORE writing content — payload keys must be control-node ids from here.

Args: app/appToken; sub_composition (optional name/id filter); response_format. Returns { subCompositions: [{ id, name, state, nodeCount, nodes: [{ id, title, type, index, defaultValue, resetValue }] }] }.

Inputs: app, appToken, response_format, sub_composition.

singular_get_output_urlsGet app output & preview URLsA
Read-onlyIdempotent

Return the app instance's live output URL, broadcast output URL, and auto-generated thumbnail preview URL (from metadata). Hand these to a human or a browser renderer to see what's on air. Note: Singular renders client-side; there is no server-side frame/snapshot render API.

Args: app/appToken; response_format. Returns { outputUrl, broadcastOutputUrl, thumbnail, name }.

Inputs: app, appToken, response_format.

singular_list_appsList registered appsA
Read-onlyIdempotent

List app aliases registered on this server (names + when added; never tokens). Args: response_format. Returns { count, apps: [{ alias, name, addedAt }] }.

Inputs: response_format.

singular_list_data_streamsList registered data streamsA
Read-onlyIdempotent

List the aliases of data streams registered on this server (no tokens). Args: response_format. Returns { count, dataStreams: [{ alias, addedAt }] }.

Inputs: response_format.

singular_list_rundown_templatesList rundown templatesA
Read-onlyIdempotent

List mapped rundown templates with their app, sub-composition, and field maps. Args: response_format. Returns { count, templates: [...] }.

Inputs: response_format.

singular_list_subcompositionsList sub-compositions (structure + current values)A
Read-onlyIdempotent

Convenience read that joins the model (structure + types) with the live control state (current values) by sub-composition id — one flat view per sub-composition with each node's id, title, type, and current value. The best single call to understand "what can I fill and what's in it now".

Args: app/appToken; response_format. Returns { subCompositions: [{ id, name, state, nodes: [{ id, title, type, currentValue, defaultValue, resetValue }] }] }.

Inputs: app, appToken, response_format.

singular_map_rundown_templateMap a rundown item type to a templateA

Bind a rundown item type (e.g. 'lower-third', 'ots', 'full-frame', 'ticker') to a specific app + sub-composition, plus a field map from story field names → control-node ids. Used by prepare_item and play_rundown_item. By default validates the sub-composition and node ids against the live model.

Args: name; appAlias; subCompositionName | subCompositionId; fieldMap (storyField → nodeId); validate (default true); response_format. Returns { name, appAlias, subComposition, fieldCount, validation }.

Inputs: name (required), appAlias (required), fieldMap (required), validate, +3 more.

singular_play_rundown_itemTake a rundown item to airA

Resolve a rundown item from its template and take it to air: fills the mapped payload and animates the sub-composition In, atomically. Optionally schedules an automatic Out after auto_out_ms (server-side, in-memory — does not survive a restart).

Args: template (name); fields (storyField → value); auto_out_ms (optional); response_format. Returns { template, subComposition, sentPayload, state, autoOutMs, unmappedFields, missingNodes }.

Inputs: fields (required), template (required), auto_out_ms, response_format.

singular_poll_onair_statePoll which sub-compositions are on airA
Read-onlyIdempotent

Poll the live control state and report which sub-compositions are currently In (on air) vs Out, plus what changed since the last poll of this app (Singular has no webhooks, so polling is the only awareness mechanism).

Args: app/appToken; response_format. Returns { onAir: [...], offAir: [...], changed: [{ subComposition, from, to }] }.

Inputs: app, appToken, response_format.

singular_prepare_itemResolve a rundown item (dry run — does not go to air)A
Read-onlyIdempotent

Resolve a rundown item against its template WITHOUT sending anything: maps story fields to control-node ids, validates them against the live model, and returns exactly the payload that play_rundown_item would send. Use to preview/verify before air.

Args: template (name); fields (storyField → value); response_format. Returns { template, subComposition, payload, unmappedFields, missingNodes, wouldSend }.

Inputs: fields (required), template (required), response_format.

singular_push_datastreamPush data to a data streamA

PUT a JSON payload to a data stream for sub-300ms delivery into any composition linked to it (scores, clocks, live stats) — bypasses the control API's per-minute rate limits. The payload shape must match what the linked sub-composition expects (configured in the Dashboard). Hard limit: 60 KB per package.

Args: stream (alias) or privateToken; data (JSON object); response_format. Returns { success, bytes }.

Inputs: data (required), stream, privateToken, response_format.

singular_register_appRegister a control-app token under an aliasA

Store a Singular app token under a friendly alias so later tools reference the app by alias (the raw token is never returned to the model). By default verifies the token by fetching metadata and uses the app's real name.

Args: alias; token; name (optional); verify (default true); response_format. Returns { alias, name, verified }.

Inputs: alias (required), token (required), name, verify, +1 more.

singular_register_data_streamRegister a data-stream private tokenA

Store a data stream's PRIVATE token under an alias so push_datastream can reference it by name (the raw token is never echoed back). Create the stream and get its private token from the Dashboard → Data Stream Manager.

Args: alias; privateToken; response_format. Returns { alias }.

Inputs: alias (required), privateToken (required), response_format.

singular_remove_appRemove a registered appA

Delete an app alias (and its stored token) from the registry. Args: alias; response_format. Returns { alias, removed }.

Inputs: alias (required), response_format.

singular_reset_nodesReset control nodes to their reset valuesA
Destructive

Clear/reset content by writing each targeted node back to its resetValue (from the model). Target specific nodeIds, or omit them to reset every node in the sub-composition — handy to blank a template between stories/segments. This changes on-air content if the sub-composition is live.

Args: app/appToken; subCompositionName | subCompositionId; nodeIds (optional — default all nodes in the sub-composition); response_format. Returns { success, subComposition, resetCount, nodeIds }.

Inputs: app, nodeIds, appToken, response_format, +2 more.

singular_server_infoSingular MCP server infoA
Read-onlyIdempotent

Report this server's runtime configuration and health without revealing secrets. Shows the API base URLs, whether a default token is set (boolean only), registry location and counts, and the HTTP timeout.

Args: response_format. Returns { server, version, apiBase, datastreamBase, defaultAppTokenConfigured, registryPath, registeredApps, registeredDataStreams, rundownTemplates, httpTimeoutMs }.

Inputs: response_format.

singular_set_imageSet an image control node by URLA

Set an image-type control node to a hosted image URL (the only way to place imagery — Singular has no upload API, so the asset must already be reachable at a public URL). Validates that the target node is type 'image' (via get_model) and, by default, that the URL is reachable and looks like an image before sending.

Args: app/appToken; subCompositionName | subCompositionId; nodeId (the image node's id) OR nodeTitle (matched from the model); imageUrl; validate_url (default true); force (default false — set true to send even if validation fails); response_format. Returns { success, subComposition, nodeId, imageUrl, urlCheck }.

Inputs: imageUrl (required), app, force, nodeId, +6 more.

singular_take_out_allTake out all output (clear the app)A
Destructive

Animate every overlay in the control app to its Out state — the 'clear the screen' / segment-end / panic control. Uses POST /command { action: 'TakeOutAllOutput' } (the only documented command action).

Args: app/appToken; response_format. Returns { success }.

Inputs: app, appToken, response_format.

singular_update_and_animateFill content and animate in one callA

Atomically set a sub-composition's payload AND its animation state in a single PATCH, so the template is filled and taken to air together (avoids a flash of stale content that a separate fill-then-animate can cause).

Args: app/appToken; subCompositionName | subCompositionId; payload (control-node id → value); state (In|Out|Out1|Out2); response_format. Returns { success, subComposition, state }.

Inputs: state (required), payload (required), app, appToken, +3 more.

singular_update_contentUpdate control-node contentA

Fill control nodes of one or more sub-compositions in a single PATCH. Each update targets a sub-composition (by name or id) and supplies a payload mapping control-node ids → values.

Value formats by node type: text/textarea→string, number→number, image/audio→URL string, color→{r,g,b,a} (0–255) or hex string, checkbox→boolean, selection→option value, json→object. Node ids come from get_model / find_nodes.

Batching multiple sub-compositions here (rather than many calls) conserves rate limit. This sets content only — it does NOT animate; use animate_state or update_and_animate to take on/off air.

Args: app/appToken; updates: [{ subCompositionName | subCompositionId, payload }]; response_format. Returns { success, updatedCount }.

Inputs: updates (required), app, appToken, response_format.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.1/5.0

    Scored across 25 tools

    Disambiguation5/5

    Every tool has a clearly distinct purpose—from registration and discovery to content updates, animation, and rundown management. Even similar tools like update_content, update_and_animate, set_image, and reset_nodes are clearly differentiated by their descriptions and use cases.

    Naming Consistency5/5

    All 25 tools follow a consistent singular_verb_noun pattern. Verbs like 'list', 'get', 'register', 'update' are used uniformly, and compound nouns (e.g., 'onair_state', 'output_urls') maintain readability without mixing conventions.

    Tool Count4/5

    At 25 tools, the count is slightly above the ideal range (3–15), but each tool serves a necessary function within the complex domain of broadcast graphics control. The set feels comprehensive without being bloated.

    Completeness4/5

    The tool surface covers registration, discovery, content manipulation, animation, rundown workflow, and server introspection. Minor gaps exist—no tool to remove data streams or delete rundown templates—but these are secondary and do not critically impede core workflows.

    Related MCP Connectors

    Related MCP Servers