Notes MCP
MCP 评测示例
一个可运行的示例,展示如何通过评测(evaluations)来验证 LLM 智能体是否真的能有效使用一个 MCP 服务器——而不只是验证服务器代码的正确性。
单元测试回答的是“delete_note 能删除一条笔记吗”这类问题,但它们回答不了那些真正决定一个 MCP 服务器好不好用的关键问题:
当用户直接用具体 id 描述一条笔记时,智能体真的能找到要找的那条笔记吗?
它会注意到列表预览已被截断,还是只凭半条笔记就回答?
它是否意识到
update_note是覆盖语义,还是会在被要求“往我的购物清单里加一行”时悄悄把用户的内容覆盖掉?它会从错误中恢复,还是直接放弃?
这些都是**工具表面(tool surface)**的属性——工具名、描述、参数、返回结果结构、错误信息——所以验证它们的唯一方式,是让一个真实智能体运行于服务器之上,并评估它的行为。这正是本仓库的用途所在。
状态
本仓库中的 MCP server、其基础设施和评估框架均已就绪。
Related MCP server: MCP Notepad Server
被测服务器:Notes MCP(笔记服务器)
一个基于内存的笔记服务。状态只在服务器进程内存中,退出后即失效,因此每一次评估程序运行,都会从相同的初始语料开始(见 seed.py)。
工具 | 行为提示 | 作用 |
| 写入 | 创建一条笔记;标题必须在忽略大小写的前提下保持唯一。 |
| 只读 | 按 id 返回一条笔记的 完整 内容。 |
列出笔记 | 只读 | 按最后更新时间倒序列出笔记,为截断后的摘要,可通过 |
| 破坏性 | 覆盖 一条笔记的标题和/或内容。 |
| 破坏性 | 永久删除一条笔记。 |
有几种设计选择,专门为了让评估能抓到点东西:
用 id,而不是标题。 每个修改协约都接收
note_id,因此当被要求修改“我的购物车”时,智能体必须先查找到对应 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 中作为模块级常量,而不是散布在各处的提起词 docstring 内。修改描述措辞是在评测失败时主要会调整的东西,把这类修改全部集中在一起可以让差异一目了然。
快速开始
需要 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 服务器,您可以手动操作。要想查看智能体实际“看到的工具接口”——也就是评测真正关心的东西——而不启动任何 agent,请运行:
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**近 f ** 关于
mcp版本的一点说明: 该服务器基于独立的 FastMCP 库,而不是旧版本mcpSDK 中提供的mcp.server.fastmcp模块 ——mcp本身 2.0 版本已移除了那个模块。FastMCP 内部管理了自己的mcp依赖(FastMCP 3.x 版本继而 one在 mcp 1.x上),因此 [pyproject] 可不包含手工指定的mcp约束。评估框架是从另一侧切入的:pydantic-ai使用了FastMCP 的Client实现。所以仓库的两半部分是通过构造保证版本一致,而不是靠用来维护的版本锁定。FastMCP 4 是将两者都推进到 mcp 2.x 的一步,这也是依赖版本被限制在 4 以下的原因。
测试策略
一共由两个 pytest 层构成,由 scripts/test.sh 执行:
test_store.py负责存储层语义——如唯一性、顺序、长度限制、时间戳。快速、可穷举,与协议无关。test_server.py与系统的test_server.py用于:通过一个进程内的 MCP 客户端会话(fastmcp.Client,运行于 FastMCP 的内存传输之上)驱动服务器,因此能断言智能体实际收到的内容:工具列表、JSON schema、行为、结构化结果和错误文本。协议是真实的;仅有子进程和 socket 是“模拟”的。
异步测试使用 anyio 的 pytest 插件而非 pytest-asyncio,原因是 MCP 客户端在整个会话期间会一直维持一个可取消的范围,而 anyio 在同一个托管任务中执行 fixture 的设置与销毁。
第三类测试 —— 即“智能体行为评估”,必须调用真实的模型,因此会产生费用,所以它完全不归入 pytest 套件;它由独立的 runner 和脚本负责执行,详见下文。
评测框架
evals/ 创建了一个 最小 的 pydantic-ai 智能体——只有一行通用 system prompt,没有 few-shot 示例,没有 case 专用的精确指令——使用 pydantic_ai.mcp.MCPToolset(见 agent.py)把该 agent 的 唯一 工具集挂在本地内存进程里的 Notes MCP 服务器上。系统提示词刻意保持极简:这正是为了验证仅靠服务器自身工具名、描述和模式是否足以使智能体行为正确,而不是让注水式的提示词工程去掩盖问题。
evals/ 放在顶层目录而不是 src/ 下面,因为它是面向本仓库的开发工具,而不是给用户安装的 notes-mcp 包的一部分。
运行的时候,pydantic_evals 将一个数据集 Dataset 里读取的每个的 Case 切成其中的一条智能体行为。每个 Case 专门测本文件开头列出的某一种关键行为:
用例 | 检查的内容 |
| 当提示删除“我的购物清单”这个笔记时,智能体必须在调用 |
| 当列表被 |
| “把‘饼干’加到我的购物清单里”,那么它必须首先读取完整笔记——并要求** |
| 创建一条已有同名标题的笔记时,不得静默丢弃新的正文内容,也不得回答“重复内容已创建”。 |
| 如果想删除的笔记不存在,智能体不得使用猜到的 id 去调用 |
| 一个健康检查类型的正常路径。 |
这些用例都定义在 cases.yaml 中,而不是写在 Python 程序里,它们属于“数据”。这样添加一个用例,或修改某个它们应该满足的评价标准,都不需要改动代码。 cases.py 只是加载器:它把每个自定义评测器传给 Dataset.from_file(因为 YAML 只能引用加载器注册过的评测器名称),并把评测 judge 模型固定下来。YAML 开头的 yaml-language-server 指令指向 cases.schema.json,因此编辑器不仅能补全,还可以对评测器的名称和参数做校验。每当你增加或改变自定义评测器后,使用并重新生成该 schema:
uv run python -c "from evals.cases import write_json_schema; print(write_json_schema())"评估由 pydantic-evals 自带评测器(如参数纠错的 ToolCorrectness、Contains、MaxToolCalls,以及其两种合法恢复路径的 LLMJudge 情况)和两个自定义小型工具 ToolNotCalled(通过监听某个工具是否从未输入——环境自身主义)与 ArgumentContains(对工具参数局部内容进行检查,用于校验旧的笔记内容是否原样留存,而无需在最后仍然固定 LLM ,只需字串或子集完全相等即可)。这两者都与内置 评测器一样,基于 Agent.instrument_all(),以及本地的 logfire.configure()(send_to_logfire=False 在那里默认设置嗯)捕获的每次工具调用 —— 细节你可参考 agent.py 的 configuration_instrumentation() 函数。
__main__.py 负责运行测试集,输出完整的报告。只要有个别用例失败——无论任务错误、评测器崩溃、还是某个断言不通过——它都会以非零退出码结束。运行方式见下:
uv run scripts/evals.sh配置模型服务
NOTES_MCP_EVAL_MODEL 环境变量同时选择服务端(provider)与模型,采用 pydantic-ai 认可的磁盘数据模型 provider:模型id 字符串,默认值是 anthropic:claude-haiku-4-5-20251001。请复制 .env.example 到 .env,并根据你用下面哪种方案对应一个 section 内容—— scripts/evals.sh 会自动加载 .env(经由 python-dotenv,且注意它不会覆盖已经在你的 shell 中设置过的变量),而 .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 标准 credential chain,因此无需再配置其它专门的门户变量;默认设置AWS_PROFILE使用命名 profile 即可。如果某些 profile 没有配置 region,则同时设置AWS_DEFAULT_REGION—— 注意必须用AWS_DEFAULT_REGION而不是AWS_REGION,boto3 region 的自动猜测不会读取后者。如果您想使用默认 profile/region,请不设这两个变量即可。
在代码中不从服务器使用的 torts:不管什么 provider,程序都不会特意分支,而是直接把 eval_model() 的模型字符串原样交给智能体和 LLMJudge。pydantic-ai 的 infer_model 内部会(根据前缀)路由器来选择对应的客户端和凭据。
Maintenance
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
- -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.
- FlicenseBqualityDmaintenanceA 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
- FlicenseNot gradedqualityDmaintenanceProvides MCP tools to create and retrieve notes stored in memory.
- FlicenseAqualityDmaintenanceA 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.31
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.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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