Connect an AI agent (MCP)

Connect Claude, ChatGPT, Claude Code, Cursor or the Claude API to Brix over MCP. Sign in from the app or use an API key. Endpoint, setup and tools.

View as Markdown

Brix runs a Model Context Protocol (MCP) server. Connect an agent once and it can find your screens, explain why one is blank, cast content, build playlists and pull proof-of-play and uptime reports.

Connection details

SettingValue
Endpointhttps://api.brixsignage.com/v1/mcp
TransportStreamable HTTP. POST one JSON-RPC 2.0 message; the reply is a JSON response. No SSE stream.
AuthSign in with OAuth 2.1 (claude.ai, ChatGPT and other connector apps), or Authorization: Bearer <api key> with the same keys as the REST API.
Protocol versions2024-11-05, 2025-03-26, 2025-06-18
Rate limit30 requests per minute per caller, inside the workspace’s 600 per minute. Over it: HTTP 429 with Retry-After.
Methodsinitialize, ping, tools/list, tools/call, resources/list, resources/templates/list, resources/read, prompts/list, prompts/get

There are two ways to connect:

  • Sign in (claude.ai, ChatGPT). Add the endpoint as a custom connector. The app opens a Brix page where you sign in and choose what it may do. No key to copy.
  • API key (Claude Code, Cursor, Claude Desktop config file, the Claude and OpenAI APIs). Create a key and put it in the client’s config.

Either way, the permissions you pick decide what the agent can do. See Authentication and scopes.

claude.ai and ChatGPT: sign in

  1. Copy the endpoint: https://api.brixsignage.com/v1/mcp.
  2. Add it as a custom connector.
    • Claude: Settings > Connectors > Add custom connector. Claude Desktop uses the same connectors as claude.ai.
    • ChatGPT: Settings > Apps and Connectors > Advanced settings, turn on Developer mode, then Create.
  3. The app opens a Brix page. Sign in, then pick the workspace, a location if you want to limit the app to one, and the permission areas. Click Allow.

The app acts as you, with only the areas you picked. Viewing screens is always included, so picking no areas gives a read-only connection. The app cannot get more than you have.

Every connection is listed in the CMS under Settings > API & MCP > Connected apps, with who connected it and when it was last used. Revoke ends it at once: the app’s next call is refused and it has to be connected again.

For client developers: the server follows the MCP authorization spec. A call without a token gets 401 with a WWW-Authenticate header that points to the protected-resource metadata at /.well-known/oauth-protected-resource/v1/mcp. The authorization server is https://api.brixsignage.com (metadata at /.well-known/oauth-authorization-server). It supports authorization code with PKCE (S256), dynamic client registration and client ID metadata documents. Access tokens last an hour. Refresh tokens rotate on every use. An access token from this flow works only at /v1/mcp.

API key clients

1. Create a key for the agent

In the CMS: Settings > API & MCP > New key. Give it only the areas the agent needs. For a first try, a view-only key (Screens, Playlists, Schedules, Files, Proof of Play) is safe: the agent can answer questions but cannot change what plays.

The same page has a Model Context Protocol (MCP) section with the endpoint and Copy config and Copy command buttons.

Claude Code

claude mcp add --transport http brix https://api.brixsignage.com/v1/mcp \
  --header "Authorization: Bearer $BRIX_API_KEY"

Then run claude and ask: “Which of my Brix screens are offline, and why?” Check the connection with /mcp.

Claude Desktop

To connect by signing in, add Brix as a custom connector (see above). To use a key instead: Claude Desktop’s config file starts local (stdio) servers, so connect through the mcp-remote bridge. It needs Node.js. Open Settings > Developer > Edit Config and add:

{
  "mcpServers": {
    "brix": {
      "command": "npx",
      "args": [
        "-y", "mcp-remote",
        "https://api.brixsignage.com/v1/mcp",
        "--header", "Authorization:${BRIX_AUTH}"
      ],
      "env": { "BRIX_AUTH": "Bearer ak_us_live_..." }
    }
  }
}

Restart Claude Desktop. The header value goes through env because some platforms split arguments that contain spaces.

Cursor

Add to ~/.cursor/mcp.json (all projects) or .cursor/mcp.json (one project):

{
  "mcpServers": {
    "brix": {
      "url": "https://api.brixsignage.com/v1/mcp",
      "headers": { "Authorization": "Bearer ${env:BRIX_API_KEY}" }
    }
  }
}

