kenari-media-mcp
This server is an MCP server that exposes the Kenari.id media APIs to MCP clients, generating/editing images and videos, saving results to disk, and returning absolute file paths.
List media models: Fetch the public Kenari media model catalog, optionally filtered by
imageorvideo, including per-image IDR costs.Generate images: Create images via text prompts with configurable model, count, size, background, and preview mode; files are saved under the output directory and absolute paths are returned.
Edit images: Edit a local image with a prompt and optional mask image, supporting
opaque/autobackgrounds and refusing obviously sensitive local files.Create videos: Start video-generation jobs from text, images, or source videos, with optional duration, resolution, aspect ratio, and start/end frames.
Extend videos: Extend a finished video using a prior job ID or an HTTPS URL, with optional continuation prompt and duration.
Check video status: Poll or fetch Kenari video job status; finished videos are automatically downloaded to the output directory.
Wait for videos: Poll until completion/failure/timeout, emit progress notifications when requested, and auto-download finished results.
Download videos: Fetch finished video bytes to the output directory when rendering is complete.
Cost and safety controls: Enforce per-call cost caps, cap image counts and video durations, optionally disable video tools, and redact API keys from logs.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@kenari-media-mcp Generate an image of a serene mountain lake at sunrise"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
kenari-media-mcp
An MCP (Model Context Protocol) server for the Kenari.id media APIs (https://kenari.id/v1). It gives any MCP client (Claude Desktop, Cursor, OpenCode, etc.) 8 tools for image generation and editing plus video generation, extension, status polling, and download. It runs over stdio and is designed for agentic use: all generated media is written to disk under a configurable output directory and returned as absolute file paths — never base64 or ImageContent — so the calling agent can open, move, or post-process files directly.
This project fills a gap: Kenari's own official MCP server covers docs, balance, and search, but not the media generation endpoints. This is a third-party, unaffiliated implementation for those endpoints.
⚠️ Cost warning — real money
Generation endpoints spend real Indonesian Rupiah (IDR) per call, billed to your Kenari account. Prices vary per model —
gpt-image-2is roughly 175 IDR per image, while others cost several times more. Treat the catalog prices you see vialist_media_modelsas examples only; live prices come fromlist_media_models.Note that
previewdoes not save money — files are always written to disk regardless.Never point automated tests or scheduled jobs at generation tools; the test suite uses a mocked
fetchand spends nothing.
Related MCP server: Magic Hour MCP Server
Requirements
Node.js >= 20 (
enginesfield is enforced;node --versionto check)pnpm (or npm) for installing and running scripts
A Kenari API key (
kn-...), obtained from the Kenari dashboard. Required for everything exceptlist_media_models(GET /v1/modelsis public).
Install from source
This is the primary installation path until the package is published to npm (it is not on npm yet — npx will not work yet).
git clone https://github.com/dnshtm9/kenari-media-mcp.git
cd kenari-media-mcp
pnpm install
pnpm buildThe build writes the server entry to dist/index.js (the bin is kenari-media-mcp).
Environment variables
Var | Default | Notes |
| (none) | Required for everything except |
|
| Trailing slashes trimmed. |
|
| Media save directory (created recursively). Absolute paths are returned. |
| allow | Set to |
|
| Preflight cap for |
|
| Preflight cap for |
| (none) | Optional per-call IDR ceiling for image and video tools (best-effort catalog lookup; fail-open when the price is unknown). |
.env files — important caveat
The repository includes a .env.example. Copy it to .env if you want pnpm start (or other local runs) to pick up defaults — pnpm start loads .env via a small built-in reader; existing environment variables always win.
MCP clients do not reliably load .env. When a client spawns this server, the server's working directory is often not the repository, so any .env next to it may be invisible. You must inject environment variables in the client JSON env block. Treat .env as a local-run convenience only.
Never commit .env or any file containing your API key.
MCP client configuration
Recommended (local build): node + absolute path
Point the client at the built server. Most MCP clients require an absolute path in args — relative paths are resolved against the client's own working directory, not yours.
Substitute the location where you cloned the repository. On Windows, double every backslash in JSON strings and save the config file as UTF-8 without BOM (e.g. VS Code → "Save with Encoding → UTF-8"); a BOM can break config parsing.
Windows pattern:
{
"mcpServers": {
"kenari-media": {
"command": "node",
"args": ["C:\\\\path\\\\to\\\\kenari-media-mcp\\\\dist\\\\index.js"],
"env": {
"KENARI_API_KEY": "kn-REPLACE_ME",
"KENARI_OUTPUT_DIR": "C:\\\\path\\\\to\\\\kenari-output"
}
}
}
}macOS / Linux pattern:
{
"mcpServers": {
"kenari-media": {
"command": "node",
"args": ["/path/to/kenari-media-mcp/dist/index.js"],
"env": {
"KENARI_API_KEY": "kn-REPLACE_ME",
"KENARI_OUTPUT_DIR": "/path/to/kenari-output"
}
}
}
}KENARI_OUTPUT_DIR is any directory you choose; if you omit it, media is saved to ./kenari-output relative to the server's working directory (which may not be where you expect — setting it explicitly is safer).
After npm publish: npx (not yet possible)
The package is not published to npm yet, so the following does not work today. Once it is published, npx-based config will be:
Unix / macOS:
{
"mcpServers": {
"kenari-media": {
"command": "npx",
"args": ["-y", "kenari-media-mcp"],
"env": {
"KENARI_API_KEY": "kn-REPLACE_ME"
}
}
}
}Windows (npx is npx.cmd, which cannot be spawned directly, so clients must go through cmd):
{
"mcpServers": {
"kenari-media": {
"command": "cmd",
"args": ["/c", "npx", "-y", "kenari-media-mcp"],
"env": {
"KENARI_API_KEY": "kn-REPLACE_ME"
}
}
}
}Until then, use the local node + absolute-path config above.
Tools (8)
list_media_models { modality?: "image"|"video" }— public catalog with per-image IDR costs. Snapshot example: 5 image models, 0 video-generation models (the video list may be[]— video tools are still implemented and pass through to the API).generate_image { model, prompt, n?, size?, background?: transparent|opaque|auto, preview?=false }— text-to-image;ncapped byKENARI_MAX_IMAGE_N.edit_image { model, prompt, image_path, mask_path?, n?, size?, background?: opaque|auto }— reads local image files you pass;background: transparentis rejected by schema.create_video { model, prompt, duration?, resolution?, image_url?, end_image_url?, input_images?, video_url?, aspect_ratio? }— text/image-to-video;durationcapped byKENARI_MAX_VIDEO_DURATION; also honorsKENARI_MAX_COST_IDR_PER_CALL(per-call preflight estimate, see "Cost caps" below).extend_video { model, source, prompt?, duration? }—sourceis a job id or an https URL; local file paths are rejected before any HTTP request. Also honorsKENARI_MAX_COST_IDR_PER_CALLlikecreate_video.get_video_status { id }— checks a video job; auto-downloads the result when done.wait_for_video { id, timeout_ms?=1200000, download?=true }— polls every 5s with backoff capped at 15s; emitsnotifications/progresswhen the client sends a progress token. On timeout it returnsstill_renderingwithisError: falseso the agent can retry later.download_video { id }— downloads a finished video; returns astill_renderingerror while rendering.
Error codes, heartbeat, timeouts
Error codes surfaced in structuredContent: unauthorized | insufficient_balance | content_policy_violation | rate_limited | invalid_model | video_failed | video_expired | still_rendering | bad_request | upstream_error.
401 error bodies are plain text and are never assumed to be JSON.
Kenari image POSTs may stream ASCII-space "heartbeat" characters (~every 20s) before the JSON body — the server reads the full body, trims, then parses.
HTTP timeout: 300s for image calls.
wait_for_videodefault budget: 20 minutes.stdout carries JSON-RPC only; all logs go to stderr (
console.error).
Cost caps — per-call, fail-open, no accumulation
KENARI_MAX_COST_IDR_PER_CALL applies to generate_image, edit_image, create_video, and extend_video as a per-call preflight estimate, checked against the live catalog (list_media_models pricing) before any paid POST is made. Three fail-open paths, deliberately identical for images and video:
the env var is unset,
the catalog lookup itself fails, or
the model has no parseable price line.
In those cases the call proceeds and a warning is written to stderr. Video pricing calibration: no video-generation model has appeared in Kenari's catalog yet (pricing_lines with billable: "output_video" and a second/video/call unit). Once one appears, per-second prices are multiplied by duration (falling back to KENARI_MAX_VIDEO_DURATION when unset) and flat per-call prices are used as-is — no code change expected, but re-verify the estimate against the live catalog then.
There is no cross-call accounting: nothing accumulates spend across calls or sessions. An in-memory ledger was considered and intentionally deferred — it would reset on every server restart, producing dishonest numbers. If you need a hard budget, set the cap and monitor your Kenari dashboard.
Manual live smoke test
⚠️ This spends real money. Do it once by hand, never in CI or automated tests.
Build (
pnpm build) and setKENARI_API_KEY.Call
list_media_models→ expect the image model catalog.Call
generate_imagewith the cheapest model,n: 1→ confirm a file appears in the output dir.Call
get_video_statuson a bogus id → expectvideo_expired/bad_request, no spend.Only then try
create_videoif a video model exists in the catalog.
Development
pnpm test # tsx --test tests/**/*.test.ts — mocked fetch, no live spend
pnpm typecheck # tsc --noEmit
pnpm lint # tsc --noEmitTests mock fetch entirely, so running them costs nothing and requires no API key. The model-catalog fixture lives at tests/fixtures/models.json (a copy of a real GET /v1/models snapshot).
Security notes
kn-*API key values are redacted in logs.The
Authorization: Bearerheader is attached only to requests whose host matchesKENARI_BASE_URL's host (exact host match, port-agnostic, so self-hosted bases keep working). CDN URLs returned by the API (content/image downloads) are fetched unauthenticated, and a CDN-side 401 is reported asupstream_error— never as an API-key failure.Never commit API keys (
.env, shell history, config dumps).edit_imagereads local files you pass to it — only pass files you intend to upload. Obvious sensitive files are refused before reading:.env/*.env(incl..env.*), private keys (.pem,.key,id_rsa/id_ed25519-style names), and paths under.ssh,.aws, or.gclouddirectories. The blocklist is a guardrail, not a sandbox — do not point the tool at directories you do not control.Generation costs money on every call; beware of an agent looping on generation tools and burning IDR unattended (see "Cost caps" above).
License
See LICENSE.
Available Tools
8 toolscreate_videoCreate VideoA
Start a Kenari video job via POST /v1/videos/generations. Returns job id; poll with get_video_status/wait_for_video. Costs money.
Inputs: model (required), prompt (required), duration, image_url, +5 more. Returns: id, op, raw, code, +4 more
download_videoDownload VideoA
Download finished video bytes via GET /v1/videos/{id}/content to KENARI_OUTPUT_DIR. Errors with still_rendering while the job renders.
Inputs: id (required). Returns: id, op, code, hint, +3 more
edit_imageEdit ImageA
Edit a local image via POST /v1/images/edits (multipart: image, mask?, prompt, model, n?, size?, background?). background=transparent is rejected (use generate_image instead).
Inputs: model (required), prompt (required), image_path (required), n, +3 more. Returns: id, op, code, hint, +5 more
extend_videoExtend VideoA
Extend a finished video via POST /v1/videos/extensions {model, video:{url}, prompt?, duration?}. source is a job id or https URL — local paths are rejected before any HTTP call.
Inputs: model (required), source (required), prompt, duration. Returns: id, op, raw, code, +4 more
generate_imageGenerate ImageA
Generate image(s) via POST /v1/images/generations. Saves PNG/JPG to KENARI_OUTPUT_DIR and returns absolute path(s). Costs IDR per image — check list_media_models first.
Inputs: model (required), prompt (required), n, size, +2 more. Returns: id, op, code, hint, +5 more
get_video_statusGet Video StatusAIdempotent
GET /v1/videos/{id}. Reports queued/rendering/done/failed/expired. Auto-downloads to KENARI_OUTPUT_DIR when done.
Inputs: id (required). Returns: id, op, code, hint, +3 more
list_media_modelsList Kenari Media ModelsARead-only
List Kenari media-capable models (image/video). Public, no key needed.
Inputs: modality. Returns: id, op, code, hint, +3 more
wait_for_videoWait For VideoA
Poll GET /v1/videos/{id} every 5s (backoff cap 15s) until done/failed/expired or timeout (default 20 min). Auto-downloads when done. Timeout returns still_rendering with isError=false.
Inputs: id (required), download, timeout_ms. Returns: id, op, code, hint, +3 more
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 8 tools
Image generation/edit and video create/extend are clearly separated, and each tool has a distinct action. However, get_video_status, wait_for_video, and download_video overlap somewhat: both status tools auto-download and wait_for_video is essentially status polling with a loop, which can cause agent misselection.
All tool names follow a consistent snake_case verb_noun pattern: generate_image, edit_image, create_video, extend_video, download_video, etc. The naming style is uniform and predictable across image and video workflows.
Eight tools is well-scoped for a media generation server covering both images and videos. Each tool maps to a meaningful API operation or workflow helper without unnecessary redundancy.
The core media lifecycle is covered: list models, generate/edit images, create/extend videos, check status, wait, and download results. Minor gaps exist around video cancellation/deletion and explicit image result management, but agents can complete primary workflows without workarounds.
Maintenance
Related MCP Connectors
Transform video, audio and images, and generate media from prompts. FFmpeg, captions, models.
Generate images, videos and music with model discovery, cost estimates and asynchronous results.
Create AI images and videos, manage projects and credits, and use workspace campaign context.
LLM chat, text tools, image generation, editing, batch image jobs, and asynchronous video generation
Related MCP Servers
- AlicenseAqualityCmaintenanceEnables text-to-image and text-to-video generation via the Agnes AI API, with support for multiple models, async video generation, and automatic file downloads.61MIT

Magic Hour MCP Serverofficial
AlicenseAqualityCmaintenanceEnables image, video, and audio generation via Magic Hour's API, including async polling, file uploads, and project downloads.459Apache 2.0- AlicenseNot gradedqualityAmaintenanceEnables MCP-compatible clients to generate images and videos, synthesize speech, clone and design voices, and create related media outputs.493 npmMIT
- AlicenseNot gradedqualityAmaintenanceEnables MCP-compatible clients to generate images and videos, synthesize text-to-speech audio, and clone or design custom voices. It also supports flexible output handling (URLs or local files) and multiple transport modes including stdio, REST, and SSE.493 npmMIT