|
| 1 | +import type { SvelteMcp } from '../../index.js'; |
| 2 | +import * as v from 'valibot'; |
| 3 | + |
| 4 | +async function compress_and_encode_text(input: string) { |
| 5 | + const reader = new Blob([input]).stream().pipeThrough(new CompressionStream('gzip')).getReader(); |
| 6 | + let buffer = ''; |
| 7 | + for (;;) { |
| 8 | + const { done, value } = await reader.read(); |
| 9 | + if (done) { |
| 10 | + reader.releaseLock(); |
| 11 | + // Some sites like discord don't like it when links end with = |
| 12 | + return btoa(buffer).replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, ''); |
| 13 | + } else { |
| 14 | + for (let i = 0; i < value.length; i++) { |
| 15 | + // decoding as utf-8 will make btoa reject the string |
| 16 | + buffer += String.fromCharCode(value[i]); |
| 17 | + } |
| 18 | + } |
| 19 | + } |
| 20 | +} |
| 21 | + |
| 22 | +type File = { |
| 23 | + type: 'file'; |
| 24 | + name: string; |
| 25 | + basename: string; |
| 26 | + contents: string; |
| 27 | + text: boolean; |
| 28 | +}; |
| 29 | + |
| 30 | +export function playground_link(server: SvelteMcp) { |
| 31 | + server.tool( |
| 32 | + { |
| 33 | + name: 'playground-link', |
| 34 | + description: |
| 35 | + 'Generates a Playground link given a Svelte code snippet. Call this tool once you have the final version of the code you want to send to the user to allow it to quickly check the code in the playground. NEVER use this tool if you have already created a file for the component. The playground accept multiple files so if are importing from other files just include them all at the root level.', |
| 36 | + schema: v.object({ |
| 37 | + name: v.pipe( |
| 38 | + v.string(), |
| 39 | + v.description('The name of the Playground, it should reflect the user task'), |
| 40 | + ), |
| 41 | + tailwind: v.pipe( |
| 42 | + v.string(), |
| 43 | + v.description( |
| 44 | + "If the code requires Tailwind CSS to work...only send true if it it's using tailwind classes in the code", |
| 45 | + ), |
| 46 | + ), |
| 47 | + files: v.pipe( |
| 48 | + v.record(v.string(), v.string()), |
| 49 | + v.description( |
| 50 | + "An object where all the keys are the filenames (with extensions) and the values are the file content. For example: { 'Component.svelte': '<script>...</script>', 'utils.js': 'export function ...' }. The playground accept multiple files so if are importing from other files just include them all at the root level.", |
| 51 | + ), |
| 52 | + ), |
| 53 | + }), |
| 54 | + outputSchema: v.object({ |
| 55 | + url: v.string(), |
| 56 | + }), |
| 57 | + }, |
| 58 | + async ({ files, name, tailwind }) => { |
| 59 | + const playground_base = new URL('https://svelte.dev/playground'); |
| 60 | + const playground_files: File[] = []; |
| 61 | + |
| 62 | + let has_app_svelte = false; |
| 63 | + |
| 64 | + for (const [filename, contents] of Object.entries(files)) { |
| 65 | + if (filename === 'App.svelte') has_app_svelte = true; |
| 66 | + playground_files.push({ |
| 67 | + type: 'file', |
| 68 | + name: filename, |
| 69 | + basename: filename.replace(/^.*[\\/]/, ''), |
| 70 | + contents, |
| 71 | + text: true, |
| 72 | + }); |
| 73 | + } |
| 74 | + |
| 75 | + if (!has_app_svelte) { |
| 76 | + return { |
| 77 | + isError: true, |
| 78 | + content: [ |
| 79 | + { |
| 80 | + type: 'text', |
| 81 | + text: JSON.stringify({ |
| 82 | + error: 'The files must contain an App.svelte file as the entry point', |
| 83 | + }), |
| 84 | + }, |
| 85 | + ], |
| 86 | + }; |
| 87 | + } |
| 88 | + |
| 89 | + const playground_config = { |
| 90 | + name, |
| 91 | + tailwind: tailwind ?? false, |
| 92 | + files: playground_files, |
| 93 | + }; |
| 94 | + |
| 95 | + playground_base.hash = await compress_and_encode_text(JSON.stringify(playground_config)); |
| 96 | + |
| 97 | + const content = { |
| 98 | + url: playground_base.toString(), |
| 99 | + }; |
| 100 | + |
| 101 | + return { |
| 102 | + content: [ |
| 103 | + { |
| 104 | + type: 'text', |
| 105 | + text: JSON.stringify(content), |
| 106 | + }, |
| 107 | + ], |
| 108 | + structuredContent: content, |
| 109 | + }; |
| 110 | + }, |
| 111 | + ); |
| 112 | +} |
0 commit comments