${env:BRIX_API_KEY} reads the key from your environment, so it stays out of the file. You can also paste the key in place of it. Open Cursor Settings > MCP to check that brix is connected.

Other clients that accept a remote URL with custom headers use the same shape. This is the config that Copy config in the CMS gives you:

{
  "mcpServers": {
    "brix": {
      "url": "https://api.brixsignage.com/v1/mcp",
      "headers": { "Authorization": "Bearer <YOUR_API_KEY>" }
    }
  }
}

OpenAI API

The OpenAI Responses API sends your key in a header to a remote MCP server:

curl https://api.openai.com/v1/responses \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-5",
    "tools": [{
      "type": "mcp",
      "server_label": "brix",
      "server_url": "https://api.brixsignage.com/v1/mcp",
      "headers": { "Authorization": "Bearer '"$BRIX_API_KEY"'" },
      "require_approval": "never"
    }],
    "input": "Which of my screens are offline?"
  }'

"require_approval": "never" lets the model call tools without a pause. Use it with a view-only key, or set it to "always" and approve each call.

Claude API

The Messages API connects to remote MCP servers with the MCP connector:

curl https://api.anthropic.com/v1/messages \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "anthropic-beta: mcp-client-2025-11-20" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-opus-5",
    "max_tokens": 4096,
    "mcp_servers": [{
      "type": "url",
      "name": "brix",
      "url": "https://api.brixsignage.com/v1/mcp",
      "authorization_token": "'"$BRIX_API_KEY"'"
    }],
    "tools": [{ "type": "mcp_toolset", "mcp_server_name": "brix" }],
    "messages": [{ "role": "user", "content": "Which of my screens are offline?" }]
  }'

Test the endpoint with curl

curl https://api.brixsignage.com/v1/mcp \
  -H "Authorization: Bearer $BRIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"whoami","arguments":{}}}'

A tool result comes back as a text block plus structuredContent. A failed tool call returns isError: true with the error text, so the agent can correct itself.

How the tools behave

  • Start with whoami in an unfamiliar workspace. It returns the key, the workspace and its permissions.
  • search_screens and search_media resolve names to real ids. Never guess an id.
  • cast_content shows existing content for a limited time (default one hour). assign_content changes what a screen plays by default. quick_post writes an announcement and casts it in one step.
  • list_api_routes finds any documented REST endpoint the key allows, for anything without a dedicated tool. api_get reads it. Writes are separate tools: api_create (POST), api_update (PATCH or PUT) and api_delete. Key clients set up before this split can still call call_api; connections made by signing in cannot.
  • Every tool declares MCP annotations: read-only, destructive, idempotent, and whether it reaches outside Brix. Clients use them to decide when to ask you first.
  • Content created over MCP lands as a draft where the location requires approval.
  • Every call is recorded in the workspace audit log.

Things to ask

  • “Which of my screens are offline, and why?”
  • “Why is the lobby screen blank? Show me what it’s displaying right now.”
  • “Put the Summer Menu playlist on every screen at the Downtown location for the next two hours.”
  • “How many times did the Burger Promo play last week, and on which screens?”
  • “Give me this month’s uptime report, worst screens first.”

Your AI app asks before it runs a tool that changes what a screen shows.

Privacy, disconnecting and support

  • The app sees only what the tools return for the workspace, location and permission areas you approved. Brix does not receive your conversation with the app, only the tool calls it makes.
  • Disconnect any time in Brix under Settings > API & MCP > Connected apps. Revoke ends the connection at once: the app’s next call is refused. Removing the connector in the app stops the app using it.
  • How Brix handles data is in the Privacy Policy. For help, email [email protected].

Tool catalog

47 tools. Each needs the permission shown on the API key. Read-only tools never change anything; the others say what they change.

