spotify-mcp-jamiew 0.3.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.
Files changed (33) hide show
  1. spotify_mcp_jamiew-0.3.0/.dockerignore +57 -0
  2. spotify_mcp_jamiew-0.3.0/.github/workflows/ci.yml +158 -0
  3. spotify_mcp_jamiew-0.3.0/.github/workflows/claude-code-review.yml +57 -0
  4. spotify_mcp_jamiew-0.3.0/.github/workflows/claude.yml +50 -0
  5. spotify_mcp_jamiew-0.3.0/.github/workflows/deploy-pages.yml +40 -0
  6. spotify_mcp_jamiew-0.3.0/.github/workflows/publish.yml +52 -0
  7. spotify_mcp_jamiew-0.3.0/.gitignore +6 -0
  8. spotify_mcp_jamiew-0.3.0/.python-version +1 -0
  9. spotify_mcp_jamiew-0.3.0/CHANGELOG.md +58 -0
  10. spotify_mcp_jamiew-0.3.0/CLAUDE.md +89 -0
  11. spotify_mcp_jamiew-0.3.0/Dockerfile +39 -0
  12. spotify_mcp_jamiew-0.3.0/LICENSE +21 -0
  13. spotify_mcp_jamiew-0.3.0/PKG-INFO +163 -0
  14. spotify_mcp_jamiew-0.3.0/README.md +147 -0
  15. spotify_mcp_jamiew-0.3.0/docs/index.html +483 -0
  16. spotify_mcp_jamiew-0.3.0/media/take5.mp4 +0 -0
  17. spotify_mcp_jamiew-0.3.0/pyproject.toml +137 -0
  18. spotify_mcp_jamiew-0.3.0/release.sh +92 -0
  19. spotify_mcp_jamiew-0.3.0/server.json +43 -0
  20. spotify_mcp_jamiew-0.3.0/src/spotify_mcp/__init__.py +23 -0
  21. spotify_mcp_jamiew-0.3.0/src/spotify_mcp/errors.py +188 -0
  22. spotify_mcp_jamiew-0.3.0/src/spotify_mcp/fastmcp_server.py +1303 -0
  23. spotify_mcp_jamiew-0.3.0/src/spotify_mcp/logging_utils.py +111 -0
  24. spotify_mcp_jamiew-0.3.0/src/spotify_mcp/spotify_api.py +124 -0
  25. spotify_mcp_jamiew-0.3.0/src/spotify_mcp/spotify_types.py +107 -0
  26. spotify_mcp_jamiew-0.3.0/src/spotify_mcp/utils.py +23 -0
  27. spotify_mcp_jamiew-0.3.0/tests/conftest.py +176 -0
  28. spotify_mcp_jamiew-0.3.0/tests/test_errors.py +185 -0
  29. spotify_mcp_jamiew-0.3.0/tests/test_fastmcp_tools.py +788 -0
  30. spotify_mcp_jamiew-0.3.0/tests/test_packaging.py +80 -0
  31. spotify_mcp_jamiew-0.3.0/tests/test_spotify_api.py +97 -0
  32. spotify_mcp_jamiew-0.3.0/tests/test_utils.py +35 -0
  33. spotify_mcp_jamiew-0.3.0/uv.lock +1258 -0
