Перейти к основному содержимому
БЛОГ

MCP Server Card: путь, схема и проверка карточки

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

Автор: IMozzОбновлено 2026-10-23
Карточка MCP-сервера: разбор — aiSiteReady

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[]Само соединение
Какие версии протокола?Подсказка, supportedProtocolVersionsserver/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 в карточке показывает, с какой эпохой клиент встретится.

Обнаружение, затем подключение: запросить AI Catalog и карточку по URL MCP плюс /server-card, выбрать remote, затем server/discover или initialize и tools/list.

Когда я делал карточку для 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 (октябрь 2025) и SEP-1960 (декабрь 2025) использовали .well-known и закрыты; SEP-2127 рекомендует URL MCP плюс /server-card.

В самом 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Да, внутри remotestreamable-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.

Устройство MCP Server Card: четыре обязательных поля и массив remotes с type, url и версиями протокола. Инструментов, capabilities и учётных данных в ней нет.

Там, где черновик и сканер расходятся, проверка мягче к содержимому и шире по путям. По состоянию на октябрь 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-catalogRFC 9727
Индекс Agent SkillsКаким процедурам следовать агенту?/.well-known/agent-skills/index.jsonЧерновик, не в IANA
Метаданные защищённого ресурсаКак получить токен?/.well-known/oauth-protected-resourceRFC 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-заглушка с 200PARTIAL (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, сканер только для чтения, который проверяет, может ли ИИ-агент прочитать и вызвать сайт. Сканер серверно рендерит собственный контент как рабочий пример.

Частые вопросы

Это официальное расположение карточки MCP-сервера — /.well-known/mcp/server-card.json?
Нет. Этот путь пришёл из SEP-1649, который закрыли в январе 2026 года и продолжили как SEP-2127. Текущий черновик разрешает держать карточку по любому незарезервированному URI, рекомендует URL MCP-эндпоинта плюс /server-card и ждёт, что клиенты найдут её через AI Catalog по адресу /.well-known/ai-catalog.json. Часть сканеров всё ещё опрашивает старый путь, поэтому дублировать карточку и по этому пути — разумный компромисс.
Заменяет ли карточка MCP-сервера initialize или tools/list?
Нет. Карточка содержит только идентификационные данные, удалённые эндпоинты и поддерживаемые версии протокола. Инструменты, ресурсы, промпты и capabilities остаются в живых вызовах протокола вроде tools/list, а черновик велит клиентам при расхождении доверять живым значениям. В ревизии 2026-07-28 initialize нет: серверы обязаны реализовать server/discover, а клиент может вызвать его первым. Более ранние ревизии начинают с initialize.
Какие поля обязательны в карточке MCP-сервера?
По текущей схеме черновика — четыре: $schema, name, version и description. Имя пишут в формате reverse-DNS ровно с одним слэшем, например com.example/weather, а описание занимает от 1 до 100 символов. Массив remotes в схеме необязателен, но без remote с type и url клиенту просто не к чему подключаться.
Можно ли указывать в карточке учётные данные или список инструментов?
Нельзя ни то ни другое. Карточки публичны по замыслу и не должны содержать учётные данные, токены или внутренние эндпоинты. Черновик также исключает инструменты, ресурсы и промпты, потому что статический список расходится с тем, что сервер возвращает в живой аутентифицированной сессии. Считайте каждое поле карточки справочным и никогда не используйте его как основание для решений о доступе.