Skip to content

Feature/packaging #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ uv sync
"--directory",
"<プロジェクトの絶対パス>",
"run",
"main.py"
"-m"
"mcp_esa_server.server"

],
"env": {
"ESA_TEAM_NAME": "<your-team-name>",
Expand Down
4 changes: 2 additions & 2 deletions docs/mcp_esa_server_usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` です。
Expand Down
34 changes: 28 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -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 = "[email protected]"},
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 = [
Expand All @@ -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バージョンに合わせてください
Expand Down
10 changes: 10 additions & 0 deletions src/mcp_esa_server/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from . import server


def main():
"""このパッケージのメインエントリポイント"""
server.main()


# パッケージレベルで公開する他のアイテム
__all__ = ["main", "server"]
7 changes: 3 additions & 4 deletions esa_client.py → src/mcp_esa_server/esa_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@

import requests

# from dotenv import load_dotenv

# load_dotenv()
logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -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}")
Expand Down
23 changes: 13 additions & 10 deletions main.py → src/mcp_esa_server/server.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
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()

# Setup logging using standard library
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:
Expand Down Expand Up @@ -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()
2 changes: 1 addition & 1 deletion tests/integration/test_esa_client_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
28 changes: 14 additions & 14 deletions tests/test_esa_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import pytest
import requests

from esa_client import EsaClient
from src.mcp_esa_server.esa_client import EsaClient


@pytest.fixture
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
8 changes: 3 additions & 5 deletions tests/test_main_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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


Expand Down
4 changes: 2 additions & 2 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading