Back
BY
Anmol Baranwal
and
May 1, 2025
0

AI Agents have unlocked powerful use cases, so developers are automating complex workflows.

But sometimes you just want to build smart stuff without all the extra complexity, which can be achieved using agentless architecture.

Today, we will learn about what it means, how MCP fits in the whole scenario, and how to build our own agentless applications using CopilotKit (framework) with Composio servers.

Let's jump in.

What is covered?

In a nutshell, we are covering these topics in detail.

  1. What is Agentless Architecture?
  2. Introduction to MCP and its core components.
  3. How to get a Next.js + MCP Client running in under thirty minutes.
  4. A step-by-step guide on adding MCP support in your Next.js project.
  5. Building a working memory project to manage tasks & projects from tools like Linear.
  6. Some real-world examples with use cases.

Note: Copilotkit (framework for building AI Copilots) recently launched built-in support for MCP, which is what we will use in this guide.

To make things easier, we will connect it to Composio, which provides ready-to-use MCP servers with built-in authentication and minimal setup.

We will be covering a lot, so let's get started.

1. What is Agentless Architecture?

Agentless architecture refers to a system design where the frontend (usually a web or mobile client) directly interacts with an intelligent backend (such as an MCP server), without deploying or maintaining a custom agent to manage state, tools, or actions.

In the context of AI applications, this means your React frontend can send structured prompts and context to an MCP-compatible server. And receive intelligent responses without needing to host your own AI agent.

Instead of writing backend logic to handle tools, memory, or actions (which are all part of MCP), you simply send a request to an existing MCP server, and it handles the smart stuff for you.

🧠 Example: AI Task Assistant in a React App

Let’s say you are building a React-based project management app.

With agentless architecture:

  • You add a chat box.
  • The user types: Create a new task for our launch checklist.
  • Your app forwards this to an MCP server like CopilotKit’s.
  • The MCP server understands the request, connects with your tool (such as Linear), and replies: Task Finalize Launch Assets has been created in the Marketing project.

All without hosting your own AI agent or writing orchestration logic. It simply works.

When to go agentless vs agent-based?

It depends on the complexity of your use case. Here's a quick comparison:

  Agentless Architecture | Agent-Based Architecture                                      
  Easy to set up, no backend agent required  | Requires extra setup to host and manage the agent      
  Great for simple frontends, static sites, or quick demos   | Best for complex workflows, tools, or apps needing memory      
  No built-in memory, mostly stateless | Supports memory, history and evolving context                 
  Ideal for AI chat UIs, sidekicks, or small assistants  | Perfect for dev agents, RPA bots, and fully autonomous agents  
  Only needs access to an MCP-compatible server | Needs its own backend or container runtime                   
  Easy to scale across users by just deploying the frontend | Needs backend scaling strategy for multiple concurrent agents

My recommendation is to try both and see what works. At least, you will get an idea of the potential problems associated with each.

Now that we have learned about Agentless architecture, let's quickly understand MCP and its core components.

2. Introduction to MCP and its core components.

Model Context Protocol (MCP) is a new open protocol that standardizes how applications provide context and tools to LLMs.

Think of it as a universal connector for AI. MCP works as a plugin system for Cursor, which allows you to extend the Agent’s capabilities by connecting it to various data sources and tools.

mcp server

credit goes to Greg Isenburg on YouTube

MCP helps you build agents and complex workflows on top of LLMs.

For example, an MCP server for Obsidian helps AI assistants search and read notes from your Obsidian vault.

Your AI agent can now:

→ Send emails through Gmail
→ Create tasks in Linear
→ Search documents in Notion
→ Post messages in Slack
→ Update records in Salesforce

All by sending natural-language instructions through a standardized interface.

Think about what this means for productivity. Tasks that once required switching between 5+ apps can now happen in a single conversation with your agent.

At its core, MCP follows a client-server architecture where a host application can connect to multiple servers.

mcp server

Credit goes to ByteByteGo

Core Components.

