Перейти до вмісту
27/30Розділ 27 з 30

Випустити MCP server: TypeScript і Python у вимірах

Один server двічі: три інструменти, ресурс і prompt. 94 пакети проти 28, cold start 145 мс проти 709.

На цій сторінці

Ось увесь мовний аргумент, виміряний ще до того, як буде сказано бодай слово.

spawn → tools/list answered, median of 25 launchesTEXT
node ./incidents.js         144.5 ms
python incidents.py         709.4 ms
npx incidents-mcp           712.6 ms

Перші два рядки — це порівняння, якого хочуть усі. Третій рядок — той самий TypeScript server з першого рядка, запущений так, як його справді поширювали б, — і він опиняється за три мілісекунди від Python.

Розділ 26 читав Model Context Protocol проти його власної специфікації через сирий JSON-RPC, бо сирий JSON-RPC не має мови. У цьому розділі їх дві, і вага аргументу тут: той самий server, написаний двічі. Три інструменти, один ресурс, один prompt, обидва SDK, без скорочень з жодного боку. Потім transports, inspector, 401 і числа, яких ніхто не публікував.

Журнал інцидентів. Три інструменти, бо поділ із розділу 18 між читанням і записом має бути видимим: search_incidents читає, open_incident пише й повертає handle, resolve_incident бере цей handle і закриває. Один ресурс, incidents://open, бо читання поточного списку — це те, що приєднує застосунок. Один prompt, postmortem, бо «опиши це» — це slash command людини. Це ієрархія контролю з розділу 26 — модель, застосунок, людина — перетворена на п’ять реєстрацій.

Handle важливіший, ніж здається. Розділ 26 зламав іграшковий календар, тримаючи його стан у масиві на рівні модуля: protocol не має session, тому інструмент створення повертає opaque identifier, а кожен наступний виклик приймає його як звичайний аргумент. Ніщо в жодному файлі не припускає, що caller — це процес, який його відкрив.

Ось той самий інструмент обома мовами, зареєстрований поруч:

incidents.tsTS
server.registerTool(
  "resolve_incident",
  {
    description:
      "Close an incident by handle and record its cause.",
    inputSchema: {
      id: z.string().describe(
        "The handle returned by open_incident, e.g. INC-3."),
      cause: z.string().describe(
        "One sentence. What actually broke."),
    },
    annotations: {
      readOnlyHint: false,
      destructiveHint: true,
      idempotentHint: true,
    },
  },
  async ({ id, cause }) => {
    const at = OPEN.findIndex((i) => i.id === id);
    if (at < 0) {
      return { isError: true, content: [{ type: "text",
        text: `No open incident ${id}. ` +
              `Call search_incidents first.` }] };
    }
    const [done] = OPEN.splice(at, 1);
    return { content: [{ type: "text",
      text: JSON.stringify({ ...done, cause }) }] };
  },
);
incidents.pyPYTHON
@server.tool(
    description=
      "Close an incident by handle and record its cause.",
    annotations=ToolAnnotations(
        readOnlyHint=False,
        destructiveHint=True,
        idempotentHint=True,
    ),
)
def resolve_incident(
    id: Annotated[str, Field(description=
        "The handle returned by open_incident, e.g. INC-3.")],
    cause: Annotated[str, Field(description=
        "One sentence. What actually broke.")],
) -> Incident:
    for at, i in enumerate(OPEN):
        if i["id"] == id:
            done = OPEN.pop(at)
            return {**done, "cause": cause}
    raise ValueError(
        f"No open incident {id}. Call search_incidents first.")

Спершу прочитайте те, що однакове, бо це і є знахідка. Обидва оголошують назву, опис, два описані рядкові аргументи й три annotations; обидва — одна function; жоден не згадує JSON-RPC, framing, stdout чи версію protocol. Два SDK зійшлися на тій самій формі, а саме це й має означати «Tier 1».1