ToolPermissionBehavior
capture_device_framescreen.editcreates
get_screen_whyscreen.viewread-only
diagnose_screenscreen.viewread-only
list_open_alertsscreen.viewread-only
assign_contentscreen.castchanges existing data, idempotent
send_commandscreen.editchanges existing data
trigger_emergency_templatescreen.editchanges existing data
upload_media_from_urlmedia.createcreates, reaches outside Brix
mint_upload_urlmedia.createcreates
confirm_media_uploadmedia.createcreates
bulk_create_playlistsplaylist.createcreates
bulk_create_org_nodesorg-unit.createcreates
search_screensscreen.viewread-only
bulk_create_schedulesschedule.createcreates
assign_screen_to_nodescreen.editchanges existing data, idempotent
create_creativescreative.createcreates
whoamiscreen.viewread-only
list_api_routesscreen.viewread-only
api_getscreen.viewread-only
api_createscreen.viewchanges existing data
api_updatescreen.viewchanges existing data
api_deletescreen.viewchanges existing data, idempotent
search_mediamedia.viewread-only
list_emergency_templatesemergency-override.viewread-only
list_pending_approvalsscreen.viewread-only
list_recycle_binscreen.viewread-only
update_playlistplaylist.editchanges existing data
update_mediamedia.editchanges existing data, idempotent
create_web_linkmedia.createcreates, reaches outside Brix
configure_web_linkmedia.editcreates, idempotent
render_web_linkmedia.editcreates, idempotent, reaches outside Brix
restore_recycle_bin_itemscreen.viewcreates, idempotent
decide_approvalscreen.viewchanges existing data
cast_contentscreen.castchanges existing data, idempotent
end_castscreen.castchanges existing data, idempotent
quick_postscreen.castchanges existing data
create_screen_groupscreen.editcreates
add_screens_to_groupscreen.editcreates, idempotent
remove_screens_from_groupscreen.editchanges existing data, idempotent
sync_data_sourceintegration.editcreates, reaches outside Brix
refresh_all_data_sourcesintegration.editcreates, reaches outside Brix
get_proof_of_playplayback-log.viewread-only
get_screen_outagesscreen.viewread-only
get_screen_display_historyscreen.viewread-only
get_uptime_reportscreen.viewread-only
search_audit_logaudit-log.viewread-only
bulk_create_celebration_entriesintegration.createcreates

capture_device_frame

screen.editcreates

Ask a screen to capture a screenshot now and return the newest stored frame — `latestScreenshotId`, `latestFrameUrl`, `latestFrameAt`, and `fresh` (true when the frame post-dates this request). To see the picture, pass `latestFrameUrl` to api_get: the image comes back as { contentType, totalBytes, bodyBase64 }. Use to see what's actually on the panel.

ArgumentTypeRequiredDescription
screenIdstringyes

get_screen_why

screen.viewread-only

Explain why a screen is showing what it's showing, in plain language. Returns the resolution chain (deactivated / emergency / cast / scheduled / default).

ArgumentTypeRequiredDescription
screenIdstringyesThe screen ID, e.g. scr_aurora_42

diagnose_screen

screen.viewread-only

"What might be wrong" — a likely-cause troubleshooting verdict for a screen that looks blank/wrong, reasoned by ELIMINATION from verifiable signals (online/offline, render-truth, resource pressure, hardware early-warning). Returns one likely cause + a guided next-step checklist. Note: the player cannot read TV/HDMI/input state, so when the stick is healthy and outputting content the verdict is the `downstream` layer with `guided: true` and a TV/input/cable CHECKLIST — a guided guess, NOT a detection. A definitive CEC `display` read (tv-off/disconnected) is the one real display signal.

ArgumentTypeRequiredDescription
screenIdstringyesThe screen ID, e.g. scr_aurora_42

list_open_alerts

screen.viewread-only

List currently-open alert events, newest first. Optionally filter by severity, screen or org node. Paginate with offset; the response reports total + hasMore.

ArgumentTypeRequiredDescription
severityinfo | warning | criticalno
screenIdstringno
nodeIdstringnoLimit to alerts raised at (or under) this org node.
offsetintegernoSkip this many matching alerts (default 0).

assign_content

screen.castchanges existing data, idempotent

Push a playlist, schedule, app, media or layout to a screen — or to MANY screens at once via screenIds. Requires `screen.cast`.

ArgumentTypeRequiredDescription
screenIdstringnoSingle target screen.
screenIdsarraynoBulk alternative: assign the same content to every listed screen (max 200).
contentKindplaylist | schedule | app | mediayes
contentIdstringno

send_command

screen.editchanges existing data

Send a runtime command (reboot, refresh, screenshot, clear-cache, cec-on, cec-off) to one screen or many (screenIds). Reboot interrupts what's on screen — confirm with the operator when unsure.

