Skip to main content
Glama

livekit-ops-mcp

An MCP server that gives an AI agent operator control of a LiveKit deployment, plus a workshop kit that runs a hackathon room-by-room in one call.

24 tools over stdio. TypeScript, @modelcontextprotocol/sdk, livekit-server-sdk. Works against livekit-server --dev with zero configuration and against LiveKit Cloud with three environment variables.

npx livekit-ops-mcp        # after publish; or: node dist/index.js

What it is

LiveKit already has MCP on the agent side: the Agents framework can consume MCP tools, and docs.livekit.io serves a documentation MCP. What did not exist was the other direction: an MCP server that exposes the LiveKit server API (rooms, access tokens, participants, data messages, egress, ingress) so that Claude, Cursor, or any MCP client can operate a deployment by talking to it.

On top of that control plane sits a workshop kit, because the operator who needs this most is the person running a room full of developers:

  • workshop_spinup: one call creates a room per team, mints a scoped join token per seat, and a mentor token with admin and record rights per room.

  • workshop_join_sheet: turns that manifest into a printable HTML sheet with a QR code per seat (or Markdown).

  • workshop_status: which teams are in their rooms, which rooms are empty, who is publishing.

  • workshop_broadcast: "30 minutes left, submit your repo link" to every team room at once.

  • workshop_teardown: delete everything under the event prefix when the day ends.

Related MCP server: webrtc-mcp-server

Why

Events and community work run on repetition: the same room setup, the same token hand-out, the same "who is stuck" walk around the room, the same cleanup. Making that a five-prompt conversation with an agent means a meetup or hackathon host spends the evening with developers instead of with a terminal. The same tools double as the feedback loop: workshop_status and participant_list show, in real time, where builders drop off, and every session leaves a manifest that says what was tried. That is the signal a developer advocate carries back to Product.

Install

git clone https://github.com/ExpertVagabond/livekit-ops-mcp
cd livekit-ops-mcp
npm install
npm run build

Run a local LiveKit server (no account needed):

# Docker
docker run --rm -p 7880:7880 -p 7881:7881 -p 7882:7882/udp \
  livekit/livekit-server --dev --bind 0.0.0.0 --node-ip 127.0.0.1
# or Homebrew
brew install livekit && livekit-server --dev

--dev uses the well-known keys devkey / secret, which are also this server's defaults. For LiveKit Cloud or your own deployment:

Variable

Default

Notes

LIVEKIT_URL

http://localhost:7880

wss://<project>.livekit.cloud works; the http host is derived

LIVEKIT_API_KEY

devkey

LIVEKIT_API_SECRET

secret

LIVEKIT_MEET_URL

https://meet.livekit.io/custom

Base for join links (?liveKitUrl=&token=)

Claude Desktop / Claude Code / Cursor config:

{
  "mcpServers": {
    "livekit": {
      "command": "node",
      "args": ["/path/to/livekit-ops-mcp/dist/index.js"],
      "env": {
        "LIVEKIT_URL": "wss://your-project.livekit.cloud",
        "LIVEKIT_API_KEY": "APIxxxxxxxx",
        "LIVEKIT_API_SECRET": "..."
      }
    }
  }
}

Claude Code one-liner: claude mcp add livekit -- node /path/to/livekit-ops-mcp/dist/index.js

Tools

Tool

What it does

server_health

Round-trips ListRooms; reports host, key, latency, active rooms. Run first.

room_create

Create a room with empty/departure timeouts, participant cap, metadata

room_list

List rooms, filter by exact names or prefix

room_delete

Delete a room and disconnect everyone

room_update_metadata

Replace room metadata (pushed to all participants)

participant_list

Participants with state, permissions, published tracks

participant_get

One participant, including track SIDs

participant_remove

Kick a participant

participant_mute_track

Server-side mute/unmute of a published track

participant_update

Name, metadata, attributes, permissions

token_create

Mint a scoped join JWT (room, identity, TTL, publish/subscribe/data/admin/record grants) and a Meet join link

token_verify

Verify a JWT against the configured secret and return its claims

data_send

Publish a data message into a room from the server (topic, reliable/lossy, per-identity targeting)

egress_start_room_composite

Record a room to MP4/OGG/MP3 or push to RTMP/SRT

egress_list

List egress jobs (filter by room, active only)

egress_stop

Stop an egress job

ingress_create

RTMP / WHIP / URL ingress into a room; returns URL and stream key for OBS

ingress_list

List ingress endpoints

ingress_delete

Delete an ingress endpoint

workshop_spinup

N team rooms + seat tokens + mentor tokens in one call; returns a manifest

workshop_join_sheet

Printable HTML (QR per seat) or Markdown join sheet from the manifest

workshop_status

Live per-room participant and publisher counts, empty rooms

workshop_broadcast

One data message to every room under the event prefix

workshop_teardown

Delete every room under the event prefix (dryRun to preview)

Every tool returns both human-readable text and structuredContent. SDK errors come back as MCP error results with a hint (for example, egress and ingress need their services running; --dev alone has neither).

