├── .npmrc ├── apps └── mcp-remote │ ├── src │ ├── lib │ │ ├── index.ts │ │ ├── server │ │ │ └── db │ │ │ │ ├── schema.ts │ │ │ │ └── index.ts │ │ ├── mcp │ │ │ └── index.ts │ │ └── assets │ │ │ └── favicon.svg │ ├── routes │ │ ├── +page.svelte │ │ └── +layout.svelte │ ├── app.d.ts │ ├── app.html │ └── hooks.server.ts │ ├── static │ ├── robots.txt │ ├── logo.png │ └── logo.svg │ ├── .env.example │ ├── svelte.config.js │ ├── drizzle.config.ts │ ├── vite.config.ts │ ├── tsconfig.json │ └── package.json ├── .cocoignore ├── .github ├── FUNDING.yml ├── workflows │ ├── lint.yml │ ├── check.yml │ ├── test.yml │ ├── publish-mcp.yml │ ├── update-prompt-docs.yml │ └── release.yml └── ISSUE_TEMPLATE │ ├── autofixer_request.yml │ ├── feature_request.yml │ └── bug_report.yml ├── .cocominify ├── documentation └── docs │ ├── index.md │ ├── 20-setup │ ├── index.md │ ├── 30-remote-setup.md │ └── 20-local-setup.md │ ├── 10-introduction │ ├── index.md │ └── 10-overview.md │ └── 30-capabilities │ ├── index.md │ ├── 20-resources.md │ └── 10-tools.md ├── packages ├── mcp-server │ ├── src │ │ ├── index.ts │ │ ├── mcp │ │ │ ├── handlers │ │ │ │ ├── resources │ │ │ │ │ ├── index.ts │ │ │ │ │ └── doc-section.ts │ │ │ │ ├── prompts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── svelte-task.ts │ │ │ │ ├── tools │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── prompts.ts │ │ │ │ │ ├── list-sections.ts │ │ │ │ │ ├── playground-link.test.ts │ │ │ │ │ ├── get-documentation.ts │ │ │ │ │ ├── playground-link.ts │ │ │ │ │ ├── svelte-autofixer.test.ts │ │ │ │ │ └── svelte-autofixer.ts │ │ │ │ └── index.ts │ │ │ ├── autofixers │ │ │ │ ├── ast │ │ │ │ │ ├── utils.ts │ │ │ │ │ └── walk.ts │ │ │ │ ├── add-autofixers-issues.ts │ │ │ │ ├── visitors │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── read-state-with-dollar.ts │ │ │ │ │ ├── use-runes-instead-of-store.ts │ │ │ │ │ ├── imported-runes.ts │ │ │ │ │ ├── derived-with-function.ts │ │ │ │ │ ├── suggest-attachments.ts │ │ │ │ │ ├── wrong-property-access-state.ts │ │ │ │ │ └── assign-in-effect.ts │ │ │ │ ├── add-compile-issues.ts │ │ │ │ └── add-eslint-issues.ts │ │ │ ├── index.ts │ │ │ ├── utils.ts │ │ │ └── icons │ │ │ │ └── index.ts │ │ ├── constants.ts │ │ ├── parse │ │ │ ├── parse.ts │ │ │ └── parse.spec.ts │ │ ├── lib │ │ │ ├── schemas.ts │ │ │ └── anthropic.ts │ │ └── use_cases.json │ ├── .env.example │ ├── scripts │ │ ├── tsconfig.json │ │ └── generate-summaries.ts │ └── package.json ├── mcp-stdio │ ├── vitest.config.ts │ ├── tsconfig.json │ ├── src │ │ └── index.ts │ ├── README.md │ ├── scripts │ │ └── update-version.ts │ ├── tsdown.config.ts │ ├── server.json │ ├── package.json │ ├── checksums │ │ └── registry_1.4.0_checksums.txt │ └── CHANGELOG.md └── mcp-schema │ ├── src │ ├── index.js │ ├── utils.js │ └── schema.js │ └── package.json ├── .gitattributes ├── .mcp.json ├── .editorconfig ├── .vscode ├── mcp.json └── mcp-snippets.code-snippets ├── .prettierignore ├── .prettierrc ├── .changeset ├── config.json └── README.md ├── renovate.json ├── tsconfig.json ├── .gitignore ├── README.md ├── scripts └── update-docs-prompts.ts ├── pnpm-workspace.yaml ├── package.json ├── eslint.config.js ├── CLAUDE.md └── docs └── tmcp.md /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /apps/mcp-remote/src/lib/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.cocoignore: -------------------------------------------------------------------------------- 1 | .claude 2 | .github 3 | .vscode -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | open_collective: svelte 2 | -------------------------------------------------------------------------------- /.cocominify: -------------------------------------------------------------------------------- 1 | packages/mcp-server/src/use_cases.json -------------------------------------------------------------------------------- /documentation/docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: MCP 3 | --- 4 | -------------------------------------------------------------------------------- /apps/mcp-remote/src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 |

Official Svelte MCP