ArgumentTypeRequiredDescription
screenIdstringno
screenIdsarraynoBulk alternative: command every listed screen (max 200) in one call.
kindreboot | refresh | screenshot | clear-cache | cec-on | cec-offyes

trigger_emergency_template

screen.editchanges existing data

Fire a pre-armed emergency template (Lockdown, Severe weather, Fire drill, …) across its in-scope screens — an immediate takeover. List ids first with the brix://emergency-templates resource; confirm scope with the operator.

ArgumentTypeRequiredDescription
templateIdstringyes

upload_media_from_url

media.createcreates, reaches outside Brix

Download a file from a public URL into Brix media. Returns the new asset id + URL. Use for migration — pull from a Drive share, S3, the customer's old-CMS export, etc. Max 500 MB; use mint_upload_url for larger.

ArgumentTypeRequiredDescription
urlstringyesPublicly fetchable http(s) URL.
namestringyesDisplay name for the asset.
folderIdstringno
tagsarrayno

mint_upload_url

media.createcreates

Get a one-shot upload URL for a large file. The model's host PUTs bytes to it then calls confirm_media_upload with the same `uploadId`.

ArgumentTypeRequiredDescription
contentTypestringyes
namestringyes

confirm_media_upload

media.createcreates

Finalize an upload after PUTting bytes to a mint_upload_url. Creates the media_assets row.

ArgumentTypeRequiredDescription
uploadIdstringyes
folderIdstringno
tagsarrayno

bulk_create_playlists

playlist.createcreates

Create many playlists at once. Max 500 per call. Items reference media or app-instance ids that must already exist in this workspace.

ArgumentTypeRequiredDescription
playlistsarrayyes

bulk_create_org_nodes

org-unit.createcreates

Create many org-tree nodes in one call. Max 1000. Each node hangs off `parentId` (an existing node), `parentExternalId` (the Location ID of an existing node or of an EARLIER node in this same call — build regions then stores in one call), or the Space root when both are omitted. `externalId` sets the node's Location ID (store number / region code; unique per workspace) — SSO claim mapping matches IdP location claims against it. A node whose externalId already exists is skipped, so re-running an import is safe.

ArgumentTypeRequiredDescription
nodesarrayyes

search_screens

screen.viewread-only

Filter the screen fleet. All criteria optional and AND-ed. Returns up to 200 per page; the response carries total + hasMore so you can page with `offset` instead of re-querying.

ArgumentTypeRequiredDescription
qstringnoSubstring match on screen name.
statusonline | offline | pairingno
nodeIdstringnoLimit to screens at (or under) this org node.
tagstringno
offsetintegernoSkip this many matches (default 0).

bulk_create_schedules

schedule.createcreates

Create many schedules in one call. Max 200; up to 50 blocks each. Times are HH:MM 24-hour in the SCREEN's timezone.

ArgumentTypeRequiredDescription
schedulesarrayyes

assign_screen_to_node

screen.editchanges existing data, idempotent

Move a screen to a different org node — useful for post-migration cleanup. Requires `screen.edit`.

ArgumentTypeRequiredDescription
screenIdstringyes
nodeIdstringnonull to clear; otherwise the destination node id.

create_creatives

creative.createcreates

Author new creatives (data-bound canvases) from scratch. This is how you BUILD templates programmatically. Each: { name, stage?:'landscape'|'portrait', backgroundUrl?, dataSourceId?, nodeId?, boxes:[…] }. A box is { kind, x, y, w, h, … } where x/y/w/h are 0–1 fractions of the stage and kind is one of text|field|list|image|shape|celebrations|event-board|signage|group|slot. Shapes use { shape:'rect'|'circle'|'line'|'triangle', fill, stroke, strokeWidth, cornerRadius }; text uses { text, color, fontWeight, align, fontSize? }; field/list bind via { field:'dot.path' }; image uses { url }; slot is a transparent hole the screen fills with real content, like a layout zone — { slot: { kind:'media'|'playlist'|'app', id, name } } naming a file / playlist / app in THIS workspace (or slot:null for an empty hole the customer fills later; borderRadius clips what plays there); signage is a full Signage Template Engine block — { signage: { archetype, style, mode, industry, orientation, content, anim } } — rendered 1:1 (layout + animations), usually one full-bleed box (x:0,y:0,w:1,h:1). Boxes are validated + clamped server-side; an invalid box rejects the whole call. Max 100 creatives per call. Designs also pass a quality gate (the Brix template standard): REJECTED with named findings when text pins under 24px (1920w scale), an authored contrast pair computes <3:1, or one layer fully hides another; WARNED as structured diagnostics for busy layouts (>36 content layers), >5 font sizes, >2 font families, hand-frozen list rows that should be ONE list box, static clock/date copy instead of {{now.*}} tokens, and competing loop animations. Treat warnings as defects: fix and re-emit.

