Skip to content

Commit 2c7c437

Browse files
committed
feat: port mcp-apps, tool-rendering-reasoning-chain, gen-ui-tool-based, hitl-in-chat-booking, hitl to mastra integration
Picks up the remaining demos from D13's port. Adds: - mcp-apps: dedicated route with mcpApps.servers config (Excalidraw), no-tools Mastra agent - tool-rendering-reasoning-chain: per-tool renderers + custom reasoningMessage slot - gen-ui-tool-based: manifest entry for the existing useFrontendTool haiku page - hitl-in-chat-booking: manifest alias pointing at the existing /demos/hitl-in-chat route - hitl: manifest entry for the existing step-selection page (useLangGraphInterrupt branch is a no-op on Mastra; useHumanInTheLoop branch works) BLOCKED on Mastra adapter capabilities: - gen-ui-interrupt: useInterrupt requires AG-UI INTERRUPT events the Mastra adapter does not emit - interrupt-headless: same root cause
1 parent e96471b commit 2c7c437

14 files changed

Lines changed: 844 additions & 1 deletion

File tree

showcase/integrations/mastra/manifest.yaml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ features:
5858
- open-gen-ui
5959
- open-gen-ui-advanced
6060
- voice
61+
- gen-ui-tool-based
62+
- hitl
63+
- hitl-in-chat-booking
64+
- mcp-apps
65+
- tool-rendering-reasoning-chain
6166
demos:
6267
- id: cli-start
6368
name: CLI Start Command
@@ -437,3 +442,59 @@ demos:
437442
- src/app/demos/voice/page.tsx
438443
- src/app/demos/voice/sample-audio-button.tsx
439444
- src/app/api/copilotkit-voice/[[...slug]]/route.ts
445+
- id: gen-ui-tool-based
446+
name: Tool-Based Generative UI
447+
description: Agent uses a frontend tool to trigger rich generative UI inline in the chat
448+
tags:
449+
- generative-ui
450+
route: /demos/gen-ui-tool-based
451+
animated_preview_url:
452+
highlight:
453+
- src/app/demos/gen-ui-tool-based/page.tsx
454+
- src/app/api/copilotkit/route.ts
455+
- id: hitl
456+
name: Human in the Loop (Step Selection)
457+
description: Step-selection HITL surface rendered inline via `useHumanInTheLoop` (the LangGraph-specific `useLangGraphInterrupt` branch is a no-op on Mastra)
458+
tags:
459+
- interactivity
460+
route: /demos/hitl
461+
animated_preview_url:
462+
highlight:
463+
- src/app/demos/hitl/page.tsx
464+
- src/app/api/copilotkit/route.ts
465+
- id: hitl-in-chat-booking
466+
name: In-Chat HITL (Booking)
467+
description: Time-picker card rendered inline via useHumanInTheLoop for a booking flow (alias of hitl-in-chat)
468+
tags:
469+
- interactivity
470+
route: /demos/hitl-in-chat
471+
animated_preview_url:
472+
highlight:
473+
- src/app/demos/hitl-in-chat/page.tsx
474+
- src/app/demos/hitl-in-chat/time-picker-card.tsx
475+
- src/app/api/copilotkit/route.ts
476+
- id: mcp-apps
477+
name: MCP Apps
478+
description: MCP server-driven UI rendered inline via the runtime's mcpApps config and the built-in activity renderer
479+
tags:
480+
- generative-ui
481+
route: /demos/mcp-apps
482+
animated_preview_url:
483+
highlight:
484+
- src/mastra/agents/index.ts
485+
- src/app/demos/mcp-apps/page.tsx
486+
- src/app/api/copilotkit-mcp-apps/route.ts
487+
- id: tool-rendering-reasoning-chain
488+
name: Tool Rendering + Reasoning Chain
489+
description: Sequential tool calls rendered with per-tool components plus a reasoning chain rendered inline via a custom reasoningMessage slot
490+
tags:
491+
- generative-ui
492+
route: /demos/tool-rendering-reasoning-chain
493+
animated_preview_url:
494+
highlight:
495+
- src/app/demos/tool-rendering-reasoning-chain/page.tsx
496+
- src/app/demos/tool-rendering-reasoning-chain/reasoning-block.tsx
497+
- src/app/demos/tool-rendering-reasoning-chain/weather-card.tsx
498+
- src/app/demos/tool-rendering-reasoning-chain/flight-list-card.tsx
499+
- src/app/demos/tool-rendering-reasoning-chain/custom-catchall-renderer.tsx
500+
- src/app/api/copilotkit/route.ts
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// CopilotKit runtime for the MCP Apps cell.
2+
//
3+
// The runtime's `mcpApps` config auto-applies the MCP Apps middleware to the
4+
// agent: when the agent calls a tool backed by an MCP UI resource, the
5+
// middleware fetches the resource and emits the activity event that the
6+
// built-in `MCPAppsActivityRenderer` (registered by CopilotKit internally)
7+
// renders in the chat as a sandboxed iframe.
8+
9+
import { NextRequest, NextResponse } from "next/server";
10+
import {
11+
CopilotRuntime,
12+
ExperimentalEmptyAdapter,
13+
copilotRuntimeNextJSAppRouterEndpoint,
14+
} from "@copilotkit/runtime";
15+
import { getLocalAgent } from "@ag-ui/mastra";
16+
import { mastra } from "@/mastra";
17+
18+
const mcpAppsAgent = getLocalAgent({
19+
mastra,
20+
agentId: "mcpAppsAgent",
21+
resourceId: "mastra-mcp-apps",
22+
});
23+
if (!mcpAppsAgent) {
24+
throw new Error("getLocalAgent returned null for mcpAppsAgent");
25+
}
26+
27+
// @region[runtime-mcpapps-config]
28+
// The `mcpApps.servers` config is all you need server-side. The runtime
29+
// auto-applies the MCP Apps middleware to every registered agent: on each
30+
// MCP tool call it fetches the associated UI resource and emits an
31+
// `activity` event that the built-in `MCPAppsActivityRenderer` renders
32+
// inline in the chat.
33+
const runtime = new CopilotRuntime({
34+
// @ts-ignore -- see main route.ts
35+
agents: {
36+
"mcp-apps": mcpAppsAgent,
37+
default: mcpAppsAgent,
38+
},
39+
mcpApps: {
40+
servers: [
41+
{
42+
type: "http",
43+
url: process.env.MCP_SERVER_URL || "https://mcp.excalidraw.com",
44+
// Always pin a stable `serverId`. Without it CopilotKit hashes the
45+
// URL, and a URL change silently breaks restoration of persisted
46+
// MCP Apps in prior conversation threads.
47+
serverId: "excalidraw",
48+
},
49+
],
50+
},
51+
});
52+
// @endregion[runtime-mcpapps-config]
53+
54+
export const POST = async (req: NextRequest) => {
55+
try {
56+
const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({
57+
endpoint: "/api/copilotkit-mcp-apps",
58+
serviceAdapter: new ExperimentalEmptyAdapter(),
59+
runtime,
60+
});
61+
return await handleRequest(req);
62+
} catch (error: unknown) {
63+
const e = error as { message?: string; stack?: string };
64+
return NextResponse.json(
65+
{ error: e.message, stack: e.stack },
66+
{ status: 500 },
67+
);
68+
}
69+
};

