{
  "markdown": "# appsgolem-mcp (Node / TypeScript)\n\n[![npm](https://img.shields.io/npm/v/appsgolem-mcp)](https://www.npmjs.com/package/appsgolem-mcp)\n\nAn [MCP](https://modelcontextprotocol.io) server for the **AppsGolem** YouTube\ncutter API. It lets an AI agent (Claude Desktop, Claude Code, Cursor, …) cut\nclips from YouTube videos — in any format the web cutter supports — and get a\ndirect download URL back. The REST logic lives in a small, dependency-light\nclient (`src/client.ts`); `src/server.ts` is the thin MCP tool layer over it.\n\n> **Get your API key → [appsgolem.com/agents](https://appsgolem.com/agents)** — sign up, add prepaid\n> credits, and generate a key (`ag_live_…`). That page also has copy-paste\n> setup for Claude Code, Codex, Cursor, and any MCP client, plus a prompt\n> cookbook.\n\n## Requirements\n\n- Node.js >= 18 (uses the global `fetch`).\n- An AppsGolem API key (`ag_live_…`) — get one at\n  **[appsgolem.com/agents](https://appsgolem.com/agents)** (sign up → add credits → generate a key in\n  your dashboard). Credits are prepaid; buy a pack or a subscription, no\n  auto-renewal required.\n\n## Install / connect (no manual install)\n\n`npx` fetches and runs the server on demand — nothing to install globally.\n\n**Claude Desktop / Cursor** — add to the client's MCP config (e.g.\n`claude_desktop_config.json`):\n\n```json\n{\n  \"mcpServers\": {\n    \"appsgolem\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"appsgolem-mcp\"],\n      \"env\": { \"APPSGOLEM_API_KEY\": \"ag_live_…\" }\n    }\n  }\n}\n```\n\n**Claude Code** — one command:\n\n```bash\nclaude mcp add appsgolem -e APPSGOLEM_API_KEY=ag_live_… -- npx -y appsgolem-mcp\n```\n\nThe server speaks MCP over **stdio** (the transport those clients use). A\nmissing `APPSGOLEM_API_KEY` is not fatal at startup — the server still starts\nand advertises its tools; each call then returns a clear `config_error` telling\nyou to set the key.\n\n## Configuration\n\n| Env var              | Required | Default                 | Notes                         |\n| -------------------- | -------- | ----------------------- | ----------------------------- |\n| `APPSGOLEM_API_KEY`  | yes      | —                       | Your `ag_live_…` key.         |\n| `APPSGOLEM_API_BASE` | no       | `https://appsgolem.com` | Override for self-host / dev. |\n\n## Pricing\n\n1 produced clip = **1 credit**. **2160p (4K) = 4** credits per clip — *except*\n`audio_only`, which stays **1**. A source **longer than 2 h** adds **+1** once\nper job, but only when its duration is known (the surcharge is skipped if the\nprobe can't determine it). A batch/stitch of *N* clips costs *N* per-clip.\n**Failed cuts are never billed.**\n\n---\n\n## Tools\n\nThe server exposes **three** tools. A call that passes MCP input-schema\nvalidation returns a structured result — the API's own JSON on success, or\n`{ \"error\": … }` on any handler/API failure — and never raises a protocol-level\nerror, so an agent always gets a usable object. (Invalid tool *arguments* are\nrejected by the MCP SDK before the handler runs, as a text-only `isError`\nresult.)\n\n### 1. `cut_youtube_video`\n\nCut a clip (or a batch of clips) from a YouTube video. By default it **waits**\nuntil the clip is produced and returns its status (including a `download_url`\nonce a download token is ready); set `wait: false` to submit and return\nimmediately with the current job (its state is normally `queued` after dispatch).\n\n**Parameters**\n\n| Name              | Type      | Default  | Notes |\n| ----------------- | --------- | -------- | ----- |\n| `url`             | string    | —        | **Required.** YouTube watch / share / `youtu.be` URL. Playlists are rejected. |\n| `start`           | string    | —        | Clip start: `\"SS\"`, `\"MM:SS\"`, or `\"HH:MM:SS\"` (≤ 300 h). Omit when using `clips`. |\n| `end`             | string    | —        | Clip end, same formats (≤ 300 h). Omit when using `clips`. |\n| `resolution`      | string    | `1080p`  | `144p` · `240p` · `360p` · `480p` · `720p` · `1080p` · `1440p` · `2160p` (4K; total cut ≤ 60 min). |\n| `mode`            | string    | `video`  | `video` · `audio_only` · `both` · `nosound` · `short` · `gif` · `frames` (see **Modes** below). |\n| `audio_format`    | string    | —        | The `audio_only` output format: `mp3` · `m4a` · `wav` · `flac` (server defaults to `mp3`). `both` always produces MP3. |\n| `bitrate`         | string    | —        | Lossy-audio bitrate `320` · `256` · `192` · `128` (default `320`): MP3/M4A in `audio_only`, MP3 in `both`; ignored for WAV/FLAC. |\n| `fast`            | boolean   | `false`  | Stream-copy (≈10× faster, keyframe-aligned); `video` / `nosound` / `both` only. Mutually exclusive with a non-1× `speed` — if both are set, `fast` wins and `speed` is forced to `1.0`. |\n| `speed`           | number    | `1.0`    | Playback speed `0.5` · `1` · `1.25` · `1.5` · `2`. `video` / `nosound` / `both` / `audio_only`. |\n| `interval_ms`     | integer   | `2000`   | `frames` sampling interval: `100` · `500` · `1000` · `2000` · `5000` · `10000` (non-sheet extraction is capped at 1,800 JPGs total across all clips). |\n| `burn_ts`         | boolean   | `false`  | `frames`: burn the source timestamp onto each JPG. |\n| `sheet`           | boolean   | `false`  | `frames`: return a single contact-sheet JPG (2–80 frames, single clip). Setting it disables `burn_ts`. |\n| `clips`           | array     | —        | An array of **1–10** `{ start, end }` ranges **instead of** `start`/`end` (an empty array is rejected). |\n| `stitch`          | boolean   | `false`  | With 2+ `clips`, join them into one file (else a zip of clips); ignored for a single clip. `video` / `audio_only` / `both` / `short` / `nosound`. |\n| `idempotency_key` | string    | —        | A stable key (**≤ 200 chars**) so a retried request reuses the same job (sent as the `Idempotency-Key` header). |\n| `wait`            | boolean   | `true`   | Poll until ready, up to the `timeout_seconds` polling deadline. |\n| `timeout_seconds` | integer   | `300`    | Polling deadline in seconds (default 300). It bounds the *polling* only — the initial submission and one in-flight status request (each up to a 30 s request timeout) can extend total wall-clock. |\n\n**Returns (`wait: true`, default)** — the produced job status. `download_url` is\npresent once a download token is available; if it isn't yet, poll again:\n\n```json\n{\n  \"id\": \"e48db1a2-1c3d-4e5f-8a9b-0c1d2e3f4a5b\",\n  \"state\": \"produced\",\n  \"credits_reserved\": 1,\n  \"created_at\": \"2026-08-22T12:00:00+00:00\",\n  \"download_url\": \"https://appsgolem.com/v1/download/…/clip.mp4\"\n}\n```\n\n**Returns (`wait: false`)** — the job immediately, with its current state\n(normally `queued` after dispatch) and no `download_url` yet; poll\n`get_cut_status` with the `id` (or fetch `poll_url`):\n\n```json\n{\n  \"id\": \"e48db1a2-1c3d-4e5f-8a9b-0c1d2e3f4a5b\",\n  \"state\": \"queued\",\n  \"credits_reserved\": 1,\n  \"poll_url\": \"/v1/cuts/e48db1a2-1c3d-4e5f-8a9b-0c1d2e3f4a5b\"\n}\n```\n\nIf the wait times out before the clip is ready, the result carries\n`\"still_processing\": true` and the job `id` — poll `get_cut_status` with that\nid. If the job reaches a terminal failure, the result is\n`{ \"error\": \"cut_failed\", \"state\": \"failed\" | \"refunded\", \"id\": … }` (and no\ncredit is charged).\n\n### 2. `get_cut_status`\n\nCheck a cut job by its id. Use it to poll a job started with\n`cut_youtube_video(wait=false)` or one that timed out.\n\n| Name     | Type   | Notes |\n| -------- | ------ | ----- |\n| `job_id` | string | **Required.** The job id (a UUID) returned by `cut_youtube_video`. |\n\n**Returns** — the job's state; once produced/delivered it also carries a\n`download_url` when a download token is available (otherwise poll again):\n\n```json\n{ \"id\": \"e48db1a2-…\", \"state\": \"queued\", \"credits_reserved\": 1, \"created_at\": \"…\" }\n```\n\nStates progress `accepted → queued → produced → delivered`, or\n`failed → refunded` on error.\n\n### 3. `get_account_balance`\n\nReturn the API account's spendable credit balance and current hourly cap. No\nparameters.\n\n**Returns**\n\n```json\n{ \"balance\": 412, \"hourly_cap\": 60 }\n```\n\n---\n\n## Modes\n\n| `mode`       | Output | Notable options |\n| ------------ | ------ | --------------- |\n| `video`      | Video file, no watermark — normally MP4; `fast` preserves the source container (e.g. WebM at high res) | `resolution`, `fast`, `speed` |\n| `audio_only` | mp3 / m4a / wav / flac | `audio_format`, `bitrate`, `speed` |\n| `both`       | Video + MP3 together, as a zip (`fast` may preserve the video's source container) | `bitrate`, `fast`, `speed` |\n| `nosound`    | Video with no audio track — normally MP4; `fast` preserves the source container | `resolution`, `fast`, `speed` |\n| `short`      | Portrait 9:16 — AI smart-crop when applicable, else a letterbox-blur fallback whose exact aspect depends on the source (Shorts / Reels / TikTok) | `resolution` |\n| `gif`        | Animated GIF (≤ 5 min; no multi-clip) | `resolution` |\n| `frames`     | JPG stills | `interval_ms`, `burn_ts`, `sheet` |\n\n---\n\n## Example prompts\n\nBecause the agent picks the parameters from your request, you drive it in plain\nlanguage:\n\n- *\"Cut 0:30 to 1:15 from https://youtu.be/dQw4w9WgXcQ in 1080p.\"* →\n  `cut_youtube_video(url, start=\"0:30\", end=\"1:15\")`\n- *\"Grab the audio of that video from 2:00 to 5:00 as an mp3.\"* →\n  `mode=\"audio_only\", audio_format=\"mp3\"`\n- *\"Make a vertical short of the 10:00–10:45 highlight.\"* →\n  `mode=\"short\", start=\"10:00\", end=\"10:45\"`\n- *\"Turn 0:05–0:12 into a GIF.\"* → `mode=\"gif\"`\n- *\"Extract a contact sheet of frames every 5 seconds from 1:00 to 2:00.\"* →\n  `mode=\"frames\", interval_ms=5000, sheet=true`\n- *\"Stitch 0:10–0:20 and 1:00–1:10 into one clip.\"* →\n  `clips=[{start:\"0:10\",end:\"0:20\"},{start:\"1:00\",end:\"1:10\"}], stitch=true`\n- *\"Do a fast, stream-copy cut of 0:00–0:30.\"* → `fast=true`\n- *\"How many API credits do I have left?\"* → `get_account_balance()`\n\n---\n\n## Result & error shapes\n\nEvery result from a handler is a plain object (MCP argument-validation\nfailures are the exception — see the Tools note above). On failure the object has an `error` code\n(the tool call still succeeds):\n\n| `error`          | When |\n| ---------------- | ---- |\n| `config_error`   | `APPSGOLEM_API_KEY` is missing. |\n| `invalid_api_key`| The key was rejected (401). |\n| `invalid_job_id` | `job_id` isn't a UUID. |\n| `not_found`      | No such job for this account (404). |\n| `cut_failed`     | The job reached `failed`/`refunded` (never billed). |\n| `network_error`  | Connection/transport failure or request timeout. |\n| `bad_request`    | The configured API base/path couldn't be built into a URL. |\n| `http_error`     | A ≥400 response whose JSON body isn't an `{ error: … }` object (carries `status`). |\n| `bad_response`   | A success response whose body isn't a JSON object (array/scalar/null), or — with `wait: true` — a cut submission that came back without a usable job `id`. |\n\nAPI-level errors — validation `400`, `402 insufficient_credits`,\n`404 not_found`, `409 idempotency_conflict` / `duplicate_in_flight`,\n`429 rate_limited`, and `503 rate_limiter_unavailable` / `database_busy` — are\nreturned as the API's own error body plus a `status` field. A `429` also\nincludes `retry_after` (seconds, from `Retry-After`) plus a `reason`\n(`submission_cap` · `request_rate` · `poll_rate`) so an agent can tell which\nlimit it hit and back off correctly. A `503` is usually transient (retry after\na short delay), but a persistent `503` is a server-side problem to report — not\nto retry indefinitely.\n\n**Status polling is not rate-limited for normal use** — checking a job's\nprogress never counts against your submissions/hour cap. You never need to\nthrottle `get_cut_status` yourself: `cut_youtube_video(wait=true)` polls and\npaces for you (honoring the server's `poll_after` cadence and any `Retry-After`),\nso a single tool call handles even long jobs.\n\nProduced download links expire after a bounded window (~72 hours by default on\nappsgolem.com; configurable per deployment) — fetch the file within it.\n\nA relative `download_url` (the API returns a path) is resolved to a full URL\nagainst the configured API base **only when the result stays on that origin**.\nAny `download_url` that resolves **off** the API's origin — an absolute URL to\nanother host, or a relative path that escapes the origin — is **dropped** for\nsafety: the field is removed and the result carries `download_url_dropped: true`,\nso you can tell it apart from a job that simply isn't produced yet. When\n`cut_youtube_video` is waiting for you (`wait=true`, the default), an off-origin\nlink surfaces as a `download_url_unsafe` error instead of handing back a bad URL.\n\n---\n\n## Develop\n\n```bash\nnpm install\nnpm run build      # tsc -> dist/\nnpm test           # builds, then runs node --test (no network)\nnpm start          # run the stdio server locally (key needed for calls, not startup)\n```\n\n## Publishing\n\n`npm publish` (from this directory) makes `npx appsgolem-mcp` work for everyone.\nThe `prepare` script builds `dist/` automatically on install/publish.\n",
  "bytes": 12981,
  "sha": "9d2d51aeba7337eeca1977f9abf24fe89980aeced4da0a8946d91654eb4831d4",
  "repo_slug": "apancyborg/appsgolem-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_apancyborg_appsgolem_mcp_4ca7425a/readme"
}