935abf86d4
build-image / build (push) Failing after 18s
- Bump mcp[cli] 1.22 -> 1.28.1 (negotiates MCP protocol 2025-11-25, matching current Claude clients; the old 2025-06-18 server never got a tools/list on the connector surface). - Bake transport config into code: stateless_http + json_response for HTTP (single JSON body instead of a 34KB SSE stream, which the connector pipeline handles far more reliably). - Bake Authentik OAuth (AuthSettings + JWT TokenVerifier) into intervals_mcp_server.auth, configured from MCP_ISSUER/MCP_RESOURCE/MCP_JWKS_URI/MCP_CLIENT_ID — removes the runtime FastMCP.__init__ monkeypatch from the k8s deployment command. - Accept token audience with/without trailing slash (RFC 8707 clients use the slash-normalised resource metadata value). - Dockerfile CMD runs the module (transport via MCP_TRANSPORT); add .gitea CI to build+push the image to git.farh.net/farhoodlabs/intervalsicu-mcp. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
133 lines
4.5 KiB
Python
133 lines
4.5 KiB
Python
"""
|
|
Intervals.icu MCP Server
|
|
|
|
This module implements a Model Context Protocol (MCP) server for connecting
|
|
Claude with the Intervals.icu API. It provides tools for retrieving and managing
|
|
athlete data, including activities, events, workouts, and wellness metrics.
|
|
|
|
Main Features:
|
|
- Activity retrieval and detailed analysis
|
|
- Event management (races, workouts, calendar items)
|
|
- Wellness data tracking and visualization
|
|
- Error handling with user-friendly messages
|
|
- Configurable parameters with environment variable support
|
|
|
|
Usage:
|
|
This server is designed to be run as a standalone script and exposes several MCP tools
|
|
for use with Claude Desktop or other MCP-compatible clients. The server loads configuration
|
|
from environment variables (optionally via a .env file) and communicates with the Intervals.icu API.
|
|
|
|
To run the server:
|
|
$ python src/intervals_mcp_server/server.py
|
|
|
|
MCP tools provided:
|
|
- get_activities
|
|
- get_activity_details
|
|
- get_activity_intervals
|
|
- get_activity_streams
|
|
- get_activity_messages
|
|
- add_activity_message
|
|
- get_events
|
|
- get_event_by_id
|
|
- add_or_update_event
|
|
- delete_event
|
|
- delete_events_by_date_range
|
|
- get_wellness_data
|
|
- get_athlete_power_curves
|
|
- get_custom_items
|
|
- get_custom_item_by_id
|
|
- create_custom_item
|
|
- update_custom_item
|
|
- delete_custom_item
|
|
|
|
See the README for more details on configuration and usage.
|
|
"""
|
|
|
|
import logging
|
|
|
|
# Import API client and configuration
|
|
from intervals_mcp_server.api.client import (
|
|
httpx_client, # Re-export for backward compatibility with tests
|
|
make_intervals_request,
|
|
)
|
|
from intervals_mcp_server.config import get_config
|
|
from intervals_mcp_server.mcp_instance import mcp
|
|
|
|
# Import types and validation
|
|
from intervals_mcp_server.server_setup import setup_transport, start_server
|
|
from intervals_mcp_server.utils.validation import validate_athlete_id
|
|
|
|
# Configure logging
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
handlers=[logging.StreamHandler()],
|
|
)
|
|
logger = logging.getLogger("intervals_icu_mcp_server")
|
|
|
|
# Get configuration instance
|
|
config = get_config()
|
|
|
|
# Import tool modules to register them (tools register themselves via @mcp.tool() decorators)
|
|
# Import tool functions for re-export
|
|
from intervals_mcp_server.tools.activities import ( # pylint: disable=wrong-import-position # noqa: E402
|
|
add_activity_message,
|
|
get_activities,
|
|
get_activity_details,
|
|
get_activity_intervals,
|
|
get_activity_messages,
|
|
get_activity_streams,
|
|
)
|
|
from intervals_mcp_server.tools.events import ( # pylint: disable=wrong-import-position # noqa: E402
|
|
add_or_update_event,
|
|
delete_event,
|
|
delete_events_by_date_range,
|
|
get_event_by_id,
|
|
get_events,
|
|
)
|
|
from intervals_mcp_server.tools.gear import get_gear_list # pylint: disable=wrong-import-position # noqa: E402
|
|
from intervals_mcp_server.tools.wellness import get_wellness_data # pylint: disable=wrong-import-position # noqa: E402
|
|
from intervals_mcp_server.tools.power_curves import get_athlete_power_curves # pylint: disable=wrong-import-position # noqa: E402
|
|
from intervals_mcp_server.tools.custom_items import ( # pylint: disable=wrong-import-position # noqa: E402
|
|
create_custom_item,
|
|
delete_custom_item,
|
|
get_custom_item_by_id,
|
|
get_custom_items,
|
|
update_custom_item,
|
|
)
|
|
|
|
# Re-export make_intervals_request and httpx_client for backward compatibility
|
|
# pylint: disable=duplicate-code # This __all__ list is intentionally similar to tools/__init__.py
|
|
__all__ = [
|
|
"make_intervals_request",
|
|
"httpx_client", # Re-exported for test compatibility
|
|
"add_activity_message",
|
|
"get_activities",
|
|
"get_activity_details",
|
|
"get_activity_intervals",
|
|
"get_activity_messages",
|
|
"get_activity_streams",
|
|
"get_events",
|
|
"get_event_by_id",
|
|
"delete_event",
|
|
"delete_events_by_date_range",
|
|
"add_or_update_event",
|
|
"get_wellness_data",
|
|
"get_athlete_power_curves",
|
|
"get_custom_items",
|
|
"get_custom_item_by_id",
|
|
"create_custom_item",
|
|
"update_custom_item",
|
|
"delete_custom_item",
|
|
]
|
|
|
|
|
|
# Run the server
|
|
if __name__ == "__main__":
|
|
# Validate ATHLETE_ID when server starts (not at import time to allow tests)
|
|
validate_athlete_id(config.athlete_id)
|
|
|
|
# Setup transport and start server
|
|
selected_transport = setup_transport()
|
|
start_server(mcp, selected_transport)
|