IT · Model Context Protocol
Як MCP доставляє JSON-RPC між Client і Server
Урок 4
Блок 1
JSON-RPC, client-server і канал доставки
Вступ
Вступ
Request (client → server): який tool обрано і з якими аргументами.
{
"jsonrpc": "2.0",
"method": "get_weather",
"params": { "city": "Paris" },
"id": 1
}
Вступ
{
"jsonrpc": "2.0",
"result": 20,
"id": 1
}
Вступ
Transport layer — рівень MCP, який відповідає за фізичну доставку JSON-RPC повідомлень між Client і Server. Протокол визначає що передається; transport — як воно доходить від однієї програми до іншої: через який канал, на якій машині, за яким механізмом.
Аналогія: JSON-RPC — текст листа, transport — спосіб доставки. Той самий лист можна передати кур'єром по кімнаті або поштою через інтернет.
Блок 2
stdio transport
Локальний stdio
Запит у Host (Cursor, Claude Desktop)
JSON-RPC
Client запускає сервер як subprocess — JSON-RPC через stdio
Локальний stdio
stdio = standard input / standard output. Це не мережевий протокол — стандартні потоки ОС у кожного процесу: stdin (вхід), stdout (вихід), stderr (логи).
Це байтові труби в пам'яті між двома програмами на одному ПК — без TCP, без IP, без HTTP. Усередині просто текст JSON, по одному повідомленню на рядок.
Локальний stdio
Host сам стартує процес сервера при підключенні:
{
"mcpServers": {
"my-server": {
"command": "python",
"args": ["path/to/server.py"],
"env": { "API_KEY": "your_key" }
}
}
}
Локальний stdio
Host запускає програму як дочірній процес — наче node server.js у терміналі.
spawn("node filesystem-server.js")
→
Process #1
spawn("python git-server.py")
→
Process #2
spawn("node database-server.js")
→
Process #3
Кожен сервер у конфігу — окремий процес у Task Manager / Activity Monitor
Приклади
server.js
Своя пам'ять і stdin/stdout — стан не спільний
Локальний stdio
Один спільний сервер на всю команду через stdio — не працює.
spawn("node server.js")
→
Process #1
spawn("node server.js")
→
Process #2
Два запуски одного файлу — процеси не знають один про одного
Streamable HTTP
1 сервер слухає порт
100 клієнтів підключаються → працює
stdio
100 клієнтів → 100 процесів
кожен spawn() → неефективно
Обмеження
Локальний stdio
from fastmcp import FastMCP
mcp = FastMCP("Cool MCP Server")
if __name__ == "__main__":
mcp.run() # transport="stdio"
Блок 3
Streamable HTTP
Streamable HTTP
User ↔ Host — всередині LOCAL MACHINE
Масштабування
Host (Client) ↔ remote server у Cloud — один процес обслуговує тисячі клієнтів по HTTP, без subprocess
Streamable HTTP
Той самий transport на http://127.0.0.1:8000/mcp — сервер запускається окремо, Client підключається по url.
Зручно для MCP Inspector і коли один процес обслуговує кілька клієнтів на localhost.
Streamable HTTP
{
"mcpServers": {
"remote-server": {
"url": "https://mcp.example.com/mcp"
},
"local-http": {
"url": "http://127.0.0.1:8000/mcp"
}
}
}
Client не запускає subprocess — з'єднується з уже працюючим HTTP-сервером.
Streamable HTTP
if __name__ == "__main__":
mcp.run(
transport="streamable-http",
host="127.0.0.1",
port=8000
)
Блок 4
Remote MCP + OAuth
Аутентифікація
Claude Desktop з'єднується з Remote MCP по HTTP з токеном Authorization: Bearer, Auth0 — OAuth
Аутентифікація
{
"mcpServers": {
"todos": {
"url": "https://mcp.example.com/mcp",
"headers": {
"Authorization": "Bearer <access_token>"
}
}
}
}
Remote без auth — лише для dev на localhost. У production auth обов'язковий.
Блок 5
stdio vs Streamable HTTP
Порівняння
| Характеристика | |
|---|---|
| ✓ | Простота — без мережі, портів, TLS |
| ✓ | Ідеально для desktop Host |
| ✓ | Client керує життєвим циклом процесу |
| ✗ | Один subprocess на одне з'єднання |
| ✗ | Не підходить для спільного командного сервера |
Порівняння
| Характеристика | |
|---|---|
| ✓ | Один процес — багато клієнтів |
| ✓ | Деплой у хмару, Docker, Kubernetes |
| ✓ | Сесії через Mcp-Session-Id |
| ✗ | Складніша інфраструктура |
| ✗ | Auth обов'язковий у production |
Порівняння
Блок 6
HTTP → SSE → Streamable HTTP
Deprecated
Чому в MCP з'явився Streamable HTTP і чому HTTP+SSE — legacy.
Крок 1
Клієнт ──── запит ────► Сервер
Клієнт ◄─── відповідь ── Сервер
✓ з'єднання закрито
Клієнт запитав → сервер відповів → з'єднання закрилось.
Проблеми:
Крок 2
Слухаємо сервер — тримаємо з'єднання відкритим:
# Канал 1 — слухаємо сервер
Клієнт ──── GET /sse ─────────────► Сервер
Клієнт ◄════ тримаємо відкритим ════ Сервер
Крок 2
Надсилаємо команди окремим POST — відповідь приходить через перший канал:
# Канал 2 — надсилаємо команди
Клієнт ──── POST /message ────────► Сервер
# Відповідь приходить через ПЕРШИЙ канал
Клієнт ◄─── результат ────────────── Сервер
Крок 3
HTTP/1.1 вміє слати відповідь кусками (chunked) — це і використовує сучасний MCP transport.
Клієнт ──── POST /mcp ─────────────► Сервер
Клієнт ◄─── chunk 1 ──────────────── Сервер
Клієнт ◄─── chunk 2 ──────────────── Сервер
Клієнт ◄─── chunk 3 ──────────────── Сервер
✓ з'єднання закрито
Один endpoint, один запит, відповідь стримом — простіше і надійніше, ніж два канали SSE+POST.
Підсумок
| Звичайний HTTP | SSE | Streamable HTTP | |
|---|---|---|---|
| Endpoint | 1 | 2 | 1 |
| З'єднання | Закривається | Постійне | Закривається |
| Стримінг | ✗ | ✓ | ✓ |
| У MCP | Старий | Старий | Новий 2025 |
Аналогія
Блок 7
Один параметр — різний канал
Три transport
# stdio (за замовчуванням)
mcp.run()
# SSE (legacy)
mcp.run(transport="sse", host="127.0.0.1", port=8000)
# Streamable HTTP
mcp.run(transport="streamable-http", host="127.0.0.1", port=8000)
Фінал заняття
На цій лекції ми розібрали transport layer MCP: локальний stdio (subprocess, command/args) і мережевий Streamable HTTP (url, сесії, auth для remote).
При stdio кожне з'єднання Client↔Server — окремий процес; HTTP дозволяє одному серверу обслуговувати багато клієнтів.
У наступній лекції перейдемо до практики: створимо власний локальний stdio MCP-сервер на Python.