Specs, the API Route & the Dashboard Builder

Create an MCP App with json-render

Chapter 3 ยท Specs, the API Route & the Dashboard Builder

๐Ÿ“บ Based on the LinkedIn Learning course "Create an MCP App with json-render" by instructor Eve Porcello, published 17 April 2026. View the original course โ†’

Chapter 2 built the catalog (what's allowed) and the registry (how each thing renders). This chapter connects them into a real, working feature โ€” a spec, an API route that generates one from a prompt, and a component that streams the result to the screen.

The Dashboard Spec

specs/dashboard.ts is a real, concrete example of the JSON structure this whole pipeline produces โ€” a plain object describing which components render and with what props, built entirely from Chapter 2's own catalog entries.

export const dashboardSpec = { type: "card", children: { type: "stack", children: [ { type: "heading", props: { text: "Server Status" } }, { type: "badge", props: { text: "Online", variant: "success" } }, { type: "text", props: { text: "Last checked 2 minutes ago" } }, ], }, };

The root is a card; its children are a stack layout, itself containing a heading, a badge, and a text element โ€” every one of those type names has to exist in the catalog and have a real registry entry, or Chapter 2's own rendering chain breaks at that node.

The API Route: Generating a Spec From a Prompt

app/api/generate/route.ts is the real endpoint that takes a user's prompt and streams an AI-generated spec back to the UI, using streamText from the Vercel AI SDK with Claude Haiku 4.5 as the model.

import { streamText } from "ai"; export async function POST(req: Request) { const { prompt } = await req.json(); const result = await streamText({ model: "claude-haiku-4.5", system: "Use 'card' as the root. Use 'badge' for status values.", prompt, }); return result.toDataStreamResponse(); }
Why Custom Rules Matter
The system rules passed alongside the prompt ("use card as root", "badge for status") aren't decoration โ€” they're what steers the model toward specs that actually match the catalog's own real constraints and the app's own visual conventions, directly reducing how often a generated spec references something the registry can't render.

The Dashboard Builder Component

components/generated/dashboard-builder.tsx is the real, user-facing piece: a form collects the user's prompt, and the response streams back via a useUIStream hook.

1

Form

User types a prompt

2

useUIStream

Streams the API route's response

3

Renderer

Draws the streamed spec, via Chapter 2's registry

4

Fallback

Shows the static dashboardSpec while loading

Why the Static Fallback Matters
While the real, streamed spec is still arriving, the renderer displays the static dashboardSpec from earlier in this chapter instead of a blank screen โ€” a real, deliberate UX choice: showing something immediately, then replacing it once the genuinely AI-generated version has finished streaming in.

Hands-On Exercises

Exercise 1

The API route's system rules are removed entirely, leaving only the raw user prompt. Explain, in your own words, the most likely real consequence, connecting your answer back to Chapter 2's own catalog/registry material.

๐Ÿ“„ View solution
Exercise 2

Explain, in your own words, what a user would actually see, moment by moment, from the instant they submit a prompt in the Dashboard Builder to the instant the AI-generated spec finishes streaming in.

๐Ÿ“„ View solution
Exercise 3

A teammate suggests removing the static dashboardSpec fallback entirely, showing a blank screen while the real spec streams in, "to simplify the component." Explain, in your own words, the real UX tradeoff this would introduce.

๐Ÿ“„ View solution

Chapter 3 Quick Reference

  • Dashboard spec: card root โ†’ stack layout โ†’ heading, badge, text โ€” every type must exist in the catalog and registry
  • API route (app/api/generate/route.ts): streamText + Claude Haiku 4.5, with real system rules steering the model toward the catalog's own constraints
  • Dashboard Builder: form โ†’ useUIStream โ†’ renderer, with a static fallback shown while the real spec streams in