Дві відмінності справжні, і обидві повернуться пізніше. TypeScript описує аргументи через schema library — тут Zod — і schema є значенням, яке ви пишете. Python описує їх власними type hints function і читає їх під час import, тому він знає про function речі, яких TypeScript-файл йому ніколи не повідомляв. І шлях помилки: TypeScript повертає tool result із isError, Python кидає exception. Запам’ятайте це.

Інші чотири реєстрації структурно не відрізняються. Ресурс — це server.registerResource("open-incidents", "incidents://open", …) проти @server.resource("incidents://open", …); prompt — registerPrompt проти @server.prompt. Останній рядок кожного файлу — transport: await server.connect(new StdioServerTransport()) проти server.run().

Цілі файли: 81 непорожній рядок і 3 060 байтів TypeScript проти 63 і 2 555. Сприймайте це з належною часткою солі — кількість рядків вимірює formatter не менше, ніж мову, тому жодне з цих чисел не потрапило в заголовну таблицю нижче.

Доказ того, що мова невидима, — один client, запущений двічі, в одинадцять рядків:

client.tsTS
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

const client = new Client({ name: "incident-cli", version: "1.0.0" });
await client.connect(new StdioClientTransport({
  command: process.argv[2], args: process.argv.slice(3) }));

const { tools } = await client.listTools();
console.log("tools:", tools.map((t) => t.name).join(", "));

const opened = await client.callTool({ name: "open_incident",
  arguments: { title: "Queue backed up", severity: "sev2" } });
console.log("open_incident ->", JSON.stringify(opened.content));

Спрямуйте його по черзі на кожен server. Реальний output, скорочено:

TEXT
$ node client.ts node incidents.ts
tools: search_incidents, open_incident, resolve_incident
open_incident -> [{"type":"text","text":"{\"id\":\"INC-3\"}"}]

$ node client.ts ./py/.venv/bin/python ./py/incidents.py
tools: search_incidents, open_incident, resolve_incident
open_incident -> [{"type":"text","text":"{\n  \"id\": \"INC-3\"\n}"}]

Ті самі інструменти, той самий порядок, той самий handle. TypeScript client не може сказати, якою мовою написаний server, і ніколи не питає. Це вся обіцянка protocol — і вона тримається.

Тепер подивіться на whitespace у другому результаті, бо це не косметика: Python SDK serialises payloads із pydantic_core.to_json(result, fallback=str, indent=2). На читанні ресурсу з двома інцидентами у списку TypeScript body має 136 символів і 37 o200k_base token; Python body — 185 і 62. На шістдесят вісім відсотків більше token для ідентичних рядків, щоразу оплачених тим, хто читає ресурс у prompt.

У каталогу та сама історія, але з більшою причиною. Обидва servers, ті самі три інструменти, tools/list зважено ключ за ключем:

keyTypeScriptPython
name2121
description4646
annotations4646
inputSchema211192
outputSchema187
execution27
total342480

Python-ові input schemas дешевші — TypeScript-ів Zod bridge ставить $schema і additionalProperties на кожну. Увесь розрив у 138 token — це output schema, яку ніхто не писав. resolve_incident annotated як -> Incident, тому SDK вивів JSON Schema для return type і відправив її. Це справді корисно — саме це дає client змогу валідовувати structuredContent — і це 187 token вашого context window, що приходять через type hint. Правило розділу 24 про definitions, які витісняють важливий матеріал, стосується і schemas, про наявність яких ви не знали.

Два шляхи помилок вище — не питання стилю. Дайте кожному server інструмент, який падає так, як падає реальна integration, і прочитайте, що доходить до моделі.

tools/call on a tool that raisesTEXT
TypeScript  {"content":[{"type":"text","text":
              "connect ECONNREFUSED 10.0.3.7:5432 (db-prod-eu, user=reporting)"}],
             "isError":true}

Python      {"content":[{"text":"Error executing tool boom","type":"text"}],
             "isError":true}

