Fractal Chat Quickstart
This quickstart will show you how to set up a minimal chat app that can render interactive UI components returned by MCP tools. In just a few minutes, you’ll be able to run a backend + frontend locally and see UI components appear inline in chat messages.
Prerequisite
This quickstart assumes you already have an MCP server running at http://localhost:3000/mcp
.
If you don’t, first complete the MCP Quickstart.
Overview
- Backend: Express + Vercel AI SDK, connected to an MCP server.
- Frontend: React + Vite chat app with Fractal’s
FractalUIResourceRenderer
. - Goal: Type a message → model calls an MCP tool → tool returns a
UIResource
→ UI is rendered inline.
Your project will look like this:
fractal-chat-quickstart/
backend/
server.ts # Chat backend
ui/
src/Chat.tsx # Chat UI
src/main.tsx
index.html
Step 1. Backend
Create a backend folder:
mkdir -p fractal-chat-quickstart/backend && cd fractal-chat-quickstart/backend
npm init -y
npm i express cors ai @ai-sdk/openai @modelcontextprotocol/sdk
Add a server.ts
:
import express from "express";
import cors from "cors";
import dotenv from "dotenv";
import { streamText, UIMessage, convertToCoreMessages, experimental_createMCPClient } from "ai";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import { createOpenAI } from "@ai-sdk/openai";
dotenv.config();
const app = express();
app.use(cors());
app.use(express.json({ limit: "50mb" }));
const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY! });
const systemMessage = "You are a helpful assistant.";
app.post("/api/chat", async (req, res) => {
const { messages } = req.body as { messages: UIMessage[] };
const transport = new StreamableHTTPClientTransport(new URL("http://localhost:3000/mcp"));
const mcpClient = await experimental_createMCPClient({ transport });
const mcpTools = await mcpClient.tools();
const result = streamText({
model: openai("gpt-4.1"),
system: systemMessage,
messages: convertToCoreMessages(messages),
temperature: 0.0,
tools: mcpTools,
});
result.pipeDataStreamToResponse(res);
});
app.listen(3001, () => console.log("Backend on http://localhost:3001"));
Run it:
OPENAI_API_KEY=YOUR_KEY node server.ts
Step 2. Frontend
In a new terminal:
cd ../..
mkdir -p fractal-chat-quickstart/ui && cd fractal-chat-quickstart/ui
npm init -y
npm i react react-dom @ai-sdk/react @fractal-mcp/client-ui-react @mcp-ui/client
npm i -D vite typescript @vitejs/plugin-react
Add index.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Fractal Chat Example</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Add src/main.tsx
:
import React from "react";
import ReactDOM from "react-dom/client";
import Chat from "./Chat";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(<Chat />);
Add src/Chat.tsx
(with CSS classes for clean styling):
import { useChat } from "@ai-sdk/react";
import { FractalUIResourceRenderer, isUIResource } from "@fractal-mcp/client-ui-react";
import { UIResource } from "@fractal-mcp/protocol";
export default function Chat() {
const { messages, input, handleInputChange, handleSubmit } = useChat({
api: "http://localhost:3001/api/chat",
});
return (
<div className="chat-container">
<div className="chat-messages">
{messages.map((m, i) => (
<div key={i} className={`message message-${m.role}`}>
<div className="role">{m.role === "user" ? "You" : "Assistant"}</div>
<div className="content">
{m.parts.map((part, j) => {
if (part.type === "text") return <span key={j}>{part.text}</span>;
if (part.type === "tool-invocation") {
const toolInvocation = (part as any).toolInvocation;
if (toolInvocation?.result?.content?.length) {
const content = toolInvocation.result.content[0];
if (isUIResource(content)) {
return <FractalUIResourceRenderer key={j} resource={content.resource} autoResizeIframe />;
}
}
}
return null;
})}
</div>
</div>
))}
</div>
<form onSubmit={handleSubmit} className="chat-input">
<input
value={input}
onChange={handleInputChange}
placeholder="Type your message..."
/>
<button type="submit" disabled={!input.trim()}>Send</button>
</form>
</div>
);
}
Run the UI:
npx vite --port 5174
Step 3. Run it all together
-
Start your MCP server (from Producer Quickstart) at
http://localhost:3000/mcp
. -
Start backend:
cd fractal-chat-quickstart/backend OPENAI_API_KEY=YOUR_KEY node server.ts
-
Start frontend:
cd fractal-chat-quickstart/ui npm run dev
Open http://localhost:5174 → Ask your MCP-backed tool something like “Create a button” → see an inline UI render. 🎉
Next Steps
- Learn more about the Fractal messenger protocol.
- Connect to the Fractal Registry .