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.

View as Markdown
Terminal
npm install hono @hono/node-server
Terminal
pnpm add hono @hono/node-server
Terminal
yarn add hono @hono/node-server
Terminal
bun add hono @hono/node-server

Hono returns web Response objects, so MorphScreen's stream goes back to the page as it is, with no piping.

server.mjs
// 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 serve and export default app.
  • Cloudflare Workers: export default app, and read the key from the binding, c.env.MORPHSCREEN_KEY, since Workers have no process.env. Put it in with wrangler 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.