On your server
Hono
The page's route in Hono, for Node.js, Bun, Deno or Cloudflare Workers: AG-UI runs go on to MorphScreen with your key and the events stream back.
npm install hono @hono/node-serverpnpm add hono @hono/node-serveryarn add hono @hono/node-serverbun add hono @hono/node-serverHono returns web Response objects, so MorphScreen's stream goes back to the page as it is, with no piping.
// The page's route in Hono: each AG-UI run goes on to MorphScreen with your
// key, and the events stream straight back. Shown on Node.js; on Bun, Deno or
// Cloudflare Workers, export the app instead of calling serve.
import { serve } from "@hono/node-server";
import { Hono } from "hono";
const API = process.env.MORPHSCREEN_API ?? "https://api.morphscreen.com/v1";
const MODEL = process.env.MODEL ?? "deepseek/deepseek-v4.1-flash";
const app = new Hono();
app.post("/api/agent", async (c) => {
const run = await c.req.json();
run.forwardedProps = { ...run.forwardedProps, model: MODEL };
const upstream = await fetch(`${API}/ui`, {
method: "POST",
headers: { "Content-Type": "application/json", "X-MorphScreen-Key": process.env.MORPHSCREEN_KEY },
body: JSON.stringify(run),
// The reader leaving stops the call too.
signal: c.req.raw.signal,
});
return new Response(upstream.body, { status: upstream.status, headers: { "Content-Type": upstream.headers.get("content-type") ?? "text/event-stream" } });
});
serve({ fetch: app.fetch, port: Number(process.env.PORT ?? 3000) });Other runtimes
The route is the same everywhere; only the start and where the key comes from change:
- Bun and Deno: drop
serveandexport default app. - Cloudflare Workers:
export default app, and read the key from the binding,c.env.MORPHSCREEN_KEY, since Workers have noprocess.env. Put it in withwrangler secret put MORPHSCREEN_KEY.
A chat UI of your own
For your own chat UI, add a second route that sends { model, stream: true, messages } to /v1/chat/completions and returns upstream.body the same way. The Express page has both routes side by side.
We run this file in CI on Node.js against a recorded answer: a page with connectAgent draws the answer through it.