Here are the core components of any general MCP Server.

  • MCP hosts - apps like Claude Desktop, Cursor, Windsurf or AI tools that want to access data via MCP.
  • MCP Clients - protocol clients that maintain 1:1 connections with MCP servers, acting as the communication bridge.
  • MCP Servers - lightweight programs that each expose specific capabilities (like reading files, querying databases...) through the standardized Model Context Protocol.
  • Local Data Sources - files, databases and services on your computer that MCP servers can securely access. For instance, a browser automation MCP server needs access to your browser to work.
  • Remote Services - External APIs and cloud-based systems that MCP servers can connect to.

If you're interested in reading about the architecture, check out official docs. It covers protocol layers, connection lifecycle, and error handling with the overall implementation.

We are going to cover everything, but if you're interested in reading more about MCP, check out these two blogs:

3. A step-by-step guide on adding MCP support in your Next.js project.

In this section, we will talk about how to add MCP support to your Next.js project using CopilotKit.

We are going to integrate Copilotkit, which has built-in support for the Model Context Protocol (MCP). That will allow us to create a frontend to connect directly to external MCP-compatible servers.

If you're interested in reading it yourself, please read the docs at: docs.copilotkit.ai/guides/model-context-protocol. It's okay if you don't want to; I will be explaining all the steps with concepts in detail.

docs

🎯 One line command to install MCP support using CLI

If you don't have an existing Next.js application, you can create one by using the npx create-next-app@latest command in the terminal.

create a nextjs project

Once your project is ready, the fastest way to integrate MCP support is by using the CopilotKit CLI. Just run the following command in your terminal.

npx copilotkit@latest init -m MCP

This command does a lot under the hood:

  • Installs all required CopilotKit dependencies
  • Sets up a ready-to-use interface for interacting with MCP servers
  • Adds components to integrate MCP and installs shadcn/ui for UI styling

It will guide you to install the required packages and recommend you use the Copilot Cloud to deploy (which requires no extra setup).

installing using CLI

installing using CLI

It will validate the configuration and detect if the existing Next.js app is valid. After that, you will get the option of choosing either a default project or mcp demo (which is a showcase project made by the CopilotKit team).

I've tried both with CLI, but we are going to use the default project for the scope of this guide.

default project

It will then prompt you to install shadcn/ui for built-in component styling.

Since we are using React 19, which is very new. Some libraries may not officially support it yet, and npm might show errors because of version conflicts (called peer dependency issues).

To fix that, the CLI suggests using --legacy-peer-deps, which tells npm to ignore those version warnings and continue installing anyway.

It's a workaround that usually works fine, especially for newer React versions still gaining support.

installing shadcn ui

installing shadcn ui

After installation is complete, you will see a summary of what's been added.

finally installed

You can start the server locally using npm run dev and navigate to http://localhost:3000/copilotkit to view your MCP-ready frontend.

final localhost copilotkit output

📧 Connecting to the Gmail MCP Server (Demo)

You will need to enter the SSE URL of the MCP server you prefer to use. You can find a list of 100+ available managed MCP servers at mcp.composio.dev.

Let's check the flow in brief. I'm using the Gmail server at mcp.composio.dev/gmail.

gmail mcp server

Copy the MCP server URL that you generated from the composio server page and paste it into the placeholder field (Enter MCP server URL) on the page. This is sensitive and for personal use only, so that's why I've blurred it.

blurred mcp server

I've given a prompt of sending an email to hi@anmolbaranwal.com with the subject working demo of copilotkit mcp and saying composio server works in the body of the email.

It will call the appropriate MCP server (if you have multiple of those) and it will accordingly use the correct action based on your prompt.

Since there is no active connection, it will first establish one (as shown in the above image). You will need to authenticate by copying the OAuth URL in the browser.

💡 It's a good idea to test with a dummy account first, especially while experimenting. Once you're satisfied, you can automate things with your primary account.

gmail auth

You will have to provide access to the server so it can take action based on your prompt.

action permissions

Once you're authenticated, you will see confirmation in the browser.

successful authentication