TypeScript SDK поклав internal address, port, database name і service account у context моделі. Python SDK не поклав туди нічого; traceback пішов у stderr і лишився на server.

Жодне з цього не bug. Обидва — рішення, і Python-ове записане у власному docstring: ToolError — це «failure you anticipated», а його message повертається «in content for the model to read»; усе інше «is treated as a crash: the model sees only Error executing tool <name>, and the server logs the traceback at ERROR». Class для crash case проговорює решту прямо — «nothing from the original reaches the client».

Обидві поведінки помилкові в половині випадків. Розділ 18 доводив, що validation error має повертатися як tool result, який модель може прочитати й виправити, бо це рядок із найбільшим leverage у більшості integrations; на боці Python для цього треба явно кидати ToolError, а голий ValueError викидає корисне речення. Аргумент розділу 30 іде в інший бік: усе, що повертає інструмент, потрапляє в context, з якого пізніша prompt injection може спробувати це витягти, а непереглянутий exception string — найменш audited текст у вашій системі.

Правило, яке переживає обидва аргументи: вирішуйте для кожного інструмента, що failure має право сказати, і пишіть цей рядок самі. Ніколи не дозволяйте default text exception вирішувати це в будь-якій мові.

Офіційний tutorial формулює правило без застережень: «For STDIO-based servers: Never write to stdout. Writing to stdout will corrupt the JSON-RPC messages and break your server. The print() function writes to stdout by default, so keep it out of a STDIO server entirely.»1 Розділ 26 цитував normative version — server «MUST NOT write anything to its stdout that is not a valid MCP message».2

Додайте один рядок до кожного server і прочитайте raw stream:

raw stdout, first two linesTEXT
TypeScript  incidents server starting
            {"result":{"protocolVersion":"2025-11-25", … },"jsonrpc":"2.0","id":1}

Python      {"jsonrpc":"2.0","id":1,"result":{ … }}
            incidents server starting

Python-овий гірший, і причина не в MCP. Процес, чий stdout — pipe, а не terminal, отримує block-buffered stream, тож stray line flushиться тоді, коли вирішить buffer — тут, на exit, після response, перед яким його було записано. Corruption з’являється не там, де bug. Додайте flush=True або library, що flushes, — і він пересунеться.

Далі частина, яка пояснює, чому це доходить до продакшену. Дайте зламаний server трьом clients:

TEXT
naive parser, dirty server   SyntaxError: Unexpected token 'i',
                             "incidents "... is not valid JSON
SDK client, dirty server     tools: search_incidents, open_incident, resolve_incident
MCP Inspector, dirty server  full catalogue, no warning

Семирядковий parser помирає одразу. Офіційний client і Inspector лише знизують плечима — пропускають рядок і продовжують. Правило, яке ламає лише clients, якими ніхто не користується, доходить до production неушкодженим; саме тому варто зламати його навмисно тут, а не в логах клієнта.

CLI-режим Inspector — та половина, про яку забувають: npx @modelcontextprotocol/inspector --cli <command> --method tools/list друкує catalogue і завершується, що робить його scriptable так, як browser UI не може.3

Обидва SDK встановилися чисто, у власні directories, нічого спільного:

TypeScriptPython
package@modelcontextprotocol/sdk 1.30.0 + zod 3.25.76mcp 2.1.1
latest protocol revision implemented2025-11-252026-07-28
transitive packages installed9428
installed size13.9 MiB44.3 MiB
files on disk3 3862 018
third-party packages loaded to serve stdio8 of 9418 of 28
bare interpreter start, median19.4 ms11.1 ms
spawn → tools/list answered, median of 25144.5 ms709.4 ms
tools/list catalogue, o200k_base tokens342480

Кожен рядок дивує в інший бік, тому порівняння варто запускати, а не припускати.

TypeScript встановлює більш ніж утричі більше пакетів і менш ніж третину байтів. 94 dependencies — це npm ecosystem у своєму звичному вигляді: fast-deep-equal, es-errors, dunder-proto. Python-ових 28 менше, але вони величезні: cryptography, pydantic-core і uvicorn — compiled artefacts. Якщо ваш інстинкт каже, що хвилюватися треба через dependency count, цей рядок — контрприклад.

