strava-activity-mcp-server 0.1.9__tar.gz → 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (19) hide show
  1. {strava_activity_mcp_server-0.1.9 → strava_activity_mcp_server-0.2.1}/.python-version +1 -1
  2. strava_activity_mcp_server-0.2.1/.vscode/settings.json +15 -0
  3. {strava_activity_mcp_server-0.1.9 → strava_activity_mcp_server-0.2.1}/PKG-INFO +4 -3
  4. {strava_activity_mcp_server-0.1.9 → strava_activity_mcp_server-0.2.1}/README.md +3 -2
  5. {strava_activity_mcp_server-0.1.9 → strava_activity_mcp_server-0.2.1}/pyproject.toml +21 -18
  6. {strava_activity_mcp_server-0.1.9 → strava_activity_mcp_server-0.2.1}/src/strava_activity_mcp_server/__init__.py +3 -3
  7. strava_activity_mcp_server-0.2.1/src/strava_activity_mcp_server/strava_activity_mcp_server.py +176 -0
  8. {strava_activity_mcp_server-0.1.9 → strava_activity_mcp_server-0.2.1}/uv.lock +1 -1
  9. strava_activity_mcp_server-0.1.9/.vscode/settings.json +0 -6
  10. strava_activity_mcp_server-0.1.9/src/strava_activity_mcp_server/strava_activity_mcp_server.py +0 -100
  11. {strava_activity_mcp_server-0.1.9 → strava_activity_mcp_server-0.2.1}/.github/workflows/python-publish.yml +0 -0
  12. {strava_activity_mcp_server-0.1.9 → strava_activity_mcp_server-0.2.1}/.gitignore +0 -0
  13. {strava_activity_mcp_server-0.1.9 → strava_activity_mcp_server-0.2.1}/LICENSE +0 -0
  14. {strava_activity_mcp_server-0.1.9 → strava_activity_mcp_server-0.2.1}/ref/auth.jpg +0 -0
  15. {strava_activity_mcp_server-0.1.9 → strava_activity_mcp_server-0.2.1}/ref/code.jpg +0 -0
  16. {strava_activity_mcp_server-0.1.9 → strava_activity_mcp_server-0.2.1}/ref/image.jpg +0 -0
  17. {strava_activity_mcp_server-0.1.9 → strava_activity_mcp_server-0.2.1}/ref/mcp_pypi_example.md +0 -0
  18. {strava_activity_mcp_server-0.1.9 → strava_activity_mcp_server-0.2.1}/requirements.txt +0 -0
  19. {strava_activity_mcp_server-0.1.9 → strava_activity_mcp_server-0.2.1}/src/strava_activity_mcp_server/__main__.py +0 -0
