Skip to content

feat: value-add curation layer (OpenAI Apps resubmission)#3

Open
lucaperret wants to merge 1 commit into
mainfrom
feat/openai-curation-layer
Open

feat: value-add curation layer (OpenAI Apps resubmission)#3
lucaperret wants to merge 1 commit into
mainfrom
feat/openai-curation-layer

Conversation

@lucaperret

Copy link
Copy Markdown
Owner

Why

Our ChatGPT app was rejected (May 2026) as a thin wrapper / pass-through — it ported ~40 low-level Tidal tools. This adds a value-add curation layer so it clears OpenAI's bar, without changing any existing CLI command or MCP tool.

What (additive only)

New src/curation/ module + gating via TIDAL_MCP_PROFILE:

  • full (default) → the existing 32 tools + prompts, UNCHANGED
  • apponly the 4 value-add tools (the scoped OpenAI submission surface)
  • all → both

The 4 tools

Tool Type Value-add
analyze_library read-only Aggregates the library (sizes, staleness, saved composition, cross-playlist duplicates, "forgotten gems" saved-but-in-no-playlist) → host model turns it into a taste profile / blind spots / cleanup
curate_playlist read-only preview Resolves host-model-proposed tracks on Tidal + enforces a constraint server-side — audiophile = lossless/hi-res only (the generic-chat-can't differentiator), deep_cuts, energy_arc
playlist_from_text read-only preview Turns pasted text (festival lineup, article…) into a resolved playlist with fuzzy match + "not found" reporting
create_playlist write (non-destructive) The only write — commits a confirmed preview

Design

  • LLM-as-orchestrator: the host model reasons; the server only shapes data, enforces constraints, resolves on Tidal, writes. No server-side LLM, only the official Tidal v2 API.
  • Cross-client: appResponse() → clean text + structuredContent (Claude) + _meta output template (ChatGPT widgets, ignored by Claude). Widgets served as MCP resources (ui://tidal/library-insights, ui://tidal/playlist-preview, text/html;profile=mcp-app).
  • Clean responses: raw IDs/timestamps live only in structuredContent; the model-facing summary stays clean.

Non-regression (verified)

  • Default profile = 32 tools, byte-identical to before; app = exactly the 4 curation tools; all = 36.
  • 206 tests green (42 new: aggregate, resolve, MCP-protocol round-trip incl. resource serving).
  • next build (site, with curation) succeeds; curation excluded from the npm CLI tarball.
  • Validated on real data: analyze + audiophile curate work; Tidal mediaTags carry "LOSSLESS" → lossless filter works with no extra calls.

Scope notes

  • Targets the HTTP (Vercel) surface = the OpenAI submission endpoint. The mcpb/stdio (Claude Desktop) gating exists locally but is not in this PR (mcpb/ is untracked WIP).
  • ⚠️ Do not flip production to app (would drop the 32 tools for current Claude/Smithery users). Deploy the OpenAI app as a separate app-profile endpoint (see DEPLOY_CHATGPT.md).
  • Companion docs (local, not in this PR): OPENAI_RESUBMISSION.md (resubmission notes), DEPLOY_CHATGPT.md (deploy + ChatGPT test guide).

🤖 Generated with Claude Code

Additive layer to clear OpenAI's "thin wrapper / pass-through" rejection —
without changing any existing CLI command or MCP tool.

New module `src/curation/`:
- respond.ts   — appResponse(): cross-client tool result (clean text + structuredContent
                 for Claude; _meta outputTemplate for ChatGPT widgets, ignored by Claude)
- aggregate.ts — analyze_library: playlist sizes/staleness, saved composition, cross-playlist
                 duplicates, "forgotten gems" (saved-but-in-no-playlist)
- resolve.ts   — resolve engine: search -> best-match -> dedupe -> audio-quality flags
                 (LOSSLESS/HI-RES via mediaTags) -> lossless-only / deep-cuts filters; 45s deadline
- register.ts  — 4 tools: analyze_library, curate_playlist, playlist_from_text (read-only
                 previews), create_playlist (the only write, non-destructive)
- components.ts — ChatGPT UI resources (ui://tidal/library-insights, ui://tidal/playlist-preview),
                 vanilla JS, served as MCP resources (text/html;profile=mcp-app)

Gating via TIDAL_MCP_PROFILE at the registration call sites:
  full (default) -> existing 32 tools + prompts, UNCHANGED
  app            -> only the 4 curation tools (the scoped OpenAI submission surface)
  all            -> both

LLM-as-orchestrator: the host model does the taste/curation reasoning; the server only shapes
data, enforces constraints (lossless-only), resolves on Tidal, and writes. No server-side LLM,
only the official Tidal v2 API.

Cross-client: same tools work on Claude (text + structuredContent) and ChatGPT (widgets via _meta).
Additive-only: default profile is byte-identical to before (32 tools); curation excluded from the
npm CLI tarball. 42 new vitest tests (aggregate, resolve, MCP-protocol round-trip incl. resource
serving). Validated on real data: analyze_library + audiophile curate work; Tidal mediaTags carry
"LOSSLESS" so the lossless filter works without extra calls.

Note: the mcpb (stdio/Claude Desktop) gating exists locally but is not in this PR (mcpb/ is untracked
WIP). This PR targets the HTTP (Vercel) surface = the OpenAI submission endpoint.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 1, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
tidal-cli Ready Ready Preview, Comment Jul 1, 2026 11:17pm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant