@vargai/mcp
MCP server for varg.ai — AI video, image, speech, and music generation for Claude, ChatGPT, Cursor, VS Code, and any other MCP client.
Every model in the varg catalog, with inline media previews in clients that support MCP Apps.
Use the hosted server
Nothing to install. Point your client at:
https://mcp.varg.ai/mcp
It registers itself over OAuth 2.1 + PKCE — no client id, no API key. You log in, pick which account it works in, and approve.
claude mcp add --transport http varg https://mcp.varg.ai/mcp
Per-client setup for Claude.ai, Claude Desktop, ChatGPT, Claude Code, VS Code, and Cursor: docs.varg.ai/mcp-server.
Run it locally
Over stdio, against your own API key:
npx @vargai/mcp
Reads VARG_API_KEY, or ~/.varg/credentials written by bunx vargai login.
Which account it works in
Each connection — Claude Desktop, Cursor, VS Code — is bound to your personal account or to one of your teams, independently. Generations bill that account and files land in its library, so one agent can work in Marketing while another works in Product.
You choose on the consent screen, and can move a connection later by asking the assistant (list_teams, get_current_team, set_team). Local stdio mode uses whichever account the API key belongs to.
Widgets (MCP Apps)
In clients that support MCP Apps (Claude, ChatGPT, …) results render inline, from one widget bundle: ui://varg/app.html (built from views/ into dist/views/app.html). The pre-0.7 URIs (media-viewer, balance-viewer, analytics-viewer, upload-widget) are kept as aliases of the same bundle for hosts that cached old tool metadata.
Every UI tool result has three layers: JSON text for the model (unchanged in spirit — job id, status, URL, cost), the same facts as structuredContent, and a widget payload under _meta["varg/ui"] whose view picks what to draw — job (progress, result, next steps, errors), quote (cost approval), credits (balance and spend), upload, templates, presets, models, history. The contract is src/lib/ui-contract.ts. Hosts that drop _meta still get a plainer rendering from the other two layers.
Everything is in credits — no currency is shown. The model reads credits too: the API names its amounts after cents (1 credit = 1 API cent), so every *_cents key in a tool result, error detail or resource reaches the model renamed to *_credits (cents_per_second → credits_per_second, money_unit: "cent" → "credit"), check_balance adds "unit": "credits", and the server instructions tell it never to state prices in dollars — what a credit costs depends on the plan. The widget payload is built from the raw API data. Model names come from the catalog (/v2/models, cached), never provider route keys.
Spend analytics
Ask what the account spent and what drove it — "how much did video cost this month vs last?", "which API key spends the most?", "explain what this render cost". The tools read /v2/analytics, the same numbers as the dashboard and the API:
get_analytics_summary | spend, runs, charges on failed work, the comparison period, current balance (shows a spend card) |
get_analytics_breakdown | spend ranked by model, type, folder, API key, member, creator, source or status |
get_analytics_timeseries | spend per hour/day/week/month in your timezone |
get_analytics_files | media files made per hour/day/week/month, split into video, image, audio and other |
list_analytics_activity | the charges (or runs) behind any of the above |
get_job_cost | one run explained from the ledger, job by job |
Amounts are credits, in the _credits fields (spend_credits, run_total_charged_credits, …). creator is who made it, the way Resources shows a file's creator: the personal API key a run was started with, otherwise the person. source splits spend into people in the app, the API and MCP: calls made through this server count as MCP (it sends X-Varg-Client: mcp; OAuth connections are recognised by their token). Team members who don't manage the team see their own attribution; everyone else is grouped as "Other members".
Development
bun install
bun test
bun run check
bun run start
bun run start:http
bun run build bundles the widget (bun run build:views) and the server (bun run build:server); the integration tests run the built dist/index.js, so build before bun test. Pushing to main deploys the hosted server. VARG_APP_URL (default https://varg.ai) sets the app links the widgets open.
Session admission in HTTP mode is tuned through the environment (production sets them in fly.toml): MCP_MAX_SESSIONS (default 100; at the ceiling the least recently used idle session is evicted, never a newcomer refused), MCP_MAX_SESSIONS_PER_IDENTITY (50), MCP_SESSION_RATE_LIMIT (new sessions per identity per minute, 60) and MCP_MAX_BODY_MB (10). Limits are keyed on the authenticated account, never the client IP: ChatGPT and Claude reach the server from a few shared addresses for all of their users. GET /health reports the counters (sessions created, evicted, rate limited, …) and the heap limit.
MCP_STATELESS=true serves every request on its own — no sessions, so any machine can answer any request and a deploy disconnects nobody. Each request is authenticated and gets its own server; GET and DELETE on /mcp answer 405; calls that spend re-read the connection's team from the API first; waits are capped by VARG_HOSTED_WAIT_BUDGET_MS (45 s). Staging runs it (fly.staging.toml); production does not yet. On SIGTERM the server drains in either mode: waits end with the job id and a get_job hint, then it exits.
MCP_SCOPE_MODE=api hands the question "which team pays" to the API: OAuth calls send X-Team-Id: connection and varg-api applies the connection's binding per request, failing closed (needs vargHQ/api#184 deployed first). The server then caches nothing that decides billing, which is what lets it run on any number of machines. Limits here are per-machine brakes against runaway clients; the authoritative per-person and per-team job limits are the API's.
Releasing
The hosted server ships on every push to main. The npm package and the MCP Registry entry (ai.varg/mcp, from server.json) ship on a tag:
- Bump the version in
package.json, server.json (top-level and packages[0]), VERSION in src/index.ts and the McpServer version in src/server.ts — release-manifest.test.ts fails if they disagree.
- Merge, then
git tag v0.9.0 && git push origin v0.9.0.
publish.yml tests, publishes to npm, then to the registry. It needs the NPM_TOKEN and MCP_REGISTRY_PRIVATE_KEY repository secrets; the registry key is the ed25519 key behind the v=MCPv1 TXT record on varg.ai.
License
MIT