Skip to main content
Glama
enkind

Napkin

by enkind

Napkin

A blank napkin for your agent. It sketches nothing — you do.

Website · Privacy · Terms · Issues

Napkin is a small, free agent plugin whose core is an MCP App: a remote MCP server that ships an interactive widget the host renders next to the tool result. The agent calls one tool, a sheet of paper appears in the conversation, you draw on it, press Send, and the drawing arrives as an image in your next message.

It exists because the alternative is worse. Describing a shape in words is slow and lossy, and the workaround — pick up a real napkin, draw on it, photograph it, upload the photo — works but costs a detour through your phone every time. Some directions are only five seconds of pen: a logo that should feel like a mountain but rounder, which panel sits where, how three boxes connect.

Questions and bug reports go to the issue tracker; support is best-effort on a noncommercial project. MIT licensed.

Install

Paste this into Codex, Cursor, or Claude Code, then start a new chat:

Install the Napkin plugin from https://github.com/enkind/napkin

Or do what the agent would do. The repository is a marketplace containing one plugin, plugins/napkin, for each host:

# Codex
codex plugin marketplace add enkind/napkin
codex plugin add napkin@napkin

# Claude Code
claude plugin marketplace add enkind/napkin
claude plugin install napkin@napkin

In Cursor, add https://github.com/enkind/napkin.git as a marketplace through Customize → Plugins and install Napkin from it.

There is no account and no key. Every host connects to the same Streamable HTTP endpoint, https://napkin-neon.vercel.app/mcp; nothing runs locally. The napkin needs a host that renders MCP Apps and accepts images from them. Where one does not, the widget says so and Send stays disabled.

Related MCP server: cnvs-whiteboard

Repository standard

Napkin follows the same recipe as its sibling plugin, Better Response: one deployment that is both the website and the MCP server, and one directory that is everything a host installs.

app/                 Website, /demo, and the Streamable HTTP MCP route at /mcp
widget/              The napkin: a Vite app built into one self-contained HTML file
plugins/napkin/      Installable skill, host manifests, logo, and MCP URL

The remaining dotfolders are host entrypoints, not competing layouts:

.agents/plugins/marketplace.json     Codex repository marketplace
.cursor-plugin/marketplace.json      Cursor repository marketplace
.claude-plugin/marketplace.json      Claude Code repository marketplace
.cursor/mcp.json                     Cursor CLI localhost HTTP configuration
plugins/napkin/.codex-plugin/        Codex manifest, with the logo and store metadata
plugins/napkin/.cursor-plugin/       Cursor manifest, which carries its own MCP URL
plugins/napkin/.claude-plugin/       Claude Code manifest
plugins/napkin/.mcp.json             MCP URL for Codex and Claude Code

The manifests and marketplace catalogs cannot be merged because each host requires its own paths and schema. All of them describe the same plugin directory, so a host installs a ~20K payload rather than this repository. pnpm set-endpoint <url> rewrites the MCP URL in both places it lives.

The widget is built into the ignored widget/dist/index.html immediately before Next.js builds. /mcp serves it as the ui:// resource, and /demo serves the same file as a page so the website's chat mock can host the real napkin rather than a picture of one. Both routes list it in outputFileTracingIncludes.

What v1 is

A pen and a Send button. No eraser, no undo, no colours, no shapes, no rulers. A napkin is the rough version of an idea, and the constraint is the point — to change a sketch, ask for a fresh napkin.

How the sketch gets back

This is the part worth understanding, because the obvious design does not work.

The instinct is to have the widget call a tool with the PNG and let the tool return it. But the result of a tool the widget invoked goes back to the widget, and no host is obliged to also put it in front of the model. A sketch the model never sees is useless.

So the widget hands the image to the host instead, with ui/message and an image content block, and the host adds it to the thread as though you had attached the file yourself. Codex ships a localised string for exactly this — "Shared an image from {appName}" — so it is a designed path, not a loophole.

Two consequences fall out of that, and both are good:

  • The server is stateless. The drawing never touches the deployment. There is one tool, no upload route, and nothing stored, which is why this runs on serverless functions without a database.

  • The image lands in your own transcript. You see what you sent, and the turn starts on your press rather than on the model's guess about when you are done.

The route is negotiated from the host's declared capabilities rather than assumed. A host that takes images only as model context gets the image parked there with a short text message as the trigger; a host that takes them nowhere gets a disabled Send button and an honest explanation. See DECISIONS.md for the host evidence behind this.

The tool

open_napkin({ brief? }) — model-visible, and the only tool. It opens the napkin and returns, telling the agent to stop and wait. brief is a short reminder printed on the paper, like "rough logo direction".

plugins/napkin/skills/sketch/SKILL.md teaches agents when to reach for it, and — just as importantly — that after calling it they should say something brief and then wait rather than narrate a drawing that does not exist yet. Users can also ask for it directly with /sketch, listed as Napkin: Sketch.

How the MCP App works

MCP Apps is SEP-1865, stable since 2026-01-26 and identified as io.modelcontextprotocol/ui. A resource with a ui:// URI and MIME type text/html;profile=mcp-app returns the widget's HTML, the tool points at it through _meta.ui.resourceUri, and the host renders it in a sandboxed iframe and speaks MCP JSON-RPC to it over postMessage.

The widget's HTML travels as text inside the JSON-RPC response, and the host injects it into an iframe under its own origin. Host sandboxes enforce a CSP that restricts which origins a widget may load subresources from, so the widget is compiled into a single self-contained HTML file that fetches nothing and works on every host regardless of that policy. See DECISIONS 001.

app/
  page.tsx              the website
  chat-window.tsx       the chat-client mock around the live napkin
  napkin-card.tsx       the page acting as the napkin's MCP Apps host
  styles.css            the site's design language and its two rules
  privacy/ terms/ support/
  demo/route.ts         the widget bundle, served as a page for the mock
  mcp/route.ts          MCP server — open_napkin + the ui:// resource
  .well-known/openai-apps-challenge/route.ts   plugin-directory domain check
widget/
  app.tsx               the napkin: canvas, pen, Send
  use-mcp-app.ts        the host bridge, including the negotiated image route
  use-napkin-size.ts    how big the paper is and which way round it lies
  preview.tsx           dev-only mock host with measured frames
vite.config.mts         single-file widget build
proxy.ts                CORS headers, so browser-based MCP clients can connect
scripts/dev-web.mjs     watched widget + Next.js, and shared process cleanup
scripts/dev-codex.mjs   isolated Codex instance with this plugin installed
scripts/dev-cursor.mjs  isolated Cursor instance with this plugin installed
scripts/test-client.mjs protocol smoke test
scripts/set-endpoint.mjs writes a deployment URL into the plugin
scripts/capture-seed-thread.mjs captures one of your threads as a seed fixture

The canvas

The paper is a golden rectangle — 1024×633 lying down, 633×1024 standing up — and which way round it lies comes from the host: landscape on a desktop, portrait on a phone unless the phone is turned on its side. Its size on screen is chosen against the host's frame rather than against the widget's own width, so the napkin fills the card without asking for more height than the card has.

How much height there is cannot simply be read: Codex answers that question by measuring the container the widget is already in, which is the size the widget last asked for, and a napkin that believes it shrinks itself away. So the ceiling is discovered instead — ask for the size the paper wants, and let the host refuse by clamping, which shows up here as a document taller than its own viewport. See DECISIONS 003.

The pixel buffer is fixed per orientation while CSS decides the rendered size, so a layout change never wipes a drawing and every napkin submits at the same resolution. The nib is deliberately bolder than a real pen at that size, because models downscale images before reading them and a hairline does not survive it. The paper is painted rather than left transparent — a transparent PNG composited onto a dark background hides the ink completely.

Verify

Requires Node 22.12+ (Vite 8).

pnpm install
pnpm lint
pnpm build                    # widget, typecheck, then Next.js
pnpm dev:server               # widget watcher + Next.js on port 3000
pnpm test:client              # verifies the protocol against localhost:3000

pnpm test:client connects as a UI-capable client and asserts the parts that are easy to get subtly wrong: the extension capability, the ui:// metadata on the tool, the resource MIME type, that the resource is real HTML, that the bundle is self-contained, and that it still carries a canvas and a PNG export.

For site changes, open http://localhost:3000 in both colour schemes and draw on the napkin in the chat mock: pressing Send should post the sketch into the mock's thread, the same way a host posts it into yours.

Bump UI_VERSION in app/mcp/route.ts whenever you ship a widget change, or hosts will serve a cached copy. AGENTS.md lists every version that moves with a release.

Develop through Codex

pnpm dev

pnpm dev:codex is the same command, and it requires macOS with the Codex desktop app. It builds the widget, starts a Vite watcher and the Next.js dev server on port 3100, installs a copy of plugins/napkin pointed at http://127.0.0.1:3100/mcp as the only local plugin in a throwaway Codex instance, and launches a separate Codex desktop window. Closing the window removes the isolated state. Your everyday Codex install is untouched.

No tunnel is required, because the widget requests no subresources. Because each run gets a fresh Codex instance, its widget cache starts empty, so you see the current bundle without bumping UI_VERSION.

A fresh instance also starts with an empty thread list. pnpm capture-thread captures a real conversation into scripts/fixtures/ so pnpm dev replays it into the isolated instance, ready to continue — useful here, since testing Napkin means first getting to a point where a sketch would help:

pnpm capture-thread --last                    # or: pnpm capture-thread <thread-id>
pnpm capture-thread <thread-id> --name logo-brief --description "what it sets up"

Capturing rewrites your home directory, Codex home, and working directory into placeholders that the harness substitutes at seed time. It does not rewrite the conversation, so read a fixture before committing it.

To update the personal Codex installation explicitly instead, add a napkin entry pointing at ./plugins/napkin to ~/.agents/plugins/marketplace.json once, then:

pnpm install:codex

That mirrors the plugin into ~/plugins/napkin, applies the Codex cachebuster, and reinstalls napkin@personal.

Develop through Cursor

pnpm dev:cursor

The Cursor equivalent: the same watched server on port 3100, plus a separate Cursor desktop instance with a persistent isolated profile under ~/.napkin/cursor-dev. Cursor loads local plugins from ~/.cursor/plugins/local whatever the profile, so the launcher points that Napkin install at localhost for the session and restores the production plugin when you close the isolated window. Skill edits are copied into it as you save; start a new chat to load them.

To update the everyday Cursor installation explicitly instead:

mkdir -p "$HOME/.cursor/plugins/local"
pnpm install:cursor

Do not keep a marketplace-installed Napkin enabled alongside the local copy.

Widget-only work

pnpm dev:preview              # http://localhost:5173/preview.html

The preview puts the widget in a mock transcript and speaks enough of the wire protocol to act as its host: it answers ui/initialize with one of the measured frames, sizes the iframe from the widget's own resize notifications and clamps it the way a real host does, and accepts the finished sketch through ui/message. Switching frames exercises the sizing without a rebuild; a scrollbar appearing inside the card is a bug.

Deploy

Every push to main deploys to production through .github/workflows/deploy.yml. Vercel's own Git integration cannot deploy an organization repository on a Hobby team, so it is disconnected and the workflow builds with the project's settings (vercel pull, vercel build) and uploads the prebuilt output, then runs the protocol smoke test against production. It needs three repository secrets: VERCEL_TOKEN, VERCEL_ORG_ID, and VERCEL_PROJECT_ID. Run it again from the Actions tab with Run workflow.

Pull requests get no preview deployments. To deploy by hand, or to a project of your own:

pnpm build
vercel deploy --prod
pnpm set-endpoint https://your-project.vercel.app    # only for a new domain
pnpm test:client   https://your-project.vercel.app

Scope

Napkin is a free, noncommercial project in pre-release 0.x. Breaking contract changes advance the minor version; 1.0.0 is reserved for the public release.

Stack

  • mcp-handler 2 — framework-agnostic MCP HTTP adapter, serves the 2026-07-28 protocol natively with a fallback for 2025-era Streamable HTTP clients

  • @modelcontextprotocol/server 2 and @modelcontextprotocol/ext-apps 2 — MCP SDK v2 plus the MCP Apps helpers (registerAppTool, registerAppResource)

  • Vite 8 with vite-plugin-singlefile and Tailwind 4 — the widget bundle

  • Next.js 16 on Vercel Fluid Compute — the website and the MCP server

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables creating and refining pencil-style drawings as layered HTML Canvas snippets through natural language descriptions, using classical construction methodology.
    -
  • F
    license
    Not graded
    quality
    B
    maintenance
    Enables Claude Code to request and read hand-drawn sketches or annotations on a persistent canvas displayed on local or networked devices.
    1
    -