@allternit/apps-server is a thin layer over the official MCP SDK v2 (@modelcontextprotocol/server 2.3.1) and @modelcontextprotocol/ext-apps 2.0.3. It does not hide them: app.createServer() returns a normal McpServer.
Every app it builds is dual-era by default: it speaks MCP 2026-07-28 (stateless, server/discover, a _meta envelope on each request) and still answers clients that open with the older initialize handshake. Tools are listed in name order and Views in URI order, so tools/list and resources/list are the same on every request. Tool input shapes are Zod 4 (the z re-exported by the package).
defineApp
defineApp throws on mistakes you would otherwise find in review: duplicate tool names or View URIs, a tool that points at a View you did not list, a malformed or over-broad CSP domain, an invalid look pack.
It returns an app with four members:
appTool
readOnlyHint and destructiveHint are required and must be booleans. Stating them is the point: the host decides when to ask the user, and directory review checks them against the tool’s name. A tool cannot be both read-only and destructive.
When a tool has a view, the SDK registers it with registerAppTool, which sets _meta.ui.resourceUri and the older flat _meta["ui/resourceUri"] key.
z is re-exported from zod so the input shape and your code use one version.
view
A View is a ui:// resource. Build it from the view kit or write the HTML yourself.
The app’s look pack is written into every View as CSS variables, so a Views’ colors come from the pack and change when it does. A kit View also runs a small script that speaks the standard bridge:
ui/initialize, host theme variables, ui/notifications/tool-result and size changes.
Serve it
listen runs streamable HTTP in stateless mode, a fresh server per request, for both protocol eras. It also answers GET /healthz and GET /allternit.app.json. allternit dev calls it for you. Pass onerror to see tool errors (the default logs them to stderr).
To deploy somewhere that takes a web-standard fetch handler (Cloudflare Workers, Bun, Deno), use the handler directly:
Events (optional)
An app can offer MCP Events so a host subscribes once and your server calls it back when something happens. Addevents to defineApp; see the Events page for the full example.
Validating a manifest
validateManifest applies the plugin package rules (name length, required annotations, Views that exist) plus the look pack validator. The scan rules are the same code directory review uses: scanInput, lintTools, lintResources.