Back to Copilotkit

MCP

showcase/shell-docs/src/content/docs/agentic-protocols/mcp.mdx

1.68.213.0 KB
Original Source

Introduction

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:

  • Connect AI applications to your data sources
  • Enable AI tools to access and utilize your data securely
  • Build AI-powered features that have context about your application

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>

When you do (and don't) need an MCP App

"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 seeWhat to build
Reads data and returns itNothing, or a status line — the answer belongs in the agent's replyPlain MCP tool. No renderer needed.
Changes something (writes, sends, deletes)What is about to happen, with a way to stop itPlain MCP tool + a renderer, plus approval for anything irreversible
Has its own interface (a form, a picker, a chart to interact with)The interface itselfAn MCP App with a UI resource

Read-only context servers

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:

tsx
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.

Action and mutation servers

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.

Interactive MCP Apps

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>

Quickstart with CopilotKit

<Steps> <Step> ### Get an MCP Server First, we need to make sure we have an MCP server to connect to. You can use any MCP SSE endpoint you have configured.
<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>
</Step> <TailoredContent className="step" id="cli" header={ <div> <p className="text-xl font-semibold">Use the CopilotKit CLI</p> </div> } > <TailoredContentOption id="use-cli" title="Use the CopilotKit CLI" description="I have a Next.js application and want to get started quickly." icon={<SquareTerminal />} > <Step> ### Run the CLI Just run this following command in your Next.js application to get started!
    <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>
</TailoredContent> </Steps>

Advanced Usage

Implementing the McpToolCall Component

<details> <summary>Click to see the McpToolCall component implementation</summary>
tsx
"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>
  );
}
</details>

Self-Hosting Option

<details> <summary>Click here to learn how to use MCP with self-hosted runtime</summary> <Callout type="info" title="Self-Hosting vs Copilot Cloud"> The Copilot Runtime handles communication with LLMs, message history, and state. You can self-host it or use{" "} <LinkToCopilotCloud asButton={false}>Copilot Cloud</LinkToCopilotCloud>{" "} (recommended). Learn more in our [Self-Hosting Guide](/backend/copilot-runtime). </Callout>

To configure your self-hosted runtime with MCP servers, you'll need to implement the createMCPClient function that matches this interface:

typescript
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:

tsx
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);
};
</details>