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.
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_notase umClientSessiondo 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:
- 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. - Superfície MCP —
list_toolsdevolve exatamente as sete tools, e os schemas (required,type,default,outputSchema) são os gerados a partir dos type hints e docstrings. - 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. - Resources —
list_resources,list_resource_templates, leitura do índice JSON, leitura de uma nota individual e as duas formas de traversal. - Prompts —
list_prompts,get_promptdos 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. - Sessão ponta a ponta —
create_connected_server_and_client_sessionsobe um cliente e um servidor MCP conectados em memória; o teste lista tools, cria nota, lista, lê resource, pega prompt e confirmaisError: Truena tentativa de traversal. - 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/ -q→ 45 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 viaFastMCP.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 umClientSessiondo SDK executandoinitialize,list_toolsecall_toolpor ele. - Path traversal rejeitado em
sanitizar_slug, na tool, no resource e no sistema de arquivos. MCP_NOTAS_DIRrespeitado: 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
mcpServersnã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
sseestreamable-httpexistem emFastMCP.run, mas este projeto só exercitastdio. - 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
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
Neon Database
MCP server for interacting with Neon Management API and databases
E2B
Using MCP to run code via e2b.
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.