MCP: JSON-RPC паведамленні, серверы FastMCP і трэс ReAct
Выпалое запрашэнне Model Context Protocol ад інструментаў/спіса да фінальнай адпаведзі, з серверамі FastMCP, агентам LangGraph ReAct і трэкерам, які фіксуе кожны крок.
Крыніцы: How MCP Works: A Deep Dive with Code
Model Context Protocol, або MCP, — гэта адаптаваны стандарт адз Інтрапік, які дазваляе AI-мадэлям, такім як Claude, выходзіць на звязок з зовнішнімі інструментамі, джэрамі даных і сервісамі. Хорашы аналагія — універсальны штык: замест таго, каб пісаць індывідуальную інтэграцыю для кожнага інструмента, існуе стандартны спосаб для таго, каб мадэль могла весты справы з зовнішнімі сістэмамі.
Складовы
Структуру складаюць тры элементы. MCP-хост — гэта прыкладнасць, якую фактычна вікорыстоўвае чалавек, будзь то вікно чату чы інтэграваная асяроддзе разработкі, і ён мае кліента, які керуе з’яўленнем зв’язку. MCP-пратакол — гэта стандартызаваны шар камунікаціі межа хостам і серверамі. Кожны MCP-сервер падключаецца да аднаго тыпу рэсурса, напрыклад, базы даных, зовнішняго API чы сераўіса для выканання коду, і выступае як мост да яго.
Што відбываецца пад час выканання
У загальных рысах запіт праходзіць праз следуючыя этапы:
- Пользоватар адрэсуе запіт хосту.
- Хост запытае серверы MCP, якія інструменты яны прысвячаюць.
- Серверы вяртаюць своія спісы інструментаў.
- Хост адправляе запіт разам з спісам інструментаў да LLM.
- LLM выбірае інструмент і паведамляе пра гэта хоста.
- Хост вызывае правы сервер MCP, каб ўжо выканаць інструмент.
- Сервер выкананае інструмент і вяртае рэзультат.
- Хост перадае гэты рэзультат знову да LLM.
- LLM пішае заканчычную адпаведзь, а хост яе дастае.
Сервер адмініструе тры відэныя рэчы. Інструменты — гэта функцыі, якія можа выклікваць мадэль, такія як search_web, query_database чы send_email. Рэсурсы — гэта чытальныя даны, якія хост можа запрашваць як кантэкст, напрыклад, файлы, запісі базы дадзеных, запісы календара і дакументы, якія мадэль можа чытаць, але не выкананыць. Прампты — гэта загадзя напісаныя шаблоны, якія прысутныя на серверэ. Напрыклад, сервер GitHub можа запростаць шаблон „Падсумаваць гэты PR“, які хост можа паказаць корыстніку.
У сутнасці MCP — гэта проста стандартызаваны спосаб для хоста ведаць дыялог з серверам. Ён фіксуе фармат паведамленняў і правіла дыялогу, і нічога больш. Акрамя таго, ён візначае чатыры аспекты.
- Фармат запыткаў. Усё выконваецца па стандарту JSON-RPC 2.0. Запыт складаецца з
id,methodіparams, а адпаведны адпаведзь павтараеidразам зresultабоerror; такім чынам обе стороны выкорыстоўваюць аднойчыны фармат. - Доступныя методы. Пратакол вялічыць фіксаваныя варыянты запыткаў, якія хост можа адправіць, напрыклад
tools/listдля аднаработкі інфармацыі,tools/callдля запуску інструмента,resources/readдля запрашэння дадзэнняў іprompts/getдля загаворкі шаблона. Серверы зобав’язаны іх рэалізаваць. - Процэс абмовы. Пад час першага з’яўлення сервера і кліента абе стороны адмаенываюць запыткі
initialize, у якіх паказваецца версія пратакола і ўмеласці кожнага, тады кожны знае, што падтрымлівае другі, прычым перш чым адбудзецца ўсё іншае.
Адна вызова інструмента разбіваецца на чатыры паведамлення. Спачатку хост запытае, якія інструменты існуюць:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {}
}
Сервер адказвае опісамі сваіх інструментаў:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "search_web",
"description": "Search the web for a given query",
"inputSchema": {
"type": "object",
"properties": {
"query": { "type": "string" }
},
"required": ["query"]
}
}
]
}
}
Потым хазяін паклець аднаго з іх:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "search_web",
"arguments": {
"query": "latest news on MCP protocol"
}
}
}
І сервер вяртае рэзультат:
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [
{
"type": "text",
"text": "Anthropic released MCP in Nov 2024 as an open standard..."
}
]
}
}
Найэфектывейшы спосаб пры выучэнні — гэта стварэнне праекта, таму ў наступным раздзеле будзе показана невялікая сервера MCP, яку вікорыстоўвае агент.
Што робіць FastMCP
FastMCP — гэта фрэймворк на Python для стварэння сервераў MCP з мінімальным колікствам шаблонаванага коду. Без яго распрацоўшчыку давялося бы рэалізаваць цыкл сообшчэнняў JSON-RPC, серыязаваць і дэзерыязаваць кожны запіт і адпаведнае адпаведзенне, вручную пісаць схемы JSON для кожнай функцыі, якая ўпускаецца, а таксама кераваць протакольнымі сообшчэннямі, такімі як initialize, tools/list, tools/call, resources/list і prompts/list. За дапамогою яго достатньа звычайная функцыя на Python плюс дэкаратар, а фрэймворк абавяжаецца рэшэннем усіх іншых задач:
from fastmcp import FastMCP
mcp = FastMCP("My Server") # creates the server
@mcp.tool() # registers the function as an MCP tool
def add(a: float, b: float) -> float:
"""Add two numbers.""" # docstring → tool description sent to the LLM
return a + b # type hints → JSON Schema sent to the LLM
mcp.run() # starts the stdio message loop
На адзынце анатазацый тыпаў FastMCP сама стварае JSON-шыму, а докстрынг служыць опісам на простай мове, які мадэль чытае пад час адлікавання, чы рэальна выклікваць адпаведны інструмент. Выкліканне mcp.run() запускае цыкл, які блакуе на stdin і stdout, выкарыстоўваючы фармат MCP wire, і самэ гэты цыкл якраз і прыўязуе MultiServerMCPClient.
Тры прымітывы ў прыкладных серверах
Кожны сервер у праекце ўсё ў тым жа FastMCP являе сабою самастоятэльную аплікацыю, якая адкрывае доступ да трох прымітывых тыпаў.
Спачатку ідуць інструменты: функцыі, якія модэль можа вызваць. Для таго, каб прыкрасіць функцыю Python замкнутым @mcp.tool(), дастаткова сама гэта анотацыя, а FastMCP ператварае яе і докстрынг у схему JSON, якая надаецца кліенту пад запытам tools/list. Модэль аналізуе гэтую схему, каб з’ясавіць, калі трэба выкорыстоўваць інструмент і якія аргументы падаць.
# servers/math_server.py
@mcp.tool()
def divide(a: float, b: float) -> float:
"""Divide a by b. Raises an error if b is zero."""
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
Рэсурсы — гэта толькі для чытання даныя, якія знаходзяцца за URI. Мадэль не запускае іх аўтаматычна, таму іх неабходна явна запрашваць. У гэтым праекце яны толькі ілюструюць ідэю, тады як у практычным выкарыстоўванні гэта можа включаць канфігурацыйныя файлы, копіі базы дадзеных чыста ў момент зберагання або дакументацыю-справаку.
# servers/math_server.py
@mcp.resource("math://constants")
def get_math_constants() -> str:
"""Common mathematical constants."""
return f"π = {math.pi}\n e = {math.e}\n ..."
@mcp.resource("math://formulas/{category}")
def get_formulas(category: str) -> str:
"""Retrieve mathematical formulas by category (geometry | algebra | statistics)."""
catalog = {
"geometry": (
"Geometry Formulas:\n"
" Circle area: A = π × r²\n"
" Circle circumference: C = 2π × r\n"
" Rectangle area: A = length × width\n"
" Triangle area: A = (base × height) / 2\n"
" Sphere volume: V = (4/3) × π × r³\n"
),
"algebra": (
"Algebra Formulas:\n"
" Quadratic formula: x = (−b ± √(b²−4ac)) / 2a\n"
" Difference of squares: a²−b² = (a+b)(a−b)\n"
" Perfect square: (a+b)² = a²+2ab+b²\n"
" Sum of arithmetic seq: S = n(a₁+aₙ)/2\n"
),
"statistics": (
"Statistics Formulas:\n"
" Mean: μ = Σx / n\n"
" Variance: σ² = Σ(x−μ)² / n\n"
" Std Dev: σ = √(Σ(x−μ)² / n)\n"
" Z-score: z = (x−μ) / σ\n"
),
}
return catalog.get(
category,
f"Unknown category '{category}'. Available: geometry, algebra, statistics",
)
URI math://formulas/{category} — гэта шаблон. Яго частка {category} ёсць параметрам шляху, завдзякі чаму math://formulas/geometry і math://formulas/algebra стаюць двума рознымі рэсурсамі, які падаюць пад адною функцыяй.
Інструкцыі ў фармаце праграмаў ўможлівяюць ўскоранае вікорыстоўванне. Праграма — гэта параметрызаваная паведамленне сістэмы, якое кліент можа запрашаць; гэта спосаб упакавання інструкцый для эксперта чы абавязакаў унутры сервера, каб кожны падключаны кліент могаў ўсё час вікорыстоўваць іх адносова.
# servers/math_server.py
@mcp.prompt()
def math_tutor(difficulty: str = "intermediate") -> str:
return (
f"You are an expert math tutor for {difficulty}-level students. "
"Break every problem into numbered steps..."
)
Серверы разам адкрываюць 19 інструментаў, як паказана ў наступных раздзелах.
Погляд кліента
Тут паказана аднойчынна тое, што выкананне python main.py з запытам прыводзіць да. Маршрут запыту з початку да канца выглядае так:
You type a query
│
▼
main.py ← entry point, parses args, kicks off async loop
│
▼
client/agent.py ← spawns 3 MCP servers, builds the agent, invokes it
│ │
│ ▼
│ utils/tracker.py ← fires on every LLM call, tool call, and result
│
▼
LangGraph ReAct loop ← think → call tool → observe → repeat
│
▼
servers/{math,text,data}_server.py ← each runs as an isolated subprocess
Шаг 1: дзе пачынаецца выконанне
main.py — гэта першы ўскладненні, які выконвае Python. Ён завантажае зменныя сераўніка, парасканавляе аргументы з лініі запуску, выводзіць панель прыема і перадае задачу асінхроннаму выконавачу.
async def _run(queries: list) -> None:
from client.agent import run_query # imported here (late) to keep startup fast
for i, q in enumerate(queries):
await run_query(q)
def main() -> None:
...
asyncio.run(_run(queries))
asyncio.run() стварае новы цыкл з’явоў і блакуе пакуль _run() не завершыцца. Запыт, які быў выкарыстоўваны для дамастравання, быў такім:
Generate 8 random numbers between 5 and 50 using seed=42,
then calculate their statistics, and tell me if there are any outliers.
Заўярніце з командной лініі так:
python3 main.py --query "Generate 8 random numbers between 5 and 50 using seed=42, \
then calculate their statistics, and tell me if there are any outliers."
Панель статычных дазведчых інфармацый пасля чаго выдруковываецца за дапамою _print_header():
╭────────────────────────────── 🔌 MCP Example ───────────────────────────────╮
│ Multi-Server MCP Demo │
│ │
│ Three FastMCP servers, each exposing tools + resources + prompts: │
│ ● Math Server — add, subtract, multiply, divide, power, sqrt, │
│ percentage │
│ ● Text Server — count_words, word_frequency, reverse, transform, │
│ extract_emails │
│ ● Data Server — generate_numbers, calculate_statistics, find_outliers, │
│ normalise │
│ │
│ Stack : FastMCP · LangChain · LangGraph · OpenAI · Rich │
│ Track : live Rich panels + JSONL log files under logs/ │
╰──────────────────────────────────────────────────────────────────────────────╯
Шаг 2: стварэнне агента
client/agent.py мае функцыю run_query(query) — асінхронную функцыю, якая керуе всім працэсам. Спачатку яна стварае трэкер і файл журналу.
log_file = str(LOGS_DIR / f"run_{int(time.time())}.jsonl")
tracker = MCPTracker(log_file=log_file)
Перад тым, як будзе падключаны які-небудзь сервер, ствараецца MCPTracker, а пад logs/ готавіцца файл у фармаце JSONL з пазначэннем часу. З таго моменту кожная падзея запісваецца там у рэальны час.
Пасля чаго выдруковываецца запіт корыстніка:
╭──── 💬 USER QUERY ────╮
│ Generate 8 random... │
╰────────────────────────╯
Потым задаюцца серверы, якія будуць ствараны:
def _server_config() -> Dict[str, Any]:
silent_env = {**os.environ, "FASTMCP_LOG_LEVEL": "ERROR"}
return {
"math_server": {
"command": sys.executable,
"args": ["servers/math_server.py"],
"transport": "stdio",
"env": silent_env,
},
"text_server": { ... },
"data_server": { ... },
}
Кожны элемент опісвае адзін сервер MCP, які працуе як падпроцес. command — гэта ўжо запускаецца інтэрпрэтар (sys.executable), args пазначае скрыпт сервера, а transport адзначае спосаб працы кліента і сервера; stdio означае стандартныя каналы вводу і выходу. Поле env задае сяродавешча падпроцеса, а FASTMCP_LOG_LEVEL=ERROR закрычвае анунція пра запуск FastMCP.
Транспорт stdio ёсць найпростым у MCP: кліент запісвае сообшчы JSON-RPC у stdin сервера і чытае адпаведныя адпаведзі з яго stdout, таму не ачынаецца жадны сеткавы порт.
Пасля чаго кліент падключаецца да кожнага сервера і выявляе наявныя інструменты:
client = MultiServerMCPClient(_server_config())
tools = await client.get_tools()
MultiServerMCPClient, які знаходзіцца ў пакеце langchain-mcp-adapters, адразу выпрацоўвае кожную запісь сервера. Ён стварае дочырні працэс, надае сігнал MCP initialize праз стандартны ввод-выход, апыляе запіт tools/list, прыме чыстак пераказа схем абладункоў і загортае кожную схему ў об’ект LangChain Tool.
Калі завершаецца выконанне await client.get_tools(), рэзультатам яе ёсць просты чыстак з 19 об’ектаў StructuredTool: семь з сервера для матэматыкі, семь з сервера для тэксту і пяць з сервера для дадзеных. Гэты чыстак ёсць полным наборам дзеянняў, доступных для модэлю.
Connected to 3 servers (math_server, text_server, data_server) with 19 tools: add, subtract, multiply,
divide, power, square_root, calculate_percentage, count_words, word_frequency, reverse_text,
transform_case, find_and_replace, extract_emails, count_vowels_consonants, generate_numbers,
calculate_statistics, find_outliers, sort_values, normalize_values
Потым ствараюцца мадэль мовы і агент:
model = ChatOpenAI(model=model_name, temperature=0)
agent = create_react_agent(model, tools)
Функцыя create_react_agent з LangGraph стварае скомпіляваны граф стана з такой структурой:
START
│
▼
[call_model] ─── no tool call ──▶ END
│
tool call requested
│
▼
[call_tools]
│
▼
[call_model] (loop again with tool result in context)
Гэта шаблон ReAct – разумаванне плюс дзеянне. Мадэль разумавае пра наступны крок, дзеяе, вызваўшы інструмент, спазірае на рэзультат і павтарае процэс, пакуль не будзе мяць достатню інфармацыю, каб адпаведаць.
У канцы канцоў вызываецца агент:
config = {"callbacks": [tracker]}
result = await agent.ainvoke({"messages": [("human", query)]}, config=config)
"callbacks": [tracker] элемент падае ў сістэму адпаведзяў LangChain. Калі фрэймворк выканае які-небудзь значны крок, такі як запуск або завершэнне вызову LLM чы вызову інструмента, ён вызывае адпаведны метод у tracker. Самэй так tracker можа бачыць усё, не знаходзячыся ўнутрь якой-небудзь логікі.
Шаг 3: спостэраганне ў рэальны час
MCPTracker расшыруе BaseCallbackHandler з LangChain. Яго методы ніколі не вызываюцца безпосерэдна, таму што LangChain вызывае іх у правыя моменты.
agent.ainvoke() called
│
├─▶ on_chain_start() "▶ AGENT STARTED" panel
│
├─▶ on_chat_model_start() "🤖 LLM CALL #1" panel (timer starts)
├─▶ on_llm_end() "LLM responded ⏱ 1.23s → will call: add, multiply"
│
├─▶ on_tool_start() "🔧 TOOL CALL #1" panel (timer starts)
├─▶ on_tool_end() "✓ TOOL RESULT ⏱ 0.01s" panel
│
├─▶ on_tool_start() (second tool, if any)
├─▶ on_tool_end()
│
├─▶ on_chat_model_start() "🤖 LLM CALL #2" (LLM synthesises final answer)
├─▶ on_llm_end() no tools this time → loop ends
│
└─▶ agent.ainvoke() returns
Калі лямоўваецца on_tool_start, трэкер вычысляе, які сервер адпавядае за інструмент, і прызначае яму відпаведны калер. Едыная мета — спрыянне чытаемасці.
TOOL_SERVER_MAP = {
"add": "math_server",
"count_words": "text_server",
"generate_numbers": "data_server",
...
}
SERVER_COLORS = {
"math_server": "cyan",
"text_server": "green",
"data_server": "yellow",
}
Кожныя падзея стаюць адной лінійкай JSON у logs/run_<timestamp>.jsonl. Файл ачынаецца у рэжыме дадзення і запісваецца негараздо, таму запуск tail -f logs/run_*.jsonl у іншай тэрмінале паказвае первісныя падзеі, якія перакручваюцца па меры працы агента. Пасля таго, як agent.ainvoke() вяртаецца, client/agent.py ручнае вызывае print_summary(), які выдае загальную колькасць вызываў LLM, вызываў інструментаў і час, прайшоў з моменту запуску.
Першы панель, які з’являецца, — гэта вызыв LLM пад номерам 1 з адной паведамленнем:
── 🤖 LLM CALL #1 model=gpt-5-mini messages=1 ──╮
│ Role Content preview │
│ Human Generate 8 random numbers between 5 and 50… │
╰──────────────────────────────────────────────────────╯
Граф ReAct якраз запускаецца. Мадэль прымее сістэмны прамт, які Лангграф увводзіць разам з спісамі інструментаў і ўзорамі для іх налаштавання, а таксама адна паведамленне ад чалавека, з-за чаго messages=1.
LLM responded ⏱ 5.35s → will call: generate_numbers
Тракер пераглянуў неапранутую адпаведзь модэлю. У зяместо прозы модэль адпавяла структураваным запітам на выклік інструмента:
{
"name": "generate_numbers",
"arguments": {"count": 8, "min_val": 5, "max_val": 50, "seed": 42}
}
У элеменце response.generations[0][0].message.tool_calls трэкер знайшоў запіт і вывёў яго назву. 5,35 секунды — гэта проста час на перадачу дадзеных і ўзяцце ўсіх дадзеных да API OpenAI. LangGraph прачытаў гэты рашэнне і перайшоў да вузла call_tools, які даў номер 1 для вызову інструмента:
╭── 🔧 TOOL CALL #1 ──────────────────╮
│ Tool : generate_numbers │
│ Server: data_server │
│ Args : │
│ {'count': 8, 'min_val': 5, │
│ 'max_val': 50, 'seed': 42} │
╰───────────────────────────────────────╯
З гэтага назвы інструмента generate_numbers трэкер запытаўся ў TOOL_SERVER_MAP, які павярнуў значэнне data_server, і пакрасіў панель у жоўты колер, таму што SERVER_COLORS["data_server"] равнае "yellow". Самэ ў гэты момент LangChain адправляе сообшчэнне JSON-RPC tools/call праз канэл stdio да дзецявога процэса data_server:
{"method": "tools/call", "params": {"name": "generate_numbers",
"arguments": {"count": 8, "min_val": 5, "max_val": 50, "seed": 42}}}
Працэс прымае яго, запускае функцыю Python і запісвае рэзультат назад у свой stdout.
╭── ✓ TOOL RESULT ⏱ 0.63s ────────────────────────────╮
│ [33.77, 6.13, 17.38, 15.04, 38.14, 35.45, 45.15, 8.91] │
╰──────────────────────────────────────────────────────────╯
Ён выканаў гэты самы код:
random.seed(42)
return [round(random.uniform(5, 50), 2) for _ in range(8)]
# → [33.77, 6.13, 17.38, 15.04, 38.14, 35.45, 45.15, 8.91]
0,63 секунды — гэта час з момента адправкі запіту да момента отрымання адпаведнай адпаведзі праз канал зв’язку, уключаючы час на планаванне процэса Python. LangGraph дадае рэзультат у спіс паведамленняў агента як ToolMessage; такім чынам у спіску цяпер ёсць тры элементы: паведамленне ад чалавека, паведамленне ад AI (рашэнне пра выклік інструмента) і паведамленне ад самага інструмента (рэзультат).
Далей следуе выклік LLM пад номерам 2, які таксама мае тры паведамлення:
╭── 🤖 LLM CALL #2 model=gpt-5-mini messages=3 ──╮
│ Role Content preview │
│ Human Generate 8 random numbers… │
│ AI (the tool-call decision) │
│ Tool [33.77, 6.13, 17.38, ...] │
╰──────────────────────────────────────────────────────╯
Модэль зноў запускаецца з усім історыям дзеянняў, які ўжо быў. Яна бачыць первісны запіт, сваё сабе ранейшае рашэнне вызваць generate_numbers, а таксама чыслы, якія былі атрыманы. Іё логіка выглядае прыблізна так: чыслы ўжо ёсць, а запыт таксама прасіў статыстыку, таму calculate_statistics павінен быць запушчаны з іхнім выкарыстоўванням. Гэта дае номер 2 для вызову інструмента і яго рэзультат:
Tool : calculate_statistics
Server: data_server
Args : {'numbers': [33.77, 6.13, 17.38, 15.04, 38.14, 35.45, 45.15, 8.91]}
Result: {"count":8, "min":6.13, "max":45.15, "mean":24.9962,
"median":25.575, "std_dev":14.8181, "variance":219.5755,
"range":39.02, "q1":15.04, "q3":38.14}
Модэль здэсь нічога не запамятавала. Яна брала значэнні слова за словам з пакульшнага ToolMessage у сваём вікні кантэксту і перакладвала іх у аргументы наступнага інструмента. Так выглядае запэўненне ў цыкле ReAct: тое, што выдае адні інструмент, стае кантэкстам, які наступны вызов модэлі можа прачытаць і перыявіць.
У трэцім вызове LLM ёсць пяць паведамленняў, і модэль вызывае find_outliers:
Tool : find_outliers
Server: data_server
Args : {'numbers': [...], 'threshold': 2}
Result: {"outliers": [], "outlier_count": 0,
"total_checked": 8, "mean": 24.9962, "std_dev": 14.8181}
Функцыя find_outliers вычыляе з-рэшт кожнага значэння (z = (x − mean) / std_dev) і пераканальваецца, чы неякое з іх перасягае порог у 2. Ніякое не перасягае, таму outliers — гэта порожняй спіс.
Нумар 4 вызову LLM паказвае сямь паведамленняў:
╭── 🤖 LLM CALL #4 messages=7 ──╮
│ Human / AI / Tool │
│ AI / Tool │
│ AI / Tool │
╰───────────────────────────────────╯
LLM responded ⏱ 30.25s
Відсутнісць суфікса „will call“ свідчыць пра тое, што мадэль стварыла звычны тэкст, а не запіт на вікорыстанне інструмента, чым завершаецца цыкл ReAct. Зв’язак зайняў 30 секунд, адколькі кантэкст быў вялікі (сема паведамленняў, укладзеных у неапрануты фармат JSON) і мадэль стварыла дзяўжы, рэшытальную сінтэзу, ручна вычысляючы показнік z-score для кожнага значэння. LangGraph не знаходзіць жадных запытанняў на вікорыстанне інструментаў у адпаведзі і пераводзіць працэс у стан END, які выдрукоўвае заканчальную адпаведзь:
╭── ✅ FINAL ANSWER ───────────────────────────────╮
│ Random numbers: [33.77, 6.13, 17.38, ...] │
│ Mean: 24.9962 / Median: 25.575 / Std Dev: 14.8181│
│ Outliers: none (all |z| < 2) │
╰───────────────────────────────────────────────────╯
Потым паказываецца статыстыка запуску:
╭─────────────────────────────────────────────────────╮
│ LLM Calls 4 │
│ Tool Calls 3 │
│ Total Time 42.25s │
│ Log File logs/run_1776864980.jsonl │
╰─────────────────────────────────────────────────────╯
Паўная архітэктура паведамленняў
Гэта ўсе элементы ў спісе паведамленняў агента, калі быў вярнуты agent.ainvoke():
1 HumanMessage "Generate 8 random numbers…"
2 AIMessage [tool_call: generate_numbers({count:8, min:5, max:50, seed:42})]
3 ToolMessage [33.77, 6.13, 17.38, 15.04, 38.14, 35.45, 45.15, 8.91]
4 AIMessage [tool_call: calculate_statistics({numbers:[...]})]
5 ToolMessage {count:8, mean:24.9962, std_dev:14.8181, ...}
6 AIMessage [tool_call: find_outliers({numbers:[...], threshold:2})]
7 ToolMessage {outliers:[], outlier_count:0, ...}
8 AIMessage "Here are the results. Random numbers: …" ← final answer
Кожны раз, калі запускаецца LLM, ўжо наяўныя даны адбіваюцца, таму значэнне messages у панелях назірання зростае на 1, 3, 5, 7.
Лог у фармаце JSONL
Усі вышэказаныя запісы таксама фіксаваліся ў logs/run_x.jsonl па меры ўсунення змян, пры чым кожны радок містыў адна JSON-об’екта:
{"timestamp": "...", "event": "agent_start", "data": {"runnable": "LangGraph", "run_id": "..."}}
{"timestamp": "...", "event": "llm_call", "data": {"call_number": 1, "model": "gpt-5-mini", "messages": 1}}
{"timestamp": "...", "event": "llm_end", "data": {"elapsed": "5.35s", "tool_calls_requested": ["generate_numbers"]}}
{"timestamp": "...", "event": "tool_start", "data": {"call_number": 1, "tool": "generate_numbers", "server": "data_server", "args": "..."}}
{"timestamp": "...", "event": "tool_end", "data": {"elapsed": "0.63s", "output_preview": "[33.77, 6.13, ...]"}}
{"timestamp": "...", "event": "llm_call", "data": {"call_number": 2, "model": "gpt-5-mini", "messages": 3}}
...
{"timestamp": "...", "event": "run_summary", "data": {"llm_calls": 4, "tool_calls": 3, "total_elapsed": "42.25s"}}
Чаму гэта важна
Прыгледныя якосці MCP — гэта можлівасць распространення. До стварэння стандарта кожная команда, якая хацела выкорыстоўваць LLM за межамі яго можлівасцей, была змушана ствараць сабе сябе інтэграцыйны шар, пры чым весь гэты робак не пераходзіў на іншыя мадэлі чы рамкі. Ёнколі сервер пісаецца раз і можа служыць будзь-яму сумэннаму кліенту. Команда, якая займаецца базамі дадзэнняў, можа выдаты толькі для чытання сервер для свайго схематызму, команда DevOps можа адкрыць інструменты для развяртання, а платформа дадзэнняў — функцыі запытанняў і аналізу. Будзь-які агент можа падключыцца да ўсіх іх, і командам ніколі не трэба пагадвацца па дэталях рэалізацыі.
Супаўязаныя матэрыялы
- Падзбірка агента у стылі Claude Code за дапамою LangChain Deep Agents — Як працюе сістэма керавання кодавальнага агента, ад основнага цыклу да інструментаў, планавання, памяці, падагентаў і процедураў затварэння ў «песочніцы», падзбіркавана шаг за шагам з выкарыстоўванням deepagents.
- Дзесяць сервераў MCP, якія варта ўстановіць першыя, за прыорытэтам для распрацоўшчыкаў — Спіс сервераў MCP, якія ўжо ўтрымліваюць прыорытэт для AI-асістэнтаў у кодаванні, ад Context7 і GitHub да Postgres і памяці, з настройкамі установкі і паведамленнямі пра абавесці ў чысце праваў.