diff --git a/README.md b/README.md index b42f787..6cc1600 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,9 @@ uv sync "--directory", "<プロジェクトの絶対パス>", "run", - "main.py" + "-m" + "mcp_esa_server.server" + ], "env": { "ESA_TEAM_NAME": "", diff --git a/docs/mcp_esa_server_usage.md b/docs/mcp_esa_server_usage.md index 5cb0b75..8bfcde5 100644 --- a/docs/mcp_esa_server_usage.md +++ b/docs/mcp_esa_server_usage.md @@ -52,11 +52,11 @@ ESA_TEAM_NAME=your_team_name ## 4. サーバーの起動 ```bash -uv run mcp run main.py +uv run mcp run src/mcp_esa_server/server.py ``` または開発モード ```bash -uv run mcp dev main.py +uv run mcp dev src/mcp_esa_server/server.py ``` サーバーはデフォルトで `http://localhost:6274` で起動し、MCPエンドポイントは `/mcp` です。 diff --git a/pyproject.toml b/pyproject.toml index 31ecd90..a15a219 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,18 +1,30 @@ [project] name = "mcp-esa-server" -version = "0.1.0" +version = "0.1.8" description = "MCP server for esa.io API" -authors = [ - {name = "Naoki Numaguchi", email = "redheru@gmail.com"}, +readme = "README.md" +requires-python = ">=3.11" +license = "MIT" +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ] dependencies = [ "requests", "python-dotenv", "mcp[cli]>=1.7.1", ] -requires-python = ">=3.11" -readme = "README.md" -license = {text = "MIT"} + +[[project.authors]] +name = "scnsh" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" [project.optional-dependencies] dev = [ @@ -22,6 +34,16 @@ dev = [ "pytest-mock>=3.14.0", ] +[project.urls] +Homepage = "https://github.com/scnsh/mcp-esa-server-python" +Repository = "https://github.com/scnsh/mcp-esa-server-python" + +[project.scripts] +mcp-esa-server = "mcp_esa_server:main" + +[tool.hatch.build.targets.wheel] +packages = ["src/mcp_esa_server"] + [tool.ruff] line-length = 120 target-version = "py313" # プロジェクトで使用するPythonバージョンに合わせてください diff --git a/src/mcp_esa_server/__init__.py b/src/mcp_esa_server/__init__.py new file mode 100644 index 0000000..b42e3b0 --- /dev/null +++ b/src/mcp_esa_server/__init__.py @@ -0,0 +1,10 @@ +from . import server + + +def main(): + """このパッケージのメインエントリポイント""" + server.main() + + +# パッケージレベルで公開する他のアイテム +__all__ = ["main", "server"] diff --git a/esa_client.py b/src/mcp_esa_server/esa_client.py similarity index 98% rename from esa_client.py rename to src/mcp_esa_server/esa_client.py index 1b0ed7e..0f0db1f 100644 --- a/esa_client.py +++ b/src/mcp_esa_server/esa_client.py @@ -2,9 +2,6 @@ import requests -# from dotenv import load_dotenv - -# load_dotenv() logger = logging.getLogger(__name__) @@ -118,12 +115,14 @@ def delete_post(self, post_number: int): # Example usage (optional, for testing during development) if __name__ == "__main__": + import os + from dotenv import load_dotenv load_dotenv() logging.basicConfig(level=logging.INFO) try: - client = EsaClient() + client = EsaClient(os.getenv("ESA_TOKEN"), os.getenv("ESA_TEAM_NAME")) logger.info(f"EsaClient initialized for team: {client.team_name}") except ValueError as e: logger.error(f"Initialization failed: {e}") diff --git a/main.py b/src/mcp_esa_server/server.py similarity index 95% rename from main.py rename to src/mcp_esa_server/server.py index 1324bda..9c077e6 100644 --- a/main.py +++ b/src/mcp_esa_server/server.py @@ -1,13 +1,13 @@ import logging import os +import sys from typing import Annotated, Any from dotenv import load_dotenv +from .esa_client import EsaClient from mcp.server.fastmcp import FastMCP from pydantic import Field -from esa_client import EsaClient - # Load environment variables from .env file load_dotenv() @@ -15,16 +15,16 @@ logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) -# Create MCP instance -mcp = FastMCP("esa-mcp-server") - # Get environment variables esa_token = os.getenv("ESA_TOKEN") esa_team_name = os.getenv("ESA_TEAM_NAME") -logger.info("Attempting to initialize EsaClient.") -logger.info(f"ESA_TOKEN from env: {esa_token}") -logger.info(f"ESA_TEAM_NAME from env: {esa_team_name}") +logger.debug("Attempting to initialize EsaClient.") +logger.debug(f"ESA_TOKEN from env (for EsaClient): {esa_token}") +logger.debug(f"ESA_TEAM_NAME from env (for EsaClient): {esa_team_name}") + +# Create MCP instance +mcp = FastMCP("esa-mcp-server") # Initialize EsaClient if not esa_token or not esa_team_name: @@ -224,8 +224,11 @@ def posts_delete(post_number: int) -> dict[str, Any]: logger.error(f"Error deleting post {post_number}: {e}", exc_info=True) raise RuntimeError(f"Error deleting post: {e}") from e +def main(): + """MCPサーバーを起動するためのメイン関数""" + logger.info("Starting MCP server via main()...") + mcp.run() # Use mcp.run() to start the server when script is executed directly if __name__ == "__main__": - logger.info("Starting MCP server...") - mcp.run() + main() diff --git a/tests/integration/test_esa_client_integration.py b/tests/integration/test_esa_client_integration.py index b0d7017..1075487 100644 --- a/tests/integration/test_esa_client_integration.py +++ b/tests/integration/test_esa_client_integration.py @@ -6,7 +6,7 @@ import requests from dotenv import load_dotenv -from esa_client import EsaClient +from src.mcp_esa_server.esa_client import EsaClient # Load environment variables from .env file load_dotenv() diff --git a/tests/test_esa_client.py b/tests/test_esa_client.py index c618791..6e68bdb 100644 --- a/tests/test_esa_client.py +++ b/tests/test_esa_client.py @@ -3,7 +3,7 @@ import pytest import requests -from esa_client import EsaClient +from src.mcp_esa_server.esa_client import EsaClient @pytest.fixture @@ -36,7 +36,7 @@ def test_esa_client_initialization_missing_team_name(): EsaClient(token="test_token", team_name="") -@patch("esa_client.requests.Session.request") +@patch("src.mcp_esa_server.esa_client.requests.Session.request") def test_get_user_success(mock_request, client): "Test the get_user method success case." # Arrange: Configure the mock response @@ -54,7 +54,7 @@ def test_get_user_success(mock_request, client): assert user_data == expected_user_data -@patch("esa_client.requests.Session.request") +@patch("src.mcp_esa_server.esa_client.requests.Session.request") def test_get_user_api_error(mock_request, client): "Test the get_user method handles API errors." # Arrange: Configure the mock to raise an HTTPError @@ -68,7 +68,7 @@ def test_get_user_api_error(mock_request, client): # --- Tests for get_posts --- # -@patch("esa_client.EsaClient._request") +@patch("src.mcp_esa_server.esa_client.EsaClient._request") def test_get_posts_success_no_params(mock_request, client): "Test get_posts success with no parameters." # Arrange @@ -83,7 +83,7 @@ def test_get_posts_success_no_params(mock_request, client): assert posts_data == expected_response -@patch("esa_client.EsaClient._request") +@patch("src.mcp_esa_server.esa_client.EsaClient._request") def test_get_posts_success_with_query(mock_request, client): "Test get_posts success with a search query." # Arrange @@ -99,7 +99,7 @@ def test_get_posts_success_with_query(mock_request, client): assert posts_data == expected_response -@patch("esa_client.EsaClient._request") +@patch("src.mcp_esa_server.esa_client.EsaClient._request") def test_get_posts_success_with_pagination(mock_request, client): "Test get_posts success with pagination parameters." # Arrange @@ -116,7 +116,7 @@ def test_get_posts_success_with_pagination(mock_request, client): assert posts_data == expected_response -@patch("esa_client.EsaClient._request") +@patch("src.mcp_esa_server.esa_client.EsaClient._request") def test_get_posts_success_with_all_params(mock_request, client): "Test get_posts success with all parameters." # Arrange @@ -134,7 +134,7 @@ def test_get_posts_success_with_all_params(mock_request, client): assert posts_data == expected_response -@patch("esa_client.EsaClient._request") +@patch("src.mcp_esa_server.esa_client.EsaClient._request") def test_get_posts_api_error(mock_request, client): "Test get_posts handles API errors from _request." # Arrange @@ -148,7 +148,7 @@ def test_get_posts_api_error(mock_request, client): # --- Tests for get_post --- # -@patch("esa_client.EsaClient._request") +@patch("src.mcp_esa_server.esa_client.EsaClient._request") def test_get_post_success(mock_request, client): "Test get_post success case." # Arrange @@ -164,7 +164,7 @@ def test_get_post_success(mock_request, client): assert post_data == expected_response -@patch("esa_client.EsaClient._request") +@patch("src.mcp_esa_server.esa_client.EsaClient._request") def test_get_post_api_error(mock_request, client): "Test get_post handles API errors from _request." # Arrange @@ -179,7 +179,7 @@ def test_get_post_api_error(mock_request, client): # --- Tests for create_post (method to be added to EsaClient) --- -@patch("esa_client.EsaClient._request") +@patch("src.mcp_esa_server.esa_client.EsaClient._request") def test_create_post_success(mock_request, client): """Test create_post successfully creates a post and returns its data.""" # Arrange @@ -195,7 +195,7 @@ def test_create_post_success(mock_request, client): assert result == expected_response -@patch("esa_client.EsaClient._request") +@patch("src.mcp_esa_server.esa_client.EsaClient._request") def test_create_post_api_error(mock_request, client): """Test create_post handles API errors from _request.""" # Arrange @@ -210,7 +210,7 @@ def test_create_post_api_error(mock_request, client): # --- Tests for update_post (method to be added to EsaClient) --- -@patch("esa_client.EsaClient._request") +@patch("src.mcp_esa_server.esa_client.EsaClient._request") def test_update_post_success(mock_request, client): """Test update_post successfully updates a post and returns its data.""" # Arrange @@ -227,7 +227,7 @@ def test_update_post_success(mock_request, client): assert result == expected_response -@patch("esa_client.EsaClient._request") +@patch("src.mcp_esa_server.esa_client.EsaClient._request") def test_update_post_api_error(mock_request, client): """Test update_post handles API errors from _request.""" # Arrange diff --git a/tests/test_main_tools.py b/tests/test_main_tools.py index 0353608..b46e671 100644 --- a/tests/test_main_tools.py +++ b/tests/test_main_tools.py @@ -2,9 +2,7 @@ import pytest -# Assuming main.py is in the parent directory of tests, or path is configured -# For this example, let's assume 'main' can be imported due to pyproject.toml settings or structure. -from main import ( +from src.mcp_esa_server.server import ( posts_create, posts_delete, posts_get_detail, @@ -18,14 +16,14 @@ @pytest.fixture def mock_esa_client_in_main(): # Patch 'main.esa_client' which is used by the MCP tools - with patch("main.esa_client", new_callable=MagicMock) as mock_client: + with patch("src.mcp_esa_server.server.esa_client", new_callable=MagicMock) as mock_client: yield mock_client # Fixture to mock a non-initialized esa_client in main.py @pytest.fixture def mock_esa_client_none_in_main(): - with patch("main.esa_client", None) as mock_client_none: + with patch("src.mcp_esa_server.server.esa_client", None) as mock_client_none: yield mock_client_none diff --git a/uv.lock b/uv.lock index b759f68..dd11041 100644 --- a/uv.lock +++ b/uv.lock @@ -261,8 +261,8 @@ cli = [ [[package]] name = "mcp-esa-server" -version = "0.1.0" -source = { virtual = "." } +version = "0.1.8" +source = { editable = "." } dependencies = [ { name = "mcp", extra = ["cli"] }, { name = "python-dotenv" },