mcp-notas

mcp-notas

Provides tools, resources, and prompts for managing a local Markdown notes database, including create, read, update, delete, search, and statistics operations, with robust path traversal protection.

Category
访问服务器

README

mcp-server-example — servidor MCP para uma base de notas em Markdown

Um servidor MCP (Model Context Protocol) de exemplo, funcional e testado, que dá a um assistente acesso a um second brain: um diretório local de notas em Markdown que ele pode criar, ler, atualizar, listar, buscar e medir.

O foco aqui não é a quantidade de recursos, e sim mostrar um servidor MCP honesto: schemas gerados a partir dos type hints, sanitização de verdade contra path traversal, e uma suíte de testes que chama as ferramentas de verdade em vez de simular a chamada.


O que é MCP

O Model Context Protocol é um protocolo aberto que padroniza como um assistente conversa com sistemas externos. Em vez de cada aplicação inventar seu próprio formato de plugin, o servidor MCP declara três coisas — tools (ações que o modelo pode executar), resources (dados que ele pode ler, endereçados por URI) e prompts (modelos de conversa que o usuário pode invocar) — e qualquer cliente compatível descobre e usa tudo isso sozinho. A comunicação é JSON-RPC, normalmente sobre stdio: o cliente sobe o servidor como um subprocesso e troca mensagens pela entrada e saída padrão.


O que tem aqui

Arquivo O que faz
mcp_notas/server.py Define o servidor FastMCP: tools, resources, prompts e os modelos Pydantic de saída.
mcp_notas/storage.py Todo o I/O em disco e a sanitização de identificadores. Único ponto que monta caminhos.
mcp_notas/search.py Busca textual com ranking por campo (título > tags > corpo), insensível a acento.
mcp_notas/__main__.py Ponto de entrada de python3 -m mcp_notas.
tests/test_server.py 45 testes que exercitam o servidor de verdade, incluindo uma sessão MCP completa.
requirements.txt Dependências de runtime e de teste.
pytest.ini Configuração do pytest-asyncio.

Cada nota é um arquivo .md com um front matter mínimo:

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

O que o servidor expõe

Tools

Tool Argumentos Devolve
criar_nota titulo (obrigatório), corpo, tags, slug A nota criada, com datas preenchidas.
ler_nota slug A nota completa (corpo, tags, datas).
atualizar_nota slug, corpo, titulo, tags, anexar A nota já atualizada.
apagar_nota slug Confirmação em texto.
listar_notas tag (opcional) Total e resumo de cada nota, sem o corpo.
buscar_notas consulta, limite Resultados ordenados por relevância, com trecho.
estatisticas_base Contagens, tags mais usadas, nota mais longa.

Resources

URI Tipo Conteúdo
notas://index application/json Índice de toda a base: slug, título, tags e URI de cada nota.
notas://{slug} text/markdown Markdown integral de uma nota, com front matter.

Prompts

Prompt Argumentos O que monta
resumir_nota slug, tamanho (curto/longo) Um pedido de resumo com o conteúdo da nota já embutido.
sugerir_conexoes slug, quantidade Quatro mensagens: instrução, nota de partida, catálogo das demais notas e a abertura do assistente.

Instalação

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

Requer Python 3.11+ e mcp >= 1.27.0.


Como rodar

O transporte padrão é stdio — é assim que um cliente MCP sobe o servidor:

cd mcp-server-example
python3 -m mcp_notas

O processo fica em silêncio esperando mensagens JSON-RPC na entrada padrão; isso é o comportamento correto, não um travamento.

O diretório da base é configurável pela variável de ambiente MCP_NOTAS_DIR (padrão: ./notas, criado automaticamente):

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

Configuração no cliente

Bloco pronto para colar na configuração de um cliente 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"
      }
    }
  }
}

⚠️ Este bloco não foi testado contra um cliente MCP real neste ambiente. O que foi verificado aqui é o equivalente programático: o servidor foi subido como subprocesso com python3 -m mcp_notas e um ClientSession do próprio SDK completou o handshake por stdio, listou as tools e executou chamadas (ver "Status de verificação"). A tradução desse handshake para o formato de configuração de um cliente específico não foi exercitada.


Exemplo de uso

Saídas reais, capturadas rodando o servidor in-process (criar_servidor() + call_tool). O campo diretorio foi trocado por um caminho genérico; o resto é literal.

>>> 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."
    }
  ]
}

