The hosted MCP's scopes and tools (hosted-mcp.ts runs them), apart and importing nothing but the tools it shares with the dev hub (shared-tools.json, data), so the site's "Make songs with your AI" page (website/src/pages/ai.astro) lists exactly what the server offers.
Scopes. The tab tools are play: what a tab has (its sounds, its
settings, its console) and what it does (play code or a song, react,
ask Jev from the page, charged to your budget as the page's own Jev calls
are). Changing the editor's settings is play too, not a scope of its
own: the settings an MCP may touch are only how the editor looks and
types (mcp-settings.mjs refuses the rest), each undone in the settings
tab, and an app allowed to replace the editor's code and play sound in
the tab is trusted with more than its font. publish is anything under
your name that others read (songs, comments). Reading the site's
reference and songs, and an anonymous vote (anyone may vote signed out),
need only a valid token.
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];