1// The hosted MCP's scopes and tools (hosted-mcp.ts runs them), apart and 2// importing nothing but the tools it shares with the dev hub 3// (shared-tools.json, data), so the site's "Make songs with your AI" page 4// (website/src/pages/ai.astro) lists exactly what the server offers. 5// 6// Scopes. The tab tools are `play`: what a tab has (its sounds, its 7// settings, its console) and what it does (play code or a song, react, 8// ask Jev from the page, charged to your budget as the page's own Jev calls 9// are). Changing the editor's settings is `play` too, not a scope of its 10// own: the settings an MCP may touch are only how the editor looks and 11// types (mcp-settings.mjs refuses the rest), each undone in the settings 12// tab, and an app allowed to replace the editor's code and play sound in 13// the tab is trusted with more than its font. `publish` is anything under 14// your name that others read (songs, comments). Reading the site's 15// reference and songs, and an anonymous vote (anyone may vote signed out), 16// need only a valid token. 17import type { Tool } from './mcp-rpc'; 18import shared from './shared-tools.json'; 19 20export const SCOPES = { 21 play: 'use your open jevstrudel tabs: play code (in the sandbox) or songs, read what they logged, their sounds and settings, change their editor settings, react, and ask Jev there (charged to your daily Jev budget)', 22 publish: 'publish and revise listener songs and comment under your name, and see their status', 23} as const; 24export type Scope = keyof typeof SCOPES; 25const session = { type: 'string', description: 'Which of your tabs (get_status lists them). Optional when only one is open.' }; 26const songFieldsSchema = { 27 title: { type: 'string', description: 'At most 80 characters, one line.' }, 28 description: { type: 'string', description: 'At most 280 characters, one line. Shown on its card.' }, 29 spec: { type: 'string', description: 'What the song is meant to be, in markdown (at most 32 KiB): the prompt, the idea, the form.' }, 30 code: { type: 'string', description: 'The Strudel code, at most 64 KiB. It must make music and nothing else.' }, 31}; 32 33export const TOOLS: (Tool & { scope: Scope | null })[] = [ 34 { 35 name: 'get_status', 36 scope: 'play', 37 description: 38 "List your open jevstrudel tabs (pages open on the site, signed in to your account) and what each is playing. Tabs appear here once they are open; a tab must have been clicked once before it can make sound.", 39 inputSchema: { type: 'object', properties: {} }, 40 }, 41 { 42 name: 'play_code', 43 scope: 'play', 44 description: 45 "Load Strudel code into one of your tabs' editor and play it from the top. It plays in the tab's sandbox, apart from the page and your account, as any code the site did not write does; Jev's calls from it (jev()) count against your daily Jev budget. Returns the evaluation error, if any.", 46 inputSchema: { 47 type: 'object', 48 properties: { code: { type: 'string', description: 'Strudel code, at most 64 KiB' }, session_id: session }, 49 required: ['code'], 50 }, 51 }, 52 { 53 name: 'stop_play', 54 scope: 'play', 55 description: 'Stop playback in one of your tabs.', 56 inputSchema: { type: 'object', properties: { session_id: session } }, 57 }, 58 { 59 name: 'get_logs', 60 scope: 'play', 61 description: 62 "What your tab's sandbox has logged since the last play_code or play_song: Strudel's console (warnings, evaluation and scheduler errors, [jev] choices, sample loads), oldest first, with repeats counted. source \"console\" is the page's console tab instead (its last 40 lines): what a site song playing in the page logged.", 63 inputSchema: { 64 type: 'object', 65 properties: { 66 session_id: session, 67 filter: { type: 'string', description: 'Only lines containing this text, e.g. "warn" or "error".' }, 68 source: { type: 'string', enum: ['sandbox', 'console'], description: 'Default sandbox.' }, 69 }, 70 }, 71 }, 72 { 73 name: 'get_currently_playing_code', 74 scope: 'play', 75 description: "Return the code in one of your tabs' editor.", 76 inputSchema: { type: 'object', properties: { session_id: session } }, 77 }, 78 { 79 name: 'list_songs', 80 scope: null, 81 description: 82 "List the site's own songs (id \"<theme>/<song>\") and listeners' public songs (id \"listener:<id>\"), with their titles and Jev's art score (0 to 1).", 83 inputSchema: { type: 'object', properties: {} }, 84 }, 85 { 86 name: 'get_song', 87 scope: null, 88 description: "One song whole: its spec (what it was made from) and its code. Takes an id from list_songs. Good to learn from before writing your own.", 89 inputSchema: { type: 'object', properties: { id: { type: 'string', description: '"<theme>/<song>" or "listener:<id>"' } }, required: ['id'] }, 90 }, 91 { 92 name: 'publish_song', 93 scope: 'publish', 94 description: 95 "Publish a new listener song under your name. Jev screens it first (spam, abuse, or code that does anything but make music is held); once fine it is public on the site and Jev scores it for art. Returns its id and status. Screening and scoring count against your daily Jev budget.", 96 inputSchema: { type: 'object', properties: songFieldsSchema, required: ['title', 'code'] }, 97 }, 98 { 99 name: 'revise_song', 100 scope: 'publish', 101 description: 'Publish the next revision of one of your own songs (whole: title, description, spec and code). It is screened again before it replaces the public one.', 102 inputSchema: { 103 type: 'object', 104 properties: { id: { type: 'string', description: 'The song, "listener:<id>" (or the bare id)' }, ...songFieldsSchema }, 105 required: ['id', 'title', 'code'], 106 }, 107 }, 108 { 109 name: 'comment', 110 scope: 'publish', 111 description: 'Comment on a song under your name. Jev screens it first, as every comment; returns its status.', 112 inputSchema: { 113 type: 'object', 114 properties: { 115 song: { type: 'string', description: '"<theme>/<song>" or "listener:<id>"' }, 116 body: { type: 'string', description: 'At most 2000 characters.' }, 117 }, 118 required: ['song', 'body'], 119 }, 120 }, 121 { 122 name: 'my_content', 123 scope: 'publish', 124 description: 125 'Everything you have published (song revisions, covers, comments, pitches), each public, held (Jev\'s verdict) or pending (why, and when Jev is asked again).', 126 inputSchema: { type: 'object', properties: {} }, 127 }, 128 ...(shared as (Tool & { scope: Scope | null })[]), 129];