Skip to main content
Glama

Record HTML to video

record_html

Record a web page or HTML5 creative (banner, ad, animation, landing page) to a video: MP4 (default), WebM, MOV (ProRes) or GIF. Returns the finished recording with a video link.

Transparency: transparent: true with webm, mov or gif keeps what the page draws without its background (an alpha channel), for stickers, overlays and lower thirds. Give the page no background of its own beyond html and body (those are removed).

Input: either url (a public http(s) page; localhost and private networks are out of reach) or html (a complete single-file HTML document you wrote, published at an unlisted public URL and recorded from there). Set width and height to the creative's size (e.g. 300×250, 728×90, 1080×1920, 1920×1080) and durationSec to how long it runs.

Claude artifacts: a claude.ai/chat link is private, so the recorder can't open it. To record an artifact from this conversation, pass its code as html. An HTML artifact goes in as it is. A React artifact is a component, not a page: wrap it in a complete HTML document with

  • the component in , rendered with createRoot from react-dom/client into a root div. Replace what only exists inside Claude (@/components/ui imports, window.claude) with plain code. A published artifact (claude.ai/public/artifacts/…) records with Claude's page frame around it, so prefer html.

Waiting: the call waits up to waitSec (default 45 s) for the video. If it isn't done by then, you get the recording with status queued or running; it keeps going. Call get_recording with its id and waitSec to wait for it; don't call record_html again for it.

Retries are safe: identical calls within 10 minutes return the same recording. After changing the creative behind the same url, pass a new revision to record it again. A failed recording is never reused: call again once you've fixed the cause.

Trim markers: by default the video starts at the page's load event and lasts durationSec. To capture exactly the animation, the page can mark it. The recorder defines two global functions in every frame (iframes included):

  • trim_start(): call it once when the assets are loaded, just before the animation starts. The video starts there.

  • trim_end(): call it once from the animation's completion callback (e.g. GSAP onComplete, the last animationend). The video ends there and durationSec becomes the maximum. Guard both so the page still works in a normal browser: // assets loaded, just before the animation starts: if (typeof trim_start === 'function') trim_start(); // in the animation's completion callback: if (typeof trim_end === 'function') trim_end(); Never call the two right after each other: that marks an empty clip. With sync "markers" the recording fails instead of falling back to the load event when trim_start() isn't called within markerWaitSec (default 10 s). report.anchor says "trim_start" when the start marker was used.

Play buttons: a page that waits for a click records nothing but its button. For html you write, start the animation on load instead. For a page you can't change, pass actions: [{"type":"click","text":"Play"}] (the button's label, or a selector): the recorder clicks it and the video starts at the click (report.anchor "action").

Links: video.url is signed and valid for 1 hour (get_recording mints a fresh one). pageUrl is the recording's page in the web app, for a person to open later (they sign in).

Credits: each recording uses credits from the account: 1 per started 60 s of video up to 1920×1080 pixels (output size, else crop, else viewport), 5 above that. A failed recording gives its credits back. When the account runs out, the call fails with insufficient_credits: the person adds credits on the web app's Billing page. Limits on length, size, fps and recordings at a time apply too; estimate_recording shows the cost and the balance without recording. Errors say what to change: fix it and call again.

Tool call template

Replace each placeholder with a value matching the input schema. Only required parameters are included; add optional parameters as needed.

{
  "id": 1,
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "arguments": {
      "width": "<width>",
      "height": "<height>",
      "durationSec": "<durationSec>"
    },
    "name": "record_html"
  }
}

Input Schema

