Notes MCP
MCP Eval Demo
Рабочий пример применения оценочных проверок, чтобы убедиться, что LLM-агент действительно умеет пользоваться MCP-сервером, — а не только того, что код сервера корректен.
Модульные тесты отвечают на вопрос «удаляет ли delete_note заметку?» Но они не могут ответить на вопросы, которые решают, хорош ли MCP-сервер на практике:
Находит ли агент нужную заметку, когда пользователь описывает её словами, а не идентификатором?
Замечает ли он, что превью из списка было обрезано, или отвечает по половине заметки?
Понимает ли, что
update_noteперезаписывает содержимое, или молча уничтожает пользовательские данные, когда его просят «добавить строку в мой список покупок»?Приходит ли он в себя после сообщения об ошибке или сдаётся?
Это свойства поверхности инструментов — имён, описаний, схем, форм результатов, текста ошибок — и единственный способ их проверить — запустить реального агента против сервера и оценить, что он сделал. Именно для этого предназначен этот репозиторий.
Статус
MCP-сервер, его инфраструктура и стенд для оценочных проверок — всё на месте.
Related MCP server: notes-mcp-server
Сервер под тестом: Notes MCP
Записная книжка в памяти. Состояние живёт в процессе сервера и отбрасывается при выходе, поэтому каждый оценочный прогон начинается с одного и того же известного корпуса (см. seed.py).
Инструмент | Подсказка о поведении | Что делает |
| запись | Создаёт заметку; заголовки должны быть уникальными без учёта регистра. |
| только чтение | Возвращает полное содержимое одной заметки по id. |
| чтение | Список заметок сначала те, что обновлены позже, в виде усечённых превью, с опциональной подстрокой |
| разрушающее | Перезаписывает заголовок и/или содержимое заметки. |
| разрушающее | Окончательно удаляет заметку. |
Несколько проектных решений приняты специально, чтобы оценочным проверкам было что ловить:
Идентификаторы, а не заголовки. Каждый изменяющий инструмент принимает
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, а не на той копии, которая раньше поставлялась внутри SDKmcpкак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 | Что проверяется |
| При просьбе удалить «заметку мой список покупок» агент вызывает |
| Вопрос, ответ на который выходит за пределы превью |
| «Добавить крекеры в мой список покупок» требует сначала прочитать полную заметку; проверяется, что в вызове |
| При создании заметки с уже существующим заголовком нельзя молча терять новое содержимое или заявлять, что дубликат создан. |
| Запрос на удаление несуществующей заметки не должен приводить к вызову |
| Простый «счастливый путь» для контрольной проверки. |
Кейсы живут в 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.OpenAI —
NOTES_MCP_EVAL_MODEL=openai:gpt-5иOPENAI_API_KEY.Amazon Bedrock —
NOTES_MCP_EVAL_MODEL=bedrock:<bedrock-model-id>. Аутентификация идёт через обычную цепочку учётных данных boto3, поэтому ничего специфичного для оценочных проверок настраивать не нужно — используйте стандартные переменные AWS SDKAWS_PROFILEдля именованного профиля (и тожеAWS_DEFAULT_REGION, если в профиле ещё не задан регион — обратите внимание, что именноAWS_DEFAULT_REGION, неAWS_REGION, которую резолв региона в boto3 не проверяет), либо оставьте их неуказанными, чтобы использовать профиль/регион по умолчанию.
Код нигде не зависит от провайдера — строка анализа eval_model() передаётся напрямую и агенту, и LLMJudge, а infer_model из pydantic-ai выбирает нужный клиент и учётные данные для любого префикса провайдера, который видит.
Available Tools
5 toolscreate_noteA
Create a new note and return it, including the generated id to use in later calls.
Titles must be unique case-insensitively; creating a note whose title already exists is an error, so if the user wants to change an existing note, use update_note.
| Name | Required | Description | Default |
|---|---|---|---|
| title | Yes | Short, distinctive title. Must be unique. | |
| content | No | Body of the note. May be empty. |
Output Schema
| Name | Required | Description |
|---|---|---|
| id | Yes | Stable identifier for the note. |
| title | Yes | Short human-readable title. |
| content | Yes | Full note body. |
| created_at | Yes | When the note was created (UTC, ISO 8601). |
| updated_at | Yes | When the note was last modified (UTC, ISO 8601); equals `created_at` for a note that has never been updated. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the annotations (readOnlyHint=false, destructiveHint=false), the description reveals the result shape (returns note with generated id), a critical error condition (case-insensitive title collision), and a routing fallback. This adds real behavioral context without contradiction.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, the most important return behavior up front, followed by the duplicate-title warning and alternative. Each sentence adds unique operational value; there is no padding.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With only two parameters, one required, and a full output schema, the description covers everything an agent needs: what the tool does, what it returns, key constraints, and the correct sibling for modification. Nothing essential is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents both parameters well. The description adds only the case-insensitive uniqueness nuance beyond the schema's existing 'Must be unique' note. That is small extra value, consistent with the baseline 3 for high coverage.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb-resource pair ('Create a new note') and clearly states the distinguishing behavior: returning the generated id for later calls. It also contrasts with update_note by pointing out that duplicate titles are errors, making the tool's role unmistakable among its siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly names when not to use this tool ('if the user wants to change an existing note, use update_note') and clearly flags the uniqueness constraint that should silence spurious create attempts. This gives the agent direct decision support.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
delete_noteADestructive
Permanently delete the note with the given id and return what was removed.
This cannot be undone and the note's content is not recoverable, so confirm the id with list_notes or get_note before calling when the user identified the note by description rather than by id.
| Name | Required | Description | Default |
|---|---|---|---|
| note_id | Yes | Id of the note to delete. |
Output Schema
| Name | Required | Description |
|---|---|---|
| id | Yes | Identifier of the note that was deleted. |
| title | Yes | Title of the note that was deleted. |
| deleted | Yes | Always true; a missing note is an error, not a false here. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
While annotations already set destructiveHint=true, the description adds critical context: the deletion is permanent, content is not recoverable, and it returns what was removed. This warns about irreversibility and the need for confirmation beyond the annotation's binary hint.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two sentences, front-loaded with the core action, and every clause earns its place. The caution and verification guidance are placed logically without waste.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The tool is simple (one parameter, no nested objects, has output schema). The description covers what the tool does, the consequence, and the necessary verification pre-step. Nothing an agent needs to call it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema covers the only parameter note_id with a clear description 'Id of the note to delete'. Since schema description coverage is 100%, the baseline is 3. The description refers to the parameter as `id` but adds no new format or semantic detail beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb 'delete' with a specific resource 'note' and the return behavior 'return what was removed'. It distinguishes from siblings like create, get, list, update by naming the destructive action unambiguously.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit guidance on when to use this tool: confirm the id with list_notes or get_note before calling when the note was identified by description. This directly addresses potential misuse and surfaces the appropriate alternative behavior.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_noteARead-onlyIdempotent
Read one note in full by id.
Use this whenever you need a note's complete body — list_notes previews are cut off after 120 characters. Ids come from list_notes or create_note; this tool does not accept titles.
| Name | Required | Description | Default |
|---|---|---|---|
| note_id | Yes | Id of the note, as returned by `list_notes`. |
Output Schema
| Name | Required | Description |
|---|---|---|
| id | Yes | Stable identifier for the note. |
| title | Yes | Short human-readable title. |
| content | Yes | Full note body. |
| created_at | Yes | When the note was created (UTC, ISO 8601). |
| updated_at | Yes | When the note was last modified (UTC, ISO 8601); equals `created_at` for a note that has never been updated. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already establish read-only, idempotent, non-destructive behavior. The description adds useful context about return behavior (full note body) and explicitly documents a limitation ('does not accept titles'), which goes beyond the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact and front-loaded. The first sentence conveys the core purpose, and the second sentence gives the exact context needed to avoid common mistakes without unnecessary elaboration.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
A single-parameter read operation with annotations, a full output schema, and clear sibling differentiation. The description covers all functional details needed to invoke the tool correctly; nothing important is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the parameter description already says 'Id of the note, as returned by list_notes.' The tool description reinforces that note_id comes from list_notes or create_note and that titles won't work, but does not add significant meaning beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific action: 'Read one note in full by `id`.' It names the resource and the mechanism, and the first sentence identifies the tool as the way to get a note's complete body, distinguishing it from list previews.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly says to use this tool whenever a note's complete body is needed, notes that list_notes previews are truncated at 120 characters, and clarifies that IDs come from list_notes or create_note and that titles are not accepted. This provides clear when-to-use and when-not-to-use guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_notesARead-onlyIdempotent
List notes, most-recently-updated first, as previews.
Pass query to filter to notes whose title or body contains that text (case-insensitive substring, not fuzzy or semantic — prefer one distinctive word over a whole phrase, and drop the filter entirely to browse everything).
Each entry carries id, title, preview, content_truncated, content_length, created_at, and updated_at. total_count is the number of matches, which exceeds the returned notes when limit cut the page short.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Maximum notes to return. | |
| query | No | Case-insensitive substring filter on title and body. Omit to list every note. |
Output Schema
| Name | Required | Description |
|---|---|---|
| notes | Yes | Matching notes, newest-updated first. Previews only — use `get_note` for full content. |
| total_count | Yes | Number of notes matching the query, which may exceed `len(notes)` when `limit` truncated the page. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
In addition to the annotations (read-only, idempotent, non-destructive), the description adds meaningful behavior: ordering of results, preview semantics, an explicit explanation of the substring behavior, and clarity about total_count exceeding returned results when limit truncates the page. This is exactly the kind of behavioral context the description should provide.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is three concise paragraphs with a front-loaded summary, a filter guidance section, and a brief output-shape section. Every sentence contributes necessary behavioral or usage detail, and the structure makes it easy for an agent to parse.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers the core operation, ordering, filtering behavior, returned entry fields, total_count semantics, and limit-pagination behavior. Combined with a rich output schema and the read-only annotations, virtually everything an agent needs to invoke this tool correctly and interpret the default behavior is present.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema already covers both parameters thoroughly: limit defines maximum notes with defaults and bounds, query explains the case-insensitive substring filter and omission. The description restates some of this guidance but does not add significant new meaning for the parameters; its advice about using one distinctive word is minor beyond the schema's coverage.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states a specific action ('List notes') and a distinctive resource, and also specifies the ordering (most-recently-updated first) and output shape (previews), which precisely distinguishes it from the single-record sibling get_note. It doesn't explicitly call out the sibling, but listing versus retrieving a single note is semantically self-evident.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives concrete usage guidance for the query filter: it explains that the filter matches case-insensitive substrings, warns that it is not fuzzy or semantic, and counsels preferring one distinctive word over a phrase. It also says to omit the filter to browse everything, but it doesn't explicitly contrast list_notes with get_note for retrieving a single full note.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
update_noteADestructive
Overwrite the title and/or content of an existing note and return the updated note.
This REPLACES whatever you pass; it does not merge. Omit a field (or pass null) to leave it alone. To add to a note's body without losing the current text, call get_note first and pass the full combined text as content.
| Name | Required | Description | Default |
|---|---|---|---|
| title | No | Replacement title, or null to keep the current one. | |
| content | No | Replacement body — overwrites, does not append. Null keeps the current body. | |
| note_id | Yes | Id of the note to change. |
Output Schema
| Name | Required | Description |
|---|---|---|
| id | Yes | Stable identifier for the note. |
| title | Yes | Short human-readable title. |
| content | Yes | Full note body. |
| created_at | Yes | When the note was created (UTC, ISO 8601). |
| updated_at | Yes | When the note was last modified (UTC, ISO 8601); equals `created_at` for a note that has never been updated. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the annotations (readOnlyHint=false, destructiveHint=true), the description clearly discloses the destructive overwrite behavior: it replaces, does not merge, and explains the semantics of omitting or passing null. It also flags the risk of losing current text and instructs the agent on the safe pattern (get_note first). No contradiction with annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact and front-loaded with the core function. Every sentence serves a distinct purpose: state the operation and return value, warn about overwrite semantics, explain null/omit behavior, and provide the append workflow. No redundancy or filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the output schema is present, return values are already documented. The description covers the essential behavioral nuance (no merge, omission semantics) and the critical alternative workflow for append. An agent has everything necessary to call the tool correctly without external context.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the schema already explains that null keeps the current value for both fields. The description adds the clarification that omitting a field also leaves it alone, which is a small extra, but overall it does not materially extend the schema's parameter documentation.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (Overwrite), resource (title and/or content of an existing note), and outcome (return the updated note). This clearly distinguishes the tool from its siblings list_notes, get_note, create_note, and delete_note.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides clear context for use: modifying an existing note by overwriting fields. It also gives explicit guidance for the alternative of appending content, telling the agent to call get_note first and pass the full combined text. However, it does not explicitly state exclusions such as 'use create_note for new notes,' though this is implied by 'existing note.'
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
5 tool updates
v0.1.0- First observed
create_note - First observed
delete_note - First observed
get_note - First observed
list_notes - First observed
update_note
TDQS
Scored across 5 tools
Each tool maps to one distinct resource action (create, get, list, update, delete), with no overlapping operations. The descriptions reinforce boundaries by explaining when to use get_note over list_notes and create_note over update_note.
All tool names follow the same verb_note pattern: create_note, get_note, list_notes, update_note, delete_note. list_notes is pluralized because it returns a collection, but the convention is otherwise uniform and predictable.
Five tools form a tightly scoped set for a notes server: one create tool, one read tool, one list tool, one update tool, and one delete tool. Each tool earns its place and the count is ideal for the domain.
The tool surface covers the full note lifecycle with no dead ends: create, list, read by id, update, and delete. The update tool's replace-only behavior is mitigated by explicit guidance to call get_note first, so agents have a complete workflow.
Maintenance
Related MCP Connectors
Google Keep-style notes app with an MCP server for AI agents to read/write notes.
- TaprootOAuthcom.taproothq
Persistent memory layer for AI tools. Save and recall notes across Claude and other MCP clients.
MCP-native notes and memory for ChatGPT, Claude, and other AI tools.
Agent-native notes, tasks, dev-docs, vaults, sync & handoffs. MCP + OpenAPI dual surface.
Related MCP Servers
- -licenseNot gradedqualityNot gradedmaintenanceA 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.-
- FlicenseNot gradedqualityDmaintenanceProvides MCP tools to create and retrieve notes stored in memory.-
- AlicenseAqualityBmaintenanceProvides cross-session persistent memory for coding agents via MCP tools to store, retrieve, and manage notes with hybrid keyword/semantic search and automatic deduplication.8201 npmMIT
- AlicenseNot gradedqualityCmaintenanceEnables AI assistants to manage in-memory notes by listing, fetching, and creating notes through MCP tools over stdio.64 npmMIT