2 | -------------------------------------------------------------------------------- /documentation/docs/20-setup/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Setup 3 | --- 4 | -------------------------------------------------------------------------------- /documentation/docs/10-introduction/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introduction 3 | --- 4 | -------------------------------------------------------------------------------- /documentation/docs/30-capabilities/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Capabilities 3 | --- 4 | -------------------------------------------------------------------------------- /packages/mcp-server/src/index.ts: -------------------------------------------------------------------------------- 1 | export { server, type SvelteMcp } from './mcp/index.js'; 2 | -------------------------------------------------------------------------------- /packages/mcp-server/src/mcp/handlers/resources/index.ts: -------------------------------------------------------------------------------- 1 | export * from './doc-section.js'; 2 | -------------------------------------------------------------------------------- /apps/mcp-remote/static/robots.txt: -------------------------------------------------------------------------------- 1 | # allow crawling everything by default 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /apps/mcp-remote/static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sveltejs/mcp/main/apps/mcp-remote/static/logo.png -------------------------------------------------------------------------------- /packages/mcp-server/src/mcp/handlers/prompts/index.ts: -------------------------------------------------------------------------------- 1 | export { setup_svelte_task } from './svelte-task.js'; 2 | -------------------------------------------------------------------------------- /packages/mcp-server/.env.example: -------------------------------------------------------------------------------- 1 | # Anthropic API Key from: https://console.anthropic.com/ 2 | ANTHROPIC_API_KEY=your_api_key_here -------------------------------------------------------------------------------- /packages/mcp-stdio/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({}); 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | /packages/**/test/** -linguist-detectable 3 | /packages/**/fixtures/** -linguist-detectable 4 | -------------------------------------------------------------------------------- /packages/mcp-stdio/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src"], 4 | "exclude": ["node_modules", "dist"] 5 | } 6 | -------------------------------------------------------------------------------- /apps/mcp-remote/.env.example: -------------------------------------------------------------------------------- 1 | DATABASE_URL=file:test.db 2 | DATABASE_TOKEN=needs_to_be_set_but_it_can_be_anything 3 | VOYAGE_API_KEY=your_actual_api_key_here -------------------------------------------------------------------------------- /packages/mcp-server/scripts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.json", 3 | "compilerOptions": { 4 | "allowImportingTsExtensions": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.mcp.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "svelte": { 4 | "type": "stdio", 5 | "command": "node", 6 | "args": ["packages/mcp-stdio/dist/index.js"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/mcp-schema/src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @import * as schema from './schema.js' 3 | */ 4 | export * from './schema.js'; 5 | 6 | /** 7 | * @typedef {typeof schema} Schema 8 | */ 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = tab 7 | indent_size = 2 8 | charset = utf-8 9 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /apps/mcp-remote/src/lib/server/db/schema.ts: -------------------------------------------------------------------------------- 1 | // we need to re-export from here to allow for the drizzle config to pick them up for migrations 2 | export * from '@sveltejs/mcp-schema/schema'; 3 | -------------------------------------------------------------------------------- /.vscode/mcp.json: -------------------------------------------------------------------------------- 1 | { 2 | "servers": { 3 | "Svelte MCP": { 4 | "type": "stdio", 5 | "command": "node", 6 | "args": ["packages/mcp-stdio/dist/index.js"] 7 | } 8 | }, 9 | "inputs": [] 10 | } 11 | -------------------------------------------------------------------------------- /packages/mcp-server/src/mcp/handlers/tools/index.ts: -------------------------------------------------------------------------------- 1 | export * from './get-documentation.js'; 2 | export * from './list-sections.js'; 3 | export * from './svelte-autofixer.js'; 4 | export * from './playground-link.js'; 5 | -------------------------------------------------------------------------------- /apps/mcp-remote/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-vercel'; 2 | 3 | /** @type {import('@sveltejs/kit').Config} */ 4 | const config = { 5 | kit: { adapter: adapter() }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /packages/mcp-stdio/src/index.ts: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | import { server } from '@sveltejs/mcp-server'; 3 | import { StdioTransport } from '@tmcp/transport-stdio'; 4 | 5 | const transport = new StdioTransport(server); 6 | 7 | transport.listen(); 8 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Package Managers 2 | package-lock.json 3 | pnpm-lock.yaml 4 | yarn.lock 5 | bun.lock 6 | bun.lockb 7 | 8 | # Miscellaneous 9 | /static/ 10 | /drizzle/ 11 | /**/.svelte-kit/* 12 | 13 | # Claude Code 14 | .claude/ 15 | .changeset/ -------------------------------------------------------------------------------- /apps/mcp-remote/src/lib/mcp/index.ts: -------------------------------------------------------------------------------- 1 | import { server } from '@sveltejs/mcp-server'; 2 | import { HttpTransport } from '@tmcp/transport-http'; 3 | 4 | export const http_transport = new HttpTransport(server, { 5 | cors: true, 6 | path: '/mcp', 7 | }); 8 | -------------------------------------------------------------------------------- /apps/mcp-remote/src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | {@render children?.()} 12 | -------------------------------------------------------------------------------- /packages/mcp-stdio/README.md: -------------------------------------------------------------------------------- 1 | # @sveltejs/mcp 2 | 3 | The CLI version of the Svelte MCP. 4 | 5 | You can run it directly with 6 | 7 | ```bash 8 | npx @sveltejs/mcp 9 | ``` 10 | 11 | or install it and then run it 12 | 13 | ```bash 14 | pnpm i @sveltejs/mcp 15 | pnpm svelte-mcp 16 | ``` 17 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "all", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte"], 7 | "overrides": [ 8 | { 9 | "files": "*.svelte", 10 | "options": { 11 | "parser": "svelte" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /apps/mcp-remote/src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://svelte.dev/docs/kit/types#app.d.ts 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface PageState {} 9 | // interface Platform {} 10 | } 11 | } 12 | 13 | export {}; 14 | -------------------------------------------------------------------------------- /apps/mcp-remote/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | %sveltekit.head% 7 | 8 | 9 |
%sveltekit.body%
10 | 11 | 12 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json", 3 | "changelog": ["@svitejs/changesets-changelog-github-compact", { "repo": "sveltejs/mcp" }], 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": ["!@sveltejs/mcp"] 11 | } 12 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:recommended", 4 | ":preserveSemverRanges", 5 | "group:allNonMajor", 6 | ":semanticCommitTypeAll(chore)" 7 | ], 8 | "pin": { 9 | "enabled": false 10 | }, 11 | "ignoreDeps": ["@types/node", "esbuild", "rollup", "typescript"], 12 | "packageRules": [ 13 | { 14 | "matchPackageNames": ["vite"], 15 | "matchUpdateTypes": ["major"], 16 | "enabled": false 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /packages/mcp-schema/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sveltejs/mcp-schema", 3 | "version": "0.0.1", 4 | "private": true, 5 | "description": "", 6 | "main": "index.js", 7 | "exports": { 8 | ".": "./src/index.js", 9 | "./utils": "./src/utils.js", 10 | "./schema": "./src/schema.js" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "type": "module", 16 | "dependencies": { 17 | "drizzle-orm": "catalog:orm" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /apps/mcp-remote/drizzle.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'drizzle-kit'; 2 | 3 | if (!process.env.DATABASE_URL) throw new Error('DATABASE_URL is not set'); 4 | if (!process.env.DATABASE_TOKEN) throw new Error('DATABASE_TOKEN is not set'); 5 | 6 | export default defineConfig({ 7 | schema: './src/lib/server/db/schema.ts', 8 | dialect: 'turso', 9 | dbCredentials: { url: process.env.DATABASE_URL, authToken: process.env.DATABASE_TOKEN }, 10 | verbose: true, 11 | strict: true, 12 | }); 13 | -------------------------------------------------------------------------------- /packages/mcp-server/src/mcp/autofixers/ast/utils.ts: -------------------------------------------------------------------------------- 1 | import type { Identifier, MemberExpression } from 'estree'; 2 | 3 | /** 4 | * Gets the left-most identifier of a member expression or identifier. 5 | */ 6 | export function left_most_id(expression: MemberExpression | Identifier) { 7 | while (expression.type === 'MemberExpression') { 8 | expression = expression.object as MemberExpression | Identifier; 9 | } 10 | 11 | if (expression.type !== 'Identifier') { 12 | return null; 13 | } 14 | 15 | return expression; 16 | } 17 | -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /packages/mcp-server/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const base_runes = [ 2 | '$state', 3 | '$effect', 4 | '$derived', 5 | '$inspect', 6 | '$props', 7 | '$bindable', 8 | '$host', 9 | ] as const; 10 | 11 | export const nested_runes = [ 12 | '$state.raw', 13 | '$state.snapshot', 14 | '$state.eager', 15 | '$effect.pre', 16 | '$effect.tracking', 17 | '$effect.pending', 18 | '$effect.root', 19 | '$derived.by', 20 | '$inspect.trace', 21 | '$props.id', 22 | ] as const; 23 | 24 | export const runes = [...base_runes, ...nested_runes] as const; 25 | -------------------------------------------------------------------------------- /apps/mcp-remote/src/lib/server/db/index.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from '@libsql/client'; 2 | import { drizzle } from 'drizzle-orm/libsql'; 3 | import * as schema from './schema.js'; 4 | import { DATABASE_TOKEN, DATABASE_URL } from '$env/static/private'; 5 | if (!DATABASE_URL) throw new Error('DATABASE_URL is not set'); 6 | if (!DATABASE_TOKEN) throw new Error('DATABASE_TOKEN is not set'); 7 | 8 | const client = createClient({ 9 | url: DATABASE_URL, 10 | authToken: DATABASE_TOKEN, 11 | }); 12 | 13 | export const db = drizzle(client, { schema, logger: true }); 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "nodenext", 4 | "target": "esnext", 5 | "sourceMap": true, 6 | "declaration": true, 7 | "declarationMap": true, 8 | "noEmit": true, 9 | "noUncheckedIndexedAccess": true, 10 | "exactOptionalPropertyTypes": true, 11 | "checkJs": true, 12 | "allowJs": true, 13 | // Recommended Options 14 | "strict": true, 15 | "verbatimModuleSyntax": true, 16 | "isolatedModules": true, 17 | "noUncheckedSideEffectImports": true, 18 | "moduleDetection": "force", 19 | "skipLibCheck": true 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/mcp-stdio/scripts/update-version.ts: -------------------------------------------------------------------------------- 1 | import { readFile, writeFile } from 'node:fs/promises'; 2 | import { resolve } from 'node:path'; 3 | 4 | const package_json_string = await readFile(resolve('./package.json'), 'utf-8'); 5 | const package_json = JSON.parse(package_json_string); 6 | 7 | const server_json_path = resolve('./server.json'); 8 | const server_json_string = await readFile(server_json_path, 'utf-8'); 9 | const server_json = JSON.parse(server_json_string); 10 | 11 | server_json.version = package_json.version; 12 | server_json.packages[0].version = package_json.version; 13 | 14 | await writeFile(server_json_path, JSON.stringify(server_json, null, '\t') + '\n', 'utf-8'); 15 | -------------------------------------------------------------------------------- /apps/mcp-remote/vite.config.ts: -------------------------------------------------------------------------------- 1 | import devtoolsJson from 'vite-plugin-devtools-json'; 2 | import { sveltekit } from '@sveltejs/kit/vite'; 3 | import { defineConfig } from 'vite'; 4 | 5 | export default defineConfig({ 6 | plugins: [sveltekit(), devtoolsJson()], 7 | // we don't have tests yet so we just comment this out for now 8 | // test: { 9 | // expect: { requireAssertions: true }, 10 | // projects: [ 11 | // { 12 | // extends: './vite.config.ts', 13 | // test: { 14 | // name: 'server', 15 | // environment: 'node', 16 | // include: ['src/**/*.{test,spec}.{js,ts}'], 17 | // exclude: ['src/**/*.svelte.{test,spec}.{js,ts}'], 18 | // }, 19 | // }, 20 | // ], 21 | // }, 22 | }); 23 | -------------------------------------------------------------------------------- /documentation/docs/30-capabilities/20-resources.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Resources 3 | --- 4 | 5 | This is the list of available resources provided by the MCP server. Resources are included by the user (not by the LLM) and are useful if you want to include specific knowledge in your session. For example, if you know that the component will need to use transitions you can include the transition documentation directly without asking the LLM to do it for you. 6 | 7 | ## doc-section 8 | 9 | This dynamic resource allows you to add every section of the Svelte documentation as a resource. The URI looks like this `svelte://slug-of-the-docs.md` and the returned resource will contain the `llms.txt` version of the specific page you selected. 10 | -------------------------------------------------------------------------------- /packages/mcp-server/src/mcp/handlers/tools/prompts.ts: -------------------------------------------------------------------------------- 1 | export const SECTIONS_LIST_INTRO = 2 | 'List of available Svelte documentation sections with their intended use cases. The "use_cases" field describes WHEN each section would be useful - analyze these carefully to determine which sections match the user\'s query:'; 3 | 4 | export const SECTIONS_LIST_OUTRO = 5 | "Carefully analyze the use_cases field for each section to identify which documentation is relevant for the user's specific query. The use_cases contain keywords for project types, features, components, and development stages. After identifying relevant sections, use the get-documentation tool with ALL relevant section titles or paths at once (can pass multiple sections as an array)."; 6 | -------------------------------------------------------------------------------- /packages/mcp-server/src/mcp/handlers/index.ts: -------------------------------------------------------------------------------- 1 | import type { SvelteMcp } from '../index.js'; 2 | import * as prompts from './prompts/index.js'; 3 | import * as tools from './tools/index.js'; 4 | import * as resources from './resources/index.js'; 5 | 6 | export function setup_tools(server: SvelteMcp) { 7 | for (const tool in tools) { 8 | tools[tool as keyof typeof tools](server); 9 | } 10 | } 11 | 12 | export function setup_prompts(server: SvelteMcp) { 13 | for (const prompt in prompts) { 14 | prompts[prompt as keyof typeof prompts](server); 15 | } 16 | } 17 | 18 | export function setup_resources(server: SvelteMcp) { 19 | for (const resource in resources) { 20 | resources[resource as keyof typeof resources](server); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /apps/mcp-remote/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true, 12 | "moduleResolution": "bundler" 13 | } 14 | // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias 15 | // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files 16 | // 17 | // To make changes to top-level options such as include and exclude, we recommend extending 18 | // the generated config; see https://svelte.dev/docs/kit/configuration#typescript 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /apps/**/node_modules 3 | /packages/**/node_modules 4 | 5 | # Output 6 | /apps/**/.output 7 | /apps/**/.vercel 8 | /apps/**/.netlify 9 | /apps/**/.wrangler 10 | /**/.svelte-kit 11 | /apps/**/build 12 | /apps/**/dist 13 | /packages/**/dist 14 | 15 | # OS 16 | .DS_Store 17 | Thumbs.db 18 | 19 | # Env 20 | .env 21 | /apps/**/.env 22 | /packages/**/.env 23 | .env.* 24 | /apps/**/.env.* 25 | /packages/**/.env.* 26 | !.env.example 27 | /apps/**/!.env.example 28 | /packages/**/!.env.example 29 | !.env.test 30 | /apps/**/!.env.test 31 | /packages/**/!.env.test 32 | 33 | # Vite 34 | vite.config.js.timestamp-* 35 | vite.config.ts.timestamp-* 36 | /apps/**/vite.config.js.timestamp-* 37 | /apps/**/vite.config.ts.timestamp-* 38 | 39 | # SQLite 40 | *.db 41 | /apps/**/*.db 42 | /packages/**/*.db -------------------------------------------------------------------------------- /packages/mcp-server/src/mcp/autofixers/add-autofixers-issues.ts: -------------------------------------------------------------------------------- 1 | import { parse } from '../../parse/parse.js'; 2 | import { walk } from '../../mcp/autofixers/ast/walk.js'; 3 | import type { Node } from 'estree'; 4 | import * as autofixers from './visitors/index.js'; 5 | 6 | export function add_autofixers_issues( 7 | content: { issues: string[]; suggestions: string[] }, 8 | code: string, 9 | desired_svelte_version: number, 10 | filename = 'Component.svelte', 11 | async = false, 12 | ) { 13 | const parsed = parse(code, filename); 14 | 15 | // Run each autofixer separately to avoid interrupting logic flow 16 | for (const autofixer of Object.values(autofixers)) { 17 | walk( 18 | parsed.ast as unknown as Node, 19 | { output: content, parsed, desired_svelte_version, async }, 20 | autofixer, 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/mcp-stdio/tsdown.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsdown'; 2 | 3 | export default defineConfig([ 4 | { 5 | entry: ['./src/index.ts'], 6 | platform: 'node', 7 | define: { 8 | // some eslint-plugin-svelte code expects __filename to exists but in an ESM environment it does not. 9 | __filename: 'import.meta.filename', 10 | }, 11 | // we need eslint at runtime but the bundler doesn't bundle `require`'s which `eslint-plugin-svelte` uses to require 12 | // `eslint/use-at-your-own-risk`. If we didn't have `eslint` as an actual dependency and didn't externalize it 13 | // the require would fail once executed in a project without eslint installed. 14 | external: ['eslint'], 15 | publint: true, 16 | dts: false, 17 | treeshake: true, 18 | clean: true, 19 | target: 'esnext', 20 | }, 21 | ]); 22 | -------------------------------------------------------------------------------- /packages/mcp-server/src/mcp/autofixers/ast/walk.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { walk as _walk } from 'zimmerframe'; 3 | 4 | export type WalkParams< 5 | T extends { 6 | type: string; 7 | }, 8 | U extends Record | null, 9 | > = Parameters>; 10 | 11 | export function walk< 12 | T extends { 13 | type: string; 14 | }, 15 | U extends Record | null, 16 | >(...args: WalkParams) { 17 | const [node, state, visitors] = args; 18 | const visited = new WeakSet(); 19 | return _walk(node, state, { 20 | _(node, ctx) { 21 | if (visited.has(node)) return; 22 | visited.add(node); 23 | if (visitors._) { 24 | const ret = visitors._(node, ctx); 25 | if (ret) return ret; 26 | } else { 27 | ctx.next(); 28 | } 29 | }, 30 | ...visitors, 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /packages/mcp-server/src/mcp/autofixers/visitors/index.ts: -------------------------------------------------------------------------------- 1 | import type { Node } from 'estree'; 2 | import type { AST } from 'svelte-eslint-parser'; 3 | import type { Visitors } from 'zimmerframe'; 4 | import type { ParseResult } from '../../../parse/parse.js'; 5 | 6 | export type AutofixerState = { 7 | output: { issues: string[]; suggestions: string[] }; 8 | parsed: ParseResult; 9 | desired_svelte_version: number; 10 | async?: boolean; 11 | }; 12 | 13 | export type Autofixer = Visitors; 14 | 15 | export * from './assign-in-effect.js'; 16 | export * from './wrong-property-access-state.js'; 17 | export * from './imported-runes.js'; 18 | export * from './derived-with-function.js'; 19 | export * from './use-runes-instead-of-store.js'; 20 | export * from './suggest-attachments.js'; 21 | export * from './read-state-with-dollar.js'; 22 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: [main, redesign] 6 | pull_request: 7 | branches: [main] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | lint: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout Repository 16 | uses: actions/checkout@v6 17 | 18 | - name: Setup pnpm 19 | uses: pnpm/action-setup@v4 20 | with: 21 | version: 10.26.0 22 | 23 | - name: Setup Node.js 24 | uses: actions/setup-node@v6 25 | with: 26 | node-version: '24' 27 | cache: 'pnpm' 28 | 29 | - name: Install dependencies 30 | run: pnpm i 31 | 32 | - name: Run linting 33 | run: pnpm run lint 34 | env: 35 | DATABASE_URL: file:test.db 36 | VOYAGE_API_KEY: dummy-key 37 | DATABASE_TOKEN: dummy-key 38 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: Type Check 2 | 3 | on: 4 | push: 5 | branches: [main, redesign] 6 | pull_request: 7 | branches: [main] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | check: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout Repository 16 | uses: actions/checkout@v6 17 | 18 | - name: Setup pnpm 19 | uses: pnpm/action-setup@v4 20 | with: 21 | version: 10.26.0 22 | 23 | - name: Setup Node.js 24 | uses: actions/setup-node@v6 25 | with: 26 | node-version: '24' 27 | cache: 'pnpm' 28 | 29 | - name: Install dependencies 30 | run: pnpm i 31 | 32 | - name: Run type check 33 | run: pnpm run check 34 | env: 35 | DATABASE_URL: file:test.db 36 | DATABASE_TOKEN: dummy-key 37 | VOYAGE_API_KEY: dummy-key 38 | -------------------------------------------------------------------------------- /packages/mcp-server/src/mcp/autofixers/visitors/read-state-with-dollar.ts: -------------------------------------------------------------------------------- 1 | import type { Autofixer } from './index.js'; 2 | 3 | export const read_state_with_dollar: Autofixer = { 4 | Identifier(node, { state }) { 5 | if (node.name.startsWith('$')) { 6 | const reference = state.parsed.find_reference_by_id(node); 7 | if (reference && reference.resolved && reference.resolved.defs[0]?.node?.init) { 8 | const is_state = state.parsed.is_rune(reference.resolved.defs[0].node.init, [ 9 | '$state', 10 | '$state.raw', 11 | '$derived', 12 | '$derived.by', 13 | ]); 14 | if (is_state) { 15 | state.output.issues.push( 16 | `You are reading the stateful variable "${node.name}" with a "$" prefix. Stateful variables are not stores and should be read without the "$". Please read it as a normal variable "${node.name.substring(1)}"`, 17 | ); 18 | } 19 | } 20 | } 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @sveltejs/mcp 2 | 3 | Repo for the official Svelte MCP server. 4 | 5 | ## Dev setup instructions 6 | 7 | ``` 8 | pnpm i 9 | cp apps/mcp-remote/.env.example apps/mcp-remote/.env 10 | pnpm dev 11 | ``` 12 | 13 | 1. Set the VOYAGE_API_KEY for embeddings support 14 | 15 | > [!NOTE] 16 | > Currently to prevent having a bunch of Timeout logs on vercel we shut down the SSE channel immediately. This means that we can't use `server.log` and we are not sending `list-changed` notifications. We can use elicitation and sampling since those are sent on the same stream of the POST request 17 | 18 | ### Local dev tools 19 | 20 | #### MCP inspector 21 | 22 | ``` 23 | pnpm run inspect 24 | ``` 25 | 26 | Then visit http://localhost:6274/ 27 | 28 | - Transport type: `Streamable HTTP` 29 | - http://localhost:5173/mcp 30 | 31 | #### Database inspector 32 | 33 | ``` 34 | pnpm run db:studio 35 | ``` 36 | 37 | https://local.drizzle.studio/ 38 | -------------------------------------------------------------------------------- /packages/mcp-server/src/mcp/autofixers/visitors/use-runes-instead-of-store.ts: -------------------------------------------------------------------------------- 1 | import type { Autofixer } from './index.js'; 2 | 3 | export const use_runes_instead_of_store: Autofixer = { 4 | ImportDeclaration(node, { state, next }) { 5 | const source = (node.source.value || node.source.raw?.slice(1, -1))?.toString(); 6 | if (source && source === 'svelte/store') { 7 | for (const specifier of node.specifiers) { 8 | if ( 9 | specifier.type === 'ImportSpecifier' && 10 | specifier.imported.type === 'Identifier' && 11 | ['derived', 'writable', 'readable'].includes(specifier.imported.name) 12 | ) { 13 | state.output.suggestions.push( 14 | `You are importing "${specifier.imported.name}" from "svelte/store". Unless the user specifically asked for stores or it's required because some library/component requires a store as input consider using runes like \`$state\` or \`$derived\` instead, all runes are globally available.`, 15 | ); 16 | } 17 | } 18 | } 19 | next(); 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /documentation/docs/30-capabilities/10-tools.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Tools 3 | --- 4 | 5 | The following tools are provided by the MCP server to the model you are using, which can decide to call one or more of them during a session: 6 | 7 | ## list-sections 8 | 9 | Provides a list of all the available documentation sections. 10 | 11 | ## get-documentation 12 | 13 | Allows the model to get the full (and up-to-date) documentation for the requested sections directly from [svelte.dev/docs](/docs). 14 | 15 | ## svelte-autofixer 16 | 17 | Uses static analysis to provide suggestions for code that your LLM generates. It can be invoked in an agentic loop by your model until all issues and suggestions are resolved. 18 | 19 | ## playground-link 20 | 21 | Generates an ephemeral playground link with the generated code. It's useful when the generated code is not written to a file in your project and you want to quickly test the generated solution. The code is not stored anywhere except the URL itself (which will often, as a consequence, be quite large). 22 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [main, redesign] 6 | pull_request: 7 | branches: [main] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout Repository 16 | uses: actions/checkout@v6 17 | 18 | - name: Setup pnpm 19 | uses: pnpm/action-setup@v4 20 | with: 21 | version: 10.26.0 22 | 23 | - name: Setup Node.js 24 | uses: actions/setup-node@v6 25 | with: 26 | node-version: '24' 27 | cache: 'pnpm' 28 | 29 | - name: Install dependencies 30 | run: pnpm i 31 | 32 | - name: Build project 33 | run: pnpm run build 34 | env: 35 | DATABASE_URL: file:test.db 36 | VOYAGE_API_KEY: dummy-key 37 | DATABASE_TOKEN: dummy-key 38 | 39 | - name: Run tests 40 | run: pnpm run test 41 | env: 42 | DATABASE_URL: file:test.db 43 | VOYAGE_API_KEY: dummy-key 44 | DATABASE_TOKEN: dummy-key 45 | -------------------------------------------------------------------------------- /packages/mcp-stdio/server.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", 3 | "name": "dev.svelte/mcp", 4 | "title": "Svelte MCP", 5 | "description": "The official Svelte MCP server providing docs and autofixing tools for Svelte development", 6 | "repository": { 7 | "id": "1054419133", 8 | "url": "https://github.com/sveltejs/mcp", 9 | "subfolder": "packages/mcp-stdio", 10 | "source": "github" 11 | }, 12 | "version": "0.1.15", 13 | "websiteUrl": "https://svelte.dev/docs/mcp/overview", 14 | "icons": [ 15 | { 16 | "src": "https://mcp.svelte.dev/logo.svg", 17 | "mimeType": "image/svg+xml" 18 | }, 19 | { 20 | "src": "https://mcp.svelte.dev/logo.png", 21 | "mimeType": "image/png" 22 | } 23 | ], 24 | "packages": [ 25 | { 26 | "registryType": "npm", 27 | "identifier": "@sveltejs/mcp", 28 | "version": "0.1.15", 29 | "runtimeHint": "npx", 30 | "transport": { 31 | "type": "stdio" 32 | } 33 | } 34 | ], 35 | "remotes": [ 36 | { 37 | "url": "https://mcp.svelte.dev/mcp", 38 | "type": "streamable-http" 39 | } 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /scripts/update-docs-prompts.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs/promises'; 2 | import path from 'node:path'; 3 | 4 | let content = `--- 5 | title: Prompts 6 | --- 7 | 8 | This is the list of available prompts provided by the MCP server. Prompts are selected by the user and are sent as a user message. They can be useful to write repetitive instructions for the LLM on how to properly use the MCP server. 9 | 10 | `; 11 | 12 | const prompts_generators = fs.glob('./packages/mcp-server/src/mcp/handlers/prompts/*.ts'); 13 | 14 | const filename_regex = /packages\/mcp-server\/src\/mcp\/handlers\/prompts\/(?.+)\.ts/; 15 | 16 | for await (const file of prompts_generators) { 17 | const title = file.match(filename_regex)?.groups?.prompt; 18 | if (title === 'index') continue; 19 | const module = await import(path.resolve('./', file)); 20 | content += `## ${title} 21 | 22 | ${module.docs_description} 23 | 24 |
25 | Copy the prompt 26 | 27 | \`\`\`md 28 | ${await module.generate_for_docs()} 29 | \`\`\` 30 | 31 |
32 | 33 | `; 34 | } 35 | 36 | await fs.writeFile('./documentation/docs/30-capabilities/30-prompts.md', content.trim() + '\n'); 37 | -------------------------------------------------------------------------------- /packages/mcp-server/src/mcp/autofixers/visitors/imported-runes.ts: -------------------------------------------------------------------------------- 1 | import { base_runes } from '../../../constants.js'; 2 | import type { Autofixer } from './index.js'; 3 | 4 | const dollarless_runes = base_runes.map((r) => r.replace('$', '')); 5 | 6 | export const imported_runes: Autofixer = { 7 | ImportDeclaration(node, { state, next }) { 8 | const source = (node.source.value || node.source.raw?.slice(1, -1))?.toString(); 9 | if (source && (source === 'svelte' || source.startsWith('svelte/'))) { 10 | for (const specifier of node.specifiers) { 11 | const id = 12 | specifier.type === 'ImportDefaultSpecifier' 13 | ? specifier.local 14 | : specifier.type === 'ImportNamespaceSpecifier' 15 | ? specifier.local 16 | : specifier.type === 'ImportSpecifier' 17 | ? specifier.imported 18 | : null; 19 | if (id && id.type === 'Identifier' && dollarless_runes.includes(id.name)) { 20 | state.output.suggestions.push( 21 | `You are importing "${id.name}" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$${id.name}" directly.`, 22 | ); 23 | } 24 | } 25 | } 26 | next(); 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /packages/mcp-stdio/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sveltejs/mcp", 3 | "version": "0.1.15", 4 | "type": "module", 5 | "license": "MIT", 6 | "mcpName": "dev.svelte/mcp", 7 | "homepage": "https://github.com/sveltejs/mcp#readme", 8 | "bugs": { 9 | "url": "https://github.com/sveltejs/mcp/issues" 10 | }, 11 | "bin": { 12 | "svelte-mcp": "./dist/index.mjs" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/sveltejs/mcp.git", 17 | "path": "packages/mcp-stdio" 18 | }, 19 | "files": [ 20 | "dist" 21 | ], 22 | "publishConfig": { 23 | "access": "public" 24 | }, 25 | "scripts": { 26 | "build": "tsdown && publint", 27 | "dev": "tsdown --watch", 28 | "test": "vitest", 29 | "check": "tsc --noEmit", 30 | "check:publint": "publint --strict", 31 | "update:version": "node scripts/update-version.ts" 32 | }, 33 | "devDependencies": { 34 | "@sveltejs/mcp-server": "workspace:^", 35 | "@tmcp/transport-stdio": "catalog:tmcp", 36 | "@types/node": "catalog:tooling", 37 | "publint": "catalog:tooling", 38 | "tsdown": "catalog:tooling", 39 | "typescript": "catalog:tooling", 40 | "vitest": "catalog:tooling" 41 | }, 42 | "dependencies": { 43 | "eslint": "catalog:lint", 44 | "tmcp": "catalog:tmcp" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/autofixer_request.yml: -------------------------------------------------------------------------------- 1 | name: 'Autofixer Request' 2 | description: Request a new Autofixer for the MCP 3 | title: '[Autofixer Request] ' 4 | labels: [enhancement, autofixer] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to request A new autofixer! 10 | - type: textarea 11 | id: problem 12 | attributes: 13 | label: Describe the problematic code 14 | description: Please provide a clear and concise description the problem. Is much better if you can provide a code snippet the AI constantly get's wrong. 15 | placeholder: The AI keeps messing with... 16 | validations: 17 | required: true 18 | - type: textarea 19 | id: suggestion 20 | attributes: 21 | label: Describe what the autofixer should suggest 22 | description: If you were looking at this code, what would you suggest to the AI for it to fix it? 23 | placeholder: You should never do this, instead do that... 24 | validations: 25 | required: true 26 | - type: dropdown 27 | id: importance 28 | attributes: 29 | label: Importance 30 | description: How important is this feature to you? 31 | options: 32 | - nice to have 33 | - would make my life easier 34 | - the MCP is useless to me without it 35 | validations: 36 | required: true 37 | -------------------------------------------------------------------------------- /apps/mcp-remote/static/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: 'Feature Request' 2 | description: Request a new MCP feature 3 | title: '[Feature Request] ' 4 | labels: [enhancement] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to request this feature! If your feature request is complex or substantial enough to warrant in-depth discussion, maintainers may close the issue and ask you to open an [RFC](https://github.com/sveltejs/rfcs). 10 | - type: textarea 11 | id: problem 12 | attributes: 13 | label: Describe the problem 14 | description: Please provide a clear and concise description the problem this feature would solve. The more information you can provide here, the better. 15 | placeholder: I'm always frustrated when... 16 | validations: 17 | required: true 18 | - type: textarea 19 | id: solution 20 | attributes: 21 | label: Describe the proposed solution 22 | description: Please provide a clear and concise description of what you would like to happen. 23 | placeholder: I would like to see... 24 | validations: 25 | required: true 26 | - type: dropdown 27 | id: importance 28 | attributes: 29 | label: Importance 30 | description: How important is this feature to you? 31 | options: 32 | - nice to have 33 | - would make my life easier 34 | - the MCP is useless to me without it 35 | validations: 36 | required: true 37 | -------------------------------------------------------------------------------- /packages/mcp-server/src/mcp/autofixers/add-compile-issues.ts: -------------------------------------------------------------------------------- 1 | import { compile as compile_component, compileModule } from 'svelte/compiler'; 2 | import { extname } from 'path'; 3 | import ts from 'ts-blank-space'; 4 | 5 | export function add_compile_issues( 6 | content: { issues: string[]; suggestions: string[] }, 7 | code: string, 8 | desired_svelte_version: number, 9 | filename = 'Component.svelte', 10 | async = false, 11 | ) { 12 | let compile = compile_component; 13 | const extension = extname(filename); 14 | if (extension !== '.svelte') { 15 | compile = compileModule; 16 | // compile module doesn't accept .ts files so we need to transpile them first with ts-blank-space 17 | // a fast and lightweight typescript transpiler that can strips types replacing them with white spaces 18 | // so the code positions are not affected 19 | if (extension === '.ts') { 20 | code = ts(code, (node) => { 21 | content.issues.push( 22 | `The provided file is a module but it contains invalid TypeScript code: ${node.getText()} at ${node.getStart()}`, 23 | ); 24 | }); 25 | } 26 | } 27 | const compilation_result = compile(code, { 28 | filename: filename || 'Component.svelte', 29 | generate: false, 30 | runes: desired_svelte_version >= 5, 31 | experimental: { async }, 32 | }); 33 | 34 | for (const warning of compilation_result.warnings) { 35 | content.issues.push( 36 | `${warning.message} at line ${warning.start?.line}, column ${warning.start?.column}`, 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /apps/mcp-remote/src/lib/assets/favicon.svg: -------------------------------------------------------------------------------- 1 | svelte-logo -------------------------------------------------------------------------------- /packages/mcp-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sveltejs/mcp-server", 3 | "version": "0.0.1", 4 | "private": true, 5 | "description": "", 6 | "main": "index.js", 7 | "keywords": [], 8 | "author": "", 9 | "license": "ISC", 10 | "type": "module", 11 | "scripts": { 12 | "test": "vitest", 13 | "generate-summaries": "node scripts/generate-summaries.ts --experimental-strip-types", 14 | "debug:generate-summaries": "DEBUG_MODE=1 node scripts/generate-summaries.ts --experimental-strip-types" 15 | }, 16 | "exports": { 17 | ".": "./src/index.ts" 18 | }, 19 | "peerDependencies": { 20 | "drizzle-orm": "^0.45.0" 21 | }, 22 | "dependencies": { 23 | "@mcp-ui/server": "catalog:ai", 24 | "@sveltejs/mcp-schema": "workspace:^", 25 | "@tmcp/adapter-valibot": "catalog:tmcp", 26 | "@tmcp/transport-in-memory": "catalog:tmcp", 27 | "@typescript-eslint/parser": "catalog:lint", 28 | "eslint": "catalog:lint", 29 | "eslint-plugin-svelte": "catalog:lint", 30 | "svelte": "catalog:svelte", 31 | "svelte-eslint-parser": "catalog:lint", 32 | "tmcp": "catalog:tmcp", 33 | "ts-blank-space": "catalog:tooling", 34 | "typescript-eslint": "catalog:lint", 35 | "valibot": "catalog:tooling", 36 | "vitest": "catalog:tooling", 37 | "zimmerframe": "catalog:tooling" 38 | }, 39 | "devDependencies": { 40 | "@anthropic-ai/sdk": "catalog:ai", 41 | "@sveltejs/kit": "catalog:svelte", 42 | "@types/eslint-scope": "catalog:lint", 43 | "@types/estree": "catalog:tooling", 44 | "@typescript-eslint/types": "catalog:lint", 45 | "dotenv": "catalog:tooling" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /.github/workflows/publish-mcp.yml: -------------------------------------------------------------------------------- 1 | name: Publish to MCP Registry 2 | 3 | on: 4 | workflow_call: 5 | secrets: 6 | MCP_KEY: 7 | required: true 8 | workflow_dispatch: 9 | 10 | jobs: 11 | publish-mcp: 12 | name: Publish to MCP Registry 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: checkout 16 | uses: actions/checkout@v6 17 | 18 | - name: Publish to MCP Registry 19 | working-directory: packages/mcp-stdio 20 | env: 21 | MCP_KEY: ${{ secrets.MCP_KEY }} 22 | run: | 23 | NAME=mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz 24 | # Download MCP Publisher pinned to v1.4.0 using latest https for security and save it to a file named mcp-publisher.tar.gz 25 | curl --proto '=https' --proto-redir '=https' --tlsv1.2 -fL "https://github.com/modelcontextprotocol/registry/releases/download/v1.4.0/$NAME" -O 26 | 27 | # Verify the SHA256 checksum of the downloaded file 28 | sha256sum --ignore-missing -c ./checksums/registry_1.4.0_checksums.txt 29 | 30 | # Extract the tarball 31 | mkdir tmp 32 | tar -xzf $NAME --no-same-owner --no-same-permissions -C tmp 33 | 34 | # Install the MCP Publisher binary 35 | install -m 0755 tmp/mcp-publisher . 36 | 37 | # Login using DNS 38 | ./mcp-publisher login dns --domain svelte.dev --private-key "${MCP_KEY}" 39 | 40 | # Publish to MCP Registry 41 | ./mcp-publisher publish 42 | -------------------------------------------------------------------------------- /packages/mcp-server/src/mcp/index.ts: -------------------------------------------------------------------------------- 1 | import { ValibotJsonSchemaAdapter } from '@tmcp/adapter-valibot'; 2 | import { McpServer } from 'tmcp'; 3 | import { setup_prompts, setup_resources, setup_tools } from './handlers/index.js'; 4 | import type { LibSQLDatabase } from 'drizzle-orm/libsql'; 5 | import type { Schema } from '@sveltejs/mcp-schema'; 6 | import { icons } from './icons/index.js'; 7 | 8 | export const server = new McpServer( 9 | { 10 | name: 'Svelte MCP', 11 | version: '0.0.1', 12 | description: 'The official Svelte MCP server implementation', 13 | websiteUrl: 'https://mcp.svelte.dev', 14 | icons, 15 | }, 16 | { 17 | adapter: new ValibotJsonSchemaAdapter(), 18 | capabilities: { 19 | tools: {}, 20 | prompts: {}, 21 | resources: {}, 22 | completions: {}, 23 | }, 24 | instructions: 25 | 'This is the official Svelte MCP server. It MUST be used whenever svelte development is involved. It can provide official documentation, code examples and correct your code. After you correct the component call this tool again to confirm all the issues are fixed.', 26 | }, 27 | ).withContext<{ 28 | db: LibSQLDatabase; 29 | track?: (sessionId: string, event: string, extra?: string) => Promise; 30 | }>(); 31 | 32 | export type SvelteMcp = typeof server; 33 | 34 | setup_tools(server); 35 | setup_resources(server); 36 | setup_prompts(server); 37 | 38 | server.on('initialize', async ({ clientInfo: client_info }) => { 39 | if (!server.ctx.custom?.track || !server.ctx.sessionId) return; 40 | server.ctx.custom.track(server.ctx.sessionId, 'initialize', client_info.name); 41 | }); 42 | -------------------------------------------------------------------------------- /packages/mcp-server/src/mcp/autofixers/visitors/derived-with-function.ts: -------------------------------------------------------------------------------- 1 | import type { Identifier, PrivateIdentifier } from 'estree'; 2 | import type { Autofixer } from './index.js'; 3 | 4 | export const derived_with_function: Autofixer = { 5 | CallExpression(node, { state, path }) { 6 | if ( 7 | node.callee.type === 'Identifier' && 8 | node.callee.name === '$derived' && 9 | state.parsed.is_rune(node, ['$derived']) && 10 | (node.arguments[0]?.type === 'ArrowFunctionExpression' || 11 | node.arguments[0]?.type === 'FunctionExpression') 12 | ) { 13 | const parent = path[path.length - 1]; 14 | let variable_id: Identifier | PrivateIdentifier | undefined; 15 | if (parent?.type === 'VariableDeclarator' && parent.id.type === 'Identifier') { 16 | // const something = $derived(...) 17 | variable_id = parent.id; 18 | } else if (parent?.type === 'PropertyDefinition') { 19 | // class X { something = $derived(...) } 20 | variable_id = 21 | parent.key.type === 'Identifier' 22 | ? parent.key 23 | : parent.key.type === 'PrivateIdentifier' 24 | ? parent.key 25 | : undefined; 26 | } else if (parent?.type === 'AssignmentExpression') { 27 | // this.something = $derived(...) 28 | variable_id = 29 | parent.left.type === 'MemberExpression' 30 | ? parent.left.property.type === 'Identifier' 31 | ? parent.left.property 32 | : undefined 33 | : undefined; 34 | } 35 | 36 | state.output.suggestions.push( 37 | `You are passing a function to $derived ${variable_id ? `when declaring "${variable_id.name}" ` : ''}but $derived expects an expression. You can use $derived.by instead.`, 38 | ); 39 | } 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - ./packages/* 3 | - ./apps/* 4 | 5 | catalogs: 6 | ai: 7 | '@anthropic-ai/sdk': ^0.71.0 8 | '@mcp-ui/server': ^5.12.0 9 | '@modelcontextprotocol/inspector': ^0.18.0 10 | lint: 11 | '@eslint/compat': ^2.0.0 12 | '@eslint/js': ^9.36.0 13 | '@types/eslint-scope': ^8.3.2 14 | '@typescript-eslint/parser': ^8.44.0 15 | '@typescript-eslint/types': ^8.44.0 16 | eslint: ^9.36.0 17 | eslint-config-prettier: ^10.0.1 18 | eslint-plugin-import: ^2.32.0 19 | eslint-plugin-pnpm: ^1.3.0 20 | eslint-plugin-svelte: ^3.12.5 21 | globals: ^16.0.0 22 | prettier: ^3.4.2 23 | prettier-plugin-svelte: ^3.3.3 24 | svelte-eslint-parser: ^1.4.0 25 | typescript-eslint: ^8.44.0 26 | orm: 27 | '@libsql/client': ^0.15.0 28 | drizzle-kit: ^0.31.0 29 | drizzle-orm: ^0.45.0 30 | svelte: 31 | '@sveltejs/adapter-vercel': ^6.0.0 32 | '@sveltejs/kit': ^2.42.2 33 | '@sveltejs/vite-plugin-svelte': ^6.0.0 34 | svelte: ^5.39.2 35 | svelte-check: ^4.0.0 36 | tmcp: 37 | '@tmcp/adapter-valibot': ^0.1.4 38 | '@tmcp/transport-http': ^0.8.3 39 | '@tmcp/transport-in-memory': ^0.0.5 40 | '@tmcp/transport-stdio': ^0.4.0 41 | tmcp: ^1.19.0 42 | tooling: 43 | '@changesets/cli': ^2.29.7 44 | '@svitejs/changesets-changelog-github-compact': ^1.2.0 45 | '@types/estree': ^1.0.8 46 | '@types/node': ^24.3.1 47 | '@vercel/analytics': ^1.5.0 48 | dotenv: ^17.2.3 49 | node-resolve-ts: ^1.0.2 50 | publint: ^0.3.13 51 | ts-blank-space: ^0.6.2 52 | tsdown: ^0.18.0 53 | typescript: ^5.0.0 54 | valibot: ^1.1.0 55 | vite: ^7.0.4 56 | vite-plugin-devtools-json: ^1.0.0 57 | vitest: ^4.0.0 58 | zimmerframe: ^1.1.4 59 | 60 | useNodeVersion: 22.19.0 61 | -------------------------------------------------------------------------------- /apps/mcp-remote/src/hooks.server.ts: -------------------------------------------------------------------------------- 1 | import { dev } from '$app/environment'; 2 | import { http_transport } from '$lib/mcp/index.js'; 3 | import { db } from '$lib/server/db/index.js'; 4 | import { redirect } from '@sveltejs/kit'; 5 | import { track } from '@vercel/analytics/server'; 6 | 7 | export async function handle({ event, resolve }) { 8 | if (event.request.method === 'GET') { 9 | const accept = event.request.headers.get('accept'); 10 | if (accept) { 11 | const accepts = accept.split(','); 12 | if (!accepts.includes('text/event-stream')) { 13 | // the request it's a browser request, not an MCP client request 14 | // it means someone probably opened it from the docs...we should redirect to the docs 15 | redirect(302, 'https://svelte.dev/docs/mcp/overview'); 16 | } 17 | } 18 | } 19 | const mcp_response = await http_transport.respond(event.request, { 20 | db, 21 | // only add analytics in production 22 | track: dev 23 | ? undefined 24 | : async (session_id, event, extra) => { 25 | await track(event, { session_id, ...(extra ? { extra } : {}) }); 26 | }, 27 | }); 28 | // we are deploying on vercel the SSE connection will timeout after 5 minutes...for 29 | // the moment we are not sending back any notifications (logs, or list changed notifications) 30 | // so it's a waste of resources to keep a connection open that will error 31 | // after 5 minutes making the logs dirty. For this reason if we have a response from 32 | // the MCP server and it's a GET request we just return an empty response (it has to be 33 | // 200 or the MCP client will complain) 34 | if (mcp_response && event.request.method === 'GET') { 35 | try { 36 | return mcp_response; 37 | } finally { 38 | try { 39 | await mcp_response.body?.cancel(); 40 | } catch { 41 | // ignore 42 | } 43 | } 44 | } 45 | return mcp_response ?? resolve(event); 46 | } 47 | -------------------------------------------------------------------------------- /packages/mcp-server/src/mcp/handlers/tools/list-sections.ts: -------------------------------------------------------------------------------- 1 | import type { SvelteMcp } from '../../index.js'; 2 | import { format_sections_list } from '../../utils.js'; 3 | import { SECTIONS_LIST_INTRO, SECTIONS_LIST_OUTRO } from './prompts.js'; 4 | import { icons } from '../../icons/index.js'; 5 | import { tool } from 'tmcp/utils'; 6 | 7 | export function list_sections(server: SvelteMcp) { 8 | server.tool( 9 | { 10 | name: 'list-sections', 11 | description: 12 | 'Lists all available Svelte 5 and SvelteKit documentation sections in a structured format. Each section includes a "use_cases" field that describes WHEN this documentation would be useful. You should carefully analyze the use_cases field to determine which sections are relevant for the user\'s query. The use_cases contain comma-separated keywords describing project types (e.g., "e-commerce", "blog"), features (e.g., "authentication", "forms"), components (e.g., "slider", "modal"), development stages (e.g., "deployment", "testing"), or "always" for fundamental concepts. Match these use_cases against the user\'s intent - for example, if building an e-commerce site, fetch sections with use_cases containing "e-commerce", "product listings", "shopping cart", etc. If building a slider, look for "slider", "carousel", "animation", etc. Returns sections as "* title: [section_title], use_cases: [use_cases], path: [file_path]". Always run list-sections FIRST for any Svelte query, then analyze ALL use_cases to identify relevant sections, and finally use get_documentation to fetch ALL relevant sections at once.', 13 | icons, 14 | }, 15 | async () => { 16 | if (server.ctx.sessionId && server.ctx.custom?.track) { 17 | await server.ctx.custom?.track?.(server.ctx.sessionId, 'list-sections'); 18 | } 19 | const formatted_sections = await format_sections_list(); 20 | 21 | return tool.text(`${SECTIONS_LIST_INTRO}\n\n${formatted_sections}\n\n${SECTIONS_LIST_OUTRO}`); 22 | }, 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /packages/mcp-server/src/mcp/autofixers/visitors/suggest-attachments.ts: -------------------------------------------------------------------------------- 1 | import type { Identifier } from 'estree'; 2 | import type { Autofixer } from './index.js'; 3 | import { left_most_id } from '../ast/utils.js'; 4 | 5 | export const suggest_attachments: Autofixer = { 6 | SvelteDirective(node, { state, next, path }) { 7 | if (node.kind === 'Binding' && node.key.name.name === 'this') { 8 | const parent_element = path.findLast((p) => p.type === 'SvelteElement'); 9 | if (parent_element?.kind === 'html' && parent_element.startTag.attributes.includes(node)) { 10 | let better_an_attachment = ` or even better an \`attachment\``; 11 | if (state.desired_svelte_version === 4) { 12 | better_an_attachment = ``; 13 | } 14 | state.output.suggestions.push( 15 | `The usage of \`bind:this\` can often be replaced with an easier to read \`action\`${better_an_attachment}. Consider using the latter if possible.`, 16 | ); 17 | } 18 | } else if (node.kind === 'Action' && state.desired_svelte_version === 5) { 19 | let id: Identifier | null = null; 20 | if (node.key.name.type === 'Identifier') { 21 | id = node.key.name; 22 | } else if (node.key.name.type === 'MemberExpression') { 23 | id = left_most_id(node.key.name); 24 | } 25 | if (id) { 26 | const reference = state.parsed.find_reference_by_id(id); 27 | const definition = reference?.resolved?.defs[0]; 28 | if ( 29 | definition && 30 | (definition.type === 'Variable' || 31 | !(definition.type === 'ImportBinding' || definition.type === 'Parameter')) && 32 | !( 33 | definition.type === 'Variable' && 34 | definition.node.init?.type === 'CallExpression' && 35 | state.parsed.is_rune(definition.node.init, ['$props']) 36 | ) 37 | ) { 38 | state.output.suggestions.push( 39 | `Consider using an \`attachment\` instead of an \`action\` for "${id.name}".`, 40 | ); 41 | } 42 | } 43 | } 44 | next(); 45 | }, 46 | }; 47 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: "\U0001F41E Bug report" 2 | description: Report an issue with Svelte 3 | title: '[Bug] ' 4 | labels: ['triage: bug'] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to fill out this bug report! 10 | - type: textarea 11 | id: bug-description 12 | attributes: 13 | label: Describe the bug 14 | description: A clear and concise description of what the bug is. If you intend to submit a PR for this issue, tell us in the description. Thanks! 15 | placeholder: Bug description 16 | validations: 17 | required: true 18 | - type: textarea 19 | id: reproduction 20 | attributes: 21 | label: Reproduction 22 | description: Please provide a link to a repo or REPL that can reproduce the problem you ran into. If a report is vague (e.g. just a generic error message) and has no reproduction, it will receive a "need reproduction" label. If no reproduction is provided within a reasonable time-frame, the issue will be closed. 23 | placeholder: Reproduction 24 | validations: 25 | required: true 26 | - type: textarea 27 | id: logs 28 | attributes: 29 | label: Logs 30 | description: 'Please provide some logs or screenshot of the agentic workflow failing.' 31 | render: shell 32 | - type: input 33 | id: mcp-client 34 | attributes: 35 | label: MCP Client 36 | description: Which MCP client are you using? 37 | render: shell 38 | placeholder: claude-code, codex, opencode 39 | validations: 40 | required: true 41 | - type: dropdown 42 | id: severity 43 | attributes: 44 | label: Severity 45 | description: Select the severity of this issue 46 | options: 47 | - annoyance 48 | - minor functionality loss 49 | - major functionality loss 50 | - blocking all usage of the mcp 51 | validations: 52 | required: true 53 | -------------------------------------------------------------------------------- /packages/mcp-server/src/mcp/utils.ts: -------------------------------------------------------------------------------- 1 | import * as v from 'valibot'; 2 | import { documentation_sections_schema } from '../lib/schemas.js'; 3 | import summary_data from '../use_cases.json' with { type: 'json' }; 4 | 5 | export async function fetch_with_timeout( 6 | url: string, 7 | timeout_ms: number = 10000, 8 | ): Promise { 9 | try { 10 | const response = await fetch(url, { signal: AbortSignal.timeout(timeout_ms) }); 11 | return response; 12 | } catch (error) { 13 | if (error instanceof Error && error.name === 'AbortError') { 14 | throw new Error(`Request timed out after ${timeout_ms}ms`); 15 | } 16 | throw error; 17 | } 18 | } 19 | 20 | const summaries = (summary_data.summaries || {}) as Record; 21 | 22 | export async function get_sections() { 23 | const sections = await fetch_with_timeout( 24 | 'https://svelte.dev/docs/experimental/sections.json', 25 | ).then((res) => res.json()); 26 | const validated_sections = v.safeParse(documentation_sections_schema, sections); 27 | if (!validated_sections.success) return []; 28 | 29 | const mapped_sections = Object.entries(validated_sections.output).map(([, section]) => { 30 | const original_slug = section.slug; 31 | const cleaned_slug = original_slug.startsWith('docs/') 32 | ? original_slug.slice('docs/'.length) 33 | : original_slug; 34 | 35 | return { 36 | title: section.metadata.title, 37 | use_cases: 38 | section.metadata.use_cases ?? 39 | summaries[original_slug] ?? 40 | summaries[cleaned_slug] ?? 41 | 'use title and path to estimate use case', 42 | slug: cleaned_slug, 43 | // Use original slug in URL to ensure it still works 44 | url: `https://svelte.dev/${original_slug}/llms.txt`, 45 | }; 46 | }); 47 | 48 | return mapped_sections; 49 | } 50 | 51 | export async function format_sections_list() { 52 | const sections = await get_sections(); 53 | return sections 54 | .map((s) => `- title: ${s.title}, use_cases: ${s.use_cases}, path: ${s.slug}`) 55 | .join('\n'); 56 | } 57 | -------------------------------------------------------------------------------- /packages/mcp-schema/src/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @import { Column } from 'drizzle-orm'; 3 | */ 4 | import { sql } from 'drizzle-orm'; 5 | import { customType } from 'drizzle-orm/sqlite-core'; 6 | 7 | /** 8 | * Helper function to convert an array of embeddings into a format that can be inserted into a LibSQL vector column. 9 | * @param {number[]} arr The embeddings array. 10 | */ 11 | export function vector(arr) { 12 | return sql`vector32(${JSON.stringify(arr)})`; 13 | } 14 | 15 | /** 16 | * Helper function to calculate the distance between a vector column and an array of embeddings and return it as a columns. 17 | * @param {Column} column The drizzle column representing the vector. 18 | * @param {number} arr The embeddings array. 19 | * @param {string} as The name of the returned column. Default is 'distance'. 20 | * 21 | * @example 22 | * await db.select({ 23 | * id: vector_table.id, 24 | * text: vector_table.text, 25 | * distance: distance(vector_table.vector, await get_embeddings(sentence)), 26 | * }) 27 | * .from(vector_table) 28 | * .orderBy(sql`distance`) 29 | * .execute(); 30 | */ 31 | export function distance(column, arr, as = 'distance') { 32 | return /** @type {typeof sql} */ ( 33 | sql 34 | )`CASE ${column} ISNULL WHEN 1 THEN 1 ELSE vector_distance_cos(${column}, vector32(${JSON.stringify(arr)})) END`.as( 35 | as, 36 | ); 37 | } 38 | 39 | /** 40 | * Custom drizzle type to use the LibSQL vector column type. 41 | */ 42 | export const float_32_array = /** @type {typeof customType<{ 43 | data: number[]; 44 | config: { dimensions: number }; 45 | configRequired: true; 46 | driverData: Buffer; 47 | }>} */ (customType)({ 48 | dataType(config) { 49 | return `F32_BLOB(${config.dimensions})`; 50 | }, 51 | /** 52 | * @param {Buffer} value 53 | */ 54 | fromDriver(value) { 55 | return Array.from(new Float32Array(value.buffer)); 56 | }, 57 | /** 58 | * 59 | * @param {number[]} value 60 | * @returns 61 | */ 62 | toDriver(value) { 63 | return vector(value); 64 | }, 65 | }); 66 | -------------------------------------------------------------------------------- /packages/mcp-server/src/mcp/handlers/resources/doc-section.ts: -------------------------------------------------------------------------------- 1 | import type { SvelteMcp } from '../../index.js'; 2 | import { get_sections, fetch_with_timeout } from '../../utils.js'; 3 | import { icons } from '../../icons/index.js'; 4 | import { resource } from 'tmcp/utils'; 5 | 6 | export async function list_sections(server: SvelteMcp) { 7 | const sections = await get_sections(); 8 | 9 | server.template( 10 | { 11 | name: 'Svelte-Doc-Section', 12 | description: 'A single documentation section', 13 | list() { 14 | return sections.map((section) => { 15 | const section_name = section.slug; 16 | const resource_name = section_name; 17 | const resource_uri = `svelte://${section_name}.md`; 18 | return { 19 | name: resource_name, 20 | description: section.use_cases, 21 | uri: resource_uri, 22 | title: section.title, 23 | }; 24 | }); 25 | }, 26 | complete: { 27 | slug: (query) => { 28 | const values = sections 29 | .reduce((acc, section) => { 30 | const section_name = section.slug; 31 | const resource_name = section_name; 32 | if (section_name.includes(query.toLowerCase())) { 33 | acc.push(resource_name); 34 | } 35 | return acc; 36 | }, []) 37 | // there's a hard limit of 100 for completions 38 | .slice(0, 100); 39 | return { 40 | completion: { 41 | values, 42 | }, 43 | }; 44 | }, 45 | }, 46 | uri: 'svelte://{/slug*}.md', 47 | icons, 48 | }, 49 | async (uri, { slug }) => { 50 | if (server.ctx.sessionId && server.ctx.custom?.track) { 51 | await server.ctx.custom?.track?.( 52 | server.ctx.sessionId, 53 | 'svelte-doc-section', 54 | Array.isArray(slug) ? slug.join(',') : slug, 55 | ); 56 | } 57 | const section = sections.find((section) => { 58 | return slug === section.slug; 59 | }); 60 | if (!section) throw new Error(`Section not found: ${slug}`); 61 | const response = await fetch_with_timeout(section.url); 62 | const content = await response.text(); 63 | return resource.text(uri, content); 64 | }, 65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /packages/mcp-server/src/mcp/autofixers/visitors/wrong-property-access-state.ts: -------------------------------------------------------------------------------- 1 | import type { Autofixer } from './index.js'; 2 | import { left_most_id } from '../ast/utils.js'; 3 | 4 | const UPDATE_PROPERTIES = new Set(['set', 'update', '$']); 5 | const METHODS = new Set(['set', 'update']); 6 | 7 | export const wrong_property_access_state: Autofixer = { 8 | MemberExpression(node, { state, next, path }) { 9 | const parent = path[path.length - 1]; 10 | let is_property = false; 11 | if ( 12 | node.property.type === 'Identifier' && 13 | ((is_property = !METHODS.has(node.property.name)) || 14 | (parent?.type === 'CallExpression' && parent.callee === node)) && 15 | UPDATE_PROPERTIES.has(node.property.name) 16 | ) { 17 | const id = left_most_id(node); 18 | if (id) { 19 | const reference = state.parsed.find_reference_by_id(id); 20 | const definition = reference?.resolved?.defs[0]; 21 | if (definition && definition.type === 'Variable') { 22 | const init = definition.node.init; 23 | if ( 24 | init?.type === 'CallExpression' && 25 | state.parsed.is_rune(init, ['$state', '$state.raw', '$derived', '$derived.by']) 26 | ) { 27 | let suggestion = is_property 28 | ? `You are trying to read the stateful variable "${id.name}" using "${node.property.name}". stateful variables should be read just by accessing them like normal variable, do not use properties to read them.` 29 | : `You are trying to update the stateful variable "${id.name}" using "${node.property.name}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them.`; 30 | const argument = init.arguments[0]; 31 | if (!argument || (argument.type !== 'Literal' && argument.type !== 'ArrayExpression')) { 32 | suggestion += ` However I can't verify if "${id.name}" is a state variable of an object or a class with a "${node.property.name}" ${is_property ? 'property' : 'method'} on it. Please verify that before updating the code to use a normal ${is_property ? 'access' : 'assignment'}`; 33 | } 34 | state.output.suggestions.push(suggestion); 35 | } 36 | } 37 | } 38 | } 39 | next(); 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sveltejs/mcp-mono", 3 | "version": "0.0.1", 4 | "description": "The official Svelte MCP server implementation", 5 | "type": "module", 6 | "packageManager": "pnpm@10.26.0", 7 | "scripts": { 8 | "build": "pnpm -r run build", 9 | "dev": "pnpm --filter @sveltejs/mcp-remote run dev", 10 | "check": "pnpm -r run check", 11 | "check:publint": "pnpm -r run check:publint", 12 | "format": "prettier --write .", 13 | "lint": "prettier --check . && eslint .", 14 | "lint:fix": "prettier --write . && eslint . --fix", 15 | "lint:inspect": "pnpm dlx @eslint/config-inspector", 16 | "node:inspect": "pnpm dlx node-modules-inspector", 17 | "test:unit": "vitest", 18 | "test": "npm run test:unit -- --run", 19 | "test:watch": "npm run test:unit -- --watch", 20 | "inspect": "pnpm mcp-inspector", 21 | "generate-summaries": "pnpm --filter @sveltejs/mcp-server run generate-summaries", 22 | "generate-prompt-docs": "node --import node-resolve-ts/register scripts/update-docs-prompts.ts", 23 | "debug:generate-summaries": "pnpm --filter @sveltejs/mcp-server run debug:generate-summaries", 24 | "release": "pnpm --filter @sveltejs/mcp run build && changeset publish", 25 | "changeset:version": "changeset version && pnpm --filter @sveltejs/mcp run update:version && git add --all" 26 | }, 27 | "keywords": [ 28 | "svelte", 29 | "tmcp", 30 | "mcp", 31 | "server" 32 | ], 33 | "private": true, 34 | "devDependencies": { 35 | "@changesets/cli": "catalog:tooling", 36 | "@eslint/compat": "catalog:lint", 37 | "@eslint/js": "catalog:lint", 38 | "@modelcontextprotocol/inspector": "catalog:ai", 39 | "@sveltejs/adapter-vercel": "catalog:svelte", 40 | "@svitejs/changesets-changelog-github-compact": "catalog:tooling", 41 | "eslint": "catalog:lint", 42 | "eslint-config-prettier": "catalog:lint", 43 | "eslint-plugin-import": "catalog:lint", 44 | "eslint-plugin-pnpm": "catalog:lint", 45 | "eslint-plugin-svelte": "catalog:lint", 46 | "globals": "catalog:lint", 47 | "node-resolve-ts": "catalog:tooling", 48 | "prettier": "catalog:lint", 49 | "prettier-plugin-svelte": "catalog:lint", 50 | "publint": "catalog:tooling", 51 | "typescript": "catalog:tooling", 52 | "typescript-eslint": "catalog:lint", 53 | "vitest": "catalog:tooling" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /apps/mcp-remote/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sveltejs/mcp-remote", 3 | "version": "0.0.1", 4 | "description": "The official Svelte MCP server implementation", 5 | "type": "module", 6 | "main": "src/index.js", 7 | "bin": { 8 | "svelte-mcp": "./dist/lib/stdio.js" 9 | }, 10 | "scripts": { 11 | "start": "node src/index.js", 12 | "dev": "vite dev", 13 | "build": "vite build", 14 | "build:mcp": "tsc --project tsconfig.build.json", 15 | "prepublishOnly": "pnpm build:mcp", 16 | "preview": "vite preview", 17 | "prepare": "svelte-kit sync || echo ''", 18 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 19 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 20 | "format": "prettier --write .", 21 | "lint": "prettier --check . && eslint .", 22 | "lint:fix": "prettier --write . && eslint . --fix", 23 | "test:unit": "vitest", 24 | "test": "npm run test:unit -- --run", 25 | "test:watch": "npm run test:unit -- --watch", 26 | "db:push": "drizzle-kit push", 27 | "db:generate": "drizzle-kit generate", 28 | "db:migrate": "drizzle-kit migrate", 29 | "db:studio": "drizzle-kit studio", 30 | "inspect": "pnpm mcp-inspector" 31 | }, 32 | "keywords": [ 33 | "svelte", 34 | "tmcp", 35 | "mcp", 36 | "server" 37 | ], 38 | "private": true, 39 | "devDependencies": { 40 | "@eslint/compat": "catalog:lint", 41 | "@eslint/js": "catalog:lint", 42 | "@libsql/client": "catalog:orm", 43 | "@modelcontextprotocol/inspector": "catalog:ai", 44 | "@sveltejs/adapter-vercel": "catalog:svelte", 45 | "@sveltejs/kit": "catalog:svelte", 46 | "@sveltejs/vite-plugin-svelte": "catalog:svelte", 47 | "@types/node": "catalog:tooling", 48 | "@typescript-eslint/parser": "catalog:lint", 49 | "drizzle-kit": "catalog:orm", 50 | "drizzle-orm": "catalog:orm", 51 | "eslint-config-prettier": "catalog:lint", 52 | "eslint-plugin-svelte": "catalog:lint", 53 | "globals": "catalog:lint", 54 | "prettier": "catalog:lint", 55 | "prettier-plugin-svelte": "catalog:lint", 56 | "svelte": "catalog:svelte", 57 | "svelte-check": "catalog:svelte", 58 | "svelte-eslint-parser": "catalog:lint", 59 | "typescript": "catalog:tooling", 60 | "vite": "catalog:tooling", 61 | "vite-plugin-devtools-json": "catalog:tooling", 62 | "vitest": "catalog:tooling" 63 | }, 64 | "dependencies": { 65 | "@sveltejs/mcp-schema": "workspace:^", 66 | "@sveltejs/mcp-server": "workspace:^", 67 | "@tmcp/transport-http": "catalog:tmcp", 68 | "@vercel/analytics": "catalog:tooling", 69 | "tmcp": "catalog:tmcp" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /packages/mcp-stdio/checksums/registry_1.4.0_checksums.txt: -------------------------------------------------------------------------------- 1 | eb5f89b76fc45a97070fa481eb03977584a78e3dff2781402d43482f114e4d6a mcp-publisher_darwin_amd64.tar.gz 2 | 29fc1b46a6f6be2580129369a9b681204b11b425d9a44147d79c8c658c7b8474 mcp-publisher_darwin_amd64.tar.gz.sbom.json 3 | 9eddbbb95efd54b9503f6c0668f43bab3f04c856946d3c7164f6daead232402f mcp-publisher_darwin_arm64.tar.gz 4 | a8349b0ea7916f34cf4ee4e1ced5b91bc1ded6d100312cbb2095da018710da04 mcp-publisher_darwin_arm64.tar.gz.sbom.json 5 | c4b402b43a85166c3f840641ca1c9e6de5bfa1cf533c22576d663ccbda0711bb mcp-publisher_linux_amd64.tar.gz 6 | 6f21cf917055be885f16b93154e06379540236599cfad6af4404a8323bde74b7 mcp-publisher_linux_amd64.tar.gz.sbom.json 7 | ba5d486f86b2cef48ea506e8314d901a5169dcd56a5d6e9daf18d41244316235 mcp-publisher_linux_arm64.tar.gz 8 | 8a0096a407b916ac7270732df017a26f6112c73a066252f6556b8956c492a0b4 mcp-publisher_linux_arm64.tar.gz.sbom.json 9 | 59ee8c4a997f94794db8db13f8809666631686a70a8d89a9f0fea993f9aede0f mcp-publisher_windows_amd64.tar.gz 10 | 57d211fd9181f698d126bd773b55c98b92454d19b1e32e77860766179a8a2e8e mcp-publisher_windows_amd64.tar.gz.sbom.json 11 | 1410952b0a5968cbe89590e7b4ee6105147ef7267cf0cd50095c9bec2ee3b0d7 mcp-publisher_windows_arm64.tar.gz 12 | 6cb93e118a89ed1419135bfbaa7401bd3b7a7c5680a0d8fd7c78728f9d860630 mcp-publisher_windows_arm64.tar.gz.sbom.json 13 | ebc17c3b7a5b86f9c036acf1d44fb904bb363bad0ac1ac37b7979eb17cf3d218 registry-1.4.0.tar.gz 14 | 5fffe8b078513fa5fbb625a213d164bb391c7c85e216e541cab789517bc6365b registry_darwin_amd64.tar.gz 15 | 10a61cf4173d8b5be63044af0a10e6c809eebc1006c0c1643753a252db808ddd registry_darwin_amd64.tar.gz.sbom.json 16 | 437746a1045f093266ad7298a47be41dc44cc33ecaeec145449c4eefeddf8880 registry_darwin_arm64.tar.gz 17 | 4c0cb24bcef0658540fb044d771294efd662edecd2f7fae7b1ca7ca2ae68f83a registry_darwin_arm64.tar.gz.sbom.json 18 | bd77fafcc881714a63a375a9bdb53a761a2b8e367a9d2759835126c993df2356 registry_linux_amd64.tar.gz 19 | d62a461b174089fbcaa4f1ac096bca491d18bc7f70e93ce0824fe89dbe42e974 registry_linux_amd64.tar.gz.sbom.json 20 | 38cb9e6112ff11544ba8ec88c5c0f44d4c851504ec2e33d497e25616a6f7a21e registry_linux_arm64.tar.gz 21 | 84e5a004929e7231ae35350a1fe8fb08668fc05183e13d1d78004abf08a25f3b registry_linux_arm64.tar.gz.sbom.json 22 | 77ca9243d2f744f282b39d07625f802d77f593850a0392debabe407643a8579c registry_windows_amd64.tar.gz 23 | bb043e8a6a8d187ffab8987d36d0018024115d9217af6d2ddd233f94aea880ea registry_windows_amd64.tar.gz.sbom.json 24 | e4ee50e05a95f288b874f5e24c7716a5032548feeabc83b715193298cec06890 registry_windows_arm64.tar.gz 25 | 49f189b615bce3d09ea24ae5d80e0816ac69225b3c8901dd7be19ef9ca06830c registry_windows_arm64.tar.gz.sbom.json 26 | -------------------------------------------------------------------------------- /documentation/docs/10-introduction/10-overview.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Overview 3 | --- 4 | 5 | The Svelte MCP ([Model Context Protocol](https://modelcontextprotocol.io/docs/getting-started/intro)) server can help your LLM or agent of choice write better Svelte code. It works by providing documentation relevant to the task at hand, and statically analysing generated code so that it can suggest fixes and best practices. 6 | 7 | ## Setup 8 | 9 | The setup varies based on the version of the MCP you prefer — remote or local — and your chosen MCP client (e.g. Claude Code, Codex CLI or GitHub Copilot): 10 | 11 | - [local setup](local-setup) using `@sveltejs/mcp` 12 | - [remote setup](remote-setup) using `https://mcp.svelte.dev/mcp` 13 | 14 | ## Usage 15 | 16 | To get the most out of the MCP server we recommend including the following prompt in your [`AGENTS.md`](https://agents.md) (or [`CLAUDE.md`](https://docs.claude.com/en/docs/claude-code/memory#claude-md-imports), if using Claude Code. Or [`GEMINI.md`](https://geminicli.com/docs/cli/gemini-md/), if using GEMINI). This will tell the LLM which tools are available and when it's appropriate to use them. 17 | 18 | > [!NOTE] This is already setup for you when using `npx sv add mcp` 19 | 20 | ```md 21 | You are able to use the Svelte MCP server, where you have access to comprehensive Svelte 5 and SvelteKit documentation. Here's how to use the available tools effectively: 22 | 23 | ## Available MCP Tools: 24 | 25 | ### 1. list-sections 26 | 27 | Use this FIRST to discover all available documentation sections. Returns a structured list with titles, use_cases, and paths. 28 | When asked about Svelte or SvelteKit topics, ALWAYS use this tool at the start of the chat to find relevant sections. 29 | 30 | ### 2. get-documentation 31 | 32 | Retrieves full documentation content for specific sections. Accepts single or multiple sections. 33 | After calling the list-sections tool, you MUST analyze the returned documentation sections (especially the use_cases field) and then use the get-documentation tool to fetch ALL documentation sections that are relevant for the user's task. 34 | 35 | ### 3. svelte-autofixer 36 | 37 | Analyzes Svelte code and returns issues and suggestions. 38 | You MUST use this tool whenever writing Svelte code before sending it to the user. Keep calling it until no issues or suggestions are returned. 39 | 40 | ### 4. playground-link 41 | 42 | Generates a Svelte Playground link with the provided code. 43 | After completing the code, ask the user if they want a playground link. Only call this tool after user confirmation and NEVER if code was written to files in their project. 44 | ``` 45 | 46 | If your MCP client supports it, we also recommend using the [svelte-task](prompts#svelte-task) prompt to instruct the LLM on the best way to use the MCP server. 47 | -------------------------------------------------------------------------------- /.github/workflows/update-prompt-docs.yml: -------------------------------------------------------------------------------- 1 | name: Update Prompt Documentation 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - 'packages/mcp-server/src/mcp/handlers/prompts/**' 9 | 10 | permissions: 11 | contents: write 12 | pull-requests: write 13 | 14 | jobs: 15 | update-docs: 16 | # prevents this action from running on forks 17 | if: github.repository == 'sveltejs/mcp' 18 | name: Update Prompt Documentation 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v6 23 | with: 24 | fetch-depth: 0 25 | 26 | - name: Setup Node.js 27 | uses: actions/setup-node@v6 28 | with: 29 | node-version: 24 30 | package-manager-cache: false # pnpm is not installed yet 31 | 32 | - name: Install pnpm 33 | shell: bash 34 | run: | 35 | PNPM_VER=$(jq -r '.packageManager | if .[0:5] == "pnpm@" then .[5:] else "packageManager in package.json does not start with pnpm@\n" | halt_error(1) end' package.json) 36 | echo installing pnpm version $PNPM_VER 37 | npm i -g pnpm@$PNPM_VER 38 | 39 | - name: Setup Node.js with pnpm cache 40 | uses: actions/setup-node@v6 41 | with: 42 | node-version: 24 43 | package-manager-cache: true # caches pnpm via packageManager field in package.json 44 | 45 | - name: Install dependencies 46 | run: pnpm install --frozen-lockfile --prefer-offline --ignore-scripts 47 | 48 | - name: Generate prompt documentation 49 | run: pnpm generate-prompt-docs 50 | 51 | - name: Check for changes 52 | id: git-check 53 | run: | 54 | git diff --exit-code documentation/docs/30-capabilities/30-prompts.md || echo "changed=true" >> $GITHUB_OUTPUT 55 | 56 | - name: Create Pull Request 57 | if: steps.git-check.outputs.changed == 'true' 58 | uses: peter-evans/create-pull-request@v8 59 | with: 60 | token: ${{ secrets.GITHUB_TOKEN }} 61 | commit-message: 'docs: update prompts documentation' 62 | branch: docs/update-prompt-docs 63 | delete-branch: true 64 | title: 'docs: update prompt documentation' 65 | body: | 66 | ## Summary 67 | Automatically generated documentation update for MCP prompts. 68 | 69 | This PR was triggered by changes to the prompts folder in `packages/mcp-server/src/mcp/handlers/prompts/`. 70 | 71 | ## Changes 72 | - Updated `documentation/docs/30-capabilities/30-prompts.md` with latest prompt definitions 73 | 74 | ## Generated by 75 | GitHub Action: Update Prompt Documentation 76 | labels: | 77 | documentation 78 | automated 79 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | permissions: {} 8 | 9 | jobs: 10 | release: 11 | permissions: 12 | contents: write # to create release (changesets/action) 13 | id-token: write # OpenID Connect token needed for provenance 14 | pull-requests: write # to create pull request (changesets/action) 15 | # prevents this action from running on forks 16 | if: github.repository == 'sveltejs/mcp' 17 | name: Release 18 | runs-on: ${{ matrix.os }} 19 | outputs: 20 | published: ${{ steps.changesets.outputs.published }} 21 | strategy: 22 | matrix: 23 | # pseudo-matrix for convenience, NEVER use more than a single combination 24 | node: [24] 25 | os: [ubuntu-latest] 26 | steps: 27 | - name: checkout 28 | uses: actions/checkout@v6 29 | with: 30 | # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits 31 | fetch-depth: 0 32 | - uses: actions/setup-node@v6 33 | with: 34 | node-version: ${{ matrix.node }} 35 | package-manager-cache: false # pnpm is not installed yet 36 | - name: install pnpm 37 | shell: bash 38 | run: | 39 | PNPM_VER=$(jq -r '.packageManager | if .[0:5] == "pnpm@" then .[5:] else "packageManager in package.json does not start with pnpm@\n" | halt_error(1) end' package.json) 40 | echo installing pnpm version $PNPM_VER 41 | npm i -g pnpm@$PNPM_VER 42 | - uses: actions/setup-node@v6 43 | with: 44 | node-version: ${{ matrix.node }} 45 | package-manager-cache: true # caches pnpm via packageManager field in package.json 46 | - name: install 47 | run: pnpm install --frozen-lockfile --prefer-offline --ignore-scripts 48 | - name: build 49 | run: pnpm run --filter ./packages/mcp-stdio/ build 50 | 51 | - name: Create Release Pull Request or Publish to npm 52 | id: changesets 53 | # pinned for security, always review third party action code before updating 54 | uses: changesets/action@e0145edc7d9d8679003495b11f87bd8ef63c0cba # v1.5.3 55 | with: 56 | # This expects you to have a script called changeset:version version that calls changeset version and updated what it needs to be updated 57 | version: pnpm changeset:version 58 | # This expects you to have a script called release which does a build for your packages and calls changeset publish 59 | publish: pnpm release 60 | env: 61 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 62 | NPM_CONFIG_PROVENANCE: true 63 | 64 | publish-mcp: 65 | needs: release 66 | if: needs.release.outputs.published == 'true' 67 | uses: ./.github/workflows/publish-mcp.yml 68 | secrets: 69 | MCP_KEY: ${{ secrets.MCP_KEY }} 70 | -------------------------------------------------------------------------------- /packages/mcp-server/src/mcp/autofixers/visitors/assign-in-effect.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | AssignmentExpression, 3 | CallExpression, 4 | Identifier, 5 | Node, 6 | UpdateExpression, 7 | } from 'estree'; 8 | import type { Autofixer, AutofixerState } from './index.js'; 9 | import { left_most_id } from '../ast/utils.js'; 10 | import type { AST } from 'svelte-eslint-parser'; 11 | import type { Context } from 'zimmerframe'; 12 | 13 | function run_if_in_effect( 14 | path: (Node | AST.SvelteNode)[], 15 | state: AutofixerState, 16 | to_run: () => void, 17 | ) { 18 | const in_effect = path.findLast( 19 | (node) => 20 | node.type === 'CallExpression' && state.parsed.is_rune(node, ['$effect', '$effect.pre']), 21 | ); 22 | 23 | if (in_effect) { 24 | to_run(); 25 | } 26 | } 27 | 28 | function assign_or_update_visitor( 29 | node: UpdateExpression | AssignmentExpression, 30 | { state, path, next }: Context, 31 | ) { 32 | run_if_in_effect(path, state, () => { 33 | function check_if_stateful_id(id: Identifier) { 34 | const reference = state.parsed.find_reference_by_id(id); 35 | const definition = reference?.resolved?.defs[0]; 36 | if (definition && definition.type === 'Variable') { 37 | const init = definition.node.init; 38 | if ( 39 | init?.type === 'CallExpression' && 40 | state.parsed.is_rune(init, ['$state', '$state.raw', '$derived', '$derived.by']) 41 | ) { 42 | state.output.suggestions.push( 43 | `The stateful variable "${id.name}" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.`, 44 | ); 45 | } 46 | } 47 | } 48 | const variable = node.type === 'UpdateExpression' ? node.argument : node.left; 49 | 50 | if (variable.type === 'Identifier') { 51 | check_if_stateful_id(variable); 52 | } else if (variable.type === 'MemberExpression') { 53 | const object = left_most_id(variable); 54 | if (object) { 55 | check_if_stateful_id(object); 56 | } 57 | } 58 | }); 59 | next(); 60 | } 61 | 62 | function call_expression_visitor( 63 | node: CallExpression, 64 | { state, path, next }: Context, 65 | ) { 66 | run_if_in_effect(path, state, () => { 67 | const function_name = 68 | node.callee.type === 'Identifier' ? `the function \`${node.callee.name}\`` : 'a function'; 69 | state.output.suggestions.push( 70 | `You are calling ${function_name} inside an $effect. Please check if the function is reassigning a stateful variable because that's considered malpractice and check if it could use \`$derived\` instead. Ignore this suggestion if you are sure this function is not assigning any stateful variable or if you can't check if it does.`, 71 | ); 72 | }); 73 | next(); 74 | } 75 | 76 | export const assign_in_effect: Autofixer = { 77 | UpdateExpression: assign_or_update_visitor, 78 | AssignmentExpression: assign_or_update_visitor, 79 | CallExpression: call_expression_visitor, 80 | }; 81 | -------------------------------------------------------------------------------- /packages/mcp-server/src/mcp/handlers/tools/playground-link.test.ts: -------------------------------------------------------------------------------- 1 | import { InMemoryTransport } from '@tmcp/transport-in-memory'; 2 | import { beforeEach, describe, expect, it } from 'vitest'; 3 | import { server } from '../../index.js'; 4 | 5 | const transport = new InMemoryTransport(server); 6 | 7 | let session: ReturnType; 8 | 9 | describe('playground-link tool', () => { 10 | beforeEach(async () => { 11 | session = transport.session(); 12 | await session.initialize( 13 | '2025-06-18', 14 | {}, 15 | { 16 | name: 'test-client', 17 | version: '1.0.0', 18 | }, 19 | ); 20 | }); 21 | 22 | it('should create a playground link if App.svelte is present', async () => { 23 | const result = await session.callTool<{ url: string }>('playground-link', { 24 | name: 'My Playground', 25 | tailwind: false, 26 | files: { 27 | 'App.svelte': `Hi there!`, 28 | }, 29 | }); 30 | expect(result.structuredContent).toBeDefined(); 31 | expect(result.structuredContent?.url).toBeDefined(); 32 | // Verify URL structure rather than exact match (gzip compression can vary by platform) 33 | expect(result.structuredContent?.url).toMatch(/^https:\/\/svelte\.dev\/playground#H4sIA/); 34 | expect(result.structuredContent?.url).toContain('svelte.dev/playground'); 35 | }); 36 | 37 | it('should have a content with the stringified version of structured content and an ui resource', async () => { 38 | const result = await session.callTool<{ url: string }>('playground-link', { 39 | name: 'My Playground', 40 | tailwind: false, 41 | files: { 42 | 'App.svelte': `Hi there!`, 43 | }, 44 | }); 45 | expect(result.structuredContent).toBeDefined(); 46 | expect(result.content).toStrictEqual( 47 | expect.arrayContaining([ 48 | expect.objectContaining({ 49 | type: 'text', 50 | text: JSON.stringify(result.structuredContent), 51 | }), 52 | ]), 53 | ); 54 | // Verify resource structure without exact URL match (gzip compression can vary by platform) 55 | expect(result.content).toStrictEqual( 56 | expect.arrayContaining([ 57 | expect.objectContaining({ 58 | type: 'resource', 59 | resource: expect.objectContaining({ 60 | uri: 'ui://svelte/playground-link', 61 | mimeType: 'text/uri-list', 62 | _meta: { 'mcpui.dev/ui-preferred-frame-size': ['100%', '1200px'] }, 63 | text: expect.stringMatching(/^https:\/\/svelte\.dev\/playground\/embed#H4sIA/), 64 | }), 65 | }), 66 | ]), 67 | ); 68 | }); 69 | 70 | it('should not create a playground link if App.svelte is missing', async () => { 71 | const result = await session.callTool<{ url: string }>('playground-link', { 72 | name: 'My Playground', 73 | tailwind: false, 74 | files: { 75 | 'Something.svelte': `Hi there!`, 76 | }, 77 | }); 78 | expect(result.isError).toBe(true); 79 | expect(result.content?.[0]).toStrictEqual({ 80 | type: 'text', 81 | text: 'The files must contain an App.svelte file as the entry point', 82 | }); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import prettier from 'eslint-config-prettier'; 2 | import { includeIgnoreFile } from '@eslint/compat'; 3 | import js from '@eslint/js'; 4 | import svelte from 'eslint-plugin-svelte'; 5 | import globals from 'globals'; 6 | import { fileURLToPath } from 'node:url'; 7 | import ts from 'typescript-eslint'; 8 | import svelteConfig from './apps/mcp-remote/svelte.config.js'; 9 | import eslint_plugin_import from 'eslint-plugin-import'; 10 | import { configs as pnpm } from 'eslint-plugin-pnpm'; 11 | 12 | const gitignore_path = fileURLToPath(new URL('./.gitignore', import.meta.url)); 13 | 14 | export default /** @type {import("eslint").Linter.Config} */ ([ 15 | includeIgnoreFile(gitignore_path), 16 | { 17 | ignores: [ 18 | '.claude/**/*', 19 | '.changeset/*', 20 | '.github/**/*.yml', 21 | '.github/**/*.yaml', 22 | '**/pnpm-lock.yaml', 23 | ], 24 | }, 25 | js.configs.recommended, 26 | ...ts.configs.recommended, 27 | ...svelte.configs.recommended, 28 | eslint_plugin_import.flatConfigs.recommended, 29 | prettier, 30 | ...svelte.configs.prettier, 31 | { 32 | languageOptions: { 33 | globals: { ...globals.browser, ...globals.node }, 34 | }, 35 | rules: { 36 | // typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects. 37 | // see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors 38 | 'no-undef': 'off', 39 | '@typescript-eslint/naming-convention': [ 40 | 'error', 41 | { 42 | selector: ['variableLike'], 43 | format: ['snake_case', 'UPPER_CASE'], 44 | leadingUnderscore: 'allow', 45 | }, 46 | ], 47 | '@typescript-eslint/no-unused-vars': [ 48 | 'error', 49 | { 50 | varsIgnorePattern: '^_', 51 | ignoreRestSiblings: true, 52 | }, 53 | ], 54 | 'func-style': ['error', 'declaration', { allowTypeAnnotation: true }], 55 | 'import/no-unresolved': 'off', // this doesn't work well with typescript path mapping 56 | 'import/extensions': [ 57 | 'error', 58 | { 59 | ignorePackages: true, 60 | pattern: { 61 | js: 'always', 62 | mjs: 'always', 63 | cjs: 'always', 64 | ts: 'always', 65 | svelte: 'always', 66 | svg: 'always', 67 | json: 'always', 68 | }, 69 | }, 70 | ], 71 | }, 72 | }, 73 | { 74 | files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'], 75 | languageOptions: { 76 | parserOptions: { 77 | projectService: true, 78 | extraFileExtensions: ['.svelte'], 79 | parser: ts.parser, 80 | svelteConfig, 81 | }, 82 | }, 83 | }, 84 | { 85 | name: 'pnpm/exclude-some-rules', 86 | files: ['**/*.json', '**/*.yaml', '**/*.yml', 'pnpm-workspace.yaml'], 87 | rules: { 88 | '@typescript-eslint/naming-convention': 'off', 89 | '@typescript-eslint/no-unused-vars': 'off', 90 | '@typescript-eslint/no-unused-expressions': 'off', 91 | 'func-style': 'off', 92 | }, 93 | }, 94 | ...pnpm.json, 95 | ...pnpm.yaml, 96 | ]); 97 | -------------------------------------------------------------------------------- /packages/mcp-server/src/mcp/autofixers/add-eslint-issues.ts: -------------------------------------------------------------------------------- 1 | import { ESLint } from 'eslint'; 2 | import svelte_parser from 'svelte-eslint-parser'; 3 | import svelte from 'eslint-plugin-svelte'; 4 | import type { Config } from '@sveltejs/kit'; 5 | import ts from 'typescript-eslint'; 6 | 7 | let svelte_5_linter: ESLint | undefined; 8 | 9 | let svelte_4_linter: ESLint | undefined; 10 | 11 | function base_config(svelte_config: Config): ESLint.Options['baseConfig'] { 12 | return [ 13 | ...svelte.configs.recommended, 14 | { 15 | files: ['*.svelte', '*.svelte.ts', '*.svelte.js'], 16 | rules: { 17 | 'no-self-assign': 'warn', 18 | 'svelte/infinite-reactive-loop': 'warn', 19 | 'svelte/no-dupe-else-if-blocks': 'warn', 20 | 'svelte/no-dupe-on-directives': 'warn', 21 | 'svelte/no-dupe-style-properties': 'warn', 22 | 'svelte/no-dupe-use-directives': 'warn', 23 | 'svelte/no-object-in-text-mustaches': 'warn', 24 | 'svelte/no-raw-special-elements': 'warn', 25 | 'svelte/no-reactive-functions': 'warn', 26 | 'svelte/no-reactive-literals': 'warn', 27 | 'svelte/no-store-async': 'warn', 28 | 'svelte/no-svelte-internal': 'warn', 29 | 'svelte/no-unnecessary-state-wrap': 'warn', 30 | 'svelte/no-unused-props': 'warn', 31 | 'svelte/no-unused-svelte-ignore': 'warn', 32 | 'svelte/no-useless-children-snippet': 'warn', 33 | 'svelte/no-useless-mustaches': 'warn', 34 | 'svelte/prefer-svelte-reactivity': 'warn', 35 | 'svelte/prefer-writable-derived': 'warn', 36 | 'svelte/require-event-dispatcher-types': 'warn', 37 | 'svelte/require-store-reactive-access': 'warn', 38 | }, 39 | 40 | languageOptions: { 41 | ecmaVersion: 2022, 42 | sourceType: 'module', 43 | parser: svelte_parser, 44 | parserOptions: { 45 | extraFileExtensions: ['.svelte'], 46 | parser: ts.parser, 47 | svelteConfig: svelte_config, 48 | }, 49 | }, 50 | }, 51 | ]; 52 | } 53 | 54 | function get_linter(version: number, async = false) { 55 | if (version < 5) { 56 | return (svelte_4_linter ??= new ESLint({ 57 | overrideConfigFile: true, 58 | baseConfig: base_config({ 59 | compilerOptions: { 60 | runes: false, 61 | }, 62 | }), 63 | })); 64 | } 65 | return (svelte_5_linter ??= new ESLint({ 66 | overrideConfigFile: true, 67 | baseConfig: base_config({ 68 | compilerOptions: { 69 | runes: true, 70 | experimental: { async }, 71 | }, 72 | }), 73 | })); 74 | } 75 | 76 | export async function add_eslint_issues( 77 | content: { issues: string[]; suggestions: string[] }, 78 | code: string, 79 | desired_svelte_version: number, 80 | filename = 'Component.svelte', 81 | async = false, 82 | ) { 83 | const eslint = get_linter(desired_svelte_version, async); 84 | const results = await eslint.lintText(code, { filePath: filename || './Component.svelte' }); 85 | 86 | for (const message of results[0]?.messages ?? []) { 87 | if (message.severity === 2) { 88 | content.issues.push(`${message.message} at line ${message.line}, column ${message.column}`); 89 | } else if (message.severity === 1) { 90 | content.suggestions.push( 91 | `${message.message} at line ${message.line}, column ${message.column}`, 92 | ); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /documentation/docs/20-setup/30-remote-setup.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Remote setup 3 | --- 4 | 5 | The remote version of the MCP server is available at `https://mcp.svelte.dev/mcp`. 6 | 7 | Here's how to set it up in some common MCP clients: 8 | 9 | ## Claude Code 10 | 11 | To include the remote MCP version in Claude Code, simply run the following command: 12 | 13 | ```bash 14 | claude mcp add -t http -s [scope] svelte https://mcp.svelte.dev/mcp 15 | ``` 16 | 17 | You can choose your preferred `scope` (it must be `user`, `project` or `local`) and `name`. 18 | 19 | ## Claude Desktop 20 | 21 | - Open Settings > Connectors 22 | - Click on Add Custom Connector 23 | - When prompted for a name, enter `svelte` 24 | - Under the Remote MCP server URL input, use `https://mcp.svelte.dev/mcp` 25 | - Click Add 26 | 27 | ## Codex CLI 28 | 29 | Add the following to your `config.toml` (which defaults to `~/.codex/config.toml`, but refer to [the configuration documentation](https://github.com/openai/codex/blob/main/docs/config.md) for more advanced setups): 30 | 31 | ```toml 32 | experimental_use_rmcp_client = true 33 | [mcp_servers.svelte] 34 | url = "https://mcp.svelte.dev/mcp" 35 | ``` 36 | 37 | ## Gemini CLI 38 | 39 | To use the remote MCP server with Gemini CLI, simply run the following command: 40 | 41 | ```bash 42 | gemini mcp add -t http -s [scope] svelte https://mcp.svelte.dev/mcp 43 | ``` 44 | 45 | The `[scope]` must be `user` or `project`. 46 | 47 | ## OpenCode 48 | 49 | Run the command: 50 | 51 | ```bash 52 | opencode mcp add 53 | ``` 54 | 55 | and follow the instructions, selecting 'Remote' under the 'Select MCP server type' prompt: 56 | 57 | ```bash 58 | opencode mcp add 59 | 60 | ┌ Add MCP server 61 | │ 62 | ◇ Enter MCP server name 63 | │ svelte 64 | │ 65 | ◇ Select MCP server type 66 | │ Remote 67 | │ 68 | ◇ Enter MCP server URL 69 | │ https://mcp.svelte.dev/mcp 70 | ``` 71 | 72 | ## VS Code 73 | 74 | - Open the command palette 75 | - Select "MCP: Add Server..." 76 | - Select "HTTP (HTTP or Server-Sent-Events)" 77 | - Insert `https://mcp.svelte.dev/mcp` in the input and press `Enter` 78 | - Insert your preferred name 79 | - Select if you want to add it as a `Global` or `Workspace` MCP server 80 | 81 | ## Cursor 82 | 83 | - Open the command palette 84 | - Select "View: Open MCP Settings" 85 | - Click on "Add custom MCP" 86 | 87 | It will open a file with your MCP servers where you can add the following configuration: 88 | 89 | ```json 90 | { 91 | "mcpServers": { 92 | "svelte": { 93 | "url": "https://mcp.svelte.dev/mcp" 94 | } 95 | } 96 | } 97 | ``` 98 | 99 | ## GitHub Coding Agent 100 | 101 | - Open your repository in GitHub 102 | - Go to Settings 103 | - Open Copilot > Coding agent 104 | - Edit the MCP configuration 105 | 106 | ```json 107 | { 108 | "mcpServers": { 109 | "svelte": { 110 | "type": "http", 111 | "url": "https://mcp.svelte.dev/mcp", 112 | "tools": ["*"] 113 | } 114 | } 115 | } 116 | ``` 117 | 118 | - Click _Save MCP configuration_ 119 | 120 | ## Other clients 121 | 122 | If we didn't include the MCP client you are using, refer to their documentation for `remote` servers and use `https://mcp.svelte.dev/mcp` as the URL. 123 | -------------------------------------------------------------------------------- /packages/mcp-server/src/parse/parse.ts: -------------------------------------------------------------------------------- 1 | import ts_parser from '@typescript-eslint/parser'; 2 | import type { CallExpression, Identifier } from 'estree'; 3 | import type { Reference, Variable } from 'eslint-scope'; 4 | import { parseForESLint as svelte_eslint_parse } from 'svelte-eslint-parser'; 5 | import { runes } from '../constants.js'; 6 | 7 | type Scope = { 8 | variables?: Variable[]; 9 | references?: Reference[]; 10 | childScopes?: Scope[]; 11 | }; 12 | type ScopeManager = { 13 | globalScope: Scope; 14 | }; 15 | 16 | function collect_scopes(scope: Scope, acc: Scope[] = []) { 17 | acc.push(scope); 18 | for (const child of scope.childScopes ?? []) collect_scopes(child, acc); 19 | return acc; 20 | } 21 | 22 | export type ParseResult = ReturnType; 23 | 24 | export function parse(code: string, file_path: string) { 25 | const parsed = svelte_eslint_parse(code, { 26 | filePath: file_path, 27 | parser: { ts: ts_parser, typescript: ts_parser }, 28 | }); 29 | let all_scopes: Scope[] | undefined; 30 | let all_variables: Variable[] | undefined; 31 | let all_references: Reference[] | undefined; 32 | 33 | function get_all_scopes() { 34 | if (!all_scopes) { 35 | all_scopes = collect_scopes(parsed.scopeManager!.globalScope); 36 | } 37 | return all_scopes; 38 | } 39 | // walking the ast will also walk all the tokens if we don't remove them so we return them separately 40 | // we also remove the parent which as a circular reference to the ast itself (and it's not needed since we use zimmerframe to walk the ast) 41 | const { 42 | ast: { tokens, ...ast }, 43 | } = parsed; 44 | 45 | // @ts-expect-error we also have to delete it from the object or the circular reference remains 46 | delete parsed.ast.tokens; 47 | 48 | return { 49 | ast, 50 | tokens, 51 | scope_manager: parsed.scopeManager as ScopeManager, 52 | visitor_keys: parsed.visitorKeys, 53 | get all_scopes() { 54 | return get_all_scopes(); 55 | }, 56 | get all_variables() { 57 | if (!all_variables) { 58 | all_variables = get_all_scopes().flatMap((s) => s.variables ?? []); 59 | } 60 | return all_variables; 61 | }, 62 | get all_references() { 63 | if (!all_references) { 64 | all_references = get_all_scopes().flatMap((s) => s.references ?? []); 65 | } 66 | return all_references; 67 | }, 68 | find_reference_by_id(id: Identifier) { 69 | return this.all_references.find((r) => r.identifier === id); 70 | }, 71 | is_rune(call: CallExpression, rune?: (typeof runes)[number][]) { 72 | if (call.callee.type !== 'Identifier' && call.callee.type !== 'MemberExpression') 73 | return false; 74 | const id = call.callee.type === 'Identifier' ? call.callee : call.callee.object; 75 | if (id.type !== 'Identifier') return false; 76 | const property = call.callee.type === 'MemberExpression' ? call.callee.property : null; 77 | 78 | const callee_text = `${id.name}${property && property.type === 'Identifier' ? `.${property.name}` : ''}`; 79 | if (rune && !rune.includes(callee_text as never)) return false; 80 | if (!rune && !runes.includes(callee_text as never)) return false; 81 | 82 | const reference = this.find_reference_by_id(id); 83 | if (!reference) return false; 84 | const variable = reference.resolved; 85 | if (!variable) return false; 86 | return variable.defs.length === 0; 87 | }, 88 | }; 89 | } 90 | -------------------------------------------------------------------------------- /packages/mcp-server/src/lib/schemas.ts: -------------------------------------------------------------------------------- 1 | import * as v from 'valibot'; 2 | 3 | export const documentation_sections_schema = v.record( 4 | v.string(), 5 | v.object({ 6 | metadata: v.object({ 7 | title: v.string(), 8 | use_cases: v.optional(v.string()), 9 | }), 10 | slug: v.string(), 11 | }), 12 | ); 13 | 14 | // Valibot schemas for Batch API 15 | export const summary_data_schema = v.object({ 16 | generated_at: v.string(), 17 | model: v.string(), 18 | total_sections: v.number(), 19 | successful_summaries: v.number(), 20 | failed_summaries: v.number(), 21 | summaries: v.record(v.string(), v.string()), 22 | errors: v.optional( 23 | v.array( 24 | v.object({ 25 | section: v.string(), 26 | error: v.string(), 27 | }), 28 | ), 29 | ), 30 | download_errors: v.optional( 31 | v.array( 32 | v.object({ 33 | section: v.string(), 34 | error: v.string(), 35 | }), 36 | ), 37 | ), 38 | }); 39 | 40 | export const anthropic_batch_request_schema = v.object({ 41 | custom_id: v.string(), 42 | params: v.object({ 43 | model: v.string(), 44 | max_tokens: v.number(), 45 | messages: v.array( 46 | v.object({ 47 | role: v.union([v.literal('user'), v.literal('assistant')]), 48 | content: v.union([ 49 | v.string(), 50 | v.array( 51 | v.object({ 52 | type: v.string(), 53 | text: v.string(), 54 | }), 55 | ), 56 | ]), 57 | }), 58 | ), 59 | }), 60 | }); 61 | 62 | export const anthropic_batch_response_schema = v.object({ 63 | id: v.string(), 64 | type: v.string(), 65 | processing_status: v.union([v.literal('in_progress'), v.literal('ended')]), 66 | request_counts: v.object({ 67 | processing: v.number(), 68 | succeeded: v.number(), 69 | errored: v.number(), 70 | canceled: v.number(), 71 | expired: v.number(), 72 | }), 73 | ended_at: v.nullable(v.string()), 74 | created_at: v.string(), 75 | expires_at: v.string(), 76 | cancel_initiated_at: v.nullable(v.string()), 77 | results_url: v.nullable(v.string()), 78 | }); 79 | 80 | export const anthropic_batch_result_schema = v.object({ 81 | custom_id: v.string(), 82 | result: v.object({ 83 | type: v.union([ 84 | v.literal('succeeded'), 85 | v.literal('errored'), 86 | v.literal('canceled'), 87 | v.literal('expired'), 88 | ]), 89 | message: v.optional( 90 | v.object({ 91 | id: v.string(), 92 | type: v.string(), 93 | role: v.string(), 94 | model: v.string(), 95 | content: v.array( 96 | v.object({ 97 | type: v.string(), 98 | text: v.string(), 99 | }), 100 | ), 101 | stop_reason: v.string(), 102 | stop_sequence: v.nullable(v.string()), 103 | usage: v.object({ 104 | input_tokens: v.number(), 105 | output_tokens: v.number(), 106 | }), 107 | }), 108 | ), 109 | error: v.optional( 110 | v.object({ 111 | type: v.string(), 112 | message: v.string(), 113 | }), 114 | ), 115 | }), 116 | }); 117 | 118 | // Export inferred types 119 | export type SummaryData = v.InferOutput; 120 | export type AnthropicBatchRequest = v.InferOutput; 121 | export type AnthropicBatchResponse = v.InferOutput; 122 | export type AnthropicBatchResult = v.InferOutput; 123 | -------------------------------------------------------------------------------- /packages/mcp-schema/src/schema.js: -------------------------------------------------------------------------------- 1 | import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'; 2 | import { float_32_array } from './utils.js'; 3 | 4 | /** 5 | * NOTE: if you modify a schema adding a vector column you need to manually add this 6 | * 7 | * CREATE INDEX IF NOT EXISTS name_of_the_index 8 | * ON `name_of_the_table` ( 9 | * libsql_vector_idx(name_of_the_column, 'metric=cosine') 10 | * ) 11 | * 12 | * to the generated migration file 13 | */ 14 | 15 | export const distillations = sqliteTable('distillations', { 16 | id: integer('id').primaryKey(), 17 | preset_name: text('preset_name').notNull(), 18 | version: text('version').notNull(), 19 | content: text('content').notNull(), 20 | size_kb: integer('size_kb').notNull(), 21 | document_count: integer('document_count').notNull(), 22 | distillation_job_id: integer('distillation_job_id').references(() => distillation_jobs.id), 23 | created_at: integer('created_at', { mode: 'timestamp' }) 24 | .notNull() 25 | .$defaultFn(() => new Date()), 26 | }); 27 | 28 | export const distillation_jobs = sqliteTable('distillation_jobs', { 29 | id: integer('id').primaryKey(), 30 | preset_name: text('preset_name').notNull(), 31 | batch_id: text('batch_id'), 32 | status: text('status', { enum: ['pending', 'processing', 'completed', 'failed'] }).notNull(), 33 | model_used: text('model_used').notNull(), 34 | total_files: integer('total_files').notNull(), 35 | processed_files: integer('processed_files').notNull().default(0), 36 | successful_files: integer('successful_files').notNull().default(0), 37 | minimize_applied: integer('minimize_applied', { mode: 'boolean' }).notNull().default(false), 38 | total_input_tokens: integer('total_input_tokens').notNull().default(0), 39 | total_output_tokens: integer('total_output_tokens').notNull().default(0), 40 | started_at: integer('started_at', { mode: 'timestamp' }), 41 | completed_at: integer('completed_at', { mode: 'timestamp' }), 42 | error_message: text('error_message'), 43 | metadata: text('metadata', { mode: 'json' }).notNull().default({}), 44 | created_at: integer('created_at', { mode: 'timestamp' }) 45 | .notNull() 46 | .$defaultFn(() => new Date()), 47 | updated_at: integer('updated_at', { mode: 'timestamp' }) 48 | .notNull() 49 | .$defaultFn(() => new Date()), 50 | }); 51 | 52 | export const content = sqliteTable('content', { 53 | id: integer('id').primaryKey(), 54 | path: text('path').notNull(), 55 | filename: text('filename').notNull(), 56 | content: text('content').notNull(), 57 | size_bytes: integer('size_bytes').notNull(), 58 | embeddings: float_32_array('embeddings', { dimensions: 1024 }), 59 | metadata: text('metadata', { mode: 'json' }).notNull().default({}), 60 | created_at: integer('created_at', { mode: 'timestamp' }) 61 | .notNull() 62 | .$defaultFn(() => new Date()), 63 | updated_at: integer('updated_at', { mode: 'timestamp' }) 64 | .notNull() 65 | .$defaultFn(() => new Date()), 66 | }); 67 | 68 | export const content_distilled = sqliteTable('content_distilled', { 69 | id: integer('id').primaryKey(), 70 | path: text('path').notNull(), 71 | filename: text('filename').notNull(), 72 | content: text('content').notNull(), 73 | size_bytes: integer('size_bytes').notNull(), 74 | embeddings: float_32_array('embeddings', { dimensions: 1024 }), 75 | metadata: text('metadata', { mode: 'json' }).notNull().default({}), 76 | created_at: integer('created_at', { mode: 'timestamp' }) 77 | .notNull() 78 | .$defaultFn(() => new Date()), 79 | updated_at: integer('updated_at', { mode: 'timestamp' }) 80 | .notNull() 81 | .$defaultFn(() => new Date()), 82 | }); 83 | -------------------------------------------------------------------------------- /.vscode/mcp-snippets.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | // Place your svelte-mcp workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and 3 | // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope 4 | // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is 5 | // used to trigger the snippet and the body will be expanded and inserted. Possible variables are: 6 | // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. 7 | // Placeholders with the same ids are connected. 8 | // Example: 9 | "Setup Function": { 10 | "scope": "javascript,typescript", 11 | "prefix": "!setup-mcp", 12 | "body": [ 13 | "import type { SvelteMcp } from '../../index.js';", 14 | "import * as v from 'valibot';", 15 | "import { icons } from '../../icons/index.js';", 16 | "", 17 | "export function ${1:function_name}(server: SvelteMcp) {", 18 | "\t$0", 19 | "}", 20 | ], 21 | "description": "Create a setup function for a tool/resource/prompt handler", 22 | }, 23 | "Autofixer": { 24 | "scope": "javascript,typescript", 25 | "prefix": "!autofixer", 26 | "body": [ 27 | "import type { Autofixer } from './index.js';", 28 | "export const ${1:autofixer_name}: Autofixer = {", 29 | "\t$0", 30 | "};", 31 | ], 32 | "description": "Create a setup export for an autofixer", 33 | }, 34 | "Prompt Generator": { 35 | "scope": "javascript,typescript", 36 | "prefix": "!prompt", 37 | "body": [ 38 | "import type { SvelteMcp } from '../../index.js';", 39 | "import { icons } from '../../icons/index.js';", 40 | "", 41 | "/**", 42 | " * Function that actually generates the prompt string. You can use this in the MCP server handler to generate the prompt, it can accept arguments", 43 | " * if needed (it will always be invoked manually so it's up to you to provide the arguments).", 44 | " */", 45 | "function ${1:prompt_name}() {", 46 | "\treturn `$0`;", 47 | "}", 48 | "", 49 | "/**", 50 | " * This function is used to generate the prompt to update the docs in the script `/scripts/update-docs-prompts.ts` it should use the default export", 51 | " * function and pass in the arguments. Since it will be included in the documentation if it's an argument that the MCP will expose it should", 52 | " * be in the format [NAME_OF_THE_ARGUMENT] to signal the user that it can substitute it.", 53 | " * ", 54 | " * The name NEEDS to be `generate_for_docs`.", 55 | " */", 56 | "export async function generate_for_docs() {", 57 | "\treturn ${1:prompt_name}();", 58 | "}", 59 | "", 60 | "/**", 61 | " * Human readable description of what the prompt does. It will be included in the documentation.", 62 | " * ", 63 | " * The name NEEDS to be `docs_description`.", 64 | " */", 65 | "export const docs_description = '';", 66 | "", 67 | "export function setup_${1:prompt_name}(server: SvelteMcp) {", 68 | "\tserver.prompt(", 69 | "\t\t{", 70 | "\t\t\tname: '${1:prompt_name}',", 71 | "\t\t\ttitle: '${2:title}',", 72 | "\t\t\tdescription:", 73 | "\t\t\t\t'${3:llm_description}',", 74 | "\t\t\ticons,", 75 | "\t\t},", 76 | "\t\tasync () => {", 77 | "\t\t\treturn {", 78 | "\t\t\t\tmessages: [", 79 | "\t\t\t\t\t{", 80 | "\t\t\t\t\t\trole: 'assistant',", 81 | "\t\t\t\t\t\tcontent: {", 82 | "\t\t\t\t\t\t\ttype: 'text',", 83 | "\t\t\t\t\t\t\ttext: ${1:prompt_name}(),", 84 | "\t\t\t\t\t\t},", 85 | "\t\t\t\t\t},", 86 | "\t\t\t\t],", 87 | "\t\t\t};", 88 | "\t\t},", 89 | "\t);", 90 | "}", 91 | ], 92 | "description": "Create a setup export for a prompt generator", 93 | }, 94 | } 95 | -------------------------------------------------------------------------------- /documentation/docs/20-setup/20-local-setup.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Local setup 3 | --- 4 | 5 | The local (or stdio) version of the MCP server is available via the [`@sveltejs/mcp`](https://www.npmjs.com/package/@sveltejs/mcp) npm package. You can either install it globally and then reference it in your configuration or run it with `npx`: 6 | 7 | ```bash 8 | npx -y @sveltejs/mcp 9 | ``` 10 | 11 | Here's how to set it up in some common MCP clients: 12 | 13 | ## Claude Code 14 | 15 | To include the local MCP version in Claude Code, simply run the following command: 16 | 17 | ```bash 18 | claude mcp add -t stdio -s [scope] svelte -- npx -y @sveltejs/mcp 19 | ``` 20 | 21 | The `[scope]` must be `user`, `project` or `local`. 22 | 23 | ## Claude Desktop 24 | 25 | In the Settings > Developer section, click on Edit Config. It will open the folder with a `claude_desktop_config.json` file in it. Edit the file to include the following configuration: 26 | 27 | ```json 28 | { 29 | "mcpServers": { 30 | "svelte": { 31 | "command": "npx", 32 | "args": ["-y", "@sveltejs/mcp"] 33 | } 34 | } 35 | } 36 | ``` 37 | 38 | ## Codex CLI 39 | 40 | Add the following to your `config.toml` (which defaults to `~/.codex/config.toml`, but refer to [the configuration documentation](https://github.com/openai/codex/blob/main/docs/config.md) for more advanced setups): 41 | 42 | ```toml 43 | [mcp_servers.svelte] 44 | command = "npx" 45 | args = ["-y", "@sveltejs/mcp"] 46 | ``` 47 | 48 | ## Gemini CLI 49 | 50 | To include the local MCP version in Gemini CLI, simply run the following command: 51 | 52 | ```bash 53 | gemini mcp add -t stdio -s [scope] svelte npx -y @sveltejs/mcp 54 | ``` 55 | 56 | The `[scope]` must be `user`, `project` or `local`. 57 | 58 | ## OpenCode 59 | 60 | Run the command: 61 | 62 | ```bash 63 | opencode mcp add 64 | ``` 65 | 66 | and follow the instructions, selecting 'Local' under the 'Select MCP server type' prompt: 67 | 68 | ```bash 69 | opencode mcp add 70 | 71 | ┌ Add MCP server 72 | │ 73 | ◇ Enter MCP server name 74 | │ svelte 75 | │ 76 | ◇ Select MCP server type 77 | │ Local 78 | │ 79 | ◆ Enter command to run 80 | │ npx -y @sveltejs/mcp 81 | ``` 82 | 83 | ## VS Code 84 | 85 | - Open the command palette 86 | - Select "MCP: Add Server..." 87 | - Select "Command (stdio)" 88 | - Insert `npx -y @sveltejs/mcp` in the input and press `Enter` 89 | - When prompted for a name, insert `svelte` 90 | - Select if you want to add it as a `Global` or `Workspace` MCP server 91 | 92 | ## Cursor 93 | 94 | - Open the command palette 95 | - Select "View: Open MCP Settings" 96 | - Click on "Add custom MCP" 97 | 98 | It will open a file with your MCP servers where you can add the following configuration: 99 | 100 | ```json 101 | { 102 | "mcpServers": { 103 | "svelte": { 104 | "command": "npx", 105 | "args": ["-y", "@sveltejs/mcp"] 106 | } 107 | } 108 | } 109 | ``` 110 | 111 | ## Zed 112 | 113 | Install the [Svelte MCP Server extension](https://zed.dev/extensions/svelte-mcp). 114 | 115 |
116 | 117 | Configure Manually 118 | 119 | - Open the command palette 120 | - Search and select "agent:open settings" 121 | - In settings panel look for `Model Context Protocol (MCP) Servers` 122 | - Click on "Add Server" 123 | - Select: "Add Custom Server" 124 | 125 | It will open a popup with MCP server config where you can add the following configuration: 126 | 127 | ```json 128 | { 129 | "svelte": { 130 | "command": "npx", 131 | "args": ["-y", "@sveltejs/mcp"] 132 | } 133 | } 134 | ``` 135 | 136 |
137 | 138 | ## Other clients 139 | 140 | If we didn't include the MCP client you are using, refer to their documentation for `stdio` servers and use `npx` as the command and `-y @sveltejs/mcp` as the arguments. 141 | -------------------------------------------------------------------------------- /packages/mcp-server/src/mcp/icons/index.ts: -------------------------------------------------------------------------------- 1 | export const icons = [ 2 | { 3 | src: 'https://mcp.svelte.dev/logo.svg', 4 | mimeType: 'image/svg+xml', 5 | }, 6 | { 7 | src: 'https://mcp.svelte.dev/logo.png', 8 | mimeType: 'image/png', 9 | }, 10 | { 11 | src: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAACvdJREFUeJztXQuQVMUVHT5GCYmSDwaVMhQWmpSRRIEkJqmdFVQCRuRjCiVq8MMGKKTYGESEREAFhI2iSGkCFURCJKASimgpSviVrApEPoKLIGEF0aAIAgLCwsk927NxmZ2duf36zesZnFN1qqCU926fO6/79u3b3bFYAQUUUEABBRRQQAF5DsRjp6Eo1lbYW1gqHCcsE05J/HmYsI+wg/y/Z/i2N+8hQjYQni3sJZwmXC/8SHhciHrI/7ZHuEk4WzhIeL6wse/25BVEsFbCB4UfpxFby0PCZcJOvtuV8xCR2gj/IjwagvCpyK+or3RRp/hua05BRGkqLBFWZkn42jwinCe82He7cwIixLeF5Rn69mxwr/BXvtvvDdL4ZsIJiV9klMIncwYjLN96RAZpcEMOiMJXhMc8i4+EDXPECV/3rU3WIY1kXz9ZWJUDwidzwUk7OEvjGgtvEu4MTbDOXwH6tAFuuhC44QLg6m8C8Qauz53tW6vQIY1qLZwJE48HF+fy04D+PwJmjgVWvQzs2Azsfh/Yswv4+ANg51Zg7TLg7w8CQ7sAXc4I2h39VtjAt27OSPzq+yKMydTgOPDW60BVFdTYtQN4aCDQ8RTb930qbOdbPyeA+Zii2HJn4W9rB6xcqBc9FXZsAe7savvuDcLTfetoDRotHC7c5SQ8+/eHB5suJgx8ug+YcgdQ3MjGjjt862kFMbi9cI3zr/7G7wIVq8IRvjaOH5Px434ZqBtqbdkuzP2oSIw8S/g4XCdU3ZoD86YARw6HL35tJ/BL0Nt1v29964UY10jYDSbBFVz4yxoDv+tsBtkocOgAMOhnWvsqhE19a10HYlQT4QiYiCG4+F1OB2aNBw4fjEb8GqxbrrWRE8af+tb7BIhBLYQrnIRnaFj2GxO/+wJDVJ29E31r/n+IMecKVzqJz0H2pVnS13/mLiKjm+2bhG8DBz6x+7dvrwY6naqxeTPisUa+ta8JMVc59fX81dsKVR8WzQZ6tzZfE3md/PnFJ/X//ugRoO/3dLbHY+f6Fb+4esB9IbD4HPTCGmT/swEo7VT/ux4t1T/rjwO0bejiT3yzOM7JlX3quGszYMYYYO9H7sIzemGep+c56d95RRNg02rdM5+frm3LAJ8OYFXBfmvxB/7EJMzCwNb1QL/2+ndrv4KNr2mfOcKP+MXViycvWwl/zbeAF2YAVUfdhf9kN/DIEPuE2ujrdc9/f5v2mQ/4cUBR7DLYrNXe8n3T1x8/7iY8nff6i8CtFwfL8Zd0AFa+BOzbnf49lRXaZ07w5YD56kbf+J1w4vr9e4CxfW0TZ6nZq6VJPdTniNWLtM+6L3rx47Fz5MWH1Q3lgogL+Kt/9lGgewt34ZNJZ47pA7xbceI7nxitfUZp9A4oit2jMo7xPfv8oGB3RWFG9Q7nV5+OjKCemggc3G/erV8nuC5a8YurQ89ylXFMpLn86v/xmMkJZVP4ZN5eBLyzzuZr+2G0DojHmkO7qBJ0xWrNUjNYRil88perG+D3iR5NonVAUewH0JSO8JP+7JCd8FxEf2KMmaT5Et+O8yMVP+GAa1TGPT7MTvwN5SYZ519UG/b24YABKuOWPK0Xf84koNOXggtxZVNgeDfgr+OApc+YnD7nHOtfkb8/K4PrBDOQc2UtPPHZDTf34YA7VQZufFUn/tY3TY4miAgMcaeOALasybxceazKVEHMnWQmhfr13/r4ZOTiJxzwB5WBzExqwF+tbeMp3sR+wSd3HJuemWy+nGDiH5bBt5UvBwxVGckkmQZ/Gm7XeKavtV9XJux8BxhwaRAHjPMifsIB/VVGsh/WYMFUXaMZGU0baSKlMMGvKN0aQl0yAmzl0wHdVIZqZ8AH9gK/+Eb6Z/VrF176OhWYD2JyT++Enj4dcBE084Cxv9YLwGKra1vWfUaPs8JLX2fCB5XmfToHLPXpgDOFH2Y08qqvfZ5X0aDyLTN3GCLdwZCOJlO5baN7+toGC/6sjY5YaHaBHwfEq3NButKTxXOiEy8s6Auzyrw4oNoJRbFRKiOZz8k3cOKmc8C/4WuTt7z4PGjWA/g587OOCuyu2G2x+2I3xu6M3Rq7Ny3YbbL7zOwA7sxv4cUBCScsVv1SOLBxgMs2OFBzwE41kHLGbFNVzQAic9sYiPjbTywv76r8VIGb2wK7tmdN++r09W2XpLeBoa525rxwprYb+qVPB7SEzY5GTnbCrvfkpIyTM236mpM+DfQFuoN8OmC8WvwaMt3MhFgYeHOFffpamyJnglD3zLt8iX8htIvyyWQCbPooU+EQBFwj5iJ6kGzm7DLdO5hI1D1zlC8HTA0kfg0pHlPCTA3ziziWYYcjU81MOTP13CvFjFlDprz5y9aAyb5c/QJkIsaZ8H+dHJD8RXDH49S7zWIKF1G4mLJW+uElc4HHhppB3H5L6efkYg9rPbXge3XPvj16BxTFrg9N/CgYpPp6cqn2+d19OGCud1E1rKm+tk1fc7EmU5W1ISvCo920zQMr5KUHMxrHkg6WdvgSv/+Pg6evy5/XvodrwmdG6wCzyz2zcT3ONpHEyB7RCh9G+npwsfZ9K1ikFrUDrlUZx7I+gnkVlvvpPungZNkiqx4Yorqkr+k8/Zd7b6TiJxxQqjKOu85rg7tguIiejfrOHi2AZfPMRmsXcDMfj7XRvfeodMctfThAl4Z+Y0nqRnLpj1UQXGJ0FZ5hKUPUoBO62uBchOcK6d8/J3LxEw54QGVgZUX6BtMR3CQRpP6TAzzXb7lJw3Wpkt0VQ1ROCu3s8HPOqNoB3N6jAftt21/99NHh7J6vqb7++VdtxV8obOjLAcNURm5Q1u0wOaZt+F1XA9uUxV6ZwPQ1T9iy7/p4nOX5XsRPOKBEZah22s8to5nKEhlBcQsqt6K6gsFA8OprTrzu4Zq4TwdcrjJ2YolelGm/r/85DGc/fM9deMK9+np+5HF/HQfEqxdhMhvL0wptzvZZPNfkbJiYu+LLZpfK8nnhCM+VuPtucJ2ZLxI28yp+wgE8lmBzRoN50IV2R3oNeE4EY3EesMGDNlzBAz948If7noM34LMUMRlizL0qw8ff4i5iUHDpc2TPMCZ+i4W5deGDGHQptGvBa5UFumGB4SkPeXLf2MfVPk46o93/pQHMcfIbVQ1hvx5G9KIBJ1TclemeheWxlN2RyzdrQLtJg2RE5JqnSQcO9jwxN/hmixqy3nMS8uFMUDGS6wJbVA3j+i8r1bJR4Vz+XFgb+9jXd/CtqxWgzYySHAzphDCiG4KHtvLwVh7i6ib8buHdyIUQ0xZi9KnCdVYNLmlvQk0XrPhn8MqIE/kvYWvfOjoBpj5oj1XDmVDjiScs/dAunrCfZ9WyvnQ8HXkXDe8Uy91BVguYI8sGI8jNRjxCnkfJM8/Do+V5ogrjdy6is4vhmi6PoucAy8QZj6h3E55H5D+V97/6VIA5nthNIKYguCLFzdQspHXZuF2X7wk7w1caOduAuQ/A3Qnhk6HlaOTjIGsLxKtvPpqF3Ll85zXhlSftrz4VYA7tfigHfvWcKH5xrp9KBsxe4h0exOcteOf5bn9OACZEfRrRXMb2rnAgcvE4eZ+AuZitI8yOwmwIz/CXdwZHf2xMvgHmhjzOPt2urDLk4vgjyKWFknyAREoMV9skuou/wVyuzJl0pouYuSWUt3Hw8mbems1jM/P/Pi/f4PXiMAW/TA2w5GVcokspS/y5NCF42y/UpZoFFFBAAQUUUEABJy3+B6BFBuObiHkkAAAAAElFTkSuQmCC', 12 | mimeType: 'image/png', 13 | }, 14 | ]; 15 | -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- 1 | # CLAUDE.md 2 | 3 | This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 4 | 5 | ## Development Commands 6 | 7 | This is a Svelte MCP (Model Context Protocol) server implementation that includes both SvelteKit web interface and MCP server functionality. 8 | 9 | ### Setup 10 | 11 | ```bash 12 | pnpm i 13 | cp .env.example .env 14 | # Set the VOYAGE_API_KEY for embeddings support in .env 15 | pnpm dev 16 | ``` 17 | 18 | ### Common Commands 19 | 20 | - `pnpm dev` - Start SvelteKit development server 21 | - `pnpm build` - Build the application for production 22 | - `pnpm start` - Run the MCP server (Node.js entry point) 23 | - `pnpm check` - Run Svelte type checking 24 | - `pnpm check:watch` - Run type checking in watch mode 25 | - `pnpm lint` - Run prettier check and eslint 26 | - `pnpm format` - Format code with prettier 27 | - `pnpm test` - Run unit tests with vitest 28 | - `pnpm test:watch` - Run tests in watch mode 29 | 30 | ### Database Commands (Drizzle ORM) 31 | 32 | - `pnpm db:push` - Push schema changes to database 33 | - `pnpm db:generate` - Generate migration files 34 | - `pnpm db:migrate` - Run migrations 35 | - `pnpm db:studio` - Open Drizzle Studio 36 | 37 | ## Architecture 38 | 39 | ### MCP Server Implementation 40 | 41 | The core MCP server is implemented in `src/lib/mcp/index.ts` using the `tmcp` library with: 42 | 43 | - **Transport Layers**: Both HTTP (`HttpTransport`) and STDIO (`StdioTransport`) support 44 | - **Schema Validation**: Uses Valibot with `ValibotJsonSchemaAdapter` 45 | - **Main Tool**: `svelte-autofixer` - analyzes Svelte code and provides suggestions/fixes 46 | 47 | ### Code Analysis Engine 48 | 49 | Located in `src/lib/server/analyze/`: 50 | 51 | - **Parser** (`parse.ts`): Uses `svelte-eslint-parser` and TypeScript parser to analyze Svelte components 52 | - **Scope Analysis**: Tracks variables, references, and scopes across the AST 53 | - **Rune Detection**: Identifies Svelte 5 runes (`$state`, `$effect`, `$derived`, etc.) 54 | 55 | ### Autofixer System 56 | 57 | - **Autofixers** (`src/lib/mcp/autofixers.ts`): Visitor pattern implementations for code analysis 58 | - **Walker Utility** (`src/lib/index.ts`): Enhanced AST walking with visitor mixing capabilities 59 | - **Current Autofixer**: `assign_in_effect` - detects assignments to `$state` variables inside `$effect` blocks 60 | 61 | ### Database Layer 62 | 63 | - **ORM**: Drizzle with SQLite backend 64 | - **Schema** (`src/lib/server/db/schema.ts`): Vector table for embeddings support 65 | - **Utils** (`src/lib/server/db/utils.ts`): Custom float32 array type for vectors 66 | 67 | ### SvelteKit Integration 68 | 69 | - **Hooks** (`src/hooks.server.ts`): Integrates MCP HTTP transport with SvelteKit requests 70 | - **Routes**: Basic web interface for the MCP server 71 | 72 | ## Key Dependencies 73 | 74 | - **tmcp**: Core MCP server implementation 75 | - **@tmcp/transport-http** & **@tmcp/transport-stdio**: Transport layers 76 | - **@tmcp/adapter-valibot**: Schema validation adapter 77 | - **svelte-eslint-parser**: Svelte component parsing 78 | - **zimmerframe**: AST walking utilities 79 | - **drizzle-orm**: Database ORM with SQLite 80 | - **valibot**: Schema validation library 81 | 82 | ## Environment Configuration 83 | 84 | Required environment variables: 85 | 86 | - `DATABASE_URL`: SQLite database path (default: `file:test.db`) 87 | - `VOYAGE_API_KEY`: API key for embeddings support (optional) 88 | 89 | When connected to the svelte-llm MCP server, you have access to comprehensive Svelte 5 and SvelteKit documentation. Here's how to use the available tools effectively: 90 | 91 | ## Available MCP Tools: 92 | 93 | ### 1. list-sections 94 | 95 | Use this FIRST to discover all available documentation sections. Returns a structured list with titles and paths. 96 | When asked about Svelte or SvelteKit topics, ALWAYS use this tool at the start of the chat to find relevant sections. 97 | 98 | ### 2. get-documentation 99 | 100 | Retrieves full documentation content for specific sections. Accepts single or multiple sections. 101 | After calling the list-sections tool, you MUST analyze the returned documentation sections and then use the get_documentation tool to fetch ALL documentation sections that are relevant for the users task. 102 | -------------------------------------------------------------------------------- /packages/mcp-server/src/mcp/handlers/prompts/svelte-task.ts: -------------------------------------------------------------------------------- 1 | import type { SvelteMcp } from '../../index.js'; 2 | import * as v from 'valibot'; 3 | import { format_sections_list } from '../../utils.js'; 4 | import { icons } from '../../icons/index.js'; 5 | import { prompt } from 'tmcp/utils'; 6 | 7 | /** 8 | * Function that actually generates the prompt string. You can use this in the MCP server handler to generate the prompt, it can accept arguments 9 | * if needed (it will always be invoked manually so it's up to you to provide the arguments). 10 | */ 11 | function svelte_task(available_docs: string, task: string) { 12 | return `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. However: before invoking the \`get-documentation\` tool, try to answer the users query using your own knowledge and the \`svelte-autofixer\` tool. Be mindful of how many section you request, since it is token-intensive! 13 | 14 | 15 | ${available_docs} 16 | 17 | 18 | 19 | These are the available documentation sections that \`list-sections\` will return, you do not need to call it again. 20 | 21 | 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. 22 | 23 | This is the task you will work on: 24 | 25 | 26 | ${task} 27 | 28 | 29 | If you are not writing the code into a file, once you have the final version of the code ask the user if it wants to generate a playground link to quickly check the code in it and if it answer yes call the \`playground-link\` tool and return the url to the user nicely formatted. The playground link MUST be generated only once you have the final version of the code and you are ready to share it, it MUST include an entry point file called \`App.svelte\` where the main component should live. If you have multiple files to include in the playground link you can include them all at the root.`; 30 | } 31 | 32 | /** 33 | * This function is used to generate the prompt to update the docs in the script `/scripts/update-docs-prompts.ts` it should use the default export 34 | * function and pass in the arguments. Since it will be included in the documentation if it's an argument that the MCP will expose it should 35 | * be in the format [NAME_OF_THE_ARGUMENT] to signal the user that it can substitute it. 36 | * 37 | * The name NEEDS to be `generate_for_docs`. 38 | */ 39 | export async function generate_for_docs() { 40 | const available_docs = await format_sections_list(); 41 | return svelte_task(available_docs, '[YOUR TASK HERE]'); 42 | } 43 | 44 | /** 45 | * Human readable description of what the prompt does. It will be included in the documentation. 46 | * 47 | * The name NEEDS to be `docs_description`. 48 | */ 49 | export const docs_description = 50 | 'This prompt should be used whenever you are asking the model to work on a Svelte-related task. It will instruct the LLM which documentation sections are available, which tools to invoke, when to invoke them, and how to interpret the results.'; 51 | 52 | export function setup_svelte_task(server: SvelteMcp) { 53 | server.prompt( 54 | { 55 | name: 'svelte-task', 56 | title: 'Svelte-Task-Prompt', 57 | description: 58 | '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.', 59 | schema: v.object({ 60 | task: v.pipe(v.string(), v.description('The task to be performed')), 61 | }), 62 | complete: { 63 | task() { 64 | return { 65 | completion: { 66 | values: [''], 67 | }, 68 | }; 69 | }, 70 | }, 71 | icons, 72 | }, 73 | async ({ task }) => { 74 | if (server.ctx.sessionId && server.ctx.custom?.track) { 75 | await server.ctx.custom?.track?.(server.ctx.sessionId, 'svelte-task'); 76 | } 77 | const available_docs = await format_sections_list(); 78 | 79 | return prompt.text(svelte_task(available_docs, task)); 80 | }, 81 | ); 82 | } 83 | -------------------------------------------------------------------------------- /packages/mcp-server/src/mcp/handlers/tools/get-documentation.ts: -------------------------------------------------------------------------------- 1 | import type { SvelteMcp } from '../../index.js'; 2 | import * as v from 'valibot'; 3 | import { get_sections, fetch_with_timeout, format_sections_list } from '../../utils.js'; 4 | import { SECTIONS_LIST_INTRO, SECTIONS_LIST_OUTRO } from './prompts.js'; 5 | import { icons } from '../../icons/index.js'; 6 | import { tool } from 'tmcp/utils'; 7 | 8 | export function get_documentation(server: SvelteMcp) { 9 | server.tool( 10 | { 11 | name: 'get-documentation', 12 | description: 13 | 'Retrieves full documentation content for Svelte 5 or SvelteKit sections. Supports flexible search by title (e.g., "$state", "routing") or file path (e.g., "cli/overview"). 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. Before calling this tool, try to implement Svelte components using your own knowledge and the `svelte-autofixer` tool, since calling this tool is token intensive.', 14 | schema: v.object({ 15 | section: v.pipe( 16 | v.union([v.string(), v.array(v.string())]), 17 | v.description( 18 | 'The section name(s) to retrieve. Can search by title (e.g., "$state", "load functions") or file path (e.g., "cli/overview"). Supports single string and array of strings', 19 | ), 20 | ), 21 | }), 22 | icons, 23 | }, 24 | async ({ section }) => { 25 | if (server.ctx.sessionId && server.ctx.custom?.track) { 26 | await server.ctx.custom?.track?.(server.ctx.sessionId, 'get-documentation'); 27 | } 28 | let sections: string[]; 29 | 30 | if (Array.isArray(section)) { 31 | sections = section.filter((s): s is string => typeof s === 'string'); 32 | } else if ( 33 | typeof section === 'string' && 34 | section.trim().startsWith('[') && 35 | section.trim().endsWith(']') 36 | ) { 37 | try { 38 | const parsed = JSON.parse(section); 39 | if (Array.isArray(parsed)) { 40 | sections = parsed.filter((s): s is string => typeof s === 'string'); 41 | } else { 42 | sections = [section]; 43 | } 44 | } catch { 45 | sections = [section]; 46 | } 47 | } else if (typeof section === 'string') { 48 | sections = [section]; 49 | } else { 50 | sections = []; 51 | } 52 | 53 | const available_sections = await get_sections(); 54 | 55 | const settled_results = await Promise.allSettled( 56 | sections.map(async (requested_section) => { 57 | const matched_section = available_sections.find( 58 | (s) => 59 | s.title.toLowerCase() === requested_section.toLowerCase() || 60 | s.slug === requested_section || 61 | s.url === requested_section, 62 | ); 63 | 64 | if (matched_section) { 65 | try { 66 | const response = await fetch_with_timeout(matched_section.url); 67 | if (response.ok) { 68 | const content = await response.text(); 69 | return { success: true, content: `## ${matched_section.title}\n\n${content}` }; 70 | } else { 71 | return { 72 | success: false, 73 | content: `## ${matched_section.title}\n\nError: Could not fetch documentation (HTTP ${response.status})`, 74 | }; 75 | } 76 | } catch (error) { 77 | return { 78 | success: false, 79 | content: `## ${matched_section.title}\n\nError: Failed to fetch documentation - ${error}`, 80 | }; 81 | } 82 | } else { 83 | return { 84 | success: false, 85 | content: `## ${requested_section}\n\nError: Section not found.`, 86 | }; 87 | } 88 | }), 89 | ); 90 | 91 | const results = settled_results.map((result) => { 92 | if (result.status === 'fulfilled') { 93 | return result.value; 94 | } else { 95 | return { 96 | success: false, 97 | content: `Error: Couldn't fetch - ${result.reason}`, 98 | }; 99 | } 100 | }); 101 | 102 | const has_any_success = results.some((result) => result.success); 103 | let final_text = results.map((r) => r.content).join('\n\n---\n\n'); 104 | 105 | if (!has_any_success) { 106 | const formatted_sections = await format_sections_list(); 107 | 108 | final_text += `\n\n---\n\n${SECTIONS_LIST_INTRO}\n\n${formatted_sections}\n\n${SECTIONS_LIST_OUTRO}`; 109 | } 110 | 111 | return tool.text(final_text); 112 | }, 113 | ); 114 | } 115 | -------------------------------------------------------------------------------- /packages/mcp-server/src/mcp/handlers/tools/playground-link.ts: -------------------------------------------------------------------------------- 1 | import type { SvelteMcp } from '../../index.js'; 2 | import * as v from 'valibot'; 3 | import { icons } from '../../icons/index.js'; 4 | import { createUIResource } from '@mcp-ui/server'; 5 | import { tool } from 'tmcp/utils'; 6 | 7 | async function compress_and_encode_text(input: string) { 8 | const reader = new Blob([input]).stream().pipeThrough(new CompressionStream('gzip')).getReader(); 9 | let buffer = ''; 10 | for (;;) { 11 | const { done, value } = await reader.read(); 12 | if (done) { 13 | reader.releaseLock(); 14 | // Some sites like discord don't like it when links end with = 15 | return btoa(buffer).replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, ''); 16 | } else { 17 | for (let i = 0; i < value.length; i++) { 18 | // decoding as utf-8 will make btoa reject the string 19 | buffer += String.fromCharCode(value[i]!); 20 | } 21 | } 22 | } 23 | } 24 | 25 | type File = { 26 | type: 'file'; 27 | name: string; 28 | basename: string; 29 | contents: string; 30 | text: boolean; 31 | }; 32 | 33 | export function playground_link(server: SvelteMcp) { 34 | server.tool( 35 | { 36 | name: 'playground-link', 37 | description: 38 | 'Generates a Playground link given a Svelte code snippet. Once you have the final version of the code you want to send to the user, ALWAYS ask the user if it wants a playground link to allow it to quickly check the code in the playground before calling this tool. NEVER use this tool if you have written the component to a file in the user project. The playground accept multiple files so if are importing from other files just include them all at the root level.', 39 | schema: v.object({ 40 | name: v.pipe( 41 | v.string(), 42 | v.description('The name of the Playground, it should reflect the user task'), 43 | ), 44 | tailwind: v.pipe( 45 | v.boolean(), 46 | v.description( 47 | "If the code requires Tailwind CSS to work...only send true if it it's using tailwind classes in the code", 48 | ), 49 | ), 50 | files: v.pipe( 51 | v.record(v.string(), v.string()), 52 | v.description( 53 | "An object where all the keys are the filenames (with extensions) and the values are the file content. For example: { 'Component.svelte': '', 'utils.js': 'export function ...' }. The playground accept multiple files so if are importing from other files just include them all at the root level.", 54 | ), 55 | ), 56 | }), 57 | outputSchema: v.object({ 58 | url: v.string(), 59 | }), 60 | icons, 61 | }, 62 | async ({ files, name, tailwind }) => { 63 | if (server.ctx.sessionId && server.ctx.custom?.track) { 64 | await server.ctx.custom?.track?.(server.ctx.sessionId, 'playground-link'); 65 | } 66 | const playground_base = new URL('https://svelte.dev/playground'); 67 | const playground_files: File[] = []; 68 | 69 | let has_app_svelte = false; 70 | 71 | for (const [filename, contents] of Object.entries(files)) { 72 | if (filename === 'App.svelte') has_app_svelte = true; 73 | playground_files.push({ 74 | type: 'file', 75 | name: filename, 76 | basename: filename.replace(/^.*[\\/]/, ''), 77 | contents, 78 | text: true, 79 | }); 80 | } 81 | 82 | if (!has_app_svelte) { 83 | if (server.ctx.sessionId && server.ctx.custom?.track) { 84 | await server.ctx.custom?.track?.(server.ctx.sessionId, 'playground-link-no-app-svelte'); 85 | } 86 | 87 | return tool.error('The files must contain an App.svelte file as the entry point'); 88 | } 89 | 90 | const playground_config = { 91 | name, 92 | tailwind: tailwind ?? false, 93 | files: playground_files, 94 | }; 95 | 96 | playground_base.hash = await compress_and_encode_text(JSON.stringify(playground_config)); 97 | 98 | const content = { 99 | url: playground_base.toString(), 100 | }; 101 | 102 | // use the embed path to have a cleaner UI for mcp-ui 103 | playground_base.pathname = '/playground/embed'; 104 | 105 | return { 106 | content: [ 107 | { 108 | type: 'text', 109 | text: JSON.stringify(content), 110 | }, 111 | createUIResource({ 112 | uri: 'ui://svelte/playground-link', 113 | content: { 114 | type: 'externalUrl', 115 | iframeUrl: playground_base.toString(), 116 | }, 117 | uiMetadata: { 118 | 'preferred-frame-size': ['100%', '1200px'], 119 | }, 120 | encoding: 'text', 121 | }), 122 | ], 123 | structuredContent: content, 124 | }; 125 | }, 126 | ); 127 | } 128 | -------------------------------------------------------------------------------- /packages/mcp-server/src/mcp/handlers/tools/svelte-autofixer.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { InMemoryTransport } from '@tmcp/transport-in-memory'; 3 | import { beforeEach, describe, expect, it } from 'vitest'; 4 | import { server } from '../../index.js'; 5 | 6 | const transport = new InMemoryTransport(server); 7 | 8 | let session: ReturnType; 9 | 10 | async function autofixer_tool_call( 11 | code: string, 12 | is_error = false, 13 | desired_svelte_version = 5, 14 | async = false, 15 | ) { 16 | const result = await session.callTool('svelte-autofixer', { 17 | code, 18 | desired_svelte_version, 19 | filename: 'App.svelte', 20 | async, 21 | }); 22 | 23 | expect(result).toBeDefined(); 24 | if (is_error) { 25 | return result as any; 26 | } 27 | expect(result.structuredContent).toBeDefined(); 28 | return result.structuredContent as any; 29 | } 30 | 31 | describe('svelte-autofixer tool', () => { 32 | beforeEach(async () => { 33 | session = transport.session(); 34 | 35 | session = transport.session(); 36 | await session.initialize( 37 | '2025-06-18', 38 | {}, 39 | { 40 | name: 'test-client', 41 | version: '1.0.0', 42 | }, 43 | ); 44 | }); 45 | 46 | it('should add suggestions for js parse errors', async () => { 47 | const content = await autofixer_tool_call(``); 50 | expect(content.issues.length).toBeGreaterThan(0); 51 | expect(content.suggestions).toContain( 52 | "The code can't be compiled because a Javascript parse error. In case you are using runes like this `$state variable_name = 3;` or `$derived variable_name = 3 * count` that's not how runes are used. You need to use them as function calls without importing them: `const variable_name = $state(3)` and `const variable_name = $derived(3 * count)`.", 53 | ); 54 | }); 55 | 56 | it('should error out if async is true with a version less than 5', async () => { 57 | const content = await autofixer_tool_call( 58 | ``, 61 | true, 62 | 4, 63 | true, 64 | ); 65 | expect(content.isError).toBeTruthy(); 66 | expect(content.content[0]).toBeDefined(); 67 | expect(content.content[0].text).toBe( 68 | 'The async option can only be used with Svelte version 5 or higher.', 69 | ); 70 | }); 71 | 72 | it('should not add suggestion/issues if async is true and await is used in the template/derived', async () => { 73 | const content = await autofixer_tool_call( 74 | ` 79 | 80 | {double} 81 | {await slow_double(count)}`, 82 | false, 83 | 5, 84 | true, 85 | ); 86 | expect(content.issues).toHaveLength(0); 87 | expect(content.suggestions).toHaveLength(0); 88 | expect(content.require_another_tool_call_after_fixing).toBeFalsy(); 89 | }); 90 | 91 | it('should add suggestion/issues if async is false and await is used in the template/derived', async () => { 92 | const content = await autofixer_tool_call( 93 | ` 98 | 99 | {double} 100 | {await slow_double(count)}`, 101 | false, 102 | 5, 103 | ); 104 | expect(content.issues.length).toBeGreaterThanOrEqual(1); 105 | expect(content.issues).toEqual( 106 | expect.arrayContaining([expect.stringContaining('experimental_async')]), 107 | ); 108 | expect(content.require_another_tool_call_after_fixing).toBeTruthy(); 109 | }); 110 | 111 | it('should add suggestions for css invalid identifier', async () => { 112 | const content = await autofixer_tool_call(` 115 | 116 | `); 121 | 122 | expect(content.issues.length).toBeGreaterThan(0); 123 | expect(content.suggestions).toContain( 124 | "The code can't be compiled because a valid CSS identifier is expected. This sometimes means you are trying to use a variable in CSS like this: `color: {my_color}` but Svelte doesn't support that. You can use inline CSS variables for that `
` and then use the variable as usual in CSS with `color: var(--color)`.", 125 | ); 126 | }); 127 | 128 | it('should error in case the passed in version is different from 4 or 5', async () => { 129 | const content = await autofixer_tool_call(`whatever`, true, 3); 130 | 131 | expect(content.content).toBeDefined(); 132 | expect(content.content[0]).toBeDefined(); 133 | expect(content.content[0].text).toContain( 134 | 'The desired_svelte_version MUST be either 4 or 5 but received "3"', 135 | ); 136 | }); 137 | }); 138 | -------------------------------------------------------------------------------- /packages/mcp-stdio/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @sveltejs/mcp 2 | 3 | ## 0.1.15 4 | 5 | ### Patch Changes 6 | 7 | - fix: server.json version + update publisher ([`9dfb4de`](https://github.com/sveltejs/mcp/commit/9dfb4dedb42837c40c4e660f0f816d7cf9081fc4)) 8 | 9 | ## 0.1.14 10 | 11 | ### Patch Changes 12 | 13 | - fix: improve prompt to reduce token usage ([#124](https://github.com/sveltejs/mcp/pull/124)) 14 | 15 | ## 0.1.13 16 | 17 | ### Patch Changes 18 | 19 | - fix: revert name change and add title ([`98efa1e`](https://github.com/sveltejs/mcp/commit/98efa1e09ebcca7827b10dc6bc8e1699fc1e5171)) 20 | 21 | ## 0.1.12 22 | 23 | ### Patch Changes 24 | 25 | - fix: update server name on mcp registry ([`60297b3`](https://github.com/sveltejs/mcp/commit/60297b3c49bf110b48908e61b5d5d902ea1bdf39)) 26 | 27 | - chore: update tmcp ([#99](https://github.com/sveltejs/mcp/pull/99)) 28 | 29 | ## 0.1.11 30 | 31 | ### Patch Changes 32 | 33 | - fix: add `async` parameter to `svelte-autofixer` ([#94](https://github.com/sveltejs/mcp/pull/94)) 34 | 35 | - fix: install latest eslint svelte packages to support `$state.eager` ([`f6ce89f`](https://github.com/sveltejs/mcp/commit/f6ce89ff34faabc3d746a350ea347298ecfed2ec)) 36 | 37 | ## 0.1.10 38 | 39 | ### Patch Changes 40 | 41 | - fix: add icons to `server.json` ([`02c951b`](https://github.com/sveltejs/mcp/commit/02c951baa86ac8103ffc158a202c06cfe6b15c01)) 42 | 43 | - fix: add `preferred-frame-size` to UI resource ([`3fabcc0`](https://github.com/sveltejs/mcp/commit/3fabcc0f9bfee916c0deb9c2ffa931ed2168af2d)) 44 | 45 | - feat: support: `$state.eager` ([#90](https://github.com/sveltejs/mcp/pull/90)) 46 | 47 | ## 0.1.9 48 | 49 | ### Patch Changes 50 | 51 | - feat: return `mcp-ui` resource from `playground-link` ([#84](https://github.com/sveltejs/mcp/pull/84)) 52 | 53 | - feat: suggest against js variables in css ([#78](https://github.com/sveltejs/mcp/pull/78)) 54 | 55 | ## 0.1.8 56 | 57 | ### Patch Changes 58 | 59 | - fix: upgrade registry publisher cli ([`5fa2baa`](https://github.com/sveltejs/mcp/commit/5fa2baa27009f01e0e4e91cee7984b81a81c1c29)) 60 | 61 | ## 0.1.7 62 | 63 | ### Patch Changes 64 | 65 | - fix: use correct server schema version ([`579be87`](https://github.com/sveltejs/mcp/commit/579be877fa9f87f7f173450ca5bc918824d68282)) 66 | 67 | ## 0.1.6 68 | 69 | ### Patch Changes 70 | 71 | - fix: prevent `imported_runes` suggestion from being added for libs that are not svelte ([`87af64f`](https://github.com/sveltejs/mcp/commit/87af64f4bc6d07b75640eb987a33655654363997)) 72 | 73 | - feat: add svelte icon and website url for mcp server ([#75](https://github.com/sveltejs/mcp/pull/75)) 74 | 75 | - fix: use `data:` uri for local icon & add icons to tools + resources + prompts ([`cf62286`](https://github.com/sveltejs/mcp/commit/cf622869129382a97ad059bb1389f115907adc8e)) 76 | 77 | ## 0.1.5 78 | 79 | ### Patch Changes 80 | 81 | - fix: widen `desired_svelte_version` validation to accommodate some clients ([`3b301d7`](https://github.com/sveltejs/mcp/commit/3b301d7d9c2f49758023408f505bc4ca79caaff4)) 82 | 83 | - fix: minor tweaks to the prompt to allow for automatic sync ([#63](https://github.com/sveltejs/mcp/pull/63)) 84 | 85 | - feat: `read_state_with_dollar` autofixer ([#66](https://github.com/sveltejs/mcp/pull/66)) 86 | 87 | ## 0.1.4 88 | 89 | ### Patch Changes 90 | 91 | - fix: pass secrets in action and update `mcpName` ([`044f098`](https://github.com/sveltejs/mcp/commit/044f0988b935fff39911a861a648dfb276f5831a)) 92 | 93 | ## 0.1.3 94 | 95 | ### Patch Changes 96 | 97 | - fix: use DNS to publish MCP ([#59](https://github.com/sveltejs/mcp/pull/59)) 98 | 99 | ## 0.1.2 100 | 101 | ### Patch Changes 102 | 103 | - fix: publish to MCP registry (I really hope this time for real) ([`ef5241c`](https://github.com/sveltejs/mcp/commit/ef5241cbc204ad8bb84bde27db7c9d0a08280245)) 104 | 105 | ## 0.1.1 106 | 107 | ### Patch Changes 108 | 109 | - feat: publish mcp to registry (maybe for real this time) ([`132943d`](https://github.com/sveltejs/mcp/commit/132943db3b04dbbd322d08926c0880c990a61f5f)) 110 | 111 | ## 0.1.0 112 | 113 | ### Minor Changes 114 | 115 | - feat: publish to registry ([#45](https://github.com/sveltejs/mcp/pull/45)) 116 | 117 | ### Patch Changes 118 | 119 | - feat: add autofixer to tell the LLM to check if some function called in effect is assigning state #26 ([`73d7625`](https://github.com/sveltejs/mcp/commit/73d7625b3ca6a812ba91883ea668d80ff1e7c703)) 120 | 121 | - feat: add bind:this -> attachment and action -> attachment autofixer #20 ([`73d7625`](https://github.com/sveltejs/mcp/commit/73d7625b3ca6a812ba91883ea668d80ff1e7c703)) 122 | 123 | ## 0.0.4 124 | 125 | ### Patch Changes 126 | 127 | - fix: allow TS `.svelte.ts` modules ([#49](https://github.com/sveltejs/mcp/pull/49)) 128 | 129 | ## 0.0.3 130 | 131 | ### Patch Changes 132 | 133 | - fix: check effect.pre in assign-in-effect ([#41](https://github.com/sveltejs/mcp/pull/41)) 134 | 135 | - feat: `use_cases` documentation metadata ([#29](https://github.com/sveltejs/mcp/pull/29)) 136 | 137 | - fix: change title names to allow for claude code to use the prompt ([`725f785`](https://github.com/sveltejs/mcp/commit/725f785766d04e9ed810a7c3f6bcfdb2e2b8234c)) 138 | 139 | - fix: enable doc tools ([`cb316c5`](https://github.com/sveltejs/mcp/commit/cb316c5b3ebc712946969d2d57236d159e796d58)) 140 | 141 | ## 0.0.2 142 | 143 | ### Patch Changes 144 | 145 | - feat: latest version ([#25](https://github.com/sveltejs/mcp/pull/25)) 146 | -------------------------------------------------------------------------------- /packages/mcp-server/src/parse/parse.spec.ts: -------------------------------------------------------------------------------- 1 | import type { TSESTree } from '@typescript-eslint/types'; 2 | import { describe, expect, it } from 'vitest'; 3 | import { walk } from '../mcp/autofixers/ast/walk.js'; 4 | import { parse, type ParseResult } from './parse.js'; 5 | 6 | // ---------------------------------------------------------------------- 7 | // Helpers 8 | // ---------------------------------------------------------------------- 9 | 10 | function find_declaration_identifier( 11 | result: ParseResult, 12 | matches: (id: TSESTree.Identifier, path: Array | null) => boolean, 13 | ): TSESTree.Identifier { 14 | const { ast } = result; 15 | let found: TSESTree.Identifier | null = null; 16 | walk( 17 | ast as unknown as TSESTree.Identifier, 18 | {}, 19 | { 20 | Identifier(node, { stop, next, path }) { 21 | if (matches(node, path)) { 22 | found = node; 23 | stop(); 24 | } 25 | next(); 26 | }, 27 | }, 28 | ); 29 | if (!found) throw new Error('Declaration Identifier node not found'); 30 | return found; 31 | } 32 | 33 | function variable_declaration_from_id(result: ParseResult, id: TSESTree.Identifier) { 34 | const { all_variables: all_variables } = result; 35 | const variable = all_variables.find((v) => (v.defs ?? []).some((d) => d.name === id)); 36 | if (!variable) { 37 | throw new Error(`Variable for the provided declaration node not found: ${id.name}`); 38 | } 39 | 40 | return variable; 41 | } 42 | 43 | // ---------------------------------------------------------------------- 44 | // Assertions 45 | // ---------------------------------------------------------------------- 46 | 47 | function assert_svelte_file(result: ParseResult) { 48 | const { all_references } = result; 49 | 50 | const declaration_id = find_declaration_identifier(result, (id, path) => { 51 | const parent = path ? path[path.length - 1] : null; 52 | if (!parent || parent.type !== 'Property') return false; 53 | const owner = path ? path[path.length - 2] : null; 54 | return id.name === 'name' && parent.value === id && !!owner && owner.type === 'ObjectPattern'; 55 | }); 56 | 57 | const name_var = variable_declaration_from_id(result, declaration_id); 58 | expect(Array.isArray(name_var.defs)).toBe(true); 59 | expect(name_var.defs.length).toBeGreaterThan(0); 60 | expect(name_var.defs[0]?.type).toBe('Variable'); 61 | expect(name_var.defs[0]?.name && name_var.defs[0].name.name).toBe('name'); 62 | 63 | const references_to_name = all_references.filter((rf) => rf.resolved === name_var); 64 | expect(references_to_name.length).toBeGreaterThan(0); 65 | } 66 | 67 | function assert_sveltejs_file(result: ParseResult) { 68 | const { all_references: all_references } = result; 69 | 70 | const declaration_id = find_declaration_identifier(result, (id, path) => { 71 | const parent = path ? path[path.length - 1] : null; 72 | if (!parent || parent.type !== 'VariableDeclarator') return false; 73 | return id.name === 'v' && parent.id === id; 74 | }); 75 | 76 | const v_var = variable_declaration_from_id(result, declaration_id); 77 | expect(Array.isArray(v_var.defs)).toBe(true); 78 | expect(v_var.defs.length).toBeGreaterThan(0); 79 | expect(v_var.defs[0]?.type).toBeTruthy(); 80 | 81 | const references_to_v = all_references.filter((rf) => rf.resolved === v_var); 82 | expect(references_to_v.length).toBeGreaterThanOrEqual(2); 83 | 84 | const unresolved_update_references = all_references.filter( 85 | (rf) => rf.identifier && rf.identifier.name === 'update' && !rf.resolved, 86 | ); 87 | expect(unresolved_update_references.length).toBeGreaterThan(0); 88 | } 89 | 90 | // ---------------------------------------------------------------------- 91 | // Tests 92 | // ---------------------------------------------------------------------- 93 | 94 | describe('parse() - Svelte component parsing', () => { 95 | it('parses a basic .svelte component (JS)', () => { 96 | const code = ` 99 | 100 |

Hello {name}

`; 101 | 102 | const result = parse(code, '/virtual/Component.svelte'); 103 | assert_svelte_file(result); 104 | }); 105 | 106 | it('parses a .svelte component with 113 | 114 |

Hello {name}

`; 115 | 116 | const result = parse(code, '/virtual/Counter.svelte'); 117 | assert_svelte_file(result); 118 | }); 119 | 120 | it('parses a .svelte.js file as a Svelte component (JS)', () => { 121 | const code = `export const foo = () => { 122 | let v = $state(0); 123 | const updete = (value) => { 124 | v.set(value); 125 | } 126 | return { 127 | get v() { 128 | return v.get(); 129 | }, 130 | update 131 | } 132 | };`; 133 | 134 | const result = parse(code, '/virtual/Widget.svelte.js'); 135 | assert_sveltejs_file(result); 136 | }); 137 | 138 | it('parses a .svelte.ts file as a Svelte component (TS)', () => { 139 | const code = `export const foo = () => { 140 | let v: number = $state(0); 141 | const updete = (value: number) => { 142 | v.set(value); 143 | } 144 | return { 145 | get v(): number { 146 | return v.get(); 147 | }, 148 | update 149 | } 150 | };`; 151 | 152 | const result = parse(code, '/virtual/Header.svelte.ts'); 153 | assert_sveltejs_file(result); 154 | }); 155 | }); 156 | -------------------------------------------------------------------------------- /packages/mcp-server/src/lib/anthropic.ts: -------------------------------------------------------------------------------- 1 | import { Anthropic } from '@anthropic-ai/sdk'; 2 | import type { Model } from '@anthropic-ai/sdk/resources/messages/messages.js'; 3 | import * as v from 'valibot'; 4 | import { 5 | anthropic_batch_response_schema, 6 | anthropic_batch_result_schema, 7 | type AnthropicBatchRequest, 8 | } from './schemas.js'; 9 | 10 | export class AnthropicProvider { 11 | private client: Anthropic; 12 | private modelId: Model; 13 | private baseUrl: string; 14 | private apiKey: string; 15 | name = 'Anthropic'; 16 | 17 | constructor(model_id: Model, api_key: string) { 18 | if (!api_key) { 19 | throw new Error('ANTHROPIC_API_KEY is required'); 20 | } 21 | this.apiKey = api_key; 22 | this.client = new Anthropic({ apiKey: api_key, timeout: 1800000 }); 23 | this.modelId = model_id; 24 | this.baseUrl = 'https://api.anthropic.com/v1'; 25 | } 26 | 27 | get_client(): Anthropic { 28 | return this.client; 29 | } 30 | 31 | get_model_identifier(): Model { 32 | return this.modelId; 33 | } 34 | 35 | async create_batch(requests: AnthropicBatchRequest[]) { 36 | try { 37 | const response = await fetch(`${this.baseUrl}/messages/batches`, { 38 | method: 'POST', 39 | headers: { 40 | 'x-api-key': this.apiKey, 41 | 'anthropic-version': '2023-06-01', 42 | 'content-type': 'application/json', 43 | }, 44 | body: JSON.stringify({ requests }), 45 | }); 46 | 47 | if (!response.ok) { 48 | const error_text = await response.text(); 49 | throw new Error( 50 | `Failed to create batch: ${response.status} ${response.statusText} - ${error_text}`, 51 | ); 52 | } 53 | 54 | const json_data = await response.json(); 55 | const validated_response = v.safeParse(anthropic_batch_response_schema, json_data); 56 | 57 | if (!validated_response.success) { 58 | throw new Error( 59 | `Invalid batch response from Anthropic API: ${JSON.stringify(validated_response.issues)}`, 60 | ); 61 | } 62 | 63 | return validated_response.output; 64 | } catch (error) { 65 | console.error('Error creating batch with Anthropic:', error); 66 | throw new Error( 67 | `Failed to create batch: ${error instanceof Error ? error.message : String(error)}`, 68 | ); 69 | } 70 | } 71 | 72 | async get_batch_status(batch_id: string, max_retries = 10, retry_delay = 30000) { 73 | let retry_count = 0; 74 | 75 | while (retry_count <= max_retries) { 76 | try { 77 | const response = await fetch(`${this.baseUrl}/messages/batches/${batch_id}`, { 78 | method: 'GET', 79 | headers: { 80 | 'x-api-key': this.apiKey, 81 | 'anthropic-version': '2023-06-01', 82 | }, 83 | }); 84 | 85 | if (!response.ok) { 86 | const error_text = await response.text(); 87 | throw new Error( 88 | `Failed to get batch status: ${response.status} ${response.statusText} - ${error_text}`, 89 | ); 90 | } 91 | 92 | const json_data = await response.json(); 93 | const validated_response = v.safeParse(anthropic_batch_response_schema, json_data); 94 | 95 | if (!validated_response.success) { 96 | throw new Error( 97 | `Invalid batch status response from Anthropic API: ${JSON.stringify(validated_response.issues)}`, 98 | ); 99 | } 100 | 101 | return validated_response.output; 102 | } catch (error) { 103 | retry_count++; 104 | 105 | if (retry_count > max_retries) { 106 | console.error( 107 | `Error getting batch status for ${batch_id} after ${max_retries} retries:`, 108 | error, 109 | ); 110 | throw new Error( 111 | `Failed to get batch status after ${max_retries} retries: ${ 112 | error instanceof Error ? error.message : String(error) 113 | }`, 114 | ); 115 | } 116 | 117 | console.warn( 118 | `Error getting batch status for ${batch_id} (attempt ${retry_count}/${max_retries}):`, 119 | error, 120 | ); 121 | console.log(`Retrying in ${retry_delay / 1000} seconds...`); 122 | 123 | await new Promise((resolve) => setTimeout(resolve, retry_delay)); 124 | } 125 | } 126 | 127 | // This should never be reached due to the throw in the catch block, but TypeScript needs a return 128 | throw new Error(`Failed to get batch status for ${batch_id} after ${max_retries} retries`); 129 | } 130 | 131 | async get_batch_results(results_url: string) { 132 | try { 133 | const response = await fetch(results_url, { 134 | method: 'GET', 135 | headers: { 136 | 'x-api-key': this.apiKey, 137 | 'anthropic-version': '2023-06-01', 138 | }, 139 | }); 140 | 141 | if (!response.ok) { 142 | const error_text = await response.text(); 143 | throw new Error( 144 | `Failed to get batch results: ${response.status} ${response.statusText} - ${error_text}`, 145 | ); 146 | } 147 | 148 | const text = await response.text(); 149 | // Parse JSONL format (one JSON object per line) 150 | const parsed_results = text 151 | .split('\n') 152 | .filter((line) => line.trim()) 153 | .map((line) => JSON.parse(line)); 154 | 155 | // Validate all results 156 | const validated_results = v.safeParse(v.array(anthropic_batch_result_schema), parsed_results); 157 | 158 | if (!validated_results.success) { 159 | throw new Error( 160 | `Invalid batch results from Anthropic API: ${JSON.stringify(validated_results.issues)}`, 161 | ); 162 | } 163 | 164 | return validated_results.output; 165 | } catch (error) { 166 | console.error(`Error getting batch results:`, error); 167 | throw new Error( 168 | `Failed to get batch results: ${error instanceof Error ? error.message : String(error)}`, 169 | ); 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /packages/mcp-server/src/mcp/handlers/tools/svelte-autofixer.ts: -------------------------------------------------------------------------------- 1 | import { basename } from 'node:path'; 2 | import type { SvelteMcp } from '../../index.js'; 3 | import * as v from 'valibot'; 4 | import { add_compile_issues } from '../../autofixers/add-compile-issues.js'; 5 | import { add_eslint_issues } from '../../autofixers/add-eslint-issues.js'; 6 | import { add_autofixers_issues } from '../../autofixers/add-autofixers-issues.js'; 7 | import { icons } from '../../icons/index.js'; 8 | import { tool } from 'tmcp/utils'; 9 | 10 | export function svelte_autofixer(server: SvelteMcp) { 11 | server.tool( 12 | { 13 | name: 'svelte-autofixer', 14 | title: 'Svelte Autofixer', 15 | description: 16 | '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', 17 | schema: v.object({ 18 | code: v.string(), 19 | desired_svelte_version: v.pipe( 20 | v.union([v.string(), v.number()]), 21 | v.description( 22 | '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.', 23 | ), 24 | ), 25 | async: v.pipe( 26 | v.optional(v.boolean()), 27 | v.description( 28 | 'If true the code is an async component/module and might use await in the markup or top-level awaits in the script tag. If possible check the svelte.config.js/svelte.config.ts to check if the option is enabled otherwise asks the user if they prefer using it or not. You can only use this option if the version is 5.', 29 | ), 30 | ), 31 | filename: v.pipe( 32 | v.optional(v.string()), 33 | v.description( 34 | '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.', 35 | ), 36 | ), 37 | }), 38 | outputSchema: v.object({ 39 | issues: v.array(v.string()), 40 | suggestions: v.array(v.string()), 41 | require_another_tool_call_after_fixing: v.boolean(), 42 | }), 43 | annotations: { 44 | title: 'Svelte Autofixer', 45 | destructiveHint: false, 46 | readOnlyHint: true, 47 | openWorldHint: false, 48 | }, 49 | icons, 50 | }, 51 | async ({ 52 | code, 53 | filename: filename_or_path, 54 | desired_svelte_version: desired_svelte_version_unchecked, 55 | async, 56 | }) => { 57 | if (server.ctx.sessionId && server.ctx.custom?.track) { 58 | await server.ctx.custom?.track?.(server.ctx.sessionId, 'svelte-autofixer'); 59 | } 60 | // we validate manually because some clients don't support union in the input schema (looking at you cursor) 61 | const parsed_version = v.safeParse( 62 | v.union([v.literal(4), v.literal(5), v.literal('4'), v.literal('5')]), 63 | desired_svelte_version_unchecked, 64 | ); 65 | if (parsed_version.success === false) { 66 | if (server.ctx.sessionId && server.ctx.custom?.track) { 67 | await server.ctx.custom?.track?.(server.ctx.sessionId, 'svelte-autofixer-wrong-version'); 68 | } 69 | return tool.error( 70 | `The desired_svelte_version MUST be either 4 or 5 but received "${desired_svelte_version_unchecked}"`, 71 | ); 72 | } 73 | 74 | const desired_svelte_version = parsed_version.output; 75 | 76 | if (async && +desired_svelte_version < 5) { 77 | return tool.error('The async option can only be used with Svelte version 5 or higher.'); 78 | } 79 | 80 | const content: { 81 | issues: string[]; 82 | suggestions: string[]; 83 | require_another_tool_call_after_fixing: boolean; 84 | } = { issues: [], suggestions: [], require_another_tool_call_after_fixing: false }; 85 | try { 86 | // just in case the LLM sends a full path we extract the filename...it's not really needed 87 | // but it's nice to have a filename in the errors 88 | 89 | const filename = filename_or_path ? basename(filename_or_path) : 'Component.svelte'; 90 | 91 | add_compile_issues(content, code, +desired_svelte_version, filename, async); 92 | 93 | add_autofixers_issues(content, code, +desired_svelte_version, filename, async); 94 | 95 | await add_eslint_issues(content, code, +desired_svelte_version, filename, async); 96 | } catch (e: unknown) { 97 | const error = e as Error & { start?: { line: number; column: number } }; 98 | content.issues.push( 99 | `${error.message} at line ${error.start?.line}, column ${error.start?.column}`, 100 | ); 101 | if (error.message.includes('js_parse_error')) { 102 | content.suggestions.push( 103 | "The code can't be compiled because a Javascript parse error. In case you are using runes like this `$state variable_name = 3;` or `$derived variable_name = 3 * count` that's not how runes are used. You need to use them as function calls without importing them: `const variable_name = $state(3)` and `const variable_name = $derived(3 * count)`.", 104 | ); 105 | } else if (error.message.includes('css_expected_identifier')) { 106 | content.suggestions.push( 107 | "The code can't be compiled because a valid CSS identifier is expected. This sometimes means you are trying to use a variable in CSS like this: `color: {my_color}` but Svelte doesn't support that. You can use inline CSS variables for that `
` and then use the variable as usual in CSS with `color: var(--color)`.", 108 | ); 109 | } 110 | } 111 | 112 | if (content.issues.length > 0 || content.suggestions.length > 0) { 113 | content.require_another_tool_call_after_fixing = true; 114 | } 115 | 116 | return tool.structured(content); 117 | }, 118 | ); 119 | } 120 | -------------------------------------------------------------------------------- /docs/tmcp.md: -------------------------------------------------------------------------------- 1 | > [!WARNING] 2 | > 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. 3 | 4 | # tmcp 5 | 6 | A lightweight, schema-agnostic Model Context Protocol (MCP) server implementation with unified API design. 7 | 8 | ## Why tmcp? 9 | 10 | tmcp offers significant advantages over the official MCP SDK: 11 | 12 | - **🔄 Schema Agnostic**: Works with any validation library through adapters 13 | - **📦 No Weird Dependencies**: Minimal footprint with only essential dependencies (looking at you `express`) 14 | - **🎯 Unified API**: Consistent, intuitive interface across all MCP capabilities 15 | - **🔌 Extensible**: Easy to add support for new schema libraries 16 | - **⚡ Lightweight**: No bloat, just what you need 17 | 18 | ## Supported Schema Libraries 19 | 20 | tmcp works with all major schema validation libraries through its adapter system: 21 | 22 | - **Zod** - `@tmcp/adapter-zod` 23 | - **Valibot** - `@tmcp/adapter-valibot` 24 | - **ArkType** - `@tmcp/adapter-arktype` 25 | - **Effect Schema** - `@tmcp/adapter-effect` 26 | - **Zod v3** - `@tmcp/adapter-zod-v3` 27 | 28 | ## Installation 29 | 30 | ```bash 31 | pnpm install tmcp 32 | # Choose your preferred schema library adapter 33 | pnpm install @tmcp/adapter-zod zod 34 | # Choose your preferred transport 35 | pnpm install @tmcp/transport-stdio # For CLI/desktop apps 36 | pnpm install @tmcp/transport-http # For web-based clients 37 | ``` 38 | 39 | ## Quick Start 40 | 41 | ### Standard I/O Transport (CLI/Desktop) 42 | 43 | ```javascript 44 | import { McpServer } from 'tmcp'; 45 | import { ZodJsonSchemaAdapter } from '@tmcp/adapter-zod'; 46 | import { StdioTransport } from '@tmcp/transport-stdio'; 47 | import { z } from 'zod'; 48 | 49 | const adapter = new ZodJsonSchemaAdapter(); 50 | const server = new McpServer( 51 | { 52 | name: 'my-server', 53 | version: '1.0.0', 54 | description: 'My awesome MCP server', 55 | }, 56 | { 57 | adapter, 58 | capabilities: { 59 | tools: { listChanged: true }, 60 | prompts: { listChanged: true }, 61 | resources: { listChanged: true }, 62 | }, 63 | }, 64 | ); 65 | 66 | // Define a tool with type-safe schema 67 | server.tool( 68 | { 69 | name: 'calculate', 70 | description: 'Perform mathematical calculations', 71 | schema: z.object({ 72 | operation: z.enum(['add', 'subtract', 'multiply', 'divide']), 73 | a: z.number(), 74 | b: z.number(), 75 | }), 76 | }, 77 | async ({ operation, a, b }) => { 78 | switch (operation) { 79 | case 'add': 80 | return a + b; 81 | case 'subtract': 82 | return a - b; 83 | case 'multiply': 84 | return a * b; 85 | case 'divide': 86 | return a / b; 87 | } 88 | }, 89 | ); 90 | 91 | // Start the server with stdio transport 92 | const transport = new StdioTransport(server); 93 | transport.listen(); 94 | ``` 95 | 96 | ### HTTP Transport (Web-based) 97 | 98 | ```javascript 99 | import { McpServer } from 'tmcp'; 100 | import { ZodJsonSchemaAdapter } from '@tmcp/adapter-zod'; 101 | import { HttpTransport } from '@tmcp/transport-http'; 102 | import { z } from 'zod'; 103 | 104 | const adapter = new ZodJsonSchemaAdapter(); 105 | const server = new McpServer(/* ... same server config ... */); 106 | 107 | // Add tools as above... 108 | 109 | // Create HTTP transport 110 | const transport = new HttpTransport(server); 111 | 112 | // Use with your preferred HTTP server (Bun example) 113 | Bun.serve({ 114 | port: 3000, 115 | async fetch(req) { 116 | const response = await transport.respond(req); 117 | if (response === null) { 118 | return new Response('Not Found', { status: 404 }); 119 | } 120 | return response; 121 | }, 122 | }); 123 | ``` 124 | 125 | ## API Reference 126 | 127 | ### McpServer 128 | 129 | The main server class that handles MCP protocol communications. 130 | 131 | #### Constructor 132 | 133 | ```javascript 134 | new McpServer(serverInfo, options); 135 | ``` 136 | 137 | - `serverInfo`: Server metadata (name, version, description) 138 | - `options`: Configuration object with adapter and capabilities 139 | 140 | #### Methods 141 | 142 | ##### `tool(definition, handler)` 143 | 144 | Register a tool with optional schema validation. 145 | 146 | ```javascript 147 | server.tool( 148 | { 149 | name: 'tool-name', 150 | description: 'Tool description', 151 | schema: yourSchema, // optional 152 | }, 153 | async (input) => { 154 | // Tool implementation 155 | return result; 156 | }, 157 | ); 158 | ``` 159 | 160 | ##### `prompt(definition, handler)` 161 | 162 | Register a prompt template with optional schema validation. 163 | 164 | ```javascript 165 | server.prompt( 166 | { 167 | name: 'prompt-name', 168 | description: 'Prompt description', 169 | schema: yourSchema, // optional 170 | complete: (arg, context) => ['completion1', 'completion2'] // optional 171 | }, 172 | async (input) => { 173 | // Prompt implementation 174 | return { messages: [...] }; 175 | } 176 | ); 177 | ``` 178 | 179 | ##### `resource(definition, handler)` 180 | 181 | Register a static resource. 182 | 183 | ```javascript 184 | server.resource( 185 | { 186 | name: 'resource-name', 187 | description: 'Resource description', 188 | uri: 'file://path/to/resource' 189 | }, 190 | async (uri, params) => { 191 | // Resource implementation 192 | return { contents: [...] }; 193 | } 194 | ); 195 | ``` 196 | 197 | ##### `template(definition, handler)` 198 | 199 | Register a URI template for dynamic resources. 200 | 201 | ```javascript 202 | server.template( 203 | { 204 | name: 'template-name', 205 | description: 'Template description', 206 | uri: 'file://path/{id}/resource', 207 | complete: (arg, context) => ['id1', 'id2'] // optional 208 | }, 209 | async (uri, params) => { 210 | // Template implementation using params.id 211 | return { contents: [...] }; 212 | } 213 | ); 214 | ``` 215 | 216 | ##### `receive(request)` 217 | 218 | Process an incoming MCP request. 219 | 220 | ```javascript 221 | const response = server.receive(jsonRpcRequest); 222 | ``` 223 | 224 | ## Advanced Examples 225 | 226 | ### Multiple Schema Libraries 227 | 228 | ```javascript 229 | // Use different schemas for different tools 230 | import { z } from 'zod'; 231 | import * as v from 'valibot'; 232 | 233 | server.tool( 234 | { 235 | name: 'zod-tool', 236 | schema: z.object({ name: z.string() }), 237 | }, 238 | async ({ name }) => `Hello ${name}`, 239 | ); 240 | 241 | server.tool( 242 | { 243 | name: 'valibot-tool', 244 | schema: v.object({ age: v.number() }), 245 | }, 246 | async ({ age }) => `Age: ${age}`, 247 | ); 248 | ``` 249 | 250 | ### Resource Templates with Completion 251 | 252 | ```javascript 253 | server.template( 254 | { 255 | name: 'user-profile', 256 | description: 'Get user profile by ID', 257 | uri: 'users/{userId}/profile', 258 | complete: (arg, context) => { 259 | // Provide completions for userId parameter 260 | return ['user1', 'user2', 'user3']; 261 | }, 262 | }, 263 | async (uri, params) => { 264 | const user = await getUserById(params.userId); 265 | return { 266 | contents: [ 267 | { 268 | uri, 269 | mimeType: 'application/json', 270 | text: JSON.stringify(user), 271 | }, 272 | ], 273 | }; 274 | }, 275 | ); 276 | ``` 277 | 278 | ### Complex Validation 279 | 280 | ```javascript 281 | const complexSchema = z.object({ 282 | user: z.object({ 283 | name: z.string().min(1), 284 | email: z.string().email(), 285 | age: z.number().min(18).max(120), 286 | }), 287 | preferences: z 288 | .object({ 289 | theme: z.enum(['light', 'dark']), 290 | notifications: z.boolean(), 291 | }) 292 | .optional(), 293 | tags: z.array(z.string()).default([]), 294 | }); 295 | 296 | server.tool( 297 | { 298 | name: 'create-user', 299 | description: 'Create a new user with preferences', 300 | schema: complexSchema, 301 | }, 302 | async (input) => { 303 | // Input is fully typed and validated 304 | const { user, preferences, tags } = input; 305 | return await createUser(user, preferences, tags); 306 | }, 307 | ); 308 | ``` 309 | 310 | ## Contributing 311 | 312 | Contributions are welcome! Please see our [contributing guidelines](../../CONTRIBUTING.md) for details. 313 | 314 | ## Acknowledgments 315 | 316 | Huge thanks to Sean O'Bannon that provided us with the `@tmcp` scope on npm. 317 | 318 | ## License 319 | 320 | MIT © Paolo Ricciuti 321 | -------------------------------------------------------------------------------- /packages/mcp-server/scripts/generate-summaries.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import 'dotenv/config'; 3 | import { writeFile, mkdir } from 'fs/promises'; 4 | import path from 'path'; 5 | import { fileURLToPath } from 'url'; 6 | import { get_sections } from '../src/mcp/utils.ts'; 7 | import { AnthropicProvider } from '../src/lib/anthropic.ts'; 8 | import { type AnthropicBatchRequest, type SummaryData } from '../src/lib/schemas.ts'; 9 | 10 | const current_filename = fileURLToPath(import.meta.url); 11 | const current_dirname = path.dirname(current_filename); 12 | 13 | const USE_CASES_PROMPT = ` 14 | You are tasked with analyzing Svelte 5 and SvelteKit documentation pages to identify when they would be useful. 15 | 16 | Your task: 17 | 1. Read the documentation page content provided 18 | 2. Identify the main use cases, scenarios, or queries where this documentation would be relevant 19 | 3. Create a VERY SHORT, comma-separated list of use cases (maximum 200 characters total) 20 | 4. Think about what a developer might be trying to build or accomplish when they need this documentation 21 | 22 | Guidelines: 23 | - Focus on WHEN this documentation would be needed, not WHAT it contains 24 | - Consider specific project types (e.g., "e-commerce site", "blog", "dashboard", "social media app") 25 | - Consider specific features (e.g., "authentication", "forms", "data fetching", "animations") 26 | - Consider specific components (e.g., "slider", "modal", "dropdown", "card") 27 | - Consider development stages (e.g., "project setup", "deployment", "testing", "migration") 28 | - Use "always" for fundamental concepts that apply to virtually all Svelte projects 29 | - Be concise but specific 30 | - Use lowercase 31 | - Separate multiple use cases with commas 32 | 33 | Examples of good use_cases: 34 | - "always, any svelte project, core reactivity" 35 | - "authentication, login systems, user management" 36 | - "e-commerce, product listings, shopping carts" 37 | - "forms, user input, data submission" 38 | - "deployment, production builds, hosting setup" 39 | - "animation, transitions, interactive ui" 40 | - "routing, navigation, multi-page apps" 41 | - "blog, content sites, markdown rendering" 42 | 43 | Requirements: 44 | - Maximum 200 characters (including spaces and commas) 45 | - Lowercase only 46 | - Comma-separated list of use cases 47 | - Focus on WHEN/WHY someone would need this, not what it is 48 | - Be specific about project types, features, or components when applicable 49 | - Use "always" sparingly, only for truly universal concepts 50 | - Do not include quotes or special formatting in your response 51 | - Respond with ONLY the use cases text, no additional text 52 | 53 | Here is the documentation page content to analyze: 54 | 55 | `; 56 | 57 | async function fetch_section_content(url: string) { 58 | const response = await fetch(url, { signal: AbortSignal.timeout(30000) }); 59 | if (!response.ok) { 60 | throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`); 61 | } 62 | return await response.text(); 63 | } 64 | 65 | async function main() { 66 | console.log('🚀 Starting use cases generation...'); 67 | 68 | // Check for API key 69 | const api_key = process.env.ANTHROPIC_API_KEY; 70 | if (!api_key) { 71 | console.error('❌ Error: ANTHROPIC_API_KEY environment variable is required'); 72 | console.error('Please set it in packages/mcp-server/.env file or export it:'); 73 | console.error('export ANTHROPIC_API_KEY=your_api_key_here'); 74 | process.exit(1); 75 | } 76 | 77 | // Get all sections 78 | console.log('📚 Fetching documentation sections...'); 79 | let sections = await get_sections(); 80 | console.log(`Found ${sections.length} sections`); 81 | 82 | // Debug mode: limit to 2 sections 83 | const debug_mode = process.env.DEBUG_MODE === '1'; 84 | if (debug_mode) { 85 | console.log('🐛 DEBUG_MODE enabled - processing only 2 sections'); 86 | sections = sections.slice(0, 2); 87 | } 88 | 89 | // Fetch content for each section 90 | console.log('📥 Downloading section content...'); 91 | const sections_with_content: Array<{ 92 | section: (typeof sections)[number]; 93 | content: string; 94 | index: number; 95 | }> = []; 96 | const download_errors: Array<{ section: string; error: string }> = []; 97 | 98 | for (let i = 0; i < sections.length; i++) { 99 | const section = sections[i]!; 100 | try { 101 | console.log(`Fetching ${i + 1}/${sections.length}: ${section.title}`); 102 | const content = await fetch_section_content(section.url); 103 | sections_with_content.push({ 104 | section, 105 | content, 106 | index: i, 107 | }); 108 | } catch (error) { 109 | const error_msg = error instanceof Error ? error.message : String(error); 110 | console.error(`⚠️ Failed to fetch ${section.title}:`, error_msg); 111 | download_errors.push({ section: section.title, error: error_msg }); 112 | } 113 | } 114 | 115 | console.log(`✅ Successfully downloaded ${sections_with_content.length} sections`); 116 | 117 | if (sections_with_content.length === 0) { 118 | console.error('❌ No sections were successfully downloaded'); 119 | process.exit(1); 120 | } 121 | 122 | // Initialize Anthropic client 123 | console.log('🤖 Initializing Anthropic API...'); 124 | const anthropic = new AnthropicProvider('claude-sonnet-4-5-20250929', api_key); 125 | 126 | // Prepare batch requests 127 | console.log('📦 Preparing batch requests...'); 128 | const batch_requests: AnthropicBatchRequest[] = sections_with_content.map( 129 | ({ content, index }) => ({ 130 | custom_id: `section-${index}`, 131 | params: { 132 | model: anthropic.get_model_identifier(), 133 | max_tokens: 250, 134 | messages: [ 135 | { 136 | role: 'user', 137 | content: USE_CASES_PROMPT + content, 138 | }, 139 | ], 140 | temperature: 0, 141 | }, 142 | }), 143 | ); 144 | 145 | // Create and process batch 146 | console.log('🚀 Creating batch job...'); 147 | const batch_response = await anthropic.create_batch(batch_requests); 148 | console.log(`✅ Batch created with ID: ${batch_response.id}`); 149 | 150 | // Poll for completion 151 | console.log('⏳ Waiting for batch to complete...'); 152 | let batch_status = await anthropic.get_batch_status(batch_response.id); 153 | 154 | while (batch_status.processing_status === 'in_progress') { 155 | const { succeeded, processing, errored } = batch_status.request_counts; 156 | console.log(` Progress: ${succeeded} succeeded, ${processing} processing, ${errored} errored`); 157 | await new Promise((resolve) => setTimeout(resolve, 5000)); 158 | batch_status = await anthropic.get_batch_status(batch_response.id); 159 | } 160 | 161 | console.log('✅ Batch processing completed!'); 162 | 163 | // Get results 164 | if (!batch_status.results_url) { 165 | throw new Error('Batch completed but no results URL available'); 166 | } 167 | 168 | console.log('📥 Downloading results...'); 169 | const results = await anthropic.get_batch_results(batch_status.results_url); 170 | 171 | // Process results 172 | console.log('📊 Processing results...'); 173 | const summaries: Record = {}; 174 | const errors: Array<{ section: string; error: string }> = []; 175 | 176 | for (const result of results) { 177 | const index = parseInt(result.custom_id.split('-')[1] ?? '0'); 178 | const section_data = sections_with_content.find((s) => s.index === index); 179 | 180 | if (!section_data) { 181 | console.warn(`⚠️ Could not find section for index ${index}`); 182 | continue; 183 | } 184 | 185 | const { section } = section_data; 186 | 187 | if (result.result.type !== 'succeeded' || !result.result.message) { 188 | const error_msg = result.result.error?.message || 'Failed or no message'; 189 | console.error(` ❌ ${section.title}: ${error_msg}`); 190 | errors.push({ section: section.title, error: error_msg }); 191 | continue; 192 | } 193 | 194 | const output_content = result.result.message.content[0]?.text; 195 | if (output_content) { 196 | summaries[section.slug] = output_content.trim(); 197 | console.log(` ✅ ${section.title}`); 198 | } 199 | } 200 | 201 | // Write output to JSON file 202 | console.log('💾 Writing results to file...'); 203 | const output_path = path.join(current_dirname, '../src/use_cases.json'); 204 | const output_dir = path.dirname(output_path); 205 | 206 | await mkdir(output_dir, { recursive: true }); 207 | 208 | const summary_data: SummaryData = { 209 | generated_at: new Date().toISOString(), 210 | model: anthropic.get_model_identifier(), 211 | total_sections: sections.length, 212 | successful_summaries: Object.keys(summaries).length, 213 | failed_summaries: errors.length, 214 | summaries, 215 | errors: errors.length > 0 ? errors : undefined, 216 | download_errors: download_errors.length > 0 ? download_errors : undefined, 217 | }; 218 | 219 | await writeFile(output_path, JSON.stringify(summary_data, null, 2), 'utf-8'); 220 | 221 | // Print summary 222 | console.log('\n📊 Summary:'); 223 | console.log(` Total sections: ${sections.length}`); 224 | console.log(` Successfully downloaded: ${sections_with_content.length}`); 225 | console.log(` Download failures: ${download_errors.length}`); 226 | console.log(` Successfully analyzed: ${Object.keys(summaries).length}`); 227 | console.log(` Analysis failures: ${errors.length}`); 228 | console.log(`\n✅ Results written to: ${output_path}`); 229 | 230 | if (download_errors.length > 0) { 231 | console.log('\n⚠️ Some sections failed to download:'); 232 | download_errors.forEach((e) => console.log(` - ${e.section}: ${e.error}`)); 233 | } 234 | 235 | if (errors.length > 0) { 236 | console.log('\n⚠️ Some sections failed to analyze:'); 237 | errors.forEach((e) => console.log(` - ${e.section}: ${e.error}`)); 238 | } 239 | } 240 | 241 | main().catch((error) => { 242 | console.error('❌ Fatal error:', error); 243 | process.exit(1); 244 | }); 245 | -------------------------------------------------------------------------------- /packages/mcp-server/src/use_cases.json: -------------------------------------------------------------------------------- 1 | { 2 | "generated_at": "2025-10-02T20:29:23.410Z", 3 | "model": "claude-sonnet-4-5-20250929", 4 | "total_sections": 167, 5 | "successful_summaries": 167, 6 | "failed_summaries": 0, 7 | "summaries": { 8 | "docs/cli/overview": "project setup, creating new svelte apps, scaffolding, cli tools, initializing projects", 9 | "docs/cli/faq": "project setup, initializing new svelte projects, troubleshooting cli installation, package manager configuration", 10 | "docs/cli/sv-create": "project setup, starting new sveltekit app, initializing project, creating from playground, choosing project template", 11 | "docs/cli/sv-add": "project setup, adding features to existing projects, integrating tools, testing setup, styling setup, authentication, database setup, deployment adapters", 12 | "docs/cli/sv-check": "code quality, ci/cd pipelines, error checking, typescript projects, pre-commit hooks, finding unused css, accessibility auditing, production builds", 13 | "docs/cli/sv-migrate": "migration, upgrading svelte versions, upgrading sveltekit versions, modernizing codebase, svelte 3 to 4, svelte 4 to 5, sveltekit 1 to 2, adopting runes, refactoring deprecated apis", 14 | "docs/cli/devtools-json": "development setup, chrome devtools integration, browser-based editing, local development workflow, debugging setup", 15 | "docs/cli/drizzle": "database setup, sql queries, orm integration, data modeling, postgresql, mysql, sqlite, server-side data access, database migrations, type-safe queries", 16 | "docs/cli/eslint": "code quality, linting, error detection, project setup, code standards, team collaboration, typescript projects", 17 | "docs/cli/lucia": "authentication, login systems, user management, registration pages, session handling, auth setup", 18 | "docs/cli/mdsvex": "blog, content sites, markdown rendering, documentation sites, technical writing, cms integration, article pages", 19 | "docs/cli/paraglide": "internationalization, multi-language sites, i18n, translation, localization, language switching, global apps, multilingual content", 20 | "docs/cli/playwright": "browser testing, e2e testing, integration testing, test automation, quality assurance, ci/cd pipelines, testing user flows", 21 | "docs/cli/prettier": "code formatting, project setup, code style consistency, team collaboration, linting configuration", 22 | "docs/cli/storybook": "component development, design systems, ui library, isolated component testing, documentation, visual testing, component showcase", 23 | "docs/cli/sveltekit-adapter": "deployment, production builds, hosting setup, choosing deployment platform, configuring adapters, static site generation, node server, vercel, cloudflare, netlify", 24 | "docs/cli/tailwind": "project setup, styling, css framework, rapid prototyping, utility-first css, design systems, responsive design, adding tailwind to svelte", 25 | "docs/cli/vitest": "testing, unit tests, component testing, test setup, quality assurance, ci/cd pipelines, test-driven development", 26 | "docs/kit/introduction": "learning sveltekit, project setup, understanding framework basics, choosing between svelte and sveltekit, getting started with full-stack apps", 27 | "docs/kit/creating-a-project": "project setup, starting new sveltekit app, initial development environment, first-time sveltekit users, scaffolding projects", 28 | "docs/kit/project-types": "deployment, project setup, choosing adapters, ssg, spa, ssr, serverless, mobile apps, desktop apps, pwa, offline apps, browser extensions, separate backend, docker containers", 29 | "docs/kit/project-structure": "project setup, understanding file structure, organizing code, starting new project, learning sveltekit basics", 30 | "docs/kit/web-standards": "always, any sveltekit project, data fetching, forms, api routes, server-side rendering, deployment to various platforms", 31 | "docs/kit/routing": "routing, navigation, multi-page apps, project setup, file structure, api endpoints, data loading, layouts, error pages, always", 32 | "docs/kit/load": "data fetching, api calls, database queries, dynamic routes, page initialization, loading states, authentication checks, ssr data, form data, content rendering", 33 | "docs/kit/form-actions": "forms, user input, data submission, authentication, login systems, user registration, progressive enhancement, validation errors", 34 | "docs/kit/page-options": "prerendering static sites, ssr configuration, spa setup, client-side rendering control, url trailing slash handling, adapter deployment config, build optimization", 35 | "docs/kit/state-management": "sveltekit, server-side rendering, ssr, state management, authentication, data persistence, load functions, context api, navigation, component lifecycle", 36 | "docs/kit/remote-functions": "data fetching, server-side logic, database queries, type-safe client-server communication, forms, user input, mutations, authentication, crud operations, optimistic updates", 37 | "docs/kit/building-your-app": "production builds, deployment preparation, build process optimization, adapter configuration, preview before deployment", 38 | "docs/kit/adapters": "deployment, production builds, hosting setup, choosing deployment platform, configuring adapters", 39 | "docs/kit/adapter-auto": "deployment, production builds, hosting setup, choosing deployment platform, ci/cd configuration", 40 | "docs/kit/adapter-node": "deployment, production builds, node.js hosting, custom server setup, environment configuration, reverse proxy setup, docker deployment, systemd services", 41 | "docs/kit/adapter-static": "static site generation, ssg, prerendering, deployment, github pages, spa mode, blogs, documentation sites, marketing sites", 42 | "docs/kit/single-page-apps": "spa mode, single-page apps, client-only rendering, static hosting, mobile app wrappers, no server-side logic, adapter-static setup, fallback pages", 43 | "docs/kit/adapter-cloudflare": "deployment, cloudflare workers, cloudflare pages, hosting setup, production builds, serverless deployment, edge computing", 44 | "docs/kit/adapter-cloudflare-workers": "deploying to cloudflare workers, cloudflare workers sites deployment, legacy cloudflare adapter, wrangler configuration, cloudflare platform bindings", 45 | "docs/kit/adapter-netlify": "deployment, netlify hosting, production builds, serverless functions, edge functions, static site hosting", 46 | "docs/kit/adapter-vercel": "deployment, vercel hosting, production builds, serverless functions, edge functions, isr, image optimization, environment variables", 47 | "docs/kit/writing-adapters": "custom deployment, building adapters, unsupported platforms, adapter development, custom hosting environments", 48 | "docs/kit/advanced-routing": "advanced routing, dynamic routes, file viewers, nested paths, custom 404 pages, url validation, route parameters, multi-level navigation", 49 | "docs/kit/hooks": "authentication, logging, error tracking, request interception, api proxying, custom routing, internationalization, database initialization, middleware logic, session management", 50 | "docs/kit/errors": "error handling, custom error pages, 404 pages, api error responses, production error logging, error tracking, type-safe errors", 51 | "docs/kit/link-options": "routing, navigation, multi-page apps, performance optimization, link preloading, forms with get method, search functionality, focus management, scroll behavior", 52 | "docs/kit/service-workers": "offline support, pwa, caching strategies, performance optimization, precaching assets, network resilience, progressive web apps", 53 | "docs/kit/server-only-modules": "api keys, environment variables, sensitive data protection, backend security, preventing data leaks, server-side code isolation", 54 | "docs/kit/snapshots": "forms, user input, preserving form data, multi-step forms, navigation state, preventing data loss, textarea content, input fields, comment systems, surveys", 55 | "docs/kit/shallow-routing": "modals, dialogs, image galleries, overlays, history-driven ui, mobile-friendly navigation, photo viewers, lightboxes, drawer menus", 56 | "docs/kit/observability": "performance monitoring, debugging, observability, tracing requests, production diagnostics, analyzing slow requests, finding bottlenecks, monitoring server-side operations", 57 | "docs/kit/packaging": "building component libraries, publishing npm packages, creating reusable svelte components, library development, package distribution", 58 | "docs/kit/auth": "authentication, login systems, user management, session handling, jwt tokens, protected routes, user credentials, authorization checks", 59 | "docs/kit/performance": "performance optimization, slow loading pages, production deployment, debugging performance issues, reducing bundle size, improving load times", 60 | "docs/kit/icons": "icons, ui components, styling, css frameworks, tailwind, unocss, performance optimization, dependency management", 61 | "docs/kit/images": "image optimization, responsive images, performance, hero images, product photos, galleries, cms integration, cdn setup, asset management", 62 | "docs/kit/accessibility": "always, any sveltekit project, screen reader support, keyboard navigation, multi-page apps, client-side routing, internationalization, multilingual sites", 63 | "docs/kit/seo": "seo optimization, search engine ranking, content sites, blogs, marketing sites, public-facing apps, sitemaps, amp pages, meta tags, performance optimization", 64 | "docs/kit/faq": "troubleshooting package imports, library compatibility issues, client-side code execution, external api integration, middleware setup, database configuration, view transitions, yarn configuration", 65 | "docs/kit/integrations": "project setup, css preprocessors, postcss, scss, sass, less, stylus, typescript setup, adding integrations, tailwind, testing, auth, linting, formatting", 66 | "docs/kit/debugging": "debugging, breakpoints, development workflow, troubleshooting issues, vscode setup, ide configuration, inspecting code execution", 67 | "docs/kit/migrating-to-sveltekit-2": "migration, upgrading from sveltekit 1 to 2, breaking changes, version updates", 68 | "docs/kit/migrating": "migrating from sapper, upgrading legacy projects, sapper to sveltekit conversion, project modernization", 69 | "docs/kit/additional-resources": "troubleshooting, getting help, finding examples, learning sveltekit, project templates, common issues, community support", 70 | "docs/kit/glossary": "rendering strategies, performance optimization, deployment configuration, seo requirements, static sites, spas, server-side rendering, prerendering, edge deployment, pwa development", 71 | "docs/kit/@sveltejs-kit": "forms, form actions, server-side validation, form submission, error handling, redirects, json responses, http errors, server utilities", 72 | "docs/kit/@sveltejs-kit-hooks": "middleware, request processing, authentication chains, logging, multiple hooks, request/response transformation", 73 | "docs/kit/@sveltejs-kit-node-polyfills": "node.js environments, custom servers, non-standard runtimes, ssr setup, web api compatibility, polyfill requirements", 74 | "docs/kit/@sveltejs-kit-node": "node.js adapter, custom server setup, http integration, streaming files, node deployment, server-side rendering with node", 75 | "docs/kit/@sveltejs-kit-vite": "project setup, vite configuration, initial sveltekit setup, build tooling", 76 | "docs/kit/$app-environment": "always, conditional logic, client-side code, server-side code, build-time logic, prerendering, development vs production, environment detection", 77 | "docs/kit/$app-forms": "forms, user input, data submission, progressive enhancement, custom form handling, form validation", 78 | "docs/kit/$app-navigation": "routing, navigation, multi-page apps, programmatic navigation, data reloading, preloading, shallow routing, navigation lifecycle, scroll handling, view transitions", 79 | "docs/kit/$app-paths": "static assets, images, fonts, public files, base path configuration, subdirectory deployment, cdn setup, asset urls, links, navigation", 80 | "docs/kit/$app-server": "remote functions, server-side logic, data fetching, form handling, api endpoints, client-server communication, prerendering, file reading, batch queries", 81 | "docs/kit/$app-state": "routing, navigation, multi-page apps, loading states, url parameters, form handling, error states, version updates, page metadata, shallow routing", 82 | "docs/kit/$app-stores": "legacy projects, sveltekit pre-2.12, migration from stores to runes, maintaining older codebases, accessing page data, navigation state, app version updates", 83 | "docs/kit/$app-types": "routing, navigation, type safety, route parameters, dynamic routes, link generation, pathname validation, multi-page apps", 84 | "docs/kit/$env-dynamic-private": "api keys, secrets management, server-side config, environment variables, backend logic, deployment-specific settings, private data handling", 85 | "docs/kit/$env-dynamic-public": "environment variables, client-side config, runtime configuration, public api keys, deployment-specific settings, multi-environment apps", 86 | "docs/kit/$env-static-private": "server-side api keys, backend secrets, database credentials, private configuration, build-time optimization, server endpoints, authentication tokens", 87 | "docs/kit/$env-static-public": "environment variables, public config, client-side data, api endpoints, build-time configuration, public constants", 88 | "docs/kit/$lib": "project setup, component organization, importing shared components, reusable ui elements, code structure", 89 | "docs/kit/$service-worker": "offline support, pwa, service workers, caching strategies, progressive web apps, offline-first apps", 90 | "docs/kit/configuration": "project setup, configuration, adapters, deployment, build settings, environment variables, routing customization, prerendering, csp security, csrf protection, path configuration, typescript setup", 91 | "docs/kit/cli": "project setup, typescript configuration, generated types, ./$types imports, initial project configuration", 92 | "docs/kit/types": "typescript, type safety, route parameters, api endpoints, load functions, form actions, generated types, jsconfig setup", 93 | "docs/svelte/overview": "always, any svelte project, getting started, learning svelte, introduction, project setup, understanding framework basics", 94 | "docs/svelte/getting-started": "project setup, starting new svelte project, initial installation, choosing between sveltekit and vite, editor configuration", 95 | "docs/svelte/svelte-files": "always, any svelte project, component creation, project setup, learning svelte basics", 96 | "docs/svelte/svelte-js-files": "shared reactive state, reusable reactive logic, state management across components, global stores, custom reactive utilities", 97 | "docs/svelte/what-are-runes": "always, any svelte 5 project, understanding core syntax, learning svelte 5, migration from svelte 4", 98 | "docs/svelte/$state": "always, any svelte project, core reactivity, state management, counters, forms, todo apps, interactive ui, data updates, class-based components", 99 | "docs/svelte/$derived": "always, any svelte project, computed values, reactive calculations, derived data, transforming state, dependent values", 100 | "docs/svelte/$effect": "canvas drawing, third-party library integration, dom manipulation, side effects, intervals, timers, network requests, analytics tracking", 101 | "docs/svelte/$props": "always, any svelte project, passing data to components, component communication, reusable components, component props", 102 | "docs/svelte/$bindable": "forms, user input, two-way data binding, custom input components, parent-child communication, reusable form fields", 103 | "docs/svelte/$inspect": "debugging, development, tracking state changes, reactive state monitoring, troubleshooting reactivity issues", 104 | "docs/svelte/$host": "custom elements, web components, dispatching custom events, component library, framework-agnostic components", 105 | "docs/svelte/basic-markup": "always, any svelte project, basic markup, html templating, component structure, attributes, events, props, text rendering", 106 | "docs/svelte/if": "always, conditional rendering, showing/hiding content, dynamic ui, user permissions, loading states, error handling, form validation", 107 | "docs/svelte/each": "always, lists, arrays, iteration, product listings, todos, tables, grids, dynamic content, shopping carts, user lists, comments, feeds", 108 | "docs/svelte/key": "animations, transitions, component reinitialization, forcing component remount, value-based ui updates, resetting component state", 109 | "docs/svelte/await": "async data fetching, api calls, loading states, promises, error handling, lazy loading components, dynamic imports", 110 | "docs/svelte/snippet": "reusable markup, component composition, passing content to components, table rows, list items, conditional rendering, reducing duplication", 111 | "docs/svelte/@render": "reusable ui patterns, component composition, conditional rendering, fallback content, layout components, slot alternatives, template reuse", 112 | "docs/svelte/@html": "rendering html strings, cms content, rich text editors, markdown to html, blog posts, wysiwyg output, sanitized html injection, dynamic html content", 113 | "docs/svelte/@attach": "tooltips, popovers, dom manipulation, third-party libraries, canvas drawing, element lifecycle, interactive ui, custom directives, wrapper components", 114 | "docs/svelte/@const": "computed values in loops, derived calculations in blocks, local variables in each iterations, complex list rendering", 115 | "docs/svelte/@debug": "debugging, development, troubleshooting, tracking state changes, monitoring variables, reactive data inspection", 116 | "docs/svelte/bind": "forms, user input, two-way data binding, interactive ui, media players, file uploads, checkboxes, radio buttons, select dropdowns, contenteditable, dimension tracking", 117 | "docs/svelte/use": "custom directives, dom manipulation, third-party library integration, tooltips, click outside, gestures, focus management, element lifecycle hooks", 118 | "docs/svelte/transition": "animations, interactive ui, modals, dropdowns, notifications, conditional content, show/hide elements, smooth state changes", 119 | "docs/svelte/in-and-out": "animation, transitions, interactive ui, conditional rendering, independent enter/exit effects, modals, tooltips, notifications", 120 | "docs/svelte/animate": "sortable lists, drag and drop, reorderable items, todo lists, kanban boards, playlist editors, priority queues, animated list reordering", 121 | "docs/svelte/style": "dynamic styling, conditional styles, theming, dark mode, responsive design, interactive ui, component styling", 122 | "docs/svelte/class": "always, conditional styling, dynamic classes, tailwind css, component styling, reusable components, responsive design", 123 | "docs/svelte/await-expressions": "async data fetching, loading states, server-side rendering, awaiting promises in components, async validation, concurrent data loading", 124 | "docs/svelte/scoped-styles": "always, styling components, scoped css, component-specific styles, preventing style conflicts, animations, keyframes", 125 | "docs/svelte/global-styles": "global styles, third-party libraries, css resets, animations, styling body/html, overriding component styles, shared keyframes, base styles", 126 | "docs/svelte/custom-properties": "theming, custom styling, reusable components, design systems, dynamic colors, component libraries, ui customization", 127 | "docs/svelte/nested-style-elements": "component styling, scoped styles, dynamic styles, conditional styling, nested style tags, custom styling logic", 128 | "docs/svelte/svelte-boundary": "error handling, async data loading, loading states, error recovery, flaky components, error reporting, resilient ui", 129 | "docs/svelte/svelte-window": "keyboard shortcuts, scroll tracking, window resize handling, responsive layouts, online/offline detection, viewport dimensions, global event listeners", 130 | "docs/svelte/svelte-document": "document events, visibility tracking, fullscreen detection, pointer lock, focus management, document-level interactions", 131 | "docs/svelte/svelte-body": "mouse tracking, hover effects, cursor interactions, global body events, drag and drop, custom cursors, interactive backgrounds, body-level actions", 132 | "docs/svelte/svelte-head": "seo optimization, page titles, meta tags, social media sharing, dynamic head content, multi-page apps, blog posts, product pages", 133 | "docs/svelte/svelte-element": "dynamic content, cms integration, user-generated content, configurable ui, runtime element selection, flexible components", 134 | "docs/svelte/svelte-options": "migration, custom elements, web components, legacy mode compatibility, runes mode setup, svg components, mathml components, css injection control", 135 | "docs/svelte/stores": "shared state, cross-component data, reactive values, async data streams, manual control over updates, rxjs integration, extracting logic", 136 | "docs/svelte/context": "shared state, avoiding prop drilling, component communication, theme providers, user context, authentication state, configuration sharing, deeply nested components", 137 | "docs/svelte/lifecycle-hooks": "component initialization, cleanup tasks, timers, subscriptions, dom measurements, chat windows, autoscroll features, migration from svelte 4", 138 | "docs/svelte/imperative-component-api": "project setup, client-side rendering, server-side rendering, ssr, hydration, testing, programmatic component creation, tooltips, dynamic mounting", 139 | "docs/svelte/testing": "testing, quality assurance, unit tests, integration tests, component tests, e2e tests, vitest setup, playwright setup, test automation", 140 | "docs/svelte/typescript": "typescript setup, type safety, component props typing, generic components, wrapper components, dom type augmentation, project configuration", 141 | "docs/svelte/custom-elements": "web components, custom elements, component library, design system, framework-agnostic components, embedding svelte in non-svelte apps, shadow dom", 142 | "docs/svelte/v4-migration-guide": "upgrading svelte 3 to 4, version migration, updating dependencies, breaking changes, legacy project maintenance", 143 | "docs/svelte/v5-migration-guide": "migrating from svelte 4 to 5, upgrading projects, learning svelte 5 syntax changes, runes migration, event handler updates", 144 | "docs/svelte/faq": "getting started, learning svelte, beginner setup, project initialization, vs code setup, formatting, testing, routing, mobile apps, troubleshooting, community support", 145 | "docs/svelte/svelte": "migration from svelte 4 to 5, upgrading legacy code, component lifecycle hooks, context api, mounting components, event dispatchers, typescript component types", 146 | "docs/svelte/svelte-action": "typescript types, actions, use directive, dom manipulation, element lifecycle, custom behaviors, third-party library integration", 147 | "docs/svelte/svelte-animate": "animated lists, sortable items, drag and drop, reordering elements, todo lists, kanban boards, playlist management, smooth position transitions", 148 | "docs/svelte/svelte-attachments": "library development, component libraries, programmatic element manipulation, migrating from actions to attachments, spreading props onto elements", 149 | "docs/svelte/svelte-compiler": "build tools, custom compilers, ast manipulation, preprocessors, code transformation, migration scripts, syntax analysis, bundler plugins, dev tools", 150 | "docs/svelte/svelte-easing": "animations, transitions, custom easing, smooth motion, interactive ui, modals, dropdowns, carousels, page transitions, scroll effects", 151 | "docs/svelte/svelte-events": "window events, document events, global event listeners, event delegation, programmatic event handling, cleanup functions, media queries", 152 | "docs/svelte/svelte-legacy": "migration from svelte 4 to svelte 5, upgrading legacy code, event modifiers, class components, imperative component instantiation", 153 | "docs/svelte/svelte-motion": "animation, smooth transitions, interactive ui, sliders, counters, physics-based motion, drag gestures, accessibility, reduced motion", 154 | "docs/svelte/svelte-reactivity-window": "responsive design, viewport tracking, scroll effects, window resize handling, online/offline detection, zoom level tracking", 155 | "docs/svelte/svelte-reactivity": "reactive data structures, state management with maps/sets, game boards, selection tracking, url manipulation, query params, real-time clocks, media queries, responsive design", 156 | "docs/svelte/svelte-server": "server-side rendering, ssr, static site generation, seo optimization, initial page load, pre-rendering, node.js server, custom server setup", 157 | "docs/svelte/svelte-store": "state management, shared data, reactive stores, cross-component communication, global state, computed values, data synchronization, legacy svelte projects", 158 | "docs/svelte/svelte-transition": "animations, transitions, interactive ui, modals, dropdowns, tooltips, notifications, svg animations, list animations, page transitions", 159 | "docs/svelte/compiler-errors": "animation, transitions, keyed each blocks, list animations", 160 | "docs/svelte/compiler-warnings": "accessibility, a11y compliance, wcag standards, screen readers, keyboard navigation, aria attributes, semantic html, interactive elements", 161 | "docs/svelte/runtime-errors": "debugging errors, error handling, troubleshooting runtime issues, migration to svelte 5, component binding, effects and reactivity", 162 | "docs/svelte/runtime-warnings": "debugging state proxies, console logging reactive values, inspecting state changes, development troubleshooting", 163 | "docs/svelte/legacy-overview": "migrating from svelte 3/4 to svelte 5, maintaining legacy components, understanding deprecated features, gradual upgrade process", 164 | "docs/svelte/legacy-let": "migration, legacy svelte projects, upgrading from svelte 4, understanding old reactivity, maintaining existing code, learning runes differences", 165 | "docs/svelte/legacy-reactive-assignments": "legacy mode, migration from svelte 4, reactive statements, computed values, derived state, side effects", 166 | "docs/svelte/legacy-export-let": "legacy mode, migration from svelte 4, maintaining older projects, component props without runes, exporting component methods, renaming reserved word props", 167 | "docs/svelte/legacy-$$props-and-$$restProps": "legacy mode migration, component wrappers, prop forwarding, button components, reusable ui components, spreading props to child elements", 168 | "docs/svelte/legacy-on": "legacy mode, event handling, button clicks, forms, user interactions, component communication, event forwarding, event modifiers", 169 | "docs/svelte/legacy-slots": "legacy mode, migrating from svelte 4, component composition, reusable components, passing content to components, modals, layouts, wrappers", 170 | "docs/svelte/legacy-$$slots": "legacy mode, conditional slot rendering, optional content sections, checking if slots provided, migrating from legacy to runes", 171 | "docs/svelte/legacy-svelte-fragment": "named slots, component composition, layout systems, avoiding wrapper divs, legacy svelte projects, slot content organization", 172 | "docs/svelte/legacy-svelte-component": "dynamic components, component switching, conditional rendering, legacy mode migration, tabbed interfaces, multi-step forms", 173 | "docs/svelte/legacy-svelte-self": "recursive components, tree structures, nested menus, file explorers, comment threads, hierarchical data", 174 | "docs/svelte/legacy-component-api": "migration from svelte 3/4 to 5, legacy component api, maintaining old projects, understanding deprecated patterns" 175 | } 176 | } 177 | --------------------------------------------------------------------------------