Skip to main content
Glama
README.md
# takumi-mcp

A Bun stdio MCP server for Takumi images, animations, PDFs, TanStack charts, and Shiki code assets. One package, composed from Effect services. Results contain `{ path, mimeType, bytes }`; binary media never enters the agent context.

## Run

Requires Bun 1.4.2 or newer. After the package is published:

```json
{
  "mcpServers": {
    "takumi": {
      "command": "bunx",
      "args": ["--bun", "takumi-mcp"]
    }
  }
}
```

From this checkout (no npm publishing required):

```sh
bun install --frozen-lockfile
bun run build
bun dist/cli.js
```

Run `bun run demo` to generate a card, effects image, chart, code asset, animation, and PDF in `out/`. The demo overwrites only its named sample files. A machine-local `.mcp.local.json` is available in this checkout for testing with an MCP client; it is ignored by Git.


For local MCP configuration, use `bun` as the command and the absolute path to `dist/cli.js` as its argument. stdout is reserved for MCP; Effect logs go to stderr.

## Tools

| Tool | Output | Inputs |
| --- | --- | --- |
| `renderImage` | PNG (default), JPEG, WebP, SVG | source, width, height, outputPath; optional css, fonts, format, quality, timeMs, dithering |
| `renderAnimation` | Animated WebP (default), APNG, GIF; detected MP4/WebM | scenes, width, height, fps, outputPath; optional css, fonts, format |
| `renderPdf` | Paginated PDF | source, outputPath; optional css, fonts, pageSize (A4/Letter), landscape, margin, title |
| `renderChart` | SVG | type (bar/line/dot), categories, series, outputPath; optional width, height, accessibility labels |
| `renderCode` | SVG | code, outputPath; optional language, theme, width, height, css, fonts |

All tools accept `overwrite: true`; by default existing files are preserved. Writes commit atomically from a temporary file in the destination directory. Directories are created as needed. The selected format controls encoding; use a matching file extension.

`source` accepts `{ "html": "<div>...</div>" }`, `{ "sourcePath": "/absolute/template.tsx", "props": { ... } }`, or `{ "code": "const x = 1", "language": "typescript", "theme": "github-dark" }`. HTML files are also accepted; their props are unused. React modules must default-export an element or a component/factory, which may be async and receives props plus `signal`.

**Local JS/TS/JSX/TSX templates execute trusted code with the server's permissions.** They are not sandboxed. Keep template logging on stderr. Inline HTML does not execute scripts. Root modules refresh when their modification time changes; restart the server after changing imported template dependencies.

Paths are relative to the server's working directory. Image paths inside file templates resolve relative to the template. For inline HTML, image paths resolve relative to the server's working directory. Fonts are local paths or HTTP(S) URLs, resolved from the working directory. Native Takumi's default font is available without configuration; supply fonts for brand typography, monospace, or additional scripts.

## Examples

Render a reusable React/Tailwind card:

```json
{
  "source": { "sourcePath": "examples/card.tsx", "props": { "title": "A better way to render." } },
  "width": 1200,
  "height": 630,
  "outputPath": "out/card.png"
}
```

Generate a chart once with `renderChart`:

```json
{
  "type": "bar",
  "categories": ["Jan", "Feb", "Mar"],
  "series": [{ "name": "Revenue", "values": [12, 18, 27] }],
  "width": 640,
  "height": 360,
  "outputPath": "out/revenue.svg"
}
```

Then compose it into `renderPdf` (or `renderImage`) without passing SVG text through the model:

```json
{
  "source": { "html": "<main><h1>Quarterly report</h1><img src='out/revenue.svg' width='640' height='360'/></main>" },
  "pageSize": "A4",
  "margin": 32,
  "title": "Quarterly report",
  "outputPath": "out/report.pdf"
}
```

Render CSS motion with `renderAnimation`:

```json
{
  "scenes": [{ "source": { "sourcePath": "examples/animation.html" }, "durationMs": 2000 }],
  "width": 400,
  "height": 160,
  "fps": 24,
  "format": "webp",
  "outputPath": "out/motion.webp"
}
```

Scene CSS shares one animation stylesheet: use unique selectors/keyframe names when scenes define different rules. Native animated formats require no FFmpeg. Video formats are advertised only when their encoder passes the startup checks below.

Render highlighted code as a reusable SVG asset with `renderCode`:

```json
{
  "code": "const answer = Effect.succeed(42)",
  "language": "typescript",
  "theme": "github-dark",
  "outputPath": "out/code.svg"
}
```

Shiki tokens become React text nodes, preserving escaping. Use the resulting SVG with `<img>` in another card, report, or motion scene. Omit `width` and `height` to derive the asset dimensions from the highlighted content; set them to control the viewport, with long code clipped to that viewport.

SVG assets are convenient to embed and scale. Embedding one with `<img>` treats it as a single image in the outer scene, so use the direct `{ code, language, theme }` source when individual lines or tokens need CSS animation hooks.

### Animated highlighted code

Use highlighted code directly as a scene source. Shiki runs once per scene; Takumi samples the CSS animation across its frames. No intermediate PNG is needed, so individual lines/tokens remain animatable.

```json
{
  "scenes": [{
    "source": { "code": "const answer = 42;", "language": "typescript" },
    "durationMs": 1500
  }],
  "css": "@keyframes reveal { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } .shiki-line { animation: reveal 600ms ease-out both; }",
  "width": 640,
  "height": 120,
  "fps": 24,
  "format": "webp",
  "outputPath": "out/code-motion.webp"
}
```

