Command Palette

Search for a command to run...

UnylyUnyly
Весь каталог

Todo Demo

БесплатноНе проверен

A minimal MCP server that provides an interactive to-do list with checkboxes in chat, demonstrating MCP Apps UI resource integration and tool-based state update

GitHubEmbed

Описание

A minimal MCP server that provides an interactive to-do list with checkboxes in chat, demonstrating MCP Apps UI resource integration and tool-based state updates.

README

A minimal MCP server that shows an interactive to-do list (checkboxes) as in-chat UI, for teaching the basics of mcp-ui / MCP Apps:

  • widget/ - the actual UI: a real standalone React app (a normal Vite + React project), built to a single self-contained widget/dist/index.html.
  • The widget is registered as its own MCP resource (registerAppResource, URI ui://todo-list/view.html) - per the MCP Apps spec (@modelcontextprotocol/ext-apps), the UI is a separate resource that tools link to, not something a tool embeds in its own result. src/server.ts uses createUIResource's adapters.mcpApps option to inject a small runtime script translating the widget's plain postMessage calls into whatever wire format the host's MCP Apps implementation expects.
  • list_todos (registerAppTool) - links to that resource via _meta.ui.resourceUri, so the host renders it as an interactive checkbox UI.
  • toggle_todo - called automatically when the user checks/unchecks an item. The checkbox's onChange does window.parent.postMessage({ type: 'tool', ... }), the host turns that into a real tool call, and the result goes back into the conversation - that's how the LLM "knows" what's done. It's a normal, model-visible tool (not app-only), so its result - an encouraging confirmation, e.g. "Buy milk" is done! 2 to go - next up: "Walk the dog"
    • can surface as a real chat reply on its own. (An earlier version of this demo also sent a {type:'prompt', payload:{prompt}} action to force a chat reply, but that always renders as a fake role: 'user' message in the host - removed once toggle_todo itself could produce a visible reply.)
  • The host reuses the same mounted widget panel rather than recreating it from scratch on every linked tool call - it doesn't re-run our bundle's initial window.__TODOS__ injection a second time. Instead, it pushes each tool's result into the already-open iframe as a ui-lifecycle-iframe-render-data message (payload.renderData.toolOutput). Both list_todos and toggle_todo include the current state as a JSON content block for exactly this reason, and TodoList.tsx listens for that message and updates its own state from it (see extractTodos / the useEffect in widget/src/TodoList.tsx) - without this, the visible checkbox panel would silently go stale even though the server-side state (and the model's text replies) are correct.

Setup

widget/ is a pnpm workspace member (see pnpm-workspace.yaml), so one install at the root covers both packages:

pnpm install            # installs deps for both root and widget/
pnpm run build:widget    # builds widget/dist/index.html
pnpm run dev             # starts the server over stdio

Re-run pnpm run build:widget whenever you edit widget/src/TodoList.tsx. You can also run pnpm --dir widget run dev to iterate on the widget in a normal browser tab before wiring it back into the MCP server.

