spotify-analytics-mcp 0.1.0__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.
- spotify_analytics_mcp-0.1.0/.gitignore +224 -0
- spotify_analytics_mcp-0.1.0/LICENSE +21 -0
- spotify_analytics_mcp-0.1.0/PKG-INFO +29 -0
- spotify_analytics_mcp-0.1.0/README.md +3 -0
- spotify_analytics_mcp-0.1.0/SKILL.md +91 -0
- spotify_analytics_mcp-0.1.0/__init__.py +0 -0
- spotify_analytics_mcp-0.1.0/pyproject.toml +48 -0
- spotify_analytics_mcp-0.1.0/server.py +42 -0
- spotify_analytics_mcp-0.1.0/spotify_mcp/__init__.py +0 -0
- spotify_analytics_mcp-0.1.0/spotify_mcp/_mcp.py +83 -0
- spotify_analytics_mcp-0.1.0/spotify_mcp/cli.py +176 -0
- spotify_analytics_mcp-0.1.0/spotify_mcp/config.py +54 -0
- spotify_analytics_mcp-0.1.0/spotify_mcp/db_crud.py +360 -0
- spotify_analytics_mcp-0.1.0/spotify_mcp/memory_store.py +84 -0
- spotify_analytics_mcp-0.1.0/spotify_mcp/prompts.py +262 -0
- spotify_analytics_mcp-0.1.0/spotify_mcp/spotify_control.py +312 -0
- spotify_analytics_mcp-0.1.0/spotify_mcp/utils.py +84 -0
- spotify_analytics_mcp-0.1.0/spotify_mcp/wizard/__init__.py +58 -0
- spotify_analytics_mcp-0.1.0/spotify_mcp/wizard/claude_desktop.py +115 -0
- spotify_analytics_mcp-0.1.0/spotify_mcp/wizard/credentials.py +56 -0
- spotify_analytics_mcp-0.1.0/spotify_mcp/wizard/history_import.py +178 -0
- spotify_analytics_mcp-0.1.0/spotify_mcp/wizard/oauth_step.py +33 -0
- spotify_analytics_mcp-0.1.0/spotify_mcp/wizard/spotify_app.py +63 -0
- spotify_analytics_mcp-0.1.0/spotify_mcp/wizard/state.py +138 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
#uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
#poetry.lock
|
|
109
|
+
#poetry.toml
|
|
110
|
+
|
|
111
|
+
# pdm
|
|
112
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
113
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
114
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
115
|
+
#pdm.lock
|
|
116
|
+
#pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# pixi
|
|
121
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
122
|
+
#pixi.lock
|
|
123
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
124
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
125
|
+
.pixi
|
|
126
|
+
|
|
127
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
128
|
+
__pypackages__/
|
|
129
|
+
|
|
130
|
+
# Celery stuff
|
|
131
|
+
celerybeat-schedule
|
|
132
|
+
celerybeat.pid
|
|
133
|
+
|
|
134
|
+
# SageMath parsed files
|
|
135
|
+
*.sage.py
|
|
136
|
+
|
|
137
|
+
# Environments
|
|
138
|
+
.env
|
|
139
|
+
.envrc
|
|
140
|
+
.venv
|
|
141
|
+
env/
|
|
142
|
+
venv/
|
|
143
|
+
ENV/
|
|
144
|
+
env.bak/
|
|
145
|
+
venv.bak/
|
|
146
|
+
|
|
147
|
+
# Spyder project settings
|
|
148
|
+
.spyderproject
|
|
149
|
+
.spyproject
|
|
150
|
+
|
|
151
|
+
# Rope project settings
|
|
152
|
+
.ropeproject
|
|
153
|
+
|
|
154
|
+
# mkdocs documentation
|
|
155
|
+
/site
|
|
156
|
+
|
|
157
|
+
# mypy
|
|
158
|
+
.mypy_cache/
|
|
159
|
+
.dmypy.json
|
|
160
|
+
dmypy.json
|
|
161
|
+
|
|
162
|
+
# Pyre type checker
|
|
163
|
+
.pyre/
|
|
164
|
+
|
|
165
|
+
# pytype static type analyzer
|
|
166
|
+
.pytype/
|
|
167
|
+
|
|
168
|
+
# Cython debug symbols
|
|
169
|
+
cython_debug/
|
|
170
|
+
|
|
171
|
+
# PyCharm
|
|
172
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
173
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
174
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
175
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
176
|
+
#.idea/
|
|
177
|
+
|
|
178
|
+
# Abstra
|
|
179
|
+
# Abstra is an AI-powered process automation framework.
|
|
180
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
181
|
+
# Learn more at https://abstra.io/docs
|
|
182
|
+
.abstra/
|
|
183
|
+
|
|
184
|
+
# Visual Studio Code
|
|
185
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
186
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
187
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
188
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
189
|
+
# .vscode/
|
|
190
|
+
|
|
191
|
+
# Ruff stuff:
|
|
192
|
+
.ruff_cache/
|
|
193
|
+
|
|
194
|
+
# PyPI configuration file
|
|
195
|
+
.pypirc
|
|
196
|
+
|
|
197
|
+
# Cursor
|
|
198
|
+
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
|
199
|
+
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
|
200
|
+
# refer to https://docs.cursor.com/context/ignore-files
|
|
201
|
+
.cursorignore
|
|
202
|
+
.cursorindexingignore
|
|
203
|
+
|
|
204
|
+
# Marimo
|
|
205
|
+
marimo/_static/
|
|
206
|
+
marimo/_lsp/
|
|
207
|
+
__marimo__/
|
|
208
|
+
|
|
209
|
+
# Git worktrees
|
|
210
|
+
.worktrees/
|
|
211
|
+
.claude/worktrees/
|
|
212
|
+
|
|
213
|
+
# Project specific
|
|
214
|
+
# Vector database and user data
|
|
215
|
+
data/vectordb/
|
|
216
|
+
# sqlite databases and wal, shm files - ignore all but keep directory
|
|
217
|
+
data/*.db*
|
|
218
|
+
# Exclude user's actual Spotify data, but keep sample data
|
|
219
|
+
data/spotify_history/*
|
|
220
|
+
!data/spotify_history/sample_history.json
|
|
221
|
+
# logs
|
|
222
|
+
logs/
|
|
223
|
+
# Internal planning docs (superpowers/Claude Code session artifacts)
|
|
224
|
+
docs/superpowers/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 WC Chang
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spotify-analytics-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server and CLI installer for the Spotify AI Analytics project.
|
|
5
|
+
Project-URL: Homepage, https://github.com/wcnoname5/spotify-ai-analytics
|
|
6
|
+
Project-URL: Repository, https://github.com/wcnoname5/spotify-ai-analytics
|
|
7
|
+
Project-URL: Issues, https://github.com/wcnoname5/spotify-ai-analytics/issues
|
|
8
|
+
Author: WC Chang
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: analytics,claude,cli,listening-history,mcp,spotify
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Requires-Python: >=3.12
|
|
19
|
+
Requires-Dist: fastmcp>=2.0
|
|
20
|
+
Requires-Dist: platformdirs>=4.0
|
|
21
|
+
Requires-Dist: rich>=13.7
|
|
22
|
+
Requires-Dist: spotify-analytics-core>=0.1.0
|
|
23
|
+
Requires-Dist: spotify-analytics-dataloader>=0.1.0
|
|
24
|
+
Requires-Dist: typer>=0.12
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# spotify-analytics-mcp
|
|
28
|
+
|
|
29
|
+
MCP server and `spotify-mcp` setup CLI for Spotify AI Analytics.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Spotify AI Analytics — Claude Skill
|
|
2
|
+
|
|
3
|
+
> Add the contents of this file to your project's CLAUDE.md (or paste it into Claude Desktop's system prompt field) so Claude knows how to use this MCP server effectively.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## What this MCP server provides
|
|
8
|
+
|
|
9
|
+
You are connected to a local Spotify AI Analytics MCP server with these capabilities:
|
|
10
|
+
- **History analytics** — query local SQLite DB of listening history (no auth needed)
|
|
11
|
+
- **Playback control** — control Spotify (requires auth + Spotify Premium)
|
|
12
|
+
- **Long-term memory** — store and retrieve user preferences across conversations
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## First interaction with a new user
|
|
17
|
+
|
|
18
|
+
When the user first mentions Spotify or seems to be setting up the server:
|
|
19
|
+
|
|
20
|
+
1. Call `setup_check()` immediately.
|
|
21
|
+
2. Read the `actions_needed` list. Walk the user through each item in order — do not skip ahead.
|
|
22
|
+
3. After the user completes an action, call `setup_check()` again to confirm before moving on.
|
|
23
|
+
4. Once `ready: true`, confirm the server is fully configured and offer to show their listening stats.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## When analytics tools return a warning (empty DB)
|
|
28
|
+
|
|
29
|
+
If `get_listening_summary`, `get_top_artists`, or `get_top_tracks` return a dict with a `warning` field:
|
|
30
|
+
|
|
31
|
+
1. Tell the user: "Your local listening history database is empty."
|
|
32
|
+
2. Ask: "Have you already downloaded your Spotify data from https://www.spotify.com/account/privacy/?"
|
|
33
|
+
- **If yes:** Guide them to run `uv run python scripts/import_json.py --dir data/spotify_history`
|
|
34
|
+
- **If no:** Explain that requesting the JSON export takes a few days. In the meantime, they can sync recent plays: `uv run python scripts/sync_api.py --user-id <their_user_id>` (last 50 tracks only).
|
|
35
|
+
3. After they run one of these, call the analytics tool again to confirm data loaded.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## When a tool returns `requires_auth: true`
|
|
40
|
+
|
|
41
|
+
The user's Spotify OAuth tokens are missing or expired. Guide them to:
|
|
42
|
+
```bash
|
|
43
|
+
uv run python scripts/init_db.py --auth --user-id <their_user_id>
|
|
44
|
+
```
|
|
45
|
+
This opens a browser tab. They approve access, browser shows "Authentication complete", done. Tokens are saved and auto-refresh going forward.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Tool selection guide
|
|
50
|
+
|
|
51
|
+
| User says… | Use this tool |
|
|
52
|
+
|------------|---------------|
|
|
53
|
+
| "What's playing?" / "Now playing?" | `get_now_playing` |
|
|
54
|
+
| "Play [track]" / "Resume" | `play_track` (need URI first — ask or search) |
|
|
55
|
+
| "Pause" / "Stop" | `pause_playback` |
|
|
56
|
+
| "Skip" / "Next song" | `skip_track` |
|
|
57
|
+
| "Volume up/down" / "Set volume to X" | `set_volume` |
|
|
58
|
+
| "Add to queue" | `add_to_queue` |
|
|
59
|
+
| "Top artists" / "Most played artists" | `get_top_artists` |
|
|
60
|
+
| "Top tracks" / "Most played songs" | `get_top_tracks` |
|
|
61
|
+
| "How much have I listened?" / "Overview" | `get_listening_summary` |
|
|
62
|
+
| "Sync my plays" / "Update history" | `sync_history` |
|
|
63
|
+
| "Remember that I like X" / "Note that…" | `remember_preference` |
|
|
64
|
+
| "What do you know about my taste?" | `get_memory_summary` |
|
|
65
|
+
| "Create a playlist" | `create_playlist` (generate URIs from analytics first) |
|
|
66
|
+
| "Is everything set up?" / "Something isn't working" | `setup_check` |
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Date range filtering
|
|
71
|
+
|
|
72
|
+
`get_top_artists` and `get_top_tracks` accept `start_date` and `end_date` in `"YYYY-MM-DD"` format.
|
|
73
|
+
|
|
74
|
+
Examples:
|
|
75
|
+
- "Last year" → `start_date="2024-01-01"`, `end_date="2024-12-31"`
|
|
76
|
+
- "This month" → compute from today's date
|
|
77
|
+
- "2023" → `start_date="2023-01-01"`, `end_date="2023-12-31"`
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Timestamps
|
|
82
|
+
|
|
83
|
+
`get_listening_summary` returns `earliest_played_at` and `latest_played_at` in the user's **local timezone** ISO format. Display them as-is — no further conversion needed.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Playback tool limitations
|
|
88
|
+
|
|
89
|
+
- All playback tools (`play_track`, `pause_playback`, `skip_track`, `set_volume`, `add_to_queue`) require **Spotify Premium**.
|
|
90
|
+
- If they return `{"error": "Spotify Premium required for playback control."}`, tell the user Premium is required — do not retry.
|
|
91
|
+
- Track URIs follow the format `spotify:track:<22-char-id>`. You can get them from the analytics tools' `track_id` field or by asking the user to copy from Spotify.
|
|
File without changes
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "spotify-analytics-mcp"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "MCP server and CLI installer for the Spotify AI Analytics project."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = { text = "MIT" }
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
authors = [{ name = "WC Chang" }]
|
|
9
|
+
requires-python = ">=3.12"
|
|
10
|
+
keywords = ["spotify", "mcp", "claude", "analytics", "cli", "listening-history"]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Programming Language :: Python :: 3",
|
|
13
|
+
"Programming Language :: Python :: 3.12",
|
|
14
|
+
"Programming Language :: Python :: 3.13",
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
]
|
|
19
|
+
dependencies = [
|
|
20
|
+
"fastmcp>=2.0",
|
|
21
|
+
"spotify-analytics-core>=0.1.0",
|
|
22
|
+
"spotify-analytics-dataloader>=0.1.0",
|
|
23
|
+
"typer>=0.12",
|
|
24
|
+
"rich>=13.7",
|
|
25
|
+
"platformdirs>=4.0",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.scripts]
|
|
29
|
+
spotify-mcp = "spotify_mcp.cli:app"
|
|
30
|
+
|
|
31
|
+
[project.entry-points."pipx.run"]
|
|
32
|
+
spotify-analytics-mcp = "spotify_mcp.cli:app"
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://github.com/wcnoname5/spotify-ai-analytics"
|
|
36
|
+
Repository = "https://github.com/wcnoname5/spotify-ai-analytics"
|
|
37
|
+
Issues = "https://github.com/wcnoname5/spotify-ai-analytics/issues"
|
|
38
|
+
|
|
39
|
+
[tool.uv.sources]
|
|
40
|
+
spotify-analytics-core = { workspace = true }
|
|
41
|
+
spotify-analytics-dataloader = { workspace = true }
|
|
42
|
+
|
|
43
|
+
[build-system]
|
|
44
|
+
requires = ["hatchling"]
|
|
45
|
+
build-backend = "hatchling.build"
|
|
46
|
+
|
|
47
|
+
[tool.hatch.build.targets.wheel]
|
|
48
|
+
packages = ["spotify_mcp"]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Spotify AI Analytics MCP Server — development entry point.
|
|
2
|
+
|
|
3
|
+
For PyPI-installed users the server is launched via `spotify-mcp serve`.
|
|
4
|
+
This file exists as a convenience for checkout-mode development:
|
|
5
|
+
|
|
6
|
+
uv run python apps/mcp/server.py
|
|
7
|
+
|
|
8
|
+
Required environment variables:
|
|
9
|
+
SPOTIFY_CLIENT_ID — Spotify app client ID
|
|
10
|
+
TOKEN_ENCRYPT_KEY — Fernet key bytes (base64-encoded) for token encryption
|
|
11
|
+
"""
|
|
12
|
+
import logging
|
|
13
|
+
import os
|
|
14
|
+
|
|
15
|
+
from dotenv import load_dotenv
|
|
16
|
+
|
|
17
|
+
from spotify_core import paths
|
|
18
|
+
from spotify_core.logging import setup_mcp_logging
|
|
19
|
+
from spotify_mcp.config import get_client_id, get_fernet_key
|
|
20
|
+
|
|
21
|
+
# Load platformdirs .env first; cwd .env fills any gaps but never overrides.
|
|
22
|
+
if paths.env_file().exists():
|
|
23
|
+
load_dotenv(paths.env_file())
|
|
24
|
+
load_dotenv(override=False)
|
|
25
|
+
|
|
26
|
+
_raw_level = os.getenv("LOG_LEVEL", "DEBUG").upper()
|
|
27
|
+
_log_file = setup_mcp_logging(level=logging.getLevelNamesMapping().get(_raw_level, logging.DEBUG))
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
logger.info("Logging to %s", _log_file)
|
|
30
|
+
|
|
31
|
+
if not get_client_id():
|
|
32
|
+
logger.warning("SPOTIFY_CLIENT_ID is not set. Set it in .env or as an environment variable.")
|
|
33
|
+
if not get_fernet_key():
|
|
34
|
+
logger.warning(
|
|
35
|
+
"TOKEN_ENCRYPT_KEY is not set. "
|
|
36
|
+
'Generate one with: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"'
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
from spotify_mcp._mcp import main # noqa: E402 — must come after env/logging setup
|
|
40
|
+
|
|
41
|
+
if __name__ == "__main__":
|
|
42
|
+
main()
|
|
File without changes
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""FastMCP application — mcp instance, tool registrations, and main().
|
|
2
|
+
|
|
3
|
+
This module owns the FastMCP app object so it can be imported by both the
|
|
4
|
+
development entry point (apps/mcp/server.py) and the `spotify-mcp serve`
|
|
5
|
+
CLI command without duplicating server logic.
|
|
6
|
+
"""
|
|
7
|
+
import logging
|
|
8
|
+
import sys
|
|
9
|
+
from contextlib import asynccontextmanager
|
|
10
|
+
|
|
11
|
+
from fastmcp import FastMCP
|
|
12
|
+
|
|
13
|
+
from spotify_mcp import db_crud, spotify_control
|
|
14
|
+
from spotify_mcp.config import get_client_id, get_fernet_key
|
|
15
|
+
from spotify_mcp.prompts import register_prompts
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@asynccontextmanager
|
|
21
|
+
async def lifespan(server: FastMCP):
|
|
22
|
+
from spotify_mcp.wizard.state import collect_report
|
|
23
|
+
|
|
24
|
+
report = collect_report()
|
|
25
|
+
blocking = [a for a in report["actions_needed"] if "Load history" not in a]
|
|
26
|
+
if blocking:
|
|
27
|
+
msg = "spotify-mcp not configured. Run: spotify-mcp setup"
|
|
28
|
+
|
|
29
|
+
def _ascii_safe(s: str) -> str:
|
|
30
|
+
# Replace Unicode dashes with ASCII hyphen so Windows consoles
|
|
31
|
+
# (cp1252) don't produce un-decodable bytes in the stderr stream.
|
|
32
|
+
return s.replace("—", "-").replace("–", "-")
|
|
33
|
+
|
|
34
|
+
print(_ascii_safe(msg), file=sys.stderr, flush=True)
|
|
35
|
+
for action in blocking:
|
|
36
|
+
print(_ascii_safe(f" - {action}"), file=sys.stderr, flush=True)
|
|
37
|
+
logger.error("%s\n%s", msg, "\n".join(f" - {action}" for action in blocking))
|
|
38
|
+
raise SystemExit(1)
|
|
39
|
+
|
|
40
|
+
logger.info("MCP server ready: all checks passed.")
|
|
41
|
+
yield
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
mcp = FastMCP("spotify_mcp", lifespan=lifespan)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@mcp.tool(
|
|
48
|
+
name="setup_check",
|
|
49
|
+
annotations={
|
|
50
|
+
"title": "Check Server Setup",
|
|
51
|
+
"readOnlyHint": True,
|
|
52
|
+
"destructiveHint": False,
|
|
53
|
+
"idempotentHint": True,
|
|
54
|
+
"openWorldHint": False,
|
|
55
|
+
},
|
|
56
|
+
)
|
|
57
|
+
def setup_check() -> dict:
|
|
58
|
+
"""Diagnose the MCP server configuration. Call this first if something isn't working.
|
|
59
|
+
|
|
60
|
+
Returns a structured report of what is configured and what actions are still needed,
|
|
61
|
+
in the order they must be completed.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
{
|
|
65
|
+
"ready": bool,
|
|
66
|
+
"checks": dict[str, bool],
|
|
67
|
+
"actions_needed": list[str],
|
|
68
|
+
"message": str,
|
|
69
|
+
}
|
|
70
|
+
"""
|
|
71
|
+
logger.debug("[Tool] setup_check: running diagnostics on server configuration.")
|
|
72
|
+
from spotify_mcp.wizard.state import collect_report
|
|
73
|
+
|
|
74
|
+
return collect_report()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
register_prompts(mcp)
|
|
78
|
+
db_crud.register(mcp)
|
|
79
|
+
spotify_control.register(mcp)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def main() -> None:
|
|
83
|
+
mcp.run(transport="stdio")
|