Python interpreter стартує швидше за Node, і різниця значна — 11.1 ms проти 19.4 ms на порожній програмі. Тож 565 ms у рядку cold-start — це не мова. Це SDK, і рядок loaded-packages пояснює чому:

third-party modules loaded to answer one tools/list over stdioTEXT
TypeScript   8 of 94   sdk, zod, zod-to-json-schema, ajv, ajv-formats,
                       fast-deep-equal, fast-uri, json-schema-traverse

Python      18 of 28   mcp, mcp_types, pydantic, pydantic_core, anyio,
                       starlette, uvicorn, sse_starlette, httpx2,
                       cryptography, _cffi_backend, opentelemetry, click, …

Server, чий єдиний I/O — pipe, imports ASGI web server, HTTP client і TLS library до того, як прочитає перший рядок. TypeScript SDK теж ships Express, Hono, jose і eventsource — вони лежать на disk непрочитаними, бо package boundary не пускає їх у server/stdio.js import. Python package — це один import graph, тож import mcp означає весь він: python -X importtime attributes 727 ms to import mcp.server.mcpserver — число, виміряне під import profiler, тому воно виходить більшим за 709 ms, які unprofiled run займає від spawn до answer — і 269 із них до mcp.types subtree alone — wire types are Pydantic models, one class per protocol message per revision, and building them is work done at import. Це design trade, а не недбалість — eager imports саме тому дають Python SDK змогу видати вам run(transport="streamable-http") у наступному рядку без другого install.

А потім останній рядок початкового блоку скасовує аргумент. Запакуйте TypeScript server належно — bin entry, shebang, npm link, нічого завантажувати — і запустіть його через npx із --no-install, тобто так, як published stdio server фактично стартує:

median of 25, spawn → tools/list answeredTEXT
node ./incidents.js       144.5 ms
npx incidents-mcp         712.6 ms      (+568.1 ms of launcher)
python incidents.py       709.4 ms

Launcher коштує 568 ms за start — у чотири з половиною рази більше за весь TypeScript SDK import — і ця ціна сплачується на кожному launch, бо MCP host запускає stdio server виконанням цієї command. Тож чесна форма «TypeScript starts five times faster» така: так, доки ви не поширюєте його звичайним способом. Та сама caveat, імовірно, стосується uvx; на цій машині не було встановлено uv, тому такого рядка немає. Нічого невиміряного не потрапляє в таблицю.

Розділ 26 розбирав framing stdio. Дві речі він лишив сюди.

Перша: запуск server через npx або uvx і є stdio transport. Окремого «package mode» немає. Конфігурація host називає command і arguments; host spawnить її й говорить через pipes. Саме тому «як це поширювати» і «яким transport воно говорить» локально є одним питанням, і саме тому cost launcher належить розділу про shipping.

Друга: stdio взагалі не має authorization section, і specification каже це одним рядком — implementations using stdio «SHOULD NOT follow this specification, and instead retrieve credentials from the environment».4 Його security model — це model операційної системи, і його limit теж: local subprocess обслуговує рівно одну машину й одного користувача.

Інший live transport — Streamable HTTP: один endpoint, що приймає POST, один HTTP request на JSON-RPC message, і header Accept, який мусить перелічувати і application/json, і text/event-stream, бо server для кожного request обирає, яким із двох відповісти.5 Розділ 14 вручну розбирав цей event stream, тож у wire format немає нічого нового — лише те, що його обгортає. Три obligations поточної revision легко пропустити, і всі три testable:

Кожен POST несе MCP-Protocol-Version, і його value має збігатися з protocolVersion всередині власного _meta request. Mismatch — це 400 із header-mismatch error, а не shrug.5

