Skip to main content
Glama

MCP Eval Demo

Рабочий пример применения оценочных проверок, чтобы убедиться, что LLM-агент действительно умеет пользоваться MCP-сервером, — а не только того, что код сервера корректен.

Модульные тесты отвечают на вопрос «удаляет ли delete_note заметку?» Но они не могут ответить на вопросы, которые решают, хорош ли MCP-сервер на практике:

  • Находит ли агент нужную заметку, когда пользователь описывает её словами, а не идентификатором?

  • Замечает ли он, что превью из списка было обрезано, или отвечает по половине заметки?

  • Понимает ли, что update_note перезаписывает содержимое, или молча уничтожает пользовательские данные, когда его просят «добавить строку в мой список покупок»?

  • Приходит ли он в себя после сообщения об ошибке или сдаётся?

Это свойства поверхности инструментов — имён, описаний, схем, форм результатов, текста ошибок — и единственный способ их проверить — запустить реального агента против сервера и оценить, что он сделал. Именно для этого предназначен этот репозиторий.

Статус

MCP-сервер, его инфраструктура и стенд для оценочных проверок — всё на месте.

Related MCP server: MCP Notepad Server

Сервер под тестом: Notes MCP

Записная книжка в памяти. Состояние живёт в процессе сервера и отбрасывается при выходе, поэтому каждый оценочный прогон начинается с одного и того же известного корпуса (см. seed.py).

Инструмент

Подсказка о поведении

Что делает

create_note

запись

Создаёт заметку; заголовки должны быть уникальными без учёта регистра.

get_note

только чтение

Возвращает полное содержимое одной заметки по id.

list_notes

чтение

Список заметок сначала те, что обновлены позже, в виде усечённых превью, с опциональной подстрокой query.

update_note

разрушающее

Перезаписывает заголовок и/или содержимое заметки.

delete_note

разрушающее

Окончательно удаляет заметку.

Несколько проектных решений приняты специально, чтобы оценочным проверкам было что ловить:

  • Идентификаторы, а не заголовки. Каждый изменяющий инструмент принимает note_id, поэтому агент, которому нужно изменить «мой список покупок», должен сначала найти идентификатор. Именно здесь агенты обычно пытаются угадать.

  • Усечённые превью. list_notes возвращает только первые 120 символов каждой заметки, помечая результат content_truncated и content_length. Агент, который отвечает на вопрос по содержимому прямо по списку, ошибается; хороший агент вызывает get_note.

  • Замена, а не добавление. update_note перезаписывает. «Добавь яйца в мой список покупок» — это то есть операция «прочитать-изменить-записать», и агент, пропустивший чтение, уничтожает данные.

  • Ошибки, которые учат. Каждая ошибка называет виновное значение и указывает на инструмент, который её решит, — так у агента есть путь вперёд, а не тупик.

Структура

src/notes_mcp/
  models.py    Pydantic models — also the tool input/output schemas the agent sees
  store.py     In-memory storage and its error types
  seed.py      Fixed corpus: stable ids and timestamps, so evals are reproducible
  server.py    MCP tool definitions, descriptions, and annotations
  cli.py       `notes-mcp` entry point
evals/
  agent.py       Builds the pydantic-ai agent under test + local trace capture
  task.py        One agent turn against a freshly seeded server — the thing evaluated
  evaluators.py  Custom pydantic-evals evaluators (tool-not-called, argument-contains)
  cases.yaml     The dataset itself: cases that probe specific MCP misuse patterns
  cases.py       Loads cases.yaml — registers the custom evaluators, picks the judge model
  __main__.py    `python -m evals` — runs the dataset against a live model
tests/
  test_store.py    Unit tests for the storage layer
  test_server.py   Protocol-level tests through a real MCP client session
scripts/
  lint.sh    Ruff + pyright + format check
  test.sh    Unit + protocol tests (fast, free)
  evals.sh   Agent-behaviour evals against a live model (slow, costs money)

Описания инструментов хранятся в виде констант уровня модуля в server.py, а не в инлайн-докстрингах. Формулировки описаний — это основное, что вы настраиваете в ответ на падение оценочной проверки, и хранение их в одном месте делает эти дифы читабельными.

Начало работы

Требуется uv и Python 3.12 (зафиксирован в .python-version).

uv sync                       # create .venv and install everything
uv run scripts/test.sh        # unit + protocol tests
uv run scripts/lint.sh        # ruff check, pyright (strict), format check
uv run pre-commit install     # optional: run the same checks on commit