ArgumentTypeRequiredDescription
creativesarrayyes

whoami

screen.viewread-only

Who this connection is and what it may do. Useful at the start of work in an unfamiliar workspace. Returns the actor (an API key with the org node it is pinned to, or the signed-in user), the workspace id + name, and the flat permission set. A pinned key simply cannot see or write outside its branch, so this is usually the answer to an unexplained 403 or an empty list. The permission set is a planning hint: node-precise checks still run on every write.

list_api_routes

screen.viewread-only

Lists the Brix REST API (https://brixsignage.com/developers/api/): every documented endpoint as { method, path, description }. Everything the CMS UI can do lives behind one of these routes. A GET route is read with api_get; POST, PUT/PATCH and DELETE routes go through api_create, api_update and api_delete. Narrow with `prefix` (e.g. '/v1/screens') or free-text `q` (matched against path + description). One page is 200 routes and the surface is larger than that: while the reply carries `nextOffset`, call again with that `offset` to see the rest.

ArgumentTypeRequiredDescription
prefixstringnoPath prefix filter, e.g. '/v1/playlists'.
qstringnoCase-insensitive substring match on path or description.
offsetnumbernoSkip this many matching routes — pass the `nextOffset` from the previous reply to page through the whole surface.

api_get

screen.viewread-only

Sends a GET request to the Brix REST API (reference: https://brixsignage.com/developers/api/) with this connection's own permissions and returns { status, body }. Find routes with list_api_routes. Query strings may be included in `path`. A route that answers with bytes (a media file, a poster, an invoice PDF, a screenshot) comes back as { contentType, totalBytes, bodyBase64 }. A 4xx/5xx returns the API's error body.

ArgumentTypeRequiredDescription
pathstringyesAbsolute API path starting with /v1/ with real ids in place of :params, optionally with ?query.

api_create

screen.viewchanges existing data

Sends a POST request to the Brix REST API (reference: https://brixsignage.com/developers/api/) with this connection's own permissions: creates a record or runs an action (some POST routes act on screens, e.g. commands). Find POST routes with list_api_routes. Pass a JSON `body`, or `bodyBase64` + `contentType` for the few routes that take raw bytes; whole media files upload better with mint_upload_url. Pass `idempotencyKey` on a create you might retry and the API dedupes on it. Returns { status, body }; a 4xx/5xx returns the API's error body.

ArgumentTypeRequiredDescription
pathstringyesAbsolute API path starting with /v1/ with real ids in place of :params, optionally with ?query.
bodyanynoJSON request body. Sent as application/json. Ignored when `bodyBase64` is present.
bodyBase64stringnoRaw request body, base64-encoded, for the routes that take bytes (POST /v1/media/:id/poster, /captions/upload, /captions/transcribe, PUT /v1/media/upload/:uploadId). Requires `contentType`. Capped at ~6 MB encoded.
contentTypestringnoContent-Type for `bodyBase64`, e.g. image/jpeg, text/vtt, audio/wav. Required with bodyBase64.
idempotencyKeystringnoForwarded as the `idempotency-key` header so a retried create cannot duplicate. Use any stable unique string per logical create.

api_update

screen.viewchanges existing data

Sends a PATCH (default) or PUT request to the Brix REST API (reference: https://brixsignage.com/developers/api/) with this connection's own permissions, changing an existing record. Find PATCH/PUT routes with list_api_routes. Pass a JSON `body`, or `bodyBase64` + `contentType` for PUT /v1/media/upload/:uploadId. Returns { status, body }; a 4xx/5xx returns the API's error body.

ArgumentTypeRequiredDescription
methodPATCH | PUTnoPATCH (the default) or PUT, as the route lists it.
pathstringyesAbsolute API path starting with /v1/ with real ids in place of :params, optionally with ?query.
bodyanynoJSON request body. Sent as application/json. Ignored when `bodyBase64` is present.
bodyBase64stringnoRaw request body, base64-encoded, for the routes that take bytes (POST /v1/media/:id/poster, /captions/upload, /captions/transcribe, PUT /v1/media/upload/:uploadId). Requires `contentType`. Capped at ~6 MB encoded.
contentTypestringnoContent-Type for `bodyBase64`, e.g. image/jpeg, text/vtt, audio/wav. Required with bodyBase64.

api_delete

screen.viewchanges existing data, idempotent

Sends a DELETE request to the Brix REST API (reference: https://brixsignage.com/developers/api/) with this connection's own permissions. Most content deletes move the item to the recycle bin for 30 days (see list_recycle_bin); a purge route deletes permanently. Find DELETE routes with list_api_routes. Returns { status, body }; a 4xx/5xx returns the API's error body.

ArgumentTypeRequiredDescription
pathstringyesAbsolute API path starting with /v1/ with real ids in place of :params, optionally with ?query.

search_media

media.viewread-only

Search the media library by name, kind, folder or tag. Resolves media names to asset ids before building playlists, casting or assigning. Returns id/name/kind/tags/state/duration per asset; results are node-scoped to this key.

ArgumentTypeRequiredDescription
qstringnoCase-insensitive substring match on asset name.
kindstringnoAsset kind filter, e.g. image | video | audio | pdf | web | rss.
folderIdstringnoOnly assets filed in this folder.
tagstringnoExact tag match (case-insensitive).
limitintegernoPage size (default 50).
offsetintegernoSkip this many matches (default 0).

list_emergency_templates

emergency-override.viewread-only

List the pre-armed emergency takeover templates (Lockdown, Severe weather, Fire drill, …) with their ids, scope and severity. Pass an id to trigger_emergency_template — never guess one.

list_pending_approvals

screen.viewread-only

List approval requests (content awaiting an approve/reject decision before it airs). Defaults to pending; pass state to see decided history. Content created over MCP lands as 'draft' when the location requires approval — check here to see what's waiting.

ArgumentTypeRequiredDescription
statepending | approved | rejected | withdrawnnoFilter by decision state (default pending).
limitintegernoPage size (default 50).

list_recycle_bin

screen.viewread-only

List soft-deleted items across every content kind within the 30-day restore window, with what used them before deletion. Pair with restore_recycle_bin_item for 'undo that delete' requests. Deleted SCREENS appear here too — DELETE /v1/screens/:id soft-deletes into the same 30-day window.

update_playlist

playlist.editchanges existing data

Edit an EXISTING playlist: rename/describe it, set its play window, toggle shuffle, append items, remove items, patch item duration/fit, or reorder. Compose any subset in one call; each change runs through the same validation + approval invalidation as the CMS editor. Requires `playlist.edit` at the playlist's node.

ArgumentTypeRequiredDescription
playlistIdstringyes
namestringnoNew display name.
descriptionstringno
shufflebooleanno
fullscreenbooleannoWhen this playlist plays inside a layout, EVERY item fills the whole screen and the other zones hide. Per-item: patchItems[].fullscreen.
startsAtstringnoISO datetime the playlist starts airing; null clears.
expiresAtstringnoISO datetime the playlist stops airing; null clears.
appendItemsarrayno
patchItemsarrayno
removeItemIdsarrayno
reorderItemIdsarraynoThe COMPLETE item order after the move — unknown ids are dropped, the rest keep this sequence.

update_media

media.editchanges existing data, idempotent

Update a media asset's metadata: rename it, retag it, move it to another folder, set alt text, set how it fills the screen, or give it an expiry (+ optional auto-archive into the recycle bin). Bytes are never touched. Requires `media.edit`.

ArgumentTypeRequiredDescription
mediaIdstringyes
namestringno
tagsarraynoReplaces the whole tag list.
folderIdstringnoDestination folder in THIS workspace; null moves to the library root.
altTextstringno
fitcontain | cover | fill | blur-fillnoHow this file fills any screen or zone it plays in: contain (whole picture, black bars), blur-fill (whole picture, blurred edges), cover (fills, crops the edges), fill (fills, distorts). Applies everywhere the file plays; a playlist slot can override it.
expiresAtstringnoISO datetime after which the asset stops airing; null clears.
autoArchiveOnExpirybooleannoSoft-delete the asset into the recycle bin when it expires.

restore_recycle_bin_item

screen.viewcreates, idempotent

Restore ONE soft-deleted item from the recycle bin (kind from list_recycle_bin). Playlists/screens referencing it resume automatically where they still do. Each kind's own permission applies (e.g. playlist.delete restores playlists). Purging (permanent deletion) is not part of this tool.

ArgumentTypeRequiredDescription
kindscreen | media | playlist | schedule | layout | creative | app | data-source | signage | banner | media-folderyes
idstringyes

decide_approval

screen.viewchanges existing data

Approve or reject a pending approval request (from list_pending_approvals). Rejecting requires a reason. Only works when this key's identity is actually an approver for the request's chain — the API enforces that, not this tool.

ArgumentTypeRequiredDescription
approvalIdstringyes
decisionapprove | rejectyes
reasonstringnoRequired for reject.

cast_content

screen.castchanges existing data, idempotent

Show EXISTING content on screens as a TIMED takeover (a cast) — screens return to their normal schedule when it expires. Scope it to everything (all), an org node subtree, or explicit screenIds. This is the tool for 'put the lunch menu on the lobby screens until 2pm'. Requires `screen.cast`.

ArgumentTypeRequiredDescription
contentKindmedia | playlist | app | creative | scheduleyes
contentIdstringyes
scopeKindall | node | screensnoDefault all — every screen this key can reach.
scopeNodeIdstringnoWith scopeKind=node: the org-node subtree to cast across.
screenIdsarraynoWith scopeKind=screens: explicit targets (max 500).
expiresAtstringnoISO datetime the cast ends. Default: one hour from now.
headlinestringnoDisplay name recorded for the cast.

end_cast

screen.castchanges existing data, idempotent

End an active cast early — its screens return to scheduled content immediately. Requires `screen.cast`.

ArgumentTypeRequiredDescription
castIdstringyes

quick_post

screen.castchanges existing data

Author a headline (+ optional body) announcement and put it on screens in ONE step — the agent twin of Quick Post. Creates a branded takeover creative and casts it across the chosen scope. Needs creative.create AND screen.cast.

ArgumentTypeRequiredDescription
headlinestringyes
bodystringno
toneannouncement | alert | celebrate | infonoPicks the background treatment. Default announcement.
scopeKindall | node | screensnoDefault all.
scopeNodeIdstringno
screenIdsarrayno
expiresInSecintegernoHow long the message stays up (default 3600).

create_screen_group

screen.editcreates

Create a named, saved cohort of screens (optionally seeding members). Groups are the reusable target for bulk commands, casts and assignments. Requires `screen.edit`.

ArgumentTypeRequiredDescription
namestringyes
descriptionstringno
screenIdsarraynoInitial membership; foreign/out-of-scope ids are skipped silently.

add_screens_to_group

screen.editcreates, idempotent

Add screens to a saved screen group. Idempotent; out-of-scope ids are skipped, never fatal. Requires `screen.edit`.

ArgumentTypeRequiredDescription
groupIdstringyes
screenIdsarrayyes

remove_screens_from_group

screen.editchanges existing data, idempotent

Take screens out of a saved screen group — only the label drops, the screens themselves are untouched. Requires `screen.edit`.

ArgumentTypeRequiredDescription
groupIdstringyes
screenIdsarrayyes

sync_data_source

integration.editcreates, reaches outside Brix

Pull fresh data through one connected feed (CSV, Google Sheets, POS…) now, so data-bound apps and creatives show current values. Requires `integration.edit`.

ArgumentTypeRequiredDescription
dataSourceIdstringyes

refresh_all_data_sources

integration.editcreates, reaches outside Brix

Sync EVERY connected data feed in this workspace once. Use before big announcements so menus/prices are current. Requires `integration.edit`.

get_proof_of_play

playback-log.viewread-only

Proof-of-play rollup for a time window: plays, watch time, skips and failures per piece of content, plus which screens reported. Answers 'how did the promo actually do?'. Requires `playback-log.view`.

ArgumentTypeRequiredDescription
fromstringnoISO datetime; default 7 days ago.
tostringnoISO datetime; default now.
screenIdstringnoRestrict to one screen's report.

get_screen_outages

screen.viewread-only

One screen's outage history over the last N days (default 30, capped at the offline retention window): every outage with when / how long / a plain-language CAUSE (Wi-Fi dropped, power cut, Brix unreachable, …) and its evidence, plus a rollup sentence ('went down 6 times in the last 30 days, all Wi-Fi drops'). Planned darkness (operating hours) is listed but not counted. Requires `screen.view`.

ArgumentTypeRequiredDescription
screenIdstringyes
daysnumbernoWindow in days; default 30.
limitnumbernoMax rows; default 50, max 200.

get_screen_display_history

screen.viewread-only

What the TV itself has been doing, which the outage history cannot show because the player is online and playing through all of it: every time the panel was switched off, switched to another HDMI input, lost its HDMI connection, or came back — newest first, each with how long the previous state lasted — plus its current state. Only a device that can read its panel (a Signage Stick over CEC) produces rows; a web or Windows player returns none and no current state. Default window is the telemetry retention (14 days). Requires `screen.view`.

ArgumentTypeRequiredDescription
screenIdstringyes
daysnumbernoWindow in days; default and cap = the telemetry retention (14).
limitnumbernoMax rows; default 50, max 500.

get_uptime_report

screen.viewread-only

Uptime/SLA summary for a window: online % per screen, downtime windows (each with its cause), worst offenders first. Every screen, location and the fleet carry an `outageSummary` — cause counts, the top cause and a plain-language insight such as 'went down 6 times in the last 30 days, all Wi-Fi drops'. Requires `screen.view`.

ArgumentTypeRequiredDescription
fromstringnoISO datetime; default 7 days ago.
tostringnoISO datetime; default now.
screenIdstringno

search_audit_log

audit-log.viewread-only

Read the workspace audit log, newest first — who changed what, including changes made by MCP tools themselves. Keyset-paged via cursor. Requires `audit-log.view`.

ArgumentTypeRequiredDescription
limitintegernoPage size (default 200).
cursorstringnonextCursor from the previous page.

bulk_create_celebration_entries

integration.createcreates

Import birthdays/anniversaries/etc. for the Celebrations app in bulk — the classic migration job. Max 100 per call; each needs at least a name plus month/day. Requires `integration.create`.

ArgumentTypeRequiredDescription
entriesarrayyes

Resources

Read with resources/read. Each one checks its own view permission.

URIWhat it returns
brix://screensEvery screen in the workspace, with status + location + assigned content.
brix://playlistsPlaylists with their items (refKind/refId/duration), approval state and org node.
brix://schedulesTime-of-day schedules and which screens they target.
brix://alerts/openCurrently open alert events — anything offline / storage-critical / playback-failing.
brix://org-treeThe hierarchical org-node structure (Spaces / districts / schools / rooms).
brix://mediaImages, videos, PDFs, web links and fonts — id, name, kind, folder, tags, state.
brix://creativesCanvas creatives (data-bound designs) with stage + box count.
brix://appsInstalled app instances (weather, RSS, menus…) with their app key + node.
brix://layoutsMulti-zone layouts a screen can be pointed at, each with its `theme` (the Brand Kit look — { source: "brand", mode?, radius? } — or null for a bare canvas). Zone geometry, bindings and per-zone `frame` / `radius` / `role` come from GET /v1/layouts/:id.
brix://data-sourcesConnected data feeds (CSV, Sheets, POS…) with connection + last-sync status.
brix://screen-groupsNamed saved cohorts of screens — the unit for bulk commands and casts.
brix://emergency-templatesPre-armed emergency takeover recipes; pass one's id to trigger_emergency_template.
brix://approvals/pendingContent awaiting an approve/reject decision before it can air.
brix://screens/{screenId}/whyThe resolution chain (deactivated / emergency / cast / scheduled / default) for one screen.
brix://playlists/{playlistId}One playlist WITH its ordered items.
brix://media/{mediaId}/usageWhere a media asset is used (playlists/creatives/layouts) and when it last played.

Prompts

PromptWhat it doesArguments
fleet_health_reportA daily ops digest: fleet status, open alerts, worst uptime offenders and what to do about each.none
diagnose_offline_screenWalk the likely-cause diagnosis for one screen and hand back a guided next-step checklist.screen_id (optional)
onboard_new_locationSet up a new org location end-to-end: create the node, home its screens there and assign starter content.location_name
weekly_content_reviewProof-of-play rollup for the last 7 days: top content, skipped/failed plays, unused media worth pruning.none