You just need to type done so that the agent can verify the active connection. The part that surprised me is that it still asks for approval before sending the email, which makes sure you're still in control.

approval

Upon approval, the agent proceeds to send the email.

email sent

Somehow it didn't provide the subject, but we received the email with a proper body response.

gmail mcp

Yay! 🎉 You have now completed an end-to-end integration with an MCP server using CopilotKit.

You can do the same with all the other MCP servers and create multi-step workflows.

Up next: We will build this flow manually from scratch to understand the underlying components better.

🎯 Setting up the whole integration from scratch

Let’s walk through how to set up the CopilotKit integration to use MCP servers from scratch. This will help you understand the architecture and flow end to end.

If you already have an existing Next.js project, skip to Step 2.

Step 1: Create a Next.js project with TypeScript

If you don’t have a frontend, you can create a new Next.js project with TypeScript and Tailwind CSS by using the following command.

npx create-next-app@latest 
create a nextjs project

This is what your project structure will look like. We will be using the latest Next.js version along with the App Router. I've added some more files (for my style preferences), which is something I do for every project, so it's not mandatory.

directory structure

🧠 You might notice there is no tailwind.config.js. That’s because, with Tailwind CSS V4, we can now customize styles directly in globals.css, and new next.js apps don't need the config file anymore.

Step 2: Install the CopilotKit package and set up the provider.

Install the necessary CopilotKit packages.

npm install @copilotkit/react-core @copilotkit/react-ui
  • @copilotkit/react-core provides the core context and logic to connect your React app with the CopilotKit backend and MCP servers.
  • @copilotkit/react-ui offers ready-made UI components like <CopilotChat /> to build AI chat or assistant interfaces quickly.
install the copilotkit packages

The <CopilotKit> component must wrap the Copilot-aware parts of your application. In most cases, it's best to place it around the entire app, like in app/layout.tsx.

import type { Metadata } from 'next'
import './globals.css'

import '@copilotkit/react-ui/styles.css'
import './globals.css'
import { CopilotKit } from '@copilotkit/react-core'

export const metadata: Metadata = {
  title: 'CopilotKit MCP Demo',
  description: 'CopilotKit MCP Demo',
}

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode
}>) {
  return (
    <html lang="en">
      <body
        className={
          'relative flex flex-col overflow-x-hidden font-sans antialiased'
        }
        suppressHydrationWarning
      >
        <CopilotKit publicApiKey="<replace_with_your_own>">
          {children}
        </CopilotKit>
      </body>
    </html>
  )
}

Replace <replace_with_your_own> with your API key from cloud.copilotkit.ai. It's free and recommended if you're building for production.

If you're interested in configuring your self-hosted runtime with MCP servers, please read the docs.

copilotkit cloud

Just click on Get Started, and you will find a public API key.

api key

Step 3: Create a component to configure the MCP server connection

Now we will create a helper component that connects your app to an MCP server. Create a new file at src\components\McpServerManager.tsx.

'use client'

import { useCopilotChat } from '@copilotkit/react-core'
import { useEffect } from 'react'

function McpServerManager() {
  const { setMcpServers } = useCopilotChat()

  useEffect(() => {
    setMcpServers([
      {
        // Try a sample MCP server at https://mcp.composio.dev/
        endpoint: 'your_mcp_sse_url',
      },
    ])
  }, [setMcpServers])

  return null
}

export default McpServerManager

This McpServerManager component connects your app to an MCP server.

  • When the component loads (useEffect runs), it tells CopilotKit which MCP server to connect to by providing the server URL.
  • The component itself renders nothing (return null), it's just setting up the connection in the background.

It configures your app to talk to your MCP server automatically when the page loads. You just need to set the specific MCP endpoint (your_mcp_sse_url) for tool access in the endpoint property.

Step 4: Add the chat interface.

The ChatInterface component creates the actual chat UI and makes sure the chat knows about the MCP server you want to connect to. Create a new file at src\components\ChatInterface.tsx.

'use client'

import { CopilotChat } from '@copilotkit/react-ui'
import McpServerManager from './McpServerManager'

