Skip to main content
Model Context Protocol (MCP) is an open standard that allows your agents to connect securely to external tools and data sources. Think of MCP as a “universal connector” for AI - it standardizes how language models interact with various systems like databases, APIs, file systems, and custom tools. MCP transforms your agents from isolated models into powerful assistants that can access real-time data, perform actions, and interact with your entire digital ecosystem through a single, standardized protocol.

Example: Using filesystem MCP

Filesystem is a simple MCP that allows the LLM to execute disk based tools on your server. For example, list files, read file, write file etc. In this guide, we will integrate it with <C1Chat /> to create a conversational agent that can answer questions about your filesystem.

Setting up the MCP Client

First, let’s install the necessary dependencies to work with MCP in your C1 application.
You’ll need to install the MCP client library and any specific MCP servers you want to use. For this example, we’ll use a filesystem MCP server.
cli
> npm install @modelcontextprotocol/sdk
> pip install mcp

Create an MCP client integration

Now let’s create the MCP client using the @modelcontextprotocol/sdk package. This implementation connects to a filesystem MCP server and handles tool execution.
app/api/chat/mcp.ts
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import OpenAI from "openai";

export class MCPClient {
  private mcp: Client;
  private transport: StdioClientTransport | null = null;
  public tools: OpenAI.ChatCompletionTool[] = [];

  constructor() {
    this.mcp = new Client({
      name: "c1-chat-mcp-client",
      version: "1.0.0"
    });
  }

  async connect() {
    // Connect to filesystem MCP server (no authentication required)
    const command = "npx";
    const args = [
      "-y",
      "@modelcontextprotocol/server-filesystem@latest",
      process.cwd(),
    ];

    this.transport = new StdioClientTransport({
      command,
      args,
    });

    await this.mcp.connect(this.transport);

    // List available tools from the MCP server
    const toolsResult = await this.mcp.listTools();
    this.tools = toolsResult.tools.map((tool) => ({
      type: "function" as const,
      function: {
          name: tool.name,
          description: tool.description,
          parameters: tool.inputSchema,
      },
    }));
  }

  async runTool({
    tool_call_id,
    name,
    args,
  }: {
    tool_call_id: string;
    name: string;
    args: Record<string, unknown>;
  }) {
    try {
      const result = await this.mcp.callTool({
        name,
        arguments: args,
      });

      return {
        tool_call_id,
        role: "tool" as const,
        content: JSON.stringify(result.content),
      };
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : "Unknown error";
      return {
        tool_call_id,
        role: "tool" as const,
        content: JSON.stringify({
          error: `Tool call failed: ${errorMessage}`,
        }),
      };
    }
  }

  async disconnect() {
    if (this.transport) {
      await this.transport.close();
    }
  }
}
import os
from typing import Any, Dict, List

from mcp.client import Client
from mcp.client.stdio import StdioClientTransport


class MCPClient:
    def __init__(self) -> None:
        self._client = Client(name="c1-chat-mcp-client", version="1.0.0")
        self._transport: StdioClientTransport | None = None
        self.tools: List[Dict[str, Any]] = []

    async def connect(self) -> None:
        # Connect to filesystem MCP server (no authentication required)
        command = "npx"
        args = [
            "-y",
            "@modelcontextprotocol/server-filesystem@latest",
            os.getcwd(),
        ]

        self._transport = StdioClientTransport(command=command, args=args)
        await self._client.connect(self._transport)

        # List available tools from the MCP server and map to OpenAI tool schema
        tools_result = await self._client.list_tools()
        self.tools = [
            {
                "type": "function",
                "function": {
                    "name": tool.name,
                    "description": tool.description or "",
                    "parameters": tool.inputSchema,
                    "strict": True,
                },
            }
            for tool in tools_result.tools
        ]

    async def run_tool(self, *, tool_call_id: str, name: str, args: Dict[str, Any]) -> Dict[str, Any]:
        try:
            result = await self._client.call_tool(name=name, arguments=args)
            return {
                "tool_call_id": tool_call_id,
                "role": "tool",
                "content": result.content,
            }
        except Exception as e:  # noqa: BLE001
            return {
                "tool_call_id": tool_call_id,
                "role": "tool",
                "content": {"error": f"Tool call failed: {e}"},
            }

    async def disconnect(self) -> None:
        if self._transport is not None:
            await self._transport.close()

Integrate MCP with your C1 agent

Now let’s update your chat route to use the streamlined MCP integration from the thesysdev examples. This approach uses OpenAI’s runTools method for automatic tool execution. Install the dependencies for streaming
cli
> npm install @crayonai/stream
> # no package required
app/api/chat/route.ts
import { NextRequest, NextResponse } from "next/server";
import OpenAI from "openai";
import { transformStream } from "@crayonai/stream";
import { DBMessage, getMessageStore } from "./messageStore";
import { MCPClient } from "./mcp";
import { JSONSchema } from "openai/lib/jsonschema.mjs";