@@ -0,0 +1,15 @@
1
+ {
2
+ "python-envs.defaultEnvManager": "ms-python.python:venv",
3
+ "python-envs.defaultPackageManager": "ms-python.python:pip",
4
+ "python-envs.pythonProjects": [],
5
+ "git.ignoreLimitWarning": true,
6
+ "python.testing.unittestArgs": [
7
+ "-v",
8
+ "-s",
9
+ "./src",
10
+ "-p",
11
+ "*test*.py"
12
+ ],
13
+ "python.testing.pytestEnabled": false,
14
+ "python.testing.unittestEnabled": true
15
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: strava-activity-mcp-server
3
- Version: 0.1.9
3
+ Version: 0.2.1
4
4
  Summary: STRAVA ACTIVITY MCP SERVER
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.10
@@ -13,6 +13,7 @@ Description-Content-Type: text/markdown
13
13
  ![Python Package](https://github.com/tomekkorbak/strava-mcp-server/actions/workflows/python-package.yml/badge.svg)
14
14
  [![License: GNU](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://opensource.org/licenses/gpl-3-0)
15
15
  [![Python 3.13](https://img.shields.io/badge/python-3.13-blue?logo=python&logoColor=white)](https://www.python.org/downloads/release/python-3130/)
16
+ [![PyPI - Downloads](https://img.shields.io/pypi/dm/strava-activity-mcp-server)](https://pypistats.org/packages/strava-activity-mcp-server)
16
17
 
17
18
  ![image](https://github.com/user-attachments/assets/4bb214ca-1132-4e63-9390-d6eaddab50be)
18
19
 
@@ -117,10 +118,10 @@ Any MCP-capable client can launch the server using a config similar to the follo
117
118
  To quickly test the server using the Model Context Protocol inspector tool, run:
118
119
 
119
120
  ```powershell
120
- npx @modelcontextprotocol/inspector uvx strava-mcp-server
121
+ npx @modelcontextprotocol/inspector uvx strava-activity-mcp-server
121
122
  ```
122
123
 
123
- This will attempt to start the server with the `uvx` transport and connect the inspector to the running MCP server instance named `strava-mcp-server`.
124
+ This will attempt to start the server with the `uvx` transport and connect the inspector to the running MCP server instance named `strava-activity-mcp-server`.
124
125
 
125
126
 
126
127
  ## Contributing
@@ -2,6 +2,7 @@
2
2
  ![Python Package](https://github.com/tomekkorbak/strava-mcp-server/actions/workflows/python-package.yml/badge.svg)
3
3
  [![License: GNU](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://opensource.org/licenses/gpl-3-0)
4
4
  [![Python 3.13](https://img.shields.io/badge/python-3.13-blue?logo=python&logoColor=white)](https://www.python.org/downloads/release/python-3130/)
5
+ [![PyPI - Downloads](https://img.shields.io/pypi/dm/strava-activity-mcp-server)](https://pypistats.org/packages/strava-activity-mcp-server)
5
6
 
6
7
  ![image](https://github.com/user-attachments/assets/4bb214ca-1132-4e63-9390-d6eaddab50be)
7
8
 
@@ -106,10 +107,10 @@ Any MCP-capable client can launch the server using a config similar to the follo
106
107
  To quickly test the server using the Model Context Protocol inspector tool, run:
107
108
 
108
109
  ```powershell
109
- npx @modelcontextprotocol/inspector uvx strava-mcp-server
110
+ npx @modelcontextprotocol/inspector uvx strava-activity-mcp-server
110
111
  ```
111
112
 
112
- This will attempt to start the server with the `uvx` transport and connect the inspector to the running MCP server instance named `strava-mcp-server`.
113
+ This will attempt to start the server with the `uvx` transport and connect the inspector to the running MCP server instance named `strava-activity-mcp-server`.
113
114
 
114
115
 
115
116
  ## Contributing
@@ -1,18 +1,21 @@
1
- [project]
2
- name = "strava-activity-mcp-server"
3
- version = "0.1.9"
4
- description = "STRAVA ACTIVITY MCP SERVER"
5
- readme = "README.md"
6
- requires-python = ">=3.10"
7
- dependencies = [
8
- "build>=1.3.0",
9
- "mcp[cli]>=1.16.0",
10
- "twine>=6.2.0",
11
- ]
12
-
13
- [project.scripts]
14
- strava-activity-mcp-server = "strava_activity_mcp_server:main"
15
-
16
- [build-system]
17
- requires = ["hatchling"]
18
- build-backend = "hatchling.build"
1
+ [project]
2
+ name = "strava-activity-mcp-server"
3
+ version = "0.2.1"
4
+ description = "STRAVA ACTIVITY MCP SERVER"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ dependencies = [
8
+ "build>=1.3.0",
9
+ "mcp[cli]>=1.16.0",
10
+ "twine>=6.2.0",
11
+ ]
12
+
13
+ [project.scripts]
14
+ strava-activity-mcp-server = "strava_activity_mcp_server:main"
15
+
16
+ [build-system]
17
+ requires = ["hatchling"]
18
+ build-backend = "hatchling.build"
19
+
20
+ [tool.hatch.build.targets.wheel]
21
+ packages = ["src/strava_activity_mcp_server"]
@@ -1,4 +1,4 @@
1
- from .strava_activity_mcp_server import mcp
2
- def main() -> None:
3
- """Run the MCP server."""
1
+ from .strava_activity_mcp_server import mcp
2
+ def main() -> None:
3
+ """Run the MCP server."""
4
4
  mcp.run()
@@ -0,0 +1,176 @@
1
+ import sys
2
+ import os
3
+ from mcp.server.fastmcp import FastMCP # Import FastMCP, the quickstart server base
4
+ mcp = FastMCP("Strava") # Initialize an MCP server instance with a descriptive name
5
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
6
+ import requests
7
+ import urllib.parse
8
+
9
+ @mcp.tool("strava://auth/url")
10
+ def get_auth_url(client_id: int | None = None):
11
+ """Return the Strava OAuth authorization URL. If client_id is not provided,
12
+ read it from the STRAVA_CLIENT_ID environment variable."""
13
+ if client_id is None:
14
+ client_id_env = os.getenv("STRAVA_CLIENT_ID")
15
+ if not client_id_env:
16
+ return {"error": "STRAVA_CLIENT_ID environment variable is not set"}
17
+ try:
18
+ client_id = int(client_id_env)
19
+ except ValueError:
20
+ return {"error": "STRAVA_CLIENT_ID must be an integer"}
21
+
22
+ params = {
23
+ "client_id": client_id,
24
+ "response_type": "code",
25
+ "redirect_uri": "https://developers.strava.com/oauth2-redirect/",
26
+ "approval_prompt": "force",
27
+ "scope": "read,activity:read_all",
28
+ }
29
+ # Always return whole URL and not part of it
30
+ return "https://www.strava.com/oauth/authorize?" + urllib.parse.urlencode(params)
31
+
32
+ @mcp.tool("strava://auth/refresh")
33
+ def refresh_access_token(
34
+ refresh_token: str,
35
+ client_id: int | None = None,
36
+ client_secret: str | None = None,
37
+ ) -> dict:
38
+ """Refresh an access token using a refresh token."""
39
+ if not refresh_token:
40
+ return {"error": "refresh token is required"}
41
+
42
+ if client_id is None:
43
+ client_id_env = os.getenv("STRAVA_CLIENT_ID")
44
+ if not client_id_env:
45
+ return {"error": "STRAVA_CLIENT_ID environment variable is not set"}
46
+ try:
47
+ client_id = int(client_id_env)
48
+ except ValueError:
49
+ return {"error": "STRAVA_CLIENT_ID must be an integer"}
50
+
51
+ if client_secret is None:
52
+ client_secret_env = os.getenv("STRAVA_CLIENT_SECRET")
53
+ if not client_secret_env:
54
+ return {"error": "STRAVA_CLIENT_SECRET environment variable is not set"}
55
+ try:
56
+ client_secret = str(client_secret_env)
57
+ except ValueError:
58
+ return {"error": "STRAVA_CLIENT_SECRET must be a string"}
59
+
60
+ resp = requests.post(
61
+ "https://www.strava.com/oauth/token",
62
+ data={
63
+ "client_id": client_id,
64
+ "client_secret": client_secret,
65
+ "refresh_token": refresh_token,
66
+ "grant_type": "refresh_token",
67
+ },
68
+ )
69
+
70
+ try:
71
+ resp.raise_for_status()
72
+ except requests.HTTPError:
73
+ return {"error": "token refresh failed", "status_code": resp.status_code, "response": resp.text}
74
+ except Exception as e:
75
+ return {"error": "token refresh failed", "status_code": resp.status_code, "response": resp.text, "error": str(e)}
76
+
77
+ tokens = resp.json()
78
+ print(tokens) # Print tokens for debugging (optional)
79
+
80
+ return {
81
+ "access_token": tokens.get("access_token"),
82
+ "refresh_token": tokens.get("refresh_token"),
83
+ "expires_at": tokens.get("expires_at"),
84
+ "expires_in": tokens.get("expires_in")
85
+ }
86
+
87
+ @mcp.tool("strava://athlete/stats")
88
+ def get_athlete_stats(
89
+ code: str,
90
+ client_id: int | None = None,
91
+ client_secret: str | None = None,
92
+ ) -> dict:
93
+ """Exchange an authorization code for access + refresh tokens and get athlete activities."""
94
+ if not code:
95
+ return {"error": "authorization code is required"}
96
+
97
+ if client_id is None:
98
+ client_id_env = os.getenv("STRAVA_CLIENT_ID")
99
+ if not client_id_env:
100
+ return {"error": "STRAVA_CLIENT_ID environment variable is not set"}
101
+ try:
102
+ client_id = int(client_id_env)
103
+ except ValueError:
104
+ return {"error": "STRAVA_CLIENT_ID must be an integer"}
105
+
106
+ if client_secret is None:
107
+ client_secret_env = os.getenv("STRAVA_CLIENT_SECRET")
108
+ if not client_secret_env:
109
+ return {"error": "STRAVA_CLIENT_SECRET environment variable is not set"}
110
+ try:
111
+ client_secret = str(client_secret_env)
112
+ except ValueError:
113
+ return {"error": "STRAVA_CLIENT_SECRET must be a string"}
114
+
115
+ resp = requests.post(
116
+ "https://www.strava.com/oauth/token",
117
+ data={
118
+ "client_id": client_id,
119
+ "client_secret": client_secret,
120
+ "code": code,
121
+ "grant_type": "authorization_code",
122
+ },
123
+ )
124
+
125
+ try:
126
+ resp.raise_for_status()
127
+ except requests.HTTPError:
128
+ return {"error": "token request failed", "status_code": resp.status_code, "response": resp.text}
129
+ except Exception as e:
130
+ return {"error": "token request failed", "status_code": resp.status_code, "response": resp.text, "error": str(e)}
131
+
132
+ tokens = resp.json()
133
+ # Print tokens for debugging (optional)
134
+ print(tokens)
135
+
136
+ access_token = tokens.get("access_token")
137
+ refresh_token = tokens.get("refresh_token")
138
+
139
+ # return {"tokens": tokens, "access_token": access_token, "refresh_token": refresh_token}
140
+
141
+ url = "https://www.strava.com/api/v3/athlete/activities?per_page=60"
142
+ headers = {
143
+ "accept": "application/json",
144
+ "authorization": f"Bearer {access_token}"
145
+ }
146
+
147
+ response = requests.get(url, headers=headers)
148
+
149
+ return response.json()
150
+
151
+ @mcp.tool("strava://athlete/stats-with-token")
152
+ def get_athlete_stats_with_token(access_token: str) -> dict:
153
+ """Get athlete activities using an existing access token."""
154
+ if not access_token:
155
+ return {"error": "access token is required"}
156
+
157
+ url = "https://www.strava.com/api/v3/athlete/activities?per_page=60"
158
+ headers = {
159
+ "accept": "application/json",
160
+ "authorization": f"Bearer {access_token}"
161
+ }
162
+
163
+ response = requests.get(url, headers=headers)
164
+
165
+ try:
166
+ response.raise_for_status()
167
+ except requests.HTTPError:
168
+ return {"error": "API request failed", "status_code": response.status_code, "response": response.text}
169
+ except Exception as e:
170
+ return {"error": "API request failed", "status_code": response.status_code, "response": response.text, "error": str(e)}
171
+
172
+ return response.json()
173
+
174
+
175
+ if __name__ == "__main__":
176
+ mcp.run(transport="stdio") # Run the server, using standard input/output for communication
@@ -1031,7 +1031,7 @@ wheels = [
1031
1031
 
1032
1032
  [[package]]
1033
1033
  name = "strava-activity-mcp-server"
1034
- version = "0.1.9"
1034
+ version = "0.2.1"
1035
1035
  source = { editable = "." }
1036
1036
  dependencies = [
1037
1037
  { name = "build" },
@@ -1,6 +0,0 @@
1
- {
2
- "python-envs.defaultEnvManager": "ms-python.python:venv",
3
- "python-envs.defaultPackageManager": "ms-python.python:pip",
4
- "python-envs.pythonProjects": [],
5
- "git.ignoreLimitWarning": true
6
- }
@@ -1,100 +0,0 @@
1
- import sys
2
- import os
3
- from mcp.server.fastmcp import FastMCP # Import FastMCP, the quickstart server base
4
- mcp = FastMCP("Strava") # Initialize an MCP server instance with a descriptive name
5
- sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
6
- import requests
7
- import urllib.parse
8
-
9
- @mcp.tool("strava://auth/url")
10
-
11
- def get_auth_url(client_id: int | None = None):
12
- """Return the Strava OAuth authorization URL. If client_id is not provided,
13
- read it from the STRAVA_CLIENT_ID environment variable."""
14
- if client_id is None:
15
- client_id_env = os.getenv("STRAVA_CLIENT_ID")
16
- if not client_id_env:
17
- return {"error": "STRAVA_CLIENT_ID environment variable is not set"}
18
- try:
19
- client_id = int(client_id_env)
20
- except ValueError:
21
- return {"error": "STRAVA_CLIENT_ID must be an integer"}
22
-
23
- params = {
24
- "client_id": client_id,
25
- "response_type": "code",
26
- "redirect_uri": "https://developers.strava.com/oauth2-redirect/",
27
- "approval_prompt": "force",
28
- "scope": "read,activity:read_all",
29
- }
30
- return "https://www.strava.com/oauth/authorize?" + urllib.parse.urlencode(params)
31
-
32
-
33
-
34
- @mcp.tool("strava://athlete/stats")
35
- def get_athlete_stats(
36
- code: str,
37
- client_id: int | None = None,
38
- client_secret: str | None = None,) -> dict:
39
-
40
- #'''Exchange an authorization code for access + refresh tokens.'''
41
- if not code:
42
- return {"error": "authorization code is required"}
43
-
44
- if client_id is None:
45
- client_id_env = os.getenv("STRAVA_CLIENT_ID")
46
- if not client_id_env:
47
- return {"error": "STRAVA_CLIENT_ID environment variable is not set"}
48
- try:
49
- client_id = int(client_id_env)
50
- except ValueError:
51
- return {"error": "STRAVA_CLIENT_ID must be an integer"}
52
-
53
- if client_secret is None:
54
- client_secret_env = os.getenv("STRAVA_CLIENT_SECRET")
55
- if not client_secret_env:
56
- return {"error": "STRAVA_CLIENT_SECRET environment variable is not set"}
57
- try:
58
- client_secret = str(client_secret_env)
59
- except ValueError:
60
- return {"error": "STRAVA_CLIENT_SECRET must be a string"}
61
-
62
-
63
- resp = requests.post(
64
- "https://www.strava.com/oauth/token",
65
- data={
66
- "client_id": client_id,
67
- "client_secret": client_secret,
68
- "code": code,
69
- "grant_type": "authorization_code",
70
- },
71
- )
72
- try:
73
- resp.raise_for_status()
74
- except requests.HTTPError:
75
- return {"error": "token request failed", "status_code": resp.status_code, "response": resp.text}
76
- except Exception as e:
77
- return {"error": "token request failed", "status_code": resp.status_code, "response": resp.text, "error": str(e)}
78
-
79
- tokens = resp.json()
80
- # Print tokens for debugging (optional)
81
- print(tokens)
82
-
83
- access_token = tokens.get("access_token")
84
- refresh_token = tokens.get("refresh_token")
85
-
86
-
87
- #return {"tokens": tokens, "access_token": access_token, "refresh_token": refresh_token}
88
-
89
- url = "https://www.strava.com/api/v3/athlete/activities?per_page=60"
90
- headers = {
91
- "accept": "application/json",
92
- "authorization": f"Bearer {access_token}"
93
- }
94
-
95
- response = requests.get(url, headers=headers)
96
-
97
- return response.json()
98
-
99
- if __name__ == "__main__":
100
- mcp.run(transport="stdio") # Run the server, using standard input/output for communication