Morpha SDK

`morpha-studio-sdk` is the official npm client for Morpha — the friendliest way to drive the editor from code. It calls the same tool catalog as MCP, over the same Worker HTTP endpoints, and adds one-call PNG frame + MP4 video rendering with no ffmpeg (a real local browser does the encode). It's the recommended client; it doesn't replace the MCP server — MCP-native agents (Claude Desktop, Claude Code) still connect over MCP.

npm i morpha-studio-sdk

createClient is the programmatic equivalent of an MCP session. Every tool you can call over MCP, you call here.

import { createClient } from "morpha-studio-sdk";

const morpha = createClient({ token: process.env.MORPHA_TOKEN }); // origin defaults to https://morphastudio.ai

const tools = await morpha.listTools();                  // the tool catalog (OpenAI tool shape)
const project = await morpha.getProject("your-project-id"); // the live project JSON

// One tool call = load -> dispatch -> write back, server-side (exactly like MCP):
const { result, project: next, editorUrl } = await morpha.callTool(
  "your-project-id",
  "add_text_layer",
  { text: "HELLO", x: 540, y: 600, font_family: "Anton" },
);

// A composited PNG of one frame (real browser, no ffmpeg):
const png = await morpha.renderFrame("your-project-id", 150);

// The full composition as an MP4 Buffer — same WebCodecs pipeline as the
// editor's Render button (no ffmpeg, no server):
const mp4 = await morpha.renderVideo("your-project-id");

createClient({ origin?, token?, fetch? }) returns a MorphaClient:

callTool throws only on transport/HTTP errors; a tool-level failure (e.g. a missing asset) comes back as result.ok === false — a normal outcome to inspect, not an exception. The token is the same mp_… API key MCP uses (mint it at `/app/settings`). For local dev, createClient({ origin: "http://localhost:8787" }) needs no token.

Render a frame to PNG — renderFrame (no ffmpeg)

The standalone form, if you don't need a full client:

import { renderFrame } from "morpha-studio-sdk";
import { writeFile } from "node:fs/promises";

const png = await renderFrame({ projectId: "demo", frame: 150, token: process.env.MORPHA_TOKEN });
await writeFile("frame-150.png", png);

Rendering composites the whole project (video + captions + shapes + text) in a real browser, so it's pixel-identical to the editor — and it decodes HEVC / iPhone `.MOV` via the OS decoder. It needs Playwright + Google Chrome on the calling machine:

npm i playwright    # optional peer dependency; only needed for renderFrame()

Options: { projectId, frame?, origin?, token?, width?, height?, channel?, timeoutMs? }. channel defaults to "chrome" (system Chrome — decodes HEVC on macOS/Windows); pass "chromium" for H.264/VP9/AV1 only. If a video layer can't decode, renderFrame throws a descriptive error rather than returning a black frame.

Render the full video to MP4 — renderVideo (no ffmpeg, no server)

renderVideo exports the whole composition to an MP4 — the same in-browser WebCodecs H.264 pipeline the editor's Render button uses, driven by a real local browser. There's no ffmpeg and no server-side render: the encode happens in your machine's Chrome.

import { renderVideo } from "morpha-studio-sdk";
import { writeFile } from "node:fs/promises";

const mp4 = await renderVideo({ projectId: "demo", token: process.env.MORPHA_TOKEN });
await writeFile("demo.mp4", mp4);

Options: { projectId, origin?, token?, channel?, timeoutMs? }. Like renderFrame, it needs Playwright + system Chrome (channel defaults to "chrome"don't use "chromium", which ships without the H.264 encoder and will fail). timeoutMs defaults to 10 minutes; a 30 s 1080×1920 composition encodes in well under a minute. If the export fails (e.g. a clip can't load), renderVideo throws a descriptive error rather than returning a truncated file.

Build a project offline — the pure core

The package also exports the pure tool catalog — the same functions with no network and no persistence. Use it to construct or transform a project in memory (you save it yourself).

import { blankProject, dispatch, projectSchema } from "morpha-studio-sdk";

let project = blankProject({ projectId: "demo", canvasWidth: 1080, canvasHeight: 1920 });
project = dispatch.add_text_layer(project, { text: "HELLO", x: 540, y: 600 }).project;
project = dispatch.add_caption_track(project, {
  lines: [{ text: "first line", startFrame: 0, endFrame: 30 }],
}).project;

projectSchema.parse(project); // a valid Morpha project

dispatch.<tool>(project, args) returns { project, result } and changes nothing on the server — it's local-only. To edit a project the editor will see, use callTool (above) instead. TOOL_DEFINITIONS is the full catalog in OpenAI tool shape.

Captions

import { transcriptToCaptionLines, buildCaptionsForClip } from "morpha-studio-sdk";

transcriptToCaptionLines(words) turns transcript word-timings into synced caption lines; buildCaptionsForClip / hasCaptionsForClip / removeCaptionsForClip / videoElementIdForClip build and manage a caption track on a clip.

Types

All TypeScript types are exported: Project, ImageLayer, VideoLayer, TextLayer, LayerStyle, Easing, ToolFunction, ToolResult, ToolDispatch, RenderFrameOptions, MorphaClient, MorphaClientOptions, ToolCallResult, CaptionLine, TranscriptWordLike, BlankProjectOpts.

SDK vs MCP

Use the SDK for scripts and agents you write in JS/TS — it's typed, ergonomic, and renders frames. Use [MCP](/docs) for MCP-native clients (Claude Desktop, Claude Code) that speak the protocol directly. Both hit the same catalog over the same backend; the SDK is a client of it, not a replacement.