Skip to main content
Glama
mcp_server.py15.3 kB
""" MCPサーバーモジュール Model Context Protocol (MCP)に準拠したサーバーを提供します。 JSON-RPC over stdioを使用してクライアントからのリクエストを処理します。 """ import sys import json import logging from typing import Dict, Any, List, Optional from pathlib import Path from src.notion_client import NotionClient class MCPServer: """ Model Context Protocol (MCP)に準拠したサーバークラス JSON-RPC over stdioを使用してクライアントからのリクエストを処理します。 Attributes: notion_client: NotionClientのインスタンス logger: ロガー """ def __init__(self, token: Optional[str] = None): """ MCPServerのコンストラクタ Args: token: Notion API Token(指定されない場合は環境変数から取得) """ self.notion_client = NotionClient(token) # ロガーの設定 self.logger = logging.getLogger("mcp_server") self.logger.setLevel(logging.INFO) # ファイルハンドラの設定 log_dir = Path("logs") log_dir.mkdir(exist_ok=True) file_handler = logging.FileHandler(log_dir / "mcp_server.log") file_handler.setLevel(logging.INFO) # フォーマッタの設定 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") file_handler.setFormatter(formatter) # ハンドラの追加 self.logger.addHandler(file_handler) def start(self): """ サーバーを起動し、stdioからのリクエストをリッスンします。 """ self.logger.info("MCPサーバーを起動しました") # サーバー情報を出力 self._send_response( { "jsonrpc": "2.0", "method": "server/info", "params": { "name": "notion-mcp-light", "version": "0.1.0", "description": "Notion MCP Light Server", "tools": self._get_tools(), "resources": self._get_resources(), }, } ) # ツール情報を出力 self._send_response( { "jsonrpc": "2.0", "method": "tools/list", "params": { "tools": self._get_tools(), }, } ) # リクエストをリッスン while True: try: # 標準入力からリクエストを読み込む request_line = sys.stdin.readline() if not request_line: break # リクエストをパース request = json.loads(request_line) self.logger.info(f"リクエストを受信しました: {request}") # リクエストを処理 self._handle_request(request) except json.JSONDecodeError: self.logger.error("JSONのパースに失敗しました") self._send_error(-32700, "Parse error", None) except Exception as e: self.logger.error(f"エラーが発生しました: {str(e)}") self._send_error(-32603, f"Internal error: {str(e)}", None) def _handle_request(self, request: Dict[str, Any]): """ リクエストを処理します。 Args: request: JSONリクエスト """ # リクエストのバリデーション if "jsonrpc" not in request or request["jsonrpc"] != "2.0": self._send_error(-32600, "Invalid Request", request.get("id")) return if "method" not in request: self._send_error(-32600, "Method not specified", request.get("id")) return # メソッドの取得 method = request["method"] params = request.get("params", {}) request_id = request.get("id") # メソッドの処理 if method == "initialize": self._handle_initialize(params, request_id) elif method == "tools/list": self._handle_tools_list(request_id) elif method == "tools/call": self._handle_tools_call(params, request_id) elif method == "uploadMarkdown": self._handle_upload_markdown(params, request_id) elif method == "downloadMarkdown": self._handle_download_markdown(params, request_id) else: self._send_error(-32601, f"Method not found: {method}", request_id) def _handle_initialize(self, params: Dict[str, Any], request_id: Any): """ initializeメソッドを処理します。 Args: params: リクエストパラメータ request_id: リクエストID """ # クライアント情報を取得(オプション) client_name = params.get("client_name", "unknown") client_version = params.get("client_version", "unknown") self.logger.info(f"クライアント '{client_name} {client_version}' が接続しました") # サーバーの機能を返す response = { "protocolVersion": "2024-11-05", "serverInfo": {"name": "notion-mcp-light", "version": "0.1.0", "description": "Notion MCP Light Server"}, "capabilities": {"tools": {"listChanged": False}, "resources": {"listChanged": False, "subscribe": False}}, "instructions": "NotionMCP Lightを使用する際の注意点:\n1. filepathパラメータには絶対パスを使用してください。相対パスを使用すると正しく処理できない場合があります。\n2. page_idパラメータにはNotionページIDを使用してください。URLではなくページIDのみを指定してください。\n3. Notionページのページ形式のURLからページIDを抽出するには、URLの最後の部分(https://www.notion.so/xxx/yyy-zzz の zzz部分)を使用してください。\n4. データベースIDを指定する場合も同様に、URLではなくIDのみを使用してください。", } self._send_result(response, request_id) # ツール情報を送信 self._send_response( { "jsonrpc": "2.0", "method": "tools/list", "params": { "tools": self._get_tools(), }, } ) def _handle_upload_markdown(self, params: Dict[str, Any], request_id: Any): """ uploadMarkdownメソッドを処理します。 Args: params: リクエストパラメータ request_id: リクエストID """ # パラメータのバリデーション if "filepath" not in params: self._send_error(-32602, "Invalid params: filepath is required", request_id) return filepath = params["filepath"] database_id = params.get("database_id") try: # Markdownをアップロード page_id = self.notion_client.upload_markdown(filepath, database_id) # レスポンスを送信 self._send_result({"page_id": page_id}, request_id) except FileNotFoundError: self._send_error(-32602, f"File not found: {filepath}", request_id) except Exception as e: self._send_error(-32603, f"Failed to upload markdown: {str(e)}", request_id) def _handle_download_markdown(self, params: Dict[str, Any], request_id: Any): """ downloadMarkdownメソッドを処理します。 Args: params: リクエストパラメータ request_id: リクエストID """ # パラメータのバリデーション if "page_id" not in params: self._send_error(-32602, "Invalid params: page_id is required", request_id) return if "output_path" not in params: self._send_error(-32602, "Invalid params: output_path is required", request_id) return page_id = params["page_id"] output_path = params["output_path"] try: # ページをダウンロード self.notion_client.download_page(page_id, output_path) # レスポンスを送信 self._send_result({"output_path": output_path}, request_id) except Exception as e: self._send_error(-32603, f"Failed to download markdown: {str(e)}", request_id) def _send_result(self, result: Any, request_id: Any): """ 成功レスポンスを送信します。 Args: result: レスポンス結果 request_id: リクエストID """ response = {"jsonrpc": "2.0", "result": result, "id": request_id} self._send_response(response) def _send_error(self, code: int, message: str, request_id: Any): """ エラーレスポンスを送信します。 Args: code: エラーコード message: エラーメッセージ request_id: リクエストID """ response = {"jsonrpc": "2.0", "error": {"code": code, "message": message}, "id": request_id} self._send_response(response) def _send_response(self, response: Dict[str, Any]): """ レスポンスを標準出力に送信します。 Args: response: レスポンス """ response_json = json.dumps(response) print(response_json, flush=True) self.logger.info(f"レスポンスを送信しました: {response_json}") def _get_tools(self) -> List[Dict[str, Any]]: """ サーバーが提供するツールの一覧を取得します。 Returns: ツールの一覧 """ return [ { "name": "uploadMarkdown", "description": "Markdownファイルをアップロードし、Notionページとして作成します", "inputSchema": { "type": "object", "properties": { "filepath": { "type": "string", "description": "アップロードするMarkdownファイルのパス。絶対パスを指定してください。", }, "database_id": { "type": "string", "description": "アップロード先のデータベースID。URLでなくIDです。", }, "page_id": { "type": "string", "description": "親ページID(database_idが指定されていない場合に使用)。URLでなくIDです。", }, }, "required": ["filepath"], }, }, { "name": "downloadMarkdown", "description": "NotionページをダウンロードしてMarkdownファイルとして保存します", "inputSchema": { "type": "object", "properties": { "page_id": {"type": "string", "description": "ダウンロードするNotionページのID。URLでなくIDです。"}, "output_path": {"type": "string", "description": "出力先のファイルパス。絶対ファイルパス。"}, }, "required": ["page_id", "output_path"], }, }, ] def _handle_tools_call(self, params: Dict[str, Any], request_id: Any): """ tools/callメソッドを処理します。 Args: params: リクエストパラメータ request_id: リクエストID """ # パラメータのバリデーション if "name" not in params: self._send_error(-32602, "Invalid params: name is required", request_id) return if "arguments" not in params: self._send_error(-32602, "Invalid params: arguments is required", request_id) return tool_name = params["name"] arguments = params["arguments"] # ツールの処理 if tool_name == "uploadMarkdown": try: page_id = self.notion_client.upload_markdown( arguments["filepath"], arguments.get("database_id"), arguments.get("page_id") ) self._send_result( {"content": [{"type": "text", "text": f"Markdownファイルをアップロードしました。ページID: {page_id}"}]}, request_id, ) except FileNotFoundError: self._send_result( { "content": [{"type": "text", "text": f"ファイルが見つかりません: {arguments.get('filepath')}"}], "isError": True, }, request_id, ) except Exception as e: self._send_result( { "content": [{"type": "text", "text": f"Markdownファイルのアップロードに失敗しました: {str(e)}"}], "isError": True, }, request_id, ) elif tool_name == "downloadMarkdown": try: self.notion_client.download_page(arguments["page_id"], arguments["output_path"]) self._send_result( { "content": [ {"type": "text", "text": f"Notionページをダウンロードしました。出力先: {arguments['output_path']}"} ] }, request_id, ) except Exception as e: self._send_result( { "content": [{"type": "text", "text": f"Notionページのダウンロードに失敗しました: {str(e)}"}], "isError": True, }, request_id, ) else: self._send_result( {"content": [{"type": "text", "text": f"ツールが見つかりません: {tool_name}"}], "isError": True}, request_id ) def _handle_tools_list(self, request_id: Any): """ tools/listメソッドを処理します。 Args: request_id: リクエストID """ tools = self._get_tools() self._send_result({"tools": tools}, request_id) def _get_resources(self) -> List[Dict[str, Any]]: """ サーバーが提供するリソースの一覧を取得します。 Returns: リソースの一覧 """ return []

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/karaage0703/notion-mcp-light'

If you have feedback or need assistance with the MCP directory API, please join our Discord server