export default function ChatInterface() {
  return (
    <div className="flex h-screen p-4">
      <McpServerManager />
      <CopilotChat
        instructions="You are a helpful assistant with access to MCP servers."
        className="w-full flex-grow rounded-lg"
      />
    </div>
  )
}

Here's a simple explanation of what's happening.

  • McpServerManager runs when the page loads and tells CopilotKit which MCP server to use.
  • CopilotChat displays the chat box where users can talk to the AI.
  • The instructions tell the AI what kind of assistant it should be.

Step 5: Visualize MCP Tool Calls (Optional).

To monitor tool calls triggered by the assistant, add a ToolRenderer component. This is completely optional but useful during development.

Create a new file at src\components\ToolRenderer.tsx.

'use client'

import {
  useCopilotAction,
  CatchAllActionRenderProps,
} from '@copilotkit/react-core'
import McpToolCall from './McpToolCall'

export function ToolRenderer() {
  useCopilotAction({
    /**
     * The asterisk (*) matches all tool calls
     */
    name: '*',
    render: ({ name, status, args, result }: CatchAllActionRenderProps<[]>) => (
      <McpToolCall status={status} name={name} args={args} result={result} />
    ),
  })
  return null
}

Then create McpToolCall.tsx (under the same components directory). This displays the tool name, status, arguments, and results in a collapsible UI.

It's a visual debug component that shows tool invocation details like what the assistant tried to do and what it got back.

/* eslint-disable @typescript-eslint/no-explicit-any */
'use client'

import * as React from 'react'

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)

  const classes = {
    container:
      'bg-white rounded-xl overflow-hidden w-full border-2 border-gray-200 shadow-md transition-all duration-200 hover:shadow-xl my-1',
    header:
      'p-4 flex items-center cursor-pointer group bg-gray-50 border-b border-gray-200',
    title: 'text-gray-900 font-semibold overflow-hidden text-ellipsis',
    statusContainer: 'ml-auto flex items-center gap-2',
    statusText: 'text-xs text-gray-700 font-medium mr-1',
    content: 'px-5 pb-5 pt-3 text-gray-800 font-mono text-xs',
    section: 'mb-4',
    sectionTitle:
      'text-gray-700 text-xs uppercase tracking-wider mb-2 font-sans font-bold',
    codeBlock:
      'whitespace-pre-wrap max-h-[200px] overflow-auto text-gray-900 bg-gray-50 p-3 rounded border border-gray-200',
    chevron: {
      base: 'text-gray-700 mr-2 transition-transform duration-200',
      open: 'rotate-90',
      hover: 'group-hover:text-gray-900',
    },
    contentWrapper: {
      base: 'overflow-hidden transition-all duration-300 ease-in-out',
      open: 'max-h-[600px] opacity-100',
      closed: 'max-h-0 opacity-0',
    },
  }

  // Status indicator colors
  const statusColors = {
    complete: 'bg-emerald-500 shadow-emerald-500/40',
    inProgress: 'bg-amber-500 shadow-amber-500/40',
    executing: 'bg-blue-500 shadow-blue-500/40',
  }

  // Simplified format function
  const format = (content: any): React.ReactNode => {
    if (!content) return null
    return typeof content === 'object' ? (
      <span>{JSON.stringify(content, null, 2)}</span>
    ) : (
      <span>{String(content)}</span>
    )
  }

  const getStatusColor = () => {
    const baseColor = statusColors[status].split(' ')[0]
    const shadowColor = statusColors[status].split(' ')[1]
    return `${baseColor} ${status === 'inProgress' || status === 'executing' ? 'animate-pulse' : ''} shadow-[0_0_10px] ${shadowColor}`
  }

  return (
    <div className={classes.container}>
      <div className={classes.header} onClick={() => setIsOpen(!isOpen)}>
        <ChevronRight isOpen={isOpen} chevronClasses={classes.chevron} />
        <span className={classes.title}>{name || 'MCP Tool Call'}</span>
        <div className={classes.statusContainer}>
          <span className={classes.statusText}>
            {status === 'complete'
              ? 'Completed'
              : status === 'inProgress'
                ? 'In Progress'
                : 'Executing'}
          </span>
          <div className={`h-3 w-3 rounded-full ${getStatusColor()}`} />
        </div>
      </div>

      <div
        className={`${classes.contentWrapper.base} ${isOpen ? classes.contentWrapper.open : classes.contentWrapper.closed}`}
      >
        <div className={classes.content}>
          <div className={classes.section}>
            <div className={classes.sectionTitle}>Name</div>
            <pre className={classes.codeBlock}>{name}</pre>
          </div>
          {args && (
            <div className={classes.section}>
              <div className={classes.sectionTitle}>Parameters</div>
              <pre className={classes.codeBlock}>{format(args)}</pre>
            </div>
          )}

          {status === 'complete' && result && (
            <div className={classes.section}>
              <div className={classes.sectionTitle}>Result</div>
              <pre className={classes.codeBlock}>{format(result)}</pre>
            </div>
          )}
        </div>
      </div>
    </div>
  )
}

