Feedback Form
read as.md The smallest useful ggui interaction: an agent renders a form, the user submits, the agent reads the typed data. Following the Zero Agent Code principle, you don’t hand-call handshake / render / consume — you configure the agent host with ggui’s MCP server and the LLM autonomously drives the loop from a user prompt.
Two flavors below — the canonical LLM-driven shape via the Claude Agent SDK, and a manual orchestration variant using @modelcontextprotocol/sdk when you need imperative control.
Canonical: Claude Agent SDK + ggui MCP
Section titled “Canonical: Claude Agent SDK + ggui MCP”The developer’s job: define the typed contract, configure the host, write a user-facing prompt. The LLM autonomously calls ggui_handshake → ggui_render → ggui_consume and surfaces the typed payload.
import { query } from "@anthropic-ai/claude-agent-sdk";import { defineContract } from "@ggui-ai/protocol";
// Typed contract — `actionData` for `submit` narrows to {rating, comments}.// The contract still lives in code: it's how you document the shape// the agent should negotiate during `ggui_handshake`.// NOTE: the intent string is NOT a contract field — it travels separately// as the flat `intent` argument of `ggui_handshake` (see the manual// orchestration variant below).const feedbackContract = defineContract({ propsSpec: { properties: { userName: { schema: { type: "string" } }, product: { schema: { type: "string" } }, }, }, actionSpec: { submit: { label: "Submit feedback", schema: { type: "object", properties: { rating: { type: "number", minimum: 1, maximum: 5 }, comments: { type: "string" }, }, required: ["rating", "comments"], }, }, },} as const);
async function collectFeedback(userName: string, product: string) { const result = query({ prompt: `Collect product feedback from ${userName} about ${product}. Render a feedback form with a 1–5 star rating, a comments text area, and a submit button. Greet the user by name. Wait for their submission, then report the rating and comments back to me as JSON: {"rating": number, "comments": string}.`, options: { mcpServers: { ggui: { type: "http", url: "http://127.0.0.1:6781/mcp", // ggui serve --dev-allow-all headers: { Authorization: "Bearer dev", }, }, }, allowedTools: [ "mcp__ggui__ggui_handshake", "mcp__ggui__ggui_render", "mcp__ggui__ggui_consume", ], }, });
// The LLM drives ggui_handshake → ggui_render → ggui_consume on its own. // The render surfaces as an MCP-Apps resource the host mounts; // the typed payload comes back in the final message. for await (const message of result) { if (message.type === "assistant") { console.log(message.message.content); } }}For a complete runnable example (including streaming partial events, multi-turn refinement, and the host’s MCP wiring), see Examples → Claude Agent.
Manual orchestration: @modelcontextprotocol/sdk
Section titled “Manual orchestration: @modelcontextprotocol/sdk”Use this shape when you need imperative control — e.g. you’re building a non-LLM workflow, testing the protocol directly, or reacting to every action the user fires (not just the terminal submit). This calls ggui’s MCP tools directly via the official MCP SDK; no LLM in the loop.
import { Client } from "@modelcontextprotocol/sdk/client/index.js";import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
const transport = new StreamableHTTPClientTransport(new URL("http://127.0.0.1:6781/mcp"), { requestInit: { headers: { Authorization: "Bearer dev" }, },});
const client = new Client({ name: "feedback-script", version: "1.0.0" });await client.connect(transport);
async function collectFeedbackLive(userName: string, product: string) { // 1. Handshake — negotiate the contract before rendering. // Pre-launch, `ggui_render` is handshake-first only. const hsResp = await client.callTool({ name: "ggui_handshake", arguments: { intent: "Collect product feedback (live)", blueprintDraft: { contract: feedbackContract, variance: { seedPrompt: `Show ${userName} a product feedback form for ${product}.`, }, }, }, }); const { handshakeId } = JSON.parse((hsResp.content[0] as { type: "text"; text: string }).text);
// 2. Render — accept the handshake's suggestion (omit `override`). // `ggui_render` mints the `sessionId`. const renderResp = await client.callTool({ name: "ggui_render", arguments: { handshakeId, props: { userName, product }, }, }); const { sessionId, resourceUri } = JSON.parse( (renderResp.content[0] as { type: "text"; text: string }).text ); console.log(`Render ${sessionId} ready (${resourceUri}) — a host mounts it.`);
// 3. Consume — long-poll until the user submits. while (true) { const consumeResp = await client.callTool({ name: "ggui_consume", arguments: { sessionId, timeout: 25 }, }); const { events, status } = JSON.parse( (consumeResp.content[0] as { type: "text"; text: string }).text );
for (const entry of events) { // Every consume entry has `type: 'action'`; the contract's // `actionSpec` key fires on `entry.intent`. if (entry.intent === "submit") { console.log("Submitted:", entry.actionData); return entry.actionData; } console.log(`Action ${entry.intent}:`, entry.actionData); }
if (status !== "active") break; // 'expired' — render TTL elapsed }}What the user sees
Section titled “What the user sees”The agent renders the form into whatever MCP-Apps host the user is in — inline in claude.ai / Claude Desktop, or in your own app via <AppRenderer>. The user fills it in and submits; the agent reads the typed result off ggui_consume:
Rating: 4/5 — Great product, would love dark-mode support!There is no URL to hand the user — the render is an MCP-Apps resource the host mounts. (For local development without an MCP-Apps host, the operator console bundled with ggui serve can display the render for debugging.)
Related
Section titled “Related”- Examples → Claude Agent — full runnable Claude Agent SDK + ggui MCP example
- MCP protocol reference — wire-level
ggui_handshake/ggui_render/ggui_consumeshapes - Event system —
actionSpec-driven flow and theConsumeEventEntryshape - Multi-step wizard — chain forms across successive renders
- Glossary —
render,contract,envelope,blueprint