MCP Server Card: шлях, схема й перевірка картки
MCP Server Card — JSON-файл, за яким клієнт знаходить ваш віддалений MCP-сервер. Поля, хостинг, перевірка через curl і статус чернетки на жовтень 2026.

MCP Server Card — картка MCP-сервера — це невеликий публічний JSON-документ, який повідомляє клієнтові, де живе ваш віддалений MCP-сервер і як до нього підключитися: ім'я, версія, URL ендпоінта, транспорт, підтримувані версії протоколу — ще до відкриття з'єднання. Це метадані для виявлення, а не хендшейк: вона нікого не автентифікує й не перелічує ваші інструменти.
У стеку готовності спершу йдуть читабельні сторінки, а поверх них — поверхні, доступні для виклику. Посібник про те, як зробити сайт доступним для виклику агентами, ставить картки серверів поруч з Agent Skills і картками A2A. Цей посібник присвячено самій картці: кожне поле, хостинг, перевірка й те, на якому етапі стандартизації вона перебуває в жовтні 2026 року. Тільки знайомитеся з базовим шаром? Почніть із матеріалу про те, що таке готовність до ШІ-агентів.
Ключові висновки
- Картка відповідає на одне питання до будь-якого з'єднання: де сервер і які версії протоколу він підтримує. Інструменти, capabilities і вхід лишаються в живих викликах (SEP-2127).
- Офіційним стандартом вона поки не стала. SEP-2127 — відкритий pull request з міткою
in-review, а поточна ревізія специфікації 2026-07-28 картки не визначає (версіювання MCP).- Розташування змінювалося. Поточна чернетка рекомендує
<mcp-url>/server-cardта AI Catalog за адресою/.well-known/ai-catalog.json; старі чернетки використовували/.well-known/mcp/server-card.json, і сканери, наш зокрема, його досі опитують.- Схема чернетки вимагає
$schema,name,versionіdescription; щоб картка була корисною, додайте записremotesз абсолютнимurl(schema.json).- Перевірка
mcp_server_cardв aiSiteReady читає картки й у формі чернетки, і в старій формі: за старими шляхами.well-known, за/server-cardі через AI Catalog. Але MCP-сесію вона не відкриває ніколи, тож ендпоінт перевірте самі черезcurl.
Яку задачу розв'язує MCP Server Card?
Вона дає клієнтові змогу дізнатися про віддалений MCP-сервер, не знаючи наперед, куди підключатися. Сьогодні людина вставляє URL ендпоінта в конфіг клієнта. З карткою клієнт, IDE чи краулер читає один статичний документ, показує ім'я, обирає транспорт, звіряє версії протоколу й лише потім підключається. У SEP як сценарії названо автоналаштування, обхід доменів у пошуках серверів і показ метаданих без звернення до кожного ендпоінта (SEP-2127).
Картка навмисно маленька. Таблиця показує, що вона несе, а що мусить лишатися «живим».
| Питання клієнта | Є в картці? | Авторитетна відповідь |
|---|---|---|
| Де сервер, яким транспортом? | Так, remotes[] | Саме з'єднання |
| Які версії протоколу? | Підказка, supportedProtocolVersions | server/discover (2026-07-28) або initialize (раніше) |
| Ім'я, версія, опис? | Так, довідково | Ідентифікаційні дані, які сервер повідомляє в рантаймі |
| Які інструменти, ресурси, промпти? | Ні, за задумом | tools/list, resources/list, prompts/list |
| Хто може викликати, як увійти? | Ні | 401 плюс WWW-Authenticate і метадані RFC 9728 |
Інструменти виключено свідомо: статичний документ не може описати поверхню, що залежить від користувача, сесії та feature flags (SEP-2127). Навіть ті поля, що в картці є, мають довідковий характер. Клієнтам не можна спиратися на них у рішеннях про доступ, а за розбіжності слід довіряти живим значенням (discovery.md).
Хендшейк теж змінився. Ревізія 2026-07-28 прибрала initialize і сесії на рівні протоколу; кожен запит несе свою версію протоколу, а server/discover — обов'язковий RPC, який клієнт може викликати першим (changelog). Сервери на ревізіях 2025-11-25 і раніших досі починають з initialize, а supportedProtocolVersions у картці показує, з якою епохою клієнт зустрінеться.
Коли я робив картку для aiSiteReady, найдешевшим шляхом до зеленої перевірки була статична картка, що вказує на будь-який коректний URL, адже сканер перевіряє форму картки, а не хендшейк. Я від цього відмовився (чому — пояснює запис про архітектурне рішення в проєкті). MCP-ендпоінт зобов'язаний працювати за JSON-RPC, а наш REST API так не працює, тож картка оголошувала б ендпоінт, яким не зміг би скористатися жоден MCP-клієнт.
Натомість я написав справжній MCP-сервер з одним інструментом scan_url, розгорнув його на /api/mcp і спрямував картку туди. Поки я писав цей посібник, з'ясувалося, що в продакшені він відхиляв будь-який публічний запит з 421, а картка при цьому проходила перевірку. Зараз це виправлено; історія нижче.
Чи стала картка MCP-сервера офіційним стандартом?
Ні. Станом на 2 жовтня 2026 року це пропозиція на розгляді: до ядра специфікації MCP вона не входить і в IANA не зареєстрована.
- Пропозиція. SEP-2127 — pull request треку Extensions, відкритий 21 січня 2026 року як продовження SEP-1649. Мітку
in-reviewвін має з 7 серпня, а станом на 2 жовтня, коли я перевіряв, був ще відкритий (pull request). Файл SEP у його гілці вже має позначку «Final», але нічого не влито. - Схема. Вона живе в репозиторії, перейменованому з
experimental-ext-server-cardнаext-server-card, але в README якого роботу досі названо експериментальною, а не прийнятим розширенням MCP (репозиторій). - Ядро специфікації. Актуальна ревізія 2026-07-28 (версіювання). Картки в ній немає, а в дорожній карті від 22 серпня 2026 року серверні картки не входять до п'яти пріоритетів (дорожня карта).
- SDK. Два pull request — для Python і Go, — які SEP називає референсними реалізаціями, лишалися відкритими (python-sdk #2951, go-sdk #1024). Не вважайте, що конкретний клієнт читає картки.
- IANA. У реєстрі well-known URI, оновленому 16 вересня 2026 року, є
agent-card.json,api-catalogіoauth-protected-resource, але немає нічого дляmcp,ai-catalogчиserver-card(IANA).
Де має лежати картка MCP-сервера?
Чернетки розходяться й у тому, де має лежати картка.
У самому SEP-1649 названо два шляхи: /.well-known/mcp.json в анотації та /.well-known/mcp/server-card.json у тексті (SEP-1649). SEP-1960 хотів /.well-known/mcp і був закритий наступного дня після відкриття (SEP-1960).
Поточна чернетка відмовляється від .well-known для самої картки: припустимий будь-який незарезервований URI, рекомендовано <streamable-http-url>/server-card, а клієнти знаходять її через AI Catalog. Аргумент такий: .well-known призначений для метаданих усього сайту, картка описує один застосунок, а каталог і так повідомляє URL (discovery.md). Сам каталог — теж чернетка (ai-catalog.io).
Розійшлися й ті, хто публікує картки. 2 жовтня 2026 року https://github.com/.well-known/ai-catalog.json містив один запис типу application/mcp-server-card+json із посиланням на https://api.githubcopilot.com/mcp/server-card — ця картка проходить валідацію за експериментальною схемою, — тоді як nuxt.com досі віддає стару форму serverInfo плюс endpoints за адресою /.well-known/mcp/server-card.json. Це мої спостереження через curl, а не огляд ринку.
Моя рекомендація — оцінне судження, а не частина якоїсь чернетки: публікуйте картку там, куди вказує чернетка, із записом у каталозі, а той самий файл дублюйте на /.well-known/mcp/server-card.json. Дубль коштує один статичний файл і зберігає працездатність сьогоднішніх сканерів та старих клієнтів.
Які поля містить валідна MCP Server Card?
Валідній картці потрібні чотири поля — $schema, name, version і description, — а на практиці ще запис remotes з type та абсолютним url. В останньому стовпці — що робить із кожним полем перевірка aiSiteReady станом на жовтень 2026 року.
| Поле | Обов'язково | Що з ним робить клієнт | Перевірка aiSiteReady |
|---|---|---|---|
$schema | Так | Називає формат картки (URL схеми v1) | Не вимагається; разом із remotes[] позначає форму чернетки |
name | Так | Стабільний id, reverse-DNS, один слеш: com.example/weather | Не має бути порожнім; підходять і шаблон чернетки з одним слешем, і ім'я в стилі reverse-DNS із крапкою; старі форми: будь-яке ім'я |
version | Так | Версія сервера, рекомендовано semver | Не вимагається |
description | Так, 1–100 символів | Рядок для людей | Потрібен для розпізнавання форми, якщо немає $schema; опис, довший за 100 символів, однаково проходить |
title, websiteUrl, icons, repository | Ні | Назва, сайт, іконки, вихідний код | Ігноруються |
remotes[] | Ні, але потрібен для підключення | Ендпоінти, до яких підключаються | Кожен url, доповнений відносно адреси картки, має бути коректним абсолютним URL; нуль remotes однаково проходить |
remotes[].type | Так, усередині remote | streamable-http або sse | Записується; невідомі типи відзначаються, але не призводять до FAIL |
remotes[].url | Так, усередині remote | Абсолютний http(s) URL або шаблон із {variable} | Відносний доповнюється до абсолютного й приймається; шаблон із {variable} приймається як є; localhost, хост з однієї мітки або порт, крім 80 та 443, дають PARTIAL |
remotes[].supportedProtocolVersions | Ні | Дає змогу обрати версію до підключення | Читається; значення не у форматі дати YYYY-MM-DD дає PARTIAL |
remotes[].headers, remotes[].variables, _meta | Ні | Заголовки, значення від користувача, розширення вендора | Ігноруються |
Перелік узято зі schema.json чернетки. Одна дивина: URL, якого вимагає схема в $schema, 2 жовтня відповідав 404, тож вважайте його ідентифікатором і валідуйте за #/$defs/ServerCard усередині файлу схеми. Кореневої схеми у файлі немає, тому валідатор, якому передали файл як є, прийме будь-який JSON.
Найменша валідна картка складається з чотирьох обов'язкових полів. Вона проходить валідацію, але підключитися за нею не можна: remote у ній не вказано.
{
"$schema": "https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json",
"name": "com.example/weather",
"version": "1.0.0",
"description": "Weather lookups and forecasts."
}
Корисна картка додає remote і підказку про версії протоколу, щоб клієнт міг вирішити ще до підключення.
{
"$schema": "https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json",
"name": "com.example/weather",
"version": "1.0.0",
"description": "Weather lookups and forecasts.",
"title": "Example Weather",
"websiteUrl": "https://example.com",
"remotes": [
{
"type": "streamable-http",
"url": "https://api.example.com/mcp",
"supportedProtocolVersions": ["2025-11-25", "2026-07-28"]
}
]
}
Обидва документи проходять #/$defs/ServerCard зі schema.json чернетки у валідаторі JSON Schema 2020-12.
Там, де чернетка й сканер розходяться, перевірка м'якша до вмісту й ширша за шляхами. Станом на жовтень 2026 року вона розпізнає п'ять форм картки:
- поточну чернетку, яку перевірка називає
server_card_v1; - список
serverInfoплюсendpoints[]з ранішої чернетки, який віддає nuxt.com; - форму SEP-1649 із
serverInfoтаtransport; - об'єкт
mcp_versionплюсendpointsіз SEP-1960; - документ виявлення в стилі платформ документації.
Форми serverInfo плюс endpoints[], SEP-1649 і SEP-1960 і далі приймаються, але в доказах звіту позначаються як застарілі. Картка у формі чернетки без $schema чи version або з описом, довшим за 100 символів, теж проходить. Кожне відхилення від схеми чернетки перевірка записує в докази й статус не змінює, бо форма поки лишається чернеткою.
До 4 жовтня 2026 року моя власна картка була в ранішій формі serverInfo плюс endpoints[]. У перевірці aiSiteReady вона отримувала чистий pass, але експериментальну схему не проходила з чотирьох причин:
- у неї не було
$schemaіversion; - в імені не було слеша;
- опис займав 269 символів;
- у ній був список
tools, який чернетка виключає навмисно.
Такою була ціна того, що я орієнтувався на толерантність сканера, а не на чернетку. Тепер картка в поточній формі. Ось робочий документ із продакшену повністю:
curl -sS https://www.ai-siteready.com/.well-known/mcp/server-card.json
{
"$schema": "https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json",
"name": "com.ai-siteready/aisiteready",
"title": "aiSiteReady",
"description": "Scan a public URL for AI-agent readiness and get a scored, actionable report.",
"version": "0.1.0",
"websiteUrl": "https://www.ai-siteready.com/",
"remotes": [
{
"type": "streamable-http",
"url": "https://www.ai-siteready.com/api/mcp/",
"supportedProtocolVersions": [
"2025-06-18"
]
}
]
}
Вона проходить #/$defs/ServerCard, а юніт-тест звіряє її з копією схеми в репозиторії, тож зміна, яка порушує відповідність, валить CI. Перевірка й далі ставить їй pass: форма server_card_v1, відхилень немає.
Одну частину власної рекомендації я поки не виконав. Станом на жовтень 2026 року картка лежить лише за старим шляхом, без /api/mcp/server-card і без AI Catalog, тож клієнт, який іде за поточною чернеткою, її не знайде.
Як розмістити картку й пов'язати її зі справжнім MCP-ендпоінтом?
Віддавайте її як статичний публічний GET за HTTPS з JSON-типом вмісту, відкритим CORS і валідаторами кешу, а справжній ендпоінт указуйте в remotes[].url абсолютним URL. Чернетка просить такого (discovery.md).
- Тип вмісту.
application/mcp-server-card+jsonскрізь, де розміщено картку; клієнтам рекомендовано слати його вAccept. Перевірка aiSiteReady приймаєapplication/jsonі будь-який тип+json, усе інше позначає як PARTIAL. - CORS.
Access-Control-Allow-Origin: *, з дозволенимGETі відкритимETag, щоб картку могли прочитати браузерні клієнти. Це безпечно, бо картка публічна й доступна лише для читання. - Кешування.
Cache-Control: public, max-age=3600,ETagі відповідь304 Not ModifiedнаIf-None-Match. - Транспорт. HTTPS у продакшені, звичайний HTTP лише для локальної розробки, а також обмеження частоти запитів на ендпоінті.
Для статичного файлу все це вміє nginx. Пам'ятайте: add_header усередині location типово замінює успадковані директиви, а без always пропускається у відповідях з помилкою (документація nginx). Крім того, статичний location відповідає на CORS-preflight OPTIONS кодом 405, тому останній рядок повертає 204.
location = /mcp/server-card {
alias /srv/mcp/server-card.json;
default_type application/mcp-server-card+json;
add_header Access-Control-Allow-Origin "*" always;
add_header Access-Control-Allow-Methods "GET" always;
add_header Access-Control-Allow-Headers "Content-Type, If-None-Match" always;
add_header Access-Control-Expose-Headers "ETag" always;
add_header Cache-Control "public, max-age=3600" always;
if ($request_method = OPTIONS) { return 204; }
}
aiSiteReady віддає картку з невеликого маршруту Nitro, щоб URL ішли за доменом деплою. До 4 жовтня 2026 року він виставляв лише тип вмісту й кеш на годину. Відтоді він дотримується правил хостингу з чернетки:
MCP_SERVER_CARD_CORS_HEADERSмістить перелічені вище заголовки CORS;- кеш живе годину;
ETagперетворює збіжнийIf-None-Matchна304;- клієнт, який просить
application/mcp-server-card+json, отримує цей медіатип, і кожен медіатип має свійETag.
CORS-preflight обробляє окремий маршрут для OPTIONS. Це справжній обробник у тому вигляді, у якому він був у жовтні 2026 року, скорочений:
export default defineEventHandler((event) => {
const config = useRuntimeConfig(event);
const siteUrl = typeof config.public.siteUrl === "string" ? config.public.siteUrl : "";
const contentType = pickMcpServerCardContentType(getRequestHeader(event, "accept"));
const { body, etag } = getMcpServerCardRepresentation(siteUrl, contentType);
setResponseHeaders(event, MCP_SERVER_CARD_CORS_HEADERS);
setResponseHeader(event, "Cache-Control", MCP_SERVER_CARD_CACHE_CONTROL);
setResponseHeader(event, "ETag", etag);
setResponseHeader(event, "Content-Type", contentType);
setResponseHeader(event, "Vary", "Accept");
if (ifNoneMatchMatches(getRequestHeader(event, "if-none-match"), etag)) {
setResponseStatus(event, 304);
return "";
}
return body;
});
Цей siteUrl важливіший, ніж здається, бо кожен URL у картці має бути абсолютним і публічним. Без публічного URL сайту, наприклад на машині розробника, моя функція buildMcpServerCard підставляє origin dev-сервера, і ендпоінт указує на localhost з портом 5173.
Сканер пропускає URL з картки через той самий нормалізатор, що й цілі сканування, а він відкидає хости з однієї мітки та порти, крім 80 і 443. Тому на localhost перевірка сягає щонайбільше PARTIAL; у продакшені, де задано NUXT_PUBLIC_SITE_URL, вона проходить.
Картка настільки добра, наскільки добрий ендпоінт за нею. Справжній сервер налаштовано як stateless з відповідями у звичайному JSON, тож не треба ні зберігати сесію, ні змушувати проксі буферизувати потік подій:
mcp = FastMCP(
name=MCP_SERVER_NAME,
instructions=_MCP_INSTRUCTIONS,
stateless_http=True,
json_response=True,
streamable_http_path="/",
transport_security=build_transport_security(settings),
)
application.mount("/api/mcp", mcp.streamable_http_app())
Останнього аргументу спершу не було. Через це й виникав 421, про який ідеться нижче.
Голий /api/mcp відповідає 307 і перенаправляє на /api/mcp/, тому картка вказує варіант зі слешем, і клієнти пропускають редирект.
Які межі безпеки має картка сервера?
Картка сервера публічна, має довідковий характер і не автентифікована: у ній не має бути секретів, вона може застаріти чи виявитися підробленою й не дає жодного доступу.
- Вона публічна. Жодних облікових даних, токенів, внутрішньої топології та приватних ендпоінтів (discovery.md).
- Вона довідкова. Застаріла чи підроблена картка може відвести клієнта не на той сервер, перш ніж він побачить живі значення, тож тримайте її точною.
- Вона не несе автентифікації. Захищений MCP-сервер зобов'язаний реалізувати OAuth Protected Resource Metadata (RFC 9728) і вказати клієнтам на ці метадані — через
resource_metadataу заголовкуWWW-Authenticateвідповіді401або за well-known URI (авторизація MCP). Полеheadersкартки лише описує заголовки й жодних прав не надає. Дивіться OAuth discovery для ШІ-агентів.
Як картка сервера пов'язана з картками A2A, Agent Skills і каталогом API?
Вони доповнюють одне одного: кожен документ відповідає на одне питання у своєму місці. Картка каже, де MCP-сервер; решта — що ще ви публікуєте, як цим користуватися і як отримати доступ.
| Документ | На яке питання відповідає | Розташування | Статус |
|---|---|---|---|
| MCP Server Card | Де MCP-сервер? | Будь-який URI; рекомендовано <mcp-url>/server-card | Чернетка, не в IANA |
| AI Catalog | Які артефакти для агентів публікує домен? | /.well-known/ai-catalog.json | Чернетка, не в IANA |
| Картка агента A2A | Якого агента ви запускаєте? | /.well-known/agent-card.json | Зареєстровано в IANA |
| Каталог API | Які HTTP API і де їхні описи? | /.well-known/api-catalog | RFC 9727 |
| Індекс Agent Skills | Яких процедур має дотримуватися агент? | /.well-known/agent-skills/index.json | Чернетка, не в IANA |
| Метадані захищеного ресурсу | Як отримати токен? | /.well-known/oauth-protected-resource | RFC 9728 |
Статуси взято з реєстру IANA і з власного рядка статусу пропозиції Agent Skills. AI Catalog — місток чернетки між карткою та рештою домену, а запис для картки зовсім невеликий:
{
"specVersion": "1.0",
"entries": [
{
"identifier": "urn:air:example.com:mcp:weather",
"type": "application/mcp-server-card+json",
"url": "https://api.example.com/mcp/server-card"
}
]
}
Про сусідів читайте в посібниках з каталогу API, Agent Skills і OAuth discovery.
Які помилки ламають MCP Server Card?
Найнебезпечніша помилка — картка, що вказує на ендпоінт, який не працює: жоден сканер не впіймає її ззовні. Більшість зламаних карток ламаються одним із цих шести способів.
| Помилка | Що відбувається | Як виправити |
|---|---|---|
Картка вказує ендпоінт, який не працює (404, 405, 421) | Картка валідна, будь-яке з'єднання падає | Надішліть на ендпоінт справжній запит |
Відносний remotes[].url | Схема чернетки вимагає абсолютний; наша перевірка перетворює його на абсолютний і пропускає | Завжди пишіть URL з https:// |
localhost, хост з однієї мітки або порт, відмінний від 443, у продакшені | Перевірка дає PARTIAL (mcp_server_card_invalid); приватні IP і внутрішні імена хостів однаково проходять | Задайте публічний URL сайту; зупиняйте розгортання з помилкою, якщо його немає |
Хибний тип вмісту або HTML-заглушка з 200 | PARTIAL (mcp_server_card_wrong_content_type) або картки ніби немає | Віддавайте JSON; на неопубліковані шляхи повертайте 404 |
| Список інструментів чи capabilities у картці | Розходиться з живим сервером | Нехай клієнти викликають tools/list |
| Лише старий шлях або лише новий | Невидима для половини клієнтів і сканерів | Публікуйте на /server-card із записом у каталозі; дублюйте на старому шляху |
З першим рядком таблиці я зіткнувся на власному деплої, поки писав цей посібник. 2 жовтня 2026 року картка на www.ai-siteready.com проходила перевірку aiSiteReady, але POST tools/list на заявлений ендпоінт повернув 421 Invalid Host header. Саме так відповідає офіційний Python SDK, коли ввімкнено захист від DNS-rebinding, а заголовка Host немає в списку дозволених.
SDK (python-sdk 1.x) вмикає цей захист автоматично, якщо FastMCP лишає типовий хост 127.0.0.1, і тоді пропускає лише локальні хости (вихідний код python-sdk). Мій build_mcp_server не задавав ні host, ні transport_security, що збігається.
Виправлення потрапило в продакшен 4 жовтня 2026 року. Воно явно передає TransportSecuritySettings і лишає захист увімкненим, бо специфікація MCP вимагає від серверів перевіряти Origin (Streamable HTTP). Для сайту на www.example.com суть виглядає так:
from mcp.server.transport_security import TransportSecuritySettings
transport_security = TransportSecuritySettings(
enable_dns_rebinding_protection=True,
allowed_hosts=["localhost", "localhost:*", "www.example.com", "www.example.com:*"],
allowed_origins=["http://localhost:*", "https://www.example.com"],
)
Кожен хост указано двічі. Шаблон host:* у SDK збігається лише із заголовком Host, у якому є порт, а nginx з proxy_set_header Host $host передає голе ім'я. Клієнти поза браузером не надсилають заголовок Origin, і SDK їх за це не відхиляє.
Після деплою initialize і tools/list через публічний проксі відповіли 200, чужий Host і далі отримував 421, а чужий Origin — 403. Тепер деплой падає, якщо справжній initialize, надісланий на публічний URL, не повернув 200 з назвою сервера.
Сканер ендпоінт не викликає, тому таку помилку не впіймає; звідси й наступний розділ.
Як перевірити картку через curl і jq?
Перевіряйте три речі по черзі: заголовки картки, її поля та ендпоінт, на який вона вказує. Очікуйте 200, JSON-тип вмісту і, якщо ви дотримуєтеся чернетки, CORS та валідатори кешу.
CARD=https://api.githubcopilot.com/mcp/server-card
curl -sS -D - -o /dev/null -H 'Accept: application/mcp-server-card+json' "$CARD" \
| grep -i -E '^(HTTP|content-type|access-control-allow-origin|cache-control|etag)'
2 жовтня 2026 року це вивело 200, application/mcp-server-card+json, access-control-allow-origin: *, годинний cache-control і etag. Потім прочитайте поля:
curl -sS "$CARD" | jq '{name, title, remotes}'
{
"name": "io.github.github/github-mcp-server",
"title": "GitHub",
"remotes": [
{
"type": "streamable-http",
"url": "https://api.githubcopilot.com/mcp/"
}
]
}
Нарешті, викличте ендпоінт. Stateless-серверу вистачає одного POST; stateful-сервер на старій ревізії повертає Mcp-Session-Id з initialize, і його треба передавати далі. У 2026-07-28 initialize немає: перший запит несе версію протоколу в заголовку й у _meta. Цей запит повторює форму server/discover зі специфікації; на живому сервері 2026-07-28 я його не запускав.
URL=$(curl -sS "$CARD" | jq -r '.remotes[0].url')
curl -sS -X POST "$URL" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H 'MCP-Protocol-Version: 2026-07-28' \
-H 'Mcp-Method: server/discover' \
-d '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"curl","version":"0"},"io.modelcontextprotocol/clientCapabilities":{}}}}'
Очікуйте JSON-RPC-результат із supportedVersions або UnsupportedProtocolVersionError зі списком версій, які сервер таки підтримує (server/discover).
Потім перенесіть процедуру в CI. Скрипт вважає 200, 400 і 401 ознакою «ендпоінт живий» (захищений сервер відповідає 401, а сервер лише нової епохи може відповісти 400 на голий tools/list), а будь-який інший статус, наприклад 404, 405, 421 чи 5xx, — відмовою. У жовтні 2026 року картка GitHub пройшла цей скрипт. До 4 жовтня наша не проходила перевірку полів, бо була в старій формі, а наступний крок упіймав би 421 на ендпоінті. Після обох виправлень проходить і наша.
#!/usr/bin/env bash
set -euo pipefail
CARD="${1:?usage: check-card.sh https://api.example.com/mcp/server-card}"
HEADERS="$(curl -sS -D - -o /dev/null -H 'Accept: application/mcp-server-card+json, application/json' "$CARD")"
grep -qE '^HTTP/[0-9.]+ 200' <<<"$HEADERS" || { echo 'FAIL: card did not return 200'; exit 1; }
grep -qiE '^content-type: application/(mcp-server-card\+)?json' <<<"$HEADERS" \
|| { echo 'FAIL: Content-Type is not a JSON media type'; exit 1; }
BODY="$(curl -sS "$CARD")"
jq -e '(.["$schema"] | type == "string")
and (.name | type == "string" and test("^[a-zA-Z0-9.-]+/[a-zA-Z0-9._-]+$"))
and (.version | type == "string")
and (.description | type == "string" and length >= 1 and length <= 100)' <<<"$BODY" >/dev/null \
|| { echo 'FAIL: $schema, name, version or description is missing or invalid'; exit 1; }
jq -e '[.remotes[]?.url] | length > 0 and all(test("^https://"))' <<<"$BODY" >/dev/null \
|| { echo 'FAIL: remotes[].url must be an absolute https URL'; exit 1; }
URL="$(jq -r '.remotes[0].url' <<<"$BODY")"
CODE="$(curl -sS -o /dev/null -w '%{http_code}' -X POST "$URL" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}')"
case "$CODE" in
200|400|401) echo "OK: card is well-formed, endpoint answered $CODE" ;;
*) echo "FAIL: endpoint $URL answered $CODE"; exit 1 ;;
esac
Як aiSiteReady перевіряє картку MCP-сервера?
Станом на жовтень 2026 року перевірка mcp_server_card в aiSiteReady, що входить до категорії Protocol/API, шукає картку і там, де її розміщує чернетка, і за старими well-known-шляхами й перевіряє її форму та URL, не звертаючись до ендпоінта.
Вона запитує п'ять well-known-шляхів (/.well-known/mcp, /.well-known/mcp/server-card.json, /.well-known/mcp.json, /.well-known/mcp-server.json, /.well-known/mcp/server.json) і читає заголовок Link кореневого документа в пошуках значень rel, рівних mcp-server і mcp-server-card — це власна толерантність сканера, а не те, що я знайшов у чернетках MCP. Крім того, вона вважає просканований URL адресою MCP і запитує його з доданим /server-card. Потім читає /.well-known/ai-catalog.json: бере вбудовані картки й завантажує до п'яти записів application/mcp-server-card+json. Тому під час сканування https://example.com/ перевіряється https://example.com/server-card. Картку за адресою https://api.example.com/mcp/server-card перевірка знайде через каталог або якщо сканувати сам https://api.example.com/mcp.
Запити йдуть через SSRF-безпечний fetcher, JavaScript не виконується, а URL ендпоінтів із картки не запитуються ніколи.
PASS — валідна картка без м'яких зауважень (soft issues). PARTIAL — картка є, але неповна, віддана з хибним типом вмісту, її protocolVersion або запис у supportedProtocolVersions не читається як дата, або вона містить URL ендпоінта, який відкидає нормалізатор URL сканера, наприклад хост з однієї мітки на кшталт localhost або порт, відмінний від 80 і 443. Шаблони чернетки з {variable} приймаються як є. UNKNOWN — блокування бота чи збій запиту завадили прочитати відповідь. FAIL — картку не знайдено.
PASS, PARTIAL і FAIL дають 1,0, 0,5 і 0 за середньої серйозності; уся категорія вимкнена в профілі Контентний сайт і важить більше в профілі API/застосунок. Ваги описано на сторінці методології, а 30-хвилинний аудит готовності показує, де ця перевірка стоїть у повному проході.
Запустіть безкоштовне сканування, щоб побачити, у якій формі ваша картка, чи є URL усередині неї коректними абсолютними URL та які виправлення робити першими, — англійською, українською чи російською.
Якщо коротко: MCP Server Card — це публічний довідковий покажчик, який каже, де ваш сервер і які версії він підтримує, і далі ні в що не втручається. Поки це чернетка, тож публікуйте картку там, куди вказує чернетка, дублюйте там, де дивляться сканери, тримайте кожен URL абсолютним і публічним та перевіряйте ендпоінт за нею справжнім запитом, бо більше його ніхто не перевірить.
IMozz має 20 років у розробці ПЗ, а останній рік будує продукти за допомогою LLM. Він розвиває aiSiteReady, сканер лише для читання, що перевіряє, чи може ШІ-агент прочитати й викликати сайт. Сканер серверно рендерить власний контент як робочий приклад.