Запуск сервера

uv run notes-mcp                        # stdio, seeded with the sample notes
uv run notes-mcp --empty                # stdio, no notes
uv run notes-mcp --transport streamable-http

Файл .mcp.json регистрирует stdio-сервер для этого проекта, поэтому MCP-хост, запущенный из этого каталога — например, Claude Code, — автоматически подхватывает сервер notes, и им можно управлять вручную.

Чтобы увидеть поверхность инструментов, которую видит агент — то, о чём на самом деле эти оценочные проверки, — вообще не запуская агента:

uv run fastmcp list .mcp.json                   # names, signatures, descriptions
uv run fastmcp list .mcp.json --input-schema    # ...with the full JSON schemas
npx @modelcontextprotocol/inspector uv run notes-mcp   # MCP Inspector, for clicking around

О версии mcp: сервер построен на отдельной библиотеке FastMCP, а не на той копии, которая раньше поставлялась внутри SDK mcp как mcp.server.fastmcp, — модуль mcp 2.0 был удалён. FastMCP сам управляет версией mcp, которая ему нужна (3.x резолвит mcp 1.x), поэтому в pyproject.toml нет прописанной вручную привязки к mcp. Оценочный стенд приходит к той же библиотеке с другой стороны: MCP-клиент pydantic-ai построен на FastMCP-овом Client. Обе части репозитория поэтому согласуются по версии конструктивно, а не за счёт версии, которую кому-то придётся поддержать вручную. FastMCP 4 — это шаг, который переводит обе части на mcp 2.x, поэтому зависимость я могу до границы ниже этого.

Подход к тестированию

Два уровня pytest, которые запускаются через scripts/test.sh:

  • test_store.py покрывает семантику хранения — уникальность, порядок сортировки, лимиты, временные метки. Быстро, полно, без участия протокола.

  • test_server.py гоняет сервер через внутрипроцессной сессии MCP-клиента (fastmcp.Client на транспорт в памяти FastMCP), поэтому проверяется то, что агент реально получает: список инструментов, JSON-схемы, аннотации поведения, структурированные результаты и текст ошибок. Протокол настоящий; только подпроцесс и сокет — нет.

Асинхронные тесты используют плагин anyio для pytest, а не pytest-asyncio, потому что MCP-клиент держит открытым cancel scope на время жизни сессии, а anyio выполняет настройку и закупание фикстур в одной задаче.

Третий вид проверки — оценочные тесты поведения агента — вызывает живую модель и стоит денег, поэтому он вообще не входит в набор pytest; у него есть собственный запускатор и скрипт, описанный дальше.

Оценочный стенд

evals/ собирает минимального агента на pydantic-ai — общий системный промпт в одну строку, никаких примеров с few-shot, никаких особых инструкций — и подключает его единственные инструменты к серверу Notes MCP через pydantic_ai.mcp.MCPToolset (agent.py). Системный промпт специально оставлен голым: эти проверки существуют, чтобы выяснить, достаточно ли собственных имён, описаний и схем инструментов сервера для правильного поведения, а не может ли инжиниринг промптов прикрыть слабого агента.

evals/ лежит на верхнем уровне, а не в src/: это инструменты для разработки этого репозитория, а не часть устанавливаемого пакета notes-mcp.

pydantic_evals гоняет этого агента на Dataset из Case, каждый из которых нацелен на одно из четырёх поведений из начала этого файла.:

Case

Что проверяется

delete_by_description_looks_up_the_id_first

При просьбе удалить «заметку мой список покупок» агент вызывает list_notes перед delete_note и удаляет правильный id.

answers_past_the_list_notes_preview_cutoff

Вопрос, ответ на который выходит за пределы превью list_notes, будет верно отвечен только если агент вызовет get_note.

appending_to_a_note_preserves_its_truncated_tail

«Добавить крекеры в мой список покупок» требует сначала прочитать полную заметку; проверяется, что в вызове update_note есть текст, существующий только за пределами границ превью.

title_conflict_on_create_is_not_silently_lost

При создании заметки с уже существующим заголовком нельзя молча терять новое содержимое или заявлять, что дубликат создан.

deleting_a_nonexistent_note_does_not_fabricate_success

Запрос на удаление несуществующей заметки не должен приводить к вызову delete_note с угаданным id или к ответу об успехе.

