enpilink
v1.1.1
Published
enpilink is a framework for building ChatGPT and MCP Apps
Downloads
1,342
Readme
enpilink
The open, account-free full-stack framework for MCP Apps — and the ChatGPT Apps SDK.
Build type-safe MCP servers whose tools render interactive React views inside Claude, ChatGPT, VS Code, Goose, and any other MCP-Apps-compatible host.
a fork of alpic-ai/skybridge · powered by
Account-free by design
Local dev, public tunneling, and deploy all work with no account, no token, and no vendor lock-in:
- Account-free tunneling via srv.us — open and SSH-based, no
signup.
enpilink dev --tunnelgives you a public/mcpURL in seconds and auto-generates an SSH key at~/.enpilink/id_ed25519the first time. - No telemetry — zero analytics, no network calls, no embedded keys.
- Deploy anywhere —
enpilink buildproduces a standard Node server (node dist/__entry.js); self-host on any platform or container. - All five mcp-ui interaction types —
tool,prompt,link,intent, andnotify.
MCP Apps compliance
enpilink is a compliant MCP Apps framework, built on the official
@modelcontextprotocol/ext-apps
extension (stable spec 2026-01-26). It serves view resources for both runtimes
so the same view runs in either host:
ui://views/ext-apps/*— MCP Apps (Claude, Goose, VS Code, …)ui://views/apps-sdk/*— ChatGPT Apps SDK
The mcp-ui interaction types
An interaction type is one of the structured messages a view sends back to
its host. The mcp-ui standard defines five, and enpilink
supports all five (views send them through hooks, never raw postMessage):
| Type | Hook | Behavior |
|---|---|---|
| tool | useCallTool | real on both runtimes |
| prompt | useSendFollowUpMessage | real on both runtimes |
| link | useOpenExternal | real on both runtimes |
| intent | useIntent | no spec equivalent on either runtime; best-effort extension, may no-op on hosts that don't route it |
| notify | useNotify | real MCP notifications/message on MCP Apps; best-effort extension on the ChatGPT Apps SDK |
These five are the standard's view→host actions. They're a subset of the full hook API below (the rest of the hooks read host context or control your own view). New to all this? See
PRESENTATION.mdfor a plain-language, diagram-led tour.
notify and intent are guarded and additive: they never throw and degrade to
a no-op (or a log line) on hosts without support. See
docs/guides/interaction-types.mdx for the
full per-runtime matrix.
All view hooks
Everything a view can do from inside the iframe — import from enpilink/web.
Hooks never touch raw postMessage; the bridge picks the right runtime call.
| Hook | What it does | Runtime |
|---|---|---|
| Interaction | | |
| useCallTool | Call a server tool from the view; returns { callTool, callToolAsync, data, status, error } with pending/success/error state. | both |
| useSendFollowUpMessage | Send a text message to the model as a user follow-up turn (prompt). | both |
| useNotify | Surface a notification/status to the host: notify({ message, level?, title?, data? }). | both — real notifications/message on MCP Apps, best-effort on Apps SDK |
| useIntent | Express a high-level intent for the host to route: sendIntent({ name, params? }). | both — best-effort extension, may no-op |
| Navigation / links | | |
| useOpenExternal | Open a URL outside the iframe via the host (use instead of window.open). | both |
| useSetOpenInAppUrl | Override the URL the host's fullscreen "Open in app" affordance points to. | Apps SDK only (throws on MCP Apps) |
| useRequestClose | Ask the host to dismiss/close the view. | both |
| Layout / display | | |
| useDisplayMode | Read and request the display mode — inline / pip / fullscreen. | both |
| useRequestModal | Open the view in a host modal overlay; returns { isOpen, params, open }. | both |
| useRequestSize | Ask the host to resize the iframe to fit your content. | both |
| useLayout | Read the visual environment — max height, safe-area insets, theme. | both |
| Context / data | | |
| useToolInfo | Read the typed input / output / metadata of the tool call that rendered this view. | both |
| useViewState | [state, setState] persisted on the host across remounts of the view. | both |
| useUser | Session-stable user/environment info (device type, hover/touch capability). | both |
| Files | | |
| useFiles | Host file operations — upload, getDownloadUrl, selectFiles (native picker). | Apps SDK only (throws on MCP Apps) |
| useDownload | Download an MCP EmbeddedResource / ResourceLink's contents via the host. | both |
| Advanced | | |
| useRegisterViewTool | Let the view expose its own tool to the host/model (app-provided tool). | MCP Apps only (no-op on Apps SDK) |
"both" = works on MCP Apps (Claude, Goose, VS Code…) and the ChatGPT Apps SDK. Apps-SDK-only hooks throw on MCP Apps; MCP-Apps-only hooks no-op on the Apps SDK.
Observability & Admin
enpilink ships an opt-in observability + admin layer — off by default, with zero overhead and zero network when disabled (still no telemetry to us).
- Observability — set
ENPILINK_ANALYTICS=1to record per-tool-call volume, latency (p50/p95/p99), and error rate, viewable in the Console Dashboard with a live log stream. Try it with demo data:enpilink dev --mock(deterministic, in-memory, no real traffic). Seedocs/guides/observability.mdx. - Configuration — a typed config layer with env > file > DB precedence, a
Configuration admin tab for runtime settings, env-only masked secrets,
and a change audit log. See
docs/guides/configuration.mdx. - Admin (production) — expose the Dashboard + Configuration in prod with
enpilink start --admin+ENPILINK_ADMIN_TOKEN(refuses to start without a token). Two-tier auth keeps/mcpand the SPA shell public while gating the data APIs behind a static bearer token; for SSO, front it with a reverse proxy (Cloudflare Access / oauth2-proxy / Tailscale) or self-hosted OIDC. Seedocs/guides/admin.mdx. - Storage — pluggable adapters:
memory(dev),sqlite(prod default,ENPILINK_DB_PATH),postgres(ENPILINK_DB_URL/DATABASE_URL/PG*), plusregisterStorageAdapterfor custom backends, and optional OpenTelemetry metrics export (ENPILINK_OTEL=1+OTEL_EXPORTER_OTLP_ENDPOINT). Seedocs/guides/storage.mdx.
Documentation
New here? Read PRESENTATION.md first — a plain-language,
diagram-led tour of the whole platform (what it solves, the standards, the
technology, and how interaction types relate to hooks). No prior knowledge needed.
There's no hosted docs site yet — the full docs run locally with Mintlify:
pnpm install
pnpm docs:dev # serves the docs at http://localhost:3000What's inside docs/:
- API reference (
docs/api-reference/) — a page per hook and core API (useCallTool,useNotify,useViewState,registerTool, …), each with the signature, a runnable example, and runtime support. This is the "what can I actually do with this hook" reference. - Guides (
docs/guides/) — task-oriented: communicating with the model, managing state, host-environment context, fetching data, the interaction types, and the observability / configuration / admin / storage layer. - Concepts & fundamentals — MCP Apps vs the Apps SDK, write-once-run-everywhere, type safety, and data flow.
- Quickstart — create a new app, add to an existing server, deploy, migrate.
Tip: you can also read any page directly as Markdown in
docs/on GitHub — e.g.docs/api-reference/use-notify.mdx.
Quickstart
Prerequisites
- Node.js ≥ 22
ssh(ships with macOS/Linux) — only needed for--tunnel
Run the built-in kitchen-sink demo
The fastest way to see everything is the bundled kitchen-sink showcase (a fictional store, Northwind): 9 tools, 9 views, all 4 interaction types, every host hook, deterministic mock data.
git clone https://github.com/enpitech/enpilink
cd enpilink && pnpm install && pnpm run build
cd examples/kitchen-sink
pnpm dev # Console + HMR at http://localhost:3000/
pnpm dev:tunnel # opens an account-free srv.us tunnel and prints a public /mcp URLThen connect it to Claude:
- Copy the printed
https://<hash>.srv.us/mcpURL. - In Claude → Settings → Connectors → Add custom connector, paste that URL.
- Paste the contents of
examples/kitchen-sink/specs/SYSTEM_PROMPT.mdinto your Claude project instructions (MCP can't set a host system prompt, so this tells the assistant which tools to call).
Scaffold a new app
npm create enpilink@latest my-appPOC distribution caveat.
enpilinkand@enpilink/consoleare not yet published to npm, so a freshly scaffolded app'snpm installwill fail on theworkspace:*ranges in the templates. For real distribution, publish both packages to npm and runnode scripts/bump.js <version>to rewrite the templates'workspace:*ranges to^<version>. Until then, scaffold inside this monorepo (the templates resolve via the workspace) or use localpnpm packtarballs. See Status below.
The account-free tunnel under the hood is just one SSH command:
ssh srv.us -R 1:localhost:<port>enpilink wraps this with auto key-gen (~/.enpilink/id_ed25519), URL parsing,
and auto-reconnect.
CLI
enpilink dev [--tunnel] [-p <port>] [--open] [--mock] # dev server + Console + HMR (alias: enpi)
enpilink build # compile server + views → dist/
enpilink start [--admin] # run the production build (node dist/__entry.js)
enpilink create [dir] # scaffold a new app (passthrough to create-enpilink)Production gotcha
The production entry reads the __PORT environment variable (NOT PORT),
defaulting to 3000:
__PORT=8080 node dist/__entry.jsenpilink start sets __PORT for you. enpilink build also rewrites server
@/… path aliases automatically, so you do not need tsc-alias in your own
build scripts.
Repo layout
enpilink/
├── packages/
│ ├── core/ → npm "enpilink": server framework + React hooks + Vite plugin + CLI (oclif + Ink)
│ ├── console/ → "@enpilink/console": dashboard / config / playground / docs web UI
│ └── create-enpilink/ → "create-enpilink": scaffolder (templates: blank, demo)
├── examples/
│ ├── kitchen-sink/ → the all-features showcase ("Northwind"); basis of the demo template
│ └── manifest-ui/ → minimal single-view smoke-test example
├── docs/ → Mintlify documentation
├── skills/ → agent skill for building enpilink apps
└── scripts/ → version bump / overrides helpersStatus
This is a POC fork. Today:
- ✅ Local dev, Console, HMR, build, and self-host all work account-free.
- ✅ The account-free srv.us tunnel is live-verified end-to-end (the printed
/mcpURL round-trips over a real public tunnel and survives reconnects). - ⏳ Real npm distribution requires publishing
enpilink+@enpilink/consoleto npm, then runningnode scripts/bump.js <version>. Until then,npm create enpilinkis workspace-linked (works inside this repo / via local tarballs, not from a barenpm install).
Attribution & license
enpilink is released under the MIT License. It is forked from
alpic-ai/skybridge (MIT); the original
copyright is retained in LICENSE, and the fork's changes are summarized in
NOTICE.
Built and maintained by the Enpitech team.
