Skip to Content
MCP Server

MCP Server

oJo ships an official Model Context Protocol  server, so an AI agent can design and render images directly instead of you writing HTTP calls on its behalf. It is published to npm as @ojodotso/mcp-server, runs locally over stdio, and talks to the same public API documented on these pages — there is nothing extra to host.

ℹ️

The server authenticates with your oJo API key. Create one in API settings .

The point of using it over the raw API is the design loop: an agent can iterate on a design at full resolution for free, and spend a credit only on the frame that is actually right.

Install

Requires Node.js 20 or newer. You do not need to install anything by hand — npx fetches the package on first run.

claude mcp add ojo -s user -e OJO_API_KEY=<your API key> -- npx -y @ojodotso/mcp-server

-s user makes the server available in all your projects; drop it to scope the server to the current project only. To share it with your team through version control, use -s project — that writes to .mcp.json, so reference the key as ${OJO_API_KEY} and keep the real value in your environment.

Restart your client after adding the server. It should then list six oJo tools.

Environment variables

VariableRequiredDefaultDescription
OJO_API_KEYYesYour oJo API key. The server refuses to start without it.
OJO_API_BASE_URLNohttps://api.ojo.so/v1Point the server at a different oJo API — see Self Hosting.
OJO_WEB_BASE_URLNohttps://ojo.soBase URL used to build preview links. Change it if you serve the oJo web app from your own domain.
⚠️

OJO_API_BASE_URL decides where your API key is sent. Only point it at a host you control.

The design loop

Rendering an image costs a credit; previewing one does not. The tools are built around that asymmetry, and an agent using them well will spend exactly one credit per finished image no matter how many revisions it took.

1. Draft the HTML

The agent writes plain HTML with inline CSS, using {{variable}} placeholders wherever the content should be swappable. Google Fonts and other https: resources work.

2. Preview it — free, unlimited

create_preview_link returns a URL that renders the draft live, at full resolution, through the same engine that produces the final PNG:

{ "html": "<div style=\"...\"><h1>{{title}}</h1></div>", "variables": { "title": "Preview links, zero credits" }, "viewportWidth": 1200, "viewportHeight": 630 }
https://ojo.so/preview#draft=H4sIAAAAAAAAA5VW227jNhD9FYJBuhtAkilZdmTK2Rb...

The draft is compressed into the URL fragment, so it never reaches a server and nothing is stored. From here you have two options, and both are free:

  • The agent checks its own work — open the link in a headless browser and screenshot the iframe[title="oJo draft preview"] element. Give web fonts a moment to load before capturing.
  • A human checks it — paste the link into a message. Whoever opens it sees exactly what the render will produce, with no oJo account needed.

Fix what’s wrong, call create_preview_link again, repeat. Still zero credits.

3. Render or save it

Once the design is right, spend the credit — or don’t spend one at all:

  • generate_image_from_html renders the final PNG and returns its public URL. 1 credit.
  • create_template saves the HTML as a reusable template instead. No credit. Later, generate_image_from_template renders it with per-render values, one credit each.
ℹ️

The generate tools accept inspect: true, which attaches a 512px-wide thumbnail of the result. It is useful for a quick sanity check, but far too small to judge layout or typography — that is what the preview link is for.

Tools

ToolWhat it doesCredits
create_preview_linkBuild a live, shareable preview URL for a draft0
generate_image_from_htmlRender HTML into a PNG1
generate_image_from_templateRender a saved template into a PNG1
create_templateSave HTML as a reusable template0
list_templatesList the templates on your account0
get_templateFetch one template’s HTML and variable defaults0
ParameterTypeDescription
htmlstringRequired. HTML/Handlebars to preview. Not base64-encoded.
variablesobjectValues to fill the placeholders for this preview.
viewportWidthintegerWidth in pixels (default 1280).
viewportHeightintegerHeight in pixels (default 800).

generate_image_from_html

ParameterTypeDescription
htmlstringRequired. HTML with inline CSS. Not base64-encoded.
viewportWidthintegerWidth in pixels (default 1280).
viewportHeightintegerHeight in pixels (default 800).
transparentBackgroundbooleanRender on transparency instead of white.
inspectbooleanAlso return a 512px thumbnail of the result.

Returns the image id and its public URL. Mirrors Create Image from HTML.

generate_image_from_template

ParameterTypeDescription
templateIdstringRequired. Id of an existing template.
modifyobjectPer-render values, merged over the template’s variable defaults.
viewportWidthintegerWidth in pixels (default 1280).
viewportHeightintegerHeight in pixels (default 800).
transparentBackgroundbooleanRender on transparency instead of white.
inspectbooleanAlso return a 512px thumbnail of the result.

Mirrors Create Image from Template.

create_template

ParameterTypeDescription
htmlstringRequired. HTML/Handlebars with {{variable}} placeholders.
variablesobjectDefault values for the placeholders — the template’s shape.

Mirrors Create Template.

list_templates

ParameterTypeDescription
pageinteger1-based page number (default 1).
pageSizeintegerTemplates per page (default 10).
sortenumasc or desc by creation time (default desc).

get_template

ParameterTypeDescription
templateIdstringRequired. Id of the template to fetch.

Returns the template’s decoded HTML — you can pass it straight to create_preview_link to preview a set of modify values before spending a credit on them.

Variables

Two different things share the word “variables”, and the tools keep them apart:

  • Template Variables — the defaults you pass to create_template. They describe the template’s shape: which placeholders exist and what they hold when nothing overrides them.
  • Modify Payload — the per-render values you pass as modify to generate_image_from_template (or as variables to create_preview_link). They are merged over the defaults for that one render and change nothing on the template.

Templates are rendered with Handlebars, so conditionals, loops and helpers all work — see Template Helpers for the full list. Two things catch people out: {{value}} escapes HTML, so use {{{value}}} when a variable contains markup, and formatDate renders in local time rather than UTC.

Caveats

Restart the client after upgrading. npx -y caches the package rather than re-resolving it on every run, and a running MCP server keeps whatever version it started with. If a newly released tool doesn’t appear, find the cached copy:

find ~/.npm/_npx -maxdepth 5 -type d -path '*@ojodotso/mcp-server'

Delete the _npx/<hash> directory that contains it — not all of ~/.npm/_npx, which caches every other tool you run through npx — then restart your MCP client so it spawns a fresh server.

Errors are returned as readable text, not raised as protocol errors, so the agent can act on them: a missing or invalid key reports Unauthorized, an exhausted balance reports Out of credits, and hitting a rate limit reports Rate limited by the oJo API.

Preview links are self-contained and unverified. Everything in a /preview#draft= link lives in the URL fragment, which is why nothing is stored and no credit is charged — but it also means the content is supplied entirely by whoever built the link, not by oJo. The draft is rendered inside a sandboxed iframe that cannot reach your oJo session, so opening one is safe in that sense. Still, treat a preview link you received from someone else the way you’d treat any untrusted web page: it can run its own scripts, and the page it shows is theirs, not ours.