// Initialize MCP client
const mcpClient = new MCPClient();

interface RequestBody {
  prompt: DBMessage;
  threadId: string;
  responseId: string;
}

async function ensureMCPConnection(): Promise<void> {
  if (mcpClient.tools.length === 0) {
    await mcpClient.connect();
  }
}

export async function POST(req: NextRequest): Promise<NextResponse> {
  const { prompt, threadId, responseId } = (await req.json()) as RequestBody;

  const client = new OpenAI({
    baseURL: "https://api.thesys.dev/v1/embed/",
    apiKey: process.env.THESYS_API_KEY,
  });


  // Ensure MCP connection is established
  await ensureMCPConnection();

  const llmStream = await client.beta.chat.completions.runTools({
    model: "c1/anthropic/claude-sonnet-4/v-20251230",
    messages: [
      ...messages,
      { role: "user", content: prompt }
    ],
    tools: mcpClient.tools.map((tool) => ({
      type: "function",
      function: {
        name: tool.function.name,
        description: tool.function.description || "",
        parameters: tool.function.parameters as unknown as JSONSchema,
        parse: JSON.parse,
        function: async (args: unknown) => {
          const results = await mcpClient.runTool({
            tool_call_id: tool.function.name + Date.now().toString(),
            name: tool.function.name,
            args: args as Record<string, unknown>,
          });
          return results.content;
        },
      },
    })),
    stream: true,
  });

  const responseStream = transformStream(
    llmStream,
    (chunk) => {
      return chunk.choices[0].delta.content;
    },
  ) as ReadableStream<string>;

  return new NextResponse(responseStream, {
    headers: {
      "Content-Type": "text/event-stream",
      "Cache-Control": "no-cache, no-transform",
      Connection: "keep-alive",
    },
  });
}
import os
import json
from typing import Any, Dict, List

from fastapi import FastAPI
from pydantic import BaseModel
from openai import OpenAI

from mcp import MCPClient


app = FastAPI()

client = OpenAI(
    api_key=os.environ.get("THESYS_API_KEY"),
    base_url="https://api.thesys.dev/v1/embed",
)

mcp_client = MCPClient()


class ChatRequest(BaseModel):
    prompt: str
    history: List[Dict[str, Any]] = []


async def ensure_mcp_connection() -> None:
    if not mcp_client.tools:
        await mcp_client.connect()


@app.post("/chat")
async def chat(req: ChatRequest) -> Dict[str, Any]:
    await ensure_mcp_connection()

    messages: List[Dict[str, Any]] = [
        *req.history,
        {"role": "user", "content": req.prompt},
    ]

    # First request with available tools from MCP
    completion = client.chat.completions.create(
        model="c1/anthropic/claude-sonnet-4/v-20251230",
        messages=messages,
        tools=mcp_client.tools,
    )

    # Handle tool calls loop until the model returns a final answer
    while True:
        choice = completion.choices[0]
        message = choice.message
        tool_calls = message.tool_calls or []

        if not tool_calls:
            return message.content

        # Append assistant message that requested tools
        messages.append(
            {
                "role": "assistant",
                "content": message.content or "",
                "tool_calls": [
                    {
                        "id": tc.id,
                        "type": "function",
                        "function": {
                            "name": tc.function.name,
                            "arguments": tc.function.arguments,
                        },
                    }
                    for tc in tool_calls
                ],
            }
        )

        # Execute tools (via MCP) and append results
        for tc in tool_calls:
            args = json.loads(tc.function.arguments or "{}")
            tool_result = await mcp_client.run_tool(
                tool_call_id=tc.id,
                name=tc.function.name,
                args=args,
            )
            messages.append(
                {
                    "role": "tool",
                    "tool_call_id": tool_result["tool_call_id"],
                    "content": json.dumps(tool_result["content"]),
                }
            )

        # Ask the model again with tool results
        completion = client.chat.completions.create(
            model="c1/anthropic/claude-sonnet-4/v-20251230",
            messages=messages,
            tools=mcp_client.tools,
        )

Test your MCP-enabled agent

Your agent now has access to powerful filesystem operations through MCP! You can test it with prompts like:
  • File operations: “Create a new file called ‘notes.txt’ with today’s meeting summary”
  • Directory browsing: “List all the files in the current directory”
  • File reading: “Read the contents of package.json and summarize the project dependencies”
  • File searching: “Find all TypeScript files in the src directory”
mcp-integration

View Source Code

See the full code with integrations for thinking states and error handling.