Skip to main content
Glama

mcp-server-example — MCP-сервер для базы заметок в Markdown

Пример MCP (Model Context Protocol) сервера — рабочий и протестированный, — который даёт ассистенту доступ к second brain: локальному каталогу заметок в Markdown, которые он может создавать, читать, обновлять, перечислять, искать и измерять.

Здесь важен не объём возможностей, а показ честного MCP-сервера: схемы, генерируемые из type hints, настоящая санитизация против path traversal и набор тестов, которые вызывают инструменты по-настоящему, а не имитируют вызов.


Что такое 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

Весь ввод-вывод на диск и санитизация идентификаторов. Единственная точка сборки путей.

mcp_notas/search.py

Текстовый поиск с ранжированием по полям (заголовок > теги > тело), без учёта акцентов.

mcp_notas/__main__.py

Точка входа для python3 -m mcp_notas.

tests/test_server.py

45 тестов, которые по-настоящему упражняют сервер, включая полную MCP-сессию.

requirements.txt

Зависимости для рантайма и для тестов.

pytest.ini

Конфигурация pytest-asyncio.

Каждая заметка — это файл .md с минимальным front matter:

---
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 (обязательный), corpo, tags, slug

Созданную заметку с заполненными датами.

ler_nota

slug

Полную заметку (тело, теги, даты).

atualizar_nota

slug, corpo, titulo, tags, anexar

Уже обновлённую заметку.

apagar_nota

slug

Текстовое подтверждение.

listar_notas

tag (необязательный)

Итог и краткое описание каждой заметки, без тела.

buscar_notas

consulta, limite

Результаты, отсортированные по релевантности, с фрагментом.

estatisticas_base

—

Количества, самые используемые теги, самая длинная заметка.

Resources

URI

Тип

Содержимое

notas://index

application/json

Индекс всей базы: slug, заголовок, теги и URI каждой заметки.

notas://{slug}

text/markdown

Полный Markdown заметки с front matter.

Prompts

Prompt

Аргументы

Что собирает

resumir_nota

slug, tamanho (curto/longo)

Запрос на резюме с уже встроенным содержимым заметки.

sugerir_conexoes

slug, quantidade

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


Установка

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, и ClientSession из самого SDK завершил handshake по stdio, перечислил tools и выполнил вызовы (см. «Статус проверки»). Перевод этого handshake в формат конфигурации конкретного клиента не отрабатывался.


Пример использования

Реальные выводы, снятые при запуске сервера in-process (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.

И реальный handshake по stdio, с сервером, работающим как подпроцесс, и ClientSession из SDK на другой стороне (дословный вывод, без 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-сервера, работающего с файлами, — принять идентификатор от модели и склеить его прямо с путём: 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(), и код проверяет, что его родитель — это в точности каталог базы. Эта проверка избыточна по построению — и в этом суть: если когда-нибудь в первом слое появится дыра, утечка всё равно не произойдёт.

Каноническая атака, реально выполненная против tool:

>>> 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), а percent-encoded форма notas://..%2F..%2Fetc%2Fpasswd соответствует, доходит до санитизации и блокируется там — именно этот второй, опасный случай покрыт тестом.

Тест также доказывает в файловой системе, что цель атаки не создаётся: после попытки criar_nota со slug="../vazamento" каталог базы остаётся пустым, а файл вне его не существует.

Кроме того: никаких ключей API, никакого сетевого доступа, и сервер никогда не читает и не пишет за пределами настроенного каталога.


Тесты

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

Только тесты path traversal:

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

Набор покрывает, по порядку:

  1. Санитизация — 13 параметризованных вредоносных входов (../../etc/passwd, /etc/passwd, ..\\..\\windows\\system32\\config\\sam, C:\Windows\win.ini, nota\x00.md, пустая строка…), плюс доказательство на диске, что за пределами базы ничего не создаётся.

  2. Поверхность MCP — list_tools возвращает ровно семь tools, и схемы (required, type, default, outputSchema) — это сгенерированные из type hints и docstrings.

  3. Реальный вызов каждой tool — создание с проверкой сохранения на диске, дубликат, чтение, чтение несуществующей, обновление, обновление с anexar, список с фильтром по тегу и без, поиск с ранжированием и с лимитом, статистика и удаление.

  4. Resources — list_resources, list_resource_templates, чтение JSON-индекса, чтение отдельной заметки и обе формы traversal.

  5. Prompts — list_prompts, get_prompt для обоих промптов, с проверкой, что содержимое заметки действительно встроено и что исходная заметка не появляется в каталоге остальных.

  6. Сквозная сессия — create_connected_server_and_client_session поднимает MCP-клиента и сервер, соединённых в памяти; тест перечисляет tools, создаёт заметку, перечисляет, читает resource, получает prompt и подтверждает isError: True при попытке traversal.

  7. Изолированное хранилище — round-trip front matter, а файлы, не являющиеся заметками, игнорируются при перечислении.


Статус проверки

Всё ниже выполнено в данном окружении, с mcp 1.27.0, pytest 9.1.1 и pytest-asyncio 1.4.0 на Python 3.11.

✅ Проверено

  • python3 -m pytest tests/ -q → 45 passed.

  • Все семь tools реально вызваны через FastMCP.call_tool, результаты сверены.

  • Оба resources прочитаны через FastMCP.read_resource; оба prompts — через FastMCP.get_prompt.

  • Полная MCP-сессия клиент↔сервер в памяти через mcp.shared.memory.create_connected_server_and_client_session.

  • Реальный stdio-handshake: сервер поднят как подпроцесс (python3 -m mcp_notas), и ClientSession из SDK выполнил через него initialize, list_tools и call_tool.

  • Path traversal отклонён в sanitizar_slug, в tool, в resource и в файловой системе.

  • MCP_NOTAS_DIR соблюдается: созданная заметка появилась в каталоге, указанном переменной.

  • Все выводы, показанные в этом README, скопированы из реальных запусков.

⚠️ Не проверено

  • Блок mcpServers не проверялся против реального MCP-клиента (Claude Desktop, редакторы и т.д.). В этом окружении нет ни одного установленного клиента; эту проверку заменяет программный stdio-handshake, описанный выше.

  • Транспорты sse и streamable-http существуют в FastMCP.run, но этот проект упражняет только stdio.

  • Нет тестов на конкурентность: одновременные записи в одну заметку не координируются блокировкой.

  • Нет тестов на Windows или macOS — только Linux.


Лицензия

MIT

Related MCP Connectors

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.
    84 npm
    2
    MIT
  • 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