npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@eachlabs/video-api-mcp

v0.0.4

Published

MCP server for the eachlabs Video API — run ffmpeg operations + raw ffmpeg + Shaka HLS/DASH packaging from any AI agent.

Readme

@eachlabs/video-api-mcp

The easiest video API for AI agents. Edit and process video from any agent in one line — 44 ready-made operations (plus arbitrary run_ffmpeg for the long tail), through the eachlabs Video API. Most calls return a result URL; the metadata-analysis ops (probe, scene_detect, silence_detect, audio_analysis) return a structured analysis result instead.

This is the agent-facing surface of the eachlabs Video API. ffmpeg is one engine under the hood — the goal is video editing as easy as calling a skill, with the best engine chosen per job. API docs: https://docs.eachlabs.ai/video/overview

Beta. The Video API and this MCP server are in beta: tool schemas and capabilities may still change between releases. Jobs bill to your eachlabs account — see Billing & Limits.

Install

Connect through the hosted endpoint with one OAuth login — approve access in the browser and your agent is ready. No API key to create or paste. Jobs bill to the organization you approve on the consent page. The examples register the hosted server as eachlabs-video.

Headless environments (CI runners, servers) use the local stdio server with an API key instead.

Once connected, ask your agent to "transcode this video to mp4" with a URL.

Claude Code

claude mcp add --transport http eachlabs-video https://mcp.eachlabs.ai/mcp

Then run /mcp inside Claude Code and log in when prompted.

Codex

codex mcp add eachlabs-video --url https://mcp.eachlabs.ai/mcp --oauth-resource https://mcp.eachlabs.ai/mcp

The add command opens the OAuth flow in your browser. To re-authenticate later:

codex mcp login eachlabs-video --scopes predictions:create,predictions:read

Cursor

Add to .cursor/mcp.json in your project (or ~/.cursor/mcp.json for all projects), then approve the login prompt in Cursor's MCP settings:

{
  "mcpServers": {
    "eachlabs-video": { "url": "https://mcp.eachlabs.ai/mcp" }
  }
}

Claude Desktop

Settings → Connectors → Add custom connector, set the URL to https://mcp.eachlabs.ai/mcp, then connect and approve with your eachlabs account.

Kimi Code

Add to ~/.kimi-code/mcp.json, then log in when prompted:

{
  "mcpServers": {
    "eachlabs-video": { "url": "https://mcp.eachlabs.ai/mcp" }
  }
}

Headless and CI (API key)

Environments without a browser run the same server locally over stdio. Create a key at https://eachlabs.ai under Settings → API Keys — there is no shared or default key: the server refuses to start without one, and jobs bill to the key's account. The local server is named video so it can coexist with the hosted one.

Add the mcpServers entry to your client's stdio config — for Claude Code, .mcp.json in the project root:

{
  "mcpServers": {
    "video": {
      "command": "npx",
      "args": ["-y", "@eachlabs/video-api-mcp"],
      "env": { "EACHLABS_API_KEY": "your-key" }
    }
  }
}

Or with the Claude Code CLI:

claude mcp add video --env EACHLABS_API_KEY=your-key -- npx -y @eachlabs/video-api-mcp

Codex — ~/.codex/config.toml:

[mcp_servers.video]
command = "npx"
args = ["-y", "@eachlabs/video-api-mcp"]
env = { EACHLABS_API_KEY = "your-key" }

Tools

transcode, trim, segment, thumbnails, scale, extract_audio, gif, watermark, captions, subtitle_compose, concat, concat_copy, crossfade, speed, volume, loudnorm, reverse, poster, rotate, crop, pad, reframe, reframe_track, fade, color, lut3d, stills, overlay, sticker_overlay, title_card, audio_replace, audio_duck, audio_master, package_abr, package_abr_ladder, hls_ladder, probe, scene_detect, silence_detect, audio_analysis, silence_remove, silence_split, storyboard_sprites, slideshow, plus run_ffmpeg (any ffmpeg command) and get_job (poll a long job).

The local stdio server additionally exposes upload_file(file_path, content_type?, expires_in_seconds?): it uploads a local file (max 100MB) to eachlabs storage via POST /v1/upload/presign and returns {public_url, id, expires_at} — use the public_url as input_url for the other tools. Presign and the upload PUT do not create a prediction and are not billed as one; storage egress may bill per your plan. Unrecognized file extensions upload as application/octet-stream with a warning in the tool result — pass content_type to get an extension-bearing public_url. The hosted HTTP endpoint does not serve it: /v1/upload/presign accepts only API-key auth, and the hosted transport deliberately never holds your API key.

