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. Поверхность MCPlist_tools возвращает ровно семь tools, и схемы (required, type, default, outputSchema) — это сгенерированные из type hints и docstrings.

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

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

  5. Promptslist_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/ -q45 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

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