This repo uses pnpm (there's a pnpm-lock.yaml) - don't mix in npm install, it'll create a second, conflicting lockfile.

Two ways to run it

The tools themselves (src/server.ts) don't know or care which transport serves them - that's a deliberate teaching point. index.ts and http.ts are both thin wrappers around the same createServer().

stdio (what Claude Desktop / Claude Code expect for local servers):

pnpm run dev

HTTP (exposes POST /mcp, for curl, the MCP Inspector, or any host that takes a server URL instead of a local command):

pnpm run dev:http
# MCP server listening at http://localhost:3000/mcp

It runs in stateless mode (no session IDs to manage) - every request gets its own server+transport pair, but the to-do data still persists between requests because it lives in store.ts at module scope, not on the transport. Point the MCP Inspector at it to poke around without a real host:

npx @modelcontextprotocol/inspector
# then connect to http://localhost:3000/mcp (Streamable HTTP)

Wiring it into Claude Desktop

Add to Claude Desktop's MCP config (Settings -> Developer -> Edit Config), using the absolute path to this folder:

{
  "mcpServers": {
    "todo-demo": {
      "command": "npx",
      "args": ["tsx", "/absolute/path/to/mcp-todo-demo/src/index.ts"]
    }
  }
}

Restart Claude Desktop, then in a chat: "show my to-do list". Claude calls list_todos, renders the checkbox UI in-chat, and clicking a checkbox round-trips through toggle_todo.

Wiring it into claude.ai (remote connector)

claude.ai's web UI connects to remote MCP servers over HTTP, not stdio, so this needs pnpm run dev:http plus a public URL pointing at your localhost (e.g. a pinggy/ngrok tunnel to http://localhost:3000/mcp). Add it in claude.ai under Settings -> Connectors -> Add custom connector, using the tunnel's /mcp URL.

Why the registerAppTool/registerAppResource rewrite was necessary: the older mcp-ui convention (a tool embedding content: [uiResource] directly in its own result) is not what Claude's MCP Apps support expects - it only renders UI for tools whose _meta.ui.resourceUri points at a separately-registered resource, which is what src/server.ts does now. If you still see text but no checkbox UI after this change, double check pnpm install actually pulled in @modelcontextprotocol/ext-apps and that you restarted the server/tunnel after the change.

Suggested live-coding order

  1. store.ts - just the in-memory array, explain it's a stand-in for a DB.
  2. index.ts skeleton - McpServer + StdioServerTransport, no tools yet.
  3. Add list_todos as a plain server.registerTool returning only a text block - show it works as a normal MCP tool first, no UI involved.
  4. Open widget/, show it's a normal Vite + React app (pnpm --dir widget run dev renders TodoList.tsx in a plain browser tab, no MCP involved yet).
  5. pnpm run build:widget. Introduce registerAppResource (from @modelcontextprotocol/ext-apps/server) to register the built widget/dist/index.html (via src/ui-render.ts + createUIResource) as its own resource at ui://todo-list/view.html - this is the MCP Apps tool/UI split: the UI is a resource, not something a tool returns inline.
  6. Switch list_todos to registerAppTool with _meta: { ui: { resourceUri: 'ui://todo-list/view.html' } } - now the checkbox list renders in chat, but clicking does nothing yet.
  7. Add the postMessage({ type: 'tool', payload: { toolName, params } }) call inside TodoList.tsx's checkbox onChange - this is the "UI -> LLM" half.
  8. Add toggle_todo as a second registerAppTool - this is the "LLM -> UI" half: a normal tool the host calls on your behalf, whose result lands back in the conversation and the linked resource re-renders. Make its return text an encouraging confirmation (encouragement() in src/server.ts) rather than a flat "done" message, since this is what actually shows up as the chat reply.
  9. Point out that the widget panel doesn't refresh on its own after a linked tool call - the host pushes the result in as ui-lifecycle-iframe-render-data instead of remounting the iframe. Add the useEffect/extractTodos listener in TodoList.tsx so the already-open panel picks up the change.
  10. Bonus: src/http.ts - same createServer(), different transport (StreamableHTTPServerTransport behind an Express /mcp route instead of stdio) - shows the tool/UI code is transport-agnostic.

from github.com/vishaldodiya/mcp-ui

Установка Todo Demo

У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.

▸ github.com/vishaldodiya/mcp-ui

FAQ

Todo Demo MCP бесплатный?

Да, Todo Demo MCP бесплатный — установка в пару кликов через Unyly без оплаты.

Нужен ли API-ключ для Todo Demo?

Нет, Todo Demo работает без API-ключей и переменных окружения.

Todo Demo — hosted или self-hosted?

Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.

Как установить Todo Demo в Claude Desktop, Claude Code или Cursor?

Открой Todo Demo на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.

Похожие MCP

Compare Todo Demo with

Не уверен что выбрать?

Найди свой стек за 60 секунд

Автор?

Embed-бейдж для README

Похожее

Все в категории communication