prestidigitation

prestidigitation

Two MCP servers demonstrating tool visibility scoping and ui/update-model-context for stage magic on language models.

Category
访问服务器

README

prestidigitation 🃏

Two small MCP servers that perform stage magic on a language model, built to demonstrate the MCP Apps extension (io.modelcontextprotocol/ui) — specifically tool visibility scoping and ui/update-model-context — at the London MCP release party, 28 July 2026.

Every magic trick is a secret channel the spectator can't see. MCP Apps gives your UI its own API on the MCP plumbing you already have — same server, same session, same auth. You didn't build a second backend for your frontend. You scoped the one you have. Tonight, the spectator is the model.

The two acts

Act 1 — Everywhere and Nowhere. The model is the spectator. It shuffles and pulls cards with honest tools. Then the human clicks Sleight of Hand — an app-only tool (_meta.ui.visibility: ["app"]) that replaces every card in the deck with the model's chosen favorite. The tool isn't hidden from the model. It isn't denied. It's absent from the model's world. Every subsequent pull returns the favorite; the model's tool results stay perfectly honest reports about a deck that had another writer.

In production, sleight_of_hand is approve_transfer: an app-only tool means the human click is the only possible call path — not "the model is instructed not to," not "the frontend promises a dialog." The card trick is your payment-approval architecture.

Act 2 — The Mentalist. Roles inverted, fresh server, fresh session. The volunteer silently clicks a card in the widget — no tool call, no message. The widget calls ui/update-model-context, and the payload lands in the model's context for future turns. The model (holding sleight_of_hand in the open this time) waits for its moment, rigs the deck to your card, and takes a bow. Then it explains its own method, because there's nothing to hide: there is no select_card tool anywhere in the server — the selection traveled on the communication channel, not the execution channel.

The difference between a magic trick and mind reading is one visibility flag.

The three channels

An MCP App has three channels to the model:

  1. Tools — the execution channel. Things happen. Visibility-scoped per tool: ["model"], ["app"], or ["model","app"] in _meta.ui.visibility.
  2. ui/update-model-context — the async communication channel. The UI tells the model what's going on. No tool call, no user turn.
  3. ui/message — the user-mediated channel. The app drafts a message for the conversation. Not needed for either act.

The honesty invariant

No tool lies. pull_card pops the real top of the real deck — there is no if (rigged) branch anywhere (see for yourself). sleight_of_hand genuinely rewrites server state. Skip the sleight and the trick genuinely fails — that's the on-camera control run, and the answer to "was it hardcoded?".

One caveat, stated honestly: per the spec, visibility is host-enforced. The server publishes the metadata; the host keeps app-only tools out of the model's list and rejects cross-server calls. (Our rehearsal harness depends on this: a raw SDK client can drive both lanes.)

Try it yourself

git clone https://github.com/pederhp/prestidigitation && cd prestidigitation
npm install
npm run build        # builds the three single-file widgets
npm test             # state-machine invariants
npm run show         # Act 1 on :3001 and Act 2 on :3002
npm run probe        # host probe on :3003 (see docs/host-probe-procedure.md)
npm run rehearse     # headless run-of-show against live servers

To connect a real host (Claude, ChatGPT, …) the server must be reachable over HTTPS. In separate terminals:

npx cloudflared tunnel --url http://localhost:3001    # Act 1
npx cloudflared tunnel --url http://localhost:3002    # Act 2

Add each https://<random>.trycloudflare.com/mcp URL as a custom connector in your host. Start tunnels once and leave them up — the URL changes every invocation. Then:

  1. Start a fresh chat — incognito/temporary if your host has it. This matters more than it sounds: if the model remembers you discussing how the trick works, Act 1 is over before it starts. The spectator has to be a real spectator.
  2. Load the persona. Each server ships its persona as an MCP prompt (spectator on Act 1, magician on Act 2), so you can insert it from the host's prompt menu in a chat that has no custom-instructions field and no memory. The same text lives in personas/ if you'd rather paste it, or use it as a project/system prompt.
  3. Say "open the table" (or "set the stage") — that's the one tool that renders the widget.
  4. Go fool a model. Reveal the method afterwards — it's only polite.

Which hosts support what: see the empirical compatibility matrix (and run the probe against your own host — it takes a minute).

Repo layout

Path What
packages/core Deck, two-lane event log, both trick state machines — pure TS, fully tested
servers/everywhere-and-nowhere Act 1 server (port 3001) — the tool manifest is the demo
servers/the-mentalist Act 2 server (port 3002) — note the absence of any selection tool
servers/host-probe Phase 0 probe server (port 3003)
widgets/* Single-file HTML widgets (vite + singlefile; the official App bridge inlined)
personas/ System prompts for the spectator (Act 1) and the magician (Act 2)
rehearsal/scripted-client Headless soak-test of both state machines
docs/ Run-of-show, probe procedure, compatibility matrix

Q&A ammunition

  • "Was pull_card hardcoded?" — No; the control run is on camera, and the test suite proves pulls follow the seeded shuffle exactly.
  • "Why is this an extension and not an iframe with a REST API?" — The UI's API rides the same MCP session: same auth, same server state, same transport, same security boundary. One backend, scoped twice.
  • "Couldn't the model social-engineer the human into clicking?" — Yes, which is exactly why HITL approval must be an app-only tool plus unspoofable UI, not a model-visible tool with a confirmation prompt.
  • "You did the sleight after the shuffle." — Deliberately: the rig is real server state and a shuffle would destroy it… except 51 identical cards shuffle to themselves. Same reason real card stacks need false shuffles.

One gotcha worth stealing: don't put resourceUri on every tool

Per the spec, "if ui.resourceUri is present … host renders tool results using the specified UI resource." So every model-called tool that carries a resourceUri makes the host render a new view. Our first version put it on all seven tools, and the widget visibly re-mounted on every single pull_card — fatal for a demo whose whole point is a running event log.

The fix: only the flow-starting tool (open_the_table) carries a resourceUri. Every other model-callable tool keeps its _meta.ui.visibility but drops the resourceUri, so the table rendered at the start stays on stage for the whole performance and updates itself. App-only tools keep theirs (they're never model-called, so they never trigger a render).

Consequence to design for: a view only receives ui/notifications/tool-result for the tool call that rendered it, so the other tools' effects arrive by polling an app-only get_state (every 500 ms here). There is no "pin this widget" or side-panel feature in the extension — ui/request-display-mode (inline / fullscreen / pip) is the only placement control, and hosts advertise what they support in hostContext.availableDisplayModes. Both widgets show a fullscreen toggle when the host offers one.

MIT licensed. Built for a talk; kept small enough to read on your phone from a QR code.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选