@@ -0,0 +1,57 @@
1
+ # Git and version control
2
+ .git/
3
+ .gitignore
4
+ .gitattributes
5
+
6
+ # System files
7
+ /proc/
8
+ /sys/
9
+ /dev/
10
+
11
+ # Python
12
+ __pycache__/
13
+ *.py[cod]
14
+ *$py.class
15
+ *.so
16
+ .Python
17
+ env/
18
+ venv/
19
+ .venv/
20
+ .env
21
+ .env.*
22
+
23
+ # Build artifacts
24
+ build/
25
+ dist/
26
+ *.egg-info/
27
+ .eggs/
28
+
29
+ # Testing and development
30
+ .pytest_cache/
31
+ .coverage
32
+ .mypy_cache/
33
+ .ruff_cache/
34
+ htmlcov/
35
+ *.log
36
+
37
+ # IDE and editors
38
+ .vscode/
39
+ .idea/
40
+ *.swp
41
+ *.swo
42
+ *~
43
+ .DS_Store
44
+
45
+ # Documentation
46
+ docs/
47
+ *.md
48
+ !README.md
49
+
50
+ # CI/CD
51
+ .github/
52
+ .gitlab-ci.yml
53
+
54
+ # Other
55
+ *.tmp
56
+ *.bak
57
+ node_modules/
@@ -0,0 +1,158 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ main, develop ]
6
+ pull_request:
7
+ branches: [ main, develop ]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ needs: [lint] # Don't test broken code
13
+ strategy:
14
+ matrix:
15
+ python-version: ["3.12", "3.13"]
16
+
17
+ steps:
18
+ - name: Checkout code
19
+ uses: actions/checkout@v4
20
+
21
+ - name: Set up Python ${{ matrix.python-version }}
22
+ uses: actions/setup-python@v5
23
+ with:
24
+ python-version: ${{ matrix.python-version }}
25
+
26
+ - name: Install uv
27
+ uses: astral-sh/setup-uv@v4
28
+ with:
29
+ # Install a specific uv version
30
+ version: "latest"
31
+ # Install uv globally
32
+ enable-cache: true
33
+
34
+ - name: Set up project
35
+ run: uv sync --dev
36
+
37
+ - name: Run type checking
38
+ run: uv run mypy src/
39
+
40
+ - name: Run tests
41
+ run: uv run pytest
42
+
43
+ lint:
44
+ runs-on: ubuntu-latest
45
+ steps:
46
+ - name: Checkout code
47
+ uses: actions/checkout@v4
48
+
49
+ - name: Set up Python
50
+ uses: actions/setup-python@v5
51
+ with:
52
+ python-version: "3.12"
53
+
54
+ - name: Install uv
55
+ uses: astral-sh/setup-uv@v4
56
+ with:
57
+ version: "latest"
58
+ enable-cache: true
59
+
60
+ - name: Set up project
61
+ run: uv sync --dev
62
+
63
+ - name: Run ruff linter
64
+ run: uv run ruff check src/ tests/
65
+
66
+ - name: Run ruff formatter check
67
+ run: uv run ruff format --check src/ tests/
68
+
69
+ security:
70
+ runs-on: ubuntu-latest
71
+ needs: [lint] # Don't scan broken code
72
+ steps:
73
+ - name: Checkout code
74
+ uses: actions/checkout@v4
75
+
76
+ - name: Set up Python
77
+ uses: actions/setup-python@v5
78
+ with:
79
+ python-version: "3.12"
80
+
81
+ - name: Install uv
82
+ uses: astral-sh/setup-uv@v4
83
+ with:
84
+ version: "latest"
85
+ enable-cache: true
86
+
87
+ - name: Set up project
88
+ run: uv sync --dev
89
+
90
+ - name: Run bandit security linter
91
+ run: uv run bandit -r src/
92
+
93
+ build:
94
+ runs-on: ubuntu-latest
95
+ needs: [test, lint, security] # Require all quality gates
96
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
97
+
98
+ steps:
99
+ - name: Checkout code
100
+ uses: actions/checkout@v4
101
+
102
+ - name: Set up Python
103
+ uses: actions/setup-python@v5
104
+ with:
105
+ python-version: "3.12"
106
+
107
+ - name: Install uv
108
+ uses: astral-sh/setup-uv@v4
109
+ with:
110
+ version: "latest"
111
+ enable-cache: true
112
+
113
+ - name: Set up project
114
+ run: uv sync --dev
115
+
116
+ - name: Build package
117
+ run: uv build
118
+
119
+ - name: Store build artifacts
120
+ uses: actions/upload-artifact@v4
121
+ with:
122
+ name: dist
123
+ path: dist/
124
+
125
+ docker:
126
+ runs-on: ubuntu-latest
127
+ needs: [test, lint, security]
128
+ steps:
129
+ - name: Checkout code
130
+ uses: actions/checkout@v4
131
+
132
+ - name: Set up Docker Buildx
133
+ uses: docker/setup-buildx-action@v3
134
+
135
+ - name: Build Docker image
136
+ uses: docker/build-push-action@v6
137
+ with:
138
+ context: .
139
+ file: ./Dockerfile
140
+ push: false
141
+ load: true # Load image into Docker daemon for testing
142
+ tags: spotify-mcp:test
143
+ cache-from: type=gha
144
+ cache-to: type=gha,mode=max
145
+
146
+ - name: Test Docker image
147
+ run: |
148
+ # Test that image was built and can run basic commands
149
+ docker run --rm spotify-mcp:test python --version
150
+
151
+ # Test that entry point is installed and accessible
152
+ docker run --rm spotify-mcp:test which spotify-mcp
153
+
154
+ # Test that application starts without errors (with proper env vars)
155
+ docker run --rm -e SPOTIFY_CLIENT_ID=test -e SPOTIFY_CLIENT_SECRET=test -e SPOTIFY_REDIRECT_URI=http://localhost:8888 spotify-mcp:test python -c "print('✅ Docker image works correctly')"
156
+
157
+ # Test security: ensure running as non-root user
158
+ docker run --rm spotify-mcp:test sh -c 'whoami | grep -v root'
@@ -0,0 +1,57 @@
1
+ name: Claude Code Review
2
+
3
+ on:
4
+ pull_request:
5
+ types: [opened, synchronize]
6
+ # Optional: Only run on specific file changes
7
+ # paths:
8
+ # - "src/**/*.ts"
9
+ # - "src/**/*.tsx"
10
+ # - "src/**/*.js"
11
+ # - "src/**/*.jsx"
12
+
13
+ jobs:
14
+ claude-review:
15
+ # Optional: Filter by PR author
16
+ # if: |
17
+ # github.event.pull_request.user.login == 'external-contributor' ||
18
+ # github.event.pull_request.user.login == 'new-developer' ||
19
+ # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
20
+
21
+ runs-on: ubuntu-latest
22
+ permissions:
23
+ contents: read
24
+ pull-requests: read
25
+ issues: read
26
+ id-token: write
27
+
28
+ steps:
29
+ - name: Checkout repository
30
+ uses: actions/checkout@v4
31
+ with:
32
+ fetch-depth: 1
33
+
34
+ - name: Run Claude Code Review
35
+ id: claude-review
36
+ uses: anthropics/claude-code-action@v1
37
+ with:
38
+ claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
39
+ prompt: |
40
+ REPO: ${{ github.repository }}
41
+ PR NUMBER: ${{ github.event.pull_request.number }}
42
+
43
+ Please review this pull request and provide feedback on:
44
+ - Code quality and best practices
45
+ - Potential bugs or issues
46
+ - Performance considerations
47
+ - Security concerns
48
+ - Test coverage
49
+
50
+ Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback.
51
+
52
+ Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR.
53
+
54
+ # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
55
+ # or https://docs.claude.com/en/docs/claude-code/cli-reference for available options
56
+ claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"'
57
+
@@ -0,0 +1,50 @@
1
+ name: Claude Code
2
+
3
+ on:
4
+ issue_comment:
5
+ types: [created]
6
+ pull_request_review_comment:
7
+ types: [created]
8
+ issues:
9
+ types: [opened, assigned]
10
+ pull_request_review:
11
+ types: [submitted]
12
+
13
+ jobs:
14
+ claude:
15
+ if: |
16
+ (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
17
+ (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
18
+ (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
19
+ (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
20
+ runs-on: ubuntu-latest
21
+ permissions:
22
+ contents: read
23
+ pull-requests: read
24
+ issues: read
25
+ id-token: write
26
+ actions: read # Required for Claude to read CI results on PRs
27
+ steps:
28
+ - name: Checkout repository
29
+ uses: actions/checkout@v4
30
+ with:
31
+ fetch-depth: 1
32
+
33
+ - name: Run Claude Code
34
+ id: claude
35
+ uses: anthropics/claude-code-action@v1
36
+ with:
37
+ claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
38
+
39
+ # This is an optional setting that allows Claude to read CI results on PRs
40
+ additional_permissions: |
41
+ actions: read
42
+
43
+ # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
44
+ # prompt: 'Update the pull request description to include a summary of changes.'
45
+
46
+ # Optional: Add claude_args to customize behavior and configuration
47
+ # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
48
+ # or https://docs.claude.com/en/docs/claude-code/cli-reference for available options
49
+ # claude_args: '--allowed-tools Bash(gh pr:*)'
50
+
@@ -0,0 +1,40 @@
1
+ name: Deploy GitHub Pages
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ workflow_dispatch:
7
+
8
+ # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
9
+ permissions:
10
+ contents: read
11
+ pages: write
12
+ id-token: write
13
+
14
+ # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
15
+ # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
16
+ concurrency:
17
+ group: "pages"
18
+ cancel-in-progress: false
19
+
20
+ jobs:
21
+ deploy:
22
+ environment:
23
+ name: github-pages
24
+ url: ${{ steps.deployment.outputs.page_url }}
25
+ runs-on: ubuntu-latest
26
+ steps:
27
+ - name: Checkout
28
+ uses: actions/checkout@v4
29
+
30
+ - name: Setup Pages
31
+ uses: actions/configure-pages@v5
32
+
33
+ - name: Upload artifact
34
+ uses: actions/upload-pages-artifact@v3
35
+ with:
36
+ path: './docs'
37
+
38
+ - name: Deploy to GitHub Pages
39
+ id: deployment
40
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,52 @@
1
+ name: Publish to PyPI & MCP Registry
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ id-token: write
10
+ contents: read
11
+
12
+ jobs:
13
+ publish:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - name: Install uv
19
+ uses: astral-sh/setup-uv@v5
20
+
21
+ - name: Set up Python
22
+ run: uv python install 3.12
23
+
24
+ - name: Install dependencies
25
+ run: uv sync --all-groups
26
+
27
+ - name: Run type checking
28
+ run: uv run mypy src/
29
+
30
+ - name: Run tests
31
+ run: uv run pytest
32
+
33
+ - name: Build package
34
+ run: uv build
35
+
36
+ - name: Publish to PyPI
37
+ uses: pypa/gh-action-pypi-publish@release/v1
38
+
39
+ - name: Install mcp-publisher
40
+ run: |
41
+ curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_linux_amd64.tar.gz" | tar xz mcp-publisher
42
+
43
+ - name: Authenticate to MCP Registry
44
+ run: ./mcp-publisher login github-oidc
45
+
46
+ - name: Set version in server.json
47
+ run: |
48
+ VERSION=${GITHUB_REF#refs/tags/v}
49
+ jq --arg v "$VERSION" '.version = $v | .packages[0].version = $v' server.json > server.tmp && mv server.tmp server.json
50
+
51
+ - name: Publish to MCP Registry
52
+ run: ./mcp-publisher publish
@@ -0,0 +1,6 @@
1
+ .env*
2
+ .cache
3
+ .coverage
4
+ .DS_Store
5
+ **/__pycache__
6
+ dist/
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,58 @@
1
+ # Changelog
2
+
3
+ ## 2026-05-29
4
+
5
+ ### Packaging & distribution
6
+ - published to PyPI as `spotify-mcp-jamiew` and to the MCP Registry as
7
+ `io.github.jamiew/spotify-mcp` (`spotify-mcp` is taken by upstream)
8
+ - one-command install via `uvx spotify-mcp-jamiew`; README now has copy-paste
9
+ setup for Claude Code, Claude Desktop, and Codex CLI
10
+ - automated release: a GitHub Actions workflow publishes to PyPI + the MCP
11
+ Registry on GitHub release via OIDC trusted publishing (no stored tokens)
12
+ - added packaging tests that keep `server.json` in sync with `pyproject.toml`
13
+ and the env vars the server reads
14
+
15
+ ## 2026-05-28
16
+
17
+ ### Modern MCP protocol features
18
+ - structured output: every tool returns a typed Pydantic model (real output
19
+ schemas) instead of bare dicts
20
+ - tool annotations (readOnly/destructive/idempotent/openWorld hints) + titles,
21
+ plus a Spotify icon on every tool, resource, and prompt
22
+ - progress + log notifications while paginating large playlists
23
+ - elicitation: `remove_tracks_from_playlist` confirms before deleting on clients
24
+ that support it, and proceeds without prompting on clients that don't
25
+ - new resources: track / playlist / artist / album by id
26
+
27
+ ### Fixes
28
+ - playback now reads from `current_playback()`, so device/volume/shuffle/repeat
29
+ are populated instead of always null
30
+ - a destructive removal no longer slips through when an elicitation prompt errors
31
+ on a capable client — only genuinely unsupported clients skip confirmation
32
+ - playlist add/remove now surface the returned `snapshot_id`
33
+ - search tolerates null entries in result items
34
+ - dropped unused error codes left over from the deleted error helpers
35
+
36
+ ### Dependencies updated to current majors
37
+ - bumped the runtime + dev stack to latest: mcp 1.27, spotipy 2.26, pytest 9,
38
+ mypy 2.0, ruff 0.15, pytest-cov 7 (pydantic 2.13 pulled in transitively)
39
+
40
+ ### Audio-features and recommendations tools removed
41
+ - dropped both tools — Spotify deprecated those endpoints in nov 2024 and they
42
+ return 403 for apps created after that (tool count 13 → 11)
43
+
44
+ ### Dead code removed
45
+ - removed the unused Client wrapper, utils parsers, and error/logging helpers
46
+ left over from the FastMCP rewrite (~1400 lines)
47
+
48
+ ### Test coverage raised to 95%
49
+ - every tool now has a success and a failure test; the resources and prompts
50
+ are covered too (was 55%)
51
+
52
+ ### Typed Spotify response shapes
53
+ - added TypedDicts for the Spotify objects the server consumes, applied at the
54
+ parse and model-building boundaries
55
+
56
+ ## 2025-12-08 — 0.2.0
57
+ - batch support for tracks/audio features plus new tools
58
+ - added the release.sh helper script
@@ -0,0 +1,89 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides essential guidance for working with the Spotify MCP server codebase.
4
+
5
+ ## Essential Commands
6
+
7
+ ### Development
8
+ - `uv run spotify-mcp` - Start the MCP server (local alias; the package publishes as `spotify-mcp-jamiew`, so end users run `uvx spotify-mcp-jamiew`)
9
+ - `uv sync` - Sync dependencies
10
+ - `uv run pytest` - Run all tests (must pass before commits)
11
+ - `uv run mypy src/` - Type checking (must pass before commits)
12
+ - Optional speedup on mypy 2.x: `uv run mypy src/ --num-workers 4` (parallel checking)
13
+
14
+ ### Environment Setup
15
+ Required environment variables:
16
+ - `SPOTIFY_CLIENT_ID` - Spotify API Client ID
17
+ - `SPOTIFY_CLIENT_SECRET` - Spotify API Client Secret
18
+
19
+ Three-tier configuration (highest priority first):
20
+ 1. Environment variables (for production/MCP usage)
21
+ 2. `.env` file (for local development)
22
+ 3. `pyproject.toml` defaults (fallback - edit `[tool.spotify-mcp.env]` section)
23
+
24
+ ### Git Workflow
25
+ **Quality Gates**: Before any commit, ALWAYS run:
26
+ - `uv run mypy src/` - Type checking must pass
27
+ - `uv run pytest` - All tests must pass
28
+
29
+ **Commit Message Format:**
30
+ ```
31
+ Brief description of change
32
+
33
+ Detailed explanation of what and why.
34
+
35
+ 🤖 Generated with [Claude Code](https://claude.ai/code)
36
+
37
+ Co-Authored-By: Claude <noreply@anthropic.com>
38
+ ```
39
+
40
+ ### Releasing
41
+ Publishing is fully automated via OIDC trusted publishing — no tokens stored anywhere.
42
+
43
+ 1. Bump `version` in `pyproject.toml`, commit, and create a GitHub release for tag `vX.Y.Z`
44
+ (the `/release` skill or `release.sh` does the tag + `gh release create`).
45
+ 2. The `release: published` event triggers `.github/workflows/publish.yml`, which tests → builds →
46
+ publishes to **PyPI** (`pypa/gh-action-pypi-publish`, OIDC) → publishes to the **MCP Registry**
47
+ (`mcp-publisher login github-oidc`), injecting the tag version into `server.json` at publish time.
48
+
49
+ The package publishes to PyPI as `spotify-mcp-jamiew` and to the registry as `io.github.jamiew/spotify-mcp`.
50
+
51
+ One-time setup (already required before the first successful run):
52
+ - PyPI: register a trusted publisher for project `spotify-mcp-jamiew` → owner `jamiew`, repo
53
+ `spotify-mcp`, workflow `publish.yml`.
54
+ - MCP Registry: the `io.github.jamiew/*` namespace is authenticated automatically via GitHub OIDC.
55
+
56
+ ## Architecture
57
+
58
+ FastMCP-based MCP server for Spotify Web API integration using Python/`uv`.
59
+
60
+ ### Core Files
61
+ - **`src/spotify_mcp/fastmcp_server.py`** - Main MCP server: tools, resources, and prompts using `@mcp.tool()`/`@mcp.resource()`/`@mcp.prompt()` decorators, with typed Pydantic output models
62
+ - **`src/spotify_mcp/spotify_api.py`** - OAuth client wrapper (auth/token management only); tools talk to `self.sp` directly
63
+ - **`src/spotify_mcp/spotify_types.py`** - TypedDicts for the Spotify response shapes the server consumes
64
+ - **`src/spotify_mcp/utils.py`** - Redirect-URI normalization
65
+
66
+ ### Key Features
67
+ - **MCP Tools**: Playback control, search, queue management, playlist operations, track/artist info
68
+ - **Structured Output**: Every tool returns a typed Pydantic model (real output schema)
69
+ - **Tool Annotations & Icons**: read-only/destructive hints, titles, and a Spotify glyph on tools/resources/prompts
70
+ - **Progress & Elicitation**: progress notifications for large paginations; confirmation prompts before destructive playlist removals (when the client supports it)
71
+ - **Pagination Support**: Handles large datasets (10k+ tracks) with `limit`/`offset` parameters
72
+ - **OAuth Flow**: Automatic token management via spotipy
73
+ - **Type Safety**: Full Pydantic validation and MyPy compliance
74
+ - **Performance Logging**: Comprehensive timing and debug logging for tools and API calls
75
+
76
+ ## Development Guidelines
77
+
78
+ ### Tool Design Principles
79
+ - **Single Responsibility**: One focused purpose per tool (avoid `action` parameters)
80
+ - **Structured Returns**: Return a typed Pydantic model so the tool has a real output schema
81
+ - **Pagination-First**: Add `limit`/`offset` to tools that can return >20 items
82
+ - **Type Safety**: Use strict type hints and Pydantic validation
83
+
84
+ ### Code Quality Standards
85
+ - Run `mypy` and `pytest` before every commit
86
+ - Convert Spotify exceptions to MCP-compliant errors
87
+ - Include Args/Returns in all tool docstrings
88
+ - Mock external API calls in tests
89
+
@@ -0,0 +1,39 @@
1
+ # Multi-stage build for Spotify MCP server
2
+ FROM python:3.12-alpine AS builder
3
+
4
+ # Install build dependencies
5
+ RUN apk add --no-cache build-base
6
+
7
+ # Install uv
8
+ RUN pip install --no-cache-dir uv
9
+
10
+ # Set working directory
11
+ WORKDIR /app
12
+
13
+ # Copy dependency files first for better caching
14
+ COPY pyproject.toml uv.lock ./
15
+ COPY src/ ./src/
16
+ COPY README.md ./
17
+
18
+ # Sync dependencies and build
19
+ RUN uv sync --frozen --no-dev
20
+ RUN uv build
21
+
22
+ # Production stage
23
+ FROM python:3.12-alpine AS runtime
24
+
25
+ # Install uv for runtime
26
+ RUN pip install --no-cache-dir uv
27
+
28
+ # Copy the built wheel and install it
29
+ COPY --from=builder /app/dist/*.whl /tmp/
30
+ RUN pip install --no-cache-dir /tmp/*.whl && rm /tmp/*.whl
31
+
32
+ # Create non-root user for security
33
+ RUN addgroup -g 1001 -S spotify && \
34
+ adduser -S -D -H -u 1001 -s /sbin/nologin -G spotify spotify
35
+
36
+ USER spotify
37
+
38
+ # Command to run the MCP server
39
+ CMD ["spotify-mcp"]
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Varun Neal Srivastava
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.