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.
- {strava_activity_mcp_server-0.1.9 → strava_activity_mcp_server-0.2.1}/.python-version +1 -1
- strava_activity_mcp_server-0.2.1/.vscode/settings.json +15 -0
- {strava_activity_mcp_server-0.1.9 → strava_activity_mcp_server-0.2.1}/PKG-INFO +4 -3
- {strava_activity_mcp_server-0.1.9 → strava_activity_mcp_server-0.2.1}/README.md +3 -2
- {strava_activity_mcp_server-0.1.9 → strava_activity_mcp_server-0.2.1}/pyproject.toml +21 -18
- {strava_activity_mcp_server-0.1.9 → strava_activity_mcp_server-0.2.1}/src/strava_activity_mcp_server/__init__.py +3 -3
- strava_activity_mcp_server-0.2.1/src/strava_activity_mcp_server/strava_activity_mcp_server.py +176 -0
- {strava_activity_mcp_server-0.1.9 → strava_activity_mcp_server-0.2.1}/uv.lock +1 -1
- strava_activity_mcp_server-0.1.9/.vscode/settings.json +0 -6
- strava_activity_mcp_server-0.1.9/src/strava_activity_mcp_server/strava_activity_mcp_server.py +0 -100
- {strava_activity_mcp_server-0.1.9 → strava_activity_mcp_server-0.2.1}/.github/workflows/python-publish.yml +0 -0
- {strava_activity_mcp_server-0.1.9 → strava_activity_mcp_server-0.2.1}/.gitignore +0 -0
- {strava_activity_mcp_server-0.1.9 → strava_activity_mcp_server-0.2.1}/LICENSE +0 -0
- {strava_activity_mcp_server-0.1.9 → strava_activity_mcp_server-0.2.1}/ref/auth.jpg +0 -0
- {strava_activity_mcp_server-0.1.9 → strava_activity_mcp_server-0.2.1}/ref/code.jpg +0 -0
- {strava_activity_mcp_server-0.1.9 → strava_activity_mcp_server-0.2.1}/ref/image.jpg +0 -0
- {strava_activity_mcp_server-0.1.9 → strava_activity_mcp_server-0.2.1}/ref/mcp_pypi_example.md +0 -0
- {strava_activity_mcp_server-0.1.9 → strava_activity_mcp_server-0.2.1}/requirements.txt +0 -0
- {strava_activity_mcp_server-0.1.9 → strava_activity_mcp_server-0.2.1}/src/strava_activity_mcp_server/__main__.py +0 -0
@@ -1 +1 @@
|
|
1
|
-
3.13
|
1
|
+
3.13
|
@@ -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
|
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
|

|
14
14
|
[](https://opensource.org/licenses/gpl-3-0)
|
15
15
|
[](https://www.python.org/downloads/release/python-3130/)
|
16
|
+
[](https://pypistats.org/packages/strava-activity-mcp-server)
|
16
17
|
|
17
18
|

|
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
|

|
3
3
|
[](https://opensource.org/licenses/gpl-3-0)
|
4
4
|
[](https://www.python.org/downloads/release/python-3130/)
|
5
|
+
[](https://pypistats.org/packages/strava-activity-mcp-server)
|
5
6
|
|
6
7
|

|
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
|
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
|
strava_activity_mcp_server-0.1.9/src/strava_activity_mcp_server/strava_activity_mcp_server.py
DELETED
@@ -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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{strava_activity_mcp_server-0.1.9 → strava_activity_mcp_server-0.2.1}/ref/mcp_pypi_example.md
RENAMED
File without changes
|
File without changes
|
File without changes
|