Mcp-Method mirrors method на кожному request; Mcp-Name mirrors params.name або params.uri на tools/call, resources/read і prompts/get. Вони існують, щоб proxy міг route без parsing bodies.5

GET stream, Mcp-Session-Id і Last-Event-ID resumption були removed. Server, що говорить лише цією revision, має відповідати 405 Method Not Allowed на GET або DELETE, ignore session header without minting one, and ignore Last-Event-ID.5

Тепер вимірювання, яке переосмислює весь розділ. Надішліть current-revision request до кожного server через HTTP.

POST /mcp, MCP-Protocol-Version: 2026-07-28TEXT
Python   200  {"result":{"resultType":"complete","cacheScope":"private","ttlMs":0,
              "tools":[…],"_meta":{"io.modelcontextprotocol/serverInfo":{…}}}}

TypeScript    {"error":{"code":-32000,"message":"Bad Request: Unsupported protocol
              version: 2026-07-28 (supported versions: 2025-11-25, 2025-06-18,
              2025-03-26, 2024-11-05, 2024-10-07)"}}

Constants узгоджуються з behavior: Python SDK LATEST_PROTOCOL_VERSION reads 2026-07-28, TypeScript SDK — 2025-11-25. Надішліть header-mismatch request із кроку вище, і Python server відповідає 400 з error -32020 і message «mcp-protocol-version header does not match the request envelope's protocol version»; TypeScript SDK не має такого code, бо не implements revision, яка його визначає.

Сторінка, що ставить обидва в Tier 1, також каже «Each SDK provides the same functionality».1 На дату нижче, для current revision, це речення — aspirational. Перевірте LATEST_PROTOCOL_VERSION у SDK, який ви збираєтеся встановити; це один рядок і єдина claim у цьому розділі, яка все ще матиме значення за рік.

Винесіть server зі свого laptop — і прийде чужий client із token. Це та половина, яку розділ 26 не чіпав, і та половина, яку multi-user product не може пропустити.

Specification ставить MCP server у роль OAuth 2.1 і називає її: protected MCP server is a resource server, client is an OAuth client, and authorization server is somebody else's problem.4 З цієї ролі випливають чотири mandatory clauses, процитовані повністю, бо саме paraphrasing їх і створює помилку:

MCP servers, acting in their role as an OAuth 2.1 resource server, MUST validate access tokens as described in OAuth 2.1 Section 5.2. MCP servers MUST validate that access tokens were issued specifically for them as the intended audience, according to RFC 8707 Section 2. […] MCP clients MUST NOT send tokens to the MCP server other than ones issued by the MCP server's authorization server. MCP servers MUST only accept tokens that are valid for use with their own resources. MCP servers MUST NOT accept or transit any other tokens.4

«Must not accept or transit» — це anti-passthrough rule, і саме для цього існує весь audience apparatus. Server, який replay bearer token, що йому передали, на third-party API, — confused deputy: він позичає власну trust тому, хто його викликав. Правило забороняє reuse, не лише storage.

Щоб зробити це enforceable, потрібні чотири RFC, кожен зі своєю роботою.6 RFC 9728 — як client взагалі знаходить authorization server: MCP server serves protected-resource-metadata document, а 401 points at it. RFC 8707 — параметр resource: client must send canonical URI server у both authorization request and token request, «regardless of whether authorization servers support it», so the issued token names its audience. RFC 9207 closes the loop з іншого боку: client records issuer before redirecting and compares returned iss by exact string, with no normalisation — no case folding, no default-port elision, no trailing slash. А RFC 7591, Dynamic Client Registration, тепер deprecated на користь Client ID Metadata Documents, «retained for backwards compatibility with authorization servers that do not support» them.4

Під’єднайте це на обох servers із token verifier, який не робить нічого, крім audience check. TypeScript ladder:

POST /mcp — TypeScript, with requireBearerAuthTEXT
no token            401  WWW-Authenticate: Bearer error="invalid_token",
                         error_description="Missing Authorization header",
                         scope="incidents:read",
                         resource_metadata="…/.well-known/oauth-protected-resource/mcp"
aud=other server    401  error_description="token audience is not this server"
no exp claim        401  error_description="Token has no expiration time"
right aud, no scope 403  error="insufficient_scope", scope="incidents:read"
right aud + scope   200  {"result":{"tools":[…]}}
GET /.well-known/oauth-protected-resource/mcpTEXT
{"resource":"http://127.0.0.1:8931/mcp",
 "authorization_servers":["https://auth.example.com/"],
 "scopes_supported":["incidents:read","incidents:write"],
 "resource_name":"Incidents"}

Обидва SDK serve that document, і обидва point a 401 at it, що і є entire discovery story: client, який ніколи не бачив ваш server, дізнається, де authenticate, з refusal. 403 — інша істота: token нормальний, scope ні — і challenge називає, чого бракує, щоб client міг step up, а не start over.

Дві сходинки різняться, і жодна відмінність не в specification. TypeScript SDK відмовляє token із no expiry claim; Python-овий повертає 200, бо expires_at optional на його AccessToken, а None означає «no opinion». І Python 403 carries error_description="Required scope: incidents:read" without the scope parameter, який specification says servers should include. Verifier — не місце приймати library default: audience check пишете ви в будь-якій мові, як і expiry.

Один чесний nit із того самого run. GET на endpoint відповів 404 на Express wiring і 400 Bad Request: Missing session ID на Python-овому, тоді як specification asks for 405 Method Not Allowed і де «session ID» — vocabulary, яку ця revision removed. Жодне не dangerous; обидва — shape of an ecosystem mid-migration.

Остання частина shipping — де публікувати, і тут є відповідь із числом. Crawled today, every server in the official registry at its latest version:7

servers
total (latest version, not deleted)28 170
active / deprecated27 853 / 317
ship at least one installable package13 065
remote only — a URL, nothing to install14 696
npm8 275
PyPI3 603
OCI images867
mcpb bundles706
NuGet / Cargo107 / 43

Два прочитання, спрямовані в протилежні боки. За published servers npm веде 2.3 до 1 — число, яке цитують, коли кажуть, що ecosystem is TypeScript. За downloads веде Python: за останні тридцять днів mcp взяв 286.7 million проти @modelcontextprotocol/sdk із 194.7 million, ще до додавання fastmcp із 72.1 million.7 Обидва — Tier 1, normative schema — це schema.ts, а офіційний tutorial «Build an MCP server» відкривається на вкладці Python.1 Яка б половина була у вас у голові, інша теж правдива.

І рядок, важливіший за обидва: понад половина registry — 14 696 із 28 170 — не має чого встановлювати. Це web services. Transport tallies підтверджують з іншого боку: із 14 290 package entries 13 787 declare stdio; із 16 640 remote entries 15 570 declare Streamable HTTP, а 1 070 still declare deprecated HTTP+SSE. Тож «MCP server — це subprocess на вашому laptop» описує shrinking minority, і кожному з 14 696 потрібен розділ вище, а не environment variable.

Показати подробиці

Свідомо bilingual, і прецедент для цього.

Це єдиний bilingual розділ курсу, бо чесна відповідь розділяється: registry is npm-first and downloads are Python-first, одночасно, сьогодні. Написати один із двох означало б віддати половину питання й неправильно описати ecosystem. Прецедент відкритий — Hugging Face MCP Course серед prerequisites перелічує «Experience with at least one programming language (Python or TypeScript examples will be shown)» і навчає обом.8 Protocol, чия цінність — кількість implementations, є поганим місцем для монолінгвізму.

Датований розділ: усе вище має строк придатності

Посилання на розділ: Датований розділ: усе вище має строк придатності

Прочитано й виміряно 7 вересня 2026 року, проти protocol revision 2026-07-28.

value
@modelcontextprotocol/sdk1.30.0, published 27 July 2026; 4 322 438 bytes unpacked, 693 files, 17 direct dependencies
latest revision it implements2025-11-25
mcp (PyPI)2.1.1, published 25 August 2026; 357 912-byte wheel, plus mcp-types 2.1.1 at 69 656 bytes
latest revision it implements2026-07-28
SDK tiersTypeScript, Python, C#, Go, Rust at Tier 1; Java, Ruby at Tier 2; Swift, PHP, Kotlin at Tier 3
registry servers28 170
downloads, last 30 daysmcp 286 653 871 · fastmcp 72 097 269 · @modelcontextprotocol/sdk 194 679 333

Одна migration note, яка не є числом. У mcp 2.x, FastMCP перейменовано на MCPServer, і майже кожен tutorial онлайн досі відкривається зі старого import. SDK ships module, єдина purpose якого — пояснити це, що є найуважнішою deprecation у цьому розділі:

from mcp.server.fastmcp import FastMCPTEXT
ModuleNotFoundError: No module named 'mcp.server.fastmcp'. This is mcp 2.x,
where FastMCP was renamed to MCPServer (from mcp.server.mcpserver import
MCPServer) and other APIs changed; see the migration guide … or pin 'mcp<2'
to keep running v1 code.

З таблицею перед очима recommendation нудна, і це добрий знак.

Якщо server живе всередині web application, який ви вже запускаєте, пишіть його TypeScript. Той самий process, той самий deploy, той самий request handler; Streamable HTTP — це endpoint, який ви додаєте поруч з іншими; а 13.9 MiB і 145 ms безкоштовні, бо runtime вже був up. Це більшість із 14 696 remote servers.

Якщо server обгортає data tooling, пишіть його Python. Те, що ви exposing, — це pandas, warehouse client, notebook's worth of transforms, і server іншою мовою був би subprocess call у schema. Сімсот мілісекунд import у service, що starts once, — не cost; у subprocess, який host relaunches all day, — cost.

І поки що revision row перекриває обидва. Якщо вам потрібен 2026-07-28 — multi-round-trip requests, resultType, cache hints, server/discover — один із двох SDK має це сьогодні, а інший ні.

Тепер ви можете ship той самий server будь-якою мовою, захищати вибір таблицею замість preference, запускати його через обидва live transports і давати йому token, який він відхилить.

Те, що ви збудували, все ще function: schema, endpoint, deterministic thing, яку модель invokes. Цілий клас knowledge не вміщується в цю форму — як ми пишемо postmortem, які поля потрібні нашим incident reports, у якому порядку ми робимо речі й чому. Це procedure, це prose, і заштовхувати це в tool description — саме так system prompts розростаються до двох тисяч token, оплачуваних на кожному single turn незалежно від того, чи conversation про інциденти.

Розділ 28 — інша відповідь: folder із SKILL.md усередині, який модель reads instead of calls, loaded in three levels, so that reference material costs almost nothing until the turn it is needed. Він не має main language, і це перше, чого він навчає.


Усе тут виміряно 7 вересня 2026 року на Node 22.22.3 і Python 3.14.4, проти @modelcontextprotocol/sdk 1.30.0 з zod 3.25.76 і mcp 2.1.1, кожен installed into its own throwaway directory. Timings — medians of 25 launches, wall clock from spawn до рядка з tools/list response; token counts — o200k_base via tiktoken over the JSON of each definition. Жоден paid API не викликався: тут нічому не потрібна model.

Два servers мають 81 і 63 непорожні рядки; один із їхніх трьох інструментів відтворено вище обома мовами, а інші чотири registrations differ only as described. Python SDK error-disclosure policy quoted from docstrings of ToolError and UnexpectedToolError in mcp/server/mcpserver/exceptions.py; pretty-printing default is pydantic_core.to_json(result, fallback=str, indent=2) in mcp/server/mcpserver/resources/types.py and utilities/func_metadata.py. Protocol-version constants are LATEST_PROTOCOL_VERSION in mcp_types/version.py and in the TypeScript SDK's types.js, both read from the installed packages rather than from a changelog.

  1. SDKs, modelcontextprotocol.io/docs/sdk, and Build an MCP server, modelcontextprotocol.io/docs/develop/build-server, both read 7 September 2026. Джерело tier table, речення «Each SDK provides the same functionality but follows the idioms and best practices of its language», language-tab order tutorial (Python, TypeScript, Java, Kotlin, C#, Ruby, Rust, Go) і logging rule, процитованого щодо print() і stdout. 2 3 4

  2. stdio transport, .../basic/transports/stdio. Джерело newline framing і правила purity stdout. Розділ 26 читає цю сторінку повністю; тут її цитовано заради рядка, який порушує broken server.

  3. MCP Inspector, modelcontextprotocol.io/docs/2026-07-28/tools/inspector, read 7 September 2026. One package, three clients behind one binary — web, --cli and --tui — sharing one core, one set of transports and one OAuth state on disk. CLI produced the catalogue traces here.

  4. Authorization, modelcontextprotocol.io/specification/2026-07-28/basic/authorization, read 7 September 2026. Джерело ролі resource-server; чотирьох token-handling clauses, процитованих повністю; вимоги, щоб servers implement RFC 9728, а clients use it for discovery; правил параметра resource і canonical-URI definition; issuer-validation table; deprecation of Dynamic Client Registration; таблиці 401/403/400 і challenge insufficient_scope; а також stdio exemption: «Implementations using an STDIO transport SHOULD NOT follow this specification, and instead retrieve credentials from the environment.» 2 3 4

  5. Streamable HTTP, .../basic/transports/streamable-http, and Transports overview, .../basic/transports. Джерело single-endpoint POST rule, dual Accept requirement, header MCP-Protocol-Version and its must-match-the-body rule, headers Mcp-Method and Mcp-Name described as «REQUIRED for compliance», removal of the GET stream, sessions and Last-Event-ID, guidance 405, mandatory Origin validation, and classification of the 2024-11-05 HTTP+SSE transport as Deprecated under SEP-2596. 2 3 4

  6. Чотири, на які спирається specification, разом із draft, який вона profiles: The OAuth 2.1 Authorization Framework, draft-ietf-oauth-v2-1-13. Campbell, B., Bradley, J. and Tschofenig, H., Resource Indicators for OAuth 2.0, RFC 8707, February 2020 — параметр resource і audience, яку він binds. Jones, M.B., Hunt, P. and Parecki, A., OAuth 2.0 Protected Resource Metadata, RFC 9728, April 2025 — document, на який points 401. Meyer zu Selhausen, K. and Fett, D., OAuth 2.0 Authorization Server Issuer Identification, RFC 9207, March 2022 — параметр iss і exact-string comparison. Richer, J. (ed.) et al., OAuth 2.0 Dynamic Client Registration Protocol, RFC 7591, July 2015, deprecated for this use. And Jones, M. and Hardt, D., The OAuth 2.0 Authorization Framework: Bearer Token Usage, RFC 6750, October 2012, section 3, for the WWW-Authenticate challenge shape above.

  7. Official MCP registry, registry.modelcontextprotocol.io/v0/servers, crawled 7 September 2026 with version=latest: 282 pages, 28 170 servers, tallied by registryType over distinct server names. Download figures: api.npmjs.org/downloads/point/last-month for @modelcontextprotocol/sdk (194 679 333 for 8 August – 6 September 2026) and pypistats.org/api/packages/<name>/recent for mcp and fastmcp, both read the same day. Package sizes come from the npm registry document and the PyPI JSON API. 2

  8. MCP Course, Hugging Face, huggingface.co/learn/mcp-course, unit 0, read 7 September 2026: серед prerequisites — «Experience with at least one programming language (Python or TypeScript examples will be shown)».

Готові довірити вибір моделі LIA?

Створюйте з усіма моделями ШІ в одному місці — почніть безкоштовно вже сьогодні.