That is 44 of the engine's 45 capabilities — full functional parity: the 45th, keep_ranges, is the engine's alias of silence_remove (identical behavior and billing), so it is not wrapped as a separate tool. test/capabilities.test.ts enforces this and fails if the engine surface changes.

probe, scene_detect, silence_detect, and audio_analysis are metadata-analysis ops: they return a structured analysis result (ffprobe JSON / {scenes:[...]} / {silences:[...]} / loudness+peak measurements) rather than a media URL.

reframe_track is currently inert. It needs a server-staged crop-track sidecar (an ffmpeg sendcmd script) as input1, emitted by a saliency/face model that is not yet in the catalog. It is registered but not usable — do not build on it yet.

Jobs run on shared CPU infrastructure; there is no tier to choose.

Each capability tool's parameters mirror the eachlabs Video API. run_ffmpeg takes args: string[] (omit the leading ffmpeg — it's added for you) with {input} (single input_url) or {input0}/{input1}… (multiple input_urls) for inputs and {output} for the output. run_ffmpeg access is granted per organization; without it the engine returns scope_denied.

Tool argument schemas are strict at every depth (additionalProperties: false, including nested objects and array elements): a call carrying any parameter a tool does not declare fails validation client-side naming the unknown key — nothing reaches the engine and nothing bills.

Config (local stdio)

| Env | Default | Purpose | |-|-|-| | EACHLABS_API_KEY | (required) | Your eachlabs API key | | EACHLABS_API_BASE | https://api.eachlabs.ai/v1 | API base URL | | EACHLABS_VIDEO_MODEL | eachlabs-video-api | Model slug | | EACHLABS_POLL_TIMEOUT_MS | 300000 | Max wait before returning a job id | | EACHLABS_MODEL_VERSION | 0.0.1 | Model version sent with each prediction |

HTTP transport (authenticated)

This is the server behind the hosted https://mcp.eachlabs.ai/mcp endpoint — to connect to it, see Install; this section documents deploying it. The same tool surface (44 capabilities + run_ffmpeg + get_job, but not the stdio-only upload_file — presign accepts only API-key auth and this deployment never holds a customer key) is served as a stateless Streamable HTTP server (node dist/http.js; see Dockerfile) — the authenticated deployment of this server. Clients present an OAuth 2.1 access token; the server verifies it against the authorization server's JWKS and exchanges it (RFC 8693) for the upstream credential. No API key is ever read from env on this path.

| Endpoint | Port (env) | Notes | |-|-|-| | POST /mcp | 8080 (MCP_HTTP_PORT) | Stateless — no Mcp-Session-Id; 15s SSE keepalives; notifications/progress carries the prediction id when the client sends a progressToken; jobs exceeding the poll ceiling return the last polled prediction envelope for a get_job resume | | /.well-known/oauth-protected-resource/mcp, /.well-known/oauth-protected-resource, /.well-known/oauth-authorization-server | 8080 | RFC 9728 / RFC 8414 discovery | | /healthz | 8081 (MCP_HEALTH_PORT) | Liveness | | /metrics | 9090 (MCP_METRICS_PORT) | Prometheus counters |

| Env | Default | Purpose | |-|-|-| | MCP_EXCHANGE_TOKEN | (required) | Gate header sent to the token-exchange endpoint; boot fails without it | | MCP_OAUTH_ISSUER | https://mcp.eachlabs.ai | Expected token iss; derives the URLs below | | MCP_RESOURCE_URI | <issuer>/mcp | Expected token aud; the protected-resource identifier | | MCP_OAUTH_JWKS_URI | <issuer>/oauth/jwks.json | JWKS (5m cache; an unknown kid forces one refetch, capped at 1 per 60s; a failed refetch serves stale keys for up to 30m, retrying every 30s) | | MCP_OAUTH_TOKEN_URI | <issuer>/oauth/token | RFC 8693 exchange endpoint | | MCP_OAUTH_AUTHORIZATION_ENDPOINT | https://www.eachlabs.ai/mcp/oauth/authorize | Advertised in the AS metadata document | | MCP_UPSTREAM_AUDIENCE | https://api.eachlabs.ai | resource requested at exchange | | MCP_ALLOWED_ORIGINS | (empty) | Comma-separated Origin allowlist; a present-but-unlisted Origin is 403 | | MCP_HTTP_POLL_TIMEOUT_MS | 240000 | Bounded hold before the get_job handoff | | MCP_SHUTDOWN_TIMEOUT_MS | 25000 | Hard cap on SIGTERM/SIGINT drain before a forced exit (kept under the 30s ECS stopTimeout) |