Run a hackathon in five prompts

With the server connected to Claude:

  1. "Check the LiveKit server is up." server_health returns ok: true, the host, and latency.

  2. "Spin up the SF Voice Agent Hackathon: teams Otters, Herons, Foxes, Kestrels, four seats each, tokens good for ten hours." workshop_spinup creates sf-voice-agent-hackathon-otters and the rest, mints 16 seat tokens and 4 mentor tokens, and returns the manifest.

  3. "Make me a printable join sheet." workshop_join_sheet renders one page per team with a QR code per seat. Save it as HTML, print it, cut it into strips.

  4. "How are the teams doing? Tell everyone there are 30 minutes left." workshop_status shows two rooms empty and one team with nobody publishing (go help them); workshop_broadcast drops the timer message into every room.

  5. "Tear it all down." workshop_teardown deletes every room under the prefix. room_list confirms zero.

The same manifest feeds a recap: which teams stayed in their room the whole session, which never joined, and how many published media. Those numbers go into the event write-up and back to the product team.

Tests

Unit tests run against mocked SDK clients through a real MCP client (in-memory transport):

$ npm test
 Test Files  3 passed (3)
      Tests  21 passed (21)

$ npx tsc --noEmit
(exit 0)

The integration smoke test runs against a real livekit-server --dev, drives every step through tools/call, and joins a real WebRTC participant with @livekit/rtc-node so participant_list and data_send are exercised for real. Output from 2026-09-14 against livekit-server 1.13.7 in Docker:

$ npm run smoke
[01] tools/list -> 24 tools: server_health, room_create, room_list, room_delete, room_update_metadata, participant_list, participant_get, participant_remove, participant_mute_track, participant_update, token_create, token_verify, data_send, egress_start_room_composite, egress_list, egress_stop, ingress_create, ingress_list, ingress_delete, workshop_spinup, workshop_join_sheet, workshop_status, workshop_broadcast, workshop_teardown
[02] server_health -> ok=true apiHost=http://localhost:7880 latencyMs=48.5 activeRooms=0
[03] workshop_spinup -> 2 rooms: smoke-mu1wxi2g-otters (sid RM_2CTLxz9gvyRy, 2 seats + mentor); smoke-mu1wxi2g-herons (sid RM_Kjqpeyyvvrwv, 2 seats + mentor)
[04] token_verify -> valid=true sub=otters-seat-1 room=smoke-mu1wxi2g-otters roomJoin=true
[05] room_list prefix=smoke-mu1wxi2g -> 2 rooms
[06] rtc-node Room.connect(ws://localhost:7880) as otters-seat-1 -> isConnected=true, server-side sid PA_5Z8GYhBztT3s
[07] participant_list smoke-mu1wxi2g-otters -> 1: otters-seat-1 [ACTIVE]
[08] participant_update -> name="Otter One"
[09] data_send -> 23 bytes; participant received "hello from the operator"
[10] workshop_broadcast -> 2 rooms
[11] workshop_status -> rooms=2 participants=1 empty=["smoke-mu1wxi2g-herons"]
[12] participant_remove -> removed=otters-seat-1; client saw Disconnected
[13] egress_list -> error (expected on --dev, no egress service): twirp error unknown: egress not connected (redis required)
[14] ingress_list -> error (expected on --dev, no ingress service): twirp error unknown: ingress not connected (redis required)
[15] workshop_join_sheet(markdown) -> 3157 chars, first line "# smoke mu1wxi2g: join sheet"
[16] workshop_teardown -> deleted 2: smoke-mu1wxi2g-otters, smoke-mu1wxi2g-herons
[17] room_list prefix=smoke-mu1wxi2g -> 0 rooms

SMOKE PASS: 17 steps, 0 failures against http://localhost:7880

Docker on macOS note: pass --node-ip 127.0.0.1 to the server so ICE candidates point at the mapped ports; without it the signalling connects but media never does.

Layout

src/index.ts          stdio entry
src/server.ts         McpServer + tool registration
src/config.ts         LIVEKIT_* env, dev defaults
src/clients.ts        RoomService / Egress / Ingress / AccessToken behind one interface (mockable)
src/tools/*.ts        rooms, participants, tokens, data, egress, ingress, workshop
scripts/smoke.ts      integration smoke against a real server
test/unit/*.test.ts   vitest, mocked clients through a real MCP client

Prior art

  • LiveKit Agents MCP client support (agents consuming MCP tools) and livekit-examples/basic-mcp

  • LiveKit Docs MCP at https://docs.livekit.io/mcp (documentation search)

  • Community voice agents wired to MCP servers (home-assistant-mcp-agent, voice-mcp-agent, and others)

None of these expose the server API to an MCP client; that is what this repo does.

License

MIT. Copyright (c) 2026 Matthew Karsten.

Tool Schema Changelog

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

Loading tool schema changelog…

    Related MCP Connectors

    Related MCP Servers