NameRequiredDescriptionDefault
crfNoQuality for mp4 and webm, from 10 (best, largest) to 35 (smallest). Not together with bitrateMbps; not for gif or mov.
fitNoHow the frame fills outputSize: fill (stretch), contain (letterbox in background) or cover (crop to fill).
fpsNoFrame rate: one of 15, 23.976, 24, 25, 29.97, 30, 59.94, 60 (default 30). For gif, a whole number from 1 to 30.
urlNoPublic http(s) URL of the page or creative to record. The recorder runs on the internet: localhost, private networks and pages behind a sign-in (such as claude.ai/chat links) are out of reach. Pass url or html, not both.
cropNoRecord only this region of the viewport: { x, y, width, height } in pixels, even values, inside the viewport.
htmlNoA complete HTML document to record instead of a url, e.g. a banner you just wrote or an artifact from this conversation. One file: inline the CSS and JavaScript, and load images, fonts and libraries from public https URLs or data: URIs. It is published at an unlisted public URL and recorded from there.
loopNogif only: whether the gif loops.
syncNoHow trim markers are used: auto (default: markers when the page calls trim_start(), else the load event), load (ignore markers) or markers (fail when trim_start() is not called within markerWaitSec).
audioNoRecord the page’s audio too. Not for gif.
widthYesViewport width in CSS pixels: even, 16–7680. Use the creative’s size, e.g. 300 for a 300×250 banner or 1920 for a full HD page.
formatNomp4 (default, H.264), webm (VP9), mov (ProRes, for video editors) or gif.
heightYesViewport height in CSS pixels: even, 16–7680.
actionsNoClicks, for a page that waits for a Play or Start button: [{"type":"click","text":"Play"}]. A click finds its element by selector (CSS), text (visible text, aria-label, title or alt; case and symbols such as ▶ ignored) or point ({x, y} in the viewport), afterMs after load or the previous click. The video starts at the first click unless startsVideo is false. An element that is not on screen within 10 s fails the recording.
startAtNoWhere the video starts: "load" (default: the load event, or the first trim_start() call) or that many milliseconds after it (0–120000).
waitSecNoHow long to wait for the finished video, in seconds (0–45, default 45). The recording keeps going after the call returns.
rendererNoauto (default), cpu or gpu. Leave it on auto.
revisionNoAny label that changes when the creative changes. Identical calls within 10 minutes return the same recording; after you change the creative behind the same url, pass a new revision to record it again.
maxSizeMbNoKeep the file under this many megabytes (decimal: 0.6 = 600 KB), e.g. a platform’s upload limit or an email gif. The quality is lowered only as much as needed; frames, size and duration stay as requested. Not with bitrateMode cbr or format mov.
backgroundNoLetterbox colour for fit: contain, as #rrggbb.
outputSizeNoScale the video to { width, height } (even values); see fit.
bitrateMbpsNoA target bitrate in Mbps (1–100) instead of crf, for platforms with a bitrate spec (e.g. DOOH). Not for gif or mov.
bitrateModeNovbr (default) or cbr. Needs bitrateMbps.
durationSecYesLength of the video in seconds (1–300; your account limit applies). With trim_end() it is the maximum.
pixelFormatNoyuv420p (default; plays everywhere), yuv422p or yuv444p. For mov, yuv444p picks ProRes 4444, otherwise 422 HQ.
transparentNoKeep what the page draws without its background, as an alpha channel: webm (VP9 with alpha), mov (ProRes 4444) or gif (one transparent colour). Not for mp4. For stickers, overlays, lower thirds and Lottie or Rive style animations; the page’s html and body background is removed. Width up to 3840.
markerWaitSecNoHow long to wait for trim_start() after load, in seconds (0–60, default 10).
markerLatencyMsNoOverrides the measured delay between a marker call and the frame it marks (0–1000 ms). Rarely needed.

Output Schema