On SIGTERM the task drains: /healthz starts answering 503, the MCP and metrics ports stop accepting new connections while in-flight requests finish, the health port closes last, and a fallback timer force-exits at the cap so a hung connection can never stall the stop past SIGKILL.

EACHLABS_API_BASE / EACHLABS_VIDEO_MODEL / EACHLABS_MODEL_VERSION are shared with the stdio path. Per-tool scopes are enforced pre-dispatch: the 44 capabilities + run_ffmpeg need predictions:create, get_job accepts predictions:read or predictions:create, and a tool with no entry in the scope map is refused (403 tool_unmapped).

Notes

  • Inputs are URLs today (local-file upload is on the roadmap).
  • Max job length 3600s. custom_code is not exposed.
  • Billing is per compute-second to your eachlabs account ($0.0015 per billed cpu-second). Each job includes 200 MB of output delivery free; beyond that, egress is billed at $0.30/GB.

Tool results & error handling

Every tool result relays an eachlabs API envelope verbatim — the exact shapes documented at Get Prediction and Create Prediction. Nothing is reshaped, renamed, or invented on top:

  • Terminal jobs (status success, error, or cancelled) return the GET /v1/prediction/{id} body: { id, input, status, output, logs, metrics, urls }. error and cancelled are marked isError: true. The engine's typed failure reason lives in output.error_message — usually a JSON string {code, message, retryable, stage} whose code is one of:
    • protocol_denied — an input/output protocol isn't on the allowlist (the engine opens local files only; pass media via input_url/input_urls, not concat:/ http:/pipe: inside the command).
    • placeholder_unbound — a {placeholder} had no matching input/output.
    • scope_denied — the model token may not run this mode (e.g. run_ffmpeg).
    • invalid_command — argv[0] must be ffmpeg or ffprobe, or the args are malformed.
    • platform_fault — a transient engine fault; retryable is true.
  • Jobs still running at the poll ceiling return the last polled prediction envelope unchanged (a non-terminal status such as processing); resume with get_job { prediction_id: <id> }.
  • HTTP-level API rejects (a 4xx/5xx on submit, get_job on an unknown id) return the API's error envelope { "error": "..." } verbatim with isError: true.

Cancellation

Cancelling the MCP request (notifications/cancelled) or disconnecting aborts the in-flight submit/poll fetches immediately and then fires POST /v1/prediction/{id}/cancel best-effort with the same credentials, so the job record is cancelled upstream. The upstream cancel is record-only: in-flight engine work is discarded on completion, not interrupted.

Development

npm ci
npm run build
npm test

npm run capabilities:sync regenerates test/fixtures/engine-capabilities.json from the Video API OpenAPI spec; point it at the spec with VIDEO_API_OPENAPI.

node scripts/generate-tool-docs.mjs [outfile] (after npm run build) emits the deterministic MDX tool reference consumed by the docs repo's generated video/mcp/tools page.

Container image (ECR)

The Build & Push to ECR workflow builds the root Dockerfile for linux/amd64 and pushes to eachlabs/mcp-service on every merge to main, plus on demand via workflow_dispatch. Tags: v<package.json version>-<short sha> on every run, plus the plain v<version> while that tag does not exist yet (tags are immutable and never re-pushed).

The ECR repository is operator-created per infra convention (settings recorded in the infra README); the workflow also creates it idempotently on first run with the same settings. One-time creation command:

aws ecr create-repository --repository-name eachlabs/mcp-service \
  --image-tag-mutability IMMUTABLE --image-scanning-configuration scanOnPush=true

Roadmap

  • Agentic: describe an edit in natural language and we plan + run the whole pipeline (no need to pick a tool or write ffmpeg).
  • Multi-instrument: ffmpeg and the Shaka packager (package_abr) are the engines today; more (e.g. broadcast transcoders, AI upscalers/captioners) get routed per job for the best result.
  • More capabilities: longer jobs, local-file upload, SDKs + OpenAPI.

License

MIT