Skip to main content
Glama

mcp-server-example — 用于 Markdown 笔记库的 MCP 服务器

一个功能完整且经过测试的示例 MCP(Model Context Protocol) 服务器,为助手提供对 第二大脑 的访问:一个本地 Markdown 笔记目录,助手可以对其执行创建、读取、更新、列出、搜索和统计操作。

这里的重点不在于功能数量,而在于展示一个诚实的 MCP 服务器:从类型提示生成的 schema、真正针对路径遍历的消毒处理,以及一套真实调用工具而非模拟调用的测试套件。


什么是 MCP

Model Context Protocol 是一个开放协议,它标准化了助手与外部系统对话的方式。与其让每个应用发明自己的插件格式,MCP 服务器声明三样东西——tools(模型可以执行的操作)、resources(模型可以通过 URI 寻址读取的数据)和 prompts(用户可以调用的对话模板)——任何兼容的客户端都能自动发现并使用所有这些。通信采用 JSON-RPC,通常通过 stdio:客户端将服务器作为子进程启动,并通过标准输入输出交换消息。


Related MCP server: Notes MCP Server

这里有什么

文件

作用

mcp_notas/server.py

定义 FastMCP 服务器:tools、resources、prompts 以及 Pydantic 输出模型。

mcp_notas/storage.py

所有磁盘 I/O 和标识符消毒处理。唯一拼接路径的地方。

mcp_notas/search.py

按字段(标题 > 标签 > 正文)排序的文本搜索,不区分重音。

mcp_notas/__main__.py

python3 -m mcp_notas 的入口点。

tests/test_server.py

45 个测试,真实运行服务器,包括一个完整的 MCP 会话。

requirements.txt

运行时和测试依赖。

pytest.ini

pytest-asyncio 的配置。

每条笔记都是一个带最小 front matter 的 .md 文件:

---
title: Teste env
tags: []
created: 2026-08-25T00:20:24+00:00
updated: 2026-08-25T00:20:24+00:00
---

服务器暴露了什么

Tools

Tool

参数

返回

criar_nota

titulo(必填)、corpotagsslug

创建的笔记,日期已填充。

ler_nota

slug

完整笔记(正文、标签、日期)。

atualizar_nota

slugcorpotitulotagsanexar

已更新的笔记。

apagar_nota

slug

文本确认信息。

listar_notas

tag(可选)

总数和每条笔记的摘要,不含正文。

buscar_notas

consultalimite

按相关性排序的结果,带片段。

estatisticas_base

计数、最常用标签、最长笔记。

Resources

URI

类型

内容

notas://index

application/json

整个笔记库的索引:每条笔记的 slug、标题、标签和 URI。

notas://{slug}

text/markdown

一条笔记的完整 Markdown,含 front matter。

Prompts

Prompt

参数

组装内容

resumir_nota

slugtamanhocurto/longo

一个摘要请求,笔记内容已内嵌其中。

sugerir_conexoes

slugquantidade

四条消息:指令、起始笔记、其余笔记的目录以及助手的开场白。


安装

git clone <url-do-repositorio> mcp-server-example
cd mcp-server-example
pip install -r requirements.txt

需要 Python 3.11+ 和 mcp >= 1.27.0


如何运行

默认传输方式是 stdio——MCP 客户端就是这样启动服务器的:

cd mcp-server-example
python3 -m mcp_notas

进程会保持静默,等待标准输入上的 JSON-RPC 消息;这是正确行为,不是卡死。

笔记库目录可通过环境变量 MCP_NOTAS_DIR 配置(默认:./notas,自动创建):

MCP_NOTAS_DIR=~/meu-second-brain python3 -m mcp_notas

客户端配置

可直接粘贴到 MCP 客户端配置中的代码块:

{
  "mcpServers": {
    "notas": {
      "command": "python3",
      "args": ["-m", "mcp_notas"],
      "cwd": "/caminho/absoluto/para/mcp-server-example",
      "env": {
        "MCP_NOTAS_DIR": "/caminho/absoluto/para/suas-notas"
      }
    }
  }
}

⚠️ 此代码块未在此环境中针对真实 MCP 客户端测试过。 这里实际验证的是程序化等价物:服务器以 python3 -m mcp_notas 作为子进程启动,SDK 自带的 ClientSession 通过 stdio 完成了握手、列出了 tools 并执行了调用(见"验证状态")。该握手到特定客户端配置格式的转换未经过实际测试。


使用示例

真实输出,通过进程内运行服务器(criar_servidor() + call_tool)捕获。diretorio 字段已替换为通用路径;其余为字面输出。

