On your server

Python

Call MorphScreen from Python with only the standard library: carry a page's AG-UI runs, stream an answer's text, or render a finished answer as HTML.

View as Markdown

Nothing to install: these use only Python's standard library. Keep MORPHSCREEN_KEY in the server's environment; it never goes to the page.

A route for the page

The page's connectAgent sends its runs to your route, which adds the key and the model and streams the events back:

server.py
# Your server's one route for the page, with nothing but the standard
# library: each AG-UI run goes on to MorphScreen with your key, and the
# events stream straight back. The key never reaches the browser, and the
# server, not the page, picks the model.
import json
import os
import urllib.error
import urllib.request
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer

API = os.environ.get("MORPHSCREEN_API", "https://api.morphscreen.com/v1")
MODEL = os.environ.get("MODEL", "deepseek/deepseek-v4.1-flash")


class Agent(BaseHTTPRequestHandler):
    def do_POST(self):
        if self.path != "/api/agent":
            self.send_error(404)
            return
        run = json.loads(self.rfile.read(int(self.headers["Content-Length"])))
        run["forwardedProps"] = {**run.get("forwardedProps", {}), "model": MODEL}
        req = urllib.request.Request(
            f"{API}/ui",
            data=json.dumps(run).encode(),
            headers={"Content-Type": "application/json", "X-MorphScreen-Key": os.environ.get("MORPHSCREEN_KEY", "")},
            method="POST",
        )
        try:
            upstream = urllib.request.urlopen(req)
        except urllib.error.HTTPError as e:
            upstream = e
        with upstream:
            self.send_response(upstream.status)
            self.send_header("Content-Type", upstream.headers.get("Content-Type", "text/event-stream"))
            self.end_headers()
            while chunk := upstream.read1(4096):
                self.wfile.write(chunk)
                self.wfile.flush()


ThreadingHTTPServer(("127.0.0.1", int(os.environ.get("PORT", "3000"))), Agent).serve_forever()

In FastAPI, Flask or Django the route does the same: read the run, set forwardedProps["model"], POST it to MorphScreen's /v1/ui, and stream the response body back with its status and content type, without buffering it.

The answer's text, or HTML

morphscreen.py
# An answer's text as it streams, and a finished answer as HTML, with nothing
# but the standard library. Run it on your server: the key stays there.
import json
import os
import urllib.request

API = os.environ.get("MORPHSCREEN_API", "https://api.morphscreen.com/v1")
HEADERS = {"Content-Type": "application/json", "X-MorphScreen-Key": os.environ.get("MORPHSCREEN_KEY", "")}


def stream_answer(messages, model="deepseek/deepseek-v4.1-flash"):
    """Yields the answer's text, piece by piece, as the model writes it."""
    body = json.dumps({"model": model, "stream": True, "messages": messages}).encode()
    req = urllib.request.Request(f"{API}/chat/completions", data=body, headers=HEADERS, method="POST")
    with urllib.request.urlopen(req) as res:
        for line in res:
            line = line.decode().strip()
            if not line.startswith("data: ") or line == "data: [DONE]":
                continue
            for choice in json.loads(line[6:]).get("choices", []):
                text = (choice.get("delta") or {}).get("content")
                if text:
                    yield text


def render_html(text):
    """A finished answer drawn by the core as HTML, with its verdict."""
    req = urllib.request.Request(f"{API}/render", data=json.dumps({"text": text}).encode(), headers=HEADERS, method="POST")
    with urllib.request.urlopen(req) as res:
        out = json.load(res)
    return out["html"], out["morphscreen"]
Using it
from morphscreen import render_html, stream_answer

text = "".join(stream_answer([{"role": "user", "content": "Make me a sales dashboard"}]))
html, verdict = render_html(text)

Both are what our tests run against a recorded answer.

With OpenAI's SDK

On your own OpenRouter key, OpenAI's Python SDK works with a new base_url and the X-MorphScreen-Key header (OpenAI-compatible clients). For the model in your plan, use the functions above: any Authorization header is taken as an OpenRouter key.