Skip to main content
Glama
OL486

notes-mcp

by OL486

notes-mcp

把本地 Markdown 笔记目录以只读方式暴露给 AI 助手的 MCP Server。 基于 MCP 规范 2026-07-28,Python SDK mcp[cli] >= 2.0。

不写文件、不删文件、不执行任何外部命令。所有返回值都有字符预算上限。


它提供什么

Tools(模型调用)

名称

参数

行为

search_notes

query: str, limit: int = 5

全文检索,返回命中行 ±2 行的片段(每篇最多 3 段),总预算 8000 字符

read_note

path: str

读取单篇全文,超过 20000 字符截断并标注

list_recent

days: int = 7

最近 N 天修改过的笔记(路径 + 时间 + 首行标题)

notes_stats

—

笔记总数、总字数、标签分布(解析 #tag)

Resources(客户端/用户读取)

URI

说明

notes://index

全部笔记清单(JSON:uri / path / title / mtime / units / tags)

notes://note/{+path}

单篇笔记正文,例如 notes://note/daily/2026-10-25.md

Prompts(用户触发的模板)

名称

参数

说明

weekly-review

topic: str

生成"总结本周关于 {topic} 的笔记"的指令,要求 3 条要点 + 出处


Related MCP server: MCP Notes Server

快速开始

# 方式一:uv(推荐,与文档命令一致)
uv sync
uv run notes-mcp --root ./sample_notes

# 方式二:pip 可编辑安装
python -m venv .venv
.venv/Scripts/python -m pip install -e .      # Windows
# .venv/bin/python -m pip install -e .        # macOS / Linux
.venv/Scripts/python -m notes_mcp --root ./sample_notes

--root 也可以省略,此时按 NOTES_MCP_ROOT 环境变量 → ./notes 的顺序取值。

构建可分发的产物

uv build      # -> dist/notes_mcp-0.1.0-py3-none-any.whl 与 dist/notes_mcp-0.1.0.tar.gz

把 wheel 拷到目标机器(不需要源码、不需要 uv):

python -m venv .venv
.venv/Scripts/python -m pip install notes_mcp-0.1.0-py3-none-any.whl
.venv/Scripts/notes-mcp --root D:\Notes --version

装完就多了一个 notes-mcp 命令,客户端配置里直接指向它,不用再记 python -m notes_mcp。

CI(.github/workflows/ci.yml)在每次推送时都会走一遍"构建 → 装进干净 venv → 跑 --version", 所以"构建成功"和"装得上"这两件事不会脱节。

验收(Inspector CLI,强制 modern 协议)

npx @modelcontextprotocol/inspector@2.12.0 --cli \
  --protocol-era modern \
  uv run notes-mcp --root ./sample_notes \
  --method tools/list

看到四个工具名即通过。--protocol-era modern 不能省——Inspector 的 CLI 在 ad-hoc 目标下默认走 legacy(2025 握手),不加这个参数你测的是旧时代的行为。

本地完整自检

uv run python scripts/verify.py      # 走真实 stdio 客户端,跑全部工具/资源/提示
uv run pytest                        # 单元测试 + 端到端测试

三条工程约束

1. 路径安全

read_note 的 path 来自模型,是不可信输入。校验流程(notes_mcp/safety.py):

  1. 拒绝空、NUL 字节;

  2. 拒绝绝对路径(同时覆盖 POSIX /x、Windows C:\x、\\server\share);

  3. 拒绝任何含 .. 的路径;

  4. Path.resolve() 展开符号链接之后,用 is_relative_to(root) 判断是否越界;

  5. 只允许 .md / .markdown。

第 4 步是唯一真正有效的一步。字符串里看不出问题的 escape.md 完全可能是个 指向 C:\Windows\System32\drivers\etc\hosts 的软链接——只有 resolve 之后再比较才拦得住。

2. 上下文预算

search_notes 绝不返回整篇。每个片段前后各 2 行、每篇最多 3 段、总预算 8000 字符。 超限时返回文本里会带 [已截断:…],明确告诉模型"你还没看完"。

完整预算表见 docs/WALKTHROUGH.md 的 S4 一节。

3. 结构化错误

工具失败时不抛裸异常,而是 raise ToolError("路径越界:…。只允许笔记根目录内的相对路径")。 客户端收到 isError: true + 可读文本,模型能据此改写参数重试。 抛未捕获异常的话,模型只会看到 Error executing tool read_note,学不到任何东西。


接入客户端

Claude Desktop

编辑 %APPDATA%\Claude\claude_desktop_config.json(macOS:~/Library/Application Support/Claude/claude_desktop_config.json):

{
  "mcpServers": {
    "notes": {
      "command": "C:\\Users\\<你>\\projects\\notes-mcp\\.venv\\Scripts\\python.exe",
      "args": ["-m", "notes_mcp", "--root", "D:\\Notes"]
    }
  }
}

三个必须注意的点:

  • Windows 路径在 JSON 里要用双反斜杠,否则 \U、\n 会被当转义序列;

  • 用 .venv 里 python 的绝对路径,不要写 uv run——Claude Desktop 不继承你的 shell 环境, 找不到 uv 就静默失败;

  • 改完必须完全退出 Claude Desktop(托盘图标也要退),它只在启动时读配置。

Cursor

~/.cursor/mcp.json(项目级用 .cursor/mcp.json),结构同上:

{
  "mcpServers": {
    "notes": {
      "command": "C:\\Users\\<你>\\projects\\notes-mcp\\.venv\\Scripts\\python.exe",
      "args": ["-m", "notes_mcp", "--root", "D:\\Notes"]
    }
  }
}

在 Cursor 的 Settings → MCP 里确认 notes 显示为已连接、工具数为 4。

用 wheel 装好的命令

如果是 pip install notes_mcp-*.whl 装的,直接指向虚拟环境里的可执行文件更省事:

{
  "mcpServers": {
    "notes": {
      "command": "C:\\Users\\<你>\\notes-venv\\Scripts\\notes-mcp.exe",
      "args": ["--root", "D:\\Notes"]
    }
  }
}

macOS / Linux 上 command 换成 /Users/<你>/notes-venv/bin/notes-mcp。


配置项

环境变量

默认值

说明

NOTES_MCP_ROOT

./notes

笔记根目录

NOTES_MCP_MAX_NOTE_CHARS

20000

read_note / 单篇 Resource 的返回上限

NOTES_MCP_SEARCH_BUDGET

8000

search_notes 总预算

命令行参数:--root、--transport {stdio,streamable-http}、--host、--port、 --stateless-http、--log-level。

完整的实施过程(每个阶段的协议报文、验收输出、踩过的坑)见 docs/WALKTHROUGH.md。


项目结构

notes_mcp/
├── config.py     运行期配置(根目录 + 各类预算)
├── safety.py     不可信路径校验(工具与资源共用同一套规则)
├── store.py      扫描 / 解析 / 片段化检索(朴素实现)
├── budget.py     截断与预算拼装
├── tools.py      四个 Tool 的纯逻辑(不认识 MCP,可单测)
├── resources.py  两个 Resource 的纯逻辑
├── prompts.py    Prompt 模板
├── server.py     注册到 MCPServer
└── __main__.py   进程入口:参数、stderr 日志、传输

分层的关键是 tools.py / resources.py 不 import 任何 MCP 的东西。 它们只接收 Settings 和字符串,抛 PathRejected / QueryRejected; server.py 才把这些异常翻译成 ToolError / ResourceNotFoundError。 这样单测不需要起 server,也不用解析 JSON-RPC。


故障排查

现象

判断依据

处理

客户端显示 "Server disconnected"

stderr 里有 traceback

直接跑 python -m notes_mcp --root ... 看真实报错

工具列表是空的

Inspector 没加 --protocol-era modern

加上参数重测

客户端读到一半乱码

stdout 混入了非协议输出

检查是否有 print();日志必须走 stderr

read_note 报"笔记不存在"

路径相对的是根目录,不是当前目录

先读 notes://index 拿准确路径


已知限制

  • 朴素检索:每次调用都全量扫描并读盘。几千篇以内无感,10 万篇需要倒排索引 (改造方向见 docs/WALKTHROUGH.md S1 后半段)。

  • 只读:没有任何写路径。需要写入请另开一个 server,并显式做权限与备份。

  • 单进程单根目录:一个进程只服务一个 --root。多个目录就配多个 server 条目。


开发

uv sync                                  # 含 dev 依赖(pytest / anyio)
uv run pytest                            # 60 个用例
uv run python scripts/verify.py          # 22 项端到端检查(真实 stdio + 报文纯净度)
uv run python scripts/budget_report.py   # 打印各类返回的实测体积,确认没越预算
uv run python scripts/demo_weekly_review.py   # 按 prompt 模板走一遍完整调用链

实施过程与逐阶段协议报文见 docs/WALKTHROUGH.md, 可复现的验收结论见 ISSUE.md。

许可证

MIT

Available Tools

4 tools
list_recentA

列出最近 N 天内修改过的笔记(相对路径 + 修改时间 + 首行标题),按修改时间倒序。适合回答'上周我写了什么'这类问题。

NameRequiredDescriptionDefault
daysNo

TDQS

A3.8/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations exist, so the description carries the full burden; it does disclose the return shape and ordering, which is genuinely useful. It does not say whether 'N 天' means calendar days or a rolling 24h window, whether there is an upper bound on N, how results are truncated, or timezone handling — all material for a recency filter.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, zero waste: the first is the capability and output shape, the second is the routing cue. Nothing is repeated and the most actionable information leads.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-parameter listing tool with no output schema, describing the returned fields and sort order covers the main gap an agent would have. Missing only the default value and any result-count/limit behavior, which are minor.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description must compensate: "最近 N 天内" does clarify that `days` is a lookback window in days. However it omits the default (7, visible only in the schema as a bare title/default) and any bounds or timezone semantics, leaving the one parameter only partially specified.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (列出) and resource (最近 N 天内修改过的笔记), and even enumerates the returned fields (相对路径 + 修改时间 + 首行标题) plus sort order. It is clearly distinct from search_notes/read_note/notes_stats, though it never names a sibling to confirm the boundary.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives an explicit use case: "适合回答'上周我写了什么'这类问题", which anchors it to recency-browsing rather than query-based lookup. It stops short of naming alternatives such as search_notes for when the user has a keyword instead of a time window.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

notes_statsA

返回笔记总数、总字数、标签分布(解析正文里的 #tag)和最近修改时间。用于先摸清语料规模。

NameRequiredDescriptionDefault

No parameters

TDQS

A3.5/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations and no output schema, the description carries the full burden. It does disclose what is computed and notes that tag distribution comes from parsing #tag in the body, which is useful behavioral detail. It does not address cost (e.g., whether it scans all notes), whether it operates on all notes or a subset, or the shape of the returned data.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences with no wasted words, and the output content is front-loaded. The second sentence adds usage context efficiently, though it is slightly vague about what '语料规模' entails.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple no-parameter statistics tool, the description is adequate but incomplete. It lists the metrics returned but, without an output schema, does not describe the structure of the response (e.g., how tag distribution is formatted), so an agent cannot fully predict the return shape.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so the baseline is 4. There is nothing extra to document, and the description does not need to compensate for missing parameter information.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific purpose: returning aggregate corpus statistics (note count, word count, tag distribution, last-modified time). It does not explicitly name or contrast with the siblings search_notes, read_note, or list_recent, but the aggregate nature makes it obviously distinct from those tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The phrase '用于先摸清语料规模' gives a clear implied usage context: use this first to gauge the size of the corpus. However, it does not explicitly state when not to use it, nor does it name alternatives or prerequisites.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

read_noteA

读取单篇笔记的完整正文。path 必须是相对笔记根目录的路径(如 daily/2026-10-01.md),超过 20000 字符会截断并在末尾标注。绝对路径、.. 和指向根目录外的符号链接都会被拒绝。

NameRequiredDescriptionDefault
pathYes

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden, and it discloses real behavior: truncation at 20000 characters with a marker appended at the end, and rejection of absolute paths, '..' and out-of-root symlinks. It omits what happens for a missing path or an unreadable file, so the error behavior is still undisclosed.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences: the purpose first, then the path contract, then the safety rules. Each sentence adds information the agent cannot get from the schema or annotations, so there is no waste.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-parameter read tool with no output schema, the description covers purpose, path format, size limits and path safety. Only error behavior for nonexistent or unreadable notes is missing, which is a minor gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0% for the single 'path' parameter, so the description must compensate, and it does: it mandates a path relative to the notes root, gives a concrete example (daily/2026-10-01.md), and enumerates the path forms that will be rejected.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: '读取单篇笔记的完整正文' (read the full body of a single note). This clearly separates it from list/search siblings by emphasizing a single note and its full content, though it does not name the alternatives explicitly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The phrase '单篇笔记' implies this is the tool to use when you know which note you want, versus search_notes or list_recent. However, the description never states when to prefer this tool over those siblings, nor what to do first if the path is unknown.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_notesA

按关键词全文检索本地笔记,返回命中行前后各若干行的片段(每篇最多 3 段),总长度不超过 8000 字符。适合先定位再精读;不要用它读整篇笔记,那是 read_note 的职责。

NameRequiredDescriptionDefault
limitNo
queryYes

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the disclosure burden and does well: it describes the snippet-based return format, the 3-segment-per-note cap, and the 8000-character total limit, which are real behavioral constraints an agent must plan around. It does not mention permissions, indexing scope, or error behavior, which keeps it short of a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two dense sentences with the capability and constraints up front and the alternative routing at the end; no filler and the most important limit information is front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema or annotations exist, and the description compensates by describing the returned snippet format and size caps, which is the key unknown for a search tool. The remaining gap is the undocumented limit parameter, which leaves an agent guessing at result-count control.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must explain the parameters, but it never mentions 'limit' or its default of 5, nor does it clarify query syntax beyond 'keyword'. Only the implicit notion of a keyword query is conveyed, leaving half the parameters undocumented anywhere.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Names a specific verb and resource (full-text keyword search over local notes) and specifies the return shape (snippets, max 3 segments per note, 8000-character cap). It also explicitly separates itself from read_note, so an agent can distinguish the two without opening schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

States the intended workflow (locate first, then read in detail) and names the alternative for the opposite case: do not use it to read a whole note, that is read_note's job. Both when-to-use and when-not-to-use are given explicitly.

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.

  1. 4 tool updatesv0.1.0
    • First observedlist_recent
    • First observednotes_stats
    • First observedread_note
    • First observedsearch_notes

TDQS

A3.9/5.0

Scored across 4 tools

Disambiguation5/5

Each tool has a clearly distinct role: search_notes returns snippets, read_note returns full content, list_recent lists recently modified notes, and notes_stats summarizes corpus metadata. The descriptions explicitly clarify boundaries, especially search versus read.

Naming Consistency4/5

Three tools follow a verb_object pattern (search_notes, read_note, list_recent), while notes_stats is noun_noun. The deviation is minor and still readable, but it breaks strict consistency.

Tool Count5/5

Four tools are well-scoped for a focused notes retrieval server. Each tool earns its place and covers a distinct query pattern without bloat.

Completeness3/5

The read-only surface covers search, full read, recent listing, and stats, but there are no create, update, append, or delete operations. For a notes server, this is a notable gap if agents are expected to manage notes rather than only inspect them.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    A read-only toolkit for searching and analyzing Markdown note directories and Obsidian vaults through AI clients. It enables metadata extraction, full-text search, and natural language querying of note content, tags, and backlinks.
    10 npm
    4
    AGPL 3.0
  • A
    license
    A
    quality
    D
    maintenance
    Enables AI assistants to interact with a local folder of Markdown notes, supporting listing, reading, searching, creating, and appending to notes with strict security boundaries.
    5
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Provides controlled, read-only or write-enabled access to a private local Yaps Markdown vault, enabling AI clients to search, read, and manage notes securely.
    MIT