MCP на практике: как построить инструменты для AI-агентов по стандарту Model Context Protocol
USB для AI-агентов: зачем нужен MCP
Представьте, что каждый принтер требует собственного уникального кабеля. Именно в такой ситуации находилась индустрия AI-агентов до появления MCP. Каждый агент реализовывал собственную интеграцию с каждым инструментом. N агентов × M инструментов = N×M уникальных интеграций. Это не масштабируется.
Model Context Protocol (MCP) — это открытый стандарт, созданный Anthropic и переданный в Linux Foundation, который решает эту проблему. MCP определяет единый протокол взаимодействия между AI-агентами (клиентами) и внешними инструментами (серверами). Один стандарт — и любой агент может работать с любым инструментом.
Цифры говорят сами за себя: 97 миллионов загрузок SDK в месяц, более 10 000 активных серверов, и первоклассная поддержка со стороны ChatGPT, Claude, Cursor, Gemini, Microsoft Copilot и VS Code. MCP — один из самых быстрорастущих открытых стандартов в истории AI.
Архитектура MCP: три слоя
MCP построен на клиент-серверной архитектуре с тремя ключевыми компонентами:
1. Host (хост)
Приложение, в котором работает AI-агент: Claude Desktop, VS Code, Cursor, или ваше собственное приложение. Хост управляет жизненным циклом MCP-клиентов и обеспечивает безопасность.
2. Client (клиент)
MCP-клиент создаётся хостом и поддерживает соединение 1:1 с конкретным MCP-сервером. Клиент отвечает за обнаружение доступных инструментов, отправку запросов и обработку ответов.
3. Server (сервер)
MCP-сервер предоставляет три типа возможностей:
- Tools (инструменты) — функции, которые агент может вызывать: запросы к базе данных, отправка сообщений, работа с файлами
- Resources (ресурсы) — данные, доступные для чтения: файлы, записи из базы данных, конфигурации
- Prompts (промпты) — шаблоны промптов, которые сервер предлагает клиенту для типовых задач
┌─────────────────────────────────────────┐
│ Host (Claude, VS Code) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │Client A │ │Client B │ │Client C │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
└───────┼────────────┼────────────┼───────┘
│ │ │
┌────▼────┐ ┌────▼────┐ ┌───▼─────┐
│ Server │ │ Server │ │ Server │
│ (DB) │ │ (Slack) │ │ (Files) │
└─────────┘ └─────────┘ └─────────┘
Транспорт: как клиент и сервер общаются
MCP поддерживает два основных транспортных механизма:
stdio (Standard I/O)
Клиент запускает серверный процесс и общается с ним через stdin/stdout. Идеально для локальных инструментов — Claude Desktop и VS Code используют именно этот режим. Минимальная задержка, нулевая конфигурация сети.
Streamable HTTP
Новый транспорт, заменивший SSE (Server-Sent Events). Поддерживает полноценную двустороннюю коммуникацию по HTTP. Подходит для облачных деплоев — AWS Lambda, Google Cloud Run, любая serverless инфраструктура. Работает через корпоративные прокси и файрволы.
// stdio — локальный сервер
{
"mcpServers": {
"weather": {
"command": "node",
"args": ["./weather-server/index.js"]
}
}
}
// Streamable HTTP — удалённый сервер
{
"mcpServers": {
"analytics": {
"url": "https://analytics.example.com/mcp"
}
}
}
Пишем MCP-сервер на TypeScript
Создадим практический MCP-сервер — менеджер задач, который позволяет AI-агенту работать с to-do списком.
Шаг 1: Инициализация проекта
mkdir mcp-todo-server
cd mcp-todo-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
npx tsc --init
Шаг 2: Определяем сервер и инструменты
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// Хранилище задач (в продакшне — база данных)
interface Todo {
id: string;
title: string;
done: boolean;
createdAt: string;
}
const todos: Map<string, Todo> = new Map();
// Инициализируем MCP-сервер
const server = new McpServer({
name: "todo-manager",
version: "1.0.0",
});
// Инструмент: добавить задачу
server.tool(
"add_todo",
"Add a new todo item",
{ title: z.string().describe("The todo item title") },
async ({ title }) => {
const id = crypto.randomUUID();
const todo: Todo = {
id,
title,
done: false,
createdAt: new Date().toISOString(),
};
todos.set(id, todo);
return {
content: [{ type: "text", text: \`Created todo: \${title} (id: \${id})\` }],
};
}
);
// Инструмент: получить все задачи
server.tool(
"list_todos",
"List all todo items",
{},
async () => {
const items = Array.from(todos.values());
if (items.length === 0) {
return { content: [{ type: "text", text: "No todos yet." }] };
}
const list = items
.map((t) => \`[\${t.done ? "✓" : " "}] \${t.title} (id: \${t.id})\`)
.join("\\n");
return { content: [{ type: "text", text: list }] };
}
);
// Инструмент: отметить задачу как выполненную
server.tool(
"complete_todo",
"Mark a todo item as completed",
{ id: z.string().describe("The todo item ID to complete") },
async ({ id }) => {
const todo = todos.get(id);
if (!todo) {
return { content: [{ type: "text", text: "Todo not found." }] };
}
todo.done = true;
return {
content: [{ type: "text", text: \`Completed: \${todo.title}\` }],
};
}
);
// Запускаем через stdio
const transport = new StdioServerTransport();
await server.connect(transport);
Шаг 3: Конфигурация для Claude Desktop
{
"mcpServers": {
"todo": {
"command": "node",
"args": ["--loader", "ts-node/esm", "./src/index.ts"]
}
}
}
После этого Claude сможет создавать, просматривать и завершать задачи прямо в диалоге. AI-агент увидит доступные инструменты через MCP discovery и будет использовать их контекстно.
Пишем MCP-сервер на Python
Python-версия ещё проще благодаря FastMCP — высокоуровневому API, который использует type hints и docstrings для автоматической генерации описания инструментов.
from mcp.server.fastmcp import FastMCP
from datetime import datetime
import uuid
# Инициализируем сервер
mcp = FastMCP("todo-manager")
# Хранилище
todos: dict[str, dict] = {}
@mcp.tool()
async def add_todo(title: str) -> str:
"""Add a new todo item.
Args:
title: The todo item title
"""
todo_id = str(uuid.uuid4())[:8]
todos[todo_id] = {
"title": title,
"done": False,
"created_at": datetime.now().isoformat(),
}
return f"Created todo: {title} (id: {todo_id})"
@mcp.tool()
async def list_todos() -> str:
"""List all todo items."""
if not todos:
return "No todos yet."
lines = []
for tid, t in todos.items():
status = "✓" if t["done"] else " "
lines.append(f"[{status}] {t['title']} (id: {tid})")
return "\\n".join(lines)
@mcp.tool()
async def complete_todo(todo_id: str) -> str:
"""Mark a todo item as completed.
Args:
todo_id: The ID of the todo item to complete
"""
if todo_id not in todos:
return "Todo not found."
todos[todo_id]["done"] = True
return f"Completed: {todos[todo_id]['title']}"
if __name__ == "__main__":
mcp.run(transport="stdio")
Запуск: uv run python todo_server.py или через конфигурацию Claude Desktop.
Resources и Prompts: не только инструменты
MCP — это не только tools. Два дополнительных типа возможностей делают протокол по-настоящему мощным.
Resources — предоставляем данные
Resources позволяют серверу экспонировать данные для чтения. Идеально для файлов, записей из базы данных, конфигураций.
// Статический ресурс
server.resource(
"config",
"config://app",
async (uri) => ({
contents: [{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(appConfig),
}],
})
);
// Динамический ресурс с шаблоном
server.resource(
"user-profile",
new ResourceTemplate("users://{userId}/profile", { list: undefined }),
async (uri, { userId }) => ({
contents: [{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(await getUser(userId)),
}],
})
);
Prompts — переиспользуемые шаблоны
Серверы могут предоставлять шаблоны промптов для типовых задач:
server.prompt(
"review-code",
{ language: z.string(), code: z.string() },
({ language, code }) => ({
messages: [{
role: "user",
content: {
type: "text",
text: \`Review this \${language} code for bugs and improvements:\\n\\n\${code}\