const ChevronRight = ({
  isOpen,
  chevronClasses,
}: {
  isOpen: boolean
  chevronClasses: any
}) => {
  return (
    <svg
      width="16"
      height="16"
      viewBox="0 0 24 24"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      className={`${chevronClasses.base} ${isOpen ? chevronClasses.open : ''} ${chevronClasses.hover}`}
      stroke="currentColor"
      strokeWidth="2.5"
      strokeLinecap="round"
      strokeLinejoin="round"
    >
      <polyline points="9 18 15 12 9 6"></polyline>
    </svg>
  )
}

Step 6: Combining all the components.

After creating all the individual components, it's time to combine all of these under src/app/page.tsx.

'use client'

import { CopilotChat } from '@copilotkit/react-ui'
import McpServerManager from '../components/McpServerManager'
import { ToolRenderer } from '../components/ToolRenderer'

export default function Page() {
  return (
    <div className="flex h-screen p-4">
      <McpServerManager />
      <CopilotChat
        instructions="You are a helpful assistant with access to MCP servers."
        className="w-full flex-grow rounded-lg"
      />
      <ToolRenderer />
    </div>
  )
}

Congrats! 🎉 Your CopilotKit & MCP server setup is ready!

Step 7: Add MCP server endpoint

Now we just need to add the endpoint URL to connect to an external MCP server. This is done inside the McpServerManager component by passing the URL using the endpoint property like this.

setMcpServers([
  {
    // Try a sample MCP server at https://mcp.composio.dev/
    endpoint: 'your_mcp_sse_url',
  },
])

There are two easier ways to instantly connect to 100+ MCP servers:

1) Composio - Composio has fully managed servers, which eliminates the need for complex setups and comes with built-in Auth. Supports over 250+ tools and provides 20,000+ pre-built API actions to integrate without coding.

It can also operate locally or remotely. And provides better tool-calling accuracy which allows AI agents to interact smoothly with integrated apps.

composio

It also means less downtime and fewer maintenance problems. You can check the status at status.composio.dev/.

status composio

With each of the options, you will find the total active users, its current version, how recently it was updated, and all the available actions.

You will find instructions to install it using TypeScript, Python, and it supports Claude (MacOS), Windsurf (MacOS), and Cursor as MCP hosts.

2) Zapier - Zapier supports over 8,000 apps, but you can only make 80 tool calls per hour, which can be a limitation.

zapier

In case there are server issues, just use the other option.

🔧 Troubleshooting MCP servers

The easiest way to do that is to use clients like Cursor or Claude (where you just need to run an npx command in Cursor), and then you can test tool invocation by opening the chat.

If you're unsure whether your MCP server works:

⚡ Open the MCP server URL directly in your browser. If it returns a stream of data (usually JSON lines), it's working.
⚡ Check your browser’s DevTools for network or console errors.
⚡ Sometimes stale connections can cause silent failures, and restarting your dev server can solve this issue.

Step 8: ADD MCP endpoint and run the server.

