MCP Server (Model Context Protocol)
Strum VOD exposes an MCP server (Model Context Protocol) so AI assistants, agents, and MCP-capable clients (VS Code, Claude Desktop, Cursor, etc.) can interact with the platform's org-scoped data and operations through a standardized tool interface — no REST plumbing needed.
The MCP endpoint is served by the API itself via the fastify-mcp-server plugin over the streamable HTTP transport. It is opt-in: disabled by default, enabled with MCP_ENABLED=true.
Enabling
| Variable | Default | Description |
|---|---|---|
MCP_ENABLED | false | Set to true to serve the MCP endpoint |
MCP_ENDPOINT | /mcp | URL path of the endpoint (e.g. /mcp → https://api.example.com/mcp) |
MCP_ENABLED=true
# MCP_ENDPOINT=/mcp # optional — default is /mcpAfter restart, the startup banner shows the MCP endpoint status, and the handshake works against:
https://<api-host>/mcpNode ≥ 22 required.
fastify-mcp-serverrequires Node.js ≥ 22 (the API's Docker image already runsnode:22, and the rootenginesfield enforces it).
Authentication
MCP clients authenticate with the exact same credentials as the REST API — no separate MCP tokens to manage:
| Credential | How to get it | Notes |
|---|---|---|
Org API key (mk_…) | Dashboard → Org → API Keys | Long-lived, ideal for MCP clients. Full org access (like X-Api-Key on REST) |
Dashboard JWT (Bearer) | Login session | Short-lived (7 days). Write tools still enforce the user's role via assertPermission — a member without ASSET_WRITE gets 403 |
Send it as a standard Authorization: Bearer <token> header on every request. Missing/invalid credentials return 401 with a WWW-Authenticate: Bearer header.
Auth is validated on every request (not just session creation) — revoking an API key or suspending an org takes effect immediately.
Example client config (VS Code)
{
"inputs": [
{
"type": "promptString",
"id": "bearer_token",
"description": "Enter your Strum VOD org API key",
"password": true
}
],
"servers": {
"strum-vod": {
"url": "https://api.example.com/mcp",
"headers": {
"Authorization": "Bearer ${input:bearer_token}"
}
}
}
}Tools
All tools are org-scoped: they resolve the authenticated org from the token and run the same queries/services the REST routes use, so a client can never read or mutate another org's data.
Read-only
| Tool | Description |
|---|---|
list_assets | List assets (newest first), filter by status and/or collectionId, with pagination |
get_asset | Full asset detail: status, playback URLs, thumbnail, AI metadata |
list_collections | Collections with per-collection video counts |
get_org_info | Org profile: tier, current metered usage, tier limits |
get_asset_analytics | Views, unique sessions, watch time, retention, heatmap, top platforms (period: 7d/30d/90d/all) |
Write
| Tool | Description |
|---|---|
create_asset | Register a new asset (status created). Source file must then be uploaded via the REST API before processing |
update_asset | Update title, description, public playback settings, custom metadata, or collection assignment |
process_asset | Queue the transcode pipeline (HLS ladder + AI steps). Honors self-hosted Nodes routing and tier limits exactly like POST /v1/assets/:id/process |
Write tools enforce ASSET_WRITE permission for JWT-authenticated users (API keys are full-access by design, matching the REST contract).
Protocol details
- Transport: MCP streamable HTTP (
POST /mcpJSON-RPC;GET /mcpSSE streams;DELETE /mcpsession teardown). - Responses: the plugin runs with
enableJsonResponse: true, so tool calls get plain JSON responses (no SSE parsing required) — fine for all MCP clients. - Sessions: each client
initializecreates a session (mcp-session-idheader); sessions are held in-memory per API instance (a single-instance deployment keeps sessions across requests; horizontal scaling of active sessions is not supported by the plugin's design). - Rate limiting: the MCP endpoint is exempt from the per-minute REST rate limit (an LLM conversation can issue many tool calls); auth is still enforced on every request.
- CORS:
Mcp-Session-Idis allowed for browser-based MCP clients.
Verification
The fastest way to try it is the MCP Inspector:
npx @modelcontextprotocol/inspector --url https://api.example.com/mcpEnter a Bearer token (org API key) in the inspector's auth tab, then call list_assets or get_org_info.
Alternatively, drive the raw protocol with curl:
# 1. initialize → capture the mcp-session-id header
curl -s -X POST https://api.example.com/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Authorization: Bearer mk_live_..." \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}'
# 2. tools/call with the session id from step 1
curl -s -X POST https://api.example.com/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Authorization: Bearer mk_live_..." \
-H "mcp-session-id: <session-id>" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_org_info","arguments":{}}}'Architecture
MCP client (VS Code / Claude Desktop / agent)
│ streamable HTTP (POST /mcp, JSON-RPC)
▼
apps/api — fastify-mcp-server plugin
│ Bearer middleware → mcpTokenVerifier (org API keys + JWTs)
▼
src/mcp/server.ts — createMcpServer() → org-scoped tools
│
├── list_assets / get_asset / list_collections / get_org_info / get_asset_analytics
│ └── reuse REST queries/services (drizzle + services/*)
└── create_asset / update_asset / process_asset
├── services/asset-record.ts (shared with POST/PATCH /v1/assets)
└── services/process-asset.ts (shared with POST /v1/assets/:id/process)src/mcp/auth.ts—mcpTokenVerifier(Bearer →AuthInfowith org context), plusgetOrgContext()used by every tool.src/mcp/server.ts—createMcpServer(), the tool factory (one freshMcpServerper session).services/asset-record.ts/services/process-asset.ts— the shared business logic behind both the REST routes and MCP tools, so the two can never drift.
Tests
- Unit (
src/tests/unit/mcp.test.ts) — verifier matrix (valid/invalid API key, suspended org, JWT, expired/tampered token), org-context extraction, andtools/listover a real in-memory transport. - E2E (
src/tests/e2e/mcp.test.ts) — full streamable-HTTP flow against Testcontainers:initialize→tools/list→tools/call(asset list, org info, create/update/process), auth failures (401), cross-org isolation, and Postgres row assertions.