Cursor IDE умеет генерировать код, рефакторить, объяснять и дебажить. Но по умолчанию он видит только файлы в вашем проекте. Если нужно, чтобы агент сходил в Google Trends, проверил задачи в Jira или прочитал что-то из Notion, приходится копировать данные руками и вставлять в чат. Агент получается не особо автономным, каждый шаг требует вашего участия.
MCP даёт агенту инструменты — функции, которые тот вызывает сам, когда ему нужны внешние данные. Вместо «вот тебе CSV, проанализируй» вы пишете «проанализируй тренды по запросу X», и агент сам вызывает нужную функцию, получает данные и работает с ними.
Как устроен MCP
MCP — открытый протокол, который стандартизирует подключение инструментов к языковым моделям. До него каждый фреймворк делал по-своему: у LangChain свои tools, у OpenAI function calling, у Cursor свой формат. MCP ввёл единый стандарт.
MCP-сервер — программа, которая запускается локально и экспортирует инструменты через стандартный протокол. Каждый инструмент имеет имя, описание и схему параметров. Cursor видит список инструментов и вызывает нужный, когда считает это полезным для ответа.
Без MCP агент знает только файлы в проекте и ваше сообщение. С MCP он может запросить данные из внешнего API, прочитать документ из базы знаний, выполнить SQL-запрос, вызвать скрипт. Агент сам решает, какой инструмент вызвать, формирует запрос, получает ответ и продолжает работу.
Подключаем MCP-сервер к Cursor
Конфигурация живёт в .cursor/mcp.json в корне проекта:
{ "mcpServers": { "google-trends": { "command": "python", "args": ["-m", "trends_server"], "env": { "SERPAPI_KEY": "your-key-here" } } } }
Cursor запускает сервер как дочерний процесс, общается через stdin/stdout по протоколу MCP. После перезапуска Cursor видит инструменты сервера и может их использовать.
Можно подключить несколько серверов одновременно:
{ "mcpServers": { "google-trends": { "command": "python", "args": ["-m", "trends_server"] }, "postgres": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-postgres"], "env": { "POSTGRES_URL": "postgresql://user:pass@localhost:5432/mydb" } }, "filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/docs"] } } }
Три сервера: Google Trends для аналитики, PostgreSQL для запросов к базе, файловая система для чтения документов за пределами проекта. Агент видит инструменты всех трёх и комбинирует их в одном запросе.
Пишем MCP-сервер для Google Trends
MCP-сервер на Python через библиотеку mcp:
from mcp.server.fastmcp import FastMCP from pytrends.request import TrendReq import json mcp = FastMCP("google-trends") pytrends = TrendReq(hl="ru", tz=180) # русский язык, московское время @mcp.tool() def get_interest_over_time(query: str, timeframe: str = "today 3-m") -> str: """Получить динамику интереса к запросу за период. Аргументы: query: поисковый запрос (например 'machine learning') timeframe: период ('today 3-m', 'today 12-m', '2024-01-01 2024-12-31') Возвращает JSON с данными по неделям. """ pytrends.build_payload([query], timeframe=timeframe) df = pytrends.interest_over_time() if df.empty: return json.dumps({"error": f"Нет данных для '{query}'"}) # Убираем колонку isPartial, она мешает анализу if "isPartial" in df.columns: df = df.drop(columns=["isPartial"]) return df.to_json(date_format="iso") @mcp.tool() def get_related_queries(query: str) -> str: """Получить связанные запросы: топ (самые частые) и растущие (с наибольшим ростом). Аргументы: query: поисковый запрос Растущие запросы с пометкой 'Breakout' выросли более чем на 5000%. """ pytrends.build_payload([query], timeframe="today 3-m") related = pytrends.related_queries() result = {} for key in related: rising = related[key].get("rising") top = related[key].get("top") result[key] = { "rising": rising.to_dict() if rising is not None else {}, "top": top.to_dict() if top is not None else {}, } return json.dumps(result, ensure_ascii=False, default=str) @mcp.tool() def compare_queries(queries: list[str], timeframe: str = "today 3-m") -> str: """Сравнить до 5 запросов по динамике интереса. Аргументы: queries: список запросов для сравнения (максимум 5) timeframe: период Значения нормализованы: 100 = пик интереса, остальные относительно него. """ if len(queries) > 5: return json.dumps({"error": "Максимум 5 запросов"}) pytrends.build_payload(queries, timeframe=timeframe) df = pytrends.interest_over_time() if "isPartial" in df.columns: df = df.drop(columns=["isPartial"]) return df.to_json(date_format="iso") @mcp.tool() def get_trending_searches(country: str = "russia") -> str: """Получить сегодняшние трендовые поисковые запросы в стране. Аргументы: country: страна ('russia', 'united_states', 'germany') """ trending = pytrends.trending_searches(pn=country) return trending.to_json(force_ascii=False) if __name__ == "__main__": mcp.run(transport="stdio")
Четыре инструмента: динамика интереса, связанные запросы, сравнение, трендовые запросы дня. Docstring каждого инструмента подробный — Cursor показывает его агенту как описание, и чем точнее описание, тем лучше агент понимает, когда какой инструмент вызвать.
Устанавливаем:
pip install mcp pytrends
Прописываем в .cursor/mcp.json, перезапускаем Cursor. Пишем в чат: «Проанализируй тренды по "AI coding assistant", найди растущие запросы и сравни топ-3 между собой». Cursor сам вызовет get_interest_over_time, потом get_related_queries, выберет три самых растущих, вызовет compare_queries и выдаст анализ. Один промпт, три вызова инструментов, ноль копирования.
MCP-сервер для своей базы данных
Допустим, у вас PostgreSQL с метриками продукта. Можно использовать готовый @modelcontextprotocol/server-postgres, а можно написать свой с контролем над тем, какие запросы допустимы:
from mcp.server.fastmcp import FastMCP import asyncpg import json mcp = FastMCP("product-metrics") DB_URL = "postgresql://readonly:pass@localhost:5432/analytics" @mcp.tool() async def get_daily_signups(days: int = 30) -> str: """Получить количество регистраций по дням за последние N дней.""" conn = await asyncpg.connect(DB_URL) try: rows = await conn.fetch(""" SELECT date_trunc('day', created_at) as day, count(*) as signups FROM users WHERE created_at > now() - interval '$1 days' GROUP BY 1 ORDER BY 1 """, days) return json.dumps([{"day": str(r["day"].date()), "signups": r["signups"]} for r in rows]) finally: await conn.close() @mcp.tool() async def get_feature_usage(feature_name: str, days: int = 7) -> str: """Получить статистику использования фичи: DAU, количество событий, среднее на пользователя.""" conn = await asyncpg.connect(DB_URL) try: rows = await conn.fetch(""" SELECT date_trunc('day', timestamp) as day, count(distinct user_id) as dau, count(*) as events, round(count(*)::numeric / nullif(count(distinct user_id), 0), 1) as avg_per_user FROM events WHERE feature = $1 AND timestamp > now() - interval '$2 days' GROUP BY 1 ORDER BY 1 """, feature_name, days) return json.dumps([dict(r) for r in rows], default=str) finally: await conn.close() @mcp.tool() async def get_retention(cohort_month: str) -> str: """Получить retention когорты по месяцам. cohort_month в формате '2026-01'.""" conn = await asyncpg.connect(DB_URL) try: rows = await conn.fetch(""" SELECT months_since_signup, count(distinct user_id) as active_users, round(100.0 * count(distinct user_id) / first_value(count(distinct user_id)) over (order by months_since_signup), 1) as retention_pct FROM user_activity_monthly WHERE cohort = $1 GROUP BY 1 ORDER BY 1 """, cohort_month) return json.dumps([dict(r) for r in rows], default=str) finally: await conn.close() if __name__ == "__main__": mcp.run(transport="stdio")
Подключение через read-only пользователя БД — агент может читать, но не может менять данные. В конфигурации:
{ "mcpServers": { "product-metrics": { "command": "python", "args": ["-m", "metrics_server"], "env": { "DATABASE_URL": "postgresql://readonly:pass@localhost:5432/analytics" } } } }
Теперь в Cursor можно писать: «Покажи retention январской когорты и сравни с использованием фичи onboarding за последний месяц». Агент вызовет get_retention("2026-01") и get_feature_usage("onboarding", 30), получит данные и проанализирует корреляцию.
Связка с LangGraph: многошаговый агент
Cursor с MCP хорош для разовых запросов. LangGraph описывает граф, где каждый узел может вызывать инструменты. MCP-инструменты подключаются через ToolNode:
from typing import TypedDict, Annotated from langgraph.graph import StateGraph, START, END from langgraph.prebuilt import ToolNode from langchain_anthropic import ChatAnthropic from langchain_core.tools import tool # Инструменты (те же, что в MCP-сервере, но как LangChain tools) @tool def get_trends(query: str) -> str: """Получить данные Google Trends.""" pytrends.build_payload([query], timeframe="today 3-m") return pytrends.interest_over_time().to_json() @tool def get_related(query: str) -> str: """Получить связанные запросы.""" pytrends.build_payload([query], timeframe="today 3-m") related = pytrends.related_queries() rising = related[query].get("rising") return rising.to_json() if rising is not None else "{}" @tool def compare(queries: list[str]) -> str: """Сравнить до 5 запросов.""" pytrends.build_payload(queries[:5], timeframe="today 3-m") return pytrends.interest_over_time().to_json() tools = [get_trends, get_related, compare] # Состояние графа class State(TypedDict): messages: list iteration: int # LLM с инструментами llm = ChatAnthropic(model="claude-sonnet-4-20250514").bind_tools(tools) def analyst(state: State) -> dict: """Узел-аналитик: решает, какой инструмент вызвать.""" response = llm.invoke(state["messages"]) return {"messages": state["messages"] + [response]} def should_continue(state: State) -> str: """Условное ребро: есть ли вызовы инструментов?""" last = state["messages"][-1] if hasattr(last, "tool_calls") and last.tool_calls: return "tools" return "end" # Сборка графа workflow = StateGraph(State) workflow.add_node("analyst", analyst) workflow.add_node("tools", ToolNode(tools)) workflow.add_edge(START, "analyst") workflow.add_conditional_edges("analyst", should_continue, {"tools": "tools", "end": END}) workflow.add_edge("tools", "analyst") # после вызова инструмента — обратно к аналитику agent = workflow.compile() # Запуск result = agent.invoke({ "messages": [{ "role": "user", "content": "Найди растущие темы в области 'AI coding' и сравни топ-3" }], "iteration": 0, })
Граф работает в цикле: аналитик решает, какой инструмент вызвать, ToolNode вызывает, результат возвращается аналитику, он решает, нужно ли ещё, и так пока не будет достаточно данных для ответа.
Три уровня использования одних и тех же инструментов. Cursor с MCP для быстрых вопросов в чате. LangGraph с теми же инструментами для многошаговых сценариев. Cursor как IDE для разработки и отладки самого агента.
MCP не делает агента умнее, он делает его полезнее. Модель та же, но вместо работы в вакууме она получает доступ к реальным данным: трендам, метрикам, базе, документам. Написать свой MCP-сервер — дело на пару часов, а эффект ощутимый: один промпт вместо пяти минут копирования данных из разных источников. Если добавить сверху LangGraph, получается агент, который не просто отвечает на вопрос, а проводит многошаговый анализ и сам решает, когда копнуть глубже.
Если у вас есть чем поделиться, пишите в комментариях.

В продолжение темы сегодня вечером, 6 мая в 20:00, пройдет бесплатный открытый урок на тему «LangGraph + MCP в Cursor IDE: создаем автономного агента для глубокого анализа Google Trends».
На занятии разберем, как связать LangGraph, MCP и Cursor IDE в одном прикладном сценарии: от подключения инструментов до агента, который ищет аномалии в Google Trends, сравнивает запросы и помогает находить темы с быстрым ростом интереса. Заодно можно будет посмотреть на формат обучения, познакомиться с экспертом курса «Разработка ИИ-агентов» Артёмом Ревой и задать вопросы. Записаться на урок.
Полный список бесплатных уроков мая смотрите в дайджесте.