Repare no ranking: a palavra "protocolo" está no título e nas tags da primeira nota (pontuação 8.0) e apenas no corpo da segunda (pontuação 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.

E o handshake real por stdio, com o servidor rodando como subprocesso e um ClientSession do SDK do outro lado (saída literal, sem os logs INFO do servidor):

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

Segurança

O bug clássico de servidor MCP que mexe em arquivos é aceitar um identificador vindo do modelo e concatená-lo direto no caminho: Path(base) / slug. Com slug = "../../etc/passwd", isso entrega o disco inteiro para quem controlar o prompt.

Aqui a defesa está em mcp_notas/storage.py e tem duas camadas.

1. sanitizar_slug() — validação por lista de permissão. Um identificador só passa se casar com ^[a-z0-9][a-z0-9._-]{0,79}$, depois de rejeitar explicitamente separadores de caminho (/, \), byte nulo, letras de unidade do Windows (C:) e qualquer ocorrência de ... Exigir que comece por letra ou dígito também derruba nomes ocultos como .ssh.

2. BaseDeNotas.caminho() — verificação do caminho resolvido. Depois de sanitizar, o caminho é resolvido com Path.resolve() e o código confere que o pai dele é exatamente o diretório da base. Essa checagem é redundante por construção — e é esse o ponto: se algum dia a primeira camada tiver um furo, o vazamento ainda não acontece.

O ataque canônico, executado de verdade contra a 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.

O resource notas://{slug} tem a mesma proteção, e por dois caminhos diferentes: a URI crua notas://../../etc/passwd nem casa com o template (Unknown resource), enquanto a forma percent-encoded notas://..%2F..%2Fetc%2Fpasswd casa, chega à sanitização e é barrada lá — é esse segundo caso, o perigoso, que o teste cobre.

Um teste também prova no sistema de arquivos que o alvo do ataque não chega a ser criado: depois de uma tentativa de criar_nota com slug="../vazamento", o diretório da base continua vazio e o arquivo fora dele não existe.

Além disso: nenhuma chave de API, nenhum acesso de rede, e o servidor nunca lê ou escreve fora do diretório configurado.


Testes

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

Só os testes de path traversal:

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

A suíte cobre, em ordem:

  1. Sanitização — 13 entradas maliciosas parametrizadas (../../etc/passwd, /etc/passwd, ..\\..\\windows\\system32\\config\\sam, C:\Windows\win.ini, nota\x00.md, string vazia…), mais a prova em disco de que nada é criado fora da base.
  2. Superfície MCPlist_tools devolve exatamente as sete tools, e os schemas (required, type, default, outputSchema) são os gerados a partir dos type hints e docstrings.
  3. Chamada real de cada tool — criação com persistência verificada em disco, duplicata, leitura, leitura de inexistente, atualização, atualização com anexar, listagem com e sem filtro de tag, busca com ranking e com limite, estatísticas e remoção.
  4. Resourceslist_resources, list_resource_templates, leitura do índice JSON, leitura de uma nota individual e as duas formas de traversal.
  5. Promptslist_prompts, get_prompt dos dois prompts, conferindo que o conteúdo da nota é realmente embutido e que a nota de partida não aparece no catálogo das outras.
  6. Sessão ponta a pontacreate_connected_server_and_client_session sobe um cliente e um servidor MCP conectados em memória; o teste lista tools, cria nota, lista, lê resource, pega prompt e confirma isError: True na tentativa de traversal.
  7. Armazenamento isolado — round-trip do front matter e arquivos que não são notas sendo ignorados na listagem.

Status de verificação

Tudo abaixo foi executado neste ambiente, com mcp 1.27.0, pytest 9.1.1 e pytest-asyncio 1.4.0 sob Python 3.11.

Verificado

  • python3 -m pytest tests/ -q45 passed.
  • As sete tools chamadas de verdade via FastMCP.call_tool, com os resultados conferidos.
  • Os dois resources lidos via FastMCP.read_resource; os dois prompts via FastMCP.get_prompt.
  • Sessão MCP completa cliente↔servidor em memória com mcp.shared.memory.create_connected_server_and_client_session.
  • Handshake stdio real: servidor subido como subprocesso (python3 -m mcp_notas) e um ClientSession do SDK executando initialize, list_tools e call_tool por ele.
  • Path traversal rejeitado em sanitizar_slug, na tool, no resource e no sistema de arquivos.
  • MCP_NOTAS_DIR respeitado: a nota criada apareceu no diretório apontado pela variável.
  • Todas as saídas mostradas neste README foram copiadas de execuções reais.

⚠️ Não testado

  • O bloco mcpServers não foi testado contra um cliente MCP real (Claude Desktop, editores, etc.). Não há nenhum cliente instalado neste ambiente; o que substitui essa verificação é o handshake stdio programático descrito acima.
  • Os transportes sse e streamable-http existem em FastMCP.run, mas este projeto só exercita stdio.
  • Sem testes de concorrência: escritas simultâneas na mesma nota não são coordenadas por lock.
  • Sem testes em Windows ou macOS — só Linux.

Licença

MIT

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选