You can run the server in the next.js app by using npm run dev. You should now see the chat interface.

ui interface of manual integration

The flow is very similar to the last one. I'm again using the Gmail server at https://mcp.composio.dev/gmail to help you understand the difference in results between setting up from scratch and doing it from the CLI.

gmail mcp server

After adding the endpoint, just provide the prompt, and you will be able to see the tool call since we used the ToolRenderer component.

component

Like before, it will ask for approval and send the email accordingly. I've verified that the email was successfully delivered to my inbox.

mcp server works

In the next section, we are going to take a complex example.

4. Building a working memory project to manage tasks & projects from tools like Linear.

Let's study a working memory project that will teach us how to build interactive, AI-powered chat applications that connect with external tools like Linear and Slack using MCP and CopilotKit.

Tech stack involved:

Before proceeding, please make sure you have Node.js (LTS version recommended).

Step 1: Clone and set up the repository.

For the sake of simplicity, we will clone the copilotkit mcp repository on GitHub.

git clone https://github.com/CopilotKit/copilotkit-mcp-demo.git
git clone

Please install the dependencies using npm install.

npm i

After that, we need to create a .env file under the root directory. Then add your OpenAI API key to the .env file. I've attached the docs link so it's easy to follow.

This will be the naming convention.

OPENAI_API_KEY= YOUR_API_KEY
openai api key

openai api key

You also need to place the API key from cloud.copilotkit.ai in the .env file. It's free and recommended if you're building for production.

This will be the naming convention.

NEXT_PUBLIC_COPILOT_CLOUD_API_KEY= YOUR_API_KEY

Just click on Get Started, and you will find a public API key.

api key

Step 2: Let's take a look at the project structure.

This is what the project structure looks like.

  • /src/app : Contains Next.js pages, layouts, and global styles.
  • /src/components : Includes reusable UI components and agent interfaces like Travel, Research, Chat, Map, and Sidebar.
  • /src/providers : Wraps global state providers for managing agent states.
  • /src/lib : Contains utility functions and configuration files.
  • /src/hooks : Custom React hooks for reusable logic.
  • /src/contexts : Provides React context for managing global state.
project structure

Step 3: Let's understand the codebase.

I won't cover the code of every component otherwise, this blog will be too long, so I will try to cover as much as possible with the concepts involved. Please check the repo and follow along to see how everything works together.

⚡ 1. The Entry is src/app/layout.tsx, which imports global styles, loads Google fonts, and then wraps everything in your top-level <Providers>.

⚡ 2. Providers (src/providers/Providers.tsx) is the component that sets up the global providers for the state, API access, and server configuration for the app.

"use client";

import { CoAgentsProvider } from "@/components/coagents-provider";
import McpServerManager from "@/components/McpServerManager";
import { ToolRenderer } from "@/components/ToolRenderer";
import { useLocalStorage } from "@/hooks/use-local-storage";
import { CopilotKit } from "@copilotkit/react-core";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import React from "react";

export interface Config {
  endpoint: string;
  serverName: string;
}

export interface ConfigContextType {
  config: Config[];
  setConfig: (config: Config[]) => void;
}

const queryClient = new QueryClient();
export const ServerConfigsContext = React.createContext<
  ConfigContextType | undefined
>(undefined);
export default function Providers({ children }: { children: React.ReactNode }) {
  const [mcpConfig] = useLocalStorage("mcpConfig", []);
  const [config, setConfig] = React.useState<Config[]>(mcpConfig || []);

  return (
    <ServerConfigsContext.Provider value={{ config, setConfig }}>
      <QueryClientProvider client={queryClient}>
        <CopilotKit
          publicApiKey={process.env.NEXT_PUBLIC_COPILOT_CLOUD_API_KEY}
        >
          <McpServerManager configs={config} />
          <ToolRenderer />
          {/* <MCPToolCall /> */}
          <CoAgentsProvider>{children}</CoAgentsProvider>
        </CopilotKit>
        <ReactQueryDevtools initialIsOpen={false} />
      </QueryClientProvider>
    </ServerConfigsContext.Provider>
  );
}

  • ServerConfigsContext : holds your list of configured “MCP” servers
  • QueryClientProvider : wires up React Query
  • <CopilotKit> : initializes the CopilotKit runtime (using your public API key)
  • <McpServerManager> : reads configs from context and tells CopilotKit which servers to use
  • <ToolRenderer> : catches all CopilotKit “tool calls” and renders them as MCPToolCall cards
  • <CoAgentsProvider> : placeholder for collating agent state (currently just passes through children)