NameRequiredDescriptionDefault
idYes
errorYes
videoYes
reportYes
statusYes
pageUrlYes
requestYes
progressYes
rendererYes
createdAtYes
finishedAtYes

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed1 schema field changed
    • addedInput schema / properties / maxSizeMb
      Added value: +{
      +  "description": "Keep the file under this many megabytes (decimal: 0.6 = 600 KB), e.g. a platform’s upload limit or an email gif. The quality is lowered only as much as needed; frames, size and duration stay as requested. Not with bitrateMode cbr or format mov.",
      +  "maximum": 10000,
      +  "minimum": 0.05,
      +  "type": "number"
      +}
  2. Changed6 schema fields changed
    • changedInput schema / properties / bitrateMbps / description
      Previous value: -"A target bitrate in Mbps (1–100) instead of crf, for platforms with a bitrate spec (e.g. DOOH). Not for gif."New value: +"A target bitrate in Mbps (1–100) instead of crf, for platforms with a bitrate spec (e.g. DOOH). Not for gif or mov."
    • changedInput schema / properties / crf / description
      Previous value: -"Quality for mp4 and webm, from 10 (best, largest) to 35 (smallest). Not together with bitrateMbps; not for gif."New value: +"Quality for mp4 and webm, from 10 (best, largest) to 35 (smallest). Not together with bitrateMbps; not for gif or mov."
    • changedInput schema / properties / format / description
      Previous value: -"mp4 (default, H.264), webm or gif."New value: +"mp4 (default, H.264), webm (VP9), mov (ProRes, for video editors) or gif."
    • changedInput schema / properties / format / enum
      Previous value: -[
      -  "mp4",
      -  "webm",
      -  "gif"
      -]New value: +[
      +  "mp4",
      +  "webm",
      +  "mov",
      +  "gif"
      +]
    • changedInput schema / properties / pixelFormat / description
      Previous value: -"yuv420p (default; plays everywhere), yuv422p or yuv444p."New value: +"yuv420p (default; plays everywhere), yuv422p or yuv444p. For mov, yuv444p picks ProRes 4444, otherwise 422 HQ."
    • addedInput schema / properties / transparent
      Added value: +{
      +  "description": "Keep what the page draws without its background, as an alpha channel: webm (VP9 with alpha), mov (ProRes 4444) or gif (one transparent colour). Not for mp4. For stickers, overlays, lower thirds and Lottie or Rive style animations; the page’s html and body background is removed. Width up to 3840.",
      +  "type": "boolean"
      +}
  3. Changed1 schema field changed
    • addedInput schema / properties / actions
      Added value: +{
      +  "description": "Clicks, for a page that waits for a Play or Start button: [{\"type\":\"click\",\"text\":\"Play\"}]. A click finds its element by selector (CSS), text (visible text, aria-label, title or alt; case and symbols such as ▶ ignored) or point ({x, y} in the viewport), afterMs after load or the previous click. The video starts at the first click unless startsVideo is false. An element that is not on screen within 10 s fails the recording.",
      +  "items": {
      +    "oneOf": [
      +      {
      +        "additionalProperties": false,
      +        "properties": {
      +          "afterMs": {
      +            "maximum": 60000,
      +            "minimum": 0,
      +            "type": "integer"
      +          },
      +          "point": {
      +            "additionalProperties": false,
      +            "properties": {
      +              "x": {
      +                "maximum": 7680,
      +                "minimum": 0,
      +                "type": "number"
      +              },
      +              "y": {
      +                "maximum": 7680,
      +                "minimum": 0,
      +                "type": "number"
      +              }
      +            },
      +            "required": [
      +              "x",
      +              "y"
      +            ],
      +            "type": "object"
      +          },
      +          "selector": {
      +            "maxLength": 500,
      +            "minLength": 1,
      +            "type": "string"
      +          },
      +          "startsVideo": {
      +            "type": "boolean"
      +          },
      +          "text": {
      +            "maxLength": 100,
      +            "minLength": 1,
      +            "type": "string"
      +          },
      +          "type": {
      +            "const": "click",
      +            "type": "string"
      +          }
      +        },
      +        "required": [
      +          "type"
      +        ],
      +        "type": "object"
      +      }
      +    ]
      +  },
      +  "maxItems": 10,
      +  "type": "array"
      +}
  4. Changed2 schema fields changed
    • changedInput schema / properties / html / description
      Previous value: -"A complete HTML document to record instead of a url, e.g. a banner you just wrote. One file: inline the CSS and JavaScript, and load images, fonts and libraries from public https URLs or data: URIs. It is published at an unlisted public URL and recorded from there."New value: +"A complete HTML document to record instead of a url, e.g. a banner you just wrote or an artifact from this conversation. One file: inline the CSS and JavaScript, and load images, fonts and libraries from public https URLs or data: URIs. It is published at an unlisted public URL and recorded from there."
    • changedInput schema / properties / url / description
      Previous value: -"Public http(s) URL of the page or creative to record. The recorder runs on the internet: localhost and private networks are out of reach. Pass url or html, not both."New value: +"Public http(s) URL of the page or creative to record. The recorder runs on the internet: localhost, private networks and pages behind a sign-in (such as claude.ai/chat links) are out of reach. Pass url or html, not both."
  5. Changed8 schema fields changed
    • addedInput schema / properties / crop / properties / height / multipleOf
      Added value: +2
    • addedInput schema / properties / crop / properties / width / multipleOf
      Added value: +2
    • addedInput schema / properties / crop / properties / x / multipleOf
      Added value: +2
    • addedInput schema / properties / crop / properties / y / multipleOf
      Added value: +2
    • addedInput schema / properties / height / multipleOf
      Added value: +2
    • addedInput schema / properties / outputSize / properties / height / multipleOf
      Added value: +2
    • addedInput schema / properties / outputSize / properties / width / multipleOf
      Added value: +2
    • addedInput schema / properties / width / multipleOf
      Added value: +2
  6. First observed

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the annotations by disclosing credit pricing (1 per started 60s up to 1080p, 5 above), what happens on insufficient_credits, the 1-hour signed URL lifetime, and that a failed recording refunds credits and is never reused. It also explains the trim-marker contract, the transparent-background caveats, and the fail-fast behavior of sync "markers". None of this duplicates readOnlyHint/openWorldHint.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Well front-loaded and segmented with labeled blocks (Transparency, Input, Claude artifacts, Waiting, Retries, Trim markers, Play buttons, Links, Credits), which makes a long text scannable. Length is largely justified by 27 parameters, though the step-by-step React/artifact boilerplate and the inline trim_start/trim_end guard snippets are heavier than most callers need.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers the full lifecycle of a render: input mode selection, viewport sizing, waiting and async follow-up, retry semantics, error remediation, and cost. Return values need not be described because an output schema exists, and the annotations already declare write/open-world behavior.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is already 100%, so the baseline is a 3, but the description adds real meaning: url vs html exclusivity and the private-network limitation, width/height as the creative's size with concrete examples (300x250, 728x90), durationSec as a maximum under trim_end, and the interaction of transparent with format and the 3840px width cap. It stops short of explaining some advanced params (crf, pixelFormat, bitrateMode, markerLatencyMs), leaving those entirely to the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb and resource ('Record a web page or HTML5 creative ... to a video') and immediately enumerates the output formats, so the agent knows exactly what it gets. It also names the sibling tools (get_recording, estimate_recording) and the cases they serve, which separates it cleanly from list_recordings and delete_recording.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly routes between url and html input, explains which to prefer for published artifacts, and says to call get_recording with the id rather than re-calling record_html when the video is still queued. It also names estimate_recording for cost checks and states the retry conditions (10-minute cache, new revision after the creative changes).

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

Resources