>>> criar_nota
{
  "slug": "protocolo-mcp",
  "titulo": "Protocolo MCP",
  "tags": [
    "mcp",
    "protocolo"
  ],
  "corpo": "O Model Context Protocol padroniza como um assistente acessa ferramentas e dados externos.",
  "criada_em": "2026-08-25T00:20:03+00:00",
  "atualizada_em": "2026-08-25T00:20:03+00:00"
}

>>> listar_notas(tag='mcp')
{
  "total": 1,
  "filtro_tag": "mcp",
  "notas": [
    {
      "slug": "protocolo-mcp",
      "titulo": "Protocolo MCP",
      "tags": [
        "mcp",
        "protocolo"
      ],
      "atualizada_em": "2026-08-25T00:20:03+00:00",
      "resumo": "O Model Context Protocol padroniza como um assistente acessa ferramentas e dados externos.",
      "tamanho": 90
    }
  ]
}

>>> buscar_notas(consulta='protocolo')
{
  "consulta": "protocolo",
  "total": 2,
  "resultados": [
    {
      "slug": "protocolo-mcp",
      "titulo": "Protocolo MCP",
      "tags": [
        "mcp",
        "protocolo"
      ],
      "pontuacao": 8.0,
      "trecho": "O Model Context Protocol padroniza como um assistente acessa ferramentas e dados externos."
    },
    {
      "slug": "memoria-de-longo-prazo",
      "titulo": "Memória de longo prazo",
      "tags": [
        "produtividade"
      ],
      "pontuacao": 1.0,
      "trecho": "Anotações sobre second brain. Cita o protocolo de revisão semanal."
    }
  ]
}

注意排序:单词"protocolo"出现在第一条笔记的标题和标签中(得分 8.0),而只出现在第二条的正文中(得分 1.0)。

>>> estatisticas_base()
{
  "total_de_notas": 2,
  "total_de_caracteres": 156,
  "total_de_palavras": 23,
  "media_de_caracteres": 78.0,
  "total_de_tags": 3,
  "tags_mais_usadas": {
    "mcp": 1,
    "produtividade": 1,
    "protocolo": 1
  },
  "nota_mais_longa": "protocolo-mcp",
  "ultima_atualizacao": "2026-08-25T00:20:03+00:00",
  "diretorio": "/caminho/para/notas"
}

>>> read_resource('notas://index')
{
  "diretorio": "/caminho/para/notas",
  "total": 2,
  "notas": [
    {
      "slug": "memoria-de-longo-prazo",
      "titulo": "Memória de longo prazo",
      "tags": [
        "produtividade"
      ],
      "uri": "notas://memoria-de-longo-prazo"
    },
    {
      "slug": "protocolo-mcp",
      "titulo": "Protocolo MCP",
      "tags": [
        "mcp",
        "protocolo"
      ],
      "uri": "notas://protocolo-mcp"
    }
  ]
}

>>> get_prompt('resumir_nota', {'slug': 'protocolo-mcp'})
Resuma em no máximo 3 bullets.
Não invente informação que não esteja na nota.

# Protocolo MCP
Tags: mcp, protocolo

O Model Context Protocol padroniza como um assistente acessa ferramentas e dados externos.

以及通过 stdio 的真实握手,服务器作为子进程运行,SDK 的 ClientSession 在另一端(字面输出,不含服务器的 INFO 日志):

serverInfo: mcp-notas 1.27.0
instructions[:60]: Servidor de uma base local de notas em Markdown. Use 'listar
tools: ['apagar_nota', 'atualizar_nota', 'buscar_notas', 'criar_nota', 'estatisticas_base', 'ler_nota', 'listar_notas']
criar_nota isError: False slug: handshake-stdio
estatisticas: 1 nota(s)
traversal isError: True
traversal msg: Error executing tool ler_nota: Identificador inválido '../../etc/passwd': separadores de caminho não são permitidos. Use

安全性

处理文件的 MCP 服务器的一个经典 bug 是接受来自模型的标识符并直接拼接到路径中:Path(base) / slug。当 slug = "../../etc/passwd" 时,这会把整个磁盘交给控制提示词的人。

这里的防御位于 mcp_notas/storage.py,有两层。

1. sanitizar_slug() — 白名单验证。 标识符只有在匹配 ^[a-z0-9][a-z0-9._-]{0,79}$ 时才能通过,在此之前会明确拒绝路径分隔符(/\)、空字节、Windows 盘符(C:)以及任何 .. 出现。要求以字母或数字开头也排除了 .ssh 之类的隐藏名称。

2. BaseDeNotas.caminho() — 解析后路径的检查。 消毒之后,路径用 Path.resolve() 解析,代码确认其父目录恰好是笔记库目录。这个检查在构造上就是冗余的——而这正是重点:如果有一天第一层出现漏洞,数据泄露仍然不会发生。