⚡ 3. MCP Server Configuration

  • MCPConfigModal (src/components/mcp-config-modal.tsx) : a modal that lets you add/remove SSE or stdio servers, persists to localStorage, updates context & CopilotKit.
  • use-local-storage (src\hooks\use-local-storage.tsx) : a hook that persists these settings

I'm not including the code because it's very long.

⚡ 4. Chat UI

  • ChatWindow (src/components/chat-window.tsx) is just a styled wrapper around @copilotkit/react-ui’s <CopilotChat>
  • You pass it:
    • “instructions” (tells the AI to always go through MCP servers & gives it your current todo state)
    • labels, icons, spinner/stop buttons
"use client";
import { useTodo } from "@/contexts/TodoContext";
import { CopilotChat } from "@copilotkit/react-ui";
import "@copilotkit/react-ui/styles.css";
import { Loader2, RotateCw, SendIcon, Square } from "lucide-react";
import { FC } from "react";
import { Loader } from "./Loader";

export const ChatWindow: FC = () => {
  const { todos } = useTodo();
  return (
    <CopilotChat
      className="h-full flex flex-col"
      instructions={`Always use the MCP server to complete the task. You will be provided with a list of MCP servers. Use the appropriate MCP server to complete the task.
      To perform any actions over the todo task use the following data for manipulation ${JSON.stringify(
        todos
      )}`}
      labels={{
        title: "To-do Assistant",
        initial:
          "Hi! I'm your AI task assistant, powered by MCP servers that you can configure to help manage and break down your tasks. \n\nHow can I help you today?",
        placeholder: "Type your message here...",
        regenerateResponse: "Try another response",
      }}
      icons={{
        sendIcon: (
          <SendIcon className="w-4 h-4 hover:scale-110 transition-transform" />
        ),
        activityIcon: (
          <Loader
            texts={[
              "Thinking...",
              "Analyzing Your Query...",
              "Taking Action...",
            ]}
          />
        ),
        spinnerIcon: <Loader2 className="w-4 h-4 animate-spin" />,
        stopIcon: (
          <Square className="w-4 h-4 hover:text-red-500 transition-colors" />
        ),
        regenerateIcon: (
          <RotateCw className="w-4 h-4 hover:rotate-180 transition-transform duration-300" />
        ),
      }}
    />
  );
};

⚡ 5. Todo State & Actions

  • TodoContext (src/contexts/TodoContext.tsx) holds your in-memory list of todos & subtasks + all the CRUD/toggle functions
  • TodoApp (src/components/Todo.tsx) renders the UI for adding/toggling/deleting tasks and subtasks
  • Inside TodoApp you also register a suite of useCopilotAction hooks (ADD_TASK, ADD_SUBTASK, COMPLETE_TASK, etc.) and a useCopilotReadable for the JSON-encoded list.

When the AI decides to call one of those actions, CopilotKit will invoke your handlers, which update the same React context.

⚡ 6. Visual Representation

  • VisualRepresentation (src/components/VisualRepresentation.tsx)
    • Subscribes to the same todo context
    • Builds a set of “nodes” and “edges” whenever your todos change
    • Renders a tree view in React Flow, with custom ParentNode/ChildNode components (src/components/Nodes.tsx)
    • Clicking on a node toggles the corresponding task/subtask

⚡ 7. Tool Calls

  • ToolRenderer (src/components/ToolRenderer.tsx) installs a wildcard useCopilotAction listener
  • Every time CopilotKit emits a “tool call” (i.e., hits an MCP server), you render an MCPToolCall card (src/components/MCPToolCall.tsx) showing status, errors/results, etc.

