TuneTidy
by peterish8
README.md
# TuneTidy
**Hosted ChatGPT ↔ Spotify control** — a small Connect website + MCP server so ChatGPT can act on *your* Spotify account.
Spotify stays the music app. TuneTidy is the middle layer: link once, then control playlists, playback (including exact-cue seek), and optional offline ZIP export from chat.
> Not a Spotify clone. Not a self-host Docker product for end users. You (operator) deploy once; everyone else just Connects.
---
## What it does
| For | Experience |
|-----|------------|
| **End user** | Open Connect → Link Spotify → add MCP URL in ChatGPT → chat |
| **Operator** | Deploy to Render (or similar), set env secrets once |
| **ChatGPT** | Calls 17 MCP tools; Spotify tokens never leave the server |
### Capabilities (Milestone A)
- **Connect site** — Spotify OAuth (Authorization Code), encrypted token storage
- **Playlist control** — list, create, add/remove tracks, update details
- **Playback** — play / pause / seek / now-playing; **exact-cue** (beat-drop seek with confirmation)
- **Offline export** — Spotify *metadata* → match on JioSaavn/Gaana APIs → ZIP download
*(not Spotify stream ripping)*
- **Safety** — write tools need `confirmationToken` + `idempotencyKey`; AES-256-GCM for tokens
---
## Architecture
```
Browser ──► apps/web (Next.js Connect + OAuth)
ChatGPT ──► apps/mcp (Fastify JSON-RPC MCP @ POST /mcp)
│
▼
packages/core (use cases)
│
┌───────────┼───────────┐
▼ ▼ ▼
packages/ packages/ packages/
spotify db saavn
│ │ │
Spotify API Postgres Saavn/Gaana APIs
```
| Path | Role |
|------|------|
| `apps/web` | Connect UX, Spotify OAuth routes, health |
| `apps/mcp` | Remote MCP: `tools/list`, `tools/call`, export downloads |
| `apps/worker` | Job worker skeleton (exports currently run in MCP process) |
| `packages/spotify` | Only place that calls Spotify HTTP |
| `packages/auth` | Token crypto, JWT verify, dev bypass |
| `packages/db` | Drizzle schema, migrations, repositories |
| `packages/core` | Playback cues, offline export pipeline |
| `packages/saavn` | Search/match for offline export |
| `packages/mcp-contracts` | Shared Zod tool schemas |
| `docs/` | Architecture, MCP catalog, deploy, security |
| `.planning/` | Roadmap / GSD state (optional for operators) |
---
## MCP tools (17)
Endpoint: **`POST /mcp`** (JSON-RPC). Also: `GET /mcp`, `GET /tools`.
| Group | Tools |
|-------|--------|
| **Read** | `account_get_status`, `playlists_list`, `library_get_summary`, `playback_get_state`, `playback_resolve_cue` |
| **Write** | `playlist_create`, `playlist_add_tracks`, `playlist_remove_tracks`, `playlist_update_details`, `playback_play`, `playback_pause`, `playback_seek`, `playback_play_exact_cue` |
| **Offline** | `offline_export_preview`, `offline_export_start`, `offline_export_status`, `offline_export_get_result` |
Full contracts: [`docs/MCP_TOOL_CATALOG.md`](docs/MCP_TOOL_CATALOG.md).
---
## Quick start (local)
**Requirements:** Node ≥ 22, pnpm 9, Postgres (Docker optional).
```bash
# 1. Install
pnpm install
# 2. Env
cp .env.example .env
# Generate encryption key:
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
# Set TOKEN_ENCRYPTION_KEY_CURRENT and DATABASE_URL
# 3. Database
docker compose up -d # optional local Postgres
pnpm db:migrate
# pnpm db:seed # optional
# 4. Run
pnpm dev:web # http://127.0.0.1:3000
pnpm dev:mcp # http://127.0.0.1:3100
```
### Minimum env for local Connect + MCP
| Variable | Notes |
|----------|--------|
| `DATABASE_URL` | Postgres connection string |
| `TOKEN_ENCRYPTION_KEY_CURRENT` | 32-byte key, base64 |
| `SPOTIFY_CLIENT_ID` / `SECRET` | [Spotify Developer Dashboard](https://developer.spotify.com/dashboard) |
| `SPOTIFY_REDIRECT_URI` | e.g. `http://127.0.0.1:3000/api/spotify/callback` |
| `ALLOW_DEV_AUTH_BYPASS` | `true` for local MCP without Auth0 |
| `DEV_USER_ID` | Stable user id when bypass is on |
| `APP_BASE_URL` / `MCP_BASE_URL` | Defaults in `.env.example` |
Auth0 vars are required for production-style login; dev bypass skips MCP Bearer checks.
See [`.env.example`](.env.example) for the full list (Saavn URLs, cover storage, classifier stubs, etc.).
### Smoke MCP (dev bypass)
```bash
curl -s -X POST http://127.0.0.1:3100/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
```
---
## Deploy — free multi-user (recommended)
**Don’t use paid Render Postgres.** Use **Convex free** for encrypted Spotify tokens:
1. `npx convex dev` / `npx convex deploy` (see [`docs/CONVEX_FREE_MULTIUSER.md`](docs/CONVEX_FREE_MULTIUSER.md))
2. Render **2 free Web Services**: `tunetidy-web` + `tunetidy-mcp` (no Postgres service)
3. Env: `STORAGE_BACKEND=convex`, `CONVEX_URL`, `CONVEX_SERVER_KEY`, encryption key, Spotify app
4. Users open Connect → Link Spotify once → ChatGPT uses MCP
| Mode | Docs |
|------|------|
| Convex multi-user (free) | [`docs/CONVEX_FREE_MULTIUSER.md`](docs/CONVEX_FREE_MULTIUSER.md) |
| MCP-only single user (no DB) | [`docs/SINGLE_USER_FREE_DEPLOY.md`](docs/SINGLE_USER_FREE_DEPLOY.md) |
| Local Postgres | [`docs/DEPLOYMENT.md`](docs/DEPLOYMENT.md) |
---
## Scripts
| Command | Purpose |
|---------|---------|
| `pnpm dev` / `dev:web` / `dev:mcp` | Local apps |
| `pnpm db:migrate` / `db:seed` | Schema + seed |
| `pnpm typecheck` | TypeScript across packages |
| `pnpm test` | Unit tests (Vitest) |
| `pnpm test:contract` | Spotify client contracts |
| `pnpm build` | Production builds |
| `pnpm verify` | lint + typecheck + tests + build |
---
## Security & compliance
- Spotify **refresh/access tokens** are encrypted at rest; tools never return them.
- Destructive writes are confirmation + idempotency gated.
- **Offline export** uses third-party catalog APIs (Saavn-compatible / Gaana). Not official Spotify Offline. Operators are responsible for ToS/legal fit in their region.
- Never commit `.env` — only `.env.example`.
More: [`docs/SECURITY.md`](docs/SECURITY.md).
---
## Docs
| Doc | Content |
|-----|---------|
| [`docs/MCP_TOOL_CATALOG.md`](docs/MCP_TOOL_CATALOG.md) | All tools + curl examples |
| [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) | Package boundaries |
| [`docs/FEATURES_EXACT_CUE_PLAYBACK.md`](docs/FEATURES_EXACT_CUE_PLAYBACK.md) | Beat-drop / exact seek |
| [`docs/FEATURES_OFFLINE_EXPORT.md`](docs/FEATURES_OFFLINE_EXPORT.md) | JioSaavn match → ZIP |
| [`docs/DEPLOYMENT.md`](docs/DEPLOYMENT.md) | Local + credential setup |
| [`docs/CODE_AUDIT_REPORT.md`](docs/CODE_AUDIT_REPORT.md) | Build completeness audit |
| [`docs/CONNECT_LOCAL_TEST.md`](docs/CONNECT_LOCAL_TEST.md) | Connect smoke checklist |
Planning source (optional): `.planning/`, archive PRDs under `all_raw_infos/`.
---
## Status
| Area | State |
|------|--------|
| Connect + Spotify OAuth | ✅ Implemented |
| MCP tools (17) | ✅ Implemented |
| Exact-cue playback | ✅ Implemented |
| Offline export (preview → ZIP) | ✅ Implemented |
| Automated tests / typecheck | ✅ Passing in CI path |
| Production Render + live ChatGPT smoke | ⬜ Operator (you) |
| Full library organizer (rules, heavy sync UI) | ⬜ Backlog |
---
## License
Private / unlicensed unless you add a license file. Spotify, JioSaavn, Gaana, Auth0, and ChatGPT are trademarks of their respective owners; this project is an independent companion stack.
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues