# Assist With Music MCP Tool Contract Updated: 2026-09-03 Status: live. Assist With Music remote MCP server is live and connectable. Point any MCP client that speaks Streamable HTTP with OAuth at the connector URL below, sign in, approve a grant, and the tools operate on the signed-in user's own private planner data. No named client has completed a recorded connection lifecycle yet, so no client is described here as supported. Canonical connector URL (publish this for new connections): https://assistwithmusic.com/mcp https://thesongplanner.com/mcp remains a direct transport compatibility route for existing clients rather than following the old web-domain redirect. ## App And Agent Split Assist With Music is the user's durable music-creation workspace. It owns private planner data, permissions, workflow state, source/provenance records, durable song creation records, genre context, and user review. The user's assistant owns reasoning, research, synthesis, drafting, browser work, and judgment. External tools such as Suno are used only when the user has access and explicitly asks for that operation. Suno generation is desktop-only: these tools build the plan, prompt, and lyrics and LOG generation results — they never generate audio or operate external accounts. AI-produced drafts are labeled as assistant-produced. ## Auth And Ownership - Private tool calls require a signed-in Assist With Music account. - Tool handlers must derive owner/account identity from verified auth. - Clients and agents must not send owner IDs, user IDs, tenant IDs, or account IDs. - Anonymous remote MCP requests must return an OAuth challenge before tool listing or tool calls. - V1 must not include destructive tools, billing tools, public exports of private data, or external-account automation. ## Transport Compatibility - Modern MCP `2026-07-28` clients use self-contained POST requests. The client sends protocol/client metadata plus the mirrored method and tool name headers on every request; the server does not issue or require an MCP session ID. - `server/discover` is the modern discovery entry point. Discovery and tool-list responses are private with a zero cache lifetime. - Every request presents its OAuth bearer and the server verifies the signed Clerk access-token JWT against Clerk's public JWKS. The verified `sub` binds the owner and verified `client_id` selects exactly that client's grant; protocol state never supplies account authority. - Existing `2025-06-18` and `2025-03-26` initialize/session clients remain supported during rollout. This is compatibility, not a claim that every AI client has already adopted `2026-07-28`. - The repository's HTTP-level tests prove the Music feature subset: OAuth challenge, discovery/listing, context, structured save/read, Queue handoff, cross-owner denial, browser Origin rejection, and legacy session listing. The server does not claim full protocol conformance for unused features. ## Connecting A Client - Music has NOT recorded a completed connection lifecycle for any named client. The names below are examples of each client SHAPE, not a support claim. - Clients with a connector settings screen (Claude, ChatGPT, Cursor and similar): open the client's settings, find its connectors or MCP section, add a custom connector, paste https://assistwithmusic.com/mcp, continue, sign in, and approve the scopes you want to grant. - Clients you configure from a terminal (Claude Code, Codex and similar): ` mcp add --transport http assistwithmusic https://assistwithmusic.com/mcp`, then run the client's authenticate/login step. Do NOT pass an Authorization header — leaving it off is what triggers the OAuth sign-in. - Signing in IS the approval. Completing the OAuth flow gives the connection the default tier straight away: read the owner's work, save reviewed results into Planner review, and work the Queue items they handed off. Nothing else has to be pressed for the tools to start working. - Four authorities signing in never grants, and the owner raises them at https://assistwithmusic.com/settings/ai: `music.media.read`, `music.media.write`, music:archive (permanently removing an empty project), and credit:spend (the external generation preparation gate). That page is a dial, not a turnstile — it also narrows a connection. - One authority no approval can ever grant: adding to the owner's Queue. The Queue is their list of what they asked for. Use songplanner_suggest, which lands in Ideas at https://assistwithmusic.com/ideas and becomes Queue work only if the owner confirms it there. - Connecting takes minutes, not seconds, and two steps cannot be delegated: signing in, and clicking Allow. ## First Call Call `songplanner_context` with `action:"workspace"` first. It returns the signed-in account, projects, runnable owners, recent plan summaries, indexed queue funnel counts, and `agentPolicy` — the user's AUTONOMY CEILING (`prep` | `generate` | `distribute`). Obey it: at `prep` (the default), do all first-party prep then `songplanner_work` with `action:"park"` and `waitingOnUser:"approve_plan"`/`"login:suno"` before generating on Suno; at `generate`, you may generate + save but stop before download/selection. The legacy `songplanner_get_workspace_context` remains live only for compatibility. When the user's creative or planning task may benefit from a repeatable Music workflow, call `songplanner_loops` with `action:"discover"` and describe the task, theme, or workflow. Read one returned stable slug before applying it. Before writing lyrics or a style prompt for a project, read its direction from workspace/project/brief context. `avoid[]` is a hard rule and is off-limits; `useSparingly[]` is a legitimate occasional sub-genre mix, used deliberately and infrequently — never as the default or fallback. `agentPolicy` also carries `creditPosture` (`unlimited` | `mindful`) and the derived `mentionCredits` flag. Default is `unlimited`: the user has ample Suno credits, so NEVER mention, count, or ask about credit cost — generating music is the intended use. Only when `creditPosture:"mindful"` (and `mentionCredits:true`) may you note credit usage or check before large batches; there the prep park uses `waitingOnUser:"approve_cost"`. Follow `agentPolicy.guidance` verbatim. ## Production Tools And Release Additions Of the 36 tools in this release contract, 33 are deployed and callable today. The three private-media tools are release-source only until the Convex production deploy and operational proof. Each deployed private write is idempotent on the natural key shown. Save/upsert tools update the same record. A connected AI cannot add to the Queue: use `songplanner_suggest` for anything you think the owner might want, and queue `edit` to modify a request they already made. The website's `/requests` page is the existing work desk: it captures asks, shows request history, and hosts connected-AI preferences. `/queue` is the shared four-state handoff/lifecycle surface: Waiting for your AI, Needs you, Working, and Done. They use the same owner-scoped plan records; a connected AI cannot clear a Needs you gate. Untriaged asks live in the reserved inbox until an agent files them. When the user explicitly chooses no project, the song or one-off album lives in the visible Singles system project, and agents pull work with `songplanner_queue` action `list` -> `songplanner_work` action `claim` -> (work) -> `songplanner_work` action `complete`. Work to your CAPACITY: do everything your environment allows, then if a step needs something you don't have (a browser for Suno, the user to log in, credit approval), call `songplanner_report_blocked` to park the plan at the stage you reached with the exact resume step. A later run that arrives WITH that capability calls `songplanner_list_deferred_work`, claims an item it can satisfy (claiming resumes it), and finishes the parked step. This is how "prep the song now, add it to Suno when a browser is available" works without losing progress. Prefer `songplanner_work` for new queue-working sessions. The older `songplanner_claim_request` / `songplanner_report_blocked` / `songplanner_complete_request` tools remain live for compatibility. Legacy compatibility remains evidence-gated: all 19 legacy handlers record owner-scoped, per-tool daily completed-invocation counts. Operators can inspect them with internal `mcpSetup:legacyToolUsage` for a named owner and date window. No legacy tool is removed by this instrumentation pass; gateway audit remains the companion source for failed-call diagnostics. ### `songplanner_context` Live. Preferred v2 orientation tool. Use `workspace` first for the signed-in account, projects, autonomy ceiling, runnableOwners, recent plan summaries, queue funnel counts, `creationCapabilities`, and `capabilityDemand`. The compact `capabilities` action returns browser/local-file/login/prep-only boundaries, the Suno desktop-only handoff, autonomy policy, `handoffSurfaces` roles for Requests and Queue, and the capped queue items that need each capability. Untriaged web requests are stored in the reserved inbox project until an agent moves them. Explicitly standalone work is stored in the visible Singles project. Use `projects` for only the visible project list. The same tool provides a bounded public encyclopedia workflow without dumping the corpus: `genres` lists top-level genres; `genre_families` with `name` lists families for one returned genre; `genre_subgenres` with a returned genre or `Genre › Family` path lists direct children; and `genre_search` with `query` returns up to 20 concise ranked candidates from a scan capped at 1,000 rich encyclopedia rows. Results include stable names/paths and `exactReaderName`. Call `genre_recipe` with `name` equal to one returned `exactReaderName` (or `names[]` for a batch) to hydrate the rich descriptions, history, artists, songs, sources, and prompt guidance. Unknown selections and empty searches return `found:false` plus an empty `items[]`; list/search caps and truncation are explicit. Discovery is public reference data and never reads a different owner's Music records. Use it when a requested style is unfamiliar, when navigating deep subgenres, or before writing a research-backed style prompt. Calls stamp `agentLastActiveAt`. Each project row includes its existing `config`, including `config.direction` when the owner has set one. Direction is optional; its absence does not change the workflow. Each `runnableOwners[]` row reports `openRequestCount` using the same actionable queue membership rule as `songplanner_queue` (`enqueued !== false`). The broader `activePlanCount` separately counts non-parked, non-published plans; both fields have matching `...Capped` flags. Inputs: `action` (workspace|capabilities|projects|genres|genre_families|genre_subgenres|genre_search|genre_recipe), optional `names[]`, `name`, `query`, `limit`, and guarded `onBehalfOf`. Delegation: omit `onBehalfOf` for your own account. Pass `onBehalfOf` only for an owner who has enabled delegation and granted your agent an active scope: `queue:read` for context/read/status, `queue:work` for work/save/memory and organization, and `credit:spend` before a delegated agent can select a generation or spend/advance credit-like actions. Revoked or expired grants release delegated queue leases. ### `songplanner_read` Live. Preferred v2 read tool. Use `brief` to resume a plan from durable memory: active decisions, ruled-out corrections, open questions, stale artifacts, and the plan's `stageReached` / `resumeStep` anchors. Use `plan` to read one plan with bounded child pages. Use `project` to read a capped project overview with small child previews. Project reads return both `config` and `direction`; plan and brief reads return the full `projectDirection` or `null`. Read that direction before making creative choices. Inputs: `action` (brief|plan|project), optional `projectKey`, `planId`, short owner-scoped `queueNumber`, compatibility `queueRef`, `planKey`, `section`, `cursor`, `limit`, and guarded `onBehalfOf`. For child pagination, call `action:"plan"` with a specific `section` (`researchNotes`, `lyricDrafts`, `promptAttempts`, `generationResults`, or `sourceChecks`) plus the returned `cursor`. The full plan read returns those exact child names under `children`; project previews return `researchNotes`, `lyricDrafts`, `promptAttempts`, and `generationResults` under `childPreview`. Each `researchNotes` item is either `kind:"note"` or `kind:"document"` and includes `contentClass` (`source_fact`, `inference`, `creative_idea`, or `generated_draft`), the current markdown and sources, `currentVersion`, and append-only `revisions[]` metadata (revision number, actor principal/type, timestamp, and sources). Errors are structured and safe for clients: malformed `planId` returns `invalid_id_format`; a valid missing plan returns `plan_not_found`; and an unknown `projectKey` returns `project_not_found`. Payloads name the offending field and never include server stack traces, file paths, or line numbers. Use `songplanner_queue` when you need to fill or organize the backlog itself: create batches of typed requests, edit them, reorder them, or discard them without deleting creative work. Request metadata can include `requestScope`, `requestedCount`, `sourceMode`, `directed`, `title`, `summary`, and `sourceRefs`. Use `songplanner_memory` when you need durable plan-specific reasoning: record small decisions, ruled-out corrections, open questions, and notes so a later run does not repeat a rejected path or overwrite a higher-evidence choice. ### `songplanner_loops` Live. Preferred read-only access to the public, sanitized Music Loops & AI Skill Library. Use `action:"discover"` early when the user's task may benefit from a repeatable creative or planning workflow. Search with a task, theme, or workflow `query`, optionally filtered by pipeline `stage` and `status`. Results are capped to 10 and return only product-native summaries: suitability/capability signals, skill names, relevance terms, a stable slug, and the canonical branded loop URL. Use `action:"read"` with one exact returned `slug` for bounded overview, skills, steps, outputs, suitability, requirements, alternatives, and paired-loop stable references. The response identifies its scope as `public_library`; it never reads or changes planner records and never returns the raw catalog source. Inputs: `action` (`discover`|`read`); optional `query`, `slug`, `stage` (`write`|`create`|`download`|`master`|`distribute`|`organize`), `status` (`available`|`planned`), and `limit` (clamped to 1–10). Discovery requires a query or filter. A zero-match query returns `matched:false` with `matches:[]`. An unknown exact slug returns `found:false` with `loop_not_found`; clients must discover again rather than inventing a replacement. ### `songplanner_get_workspace_context` [LEGACY — use songplanner_context] Live. Deprecated legacy tool; prefer `songplanner_context` action `workspace`. Returns the user's projects, recent plan summaries, and creation modes in one read. Legacy `projects`, `recentPlans`, and `counts` map to the v2 fields with the same names. Inputs: optional `projectKey`, optional `limit`. ### `songplanner_list_projects` [LEGACY — use songplanner_context] Live. Deprecated legacy tool; prefer `songplanner_context` action `projects`. Lists the user's projects (key + name) as a compact picker. Field mapping: legacy `projects` -> v2 `projects`. Inputs: optional `limit`. ### `songplanner_get_project_context` [LEGACY — use songplanner_read] Live. Deprecated legacy tool; prefer `songplanner_read` action `project`. Loads one project's plans and child records before creating or editing. Child-field mapping within each plan: legacy `research` -> v2 `childPreview.researchNotes`; `lyrics` -> `childPreview.lyricDrafts`; `prompts` -> `childPreview.promptAttempts`; `generations` -> `childPreview.generationResults`. Inputs: `projectKey`, optional `includeRecentPlans`, optional `limit`. ### `songplanner_get_song_plan_context` [LEGACY — use songplanner_read] Live. Deprecated legacy tool; prefer `songplanner_read` action `plan` or action `brief`. Loads ONE song plan in full — the plan plus all of its research notes, lyric drafts, prompt attempts, generation attempts, and source checks (with content). Call before editing a plan so you build on what already exists. Inputs: `planId`. Child-field mapping: legacy `research` -> v2 `children.researchNotes`; `lyrics` -> `children.lyricDrafts`; `prompts` -> `children.promptAttempts`; `generations` -> `children.generationResults`; `sourceChecks` -> `children.sourceChecks`. ### `songplanner_get_genre_recipe` [LEGACY — use songplanner_context] Live. Deprecated legacy tool; prefer `songplanner_context` action `genre_recipe`. Looks up genres/sub-genres in the public encyclopedia and returns their style "ingredients" — history, eras, defining traits, top artists, and essential songs — to build a Suno prompt FROM. Use genre traits, never a protected artist's identity. Names are EXACT-match on the encyclopedia's casing/spelling ("Americana", not "americana"); misses return `matched: false` with a hint. Inputs: `names` (array). Field mapping: legacy `recipes` -> v2 `recipes`. ### `songplanner_upsert_song_plan` [LEGACY — use songplanner_save] Live. Deprecated legacy tool; prefer `songplanner_save`. Creates or updates a song plan (the durable idea + workflow state). Inputs: `projectKey`, `planKey`, title, optional classification, mode, status, source refs, summary, target use, optional song key. Idempotency: `projectKey + planKey`. Omitted optional patch fields leave existing values unchanged. ### `songplanner_write_research_note` [LEGACY — use songplanner_save] Live. Deprecated legacy tool; prefer `songplanner_save` with `researchNotes[]`. Saves a research note on a plan — source notes, constraints, observations, and uncertainty. Inputs: `planId`, optional `sectionKey`, title, markdown content, structured data, source refs, status. Idempotency: `planId + sectionKey`. ### `songplanner_upsert_lyric_draft` [LEGACY — use songplanner_save] Live. Deprecated legacy tool; prefer `songplanner_save` with `lyricDrafts[]`. Saves a lyric draft in one of the supported creation modes. Inputs: `planId`, `draftKey`, title, mode, lyrics markdown, optional rationale and status. (Draft versions are auto-incremented on re-save; there is no version input.) Idempotency: `planId + draftKey`. ### `songplanner_upsert_prompt_attempt` [LEGACY — use songplanner_save] Live. Deprecated legacy tool; prefer `songplanner_save` with `promptAttempts[]`. Saves a Suno/style prompt attempt. Inputs: `planId`, `attemptKey`, title, style prompt, optional negative prompt, optional lyric draft ID (must belong to the same plan), genre recipe, settings, target platform, status. Idempotency: `planId + attemptKey`. ### `songplanner_log_generation_attempt` [LEGACY — use songplanner_save] Live. Deprecated legacy tool; prefer `songplanner_save` with `generationResults[]`. RECORDS a generation result (Suno URL, length, rating, status). Does NOT generate audio and does not operate Suno. Inputs: `planId`, optional `attemptKey`, optional prompt attempt ID, platform, external ID, Suno URL, audio URL, status, result metadata, notes, rating. Idempotency: `planId + attemptKey`. ### `songplanner_log_source_check` [LEGACY — use songplanner_save] Live. Deprecated legacy tool; prefer `songplanner_save` with `sourceChecks[]`. Records where you looked to verify a source (provenance) so a claim is backed by an actual check. Status is one of checked|blocked|gated|requires_user. Never claim public-domain status without a logged source check. Inputs: `planId`, `checkKey`, status, and the source/query/summary details of the check. Idempotency: `planId + checkKey`. ### `songplanner_upsert_project` [LEGACY — use songplanner_organize] Live. Deprecated legacy tool; prefer `songplanner_organize` action `upsert_project`. Creates or updates a project. Inputs: `key`, name, optional description and config fields. Idempotency: `key`. ### `songplanner_move_plan` [LEGACY — use songplanner_organize] Live. Deprecated legacy tool; prefer `songplanner_organize` action `move_plan`. Moves a plan to another project. Inputs: source `projectKey` + `planKey`, and target `projectKey`. The preferred v2 `songplanner_organize` move also accepts `planId`. ### `songplanner_list_requests` [LEGACY — use songplanner_queue] Live. Deprecated legacy tool; prefer `songplanner_queue` action `list`. Lists the user's OPEN requests from the website's /requests queue. Each request may include `requestKind`, `requestScope` (one|multi|album), `requestedCount`, `sourceMode` (original|based_on|exact_lyrics), `directed`, title, summary, project, and sourceRefs. Untriaged web requests live in the reserved inbox until an agent files them; explicitly standalone work lives in Singles. Use this when the user says "work my queue" or "do my requests". Inputs: optional `limit`, optional `requestKind` filter. ### `songplanner_claim_request` [LEGACY — use songplanner_work] Live. Deprecated legacy tool; prefer `songplanner_work` action `claim`. Claims ONE request off the queue before working it. Claiming moves it out of the open inbox so a second concurrent run will not grab the same one, and returns a per-kind next step. Pass the `planId` from `songplanner_list_requests`. Inputs: `planId`. ### `songplanner_complete_request` [LEGACY — use songplanner_work] Live. Deprecated legacy tool; prefer `songplanner_work` action `complete`. Marks a claimed request done — mainly for research/organize requests, which have no song lifecycle (defaults to "parked"). A new_song request usually advances on its own through the normal plan workflow. Inputs: `planId`, optional final status. ### `songplanner_report_blocked` [LEGACY — use songplanner_work] Live. Deprecated legacy tool; prefer `songplanner_work` action `park`. Call when you've done everything you CAN on a plan and must stop because you lack a capability (e.g. no browser for Suno) or need the user (log in / approve credits / answer a question). All prep stays saved; the plan keeps its furthest stage. Records the stage reached, what's required, the exact resume step, and an optional user ask — the resumable "prep-and-park" state. Set `waitingOnUser` when the USER must act first. Inputs: `planId`, `reason`, optional `stageReached`, `needsCapabilities` (e.g. ["browser","login:suno"]), `resumeStep` (generate|download_wav|distribute| select), and `waitingOnUser`. Published tokens: `approve_plan`, `approve_cost`, `approve_release`, `login:suno`, `login:distrokid`, `manual_generate`, `question`, and `captcha:`. Unknown values are preserved for existing data and return an `unknown_waiting_on_user` warning rather than failing. ### `songplanner_list_deferred_work` [LEGACY — use songplanner_work] Live. Deprecated legacy tool; prefer `songplanner_work` action `list_deferred`. Lists the user's parked-pending work — plans someone prepped and stopped on, each with stageReached, needsCapabilities, resumeStep, blockedReason, waitingOnUser, and the anchor IDs to resume from. Call this when you ARRIVE with new capabilities (e.g. a logged-in browser): take items whose needsCapabilities your environment can satisfy, claim one (claiming resumes it), and do its resumeStep. Skip items with `waitingOnUser` set — those need the user first. A connected AI cannot attest that the human action happened; the signed-in owner clears the gate in the product Queue, then a capable AI claims the item. Inputs: optional `limit`. ### `songplanner_work` Live. Consolidated queue-working tool. Use this instead of several single-step queue tools when working requests end to end: `claim` leases prioritized work by capability, `heartbeat` keeps the lease alive, `unclaim` releases a mistaken claim, `begin_generation` arms the duplicate-spend guard immediately before a Suno Create click, `park` saves progress when you hit a browser/login/user boundary, `fail` records a retryable or terminal failure, `retry` returns a retryable failure to the handoff lane, `complete` records a durable result, and `list_deferred` shows parked work that can be resumed. Suno and other browser/account steps are still desktop-only; prep what you can, then park with exact resume instructions. Inputs: `action` (claim|heartbeat|unclaim|begin_generation|park|fail|retry|complete|list_deferred), optional `planId`, short owner-scoped `queueNumber`, or compatibility `queueRef`, `runKey`, `batchSize`, `capabilities`, `forceGeneration`, `reason`, `stageReached`, `needsCapabilities`, `resumeStep`, `waitingOnUser`, `status`, `note`, `sourceRefs`, `failureCode`, `retryable`, `retryAt`, `expectedVersion`, `operationId`, `limit`, and guarded `onBehalfOf`. For `complete`, optional `mediaIds` (maximum 20) attaches already-committed private media to the Queue item. That shape additionally requires `music.media.write`; ordinary Queue work cannot attach media. Every state-changing action requires both fields. Read `queueVersion` first, send it as `expectedVersion`, and reuse one client-generated `operationId` when retrying the same operation. A stale version is rejected before mutation and an operation ID reused for a different item or action is rejected as a collision. For `claim`, read the prioritized Queue first, then pass one exact `planId` or its short `queueNumber`, its `expectedVersion`, and `batchSize:1`. v2 claims are deliberately single-item so the version precondition cannot ambiguously apply to several Queue rows. Every claimed item includes full `projectDirection` plus `directionNote`, or `projectDirection:null` when the project has no direction. runKey: supply ONE runKey on `claim` and reuse that exact value on every `heartbeat`/`begin_generation`/`unclaim`/`park`/`fail`/`complete` for that claim. If you omit it, the server derives one and the claim response echoes it (`runKey`); calling later actions with a different key reads as another run and returns `lost_lease`. `begin_generation` arms ONCE per plan — if any generation attempt is already logged, re-arming requires `forceGeneration: true` (an intentional regenerate). `waitingOnUser` vocabulary: `approve_plan`, `approve_cost`, `approve_release`, `login:suno`, `login:distrokid`, `manual_generate`, `question`, or `captcha:`. Unknown values are stored for compatibility and returned with a structured `unknown_waiting_on_user` warning. `resume_user_gate` is rejected at the MCP boundary, including for an AI connected as the owner, because an agent cannot confirm that a human action occurred. The signed-in product mutation performs this owner-only confirmation; a later `claim` with the required capabilities starts a fresh 15-minute lease. ### `songplanner_queue` Live. Consolidated backlog-management tool for work the owner asked for. Use `list` to see bounded work in priority order (`priorityRank` then created time) with optional four-state `familyState`, `activity` for cursor-paginated attributable history on one Queue item, `edit` to patch an existing request, `reorder` to move a request by explicit rank or before/after another request, and `discard` to remove a request from the queue while keeping the song plan row and any creative work. There is no `create`. The Queue is the owner's own list of what they asked for; a connected AI answers it and does not add to it. Anything the AI thinks the owner might want goes to `songplanner_suggest`, which lands in Ideas and becomes Queue work only if the owner confirms it there. Delegated scope is least-privilege: `queue:read` permits list and activity, and `queue:work` is required for edit, reorder, or discard. `queue:create` is a reserved scope no connection may hold at any tier. Inputs: `action` (list|activity|edit|reorder|discard), optional `planId`, short owner-scoped `queueNumber`, or compatibility `queueRef`, `queueStatus`, `requestKind`, `limit`, `title`, `targetProjectKey`, `targetPlanKey`, `requestScope`, `requestedCount`, `sourceMode`, `directed`, `genreHint`, `instructions`, `summary`, `sourceRefs`, `priorityRank` (reorder only), `beforePlanId`, `afterPlanId`, `familyState`, `cursor`, `expectedVersion`, `operationId`, and guarded `onBehalfOf`. `list` and `activity` are reads and omit the mutation envelope. Every other action requires a client-generated `operationId` and `expectedVersion`. Reuse the same operation ID only to retry the same owner-scoped action. Stale versions and cross-action replays fail closed. ### `songplanner_suggest` Live. Where an AI's own recommendations go, since they do not go in the Queue. Use `suggest` to file ideas, `list` to read back what you filed and what became of it, and `withdraw` to take one back while the owner has not yet answered it. Nothing filed here is queued. Each idea lands on the owner's Ideas page at https://assistwithmusic.com/ideas attributed to the connection that filed it, and becomes Queue work only when the owner confirms it. Confirm and dismiss are session-authenticated mutations no tool reaches, and they carry equal weight in the interface — making confirm primary would be the product recommending the AI's suggestion on the AI's behalf. Nothing is deleted: a dismissed idea stays readable. Inputs: `action` (suggest|list|withdraw), `entries[]` for suggest, optional `suggestionKey` for withdraw, optional `state` and `limit` for list. Each entry takes `suggestionKey`, `title`, and `reason` (all required — an unexplained suggestion is noise the owner has to reverse-engineer before dismissing it), plus optional `requestKind`, `targetProjectKey`, and `suggestedOrder`. `suggestedOrder` records the order the AI would work through them; it is stored as the AI's opinion, nothing sorts by it, and it is rendered as a sentence naming whose opinion it is. Unknown keys return `acceptedKeys`. Idempotent on `suggestionKey`: re-running a pass files one idea rather than two. Up to 25 entries per call, and at most 100 ideas may wait undecided. Scope: `music:read` for list, `music:write` for suggest and withdraw. It needed no new scope — filing something the owner must confirm is the same trust as saving a reviewed result they must read. `summary` is an alias for `instructions`; both become the same stored summary text, and `instructions` wins if both are present. Modifications use `action:"edit"`; there is no `create`. Every row returns `outcome`, and `createdOrUpdated` excludes collision/error and already-active rows. Unknown entry properties return `unknown_property` with `offendingKey` and `acceptedKeys`. Every Queue row has a short conversational identity: `queueNumber:42` and `queueLabel:"Queue 42"`, plus a direct `queueHref`. Say “Queue 42” to the user and pass `queueNumber:42` on read/action calls. The number is assigned once from a private per-owner sequence, is unique only within that owner's workspace, never reveals global or other-owner counts, is never reused, and remains stable after Done, discard, or archive-like lifecycle changes. Legacy Queue rows receive persisted creation-order numbers through a dry-run-first bounded backfill. Lookup is always scoped to the connected owner; absent and foreign numbers return the same not-found result. The opaque `MUS-Q-…` `queueRef` and old `?ref=` deep links remain supported for compatibility, but are not the person-facing reference. For an explicitly standalone song or one-off album, create with `targetProjectKey:"singles"`. The server resolves the owner's marker-identified Singles system project even when a user project already owns the `singles` key. Never invent a project or force a choice when the user says the work belongs to no project. ### `songplanner_memory` Live. Consolidated durable memory tool for one song plan. Use `record_decision` for small claim-key facts, `record_correction` for ruled-out approaches, `log_question` for open questions, and `note` for deduped interaction notes. Evidence tiers are, from strongest to weakest: `user_stated`, `user_selected_take`, `logged_generation`, `web_source`, `agent_inference`. A lower-evidence conflicting decision is rejected with a structured conflict payload instead of clobbering higher-evidence memory. An unsupported `evidenceTier` returns structured `invalid_enum` with `allowedValues`. Keep `claimKey` vocabulary small and stable, for example: `public_domain_status`, `lyric_source`, `arrangement_direction`, `target_mood`, `must_keep`, `must_avoid`, `selected_take`, `album_role`. Inputs: `action` (record_decision|record_correction|log_question|note), `planId`, and action-specific fields: decisions use `claimKey`, `valueText`, `evidenceTier`, optional `dependsOnKeys` and `sourceRefs`; corrections use `hypothesisKey`, `disprovingEvidence`, optional `wrongValue` and `lesson`; questions use `question`, optional `cheapestResolution`, `status`, and `answerDecisionId`; notes use `summary`, optional `kind`, `touchedIds`, and `contentHash`, plus guarded `onBehalfOf`. ### `songplanner_save_creation_result` [LEGACY — use songplanner_save] Live. Deprecated legacy tool; prefer `songplanner_save`. Writes the plan + research notes + lyric drafts + prompt attempts + generation results + source checks + a run record in a SINGLE approval, instead of many granular calls. Idempotent on each child's natural key and owner-checked per child, so re-running updates the same records rather than duplicating them. Suno generation is still desktop-only — this LOGS generation results, it never generates audio. Inputs: `projectKey`, the song plan (with optional `planKey`), and optional arrays of research notes, lyric drafts, prompt attempts, generation attempts, source checks, plus an optional run record. Idempotency: `projectKey + planKey` for the plan, and each child's natural key. ### `songplanner_save` Live. Preferred v2 fat save. Writes a plan plus optional decisions, research notes, lyric drafts, prompt attempts, generation results, source checks, and run metadata in one approval. Omitted arrays are untouched. Child writes are idempotent on explicit client keys (`sectionKey`, `draftKey`, `attemptKey`, `checkKey`) or stable array-position fallback keys, so title rephrases update the same row instead of creating duplicates. Inputs: `projectKey`, `plan`, optional `plannerSong`, `decisions[]`, `researchNotes[]`, `lyricDrafts[]`, `promptAttempts[]`, `generationResults[]`, `sourceChecks[]`, optional `mediaIds[]`, optional `agentRun`, and guarded `onBehalfOf`. `mediaIds` (maximum 20) attaches already-committed private media to the saved plan and additionally requires `music.media.write`. Media upload, save attachment, and publication remain separate actions. Use `plannerSong` with a stable `key` to materialize and link the finished title and lyrics as the owner's normal Planner song. Supply its `lyricsMarkdown`, or the save uses the last `lyricDrafts[]` entry from the same call. For a completed external take, set `generationResults[].saveToPlanner:true` and provide its stable `externalId` plus `audioUrl` or `sunoUrl`; Music then records the same take as a normal Planner version. These are workflow saves, not raw catalog CRUD. They never start Suno, spend credits, select a take, publish, or operate an external account. A prompt-only save returns the plan's `queueVersion` and a `generationGate`. When the account autonomy ceiling is `prep`, the gate is `needs_owner_action`: the connected AI must stop before `begin_generation` and before the Suno Create click, present the saved Planner work, and let the owner explicitly approve generation or click Create. `songplanner_read` also returns the plan `queueVersion` so an approved later handoff can re-read rather than guessing version zero. A delegated agent without `credit:spend` gets the same stop with `gateReason:"delegated_without_credit_spend"` and `autonomyCeiling:null`, keeping the delegation restriction distinct from the owner's actual autonomy ceiling. Research entries accept `kind` (`note|document`), `contentClass` (`source_fact|inference|creative_idea|generated_draft`), rich markdown, and source links. Every real change appends a revision attributed to the acting agent principal. Repeating the same keyed payload is idempotent and does not append a duplicate revision. Safeguards: `selected`, `reviewed`, `parked`, and `published` plan statuses are not downgraded. Generation-result re-logs merge metadata, so a partial rating update does not wipe stored duration or structure. Public-domain assertions in this tool (`mode:"exact-public-domain"`, `classification:"text-only"` or `"text-plus"`, or a `public_domain_status` decision claiming public domain) require a checked `sourceCheck` in the same call or already logged on the plan. This is name/url-level source evidence, not a semantic public-domain judgment. Prompt saves never block on direction conflicts. The response adds `directionWarnings[]` with `attemptKey`, matched phrases, and tier (`avoid` or `useSparingly`). Revise hard-tier matches unless the user explicitly requested them; confirm soft-tier matches are deliberate occasional mixes, not fallbacks. Project direction may include `exampleArtistIds[]` pointing to the owner's private `userResearch` artist rows. Cross-owner or non-artist IDs are rejected; these references never expose the private artist through public artist APIs. ### `songplanner_organize` Live. Preferred v2 organization tool. Use `upsert_project`, `move_plan`, `set_album_stage`, owner-only `set_autonomy`, or `delete_project`. Album stage is stored in the typed project `distributionStage` field. `delete_project` is empty-only and refuses a project that still owns plans. Inputs: `action`, project fields (`key`, `name`, `description`, `visibility`, `config`, including optional validated `config.direction`), move fields (`planId` or `fromProjectKey` + `planKey`, `toProjectKey`, `newPlanKey`), `projectKey`, `distributionStage`, and for `set_autonomy`, `ceiling` (prep|generate|distribute) plus `userInstruction`. `set_autonomy` is owner-only and requires the user's explicit verbal instruction to be carried in `userInstruction`; agents must never infer permission from prior behavior. The `distribute` vocabulary value remains unavailable while full-pipeline autonomy is in testing, matching the web setting. `toProjectKey:"singles"` is a legal file-from-inbox destination and resolves the owner's marker-identified Singles project. The same alias supports Singles -> project and project -> Singles moves; `planId` is preferred when already known. Only set project direction when the user asked for it in conversation. Partial direction updates merge with existing direction and never clobber naming, Suno, playlist, or unspecified direction fields. Ceiling guard: setting stage to `submitted` or `released` returns structured `needs_approval` unless the user's autonomy ceiling allows distribution. ### `songplanner_status` Live. Preferred v2 status/provenance tool. Use `agent_status` to confirm the account is connected, read `agentLastActiveAt`, runnableOwners, `deferredNeedingDesktop`, and the effective autonomy ceiling. Use `runs` for recent self-reported `agentRuns`. Inputs: `action` (agent_status|runs), optional `limit`, and guarded `onBehalfOf`. ### `songplanner_media_read` Production pending. Release source can read a small, owner-scoped batch of private media attached to one Music target. Images return only server-generated `tiny`, `medium`, or `large` derivatives whose metadata-clean flag and exact object version are sealed in Convex. Recordings return exact private bytes only when the whole response fits the 20 MiB call ceiling. Every object is read by exact provider version and its SHA-256 and byte size are checked again before disclosure. The response never contains a provider URL, object key, credential, or local path. Inputs: `targetKind` (project|album|song|version|research_note|queue), `targetKey`, 1-10 `mediaIds`, and optional `rendition` (tiny|medium|large; default medium). Requires `music.media.read`; ordinary `music:read` does not imply private-media access. ### `songplanner_media_begin_upload` Production pending. Release source can begin one idempotent private-media operation with 1-50 image or recording lanes. The same `operationId` plus the same canonical intent replays; reuse with different input is refused. Each lane declares an exact lowercase SHA-256, byte size, MIME type, kind, role, and stable lane ID. A lane may also include `captureTime` with exactly one honest `captureTimeSource`: `camera_metadata`, `message_context`, `filesystem_observation`, or `person_entered`. Omit both fields when the time is unknown; Music records `missing` rather than inventing upload time. The response returns a short-lived same-origin `PUT` URL under `https://assistwithmusic.com`; provider hostnames and coordinates never leave the server. Inputs: client-generated `operationId`, `targetKind` (project|album|song|version|research_note|queue|pending), optional `targetKey`, and `lanes[]` containing `laneId`, `kind`, `role`, `expectedSha256`, `expectedByteSize`, and `declaredMimeType`. Requires `music.media.write`. Optional lane fields are `captureTime` (Unix milliseconds) and `captureTimeSource` as described above. ### `songplanner_media_finish_upload` Production pending. Release source can finish 1-50 lanes from `songplanner_media_begin_upload`. A lane with only `laneId` finalizes bytes already sent through the same-origin relay. A client that cannot `PUT` may instead include exactly one `inlineBase64` or `sourceUrl`. Inline payloads are the tiny exception and may not exceed 512 KiB. `fetch_url` accepts only public HTTPS on port 443, resolves every redirect hop, refuses private/reserved/mapped addresses and URL credentials, follows at most two redirects, times out, checks an allowlisted response MIME, and enforces a streaming byte cap. The server reads back the exact stored version, verifies length, SHA-256, magic-byte MIME and image decodability, preserves supplied originals unchanged and private, and creates freshly encoded metadata-clean image derivatives. Capture time is stored only from an explicit approved source; GPS is neither extracted nor stored. Successful finalization returns stable media IDs without attaching or publishing them; pass those IDs to `songplanner_save` or `songplanner_work` action `complete`. Requires `music.media.write`. Inputs: `operationId` and `lanes[]` containing `laneId` plus at most one of `inlineBase64` or `sourceUrl`. ### `songplanner_upsert_album` Live. Create or update an owner-scoped album, idempotent on `key`. An album is a cross-project container: its tracks are the owner's songs whose effective album equals this key, gathered across all projects. `homeProjectKey` is only a navigation hint and must name one of the owner's projects. Responses are allowlisted DTOs with stable `albumId`; raw `data` is never exposed. Inputs: `key`, optional `name`, `description`, `homeProjectKey`, `visibility`, `sunoUrl`, `playlistUrl`, `distrokidUrl`. ### `songplanner_set_album_tracks` Live. Atomically validate and assign an album's ordered cross-project tracklist. Each requested track must name an owner-scoped song and one of that song's owner-scoped selected versions. Songs are not moved between projects. With `replace:true`, absent songs lose only this album membership; with `replace:false`, requested tracks move to the front and existing members retain their relative order. A changed version selection resets release proof that no longer applies. Duplicate tracks, cross-owner records, missing selections, and more than 100 tracks are rejected before any write. Responses return stable `songId` and `versionDocId` values. Inputs: `albumKey`, `tracks` (array of `{songKey, versionId}` in play order), optional `replace`. ### `songplanner_get_album_context` Live. The agent's "what's next" read: an allowlisted album DTO plus up to 100 ordered cross-project tracks, each with stable IDs, selected-version status, portable release proof, the bottleneck, and the next actionable step. `selection_needed` is returned when a selected version is missing or mismatched; release progress is blocked until the selection is repaired. The read enforces a bounded transaction budget and never returns raw `data`. Inputs: `albumKey`. ### `songplanner_update_release_progress` Live. Record release work already completed; the tool never performs the work. Per-track steps use `selected → wav_downloaded → mastered → qc_passed → metadata_ready → staged`. Forward movement is exactly one step, same-step retries are idempotent no-ops, and a backward reset requires `resetReason` and clears evidence invalidated by the reset. Evidence is an allowlisted portable object: `artifactName`, `sha256`, `byteSize`, `integratedLufs`, `truePeakDbtp`, `sourceUrl`, and `externalReference`; local filesystem paths are rejected. Album `artwork` and `metadata` states are `todo|ready`; `distrokid` is `todo|staged|submitted|live`. Each state advances one step at a time. Moving DistroKid to `submitted` or `live` requires `confirmedByUser:true` and a `confirmationNote` after the owner completed or verified the external action. No album-release tool uploads, submits, publishes, purchases, accepts terms, or spends money. All track updates in a call are validated before the release record is written. Inputs: `albumKey`, optional `tracks` (array of `{songKey, step, note?, evidence?, resetReason?}`), optional `album` (`{artwork?, metadata?, distrokid?, resetReason?, confirmedByUser?, confirmationNote?}`). ## Response Caps - Workspace/project context is compact by default. - Arrays are capped and include truncation flags. - Long markdown fields are summarized unless full detail is requested. - Tools return stable IDs, natural keys, timestamps, and next-step guidance. ## Failure Modes To Prevent - stale tool lists after deploy; - approval fatigue from too many granular writes; - duplicate records from missing natural keys; - clients sending owner/user IDs; - agents scraping private web pages instead of using MCP tools; - repeated retries against blocked/gated sources; - claims that public-domain status is certain without a source check; - external account automation without explicit user direction; - offering a contact email address for Assist With Music. There is no inbox and none is planned; every support route is `https://assistwithmusic.com/support`, which continues to the central desk at `https://assistwithlife.com/support`. Never invent, guess, or infer an address, and do not claim from memory whether the desk is currently accepting requests. ## Stale Client Recovery Disconnect and reconnect the connector at `https://assistwithmusic.com/mcp`, complete OAuth again, refresh/list tools, and call `songplanner_context` with `action:"workspace"` before resuming. Re-read `queueVersion` before a Queue mutation and never reuse an operation ID for a different action. Reconnecting does not delete Music records. ## Keeping This In Sync The source of truth for the live tool list is `convex/mcp.ts`. Any agent-facing tool change must update this contract, `public/llms-full.txt`, `app/ai.txt`, and the verify scripts so they stay consistent.