@airi.live/mcp
v1.0.0
Published
MCP server for Airi — upload, transcode, DRM-encrypt and publish video from your AI assistant
Maintainers
Readme
@airi.live/mcp
Airi video infrastructure as MCP tools: upload a file, transcode it into a quality ladder, encrypt it with Widevine, PlayReady and FairPlay, and get back an embeddable player — from your AI assistant, in one conversation.
Runs locally over stdio. Your token never leaves your machine, and an upload's bytes go from disk straight to the bucket.
Add it
Get a token at airi.live/app/developers, then:
Claude Code
claude mcp add airi --env AIRI_KMS_TOKEN=kms_live_... -- npx -y @airi.live/mcpClaude Desktop, Cursor, Windsurf, and anything else with a JSON config
{
"mcpServers": {
"airi": {
"command": "npx",
"args": ["-y", "@airi.live/mcp"],
"env": { "AIRI_KMS_TOKEN": "kms_live_..." }
}
}
}Node 20 or newer. Nothing to install — npx fetches it on first run.
Try it
Upload ~/Videos/keynote.mp4, encode it at 1080p and 720p with DRM, and give me an embed I can paste into our site.
The assistant will run roughly this:
airi_storage_upload path=~/Videos/keynote.mp4 → file id
airi_encoding_quote heights=[1080,720] duration=600 → $0.16
airi_encoding_start package=hls+dash drm=true → job id
airi_encoding_job wait=true → completed
airi_player_create name="Site player" → player id
airi_player_embed file=<id> → signed URL + <iframe>The tools
| | | |---|---| | Storage | upload, list, get, sign_url, delete, usage | | Encoding | quote, renditions, start, job, list, cancel, packaging_job | | Players | create, list, update, delete, embed | | DRM keys | create, get, list, rotate, periods | | Policies | set, list, resolve, delete | | Usage | balance, licenses, stats | | Anything else | api_endpoints, api_call |
Thirty-two in all, prefixed airi_.
Three of them do more than one HTTP call, because the shape of the API is built
for browsers and an assistant is not one. airi_storage_upload takes a path and
returns a sealed file — declare, multipart upload, ETag collection and complete,
all inside the tool. airi_encoding_job with wait: true blocks until the job
finishes, so an encode is one step rather than a poll loop. airi_player_embed
returns the <iframe> and player.js snippets alongside the resolved player.
The last two are an escape hatch. airi_api_endpoints reads the deployed API's
own OpenAPI document — generated from the schemas that validate requests, so it
is never stale — and airi_api_call runs whatever it finds. New endpoints are
reachable the day they ship, without waiting for this package to catch up.
Two things it will not do
It will not hand your content keys to the model. key and iv are the AES
secret that decrypts your video, and a tool result lands verbatim in the
assistant's transcript — which is logged, replayed, and often sent to a third
party. The key tools return the key id, the PSSH boxes and the licence URLs, and
redact the rest. includeKeyMaterial: true returns it when you genuinely need to
encrypt locally with shaka-packager. Encoding with drm: true never needs it.
It will not touch billing or your account. /v1/admin (credits balances,
mints credentials), /v1/auth (issues session tokens) and /cas (the DRM
provider's callback) are refused no matter what your token is allowed to do.
What an agent may do and what a credential may do are different questions, and
the answer to the first should not depend on how generously the second was
minted. --allow-admin unlocks them if you decide otherwise.
Everything else is reachable, because it is the job. Deletes and encodes carry
MCP's destructiveHint so your client can ask before running them, and the tool
descriptions say plainly what cannot be undone: a contentId is permanent at the
DRM provider, a deleted file has no undo, and cancelling an encode stops the
billing rather than the encoder.
Scope it down
The token is the real boundary — this server cannot grant what your credential does not have. Mint one for the assistant rather than reusing your own, with only the scopes it needs:
| scope | |
|---|---|
| storage:read storage:write | files and signed URLs |
| encoding:read encoding:write | transcoding and packaging |
| player:read player:write | players and embeds |
| keys:read keys:write | DRM keys, policies, balance and usage |
A read-only assistant is a credential with only the :read scopes. Revoke it
from airi.live/app/developers and every tool
stops working on the next call — Hyperdrive query caching is off for exactly
this reason.
Options
Flags and environment variables both work; flags win. An env block is the better place for the token, since a flag shows up in the process list.
| flag | env | default |
|---|---|---|
| --token | AIRI_KMS_TOKEN | — (required) |
| --namespace | AIRI_KMS_NAMESPACE | — |
| --url | AIRI_KMS_URL | https://kms.airi.live |
| --allow-admin | AIRI_MCP_ALLOW_ADMIN=true | off |
| --timeout | AIRI_KMS_TIMEOUT_MS | 60000 |
| | AIRI_DASHBOARD_URL | https://airi.live |
--namespace is only needed when your token is not already confined to one.
Storage, encoding and players refuse to run without a concrete namespace: the
object key is ${namespace}/${id}, so a wildcard would write to /uuid.
airi-mcp --help prints all of it.
Development
npm install
npm run dev # run from source
npm run typecheck
npm run test:upload # multipart correctness against a stub bucket
npm run smoke # CLI, JSON-RPC handshake, tool registration, the admin guard
SMOKE_LIVE=true npm run smoke # also calls read-only tools against the APItest/upload.ts is the one that matters. The multipart path is the only part of
this server that can corrupt data rather than merely fail — an off-by-one in the
offset arithmetic produces an object of exactly the right size whose bytes are
wrong, and nothing downstream notices until a viewer sees a broken video. The
live part size is 100 MB, so testing that boundary against the real service would
mean pushing a gigabyte; the stub uses 64 KB parts and reassembles what the
bucket received, including the ragged-last-part case.
Nothing may be written to stdout that is not a JSON-RPC message — stdout is the
transport, and one stray console.log corrupts the stream in a way users see as
"the MCP server failed to start" with no clue why. Diagnostics go to stderr.
Links
- MCP guide — this package, in prose
- Docs — how the pieces fit together
- API reference — every endpoint and parameter
- Issues
ISC