showcase/integrations/mastra/src/app/api/copilotkit/route.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export const demoAgentNames = [
7171
"declarative-gen-ui",
7272
"a2ui-fixed-schema",
7373
"headless-complete",
74+
"tool-rendering-reasoning-chain",
7475
] as const;
7576

7677
// Per-demo override map: any demo alias listed here resolves to a dedicated
@@ -99,7 +100,8 @@ export type LocalMastraAgentName =
99100
| "headlessCompleteAgent"
100101
| "sharedStateReadWriteAgent"
101102
| "subagentsSupervisorAgent"
102-
| "multimodalAgent";
103+
| "multimodalAgent"
104+
| "mcpAppsAgent";
103105

104106
export type BuiltAgents = Record<
105107
DemoAgentName | LocalMastraAgentName,
@@ -157,6 +159,11 @@ export function buildAgents(
157159
"multimodalAgent missing from Mastra config — required for /demos/multimodal",
158160
);
159161
}
162+
if (!baseLocalAgents.mcpAppsAgent) {
163+
throw new Error(
164+
"mcpAppsAgent missing from Mastra config — required for /demos/mcp-apps",
165+
);
166+
}
160167
const headlessCompleteAgentInstance = getLocalAgent({
161168
mastra: mastraInstance,
162169
agentId: "headlessCompleteAgent",
@@ -191,12 +198,21 @@ export function buildAgents(
191198
if (!multimodalAgentInstance) {
192199
throw new Error("getLocalAgent returned null for multimodalAgent");
193200
}
201+
const mcpAppsAgentInstance = getLocalAgent({
202+
mastra: mastraInstance,
203+
agentId: "mcpAppsAgent",
204+
resourceId: "mastra-mcpAppsAgent",
205+
});
206+
if (!mcpAppsAgentInstance) {
207+
throw new Error("getLocalAgent returned null for mcpAppsAgent");
208+
}
194209
const localAgents = {
195210
weatherAgent: baseLocalAgents.weatherAgent,
196211
headlessCompleteAgent: headlessCompleteAgentInstance,
197212
sharedStateReadWriteAgent: sharedStateRWAgentInstance,
198213
subagentsSupervisorAgent: subagentsSupervisorAgentInstance,
199214
multimodalAgent: multimodalAgentInstance,
215+
mcpAppsAgent: mcpAppsAgentInstance,
200216
};
201217

202218
// Guard against silent shadowing: if Mastra ever registers a local agent
@@ -241,6 +257,7 @@ export function buildAgents(
241257
"mastra-subagentsSupervisorAgent",
242258
);
243259
resourceIdByAgent.set("multimodalAgent", "mastra-multimodalAgent");
260+
resourceIdByAgent.set("mcpAppsAgent", "mastra-mcpAppsAgent");
244261

245262
const demoAliases: Record<
246263
string,
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# MCP Apps
2+
3+
## What This Demo Shows
4+
5+
MCP server-driven UI rendered inline via the runtime's `mcpApps` config and the built-in activity renderer.
6+
7+
## How to Interact
8+
9+
Try asking:
10+
11+
- "Use Excalidraw to draw a simple flowchart with three steps."
12+
- "Open Excalidraw and sketch a system diagram with a client, server, and database."
13+
14+
The Mastra agent calls a remote MCP tool (Excalidraw's `create_view`); the runtime fetches the UI resource and emits an activity event, which the built-in `MCPAppsActivityRenderer` renders as a sandboxed iframe inline in the chat.
15+
16+
## Technical Details
17+
18+
- `CopilotRuntime({ mcpApps: { servers: [...] } })` auto-applies MCP Apps middleware to every registered agent
19+
- The agent itself defines no tools — MCP server tools are injected at request time
20+
- The frontend renders nothing custom: `<CopilotChat />` plus the auto-registered `MCPAppsActivityRenderer` does the work
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
"use client";
2+
3+
/**
4+
* MCP Apps demo (Mastra).
5+
*
6+
* MCP Apps are MCP servers that expose tools with associated UI resources.
7+
* The CopilotKit runtime is wired with `mcpApps: { servers: [...] }`
8+
* (see `src/app/api/copilotkit-mcp-apps/route.ts`), which auto-applies the
9+
* MCP Apps middleware. When the agent calls an MCP tool, the middleware
10+
* fetches the associated UI resource and emits an activity event; the
11+
* built-in `MCPAppsActivityRenderer` registered by `CopilotKitProvider`
12+
* renders the sandboxed iframe inline in the chat — no app-side renderer
13+
* registration required.
14+
*
15+
* This cell points at the public Excalidraw MCP app
16+
* (https://mcp.excalidraw.com).
17+
*/
18+
19+
import React from "react";
20+
import {
21+
CopilotKit,
22+
CopilotChat,
23+
useConfigureSuggestions,
24+
} from "@copilotkit/react-core/v2";
25+
26+
export default function MCPAppsDemo() {
27+
// @region[no-frontend-renderer-needed]
28+
// No `renderActivityMessages`, no `useRenderActivityMessage` — the
29+
// CopilotKitProvider auto-registers the built-in `MCPAppsActivityRenderer`
30+
// for the "mcp-apps" activity type. A plain <CopilotChat /> is enough.
31+
return (
32+
<CopilotKit runtimeUrl="/api/copilotkit-mcp-apps" agent="mcp-apps">
33+
<div className="flex justify-center items-center h-screen w-full">
34+
<div className="h-full w-full max-w-4xl">
35+
<Chat />
36+
</div>
37+
</div>
38+
</CopilotKit>
39+
);
40+
// @endregion[no-frontend-renderer-needed]
41+
}
42+
43+
function Chat() {
44+
useConfigureSuggestions({
45+
suggestions: [
46+
{
47+
title: "Draw a flowchart",
48+
message: "Use Excalidraw to draw a simple flowchart with three steps.",
49+
},
50+
{
51+
title: "Sketch a system diagram",
52+
message:
53+
"Open Excalidraw and sketch a system diagram with a client, server, and database.",
54+
},
55+
],
56+
available: "always",
57+
});
58+
59+
return <CopilotChat agentId="mcp-apps" className="h-full rounded-2xl" />;
60+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Tool Rendering + Reasoning Chain
2+
3+
## What This Demo Shows
4+
5+
Sequential tool calls rendered with per-tool components plus a reasoning chain rendered inline via a custom `reasoningMessage` slot.
6+
7+
## How to Interact
8+
9+
Try asking:
10+
11+
- "What's the weather in Tokyo?"
12+
- "Find flights from SFO to JFK."
13+
- "Roll a 20-sided die for me."
14+
15+
The agent's reasoning tokens stream into a `ReasoningBlock` while tool calls render as `WeatherCard`, `FlightListCard`, or the catch-all renderer.
16+
17+
## Technical Details
18+
19+
- `useRenderTool({ name: "get_weather" })` and `useRenderTool({ name: "search_flights" })` register named per-tool renderers
20+
- `useDefaultRenderTool` registers the catch-all that handles every other tool
21+
- The reasoning chain comes from AG-UI `REASONING_MESSAGE_*` events emitted by the Mastra agent when the underlying model produces reasoning tokens
22+
- The `messageView.reasoningMessage` slot on `<CopilotChat />` overrides the default reasoning UI

0 commit comments

Comments
 (0)