⚡ 8. Canvas Layout

  • Canvas (src/components/canvas.tsx) stitches it all together:
    • Left pane: ChatWindow
    • Main area: two cards side by side (VisualRepresentation + TodoApp)
    • “MCP Servers” button that toggles the MCPConfigModal
    • Under the hood, it also calls useCopilotChatSuggestions to surface AI-driven suggestions

⚡ 9. UI Primitives & Utilities

  • src/components/ui/… – Radix-based buttons, cards, dialogs, etc., all styled with Tailwind
  • src/lib/utils.ts – a small “cn” helper for combining Tailwind classes
  • src/hooks – useLocalStorage, useIsMobile

🧠 Flow summary:

Here is the flow of all of the components.

1) You configure one or more MCP servers via the modal → they are saved in localStorage → then passed into CopilotKit through McpServerManager.

2) You type a message in the chat → CopilotKit routes it to the AI → the AI can “call” your registered actions (like ADD_TASK) through the MCP servers.

3) Then ToolRenderer visualizes each tool call → your action handlers update state in React context → both TodoApp and VisualRepresentation re-render with the new state → your chat stream continues.

I'm confident that if you open the codebase and learn about the flow, everything will make sense.

Step 4: Final demo

You can run the server locally using npm run dev and check the live demo app at copilotkit-mcp-demo.vercel.app.

official demo of copilotkit mcp

Star CopilotKit MCP Demo ⭐️

5. Some real-world examples with use cases.

Once you're comfortable with the basics, here are some advanced and creative ways you can use CopilotKit with MCP to build agentless, powerful workflows.

We can use chain tool interactions using MCP’s composable server endpoints.

✅ Multi-step workflow automation across APIs.

Let's suppose an example task: Fetch unresolved Linear issues, summarize them and send a report to the team via Slack.

The technical flow could be:

  • MCP Server 1 (Linear): GET issues in Linear using the Linear_get_linear_issue action.
linear issue
  • Local LLM (such as OpenAI): summarize the issue titles & descriptions using any API.
  • MCP Server 2 (Slack): Use the slack_message action to post a summary in the defined channel.
slack post message

You can define multi-step workflows via prompts, tools, and MCP servers.

✅ Semantic code review assistant.

We can build a simple code review assistant with custom logic.

Let's suppose an example task: Review this PR using our frontend checklist and comment inline on unoptimized code.

The technical flow could be:

  • GitHub MCP Server: fetches PR diff or file list.
  • Prompt Templates: Load checklist rules as structured prompts.
  • LLM (such as OpenAI): Run semantic analysis and generate inline feedback.
  • GitHub MCP: using an appropriate action like post_comment_on_pull_request to leave remarks.
github mcp server

✅ Contextual CRM assistant

We can build assistants that respond based on past threads and conversation history.

Let's suppose an example task: Find all unanswered emails from investors and suggest follow-ups based on the last threads.

The technical flow could be:

  • Gmail MCP: GET a list of threads from Gmail using gmail_list_threads.
  • LLM + Context Chunking: Summarize recent threads using OpenAI function calls.
  • Draft Reply: Generate responses via LLM.
  • Gmail MCP: Use gmail_send_email to send the draft.
gmail mcp server

You can extend this with Notion or Airtable MCPs to log interactions or add reminders like: Remind me next week if no reply.

These are just starting points. MCP lets you automate almost any multi-tool workflow, entirely through prompts (as logic).

Many developers assumed that integrating MCP was too early or too complex. Hopefully, CopilotKit’s built-in support proves otherwise.

So go build something wild. Make it useful. And let the world see what's possible when workflows meet context.

Have a great day! Until next time :)

You can checkmy work at anmolbaranwal.com.Thank you for reading! 🥰
twitter
github
linkedin

Follow CopilotKit on Twitter and say hi, and if you'd like to build something cool, join the Discord community.

Subscribe to the newsletter

Get notified of the latest news and updates.