showcase/shell-docs/src/content/docs/agentic-protocols/mcp.mdx
The Model Context Protocol is an open standard that enables developers to build secure, two-way connections between their data sources and AI-powered tools. With MCP, you can:
For further reading, check out the Model Context Protocol website.
<Callout type="info" title="Looking for MCP Apps?"> If you want MCP servers to return **interactive UI components** that render directly in the chat, check out [MCP Apps](/generative-ui/mcp-apps). </Callout><Image src="/images/any-agentic-backend-light.png" alt="CopilotKit with Agentic Protocols" width={4096} height={2304} className="block dark:hidden w-full mx-auto" /> <Image src="/images/any-agentic-backend-dark.png" alt="CopilotKit with Agentic Protocols" width={4096} height={2304} className="hidden dark:block w-full mx-auto" />
<div className="text-center text-sm text-muted-foreground mt-2 mb-8 w-3/5 mx-auto"> MCP is one of three prominent [agentic protocols](/agentic-protocols) CopilotKit supports to connect agents to user-facing frontends </div>"MCP server" covers three quite different things, and only one of them needs an MCP App. Picking the wrong one means either building UI you didn't need or shipping a tool nobody can act on.
Work out which kind of server you have:
| Your server… | What the user should see | What to build |
|---|---|---|
| Reads data and returns it | Nothing, or a status line — the answer belongs in the agent's reply | Plain MCP tool. No renderer needed. |
| Changes something (writes, sends, deletes) | What is about to happen, with a way to stop it | Plain MCP tool + a renderer, plus approval for anything irreversible |
| Has its own interface (a form, a picker, a chart to interact with) | The interface itself | An MCP App with a UI resource |
The most common case, and the one that needs the least. A docs search, a database query, a "look up this customer" tool — the tool result is context for the model, not something the user reads directly. The agent folds it into its answer, and that answer is the UI.
Connect the server and stop there. If you want the call to be visible rather than invisible, a wildcard renderer gives every tool call a status card without writing one renderer per tool:
import { useDefaultRenderTool } from "@copilotkit/react-core/v2";
useDefaultRenderTool({
render: ({ name, status, parameters, result }) => (
<McpToolCall status={status} name={name} args={parameters} result={result} />
),
});
Reach for an MCP App here only if the result is genuinely interactive — a chart the user filters, not a table they read.
When a tool sends an email, files a ticket, or deletes a row, the user needs to see it — and usually needs to authorize it. That's still not an MCP App: it's a renderer plus human-in-the-loop.
The renderer shows what happened. The approval step decides whether it happens at all, and belongs on anything the user can't undo. See Human in the loop for the approval flow, and Tool rendering for renderer options.
A useful test: if you're considering an MCP App purely to render a confirmation button, you want human-in-the-loop instead. Approval is a CopilotKit concern, not a server-supplied UI.
An MCP App is the right answer when the server owns the interface — it exposes a tool with an associated UI resource, and CopilotKit fetches and renders that resource in a sandboxed iframe with no frontend code from you.
That's worth it when the interaction is rich enough to need real UI (a seat picker, a configuration form, an embedded editor), and when the server author — not the app author — is the one who should define how it looks. The cost is that the server now ships and versions UI, so a read-only lookup doesn't justify it.
See MCP Apps for setup.
<Callout type="info"> These aren't exclusive. One server can expose plain tools and App-backed tools side by side; CopilotKit renders each call according to whether it carries a UI resource. </Callout><Accordions className="shadow-lg ring-1 ring-indigo-400 selected bg-gradient-to-r from-indigo-100/80 to-purple-200 dark:from-indigo-900/30 dark:to-purple-900/50">
<Accordion title="Get an MCP Server from Composio">
Composio provides a registry of ready-to-use MCP servers with simple authentication and setup.
To get started, go to [Composio](https://mcp.composio.dev/), find a server that suits your needs and copy the SSE URL before continuing here.
</Accordion>
</Accordions>
<Accordions>
<Accordion title="Don't have a Next.js application?">
No problem! Just use `create-next-app` to make one quickly.
```bash
npx create-next-app@latest
```
</Accordion>
</Accordions>
```bash
npx copilotkit@latest init -m MCP
```
</Step>
</TailoredContentOption>
<TailoredContentOption
id="do-it-manually"
title="Code along"
description="I want to deeply understand what's happening under the hood or don't have a Next.js application."
icon={<SquareChartGantt />}
>
<Step>
#### Set up the CopilotKit Provider
Wrap your application with the `CopilotKit` provider:
```tsx
"use client";
import { CopilotKit } from "@copilotkit/react-core/v2";
export default function App() {
return (
<CopilotKit publicApiKey="<replace_with_your_own>">
</CopilotKit>
);
}
```
</Step>
<Step>
#### Connect to MCP Servers
Create a component to manage MCP server connections:
```tsx
"use client";
import { useEffect } from "react";
import { useCopilotKit } from "@copilotkit/react-core/v2";
function McpServerManager() {
const { setMcpServers } = useCopilotKit();
useEffect(() => {
setMcpServers([
{
// Try a sample MCP server at https://mcp.composio.dev/
endpoint: "your_mcp_sse_url",
},
]);
}, [setMcpServers]);
return null;
}
export default McpServerManager;
```
</Step>
<Step>
#### Add the Chat Interface
Add the `CopilotChat` component to your page:
```tsx
"use client";
import { CopilotChat } from "@copilotkit/react-core/v2";
export default function ChatInterface() {
return (
<div className="flex h-screen p-4">
<McpServerManager />
<CopilotChat
className="flex-grow rounded-lg w-full"
/>
</div>
);
}
```
</Step>
<Step>
#### Visualize MCP Tool Calls (Optional)
Create a component to display MCP tool calls in your UI:
```tsx
"use client";
import {
useFrontendTool,
CatchAllActionRenderProps,
} from "@copilotkit/react-core/v2";
export function ToolRenderer() {
useFrontendTool({
/**
* The asterisk (*) matches all tool calls
*/
name: "*",
render: ({ name, status, args, result }: CatchAllActionRenderProps<[]>) => (
<McpToolCall status={status} name={name} args={args} result={result} />
),
});
return null;
}
```
</Step>
<Step>
#### Complete Implementation
Combine all components together:
```tsx
"use client";
import { CopilotKit, CopilotChat } from "@copilotkit/react-core/v2";
export default function Page() {
return (
<CopilotKit publicApiKey="<replace_with_your_own>">
<div className="flex h-screen p-4">
<McpServerManager />
<CopilotChat
className="flex-grow rounded-lg w-full"
/>
<ToolRenderer />
</div>
</CopilotKit>
);
}
```
</Step>
</TailoredContentOption>
"use client";
interface ToolCallProps {
status: "complete" | "inProgress" | "executing";
name?: string;
args?: any;
result?: any;
}
export default function MCPToolCall({
status,
name = "",
args,
result,
}: ToolCallProps) {
const [isOpen, setIsOpen] = React.useState(false);
// Format content for display
const format = (content: any): string => {
if (!content) return "";
const text =
typeof content === "object"
? JSON.stringify(content, null, 2)
: String(content);
return text
.replace(/\\n/g, "\n")
.replace(/\\t/g, "\t")
.replace(/\\"/g, '"')
.replace(/\\\\/g, "\\");
};
return (
<div className="bg-[#1e2738] rounded-lg overflow-hidden w-full">
<div
className="p-3 flex items-center cursor-pointer"
onClick={() => setIsOpen(!isOpen)}
>
<span className="text-white text-sm overflow-hidden text-ellipsis">
{name || "MCP Tool Call"}
</span>
<div className="ml-auto">
<div
className={`w-2 h-2 rounded-full ${
status === "complete"
? "bg-gray-300"
: status === "inProgress" || status === "executing"
? "bg-gray-500 animate-pulse"
: "bg-gray-700"
}`}
/>
</div>
</div>
{isOpen && (
<div className="px-4 pb-4 text-gray-300 font-mono text-xs">
{args && (
<div className="mb-4">
<div className="text-gray-400 mb-2">Parameters:</div>
<pre className="whitespace-pre-wrap max-h-[200px] overflow-auto">
{format(args)}
</pre>
</div>
)}
{status === "complete" && result && (
<div>
<div className="text-gray-400 mb-2">Result:</div>
<pre className="whitespace-pre-wrap max-h-[200px] overflow-auto">
{format(result)}
</pre>
</div>
)}
</div>
)}
</div>
);
}
To configure your self-hosted runtime with MCP servers, you'll need to implement the createMCPClient function that matches this interface:
type CreateMCPClientFunction = (
config: MCPEndpointConfig,
) => Promise<MCPClient>;
For detailed implementation guidance, refer to the official MCP SDK documentation.
Here's a basic example of configuring the runtime:
import {
CopilotRuntime,
OpenAIAdapter,
copilotRuntimeNextJSAppRouterEndpoint,
} from "@copilotkit/runtime";
import { NextRequest } from "next/server";
const serviceAdapter = new OpenAIAdapter();
const runtime = new CopilotRuntime({
createMCPClient: async (config) => {
// Implement your MCP client creation logic here
// See the MCP SDK docs for implementation details
},
});
export const POST = async (req: NextRequest) => {
const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({
runtime,
serviceAdapter,
endpoint: "/api/copilotkit",
});
return handleRequest(req);
};