CSS hooks are `.shiki` (the whole block), `.shiki-line`, `.shiki-line-1` (1-based line number), `.shiki-token`, and `.shiki-token-line-1`. Add different `animation-delay` values to numbered lines for staggered reveals. Whitespace and empty lines are preserved. Use `!important` when overriding the block's inline theme/layout defaults, such as font size or padding.

Run `bun run demo:code` for a complete staggered reveal, using the ready-to-call [MCP arguments](examples/code-animation.json). It writes `out/code-animation.webp` and a still frame at `out/code-animation.png`. The same code source works with `renderImage` (including `timeMs`) and `renderPdf`.

This follows [Takumi's CSS keyframe animation model](https://takumi.kane.tw/docs/keyframe-animation). Output is WebP/APNG/GIF, plus MP4/WebM when supported by the locally installed FFmpeg.

## Styling and visual effects

`className`, HTML `class`, and `tw` compile through the installed Tailwind 4 compiler. Standard theme utilities and arbitrary values work without a CSS build step. No Tailwind plugins, project config, or browser preflight are loaded. Raw styles, `<style>` tags, and the tool's `css` argument pass through to Takumi.

Use the [effects example](examples/effects.html) for gradient text, masks, clipping, filters, and shadows. CSS also exposes Takumi's blend modes, conic gradients, inline SVG filter graphs, and motion paths. `renderImage.dithering` accepts `none` or `ordered-bayer`. See [Takumi visual effects](https://takumi.kane.tw/docs/visual-effects) for the supported CSS recipes. Rendering support is determined by Takumi, not by browser CSS support; SVG/PDF output can differ from raster output for advanced effects. For exact raster appearance in PDFs, render the effect to PNG and embed it.

## Optional FFmpeg video

At startup, the server checks `ffmpeg` on PATH, inspects its encoders, and tests a tiny encode. Each successful format is added to `renderAnimation.format`:

| FFmpeg encoder | Additional format |
| --- | --- |
| `libx264` | `mp4` (H.264) |
| `libvpx-vp9` | `webm` (VP9) |

If FFmpeg is missing, unusable, or lacks a working encoder, that format is omitted from both the tool schema and description. The server never installs FFmpeg. Restart the server after installing or changing it. Set `TAKUMI_FFMPEG_PATH` to an executable path to use a particular installation.

Video uses the same scenes, CSS, dimensions, and frame rate as native animations. Frames stream from Takumi into FFmpeg with backpressure; output is written directly to a temporary file and committed only on success. Cancellation aborts rendering and terminates the encoder, escalating to a forced kill after two seconds if necessary.

Video dimensions must be even. MP4/WebM have no audio or alpha channel; transparent pixels are composited over black so opacity animations remain visible. The current presets prioritize readable graphics: H.264 CRF 18/veryfast, or VP9 CRF 28 with unconstrained bitrate. Scene clocks reset at scene boundaries, with each scene rounded up to a whole number of frames.

If MP4 is detected, try `bun run demo:code mp4`; use `webm` for VP9. Unsupported formats fail validation. Standard `bun run demo:code` always uses WebP.

## Architecture and limits

- Effect RC's MCP server, Toolkit, Schema, Context services, Layers, filesystem, HTTP client, semaphores, timeouts, and scoped finalizers handle orchestration. Promises are limited to library/runtime boundaries.
- A shared native renderer reuses a 32 MiB cache and serializes native renders. At most two tool jobs prepare/render concurrently.
- `Effect.tryPromise` interruption and scoped finalizers propagate AbortSignal to Takumi and template factories. PDF rendering runs in a disposable worker because takumi-pdf has no native signal API; cancellation terminates its WASM runtime.
- Effect `4.0.0-rc.112` incorrectly converts numeric MCP cancellation IDs to strings. The checked-in one-line patch preserves ID types. Effect and its Bun platform are bundled into the CLI so npm users receive the fix. Remove the patch after an upstream release passes the numeric cancellation integration test.
- File-backed templates/assets are limited to 32 MiB each. Each prepared source allows 10,000 nodes, 64 image references, 1 MiB of text/classes/CSS, and 64 MiB of fetched assets; fonts allow 64 MiB per request. HTTP requests time out after 30 seconds. Tool jobs time out after two minutes. Dimensions are 1–8192, raster images at most 32 million pixels. Animations are at most 60 seconds, 600 sampled frames, and 250 million total pixels. Charts allow 512 categories and 64 series; code inputs allow 100,000 characters.
- The source pipeline loads `<img>` resources locally or over HTTP(S). Prefer image elements/data URIs for assets; external CSS resources and web font declarations are not a browser loading pipeline. Pass font files through `fonts`.

The single package keeps these concerns as services rather than small published subpackages. Dependencies and the Bun lockfile pin the verified versions, including the Effect RC.

## Development and publishing

```sh
bun run format
bun run check
bun test
bun run build
bun run test:dist
npm pack --dry-run
```

Tests exercise the real stdio protocol, native raster pixels, chart-to-PDF composition, all animation formats, Shiki, output commits, cancellation, and worker failures. CI checks macOS and Linux. [Publishing setup](docs/publishing.md) covers npm OIDC and the one-time account configuration. No package has been published by this checkout.