针对该工具实际执行的经典攻击:

>>> call_tool('ler_nota', {'slug': '../../etc/passwd'})
ToolError: Error executing tool ler_nota: Identificador inválido '../../etc/passwd': separadores de caminho não são permitidos. Use apenas o slug da nota, sem diretórios.

resource notas://{slug} 有同样的保护,而且通过两条不同的路径:原始 URI notas://../../etc/passwd 根本不匹配模板(Unknown resource),而百分号编码形式 notas://..%2F..%2Fetc%2Fpasswd 能匹配、到达消毒层并在那里被拦截——测试覆盖的正是这第二种危险情况。

还有一个测试在文件系统上证明攻击目标不会被创建:在尝试用 slug="../vazamento" 调用 criar_nota 之后,笔记库目录仍然为空,目录外的文件也不存在。

此外:没有 API 密钥,没有网络访问,服务器从不读取或写入配置目录之外的内容。


测试

$ python3 -m pytest tests/ -q
.............................................                            [100%]
45 passed in 1.48s

仅路径遍历测试:

$ python3 -m pytest tests/ -q -k traversal
.................                                                        [100%]
17 passed, 28 deselected in 0.67s

测试套件按顺序覆盖:

  1. 消毒处理 — 13 个参数化的恶意输入(../../etc/passwd/etc/passwd..\\..\\windows\\system32\\config\\samC:\Windows\win.ininota\x00.md、空字符串……),外加磁盘上证明不会在笔记库之外创建任何内容的测试。

  2. MCP 表面list_tools 恰好返回七个 tools,schema(requiredtypedefaultoutputSchema)是从类型提示和 docstring 生成的。

  3. 每个工具的真实调用 — 创建并在磁盘上验证持久化、重复创建、读取、读取不存在的笔记、更新、带 anexar 的更新、带和不带标签过滤的列表、带排序和限制的搜索、统计和删除。

  4. Resourceslist_resourceslist_resource_templates、读取 JSON 索引、读取单条笔记以及两种遍历形式。

  5. Promptslist_prompts、获取两个 prompt 的 get_prompt,确认笔记内容确实被内嵌,且起始笔记不会出现在其他笔记的目录中。

  6. 端到端会话create_connected_server_and_client_session 在内存中启动一个连接的 MCP 客户端和服务器;测试列出 tools、创建笔记、列出、读取 resource、获取 prompt,并确认遍历尝试时 isError: True

  7. 隔离存储 — front matter 的往返测试,以及非笔记文件在列表中被忽略的测试。


验证状态

以下所有内容均在此环境中执行,使用 mcp 1.27.0、pytest 9.1.1 和 pytest-asyncio 1.4.0,Python 3.11。

已验证

  • python3 -m pytest tests/ -q45 passed

  • 七个 tools 通过 FastMCP.call_tool 真实调用,结果已核对。

  • 两个 resources 通过 FastMCP.read_resource 读取;两个 prompts 通过 FastMCP.get_prompt 获取。

  • 通过 mcp.shared.memory.create_connected_server_and_client_session 在内存中完成完整的客户端↔服务器 MCP 会话。

  • 真实 stdio 握手:服务器作为子进程启动(python3 -m mcp_notas),SDK 的 ClientSession 通过它执行 initializelist_toolscall_tool

  • 路径遍历在 sanitizar_slug、工具、resource 和文件系统层面均被拒绝。

  • MCP_NOTAS_DIR 被遵守:创建的笔记出现在变量指向的目录中。

  • 本 README 中显示的所有输出均来自真实执行。

⚠️ 未测试

  • mcpServers 代码块未针对真实 MCP 客户端测试过(Claude Desktop、编辑器等)。此环境中未安装任何客户端;替代此验证的是上述程序化 stdio 握手。

  • ssestreamable-http 传输方式存在于 FastMCP.run 中,但本项目只使用 stdio

  • 无并发测试:对同一笔记的并发写入没有通过锁协调。

  • 未在 Windows 或 macOS 上测试——仅 Linux。


许可证

MIT

A
license - permissive license
Not graded
quality - not tested
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

  • F
    license
    Not graded
    quality
    D
    maintenance
    Manages markdown notes in a specified directory, allowing users to create, read, update, and list notes through the Model Context Protocol.
    1
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI assistants to search, read, create, update, and remove personal markdown notes stored locally, providing persistent memory across sessions.
    13
    2
    MIT
  • A
    license
    A
    quality
    C
    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

View all related MCP servers

Related MCP Connectors

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

  • Read and write your Fresh Jots notes from Claude, Cursor, and any MCP client.

  • Create, validate, edit, export (markdown/svg/png/mermaid), and search JSON Canvas files.

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/herickbrandao483-jpg/mcp-server-example'

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