From 8d53f56151c16e55499768d47d9cf91ebe574f15 Mon Sep 17 00:00:00 2001 From: Stanislav Khromov Date: Fri, 19 Sep 2025 00:55:36 +0200 Subject: [PATCH 01/11] add tmcp docs --- .cocoignore | 2 + docs/tmcp.md | 320 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 322 insertions(+) create mode 100644 .cocoignore create mode 100644 docs/tmcp.md diff --git a/.cocoignore b/.cocoignore new file mode 100644 index 0000000..e28784b --- /dev/null +++ b/.cocoignore @@ -0,0 +1,2 @@ +.claude +.github \ No newline at end of file diff --git a/docs/tmcp.md b/docs/tmcp.md new file mode 100644 index 0000000..5be172b --- /dev/null +++ b/docs/tmcp.md @@ -0,0 +1,320 @@ +> [!WARNING] +> Unfortunately i published the 1.0 by mistake...this package is currently under heavy development so there will be breaking changes in minors...threat this `1.x` as the `0.x` of any other package. Sorry for the disservice, every breaking will be properly labeled in the PR name. + +# tmcp + +A lightweight, schema-agnostic Model Context Protocol (MCP) server implementation with unified API design. + +## Why tmcp? + +tmcp offers significant advantages over the official MCP SDK: + +- **🔄 Schema Agnostic**: Works with any validation library through adapters +- **📦 No Weird Dependencies**: Minimal footprint with only essential dependencies (looking at you `express`) +- **🎯 Unified API**: Consistent, intuitive interface across all MCP capabilities +- **🔌 Extensible**: Easy to add support for new schema libraries +- **⚡ Lightweight**: No bloat, just what you need + +## Supported Schema Libraries + +tmcp works with all major schema validation libraries through its adapter system: + +- **Zod** - `@tmcp/adapter-zod` +- **Valibot** - `@tmcp/adapter-valibot` +- **ArkType** - `@tmcp/adapter-arktype` +- **Effect Schema** - `@tmcp/adapter-effect` +- **Zod v3** - `@tmcp/adapter-zod-v3` + +## Installation + +```bash +pnpm install tmcp +# Choose your preferred schema library adapter +pnpm install @tmcp/adapter-zod zod +# Choose your preferred transport +pnpm install @tmcp/transport-stdio # For CLI/desktop apps +pnpm install @tmcp/transport-http # For web-based clients +``` + +## Quick Start + +### Standard I/O Transport (CLI/Desktop) + +```javascript +import { McpServer } from 'tmcp'; +import { ZodJsonSchemaAdapter } from '@tmcp/adapter-zod'; +import { StdioTransport } from '@tmcp/transport-stdio'; +import { z } from 'zod'; + +const adapter = new ZodJsonSchemaAdapter(); +const server = new McpServer( + { + name: 'my-server', + version: '1.0.0', + description: 'My awesome MCP server', + }, + { + adapter, + capabilities: { + tools: { listChanged: true }, + prompts: { listChanged: true }, + resources: { listChanged: true }, + }, + }, +); + +// Define a tool with type-safe schema +server.tool( + { + name: 'calculate', + description: 'Perform mathematical calculations', + schema: z.object({ + operation: z.enum(['add', 'subtract', 'multiply', 'divide']), + a: z.number(), + b: z.number(), + }), + }, + async ({ operation, a, b }) => { + switch (operation) { + case 'add': + return a + b; + case 'subtract': + return a - b; + case 'multiply': + return a * b; + case 'divide': + return a / b; + } + }, +); + +// Start the server with stdio transport +const transport = new StdioTransport(server); +transport.listen(); +``` + +### HTTP Transport (Web-based) + +```javascript +import { McpServer } from 'tmcp'; +import { ZodJsonSchemaAdapter } from '@tmcp/adapter-zod'; +import { HttpTransport } from '@tmcp/transport-http'; +import { z } from 'zod'; + +const adapter = new ZodJsonSchemaAdapter(); +const server = new McpServer(/* ... same server config ... */); + +// Add tools as above... + +// Create HTTP transport +const transport = new HttpTransport(server); + +// Use with your preferred HTTP server (Bun example) +Bun.serve({ + port: 3000, + async fetch(req) { + const response = await transport.respond(req); + if (response === null) { + return new Response('Not Found', { status: 404 }); + } + return response; + }, +}); +``` + +## API Reference + +### McpServer + +The main server class that handles MCP protocol communications. + +#### Constructor + +```javascript +new McpServer(serverInfo, options); +``` + +- `serverInfo`: Server metadata (name, version, description) +- `options`: Configuration object with adapter and capabilities + +#### Methods + +##### `tool(definition, handler)` + +Register a tool with optional schema validation. + +```javascript +server.tool( + { + name: 'tool-name', + description: 'Tool description', + schema: yourSchema, // optional + }, + async (input) => { + // Tool implementation + return result; + }, +); +``` + +##### `prompt(definition, handler)` + +Register a prompt template with optional schema validation. + +```javascript +server.prompt( + { + name: 'prompt-name', + description: 'Prompt description', + schema: yourSchema, // optional + complete: (arg, context) => ['completion1', 'completion2'] // optional + }, + async (input) => { + // Prompt implementation + return { messages: [...] }; + } +); +``` + +##### `resource(definition, handler)` + +Register a static resource. + +```javascript +server.resource( + { + name: 'resource-name', + description: 'Resource description', + uri: 'file://path/to/resource' + }, + async (uri, params) => { + // Resource implementation + return { contents: [...] }; + } +); +``` + +##### `template(definition, handler)` + +Register a URI template for dynamic resources. + +```javascript +server.template( + { + name: 'template-name', + description: 'Template description', + uri: 'file://path/{id}/resource', + complete: (arg, context) => ['id1', 'id2'] // optional + }, + async (uri, params) => { + // Template implementation using params.id + return { contents: [...] }; + } +); +``` + +##### `receive(request)` + +Process an incoming MCP request. + +```javascript +const response = server.receive(jsonRpcRequest); +``` + +## Advanced Examples + +### Multiple Schema Libraries + +```javascript +// Use different schemas for different tools +import { z } from 'zod'; +import * as v from 'valibot'; + +server.tool( + { + name: 'zod-tool', + schema: z.object({ name: z.string() }), + }, + async ({ name }) => `Hello ${name}`, +); + +server.tool( + { + name: 'valibot-tool', + schema: v.object({ age: v.number() }), + }, + async ({ age }) => `Age: ${age}`, +); +``` + +### Resource Templates with Completion + +```javascript +server.template( + { + name: 'user-profile', + description: 'Get user profile by ID', + uri: 'users/{userId}/profile', + complete: (arg, context) => { + // Provide completions for userId parameter + return ['user1', 'user2', 'user3']; + }, + }, + async (uri, params) => { + const user = await getUserById(params.userId); + return { + contents: [ + { + uri, + mimeType: 'application/json', + text: JSON.stringify(user), + }, + ], + }; + }, +); +``` + +### Complex Validation + +```javascript +const complexSchema = z.object({ + user: z.object({ + name: z.string().min(1), + email: z.string().email(), + age: z.number().min(18).max(120), + }), + preferences: z + .object({ + theme: z.enum(['light', 'dark']), + notifications: z.boolean(), + }) + .optional(), + tags: z.array(z.string()).default([]), +}); + +server.tool( + { + name: 'create-user', + description: 'Create a new user with preferences', + schema: complexSchema, + }, + async (input) => { + // Input is fully typed and validated + const { user, preferences, tags } = input; + return await createUser(user, preferences, tags); + }, +); +``` + +## Contributing + +Contributions are welcome! Please see our [contributing guidelines](../../CONTRIBUTING.md) for details. + +## Acknowledgments + +Huge thanks to Sean O'Bannon that provided us with the `@tmcp` scope on npm. + +## License + +MIT © Paolo Ricciuti From 26b39867402a0e0a355de82390543ef9fceddff2 Mon Sep 17 00:00:00 2001 From: Stanislav Khromov Date: Fri, 19 Sep 2025 01:01:46 +0200 Subject: [PATCH 02/11] wip --- src/lib/mcp/index.ts | 59 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/src/lib/mcp/index.ts b/src/lib/mcp/index.ts index 519726b..7fa97d2 100644 --- a/src/lib/mcp/index.ts +++ b/src/lib/mcp/index.ts @@ -89,6 +89,65 @@ server.tool( }, ); +// Handler functions +const listSectionsHandler = async (): Promise<{ + content: Array<{ type: string; text: string }>; +}> => { + return { + content: [ + { + type: 'text', + text: 'tool list_sections called', + }, + ], + }; +}; + +const getDocumentationHandler = async ({ + section, +}: { + section: string | string[]; +}): Promise<{ + content: Array<{ type: string; text: string }>; +}> => { + return { + content: [ + { + type: 'text', + text: 'tool get_documentation called', + }, + ], + }; +}; + +// List sections tool +server.tool( + { + name: 'list_sections', + description: + 'Lists all available Svelte 5 and SvelteKit documentation sections in a structured format. Returns sections as a list of "* title: [section_title], path: [file_path]" - you can use either the title or path when querying a specific section via the get_documentation tool. Always run list_sections first for any query related to Svelte development to discover available content.', + }, + listSectionsHandler, +); + +// Get documentation tool +server.tool( + { + name: 'get_documentation', + description: + 'Retrieves full documentation content for Svelte 5 or SvelteKit sections. Supports flexible search by title (e.g., "$state", "routing") or file path (e.g., "docs/svelte/state.md"). Can accept a single section name or an array of sections. Before running this, make sure to analyze the users query, as well as the output from list_sections (which should be called first). Then ask for ALL relevant sections the user might require. For example, if the user asks to build anything interactive, you will need to fetch all relevant runes, and so on.', + schema: v.object({ + section: v.pipe( + v.union([v.string(), v.array(v.string())]), + v.description( + 'The section name(s) to retrieve. Can search by title (e.g., "$state", "load functions") or file path (e.g., "docs/svelte/state.md"). Supports single string and array of strings', + ), + ), + }), + }, + getDocumentationHandler, +); + export const http_transport = new HttpTransport(server, { cors: true, }); From da995bdc69b805bf0f4a49b46597d438d3be1bd8 Mon Sep 17 00:00:00 2001 From: Stanislav Khromov Date: Fri, 19 Sep 2025 01:05:19 +0200 Subject: [PATCH 03/11] Update index.ts --- src/lib/mcp/index.ts | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/src/lib/mcp/index.ts b/src/lib/mcp/index.ts index 7fa97d2..1909796 100644 --- a/src/lib/mcp/index.ts +++ b/src/lib/mcp/index.ts @@ -106,15 +106,49 @@ const listSectionsHandler = async (): Promise<{ const getDocumentationHandler = async ({ section, }: { - section: string | string[]; + section: string | string[] | unknown; }): Promise<{ content: Array<{ type: string; text: string }>; }> => { + // Parse sections with error checking + let sections: string[]; + + if (Array.isArray(section)) { + sections = section.filter((s): s is string => typeof s === 'string'); + } else if ( + typeof section === 'string' && + section.trim().startsWith('[') && + section.trim().endsWith(']') + ) { + // Try to parse JSON string array + try { + const parsed = JSON.parse(section); + if (Array.isArray(parsed)) { + sections = parsed.filter((s): s is string => typeof s === 'string'); + } else { + sections = [section]; + } + } catch (parseError) { + // JSON parse failed, treat as single string + sections = [section]; + } + } else if (typeof section === 'string') { + sections = [section]; + } else { + // Fallback for unexpected input + sections = []; + } + + // Return formatted message + const sectionsList = sections.length > 0 + ? sections.join(', ') + : 'no sections'; + return { content: [ { type: 'text', - text: 'tool get_documentation called', + text: `called for sections: ${sectionsList}`, }, ], }; From 830fd73ab1886ab64891cbac6dca7032e2016a58 Mon Sep 17 00:00:00 2001 From: Stanislav Khromov Date: Fri, 19 Sep 2025 01:06:53 +0200 Subject: [PATCH 04/11] move handlers to separate directory --- .../mcp/handlers/getDocumentationHandler.ts | 50 +++++++++++++++++++ src/lib/mcp/handlers/listSectionsHandler.ts | 12 +++++ 2 files changed, 62 insertions(+) create mode 100644 src/lib/mcp/handlers/getDocumentationHandler.ts create mode 100644 src/lib/mcp/handlers/listSectionsHandler.ts diff --git a/src/lib/mcp/handlers/getDocumentationHandler.ts b/src/lib/mcp/handlers/getDocumentationHandler.ts new file mode 100644 index 0000000..57b0084 --- /dev/null +++ b/src/lib/mcp/handlers/getDocumentationHandler.ts @@ -0,0 +1,50 @@ +export const getDocumentationHandler = async ({ + section, +}: { + section: string | string[] | unknown; +}): Promise<{ + content: Array<{ type: string; text: string }>; +}> => { + // Parse sections with error checking + let sections: string[]; + + if (Array.isArray(section)) { + sections = section.filter((s): s is string => typeof s === 'string'); + } else if ( + typeof section === 'string' && + section.trim().startsWith('[') && + section.trim().endsWith(']') + ) { + // Try to parse JSON string array + try { + const parsed = JSON.parse(section); + if (Array.isArray(parsed)) { + sections = parsed.filter((s): s is string => typeof s === 'string'); + } else { + sections = [section]; + } + } catch (parseError) { + // JSON parse failed, treat as single string + sections = [section]; + } + } else if (typeof section === 'string') { + sections = [section]; + } else { + // Fallback for unexpected input + sections = []; + } + + // Return formatted message + const sectionsList = sections.length > 0 + ? sections.join(', ') + : 'no sections'; + + return { + content: [ + { + type: 'text', + text: `called for sections: ${sectionsList}`, + }, + ], + }; +}; diff --git a/src/lib/mcp/handlers/listSectionsHandler.ts b/src/lib/mcp/handlers/listSectionsHandler.ts new file mode 100644 index 0000000..0628961 --- /dev/null +++ b/src/lib/mcp/handlers/listSectionsHandler.ts @@ -0,0 +1,12 @@ +export const listSectionsHandler = async (): Promise<{ + content: Array<{ type: string; text: string }>; +}> => { + return { + content: [ + { + type: 'text', + text: 'tool list_sections called', + }, + ], + }; +}; From 86675ea1d7d62cedc00223258dbb176201ecba8b Mon Sep 17 00:00:00 2001 From: Stanislav Khromov Date: Fri, 19 Sep 2025 01:08:03 +0200 Subject: [PATCH 05/11] Update index.ts --- src/lib/mcp/index.ts | 67 ++------------------------------------------ 1 file changed, 2 insertions(+), 65 deletions(-) diff --git a/src/lib/mcp/index.ts b/src/lib/mcp/index.ts index 1909796..a4af68f 100644 --- a/src/lib/mcp/index.ts +++ b/src/lib/mcp/index.ts @@ -6,6 +6,8 @@ import * as v from 'valibot'; import { add_autofixers_issues } from './autofixers/add-autofixers-issues.js'; import { add_compile_issues } from './autofixers/add-compile-issues.js'; import { add_eslint_issues } from './autofixers/add-eslint-issues.js'; +import { listSectionsHandler } from './handlers/listSectionsHandler.js'; +import { getDocumentationHandler } from './handlers/getDocumentationHandler.js'; const server = new McpServer( { @@ -89,71 +91,6 @@ server.tool( }, ); -// Handler functions -const listSectionsHandler = async (): Promise<{ - content: Array<{ type: string; text: string }>; -}> => { - return { - content: [ - { - type: 'text', - text: 'tool list_sections called', - }, - ], - }; -}; - -const getDocumentationHandler = async ({ - section, -}: { - section: string | string[] | unknown; -}): Promise<{ - content: Array<{ type: string; text: string }>; -}> => { - // Parse sections with error checking - let sections: string[]; - - if (Array.isArray(section)) { - sections = section.filter((s): s is string => typeof s === 'string'); - } else if ( - typeof section === 'string' && - section.trim().startsWith('[') && - section.trim().endsWith(']') - ) { - // Try to parse JSON string array - try { - const parsed = JSON.parse(section); - if (Array.isArray(parsed)) { - sections = parsed.filter((s): s is string => typeof s === 'string'); - } else { - sections = [section]; - } - } catch (parseError) { - // JSON parse failed, treat as single string - sections = [section]; - } - } else if (typeof section === 'string') { - sections = [section]; - } else { - // Fallback for unexpected input - sections = []; - } - - // Return formatted message - const sectionsList = sections.length > 0 - ? sections.join(', ') - : 'no sections'; - - return { - content: [ - { - type: 'text', - text: `called for sections: ${sectionsList}`, - }, - ], - }; -}; - // List sections tool server.tool( { From 74d2fb8f0e8d80cbf9cd2def11aeec17e9aa0f7f Mon Sep 17 00:00:00 2001 From: Stanislav Khromov Date: Fri, 19 Sep 2025 01:15:58 +0200 Subject: [PATCH 06/11] Update getDocumentationHandler.ts --- src/lib/mcp/handlers/getDocumentationHandler.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/lib/mcp/handlers/getDocumentationHandler.ts b/src/lib/mcp/handlers/getDocumentationHandler.ts index 57b0084..9bf5e5b 100644 --- a/src/lib/mcp/handlers/getDocumentationHandler.ts +++ b/src/lib/mcp/handlers/getDocumentationHandler.ts @@ -5,7 +5,6 @@ export const getDocumentationHandler = async ({ }): Promise<{ content: Array<{ type: string; text: string }>; }> => { - // Parse sections with error checking let sections: string[]; if (Array.isArray(section)) { @@ -15,7 +14,6 @@ export const getDocumentationHandler = async ({ section.trim().startsWith('[') && section.trim().endsWith(']') ) { - // Try to parse JSON string array try { const parsed = JSON.parse(section); if (Array.isArray(parsed)) { @@ -24,17 +22,14 @@ export const getDocumentationHandler = async ({ sections = [section]; } } catch (parseError) { - // JSON parse failed, treat as single string sections = [section]; } } else if (typeof section === 'string') { sections = [section]; } else { - // Fallback for unexpected input sections = []; } - // Return formatted message const sectionsList = sections.length > 0 ? sections.join(', ') : 'no sections'; From 5d50518c3c88619f2d81be2a5d2cf3a9ce61acdf Mon Sep 17 00:00:00 2001 From: Stanislav Khromov Date: Fri, 19 Sep 2025 01:17:02 +0200 Subject: [PATCH 07/11] types --- src/lib/mcp/handlers/getDocumentationHandler.ts | 4 ++-- src/lib/mcp/handlers/listSectionsHandler.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/mcp/handlers/getDocumentationHandler.ts b/src/lib/mcp/handlers/getDocumentationHandler.ts index 9bf5e5b..a796649 100644 --- a/src/lib/mcp/handlers/getDocumentationHandler.ts +++ b/src/lib/mcp/handlers/getDocumentationHandler.ts @@ -1,9 +1,9 @@ export const getDocumentationHandler = async ({ section, }: { - section: string | string[] | unknown; + section: string | string[]; }): Promise<{ - content: Array<{ type: string; text: string }>; + content: Array<{ type: 'text'; text: string }>; }> => { let sections: string[]; diff --git a/src/lib/mcp/handlers/listSectionsHandler.ts b/src/lib/mcp/handlers/listSectionsHandler.ts index 0628961..d3aff48 100644 --- a/src/lib/mcp/handlers/listSectionsHandler.ts +++ b/src/lib/mcp/handlers/listSectionsHandler.ts @@ -1,5 +1,5 @@ export const listSectionsHandler = async (): Promise<{ - content: Array<{ type: string; text: string }>; + content: Array<{ type: 'text'; text: string }>; }> => { return { content: [ From dedfd0b3b77d057c87fa8f09a002f212653b25d7 Mon Sep 17 00:00:00 2001 From: Stanislav Khromov Date: Fri, 19 Sep 2025 01:20:04 +0200 Subject: [PATCH 08/11] Move auto fixer handler --- .../mcp/handlers/getDocumentationHandler.ts | 6 +-- .../mcp/handlers/svelteAutofixerHandler.ts | 53 +++++++++++++++++++ src/lib/mcp/index.ts | 38 +------------ 3 files changed, 57 insertions(+), 40 deletions(-) create mode 100644 src/lib/mcp/handlers/svelteAutofixerHandler.ts diff --git a/src/lib/mcp/handlers/getDocumentationHandler.ts b/src/lib/mcp/handlers/getDocumentationHandler.ts index a796649..2e524f8 100644 --- a/src/lib/mcp/handlers/getDocumentationHandler.ts +++ b/src/lib/mcp/handlers/getDocumentationHandler.ts @@ -6,7 +6,7 @@ export const getDocumentationHandler = async ({ content: Array<{ type: 'text'; text: string }>; }> => { let sections: string[]; - + if (Array.isArray(section)) { sections = section.filter((s): s is string => typeof s === 'string'); } else if ( @@ -30,9 +30,7 @@ export const getDocumentationHandler = async ({ sections = []; } - const sectionsList = sections.length > 0 - ? sections.join(', ') - : 'no sections'; + const sectionsList = sections.length > 0 ? sections.join(', ') : 'no sections'; return { content: [ diff --git a/src/lib/mcp/handlers/svelteAutofixerHandler.ts b/src/lib/mcp/handlers/svelteAutofixerHandler.ts new file mode 100644 index 0000000..1698325 --- /dev/null +++ b/src/lib/mcp/handlers/svelteAutofixerHandler.ts @@ -0,0 +1,53 @@ +import { add_autofixers_issues } from '../autofixers/add-autofixers-issues.js'; +import { add_compile_issues } from '../autofixers/add-compile-issues.js'; +import { add_eslint_issues } from '../autofixers/add-eslint-issues.js'; + +export const svelteAutofixerHandler = async ({ + code, + filename, + desired_svelte_version, +}: { + code: string; + filename?: string; + desired_svelte_version: 4 | 5; +}): Promise<{ + content: Array<{ type: 'text'; text: string }>; + structuredContent: { + issues: string[]; + suggestions: string[]; + require_another_tool_call_after_fixing: boolean; + }; +}> => { + const content: { + issues: string[]; + suggestions: string[]; + require_another_tool_call_after_fixing: boolean; + } = { issues: [], suggestions: [], require_another_tool_call_after_fixing: false }; + + try { + add_compile_issues(content, code, desired_svelte_version, filename); + + add_autofixers_issues(content, code, desired_svelte_version, filename); + + await add_eslint_issues(content, code, desired_svelte_version, filename); + } catch (e: unknown) { + const error = e as Error & { start?: { line: number; column: number } }; + content.issues.push( + `${error.message} at line ${error.start?.line}, column ${error.start?.column}`, + ); + } + + if (content.issues.length > 0 || content.suggestions.length > 0) { + content.require_another_tool_call_after_fixing = true; + } + + return { + content: [ + { + type: 'text', + text: JSON.stringify(content), + }, + ], + structuredContent: content, + }; +}; diff --git a/src/lib/mcp/index.ts b/src/lib/mcp/index.ts index a4af68f..626c98b 100644 --- a/src/lib/mcp/index.ts +++ b/src/lib/mcp/index.ts @@ -3,11 +3,9 @@ import { HttpTransport } from '@tmcp/transport-http'; import { StdioTransport } from '@tmcp/transport-stdio'; import { McpServer } from 'tmcp'; import * as v from 'valibot'; -import { add_autofixers_issues } from './autofixers/add-autofixers-issues.js'; -import { add_compile_issues } from './autofixers/add-compile-issues.js'; -import { add_eslint_issues } from './autofixers/add-eslint-issues.js'; import { listSectionsHandler } from './handlers/listSectionsHandler.js'; import { getDocumentationHandler } from './handlers/getDocumentationHandler.js'; +import { svelteAutofixerHandler } from './handlers/svelteAutofixerHandler.js'; const server = new McpServer( { @@ -56,39 +54,7 @@ server.tool( openWorldHint: false, }, }, - async ({ code, filename, desired_svelte_version }) => { - const content: { - issues: string[]; - suggestions: string[]; - require_another_tool_call_after_fixing: boolean; - } = { issues: [], suggestions: [], require_another_tool_call_after_fixing: false }; - try { - add_compile_issues(content, code, desired_svelte_version, filename); - - add_autofixers_issues(content, code, desired_svelte_version, filename); - - await add_eslint_issues(content, code, desired_svelte_version, filename); - } catch (e: unknown) { - const error = e as Error & { start?: { line: number; column: number } }; - content.issues.push( - `${error.message} at line ${error.start?.line}, column ${error.start?.column}`, - ); - } - - if (content.issues.length > 0 || content.suggestions.length > 0) { - content.require_another_tool_call_after_fixing = true; - } - - return { - content: [ - { - type: 'text', - text: JSON.stringify(content), - }, - ], - structuredContent: content, - }; - }, + svelteAutofixerHandler, ); // List sections tool From bf1a4178bfae5b11a1595215908f3c542e724340 Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Sun, 21 Sep 2025 14:49:44 +0200 Subject: [PATCH 09/11] fix: move handler in their own folder while keeping TS --- .../mcp/handlers/getDocumentationHandler.ts | 43 ----- src/lib/mcp/handlers/index.ts | 22 +++ src/lib/mcp/handlers/listSectionsHandler.ts | 12 -- src/lib/mcp/handlers/prompts/index.ts | 1 + src/lib/mcp/handlers/prompts/svelte-task.ts | 44 +++++ src/lib/mcp/handlers/resources/index.ts | 1 + .../mcp/handlers/resources/list-sections.ts | 24 +++ .../mcp/handlers/svelteAutofixerHandler.ts | 53 ------ .../mcp/handlers/tools/get-documentation.ts | 57 +++++++ src/lib/mcp/handlers/tools/index.ts | 3 + src/lib/mcp/handlers/tools/list-sections.ts | 21 +++ .../mcp/handlers/tools/svelte-autofixer.ts | 81 ++++++++++ src/lib/mcp/index.ts | 152 +----------------- 13 files changed, 259 insertions(+), 255 deletions(-) delete mode 100644 src/lib/mcp/handlers/getDocumentationHandler.ts create mode 100644 src/lib/mcp/handlers/index.ts delete mode 100644 src/lib/mcp/handlers/listSectionsHandler.ts create mode 100644 src/lib/mcp/handlers/prompts/index.ts create mode 100644 src/lib/mcp/handlers/prompts/svelte-task.ts create mode 100644 src/lib/mcp/handlers/resources/index.ts create mode 100644 src/lib/mcp/handlers/resources/list-sections.ts delete mode 100644 src/lib/mcp/handlers/svelteAutofixerHandler.ts create mode 100644 src/lib/mcp/handlers/tools/get-documentation.ts create mode 100644 src/lib/mcp/handlers/tools/index.ts create mode 100644 src/lib/mcp/handlers/tools/list-sections.ts create mode 100644 src/lib/mcp/handlers/tools/svelte-autofixer.ts diff --git a/src/lib/mcp/handlers/getDocumentationHandler.ts b/src/lib/mcp/handlers/getDocumentationHandler.ts deleted file mode 100644 index 2e524f8..0000000 --- a/src/lib/mcp/handlers/getDocumentationHandler.ts +++ /dev/null @@ -1,43 +0,0 @@ -export const getDocumentationHandler = async ({ - section, -}: { - section: string | string[]; -}): Promise<{ - content: Array<{ type: 'text'; text: string }>; -}> => { - let sections: string[]; - - if (Array.isArray(section)) { - sections = section.filter((s): s is string => typeof s === 'string'); - } else if ( - typeof section === 'string' && - section.trim().startsWith('[') && - section.trim().endsWith(']') - ) { - try { - const parsed = JSON.parse(section); - if (Array.isArray(parsed)) { - sections = parsed.filter((s): s is string => typeof s === 'string'); - } else { - sections = [section]; - } - } catch (parseError) { - sections = [section]; - } - } else if (typeof section === 'string') { - sections = [section]; - } else { - sections = []; - } - - const sectionsList = sections.length > 0 ? sections.join(', ') : 'no sections'; - - return { - content: [ - { - type: 'text', - text: `called for sections: ${sectionsList}`, - }, - ], - }; -}; diff --git a/src/lib/mcp/handlers/index.ts b/src/lib/mcp/handlers/index.ts new file mode 100644 index 0000000..6494ca4 --- /dev/null +++ b/src/lib/mcp/handlers/index.ts @@ -0,0 +1,22 @@ +import type { SvelteMcp } from '../index.js'; +import * as prompts from './prompts/index.js'; +import * as tools from './tools/index.js'; +import * as resources from './resources/index.js'; + +export function setup_tools(server: SvelteMcp) { + for (const tool in tools) { + tools[tool as keyof typeof tools](server); + } +} + +export function setup_prompts(server: SvelteMcp) { + for (const prompt in prompts) { + prompts[prompt as keyof typeof prompts](server); + } +} + +export function setup_resources(server: SvelteMcp) { + for (const resource in resources) { + resources[resource as keyof typeof resources](server); + } +} diff --git a/src/lib/mcp/handlers/listSectionsHandler.ts b/src/lib/mcp/handlers/listSectionsHandler.ts deleted file mode 100644 index d3aff48..0000000 --- a/src/lib/mcp/handlers/listSectionsHandler.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const listSectionsHandler = async (): Promise<{ - content: Array<{ type: 'text'; text: string }>; -}> => { - return { - content: [ - { - type: 'text', - text: 'tool list_sections called', - }, - ], - }; -}; diff --git a/src/lib/mcp/handlers/prompts/index.ts b/src/lib/mcp/handlers/prompts/index.ts new file mode 100644 index 0000000..8c16a8b --- /dev/null +++ b/src/lib/mcp/handlers/prompts/index.ts @@ -0,0 +1 @@ +export * from './svelte-task.js'; diff --git a/src/lib/mcp/handlers/prompts/svelte-task.ts b/src/lib/mcp/handlers/prompts/svelte-task.ts new file mode 100644 index 0000000..2c7d9f4 --- /dev/null +++ b/src/lib/mcp/handlers/prompts/svelte-task.ts @@ -0,0 +1,44 @@ +import type { SvelteMcp } from '../../index.js'; +import * as v from 'valibot'; + +export function setup_svelte_task(server: SvelteMcp) { + server.prompt( + { + name: 'svelte-task-prompt', + title: 'Svelte Task Prompt', + description: + 'Use this Prompt to ask for any svelte related task. It will automatically instruct the LLM on how to best use the autofixer and how to query for documentation pages.', + schema: v.object({ + task: v.pipe(v.string(), v.description('The task to be performed')), + }), + }, + async ({ task }) => { + // TODO: implement logic to fetch the available docs paths to return in the prompt + const available_docs: string[] = []; + + return { + messages: [ + { + role: 'user', + content: { + type: 'text', + text: `You are a Svelte expert tasked to build components and utilities for Svelte developers. If you need documentation for anything related to Svelte you can invoke the tool \`get_documentation\` with one of the following paths: + +${JSON.stringify(available_docs, null, 2)} + + +Every time you write a Svelte component or a Svelte module you MUST invoke the \`svelte-autofixer\` tool providing the code. The tool will return a list of issues or suggestions. If there are any issues or suggestions you MUST fix them and call the tool again with the updated code. You MUST keep doing this until the tool returns no issues or suggestions. Only then you can return the code to the user. + +This is the task you will work on: + + +${task} + +`, + }, + }, + ], + }; + }, + ); +} diff --git a/src/lib/mcp/handlers/resources/index.ts b/src/lib/mcp/handlers/resources/index.ts new file mode 100644 index 0000000..087f054 --- /dev/null +++ b/src/lib/mcp/handlers/resources/index.ts @@ -0,0 +1 @@ +export * from './list-sections.js'; diff --git a/src/lib/mcp/handlers/resources/list-sections.ts b/src/lib/mcp/handlers/resources/list-sections.ts new file mode 100644 index 0000000..36cf0ec --- /dev/null +++ b/src/lib/mcp/handlers/resources/list-sections.ts @@ -0,0 +1,24 @@ +import type { SvelteMcp } from '../../index.js'; + +export function list_sections(server: SvelteMcp) { + server.resource( + { + name: 'list-sections', + description: + 'The list of all the available Svelte 5 and SvelteKit documentation sections in a structured format.', + uri: 'svelte://list-sections', + title: 'Svelte Documentation Section', + }, + async (uri) => { + return { + contents: [ + { + uri, + type: 'text', + text: 'resource list-sections called', + }, + ], + }; + }, + ); +} diff --git a/src/lib/mcp/handlers/svelteAutofixerHandler.ts b/src/lib/mcp/handlers/svelteAutofixerHandler.ts deleted file mode 100644 index 1698325..0000000 --- a/src/lib/mcp/handlers/svelteAutofixerHandler.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { add_autofixers_issues } from '../autofixers/add-autofixers-issues.js'; -import { add_compile_issues } from '../autofixers/add-compile-issues.js'; -import { add_eslint_issues } from '../autofixers/add-eslint-issues.js'; - -export const svelteAutofixerHandler = async ({ - code, - filename, - desired_svelte_version, -}: { - code: string; - filename?: string; - desired_svelte_version: 4 | 5; -}): Promise<{ - content: Array<{ type: 'text'; text: string }>; - structuredContent: { - issues: string[]; - suggestions: string[]; - require_another_tool_call_after_fixing: boolean; - }; -}> => { - const content: { - issues: string[]; - suggestions: string[]; - require_another_tool_call_after_fixing: boolean; - } = { issues: [], suggestions: [], require_another_tool_call_after_fixing: false }; - - try { - add_compile_issues(content, code, desired_svelte_version, filename); - - add_autofixers_issues(content, code, desired_svelte_version, filename); - - await add_eslint_issues(content, code, desired_svelte_version, filename); - } catch (e: unknown) { - const error = e as Error & { start?: { line: number; column: number } }; - content.issues.push( - `${error.message} at line ${error.start?.line}, column ${error.start?.column}`, - ); - } - - if (content.issues.length > 0 || content.suggestions.length > 0) { - content.require_another_tool_call_after_fixing = true; - } - - return { - content: [ - { - type: 'text', - text: JSON.stringify(content), - }, - ], - structuredContent: content, - }; -}; diff --git a/src/lib/mcp/handlers/tools/get-documentation.ts b/src/lib/mcp/handlers/tools/get-documentation.ts new file mode 100644 index 0000000..a3f7b19 --- /dev/null +++ b/src/lib/mcp/handlers/tools/get-documentation.ts @@ -0,0 +1,57 @@ +import type { SvelteMcp } from '../../index.js'; +import * as v from 'valibot'; + +export function get_documentation(server: SvelteMcp) { + server.tool( + { + name: 'get_documentation', + description: + 'Retrieves full documentation content for Svelte 5 or SvelteKit sections. Supports flexible search by title (e.g., "$state", "routing") or file path (e.g., "docs/svelte/state.md"). Can accept a single section name or an array of sections. Before running this, make sure to analyze the users query, as well as the output from list_sections (which should be called first). Then ask for ALL relevant sections the user might require. For example, if the user asks to build anything interactive, you will need to fetch all relevant runes, and so on.', + schema: v.object({ + section: v.pipe( + v.union([v.string(), v.array(v.string())]), + v.description( + 'The section name(s) to retrieve. Can search by title (e.g., "$state", "load functions") or file path (e.g., "docs/svelte/state.md"). Supports single string and array of strings', + ), + ), + }), + }, + ({ section }) => { + let sections: string[]; + + if (Array.isArray(section)) { + sections = section.filter((s): s is string => typeof s === 'string'); + } else if ( + typeof section === 'string' && + section.trim().startsWith('[') && + section.trim().endsWith(']') + ) { + try { + const parsed = JSON.parse(section); + if (Array.isArray(parsed)) { + sections = parsed.filter((s): s is string => typeof s === 'string'); + } else { + sections = [section]; + } + } catch { + sections = [section]; + } + } else if (typeof section === 'string') { + sections = [section]; + } else { + sections = []; + } + + const sections_list = sections.length > 0 ? sections.join(', ') : 'no sections'; + + return { + content: [ + { + type: 'text', + text: `called for sections: ${sections_list}`, + }, + ], + }; + }, + ); +} diff --git a/src/lib/mcp/handlers/tools/index.ts b/src/lib/mcp/handlers/tools/index.ts new file mode 100644 index 0000000..1069185 --- /dev/null +++ b/src/lib/mcp/handlers/tools/index.ts @@ -0,0 +1,3 @@ +export * from './get-documentation.js'; +export * from './list-sections.js'; +export * from './svelte-autofixer.js'; diff --git a/src/lib/mcp/handlers/tools/list-sections.ts b/src/lib/mcp/handlers/tools/list-sections.ts new file mode 100644 index 0000000..c821455 --- /dev/null +++ b/src/lib/mcp/handlers/tools/list-sections.ts @@ -0,0 +1,21 @@ +import type { SvelteMcp } from '../../index.js'; + +export function list_sections(server: SvelteMcp) { + server.tool( + { + name: 'list_sections', + description: + 'Lists all available Svelte 5 and SvelteKit documentation sections in a structured format. Returns sections as a list of "* title: [section_title], path: [file_path]" - you can use either the title or path when querying a specific section via the get_documentation tool. Always run list_sections first for any query related to Svelte development to discover available content.', + }, + () => { + return { + content: [ + { + type: 'text', + text: 'tool list_sections called', + }, + ], + }; + }, + ); +} diff --git a/src/lib/mcp/handlers/tools/svelte-autofixer.ts b/src/lib/mcp/handlers/tools/svelte-autofixer.ts new file mode 100644 index 0000000..34d7bd8 --- /dev/null +++ b/src/lib/mcp/handlers/tools/svelte-autofixer.ts @@ -0,0 +1,81 @@ +import { basename } from 'node:path'; +import type { SvelteMcp } from '../../index.js'; +import * as v from 'valibot'; +import { add_compile_issues } from '../../autofixers/add-compile-issues.js'; +import { add_eslint_issues } from '../../autofixers/add-eslint-issues.js'; +import { add_autofixers_issues } from '../../autofixers/add-autofixers-issues.js'; + +export function svelte_autofixer(server: SvelteMcp) { + server.tool( + { + name: 'svelte-autofixer', + title: 'Svelte Autofixer', + description: + 'Given a svelte component or module returns a list of suggestions to fix any issues it has. This tool MUST be used whenever the user is asking to write svelte code before sending the code back to the user', + schema: v.object({ + code: v.string(), + desired_svelte_version: v.pipe( + v.union([v.literal(4), v.literal(5), v.literal('4'), v.literal('5')]), + v.description( + 'The desired svelte version...if possible read this from the package.json of the user project, otherwise use some hint from the wording (if the user asks for runes it wants version 5). Default to 5 in case of doubt.', + ), + ), + filename: v.pipe( + v.optional(v.string()), + v.description( + 'The filename of the component if available, it MUST be only the Component name with .svelte or .svelte.ts extension and not the entire path.', + ), + ), + }), + outputSchema: v.object({ + issues: v.array(v.string()), + suggestions: v.array(v.string()), + require_another_tool_call_after_fixing: v.boolean(), + }), + annotations: { + title: 'Svelte Autofixer', + destructiveHint: false, + readOnlyHint: true, + openWorldHint: false, + }, + }, + async ({ code, filename: filename_or_path, desired_svelte_version }) => { + const content: { + issues: string[]; + suggestions: string[]; + require_another_tool_call_after_fixing: boolean; + } = { issues: [], suggestions: [], require_another_tool_call_after_fixing: false }; + try { + // just in case the LLM sends a full path we extract the filename...it's not really needed + // but it's nice to have a filename in the errors + + const filename = filename_or_path ? basename(filename_or_path) : 'Component.svelte'; + + add_compile_issues(content, code, +desired_svelte_version, filename); + + add_autofixers_issues(content, code, +desired_svelte_version, filename); + + await add_eslint_issues(content, code, +desired_svelte_version, filename); + } catch (e: unknown) { + const error = e as Error & { start?: { line: number; column: number } }; + content.issues.push( + `${error.message} at line ${error.start?.line}, column ${error.start?.column}`, + ); + } + + if (content.issues.length > 0 || content.suggestions.length > 0) { + content.require_another_tool_call_after_fixing = true; + } + + return { + content: [ + { + type: 'text', + text: JSON.stringify(content), + }, + ], + structuredContent: content, + }; + }, + ); +} diff --git a/src/lib/mcp/index.ts b/src/lib/mcp/index.ts index cb02cdf..0b08a49 100644 --- a/src/lib/mcp/index.ts +++ b/src/lib/mcp/index.ts @@ -2,14 +2,7 @@ import { ValibotJsonSchemaAdapter } from '@tmcp/adapter-valibot'; import { HttpTransport } from '@tmcp/transport-http'; import { StdioTransport } from '@tmcp/transport-stdio'; import { McpServer } from 'tmcp'; -import * as v from 'valibot'; -import { add_autofixers_issues } from './autofixers/add-autofixers-issues.js'; -import { add_compile_issues } from './autofixers/add-compile-issues.js'; -import { add_eslint_issues } from './autofixers/add-eslint-issues.js'; -import { listSectionsHandler } from './handlers/listSectionsHandler.js'; -import { getDocumentationHandler } from './handlers/getDocumentationHandler.js'; -import { svelteAutofixerHandler } from './handlers/svelteAutofixerHandler.js'; -import { basename } from 'node:path'; +import { setup_prompts, setup_resources, setup_tools } from './handlers'; const server = new McpServer( { @@ -30,146 +23,11 @@ const server = new McpServer( }, ); -server.tool( - { - name: 'svelte-autofixer', - title: 'Svelte Autofixer', - description: - 'Given a svelte component or module returns a list of suggestions to fix any issues it has. This tool MUST be used whenever the user is asking to write svelte code before sending the code back to the user', - schema: v.object({ - code: v.string(), - desired_svelte_version: v.pipe( - v.union([v.literal(4), v.literal(5), v.literal('4'), v.literal('5')]), - v.description( - 'The desired svelte version...if possible read this from the package.json of the user project, otherwise use some hint from the wording (if the user asks for runes it wants version 5). Default to 5 in case of doubt.', - ), - ), - filename: v.pipe( - v.optional(v.string()), - v.description( - 'The filename of the component if available, it MUST be only the Component name with .svelte or .svelte.ts extension and not the entire path.', - ), - ), - }), - outputSchema: v.object({ - issues: v.array(v.string()), - suggestions: v.array(v.string()), - require_another_tool_call_after_fixing: v.boolean(), - }), - annotations: { - title: 'Svelte Autofixer', - destructiveHint: false, - readOnlyHint: true, - openWorldHint: false, - }, - }, - async ({ code, filename: filename_or_path, desired_svelte_version }) => { - const content: { - issues: string[]; - suggestions: string[]; - require_another_tool_call_after_fixing: boolean; - } = { issues: [], suggestions: [], require_another_tool_call_after_fixing: false }; - try { - // just in case the LLM sends a full path we extract the filename...it's not really needed - // but it's nice to have a filename in the errors - - const filename = filename_or_path ? basename(filename_or_path) : 'Component.svelte'; - - add_compile_issues(content, code, +desired_svelte_version, filename); - - add_autofixers_issues(content, code, +desired_svelte_version, filename); - - await add_eslint_issues(content, code, +desired_svelte_version, filename); - } catch (e: unknown) { - const error = e as Error & { start?: { line: number; column: number } }; - content.issues.push( - `${error.message} at line ${error.start?.line}, column ${error.start?.column}`, - ); - } +export type SvelteMcp = typeof server; - if (content.issues.length > 0 || content.suggestions.length > 0) { - content.require_another_tool_call_after_fixing = true; - } - - return { - content: [ - { - type: 'text', - text: JSON.stringify(content), - }, - ], - structuredContent: content, - }; - }, -); - -// List sections tool -server.tool( - { - name: 'list_sections', - description: - 'Lists all available Svelte 5 and SvelteKit documentation sections in a structured format. Returns sections as a list of "* title: [section_title], path: [file_path]" - you can use either the title or path when querying a specific section via the get_documentation tool. Always run list_sections first for any query related to Svelte development to discover available content.', - }, - listSectionsHandler, -); - -// Get documentation tool -server.tool( - { - name: 'get_documentation', - description: - 'Retrieves full documentation content for Svelte 5 or SvelteKit sections. Supports flexible search by title (e.g., "$state", "routing") or file path (e.g., "docs/svelte/state.md"). Can accept a single section name or an array of sections. Before running this, make sure to analyze the users query, as well as the output from list_sections (which should be called first). Then ask for ALL relevant sections the user might require. For example, if the user asks to build anything interactive, you will need to fetch all relevant runes, and so on.', - schema: v.object({ - section: v.pipe( - v.union([v.string(), v.array(v.string())]), - v.description( - 'The section name(s) to retrieve. Can search by title (e.g., "$state", "load functions") or file path (e.g., "docs/svelte/state.md"). Supports single string and array of strings', - ), - ), - }), - }, - getDocumentationHandler, -); - -server.prompt( - { - name: 'svelte-task-prompt', - title: 'Svelte Task Prompt', - description: - 'Use this Prompt to ask for any svelte related task. It will automatically instruct the LLM on how to best use the autofixer and how to query for documentation pages.', - schema: v.object({ - task: v.pipe(v.string(), v.description('The task to be performed')), - }), - }, - async ({ task }) => { - // TODO: implement logic to fetch the available docs paths to return in the prompt - const available_docs: string[] = []; - - return { - messages: [ - { - role: 'user', - content: { - type: 'text', - text: `You are a Svelte expert tasked to build components and utilities for Svelte developers. If you need documentation for anything related to Svelte you can invoke the tool \`get_documentation\` with one of the following paths: - -${JSON.stringify(available_docs, null, 2)} - - -Every time you write a Svelte component or a Svelte module you MUST invoke the \`svelte-autofixer\` tool providing the code. The tool will return a list of issues or suggestions. If there are any issues or suggestions you MUST fix them and call the tool again with the updated code. You MUST keep doing this until the tool returns no issues or suggestions. Only then you can return the code to the user. - -This is the task you will work on: - - -${task} - -`, - }, - }, - ], - }; - }, -); +setup_tools(server); +setup_resources(server); +setup_prompts(server); export const http_transport = new HttpTransport(server, { cors: true, From 68724731c788027d47f972b1c79f4627dfce465d Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Sun, 21 Sep 2025 14:50:01 +0200 Subject: [PATCH 10/11] chore: add snippets to create autofixers and stup functions --- .vscode/mcp-snippets.code-snippets | 33 ++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .vscode/mcp-snippets.code-snippets diff --git a/.vscode/mcp-snippets.code-snippets b/.vscode/mcp-snippets.code-snippets new file mode 100644 index 0000000..eaa7bdc --- /dev/null +++ b/.vscode/mcp-snippets.code-snippets @@ -0,0 +1,33 @@ +{ + // Place your svelte-mcp workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and + // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope + // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is + // used to trigger the snippet and the body will be expanded and inserted. Possible variables are: + // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. + // Placeholders with the same ids are connected. + // Example: + "Setup Function": { + "scope": "javascript,typescript", + "prefix": "!setup-mcp", + "body": [ + "import type { SvelteMcp } from '../../index.js';", + "import * as v from 'valibot';", + "", + "export function ${1:function_name}(server: SvelteMcp) {", + "\t$0", + "}", + ], + "description": "Create a setup function for a tool/resource/prompt handler" + }, + "Autofixer": { + "scope": "javascript,typescript", + "prefix": "!autofixer", + "body": [ + "import type { Autofixer } from '.';", + "export const ${1:autofixer_name}: Autofixer = {", + "\t$0", + "};", + ], + "description": "Create a setup export for an autofixer" + } +} \ No newline at end of file From cc3ea75c7fb5c9feae9698af2a501be31b9536ae Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Sun, 21 Sep 2025 14:52:11 +0200 Subject: [PATCH 11/11] fix: lint --- .vscode/mcp-snippets.code-snippets | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.vscode/mcp-snippets.code-snippets b/.vscode/mcp-snippets.code-snippets index eaa7bdc..931d77a 100644 --- a/.vscode/mcp-snippets.code-snippets +++ b/.vscode/mcp-snippets.code-snippets @@ -1,9 +1,9 @@ { - // Place your svelte-mcp workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and - // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope - // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is - // used to trigger the snippet and the body will be expanded and inserted. Possible variables are: - // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. + // Place your svelte-mcp workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and + // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope + // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is + // used to trigger the snippet and the body will be expanded and inserted. Possible variables are: + // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. // Placeholders with the same ids are connected. // Example: "Setup Function": { @@ -17,7 +17,7 @@ "\t$0", "}", ], - "description": "Create a setup function for a tool/resource/prompt handler" + "description": "Create a setup function for a tool/resource/prompt handler", }, "Autofixer": { "scope": "javascript,typescript", @@ -28,6 +28,6 @@ "\t$0", "};", ], - "description": "Create a setup export for an autofixer" - } -} \ No newline at end of file + "description": "Create a setup export for an autofixer", + }, +}