🐷
相対パスを使用したFileSystem MCP Serverパッケージを作成・公開してみた
1. 概要
Model Context Protocol (MCP) 用のセキュアなファイルシステムサーバーを開発し、npmパッケージとして公開しました。自分がファイルシステムMCPを使う際には、ワークディレクトリを指定することが多く、エージェントコードと同じパスでないため、指定パスが間違ってしまうことが多々ありました。エージェントがカレントディレクトリとの違いを意識せず、相対パスで実行できるようにしました。
この記事では、パッケージの紹介と使い方、そしてnpmパッケージの公開手順を簡潔にまとめます。
2. 公開したパッケージの紹介 & 使い方
パッケージ概要
パッケージ名: @m_sea_bass/relpath-filesystem-mcp
機能: セキュアなMCPファイルシステムサーバー
リンク: @m_sea_bass/relpath-filesystem-mcp
主な特徴
- 🔒 セキュリティ強化: パストラバーサル攻撃防止
- 📁 相対パス対応: 指定ディレクトリからの相対パス操作
- 🛠️ 豊富なツール: 11種類のファイル・ディレクトリ操作
- 🚫 アクセス制御: ベースディレクトリ外へのアクセスを防止
使い方
コマンドライン
npx @m_sea_bass/relpath-filesystem-mcp /path/to/directory
Pythonクライアントの例
以下のように、サーバーパラメータを設定して、MCPクライアントを作成します。
server_params = StdioServerParameters(
command="npx", # npxを使用
args=["@m_sea_bass/relpath-filesystem-mcp", "path-to-your-directory"] # パッケージ名とベースディレクトリ
)
サンプルコード
#!/usr/bin/env python3
"""
包括的なMCPクライアントテストスイート
このテストスイートは、MCPファイルシステムサーバーのすべての機能をテストします。
- サーバーに接続
- すべてのツールを順次テスト
- エラーハンドリングとログ出力
使用方法:
python simple_mcp_client.py
"""
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def test_tool(session, tool_name, arguments, description):
"""
個別ツールをテストするヘルパー関数
"""
print(f"\n🧪 テスト実行: {tool_name}")
print(f"📋 説明: {description}")
print(f"📤 引数: {arguments}")
try:
result = await session.call_tool(tool_name, arguments=arguments)
if not result.isError:
print(f"✅ {tool_name} 成功")
print(f"📄 結果: {result.content[0].text[:200]}{'...' if len(result.content[0].text) > 200 else ''}")
return True
else:
print(f"❌ {tool_name} 失敗: {result.content[0].text}")
return False
except Exception as e:
print(f"❌ {tool_name} エラー: {e}")
return False
async def run_comprehensive_mcp_test():
"""
包括的なMCPクライアントテストの実行
"""
print("🚀 包括的MCPクライアントテスト開始")
print("=" * 60)
# サーバーパラメータの設定
server_params = StdioServerParameters(
command="npx",
args=["@m_sea_bass/relpath-filesystem-mcp", "./work"]
)
test_results = {}
try:
# サーバーとの接続を確立
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
# 1. 初期化
print("🔌 サーバーに接続中...")
init_result = await session.initialize()
print(f"✅ 接続成功!")
# 2. 利用可能なツールを一覧表示
print("\n📋 利用可能なツール:")
tools = await session.list_tools()
for tool in tools.tools:
print(f" 🔧 {tool.name}: {tool.description[:80]}...")
print(f"\n📊 合計 {len(tools.tools)} 個のツールをテストします")
print("=" * 60)
# 3. 各ツールのテスト実行
# 3.1 list_allowed_directory - 許可されたディレクトリの確認
test_results["list_allowed_directory"] = await test_tool(
session, "list_allowed_directory", {},
"許可されたベースディレクトリの確認"
)
# 3.2 list_directory - 現在のディレクトリ一覧
test_results["list_directory"] = await test_tool(
session, "list_directory", {"path": "."},
"現在のディレクトリ一覧表示"
)
# 3.3 create_directory - テスト用ディレクトリ作成
test_results["create_directory"] = await test_tool(
session, "create_directory", {"path": "test_suite"},
"テスト用ディレクトリの作成"
)
# 3.4 write_file - ファイル作成
test_results["write_file"] = await test_tool(
session, "write_file", {
"path": "test_file.txt",
"content": "Hello from Comprehensive MCP Test!\nこんにちは、世界!"
},
"テストファイルの作成"
)
# 3.5 read_file - ファイル読み込み
test_results["read_file"] = await test_tool(
session, "read_file", {"path": "test_file.txt"},
"作成したファイルの読み込み"
)
# 3.6 write_file - 追加ファイル作成(複数ファイル読み込みテスト用)
await test_tool(
session, "write_file", {
"path": "test_suite/test_file2.txt",
"content": "Second test file content.\nMultiline content for testing."
},
"2番目のテストファイル作成"
)
# 3.7 read_multiple_files - 複数ファイル読み込み
test_results["read_multiple_files"] = await test_tool(
session, "read_multiple_files", {
"paths": ["test_file.txt", "test_suite/test_file2.txt"]
},
"複数ファイルの同時読み込み"
)
# 3.8 get_file_info - ファイル情報取得
test_results["get_file_info"] = await test_tool(
session, "get_file_info", {"path": "test_file.txt"},
"ファイルメタデータの取得"
)
# 3.9 edit_file - ファイル編集(dry run)
test_results["edit_file_dry"] = await test_tool(
session, "edit_file", {
"path": "test_file.txt",
"edits": [{
"oldText": "Hello from Comprehensive MCP Test!",
"newText": "Hello from EDITED MCP Test!"
}],
"dryRun": True
},
"ファイル編集のプレビュー(dry run)"
)
# 3.10 edit_file - 実際のファイル編集
test_results["edit_file"] = await test_tool(
session, "edit_file", {
"path": "test_file.txt",
"edits": [{
"oldText": "Hello from Comprehensive MCP Test!",
"newText": "Hello from EDITED MCP Test!"
}]
},
"実際のファイル編集"
)
# 3.11 directory_tree - ディレクトリツリー表示
test_results["directory_tree"] = await test_tool(
session, "directory_tree", {"path": "test_suite"},
"ディレクトリツリーのJSON表示"
)
# 3.12 search_files - ファイル検索
test_results["search_files"] = await test_tool(
session, "search_files", {
"path": ".",
"pattern": "test_file",
"excludePatterns": []
},
"ファイル名パターン検索"
)
# 3.13 move_file - ファイル移動/リネーム
test_results["move_file"] = await test_tool(
session, "move_file", {
"source": "test_suite/test_file2.txt",
"destination": "test_suite/renamed_test_file.txt"
},
"ファイルのリネーム/移動"
)
# 3.14 最終確認 - 変更後のディレクトリ確認
await test_tool(
session, "list_directory", {"path": "test_suite"},
"最終的なディレクトリ構造の確認"
)
# 3.15 セキュリティテスト - パストラバーサル攻撃防御テスト
print("\n🔒 セキュリティテスト: パストラバーサル攻撃防御")
security_test_result = await test_tool(
session, "create_directory", {"path": "../security_test"},
"パストラバーサル攻撃防御テスト(失敗が期待される)"
)
test_results["security_test"] = not security_test_result # 失敗が期待される
if not security_test_result:
print("✅ セキュリティテスト成功: パストラバーサル攻撃が正しく防がれました")
# 4. テスト結果サマリー
print("\n" + "=" * 60)
print("📊 テスト結果サマリー")
print("=" * 60)
successful_tests = sum(1 for result in test_results.values() if result)
total_tests = len(test_results)
print(f"✅ 成功: {successful_tests}/{total_tests} テスト")
print(f"❌ 失敗: {total_tests - successful_tests}/{total_tests} テスト")
print("\n📋 詳細結果:")
for tool_name, result in test_results.items():
status = "✅ PASS" if result else "❌ FAIL"
print(f" {status} {tool_name}")
if successful_tests == total_tests:
print("\n🎉 すべてのテストが成功しました!")
else:
print(f"\n⚠️ {total_tests - successful_tests} 個のテストが失敗しました。")
print("=" * 60)
print("🏁 包括的MCPクライアントテスト完了")
except Exception as e:
print(f"❌ 重大なエラーが発生しました: {e}")
import traceback
print(f"🔍 詳細なエラー情報:")
traceback.print_exc()
if __name__ == "__main__":
# 非同期関数を実行
asyncio.run(run_comprehensive_mcp_test())
利用可能なツール
- ファイル読み書き (
read_file
,write_file
) - ディレクトリ操作 (
create_directory
,list_directory
) - ファイル検索 (
search_files
) - ファイル移動 (
move_file
) - ファイル情報取得 (
get_file_info
) - など11種類
3. npmパッケージを公開する手順
前提条件
- Node.js環境
- npmアカウント: npmjs.comで無料作成可能
公開手順
1. ファイル構成の確認
以下のファイルが正しく配置されていることを確認してください:
your-project/
├── dist/
│ └── index.js # MCPサーバーの本体
├── package.json # パッケージ設定
└── README.md # パッケージの説明
1. package.jsonの準備
{
"name": "@your-username/package-name",
"version": "1.0.0",
"main": "dist/index.js",
"bin": {
"your-command": "./dist/index.js"
},
"type": "module"
}
2. npmにログイン
npm login
3. パッケージ公開
# 初回公開
npm publish --access public
# アップデート時
npm version patch # or minor, major
npm publish
公開時の注意点
-
スコープ付きパッケージ (
@username/package-name
) は--access public
が必要 -
バージョン管理 は
npm version
コマンドを使用 -
テスト は公開前に
npm pack
で確認
4. まとめ
MCPファイルシステムサーバーパッケージを開発・公開することで、以下を実現しました:
- 相対パスを使用したファイル操作環境の提供
- 簡単インストール(npxで即利用可能)
- 再利用可能なコンポーネントとしての公開
npmパッケージの公開は思っているより簡単で、適切な設定とテストがあれば数分で完了します。
この情報がご活用いただければ幸いです。
Discussion