Skip to main content

Broadcast

3 min read

MCP server — AI assistants without a raw post tool

@sizl/broadcast-mcp is the only sanctioned path for an AI assistant to interact with the broadcast runtime. It exposes preview, queue, kill, list_pending, and verify — but never a raw post.


Install

The MCP server runs over stdio. Wire it into Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json on macOS) or any MCP-compatible client:

{
  "mcpServers": {
    "broadcast": {
      "command": "npx",
      "args": ["@sizl/broadcast-mcp"],
      "env": {
        "BROADCAST_MCP_ALLOWLIST": "bluesky,mastodon",
        "BROADCAST_MCP_OPERATOR": "jason",
        "BROADCAST_MCP_NOTIFY_WEBHOOK": "https://hooks.slack.com/services/..."
      }
    }
  }
}

BROADCAST_MCP_ALLOWLIST is mandatory and explicit. If unset, no platform is allowlisted and every preview + queue rejects. Default-deny is the correct posture for an AI-facing surface — the AI doesn't get to invent destinations.

Tools

ToolWhat it does
broadcast.previewRuns the credential + voice gate on a draft. Returns rendered preview or a structured rejection. Does NOT publish.
broadcast.queueSame gate as preview, then enqueues the draft for Tier 2 approval. The MCP server never publishes — the operator approves out-of-band.
broadcast.killKills a queued draft. Ownership-guarded: only the operator who queued the draft can kill it unless BROADCAST_MCP_ALLOW_CROSS_OPERATOR_KILL=1.
broadcast.list_pendingRead-only view of the Tier 2 queue. Each entry includes queuedBy so the assistant knows who owns what.
broadcast.verifyRun the credential + voice scan on arbitrary text without enqueueing. Useful for self-linting mid-thought.

What is NOT exposed

There is no broadcast.post. There is no broadcast.publish, broadcast.send, broadcast.dispatch, or any synonym — all are explicitly denylisted at the dispatcher with a structured tool-disabled-by-contract rejection. An LLM retrying variations gets the same clear refusal each time.

This is enforced at the schema layer, not via runtime check. The contract is the API surface.

Credential scanner

The shared sanitizeDraft() handles internal-voice tokens (review-round identifiers, phase tags, severity IDs). The MCP server adds a credential-shape scan on top — AWS access keys, Stripe live/test, GitHub PATs + classic + OAuth, OpenAI (including sk-proj- and sk-org-), Anthropic, Slack bot tokens + incoming webhook URLs, JWT (3-segment base64url, tightened to 16+ char middle), PEM private key blocks, Google API keys, Twilio SIDs, GCP service-account JSON markers.

Both scans run on every preview + queue. Rejections are distinguishable by reason code so the assistant can self-correct without guessing.

Notify webhook

A stdio MCP server's stderr is captured to a log file the user rarely reads — wiring BROADCAST_MCP_NOTIFY_WEBHOOK is the right way to wake a human when an AI session queues a draft. The Worker fires a JSON POST to your Slack incoming webhook (or any compatible endpoint) with draftId, platform, queuedBy, queuedAt, operator, and text. Stderr stays the fallback if the webhook fails.

Ownership

Every queued draft records queuedBy: session.operator. Kill is ownership-guarded: a session running as alice cannot kill a draft queued by bob. For ops takeovers, set BROADCAST_MCP_ALLOW_CROSS_OPERATOR_KILL=1 in the server env. The rejection message names the original queuer so the operator running the kill knows who to coordinate with.

Previous
Cloudflare Worker

Stay in the loop. Sign up for our newsletter.

We care about your data. We'll never share your email.

Powered by Directive. This signup uses a Directive module with facts, derivations, constraints, and resolvers – zero useState, zero useEffect. Read how it works

Directive - Constraint-Driven Runtime for TypeScript | AI Guardrails & State Management