simple_lookup_answers_from_the_right_note

Простый «счастливый путь» для контрольной проверки.

Кейсы живут в cases.yaml, а не в Python — они являются данными, поэтому добавление кейса или изменение критериев оценки не трогает код. cases.py — только загрузчик: он передаёт пользовательские оценщики в Dataset.from_file (YAML-файл может назвать только тот оценщик, который загрузчик регистрирует) и задаёт модель судьи. Заголововок yaml-language-server в YAML указывает на cases_schema.json, чтобы редактор мог дополнять и проверять имена оценщиков и их аргументы; повторно создавайте его после добавления или изменения пользовательского оценщика:

uv run python -c "from evals.cases import write_json_schema; print(write_json_schema())"

Оценщики сочетают встроенные элементы pydantic-evals (ToolCorrectness, Contains, MaxToolCalls, LLMJudge для тех двух кейсов, где есть больше одного допустимого сценария) с двумя небольшими собственными в evaluators.py: ToolNotCalled (проверяет, что инструмент никогда не был вызван — встроенной негативной проверки нет) и ArgumentContains (проверка подстроки в аргументе инструмента, для случаев, где «старый текст должен сохраниться», а точную формулировку LLM нельзя зажать сравнением на равенство или по подмножеству словаря). Оба, как и встроенные, читают срезы вызовов инструментов-трейсов, которые собирают Agent.instrument_all() и локальный (send_to_logfire=False) logfire.configure() — см. configure_instrumentation() в agent.py.

__main__.py прогоняет набор данных, печатает полный отчёт и выходит с ненулевым кодом, если что-то не удалось — ошибка задачи, упавший оценщик или проваленный assert. Запуск:

uv run scripts/evals.sh

Настройка провайдера

NOTES_MCP_EVAL_MODEL задаёт и провайдера, и модель как строку provider:model из pydantic-ai и по умолчанию равенanthropic:claude-haiku-4-5-20251001. Скопируйте .env.example в .env и заполните раздел для того из трёх провайдеров, который вы используете — scripts/evals.sh загружает .env автоматически (через python-dotenv; он никогда не переопределяет уже заданную переменную окружения в вашей оболочке), а .env добавлен в .gitignore:

  • Anthropic API (по умолчанию) — нужен ANTHROPIC_API_KEY.

  • OpenAINOTES_MCP_EVAL_MODEL=openai:gpt-5 и OPENAI_API_KEY.

  • Amazon BedrockNOTES_MCP_EVAL_MODEL=bedrock:<bedrock-model-id>. Аутентификация идёт через обычную цепочку учётных данных boto3, поэтому ничего специфичного для оценочных проверок настраивать не нужно — используйте стандартные переменные AWS SDK AWS_PROFILE для именованного профиля (и тоже AWS_DEFAULT_REGION, если в профиле ещё не задан регион — обратите внимание, что именно AWS_DEFAULT_REGION, не AWS_REGION, которую резолв региона в boto3 не проверяет), либо оставьте их неуказанными, чтобы использовать профиль/регион по умолчанию.

Код нигде не зависит от провайдера — строка анализа eval_model() передаётся напрямую и агенту, и LLMJudge, а infer_model из pydantic-ai выбирает нужный клиент и учётные данные для любого префикса провайдера, который видит.

Install Server
F
license - not found
A
quality
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • -
    license
    Not graded
    quality
    Not graded
    maintenance
    A simple notes system that allows creating, storing, and accessing text notes through MCP resources and tools, with built-in prompt support for generating summaries of stored notes.
  • F
    license
    B
    quality
    D
    maintenance
    A learning-focused MCP server that demonstrates core MCP concepts through a simple notepad application, enabling users to create, update, delete, and search notes while exploring tools, resources, and prompts functionality.
    4
  • F
    license
    A
    quality
    D
    maintenance
    A minimal MCP server demonstrating tools, resources, and prompts for managing notes, with a simple notes app that supports adding, listing, deleting notes and summarizing them.
    3
    1

View all related MCP servers

Related MCP Connectors

  • Cross-session, cross-device memory for your agent: remember and recall notes. No key to start.

  • Shared, governed long-term memory for AI agents across tools and sessions via MCP and REST.

  • AI access to your aNotepad online notes: read, search, write, and organize via 22 tools.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jasongilman/mcp-eval-demo'

If you have feedback or need assistance with the MCP directory API, please join our Discord server