# enpilink — How it works, end to end A plain-language tour of the platform: what it is, the problem it solves, the standards it builds on, and the technology underneath. No prior knowledge assumed — every term is explained the first time it appears. > **One-line version:** enpilink lets you build small interactive apps — cards, > forms, buttons, dashboards — that appear **inside an AI chat** (Claude, > ChatGPT, …) instead of the AI replying with only text. You write them **once**, > and they run in every supported AI host. **No account, no vendor lock-in.** --- ## 1. The problem Today, when you ask an AI assistant something, it answers with a **wall of text**. You can't click a product, pick a date on a calendar, swipe through flights, or press "Confirm." Everything is words. **"Apps" fix this:** the assistant can show a *real little user interface* right in the conversation — a product card with an "Add to cart" button, a seat map, a form. The AI stays in charge, but now it can *show* instead of only *tell*. Three things make this hard, and enpilink removes all three: | The hard part | What enpilink does | |---|---| | **The text-wall problem** — chat is text-only by default. | Lets your tools return interactive **React views**. | | **The two-runtimes problem** — ChatGPT and Claude each invented their *own* way to embed UI, so you'd build the same app twice. | **Write once, run in both** — one codebase, two runtimes. | | **The account problem** — the popular toolchain needs a paid account + hosted cloud just to build and share. | **Account-free** — local dev, a free public tunnel, and self-host, with no signup anywhere. | Think of enpilink as a way to build *a tiny website the AI can drop into the chat when it's useful* — and that can **talk back** to the AI. --- ## 2. The big picture Here's the whole loop, start to finish. (A **"view"** = your mini web page. A **"tool"** = a function on your server the AI can call. An **"MCP server"** = the small backend you write.) ```mermaid sequenceDiagram actor User participant Host as AI Host (Claude / ChatGPT) participant Server as Your MCP Server (enpilink) participant View as Your React View (in an iframe) User->>Host: "Show me flights to Paris" Host->>Server: calls your tool search_flights() Server-->>Host: data + a link to a UI resource (ui://…) Host->>View: renders your React view in a sandboxed iframe Note over View: hydrated with the tool's data User->>View: clicks "Book this flight" View->>Host: sends an interaction (e.g. call another tool) Host->>Server: book_flight() Server-->>Host: result Host-->>User: continues the conversation ``` The view runs in a **sandboxed iframe** (a safe, isolated frame) and communicates with the host using a small, standard message protocol — never by reaching directly into the page. enpilink hides all of that behind simple React functions called **hooks** (more on those in §5–6). --- ## 3. The standards (and how they stack) This is the part that trips people up, because **four related names** show up. Here's how they fit together, from the bottom up: ```mermaid flowchart TB MCP["MCP — Model Context Protocol
the base: how an AI talks to your tools and data"] MCPUI["mcp-ui — community standard
added interactive UI over MCP
defined the 5 interaction types"] APPS["MCP Apps (ext-apps) — official MCP extension
standardized mcp-ui's ideas (spec 2026-01-26)
hosts: Claude, Goose, VS Code"] SDK["Apps SDK — OpenAI's ChatGPT-specific runtime
same idea, different API (window.openai)"] ENPI["enpilink — your framework
write once, targets BOTH runtimes, account-free"] MCP --> MCPUI --> APPS MCP --> SDK APPS --> ENPI SDK --> ENPI ``` In words: 1. **MCP (Model Context Protocol)** — the foundation. A standard way for an AI assistant to discover and call your **tools** (functions) and read your data. It's the "USB-C for AI tools." No UI on its own. 2. **mcp-ui** — a community standard that pioneered putting *interactive UI* on top of MCP. **This is the one from the lecture you saw.** Its big idea: a tool can return a UI resource, and that UI can send a small set of structured **interaction types** back to the host (see §4). It defined **five** of them. 3. **MCP Apps** (the `ext-apps` extension) — the **official** MCP standard that took mcp-ui's ideas and made them part of MCP proper (stable spec `2026-01-26`). This is what Claude, Goose, VS Code and other MCP hosts speak. 4. **Apps SDK** — OpenAI's **ChatGPT-specific** version of the same concept. Same goal (UI in the chat), but a different, proprietary API (`window.openai.*`) that only ChatGPT understands. **enpilink sits on top of the last two.** You write one app; enpilink speaks *both* MCP Apps and the Apps SDK, so the same view runs in Claude **and** ChatGPT. It does this with two internal "**bridges**" (`mcp-app` and `apps-sdk`) that translate one set of developer-facing hooks into whichever protocol the current host uses. --- ## 4. The five interaction types (the mcp-ui standard) An **interaction type** is one of the *structured messages a view can send back to the host*. The view doesn't change the conversation directly — it **emits an intent**, and the host (the AI) decides what to do. The mcp-ui standard defines **five**: ```mermaid flowchart LR V["Your View
(iframe)"] H["AI Host"] V -->|tool — run one of my tools| H V -->|prompt — say something to the model| H V -->|link — open a URL| H V -->|intent — a high-level intent| H V -->|notify — show a status| H ``` | Type | Plain meaning | Example | |---|---|---| | `tool` | "Run one of my server's tools and show the result." | Click **Details** → calls `product_details` and renders the product card. | | `prompt` | "Send this text to the model as if the user typed it." | Click **Suggest pairings** → the model replies with wine suggestions. | | `link` | "Open this URL (outside the iframe)." | Click **View docs** → opens the docs in a new tab. | | `intent` | "Here's a high-level thing I want to happen — you route it." | Click **Add to cart** → the host decides how to handle "add_to_cart". | | `notify` | "Surface a status/notification to the host." | After checkout → a "Saved!" toast/log. | > **enpilink supports all five.** `tool`, `prompt` and `link` are real, > first-class on both runtimes. `notify` is the *real* MCP notification on MCP > Apps. `intent` is a best-effort extension (it never breaks anything — if a > host doesn't route it, it simply does nothing). This diagram — a view posting a structured `{ type, payload }` message up to its host — *is* the mental model from the mcp-ui lecture. --- ## 5. "Interaction types" vs "hooks" — clearing up the two tables This is the question the README's two tables raise. They are **related but not the same thing**, and once you see the relationship it's simple: - **Interaction types** = the **5 standard *actions*** a view can send *up* to the host (§4). This is a concept from the standard. - **Hooks** = enpilink's **React functions** you call inside a view. *Some* hooks send those 5 interactions. *Other* hooks do different jobs entirely — reading the data that opened the view, remembering UI state, asking to go fullscreen, uploading a file, etc. > **Analogy:** interaction types are the **verbs your view can say to the host**. > Hooks are **all the tools in your view's toolbox** — and only *some* of those > tools are those verbs. The rest are for *listening* to the host and > *controlling* your own view. So the relationship is: **the 5 interaction types are a subset of the hooks.** ```mermaid flowchart TB subgraph HOOKS["enpilink hooks — your view's full toolbox"] direction TB subgraph INT["The 5 interaction types (talk TO the host)"] T["tool → useCallTool"] P["prompt → useSendFollowUpMessage"] L["link → useOpenExternal"] I["intent → useIntent"] N["notify → useNotify"] end subgraph OTHER["Everything else (listen / control your view)"] TI["useToolInfo — read the data that opened the view"] VS["useViewState — remember UI state"] DM["useDisplayMode / useRequestModal / useRequestSize — layout"] LY["useLayout / useUser — host & device context"] FD["useFiles / useDownload — files"] RV["useRegisterViewTool — advanced"] end end ``` So when you read the README: - The **"interaction types" table** = the 5 standard ways your view talks back (the lecture's concept). *Note: the README currently highlights four (`tool`, `prompt`, `notify`, `intent`); the fifth, `link`, is `useOpenExternal`, listed under "Navigation/links" in the hooks table.* - The **"all view hooks" table** = enpilink's **complete** React API — those 5 interaction hooks **plus** ~12 more for context, state, layout and files. Same world, two zoom levels: one shows *the standard's verbs*, the other shows *the whole toolbox*. --- ## 6. The technology underneath enpilink is a **TypeScript** framework (a pnpm monorepo). You build two things, and enpilink wires them together with full type-safety: ```mermaid flowchart LR subgraph YOURS["What you write"] SRV["server.ts — your MCP server
tools + which view each renders"] VIEW["views/*.tsx — your React views"] end subgraph ENPI["What enpilink does"] BUILD["compiles views → ui:// resources
(Vite); infers types server → view"] BRIDGE["2 bridges: MCP Apps + Apps SDK
same hooks, right protocol"] end HOST["Any host: Claude, ChatGPT, Goose, VS Code"] SRV --> BUILD VIEW --> BUILD BUILD --> BRIDGE --> HOST ``` - **Server** (`server.ts`): you declare **tools** with `registerTool(...)`, and for each tool you point at a **view** to render. enpilink builds on the official `@modelcontextprotocol/sdk` and `@modelcontextprotocol/ext-apps`. - **Views** (`views/*.tsx`): ordinary **React 19** components. They read data and talk to the host only through **hooks** — never raw browser messaging. - **Type-safety:** the data a tool returns is **typed all the way into the view**, so if the shapes don't match, you get a compile error, not a runtime surprise. - **Two bridges:** the `mcp-app` bridge speaks the official MCP Apps protocol; the `apps-sdk` bridge speaks ChatGPT's `window.openai`. You never touch them — you just call hooks, and the right bridge does the right thing per host. --- ## 7. Account-free, and live in Claude in seconds To test your app in a *real* AI, the host needs a public URL for your local server. enpilink does this with **srv.us** — an open, SSH-based tunnel that needs **no account, no signup**: ```mermaid flowchart LR DEV["your laptop
enpilink dev --tunnel"] SRV["srv.us
(open SSH tunnel, no account)"] CLAUDE["Claude / ChatGPT
(custom connector)"] DEV <-->|ssh -R| SRV SRV <-->|https://…srv.us/mcp| CLAUDE ``` 1. `enpilink dev --tunnel` starts your app and opens the tunnel (it auto-creates an SSH key the first time — nothing to configure). 2. It prints a public URL like `https://.srv.us/mcp`. 3. Paste that into **Claude → Settings → Connectors → Custom connector**, and your app is live in the chat. No PostHog, no hosted cloud, no API keys — the framework makes **zero** calls to any vendor service. --- ## 8. The developer experience - **Local playground (Console):** a browser emulator that lists your tools, lets you run them, and renders your views instantly — with hot reload as you edit. (It emulates the ChatGPT Apps-SDK runtime, so `notify`/`intent` show up as log entries there.) - **CLI:** `enpilink dev` (develop), `enpilink build` (compile), `enpilink start` (run in production), `enpilink create` (scaffold a new app). - **Scaffolder:** `npm create enpilink my-app` gives you a working starter (once the packages are published to npm). - **Built-in demo:** the **kitchen-sink** example app exercises every hook and all the interaction types, and ships with a `SYSTEM_PROMPT.md` you paste into Claude to drive it. --- ## 9. Recap in one picture ```mermaid flowchart TB U["1 · User asks the AI something"] TOOL["2 · AI calls your tool (MCP)"] DATA["3 · Tool returns data + a ui:// view"] RENDER["4 · Host renders your React view in an iframe"] INT["5 · User clicks → view sends an interaction
(tool / prompt / link / intent / notify)"] LOOP["6 · AI acts on it → conversation continues"] U --> TOOL --> DATA --> RENDER --> INT --> LOOP --> U ``` --- ## Glossary (one line each) - **MCP (Model Context Protocol):** the standard for how an AI talks to your tools and data. - **mcp-ui:** the community standard that added interactive UI over MCP and defined the 5 interaction types. - **MCP Apps / ext-apps:** the official MCP extension that standardized interactive UI (spec `2026-01-26`); spoken by Claude, Goose, VS Code… - **Apps SDK:** OpenAI's ChatGPT-specific version of the same idea. - **Host (runtime):** the AI app your view runs inside (Claude, ChatGPT, …). - **MCP server:** the small backend you write — it exposes tools and views. - **Tool:** a function on your server the AI can call. - **View:** your React mini-app, rendered in a sandboxed iframe inside the chat. - **Interaction type:** one of the 5 structured messages a view sends to the host — `tool`, `prompt`, `link`, `intent`, `notify`. - **Hook:** an enpilink React function a view calls; some send interactions, others read context / manage state / control layout / handle files. - **Bridge:** enpilink's per-runtime translator (one for MCP Apps, one for the Apps SDK) so one codebase runs in both. - **Tunnel (srv.us):** an open, account-free way to expose your local app to a real AI host over a public HTTPS URL. --- *enpilink is account-free and open. It is a fork of [`alpic-ai/skybridge`](https://github.com/alpic-ai/skybridge), maintained by [Enpitech](https://enpitech.dev).*