Configuring MCP Servers
The Model Context Protocol (MCP) is an open standard that allows AI assistants to securely connect to external data sources and services. MCP enables your chat assistant to access real-time information, interact with APIs, query databases, and perform actions across various systems.
AI Chat Bootstrap provides full support for MCP through the useMCPServer hook and createMcpToolsHandler server handler.
How MCP Works
MCP servers expose tools (functions) that the AI can discover and invoke. When you configure an MCP server:
- The client registers the server with
useMCPServer - The hook fetches available tools from your backend via
/api/mcp-discovery - The backend connects to the MCP server and returns tool summaries
- Tools are automatically included in the AI’s system prompt
- When the AI needs to use a tool, it calls your chat API
- The backend executes the tool via MCP and returns the result
Quick Start
1. Set up the MCP Discovery Endpoint
The scaffold automatically creates /api/mcp-discovery/route.ts:
import { createMcpToolsHandler } from "ai-chat-bootstrap/server";
const handler = createMcpToolsHandler({
onError: (error) => {
console.error("[mcp-api]", error);
},
});
export { handler as POST };This endpoint receives MCP server configurations from the client and returns available tools.
2. Register an MCP Server on the Client
Use useMCPServer to connect to an MCP server:
"use client";
import { ChatContainer, useMCPServer } from "ai-chat-bootstrap";
export function ChatWithMCP() {
const mcpServer = useMCPServer({
url: "http://localhost:3030/mcp",
name: "Local Tools",
transportType: "sse", // or "streamable-http"
});
return (
<div>
{mcpServer.isLoading && <p>Loading MCP tools...</p>}
{mcpServer.error && <p>Error: {mcpServer.error}</p>}
{mcpServer.tools.length > 0 && (
<p>{mcpServer.tools.length} tools available</p>
)}
<ChatContainer
transport={{ api: "/api/chat" }}
mcp={{ enabled: true, api: "/api/mcp-discovery" }}
/>
</div>
);
}3. Enable MCP in Your Chat Handler
Make sure your chat handler has MCP enabled:
import { createAIChatHandler } from "ai-chat-bootstrap/server";
import { openai } from "@ai-sdk/openai";
const handler = createAIChatHandler({
model: openai("gpt-4o"),
mcpEnabled: true, // Important!
});
export { handler as POST };Configuration Options
useMCPServer Options
| Option | Type | Default | Description |
|---|---|---|---|
url | string | — | MCP server URL (required) |
name | string | undefined | Friendly server name shown in UI |
transportType | "sse" | "streamable-http" | "sse" | Protocol for connecting to MCP server |
headers | Record<string, string> | {} | Extra headers forwarded to the MCP server |
api | string | "/api/mcp-discovery" | Your backend discovery endpoint |
autoFetchTools | boolean | true | Automatically fetch tools on mount |
id | string | url | Override the server identifier |
Multiple MCP Servers
You can connect to multiple MCP servers simultaneously:
function MultiMCPChat() {
const weatherServer = useMCPServer({
url: "http://localhost:3030/mcp",
name: "Weather Tools",
});
const databaseServer = useMCPServer({
url: "http://localhost:3031/mcp",
name: "Database Tools",
});
return (
<div>
<div className="space-y-2 mb-4">
<ServerStatus server={weatherServer} />
<ServerStatus server={databaseServer} />
</div>
<ChatContainer
transport={{ api: "/api/chat" }}
mcp={{ enabled: true }}
/>
</div>
);
}
function ServerStatus({ server }) {
return (
<div className="flex items-center gap-2 text-sm">
<span className="font-medium">{server.name}:</span>
{server.isLoading ? (
<span>Loading...</span>
) : server.error ? (
<span className="text-red-500">{server.error}</span>
) : (
<span className="text-green-500">
{server.tools.length} tools available
</span>
)}
{server.error && (
<button onClick={server.refresh} className="underline">
Retry
</button>
)}
</div>
);
}Authentication & Headers
Client-Side Headers
Pass headers that are safe to expose on the client:
useMCPServer({
url: "https://api.example.com/mcp",
headers: {
"X-Client-Version": "1.0.0",
},
});Server-Side Headers (Secure)
For sensitive credentials, use forwardHeaders in your MCP handler to inject server-side environment variables:
// /api/mcp-discovery/route.ts
import { createMcpToolsHandler } from "ai-chat-bootstrap/server";
const handler = createMcpToolsHandler({
forwardHeaders: ["authorization"],
onError: (error) => {
console.error("[mcp-api]", error);
},
});
export { handler as POST };Then set the header in your request middleware or pass it from your chat API.
Environment Variables
MCP server URLs can be configured via environment variables:
# .env.local
NEXT_PUBLIC_MCP_SERVER_URL=http://localhost:3030/mcp
MCP_API_KEY=your-secret-keyuseMCPServer({
url: process.env.NEXT_PUBLIC_MCP_SERVER_URL!,
name: "Local Tools",
});Tool Discovery & Prompts
When MCP servers are registered, their tools are automatically:
- Listed in the system prompt with
[MCP]badges - Available for the AI to invoke via the standard tool calling mechanism
- Executed server-side through the MCP transport
The AI receives information like:
## Tools
**Model Context Protocol (MCP)**: Some tools are provided via MCP, an open
standard that allows AI assistants to securely connect to external data sources
and services. MCP tools enable you to access real-time information, interact
with APIs, query databases, and perform actions across various systems.
**Available MCP Tools:**
- **weather_get_forecast**: Get weather forecast for a location
- **database_query**: Execute a read-only database query
- **file_read**: Read contents of a fileError Handling
The useMCPServer hook provides error state management:
const server = useMCPServer({ url: "..." });
if (server.error) {
return (
<div className="error-banner">
<p>Failed to connect to MCP server: {server.error}</p>
<button onClick={server.refresh}>Retry Connection</button>
</div>
);
}Partial Tool Loading
Even if some tools fail to load, successfully loaded tools will still be available:
// Server returns:
// { tools: [tool1, tool2], errors: [{ message: "tool3 failed" }] }
// The hook will:
// - Set tools to [tool1, tool2]
// - Set error to "tool3 failed"
// - Allow the chat to continue with available toolsDynamic Server Configuration
Let users configure MCP servers at runtime:
"use client";
import { useState } from "react";
import { ChatContainer, useMCPServer } from "ai-chat-bootstrap";
export function DynamicMCPChat() {
const [serverUrl, setServerUrl] = useState("");
const [enabledServers, setEnabledServers] = useState<string[]>([]);
return (
<div>
<div className="mb-4">
<input
type="text"
value={serverUrl}
onChange={(e) => setServerUrl(e.target.value)}
placeholder="Enter MCP server URL"
/>
<button
onClick={() => {
if (serverUrl) {
setEnabledServers([...enabledServers, serverUrl]);
setServerUrl("");
}
}}
>
Add Server
</button>
</div>
{enabledServers.map((url) => (
<MCPServerConnection key={url} url={url} />
))}
<ChatContainer
transport={{ api: "/api/chat" }}
mcp={{ enabled: true }}
/>
</div>
);
}
function MCPServerConnection({ url }: { url: string }) {
const server = useMCPServer({ url });
return <div>Connected to {url}</div>;
}Custom Tool Rendering
MCP tools support custom React rendering, allowing you to display rich, interactive UI components for tool results instead of plain text. This works similarly to frontend tool rendering, but for tools provided by MCP servers.
Basic Usage
Pass toolRenderers to the mcp prop to register custom renderers:
"use client";
import { ChatContainer } from "ai-chat-bootstrap";
export function ChatWithMCPRendering() {
return (
<ChatContainer
transport={{ api: "/api/chat" }}
mcp={{
enabled: true,
servers: [
{
id: "weather-server",
transport: {
type: "streamable-http",
url: "http://localhost:3030/mcp",
},
},
],
toolRenderers: [
{
serverUrl: "http://localhost:3030/mcp",
toolName: "get_weather",
render: (result) => {
// Parse MCP response format
const data = JSON.parse(result.content[0].text);
return (
<div className="p-4 bg-card rounded-lg border">
<div className="flex items-center justify-between">
<h3 className="text-lg font-semibold">{data.location}</h3>
<span className="text-3xl font-bold">{data.temperature}</span>
</div>
<p className="text-sm text-muted-foreground">{data.conditions}</p>
{data.humidity && (
<div className="text-xs text-muted-foreground mt-2">
Humidity: {data.humidity}%
</div>
)}
</div>
);
},
},
],
}}
/>
);
}Understanding MCP Response Format
MCP tools return results in a standardized format:
{
content: [
{
type: "text",
text: '{"location":"San Francisco","temperature":"72°F","conditions":"Sunny"}'
}
]
}Your renderer receives this entire object and should parse the JSON from content[0].text:
render: (result) => {
// Extract and parse JSON from MCP response
let data: Record<string, unknown> = {};
try {
const mcpResult = result as { content?: Array<{ type: string; text: string }> };
if (mcpResult.content?.[0]?.text) {
data = JSON.parse(mcpResult.content[0].text);
}
} catch (error) {
console.error("Failed to parse MCP result:", error);
// Fallback to raw result
data = result as Record<string, unknown>;
}
return <YourComponent {...data} />;
}Multiple Tool Renderers
Register renderers for multiple tools from different servers:
<ChatContainer
mcp={{
enabled: true,
servers: [
{
id: "weather-server",
transport: { type: "sse", url: "http://localhost:3030/mcp" },
},
{
id: "calendar-server",
transport: { type: "sse", url: "http://localhost:3031/mcp" },
},
],
toolRenderers: [
{
serverUrl: "http://localhost:3030/mcp",
toolName: "get_weather",
render: (result) => <WeatherCard data={parseResult(result)} />,
},
{
serverUrl: "http://localhost:3031/mcp",
toolName: "list_events",
render: (result) => <CalendarEvents events={parseResult(result)} />,
},
{
serverUrl: "http://localhost:3031/mcp",
toolName: "create_meeting",
render: (result) => <MeetingConfirmation meeting={parseResult(result)} />,
},
],
}}
/>Tool Uniqueness
Tool renderers are matched using a composite key of serverUrl + toolName. This guarantees uniqueness even when multiple MCP servers expose tools with the same name:
// These are treated as different tools:
// Server 1: http://localhost:3030/mcp -> "search"
// Server 2: http://localhost:3031/mcp -> "search"
toolRenderers: [
{
serverUrl: "http://localhost:3030/mcp",
toolName: "search",
render: (result) => <WebSearchResults {...parseResult(result)} />,
},
{
serverUrl: "http://localhost:3031/mcp",
toolName: "search",
render: (result) => <DatabaseSearchResults {...parseResult(result)} />,
},
]Complete Example
Here’s a full example with error handling and multiple renderers:
"use client";
import React from "react";
import { ChatContainer, useMCPServer } from "ai-chat-bootstrap";
// Helper to parse MCP results
function parseMCPResult(result: unknown): Record<string, unknown> {
try {
const mcpResult = result as { content?: Array<{ type: string; text: string }> };
if (mcpResult.content?.[0]?.text) {
return JSON.parse(mcpResult.content[0].text);
}
} catch (error) {
console.error("Failed to parse MCP result:", error);
}
return result as Record<string, unknown>;
}
// Weather component
function WeatherCard({ location, temperature, conditions, humidity }: any) {
return (
<div className="space-y-2 p-4 bg-card rounded-lg border">
<div className="flex items-center justify-between">
<h3 className="text-lg font-semibold">{location}</h3>
<span className="text-3xl font-bold">{temperature}</span>
</div>
<p className="text-sm text-muted-foreground capitalize">{conditions}</p>
{humidity && (
<div className="text-xs text-muted-foreground">Humidity: {humidity}%</div>
)}
</div>
);
}
// Meeting component
function MeetingAgenda({ title, date, attendees, items }: any) {
return (
<div className="space-y-3 p-4 bg-card rounded-lg border">
<div>
<h3 className="text-lg font-semibold">{title}</h3>
<p className="text-sm text-muted-foreground">{date}</p>
</div>
{attendees && (
<div className="text-sm">
<span className="font-medium">Attendees: </span>
{attendees.join(", ")}
</div>
)}
{items && items.length > 0 && (
<ol className="list-decimal list-inside space-y-1 text-sm">
{items.map((item: string, idx: number) => (
<li key={idx}>{item}</li>
))}
</ol>
)}
</div>
);
}
export function MCPWithCustomRendering() {
const mcpServer = useMCPServer({
url: process.env.NEXT_PUBLIC_MCP_SERVER_URL!,
name: "Demo Tools",
});
return (
<div>
{mcpServer.error && (
<div className="mb-4 p-3 bg-destructive/10 text-destructive rounded">
Error: {mcpServer.error}
</div>
)}
<ChatContainer
transport={{ api: "/api/chat" }}
mcp={{
enabled: true,
toolRenderers: [
{
serverUrl: process.env.NEXT_PUBLIC_MCP_SERVER_URL!,
toolName: "demo_weather_forecast",
render: (result) => <WeatherCard {...parseMCPResult(result)} />,
},
{
serverUrl: process.env.NEXT_PUBLIC_MCP_SERVER_URL!,
toolName: "create_meeting_agenda",
render: (result) => <MeetingAgenda {...parseMCPResult(result)} />,
},
],
}}
header={{ title: "MCP with Custom Rendering" }}
ui={{ placeholder: "Ask about weather or create a meeting agenda" }}
/>
</div>
);
}Best Practices for MCP Rendering
- Always parse MCP response format - Extract JSON from
content[0].text - Handle parsing errors gracefully - Provide fallback rendering
- Type your data - Create interfaces for expected tool responses
- Use environment variables - Keep server URLs configurable
- Test with real servers - Verify parsing logic with actual MCP responses
- Match exact tool names - Tool names must match exactly (case-sensitive)
- Provide meaningful fallbacks - Show useful error messages when parsing fails
Debugging Tips
If your custom renderer isn’t working:
- Check tool names match exactly - Case-sensitive matching
- Verify serverUrl matches - Must be identical to server configuration
- Inspect the result object - Log what your renderer receives
- Test JSON parsing - Ensure MCP server returns valid JSON
- Check console for errors - Parsing errors are logged
Best Practices
- Use environment variables for MCP server URLs in production
- Handle errors gracefully - show retry buttons and clear error messages
- Keep credentials server-side - use
forwardHeadersfor sensitive data - Test tool discovery - verify tools load correctly before deploying
- Monitor performance - MCP calls add latency to tool execution
- Document available tools - help users understand what MCP tools can do
Related Documentation
-
useMCPServer Hook -
createMcpToolsHandler Server Handler -
createAIChatHandler (mcpEnabled option)