Skip to main content
Glama

mcp-server-example — Markdownノートベース用MCPサーバー

動作確認済みの実用的なMCP(Model Context Protocol)サーバーのサンプルです。アシスタントに セカンドブレインへのアクセスを提供します。ローカルのMarkdownノートディレクトリに対して、 作成・読み取り・更新・一覧表示・検索・統計を実行できます。

ここでの焦点は機能の多さではなく、誠実なMCPサーバーを示すことです。型ヒントから生成される スキーマ、パストラバーサルに対する本物のサニタイズ、そして呼び出しをモックするのではなく 実際にツールを呼び出すテストスイートを備えています。


MCPとは

Model Context Protocolは、アシスタントが外部システムと対話する方法を標準化するオープンプロトコルです。 各アプリケーションが独自のプラグインフォーマットを考案する代わりに、MCPサーバーは3つのものを宣言します — tools(モデルが実行できるアクション)、resources(URIでアドレス指定され、読み取り可能なデータ)、 prompts(ユーザーが呼び出せる会話テンプレート)— そして互換性のあるクライアントはこれらすべてを 自動的に発見して使用します。通信はJSON-RPCで、通常はstdio上で行われます。クライアントはサーバーを サブプロセスとして起動し、標準入出力を介してメッセージを交換します。


Related MCP server: mor

含まれるもの

ファイル

機能

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の設定。

各ノートは最小限のフロントマターを持つ.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(必須)、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。

Prompts

Prompt

引数

組み立てる内容

resumir_nota

slug、tamanho(curto/longo)

ノートの内容がすでに埋め込まれた要約リクエスト。

sugerir_conexoes

slug、quantidade

4つのメッセージ: 指示、開始ノート、他のノートのカタログ、アシスタントの冒頭。


インストール

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)、2番目のノートの本文にのみあります(スコア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.

そして、サーバーをサブプロセスとして実行し、SDKのClientSessionを反対側に置いた、stdio経由の 実際のハンドシェイク(サーバーの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にあり、2つの層があります。

1. sanitizar_slug() — 許可リストによる検証。 識別子は、パス区切り文字(/、\)、 ヌルバイト、Windowsドライブ文字(C:)、および..の出現を明示的に拒否した後、 ^[a-z0-9][a-z0-9._-]{0,79}$に一致する場合のみ通過します。文字または数字で始まることを 要求することで、.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.

notas://{slug}リソースも同じ保護があり、しかも2つの異なる経路で保護されています: 生のURI notas://../../etc/passwdはテンプレートに一致しません(Unknown resource)が、パーセントエンコードされた 形式notas://..%2F..%2Fetc%2Fpasswdは一致し、サニタイズに到達してそこでブロックされます — 危険なのはこの2番目のケースであり、テストがカバーしているのはこれです。

また、テストはファイルシステム上で、攻撃対象が作成されないことを証明します: 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\\sam、C:\Windows\win.ini、nota\x00.md、空文字列…)、 さらにベースの外に何も作成されないことのディスク上での証明。

  2. MCPサーフェス — list_toolsは正確に7つのツールを返し、スキーマ(required、 type、default、outputSchema)は型ヒントとdocstringから生成されたものです。

  3. 各ツールの実際の呼び出し — ディスク上で永続化を確認した作成、重複、読み取り、 存在しないものの読み取り、更新、anexarでの更新、タグフィルターありとなしの一覧表示、 ランキングと制限付きの検索、統計、削除。

  4. Resources — list_resources、list_resource_templates、JSONインデックスの読み取り、 個別ノートの読み取り、および2つのトラバーサル形式。

  5. Prompts — list_prompts、2つのプロンプトのget_prompt。ノートの内容が実際に埋め込まれ、 開始ノートが他のノートのカタログに表示されないことを確認。

  6. エンドツーエンドセッション — create_connected_server_and_client_sessionは、メモリ内で 接続されたMCPクライアントとサーバーを起動します。テストはtoolsの一覧表示、ノートの作成、 一覧表示、リソースの読み取り、プロンプトの取得を行い、トラバーサル試行でisError: Trueを確認します。

  7. 分離されたストレージ — フロントマターのラウンドトリップと、ノートではないファイルが 一覧表示で無視されること。


検証ステータス

以下はすべてこの環境で、mcp 1.27.0、pytest 9.1.1、pytest-asyncio 1.4.0、Python 3.11で 実行されました。

✅ 検証済み

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

  • 7つのツールがFastMCP.call_tool経由で実際に呼び出され、結果が確認されました。

  • 2つのリソースがFastMCP.read_resource経由で読み取られ、2つのプロンプトがFastMCP.get_prompt経由で取得されました。

  • mcp.shared.memory.create_connected_server_and_client_sessionによるメモリ内の完全なMCPクライアント↔サーバーセッション。

  • 実際のstdioハンドシェイク: サーバーがサブプロセス(python3 -m mcp_notas)として起動され、 SDKのClientSessionがinitialize、list_tools、call_toolを実行しました。

  • パストラバーサルがsanitizar_slug、ツール、リソース、ファイルシステムで拒否されました。

  • MCP_NOTAS_DIRが尊重されました: 作成されたノートは変数が指すディレクトリに表示されました。

  • このREADMEに示されているすべての出力は、実際の実行からコピーされたものです。

⚠️ 未テスト

  • mcpServersブロックは実際のMCPクライアント(Claude Desktop、エディタなど)に対してテストされていません。 この環境にはクライアントがインストールされていません。この検証の代わりとなるのは、上記の プログラムによるstdioハンドシェイクです。

  • sseおよびstreamable-httpトランスポートはFastMCP.runに存在しますが、このプロジェクトは stdioのみを実行します。

  • 並行性テストなし: 同じノートへの同時書き込みはロックで調整されていません。

  • WindowsまたはmacOSでのテストなし — Linuxのみ。


ライセンス

MIT

Related MCP Connectors

Related MCP Servers

  • 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.
    18 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
  • A
    license
    A
    quality
    B
    maintenance
    Provides an AI agent with a searchable markdown notes vault, offering tools to create, read, list, search, update, and delete notes stored as plain .md files.
    6
    MIT