Compare commits

..

18 Commits

Author SHA1 Message Date
Stanislav Khromov
7440f7a8a7 add adapter-node 2025-09-24 22:11:11 +02:00
Stanislav Khromov
5923a98d3c Update README.md 2025-09-24 22:00:07 +02:00
Stanislav Khromov
de106c2f24 wip 2025-09-24 21:58:29 +02:00
Stanislav Khromov
7d2ad3fe12 Update CLAUDE.md 2025-09-24 21:55:20 +02:00
Stanislav Khromov
f9ca27b4bb format 2025-09-24 21:42:25 +02:00
Stanislav Khromov
0d773cf133 Delete db.ts 2025-09-24 21:42:16 +02:00
Stanislav Khromov
3b58d51336 Update pathUtils.ts 2025-09-24 21:42:14 +02:00
Stanislav Khromov
67f4571b2e Update schema.js 2025-09-24 21:41:58 +02:00
Stanislav Khromov
31d64712f0 Update pnpm-lock.yaml 2025-09-24 21:40:18 +02:00
Stanislav Khromov
27a6114761 Merge branch 'main' into init-docs 2025-09-24 21:39:19 +02:00
paoloricciuti
f355e6af78 fix: move everything where it should be 2025-09-23 23:31:21 +02:00
paoloricciuti
54f1ae96be Merge branch 'main' into init-docs 2025-09-23 23:10:36 +02:00
Stanislav Khromov
4def7a777a Merge branch 'init-docs' of https://github.com/sveltejs/mcp into init-docs 2025-09-23 23:02:51 +02:00
paoloricciuti
72b894e438 Merge branch 'main' into init-docs 2025-09-22 10:47:00 +02:00
Stanislav Khromov
65ecfa58f8 Create +server.ts 2025-09-22 00:59:43 +02:00
Stanislav Khromov
c15503d847 wip 2025-09-22 00:55:14 +02:00
Stanislav Khromov
250b1be7aa Refactor cache service to use Drizzle ORM 2025-09-22 00:05:03 +02:00
Stanislav Khromov
7be32f6086 wip 2025-09-21 23:57:17 +02:00
87 changed files with 4028 additions and 4138 deletions

View File

@@ -1,8 +0,0 @@
# Changesets
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)

View File

@@ -1,11 +0,0 @@
{
"$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
"changelog": ["@svitejs/changesets-changelog-github-compact", { "repo": "sveltejs/mcp" }],
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": ["!@sveltejs/mcp"]
}

View File

@@ -1,3 +1,3 @@
.claude
.github
.vscode
*.test.ts

View File

@@ -1 +0,0 @@
packages/mcp-server/src/use_cases.json

1
.github/FUNDING.yml vendored
View File

@@ -1 +0,0 @@
open_collective: svelte

View File

@@ -13,15 +13,15 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10.19.0
version: 10
- name: Setup Node.js
uses: actions/setup-node@v6
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'pnpm'

View File

@@ -13,15 +13,15 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10.19.0
version: 10
- name: Setup Node.js
uses: actions/setup-node@v6
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'pnpm'

View File

@@ -1,41 +0,0 @@
name: Publish to MCP Registry
on:
workflow_call:
secrets:
MCP_KEY:
required: true
workflow_dispatch:
jobs:
publish-mcp:
name: Publish to MCP Registry
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v5
- name: Publish to MCP Registry
working-directory: packages/mcp-stdio
env:
MCP_KEY: ${{ secrets.MCP_KEY }}
run: |
NAME=mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz
# Download MCP Publisher pinned to v1.3.3 using latest https for security and save it to a file named mcp-publisher.tar.gz
curl --proto '=https' --proto-redir '=https' --tlsv1.2 -fL "https://github.com/modelcontextprotocol/registry/releases/download/v1.3.3/$NAME" -O
# Verify the SHA256 checksum of the downloaded file
sha256sum --ignore-missing -c ./checksums/registry_1.3.3_checksums.txt
# Extract the tarball
mkdir tmp
tar -xzf $NAME --no-same-owner --no-same-permissions -C tmp
# Install the MCP Publisher binary
install -m 0755 tmp/mcp-publisher .
# Login using DNS
./mcp-publisher login dns --domain svelte.dev --private-key "${MCP_KEY}"
# Publish to MCP Registry
./mcp-publisher publish

View File

@@ -1,69 +0,0 @@
name: Release
on:
push:
branches:
- main
permissions: {}
jobs:
release:
permissions:
contents: write # to create release (changesets/action)
id-token: write # OpenID Connect token needed for provenance
pull-requests: write # to create pull request (changesets/action)
# prevents this action from running on forks
if: github.repository == 'sveltejs/mcp'
name: Release
runs-on: ${{ matrix.os }}
outputs:
published: ${{ steps.changesets.outputs.published }}
strategy:
matrix:
# pseudo-matrix for convenience, NEVER use more than a single combination
node: [24]
os: [ubuntu-latest]
steps:
- name: checkout
uses: actions/checkout@v5
with:
# This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
fetch-depth: 0
- uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node }}
package-manager-cache: false # pnpm is not installed yet
- name: install pnpm
shell: bash
run: |
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)
echo installing pnpm version $PNPM_VER
npm i -g pnpm@$PNPM_VER
- uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node }}
package-manager-cache: true # caches pnpm via packageManager field in package.json
- name: install
run: pnpm install --frozen-lockfile --prefer-offline --ignore-scripts
- name: build
run: pnpm run --filter ./packages/mcp-stdio/ build
- name: Create Release Pull Request or Publish to npm
id: changesets
# pinned for security, always review third party action code before updating
uses: changesets/action@e0145edc7d9d8679003495b11f87bd8ef63c0cba # v1.5.3
with:
# This expects you to have a script called changeset:version version that calls changeset version and updated what it needs to be updated
version: pnpm changeset:version
# This expects you to have a script called release which does a build for your packages and calls changeset publish
publish: pnpm release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_CONFIG_PROVENANCE: true
publish-mcp:
needs: release
if: needs.release.outputs.published == 'true'
uses: ./.github/workflows/publish-mcp.yml
secrets:
MCP_KEY: ${{ secrets.MCP_KEY }}

View File

@@ -13,15 +13,15 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10.19.0
version: 10
- name: Setup Node.js
uses: actions/setup-node@v6
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'pnpm'

View File

@@ -1,78 +0,0 @@
name: Update Prompt Documentation
on:
push:
branches:
- main
paths:
- 'packages/mcp-server/src/mcp/handlers/prompts/**'
permissions:
contents: write
pull-requests: write
jobs:
update-docs:
# prevents this action from running on forks
if: github.repository == 'sveltejs/mcp'
name: Update Prompt Documentation
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24
package-manager-cache: false # pnpm is not installed yet
- name: Install pnpm
shell: bash
run: |
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)
echo installing pnpm version $PNPM_VER
npm i -g pnpm@$PNPM_VER
- name: Setup Node.js with pnpm cache
uses: actions/setup-node@v6
with:
node-version: 24
package-manager-cache: true # caches pnpm via packageManager field in package.json
- name: Install dependencies
run: pnpm install --frozen-lockfile --prefer-offline --ignore-scripts
- name: Generate prompt documentation
run: pnpm generate-prompt-docs
- name: Check for changes
id: git-check
run: |
git diff --exit-code documentation/docs/30-capabilities/30-prompts.md || echo "changed=true" >> $GITHUB_OUTPUT
- name: Create Pull Request
if: steps.git-check.outputs.changed == 'true'
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'docs: update prompts documentation'
branch: docs/update-prompt-docs
delete-branch: true
title: 'docs: update prompt documentation'
body: |
## Summary
Automatically generated documentation update for MCP prompts.
This PR was triggered by changes to the prompts folder in `packages/mcp-server/src/mcp/handlers/prompts/`.
## Changes
- Updated `documentation/docs/30-capabilities/30-prompts.md` with latest prompt definitions
## Generated by
GitHub Action: Update Prompt Documentation
labels: |
documentation
automated

View File

@@ -1,9 +1,8 @@
{
"mcpServers": {
"svelte": {
"type": "stdio",
"command": "node",
"args": ["packages/mcp-stdio/dist/index.js"]
"svelte-llm": {
"type": "http",
"url": "https://svelte-llm.stanislav.garden/mcp/mcp"
}
}
}

View File

@@ -8,8 +8,4 @@ bun.lockb
# Miscellaneous
/static/
/drizzle/
/**/.svelte-kit/*
# Claude Code
.claude/
.changeset/
/**/.svelte-kit/*

View File

@@ -12,7 +12,6 @@
"body": [
"import type { SvelteMcp } from '../../index.js';",
"import * as v from 'valibot';",
"import { icons } from '../../icons/index.js';",
"",
"export function ${1:function_name}(server: SvelteMcp) {",
"\t$0",
@@ -24,71 +23,11 @@
"scope": "javascript,typescript",
"prefix": "!autofixer",
"body": [
"import type { Autofixer } from './index.js';",
"import type { Autofixer } from '.';",
"export const ${1:autofixer_name}: Autofixer = {",
"\t$0",
"};",
],
"description": "Create a setup export for an autofixer",
},
"Prompt Generator": {
"scope": "javascript,typescript",
"prefix": "!prompt",
"body": [
"import type { SvelteMcp } from '../../index.js';",
"import { icons } from '../../icons/index.js';",
"",
"/**",
" * Function that actually generates the prompt string. You can use this in the MCP server handler to generate the prompt, it can accept arguments",
" * if needed (it will always be invoked manually so it's up to you to provide the arguments).",
" */",
"function ${1:prompt_name}() {",
"\treturn `$0`;",
"}",
"",
"/**",
" * 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",
" * 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",
" * be in the format [NAME_OF_THE_ARGUMENT] to signal the user that it can substitute it.",
" * ",
" * The name NEEDS to be `generate_for_docs`.",
" */",
"export async function generate_for_docs() {",
"\treturn ${1:prompt_name}();",
"}",
"",
"/**",
" * Human readable description of what the prompt does. It will be included in the documentation.",
" * ",
" * The name NEEDS to be `docs_description`.",
" */",
"export const docs_description = '';",
"",
"export function setup_${1:prompt_name}(server: SvelteMcp) {",
"\tserver.prompt(",
"\t\t{",
"\t\t\tname: '${1:prompt_name}',",
"\t\t\ttitle: '${2:title}',",
"\t\t\tdescription:",
"\t\t\t\t'${3:llm_description}',",
"\t\t\ticons,",
"\t\t},",
"\t\tasync () => {",
"\t\t\treturn {",
"\t\t\t\tmessages: [",
"\t\t\t\t\t{",
"\t\t\t\t\t\trole: 'assistant',",
"\t\t\t\t\t\tcontent: {",
"\t\t\t\t\t\t\ttype: 'text',",
"\t\t\t\t\t\t\ttext: ${1:prompt_name}(),",
"\t\t\t\t\t\t},",
"\t\t\t\t\t},",
"\t\t\t\t],",
"\t\t\t};",
"\t\t},",
"\t);",
"}",
],
"description": "Create a setup export for a prompt generator",
},
}

2
.vscode/mcp.json vendored
View File

@@ -3,7 +3,7 @@
"Svelte MCP": {
"type": "stdio",
"command": "node",
"args": ["packages/mcp-stdio/dist/index.js"]
"args": ["dist/lib/stdio.js"]
}
},
"inputs": []

114
CLAUDE.md
View File

@@ -2,72 +2,112 @@
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Development Commands
## Monorepo Structure
This is a Svelte MCP (Model Context Protocol) server implementation that includes both SvelteKit web interface and MCP server functionality.
This is a pnpm monorepo containing Svelte MCP (Model Context Protocol) server implementations across multiple packages and applications:
- **apps/mcp-remote**: SvelteKit web application with MCP server functionality
- **packages/mcp-server**: Core MCP server implementation with code analysis tools
- **packages/mcp-stdio**: Standalone MCP server CLI with STDIO transport
- **packages/mcp-schema**: Shared schema definitions and database utilities
## Development Commands
### Setup
```bash
pnpm i
cp .env.example .env
cp apps/mcp-remote/.env.example apps/mcp-remote/.env
# Set the VOYAGE_API_KEY for embeddings support in .env
```
### Starting the mcp-remote app in dev mode
```bash
# Start the SvelteKit development server for mcp-remote (from root)
pnpm dev
```
### Common Commands
Or navigate to the app directory:
```bash
cd apps/mcp-remote
pnpm dev
```
### Common Commands (from root)
- `pnpm build` - Build all packages and applications
- `pnpm check` - Run type checking across all packages
- `pnpm lint` - Run prettier check and eslint across all packages
- `pnpm format` - Format code with prettier across all packages
- `pnpm test` - Run unit tests across all packages
- `pnpm test:watch` - Run tests in watch mode
### mcp-remote App Commands
Navigate to `apps/mcp-remote/` to run these commands:
- `pnpm dev` - Start SvelteKit development server
- `pnpm build` - Build the application for production
- `pnpm build` - Build the SvelteKit application for production
- `pnpm build:mcp` - Build the MCP server TypeScript files
- `pnpm start` - Run the MCP server (Node.js entry point)
- `pnpm check` - Run Svelte type checking
- `pnpm check:watch` - Run type checking in watch mode
- `pnpm lint` - Run prettier check and eslint
- `pnpm format` - Format code with prettier
- `pnpm test` - Run unit tests with vitest
- `pnpm test:watch` - Run tests in watch mode
### Database Commands (Drizzle ORM)
- `pnpm db:push` - Push schema changes to database
- `pnpm db:generate` - Generate migration files
- `pnpm db:migrate` - Run migrations
- `pnpm db:studio` - Open Drizzle Studio
- `pnpm inspect` - Start MCP inspector at http://localhost:6274/
### MCP Inspector Usage
After running `pnpm inspect`, visit http://localhost:6274/:
- Transport type: `Streamable HTTP`
- URL: http://localhost:5173/mcp (when dev server is running)
## Architecture
### MCP Server Implementation
### Monorepo Package Structure
The core MCP server is implemented in `src/lib/mcp/index.ts` using the `tmcp` library with:
- **@sveltejs/mcp-remote**: Full SvelteKit application with web interface and MCP server
- **@sveltejs/mcp-server**: Core MCP server logic and code analysis engine (private workspace package)
- **@sveltejs/mcp**: Standalone CLI MCP server with STDIO transport (publishable)
- **@sveltejs/mcp-schema**: Shared database schema and utilities (private workspace package)
- **Transport Layers**: Both HTTP (`HttpTransport`) and STDIO (`StdioTransport`) support
- **Schema Validation**: Uses Valibot with `ValibotJsonSchemaAdapter`
- **Main Tool**: `svelte-autofixer` - analyzes Svelte code and provides suggestions/fixes
### mcp-remote App (apps/mcp-remote)
### Code Analysis Engine
The main SvelteKit application that provides both web interface and MCP server functionality:
Located in `src/lib/server/analyze/`:
- **Entry Point**: `src/index.js` for Node.js MCP server
- **SvelteKit Integration**: `src/hooks.server.ts` integrates MCP HTTP transport with SvelteKit requests
- **MCP Server**: `src/lib/mcp/index.ts` - HTTP and STDIO transport support
- **Database**: SQLite with Drizzle ORM, vector storage for embeddings
- **Content Sync**: `src/lib/server/contentSync.ts` and `src/lib/server/contentDb.ts` for content management
- **Parser** (`parse.ts`): Uses `svelte-eslint-parser` and TypeScript parser to analyze Svelte components
- **Scope Analysis**: Tracks variables, references, and scopes across the AST
- **Rune Detection**: Identifies Svelte 5 runes (`$state`, `$effect`, `$derived`, etc.)
### mcp-server Package (packages/mcp-server)
### Autofixer System
Core MCP server implementation shared across applications:
- **Autofixers** (`src/lib/mcp/autofixers.ts`): Visitor pattern implementations for code analysis
- **Walker Utility** (`src/lib/index.ts`): Enhanced AST walking with visitor mixing capabilities
- **Current Autofixer**: `assign_in_effect` - detects assignments to `$state` variables inside `$effect` blocks
- **Main Export**: `src/index.ts`
- **MCP Implementation**: `src/mcp/index.ts` using `tmcp` library with Valibot schema validation
- **Code Analysis**: Svelte component parsing with `svelte-eslint-parser` and TypeScript parser
- **Autofixers**: Visitor pattern implementations for code analysis and suggestions
- **Tools**: `svelte-autofixer` - analyzes Svelte code and provides suggestions/fixes
### Database Layer
### mcp-stdio Package (packages/mcp-stdio)
- **ORM**: Drizzle with SQLite backend
- **Schema** (`src/lib/server/db/schema.ts`): Vector table for embeddings support
- **Utils** (`src/lib/server/db/utils.ts`): Custom float32 array type for vectors
Standalone publishable MCP server with STDIO transport:
### SvelteKit Integration
- **CLI Binary**: `svelte-mcp` command
- **Entry Point**: `src/index.ts`
- **Transport**: Uses `@tmcp/transport-stdio` for command-line integration
- **Hooks** (`src/hooks.server.ts`): Integrates MCP HTTP transport with SvelteKit requests
- **Routes**: Basic web interface for the MCP server
### Database Layer (mcp-remote)
- **ORM**: Drizzle with SQLite backend (`test.db`)
- **Schema**: Located in `src/lib/server/db/schema.ts` with vector table for embeddings
- **Configuration**: `drizzle.config.ts` in mcp-remote app
## Key Dependencies
@@ -81,7 +121,7 @@ Located in `src/lib/server/analyze/`:
## Environment Configuration
Required environment variables:
For the mcp-remote app (`apps/mcp-remote/.env`):
- `DATABASE_URL`: SQLite database path (default: `file:test.db`)
- `VOYAGE_API_KEY`: API key for embeddings support (optional)
@@ -90,12 +130,12 @@ When connected to the svelte-llm MCP server, you have access to comprehensive Sv
## Available MCP Tools:
### 1. list-sections
### 1. list_sections
Use this FIRST to discover all available documentation sections. Returns a structured list with titles and paths.
When asked about Svelte or SvelteKit topics, ALWAYS use this tool at the start of the chat to find relevant sections.
### 2. get-documentation
### 2. get_documentation
Retrieves full documentation content for specific sections. Accepts single or multiple sections.
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.
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.

View File

@@ -1,12 +1,14 @@
import { defineConfig } from 'drizzle-kit';
if (!process.env.DATABASE_URL) throw new Error('DATABASE_URL is not set');
if (!process.env.DATABASE_TOKEN) throw new Error('DATABASE_TOKEN is not set');
export default defineConfig({
schema: './src/lib/server/db/schema.ts',
dialect: 'turso',
dbCredentials: { url: process.env.DATABASE_URL, authToken: process.env.DATABASE_TOKEN },
dbCredentials: {
url: process.env.DATABASE_URL,
authToken: process.env.DATABASE_TOKEN || '',
},
verbose: true,
strict: true,
});

View File

@@ -39,15 +39,16 @@
"devDependencies": {
"@eslint/compat": "^1.3.2",
"@eslint/js": "^9.36.0",
"@libsql/client": "^0.15.0",
"@modelcontextprotocol/inspector": "^0.17.0",
"@sveltejs/adapter-vercel": "^6.0.0",
"@libsql/client": "^0.14.0",
"@modelcontextprotocol/inspector": "^0.16.7",
"@sveltejs/adapter-node": "^5.3.2",
"@sveltejs/adapter-vercel": "^5.6.3",
"@sveltejs/kit": "^2.22.0",
"@sveltejs/vite-plugin-svelte": "^6.0.0",
"@types/node": "^24.3.1",
"@typescript-eslint/parser": "^8.44.0",
"drizzle-kit": "^0.31.0",
"drizzle-orm": "^0.44.0",
"drizzle-kit": "^0.30.2",
"drizzle-orm": "^0.40.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-svelte": "^3.12.3",
"globals": "^16.0.0",
@@ -59,12 +60,14 @@
"typescript": "^5.0.0",
"vite": "^7.0.4",
"vite-plugin-devtools-json": "^1.0.0",
"vitest": "^4.0.0"
"vitest": "^3.2.3"
},
"dependencies": {
"@sveltejs/mcp-schema": "workspace:^",
"@sveltejs/mcp-server": "workspace:^",
"@tmcp/transport-http": "^0.7.1",
"tmcp": "^1.15.3"
"@tmcp/transport-http": "^0.6.2",
"@types/tar-stream": "^3.1.4",
"minimatch": "^10.0.3",
"tar-stream": "^3.1.7"
}
}

View File

@@ -1,22 +1,7 @@
import { http_transport } from '$lib/mcp/index.js';
import { db } from '$lib/server/db/index.js';
import { redirect } from '@sveltejs/kit';
export async function handle({ event, resolve }) {
if (event.request.method === 'POST') {
console.log(await event.request.clone().text());
}
if (event.request.method === 'GET') {
const accept = event.request.headers.get('accept');
if (accept) {
const accepts = accept.split(',');
if (!accepts.includes('text/event-stream')) {
// the request it's a browser request, not an MCP client request
// it means someone probably opened it from the docs...we should redirect to the docs
redirect(302, 'https://svelte.dev/docs/mcp/overview');
}
}
}
const mcp_response = await http_transport.respond(event.request, {
db,
});
@@ -28,14 +13,11 @@ export async function handle({ event, resolve }) {
// 200 or the MCP client will complain)
if (mcp_response && event.request.method === 'GET') {
try {
return mcp_response;
} finally {
try {
await mcp_response.body?.cancel();
} catch {
// ignore
}
await mcp_response.body?.cancel();
} catch {
// ignore
}
return new Response('', { status: 200 });
}
return mcp_response ?? resolve(event);
}

View File

@@ -0,0 +1,138 @@
import { db } from '$lib/server/db';
import { cache } from '$lib/server/db/schema';
import { and, eq, sql } from 'drizzle-orm';
export interface CacheEntry {
id: number;
cache_key: string;
data: Buffer;
size_bytes: number;
expires_at: Date;
created_at: Date;
}
export class CacheDbService {
private defaultTTL: number;
constructor(defaultTTLMinutes: number = 60) {
this.defaultTTL = defaultTTLMinutes;
}
async get(key: string): Promise<Buffer | null> {
try {
const result = await db
.select({ data: cache.data })
.from(cache)
.where(and(eq(cache.cache_key, key), sql`${cache.expires_at} > ${new Date()}`))
.limit(1);
if (result.length === 0) {
return null;
}
return result[0].data;
} catch (error) {
console.error('Error getting cache entry:', error);
return null;
}
}
async set(key: string, data: Buffer, ttlMinutes?: number): Promise<void> {
const ttl = ttlMinutes || this.defaultTTL;
const expires_at = new Date(Date.now() + ttl * 60 * 1000);
const now = new Date();
try {
await db
.insert(cache)
.values({
cache_key: key,
data,
size_bytes: data.length,
expires_at,
created_at: now,
updated_at: now,
})
.onConflictDoUpdate({
target: cache.cache_key,
set: {
data,
size_bytes: data.length,
expires_at,
updated_at: now,
},
});
} catch (error) {
console.error('Error setting cache entry:', error);
throw error;
}
}
async delete(key: string): Promise<boolean> {
try {
const result = await db.delete(cache).where(eq(cache.cache_key, key));
return result.rowsAffected > 0;
} catch (error) {
console.error('Error deleting cache entry:', error);
return false;
}
}
async clear(): Promise<void> {
try {
await db.delete(cache);
} catch (error) {
console.error('Error clearing cache:', error);
throw error;
}
}
async deleteExpired(): Promise<number> {
try {
const result = await db.delete(cache).where(sql`${cache.expires_at} <= ${new Date()}`);
return result.rowsAffected;
} catch (error) {
console.error('Error deleting expired cache entries:', error);
return 0;
}
}
async getStatus(): Promise<{ count: number; keys: string[]; totalSizeBytes: number }> {
try {
const result = await db
.select({
cache_key: cache.cache_key,
size_bytes: cache.size_bytes,
})
.from(cache)
.where(sql`${cache.expires_at} > ${new Date()}`)
.orderBy(cache.created_at);
const keys = result.map((row) => row.cache_key);
const totalSizeBytes = result.reduce((sum, row) => sum + row.size_bytes, 0);
return {
count: result.length,
keys,
totalSizeBytes,
};
} catch (error) {
console.error('Error getting cache status:', error);
return { count: 0, keys: [], totalSizeBytes: 0 };
}
}
async has(key: string): Promise<boolean> {
try {
const result = await db
.select({ exists: sql`1` })
.from(cache)
.where(and(eq(cache.cache_key, key), sql`${cache.expires_at} > ${new Date()}`))
.limit(1);
return result.length > 0;
} catch (error) {
console.error('Error checking cache entry:', error);
return false;
}
}
}

View File

@@ -0,0 +1,346 @@
import type { PresetConfig } from '$lib/presets';
import { env } from '$env/dynamic/private';
import tarStream from 'tar-stream';
import { Readable } from 'stream';
import { createGunzip } from 'zlib';
import { minimatch } from 'minimatch';
import { getPresetContent } from './presetCache';
import { CacheDbService } from '$lib/cacheDb';
import { log, logAlways, logErrorAlways } from '$lib/log';
import { cleanTarballPath } from '$lib/utils/pathUtils';
let cacheService: CacheDbService | null = null;
function getCacheService(): CacheDbService {
if (!cacheService) {
cacheService = new CacheDbService();
}
return cacheService;
}
function sortFilesWithinGroup(files: string[]): string[] {
return files.sort((a, b) => {
const aPath = a.split('\n')[0].replace('## ', '');
const bPath = b.split('\n')[0].replace('## ', '');
// Check if one path is a parent of the other
if (bPath.startsWith(aPath.replace('/index.md', '/'))) return -1;
if (aPath.startsWith(bPath.replace('/index.md', '/'))) return 1;
return aPath.localeCompare(bPath);
});
}
export async function fetchRepositoryTarball(owner: string, repo: string): Promise<Buffer> {
const cacheKey = `${owner}/${repo}`;
const cache = getCacheService();
const cachedBuffer = await cache.get(cacheKey);
if (cachedBuffer) {
logAlways(`Using cached tarball for ${cacheKey} from database`);
return cachedBuffer;
}
const url = `https://api.github.com/repos/${owner}/${repo}/tarball`;
logAlways(`Fetching tarball from: ${url}`);
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${env.GITHUB_TOKEN}`,
Accept: 'application/vnd.github.v3.raw',
},
});
if (!response.ok) {
throw new Error(`Failed to fetch tarball: ${response.statusText}`);
}
if (!response.body) {
throw new Error('Response body is null');
}
const chunks: Uint8Array[] = [];
const reader = response.body.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
}
const buffer = Buffer.concat(chunks);
// Cache the buffer in database with 60 minutes TTL
await cache.set(cacheKey, buffer, 60);
return buffer;
}
export async function processMarkdownFromTarball(
tarballBuffer: Buffer,
presetConfig: PresetConfig,
includePathInfo: boolean,
): Promise<string[] | { path: string; content: string }[]> {
const { glob, ignore = [], minimize = undefined } = presetConfig;
// Create a Map to store files for each glob pattern while maintaining order
const globResults = new Map<string, unknown[]>();
const filePathsByPattern = new Map<string, string[]>();
glob.forEach((pattern) => {
globResults.set(pattern, []);
filePathsByPattern.set(pattern, []);
});
const extractStream = tarStream.extract();
let processedFiles = 0;
let matchedFiles = 0;
extractStream.on('entry', (header, stream, next) => {
processedFiles++;
let matched = false;
for (const pattern of glob) {
if (shouldIncludeFile(header.name, pattern, ignore)) {
matched = true;
matchedFiles++;
if (header.type === 'file') {
let content = '';
stream.on('data', (chunk) => (content += chunk.toString()));
stream.on('end', () => {
// Use the unified path utility to clean tarball paths
const cleanPath = cleanTarballPath(header.name);
const processedContent = minimizeContent(content, minimize);
if (includePathInfo) {
const files = globResults.get(pattern) || [];
files.push({
path: cleanPath,
content: processedContent,
});
globResults.set(pattern, files);
} else {
const contentWithHeader = `## ${cleanPath}\n\n${processedContent}`;
const files = globResults.get(pattern) || [];
files.push(contentWithHeader);
globResults.set(pattern, files);
}
const paths = filePathsByPattern.get(pattern) || [];
paths.push(cleanPath);
filePathsByPattern.set(pattern, paths);
next();
});
return;
}
}
}
if (!matched) {
stream.resume();
next();
}
});
const tarballStream = Readable.from(tarballBuffer);
const gunzipStream = createGunzip();
tarballStream.pipe(gunzipStream).pipe(extractStream);
await new Promise<void>((resolve) => extractStream.on('finish', resolve));
logAlways(`Total files processed: ${processedFiles}`);
logAlways(`Files matching glob: ${matchedFiles}`);
log('\nFinal file order:');
glob.forEach((pattern, index) => {
const paths = filePathsByPattern.get(pattern) || [];
const sortedPaths = includePathInfo
? paths
: sortFilesWithinGroup(paths.map((p) => `## ${p}`)).map((p) => p.replace('## ', ''));
if (sortedPaths.length > 0) {
log(`\nGlob pattern ${index + 1}: ${pattern}`);
sortedPaths.forEach((path, i) => {
log(` ${i + 1}. ${path}`);
});
}
});
// Combine results in the order of glob patterns
const orderedResults: unknown[] = [];
for (const pattern of glob) {
const filesForPattern = globResults.get(pattern) || [];
if (includePathInfo) {
orderedResults.push(...filesForPattern);
} else {
orderedResults.push(...sortFilesWithinGroup(filesForPattern as string[]));
}
}
return orderedResults as string[] | { path: string; content: string }[];
}
function shouldIncludeFile(filename: string, glob: string, ignore: string[] = []): boolean {
const shouldIgnore = ignore.some((pattern) => minimatch(filename, pattern));
if (shouldIgnore) {
logAlways(`❌ Ignored by pattern: ${filename}`);
return false;
}
return minimatch(filename, glob);
}
export async function clearRepositoryCache(): Promise<void> {
const cache = getCacheService();
await cache.clear();
logAlways('Repository cache cleared');
}
export async function getRepositoryCacheStatus(): Promise<{
size: number;
repositories: string[];
totalSizeBytes: number;
}> {
const cache = getCacheService();
const status = await cache.getStatus();
return {
size: status.count,
repositories: status.keys,
totalSizeBytes: status.totalSizeBytes,
};
}
export interface MinimizeOptions {
normalizeWhitespace?: boolean;
removeLegacy?: boolean;
removePlaygroundLinks?: boolean;
removePrettierIgnore?: boolean;
removeNoteBlocks?: boolean;
removeDetailsBlocks?: boolean;
removeHtmlComments?: boolean;
removeDiffMarkers?: boolean;
}
const defaultOptions: MinimizeOptions = {
normalizeWhitespace: false,
removeLegacy: false,
removePlaygroundLinks: false,
removePrettierIgnore: true,
removeNoteBlocks: true,
removeDetailsBlocks: true,
removeHtmlComments: false,
removeDiffMarkers: true,
};
function removeQuoteBlocks(content: string, blockType: string): string {
return content
.split('\n')
.reduce((acc: string[], line: string, index: number, lines: string[]) => {
// If we find a block (with or without additional text), skip it and all subsequent blockquote lines
if (line.trim().startsWith(`> [!${blockType}]`)) {
// Skip all subsequent lines that are part of the blockquote
let i = index;
while (i < lines.length && (lines[i].startsWith('>') || lines[i].trim() === '')) {
i++;
}
// Update the index to skip all these lines
index = i - 1;
return acc;
}
acc.push(line);
return acc;
}, [])
.join('\n');
}
function removeDiffMarkersFromContent(content: string): string {
let inCodeBlock = false;
const lines = content.split('\n');
const processedLines = lines.map((line) => {
// Track if we're entering or leaving a code block
// eslint-disable-next-line no-useless-escape
if (line.trim().startsWith('\`\`\`')) {
inCodeBlock = !inCodeBlock;
return line;
}
if (inCodeBlock) {
// Handle lines that end with --- or +++ with possible whitespace after
// eslint-disable-next-line no-useless-escape
line = line.replace(/(\+{3}|\-{3})[\s]*$/g, '');
// Handle triple markers at start while preserving indentation
// This captures the whitespace before the marker and adds it back
// eslint-disable-next-line no-useless-escape
line = line.replace(/^(\s*)(\+{3}|\-{3})\s*/g, '$1');
// Handle single + or - markers at start while preserving indentation
// eslint-disable-next-line no-useless-escape
line = line.replace(/^(\s*)[\+\-](\s)/g, '$1');
// Handle multi-line diff blocks where --- or +++ might be in the middle of line
// eslint-disable-next-line no-useless-escape
line = line.replace(/[\s]*(\+{3}|\-{3})[\s]*/g, '');
}
return line;
});
return processedLines.join('\n');
}
export function minimizeContent(content: string, options?: Partial<MinimizeOptions>): string {
const settings: MinimizeOptions = options ? { ...defaultOptions, ...options } : defaultOptions;
let minimized = content;
minimized = minimized.replace(/NOTE: do not edit this file, it is generated in.*$/gm, '');
if (settings.removeDiffMarkers) {
minimized = removeDiffMarkersFromContent(minimized);
}
if (settings.removeLegacy) {
minimized = removeQuoteBlocks(minimized, 'LEGACY');
}
if (settings.removeNoteBlocks) {
minimized = removeQuoteBlocks(minimized, 'NOTE');
}
if (settings.removeDetailsBlocks) {
minimized = removeQuoteBlocks(minimized, 'DETAILS');
}
if (settings.removePlaygroundLinks) {
// Replace playground URLs with /[link] but keep the original link text
minimized = minimized.replace(/\[([^\]]+)\]\(\/playground[^)]+\)/g, '[$1](/REMOVED)');
}
if (settings.removePrettierIgnore) {
minimized = minimized
.split('\n')
.filter((line) => line.trim() !== '<!-- prettier-ignore -->')
.join('\n');
}
if (settings.removeHtmlComments) {
// Replace all HTML comments (including multi-line) with empty string
minimized = minimized.replace(/<!--[\s\S]*?-->/g, '');
}
if (settings.normalizeWhitespace) {
minimized = minimized.replace(/\s+/g, ' ');
}
minimized = minimized.trim();
return minimized;
}

View File

@@ -0,0 +1,32 @@
import { dev } from '$app/environment';
// eslint-disable-next-line @typescript-eslint/naming-convention, func-style
export const log = (...props: unknown[]) => {
if (dev) {
console.log(...props);
}
};
// eslint-disable-next-line @typescript-eslint/naming-convention, func-style
export const logWarning = (...props: unknown[]) => {
if (dev) {
console.warn(...props);
}
};
// eslint-disable-next-line @typescript-eslint/naming-convention, func-style
export const logError = (...props: unknown[]) => {
if (dev) {
console.error(...props);
}
};
// eslint-disable-next-line @typescript-eslint/naming-convention, func-style
export const logAlways = (...props: unknown[]) => {
console.log(...props);
};
// eslint-disable-next-line @typescript-eslint/naming-convention, func-style
export const logWarningAlways = (...props: unknown[]) => {
console.warn(...props);
};
// eslint-disable-next-line @typescript-eslint/naming-convention, func-style
export const logErrorAlways = (...props: unknown[]) => {
console.error(...props);
};

View File

@@ -3,5 +3,4 @@ import { HttpTransport } from '@tmcp/transport-http';
export const http_transport = new HttpTransport(server, {
cors: true,
path: '/mcp',
});

View File

@@ -0,0 +1,213 @@
import { ContentSyncService } from '$lib/server/contentSync';
import { presets } from '$lib/presets';
import { log, logAlways, logErrorAlways } from '$lib/log';
import { cleanDocumentationPath } from '$lib/utils/pathUtils';
import { CacheDbService } from '$lib/cacheDb';
// Maximum age of cached content in milliseconds (24 hours)
export const MAX_CACHE_AGE_MS = 24 * 60 * 60 * 1000;
let cacheService: CacheDbService | null = null;
function getCacheService(): CacheDbService {
if (!cacheService) {
cacheService = new CacheDbService();
}
return cacheService;
}
export async function getPresetContent(presetKey: string): Promise<string | null> {
try {
const preset = presets[presetKey];
if (!preset) {
log(`Preset not found: ${presetKey}`);
return null;
}
// Check cache first
const cache = getCacheService();
const cacheKey = `preset:${presetKey}`;
try {
const cachedData = await cache.get(cacheKey);
if (cachedData) {
const cachedContent = cachedData.toString('utf8');
logAlways(`Using cached content for preset ${presetKey}`);
return cachedContent;
}
} catch (cacheError) {
logErrorAlways(`Error reading cache for preset ${presetKey}:`, cacheError);
// Continue with normal flow if cache read fails
}
// Try to get files from the content table first
let filesWithPaths = await ContentSyncService.getPresetContentFromDb(presetKey);
// If no content in database, fetch from GitHub and sync
if (!filesWithPaths || filesWithPaths.length === 0) {
logAlways(`No content in database for preset ${presetKey}, fetching from GitHub...`);
// Sync the repository first
await ContentSyncService.syncRepository();
// Try again from database
filesWithPaths = await ContentSyncService.getPresetContentFromDb(presetKey);
if (!filesWithPaths || filesWithPaths.length === 0) {
log(`Still no content found for preset: ${presetKey} after sync`);
return null;
}
}
// Format files with headers and preserve the order from database
// The files are already correctly ordered by glob pattern precedence
// Use the unified path utility to clean paths
const files = filesWithPaths.map((f) => {
const cleanPath = cleanDocumentationPath(f.path);
return `## ${cleanPath}\n\n${f.content}`;
});
// DO NOT sort - files are already in correct glob pattern order from ContentSyncService
const content = files.join('\n\n');
logAlways(`Generated content for ${presetKey} on-demand (${filesWithPaths.length} files)`);
// Cache the generated content for 1 hour (60 minutes)
try {
const contentBuffer = Buffer.from(content, 'utf8');
await cache.set(cacheKey, contentBuffer, 60); // 60 minutes TTL
logAlways(`Cached content for preset ${presetKey} (expires in 1 hour)`);
} catch (cacheError) {
logErrorAlways(`Error caching content for preset ${presetKey}:`, cacheError);
// Don't fail the request if caching fails
}
return content;
} catch (error) {
logErrorAlways(`Error generating preset content for ${presetKey}:`, error);
return null;
}
}
export async function getPresetSizeKb(presetKey: string): Promise<number | null> {
try {
const content = await getPresetContent(presetKey);
if (!content) {
return null;
}
const sizeKb = Math.floor(new TextEncoder().encode(content).length / 1024);
return sizeKb;
} catch (error) {
logErrorAlways(`Error calculating preset size for ${presetKey}:`, error);
return null;
}
}
export async function isPresetStale(presetKey: string): Promise<boolean> {
try {
// Check if the repository content is stale
return await ContentSyncService.isRepositoryContentStale();
} catch (error) {
logErrorAlways(`Error checking preset staleness for ${presetKey}:`, error);
return true; // On error, assume stale
}
}
export async function presetExists(presetKey: string): Promise<boolean> {
try {
const preset = presets[presetKey];
if (!preset) {
return false;
}
// A preset "exists" if it's defined in presets.ts
// The content will be generated on-demand
return true;
} catch (error) {
logErrorAlways(`Error checking preset existence for ${presetKey}:`, error);
return false;
}
}
export async function getPresetMetadata(presetKey: string): Promise<{
size_kb: number;
document_count: number;
updated_at: Date;
is_stale: boolean;
} | null> {
try {
const preset = presets[presetKey];
if (!preset) {
return null;
}
// Try to get files from content table or GitHub
const content = await getPresetContent(presetKey);
if (!content) {
return null;
}
// Get the files again to count them (this will use cached data)
const filesWithPaths = await ContentSyncService.getPresetContentFromDb(presetKey);
const documentCount = filesWithPaths?.length || 0;
const sizeKb = Math.floor(new TextEncoder().encode(content).length / 1024);
const isStale = await isPresetStale(presetKey);
return {
size_kb: sizeKb,
document_count: documentCount,
updated_at: new Date(), // Since it's generated on-demand, it's always "now"
is_stale: isStale,
};
} catch (error) {
logErrorAlways(`Error getting preset metadata for ${presetKey}:`, error);
return null;
}
}
/**
* Clear the cache for a specific preset
*/
export async function clearPresetCache(presetKey: string): Promise<boolean> {
try {
const cache = getCacheService();
const cacheKey = `preset:${presetKey}`;
const success = await cache.delete(cacheKey);
if (success) {
logAlways(`Cleared cache for preset ${presetKey}`);
}
return success;
} catch (error) {
logErrorAlways(`Error clearing cache for preset ${presetKey}:`, error);
return false;
}
}
/**
* Clear cache for all presets
*/
export async function clearAllPresetCaches(): Promise<number> {
try {
const cache = getCacheService();
const allPresetKeys = Object.keys(presets);
let clearedCount = 0;
for (const presetKey of allPresetKeys) {
const cacheKey = `preset:${presetKey}`;
const success = await cache.delete(cacheKey);
if (success) {
clearedCount++;
}
}
logAlways(`Cleared cache for ${clearedCount} presets`);
return clearedCount;
} catch (error) {
logErrorAlways(`Error clearing all preset caches:`, error);
return 0;
}
}

View File

@@ -0,0 +1,267 @@
import type { MinimizeOptions } from './fetchMarkdown';
import { SVELTE_5_PROMPT } from '$lib/utils/prompts';
export type PresetConfig = {
title: string;
description?: string;
glob: string[];
ignore?: string[];
prompt?: string;
minimize?: MinimizeOptions;
distilled?: boolean;
distilledFilenameBase?: string;
};
// eslint-disable-next-line @typescript-eslint/naming-convention
export const combinedPresets: Record<string, PresetConfig> = {
'svelte-complete-distilled': {
title: '🔮 Svelte + SvelteKit (Recommended - LLM Distilled)',
description: 'AI-condensed version of the docs focused on code examples and key concepts',
glob: [
// Svelte
'**/apps/svelte.dev/content/docs/svelte/**/*.md',
// SvelteKit
'**/apps/svelte.dev/content/docs/kit/**/*.md',
],
minimize: {
normalizeWhitespace: false,
removeLegacy: true,
removePlaygroundLinks: true,
removePrettierIgnore: true,
removeNoteBlocks: false,
removeDetailsBlocks: false,
removeHtmlComments: true,
removeDiffMarkers: true,
},
ignore: [
// Svelte ignores (same as medium preset)
'**/apps/svelte.dev/content/docs/svelte/07-misc/04-custom-elements.md',
'**/apps/svelte.dev/content/docs/svelte/07-misc/06-v4-migration-guide.md',
'**/apps/svelte.dev/content/docs/svelte/07-misc/07-v5-migration-guide.md',
'**/apps/svelte.dev/content/docs/svelte/07-misc/99-faq.md',
'**/apps/svelte.dev/content/docs/svelte/07-misc/xx-reactivity-indepth.md',
'**/apps/svelte.dev/content/docs/svelte/98-reference/21-svelte-legacy.md',
'**/apps/svelte.dev/content/docs/svelte/99-legacy/**/*.md',
'**/apps/svelte.dev/content/docs/svelte/98-reference/**/*.md',
'**/xx-*.md',
// SvelteKit ignores (same as medium preset)
'**/apps/svelte.dev/content/docs/kit/25-build-and-deploy/*adapter-*.md',
'**/apps/svelte.dev/content/docs/kit/25-build-and-deploy/99-writing-adapters.md',
'**/apps/svelte.dev/content/docs/kit/30-advanced/70-packaging.md',
'**/apps/svelte.dev/content/docs/kit/40-best-practices/05-performance.md',
'**/apps/svelte.dev/content/docs/kit/40-best-practices/10-accessibility.md',
'**/apps/svelte.dev/content/docs/kit/60-appendix/**/*.md',
'**/apps/svelte.dev/content/docs/kit/98-reference/**/*.md',
'**/xx-*.md',
],
prompt: SVELTE_5_PROMPT,
distilled: true,
distilledFilenameBase: 'svelte-complete-distilled',
},
'svelte-complete-medium': {
title: '⭐️ Svelte + SvelteKit (Medium preset)',
description:
'Complete Svelte + SvelteKit docs excluding certain advanced sections, legacy, notes and migration docs',
glob: [
// Svelte
'**/apps/svelte.dev/content/docs/svelte/**/*.md',
// SvelteKit
'**/apps/svelte.dev/content/docs/kit/**/*.md',
],
ignore: [
// Svelte ignores
'**/apps/svelte.dev/content/docs/svelte/07-misc/04-custom-elements.md',
'**/apps/svelte.dev/content/docs/svelte/07-misc/06-v4-migration-guide.md',
'**/apps/svelte.dev/content/docs/svelte/07-misc/07-v5-migration-guide.md',
'**/apps/svelte.dev/content/docs/svelte/07-misc/99-faq.md',
'**/apps/svelte.dev/content/docs/svelte/07-misc/xx-reactivity-indepth.md',
'**/apps/svelte.dev/content/docs/svelte/98-reference/21-svelte-legacy.md',
'**/apps/svelte.dev/content/docs/svelte/99-legacy/**/*.md',
'**/apps/svelte.dev/content/docs/svelte/98-reference/30-runtime-errors.md',
'**/apps/svelte.dev/content/docs/svelte/98-reference/30-runtime-warnings.md',
'**/apps/svelte.dev/content/docs/svelte/98-reference/30-compiler-errors.md',
'**/apps/svelte.dev/content/docs/svelte/98-reference/30-compiler-warnings.md',
'**/xx-*.md',
// SvelteKit ignores
'**/apps/svelte.dev/content/docs/kit/25-build-and-deploy/*adapter-*.md',
'**/apps/svelte.dev/content/docs/kit/25-build-and-deploy/99-writing-adapters.md',
'**/apps/svelte.dev/content/docs/kit/30-advanced/70-packaging.md',
'**/apps/svelte.dev/content/docs/kit/40-best-practices/05-performance.md',
'**/apps/svelte.dev/content/docs/kit/40-best-practices/10-accessibility.md', // May the a11y gods have mercy on our souls
'**/apps/svelte.dev/content/docs/kit/60-appendix/**/*.md',
'**/xx-*.md',
],
prompt: SVELTE_5_PROMPT,
minimize: {
removeLegacy: true,
removePlaygroundLinks: true,
removeNoteBlocks: true,
removeDetailsBlocks: true,
removeHtmlComments: true,
normalizeWhitespace: true,
},
},
'svelte-complete': {
title: 'Svelte + SvelteKit (Large preset)',
description: 'Complete Svelte + SvelteKit docs excluding legacy, notes and migration docs',
glob: [
'**/apps/svelte.dev/content/docs/svelte/**/*.md',
'**/apps/svelte.dev/content/docs/kit/**/*.md',
],
ignore: [],
prompt: SVELTE_5_PROMPT,
minimize: {
removeLegacy: true,
removePlaygroundLinks: true,
removeNoteBlocks: true,
removeDetailsBlocks: true,
removeHtmlComments: true,
normalizeWhitespace: true,
},
},
'svelte-complete-tiny': {
title: 'Svelte + SvelteKit (Tiny preset)',
description: 'Tutorial content only',
glob: [
'**/apps/svelte.dev/content/tutorial/**/*.md',
'**/apps/svelte.dev/content/docs/svelte/02-runes/**/*.md',
],
ignore: [],
prompt: SVELTE_5_PROMPT,
minimize: {
removeLegacy: true,
removePlaygroundLinks: true,
removeNoteBlocks: true,
removeDetailsBlocks: true,
removeHtmlComments: true,
normalizeWhitespace: true,
},
},
'svelte-migration': {
title: 'Svelte + SvelteKit migration guide',
description: 'Only Svelte + SvelteKit docs for migrating ',
glob: [
// Svelte
'**/apps/svelte.dev/content/docs/svelte/07-misc/07-v5-migration-guide.md',
// SvelteKit
'**/apps/svelte.dev/content/docs/kit/60-appendix/30-migrating-to-sveltekit-2.md',
],
ignore: [],
prompt: SVELTE_5_PROMPT,
minimize: {
removeLegacy: true,
removePlaygroundLinks: true,
removeNoteBlocks: true,
removeDetailsBlocks: true,
removeHtmlComments: true,
normalizeWhitespace: true,
},
},
};
// eslint-disable-next-line @typescript-eslint/naming-convention
export const sveltePresets: Record<string, PresetConfig> = {
svelte: {
title: 'Svelte (Full)',
description: 'Complete documentation including legacy and reference',
glob: ['**/apps/svelte.dev/content/docs/svelte/**/*.md'],
ignore: [],
prompt: SVELTE_5_PROMPT,
minimize: {},
},
'svelte-medium': {
title: 'Svelte (Medium)',
description: 'Complete documentation including legacy and reference',
glob: ['**/apps/svelte.dev/content/docs/svelte/**/*.md'],
ignore: [
// Svelte ignores
'**/apps/svelte.dev/content/docs/svelte/07-misc/04-custom-elements.md',
'**/apps/svelte.dev/content/docs/svelte/07-misc/06-v4-migration-guide.md',
'**/apps/svelte.dev/content/docs/svelte/07-misc/07-v5-migration-guide.md',
'**/apps/svelte.dev/content/docs/svelte/07-misc/99-faq.md',
'**/apps/svelte.dev/content/docs/svelte/07-misc/xx-reactivity-indepth.md',
'**/apps/svelte.dev/content/docs/svelte/98-reference/21-svelte-legacy.md',
'**/apps/svelte.dev/content/docs/svelte/99-legacy/**/*.md',
'**/apps/svelte.dev/content/docs/svelte/98-reference/30-runtime-errors.md',
'**/apps/svelte.dev/content/docs/svelte/98-reference/30-runtime-warnings.md',
'**/apps/svelte.dev/content/docs/svelte/98-reference/30-compiler-errors.md',
'**/apps/svelte.dev/content/docs/svelte/98-reference/30-compiler-warnings.md',
],
prompt: SVELTE_5_PROMPT,
minimize: {
removeLegacy: true,
removePlaygroundLinks: true,
removeNoteBlocks: true,
removeDetailsBlocks: true,
removeHtmlComments: true,
normalizeWhitespace: true,
},
},
};
// eslint-disable-next-line @typescript-eslint/naming-convention
export const svelteKitPresets: Record<string, PresetConfig> = {
sveltekit: {
title: 'SvelteKit (Full)',
description: 'Complete documentation including legacy and reference',
prompt: SVELTE_5_PROMPT,
glob: ['**/apps/svelte.dev/content/docs/kit/**/*.md'],
minimize: {},
},
'sveltekit-medium': {
title: 'SvelteKit (Medium)',
description: 'Complete documentation including legacy and reference',
prompt: SVELTE_5_PROMPT,
glob: ['**/apps/svelte.dev/content/docs/kit/**/*.md'],
minimize: {
removeLegacy: true,
removePlaygroundLinks: true,
removeNoteBlocks: true,
removeDetailsBlocks: true,
removeHtmlComments: true,
normalizeWhitespace: true,
},
ignore: [
// SvelteKit ignores
'**/apps/svelte.dev/content/docs/kit/25-build-and-deploy/*adapter-*.md',
'**/apps/svelte.dev/content/docs/kit/25-build-and-deploy/99-writing-adapters.md',
'**/apps/svelte.dev/content/docs/kit/30-advanced/70-packaging.md',
'**/apps/svelte.dev/content/docs/kit/40-best-practices/05-performance.md',
'**/apps/svelte.dev/content/docs/kit/40-best-practices/10-accessibility.md', // May the a11y gods have mercy on our souls
'**/apps/svelte.dev/content/docs/kit/60-appendix/**/*.md',
'**/xx-*.md',
],
},
};
// eslint-disable-next-line @typescript-eslint/naming-convention
export const otherPresets: Record<string, PresetConfig> = {
'svelte-cli': {
title: 'Svelte CLI - npx sv',
glob: ['**/apps/svelte.dev/content/docs/cli/**/*.md'],
ignore: [],
minimize: {},
},
};
export const presets = {
...combinedPresets,
...sveltePresets,
...svelteKitPresets,
...otherPresets,
};
// eslint-disable-next-line @typescript-eslint/naming-convention
export function transformAndSortPresets(presetsObject: Record<string, PresetConfig>) {
return Object.entries(presetsObject)
.map(([key, value]) => ({
key: key.toLowerCase(),
...value,
}))
.sort();
}
export const DEFAULT_REPOSITORY = {
owner: 'sveltejs',
repo: 'svelte.dev',
} as const;

View File

@@ -0,0 +1,383 @@
import { query } from '$lib/server/db';
import type {
DbContent,
DbContentDistilled,
CreateContentInput,
ContentFilter,
ContentStats,
} from '$lib/types/db';
import { logAlways, logErrorAlways } from '$lib/log';
// Type mapping for table names to their corresponding types
type TableTypeMap = {
content: DbContent;
content_distilled: DbContentDistilled;
};
// Union type for valid table names
type TableName = keyof TableTypeMap;
export class ContentDbService {
static extractFilename(path: string): string {
return path.split('/').pop() || path;
}
static async upsertContent(input: CreateContentInput): Promise<DbContent> {
try {
const result = await query(
`INSERT INTO content (
path, filename, content, size_bytes, metadata
) VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (path) DO UPDATE SET
content = EXCLUDED.content,
size_bytes = EXCLUDED.size_bytes,
metadata = EXCLUDED.metadata,
updated_at = CURRENT_TIMESTAMP
RETURNING *`,
[
input.path,
input.filename,
input.content,
input.size_bytes,
input.metadata ? JSON.stringify(input.metadata) : '{}',
],
);
logAlways(`Upserted content for ${input.path}`);
return result.rows[0] as DbContent;
} catch (error) {
logErrorAlways(`Failed to upsert content for ${input.path}:`, error);
throw new Error(
`Failed to upsert content: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
static async getContentByPath(path: string): Promise<DbContent | null> {
try {
const result = await query('SELECT * FROM content WHERE path = $1', [path]);
return result.rows.length > 0 ? (result.rows[0] as DbContent) : null;
} catch (error) {
logErrorAlways(`Failed to get content ${path}:`, error);
throw new Error(
`Failed to get content: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
static async getAllContent(): Promise<DbContent[]> {
try {
const result = await query('SELECT * FROM content ORDER BY path');
return result.rows as DbContent[];
} catch (error) {
logErrorAlways('Failed to get all content:', error);
throw new Error(
`Failed to get content: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
/**
* Generic search method that works with both content and content_distilled tables
*/
static async searchContent<T extends TableName>(
searchQuery: string,
tableName: T,
pathPattern: string = 'apps/svelte.dev/content/docs/%',
): Promise<TableTypeMap[T] | null> {
try {
const lowerQuery = searchQuery.toLowerCase();
// Build table-specific WHERE clauses
let baseWhereClause = '';
let params: (string | number)[] = [];
let paramIndex = 1;
if (tableName === 'content') {
// For content table, include path filter
baseWhereClause = `WHERE path LIKE $${paramIndex}`;
params = [pathPattern];
paramIndex = 2;
} else {
// For content_distilled table, no additional filters needed
baseWhereClause = '';
paramIndex = 1;
}
// First, try exact title match using JSON operators
const exactTitleQueryStr = `
SELECT * FROM ${tableName}
${baseWhereClause}${baseWhereClause ? ' AND' : 'WHERE'} LOWER(metadata->>'title') = $${paramIndex}
LIMIT 1
`;
const exactTitleParams = [...params, lowerQuery];
const exactTitleResult = await query(exactTitleQueryStr, exactTitleParams);
if (exactTitleResult.rows.length > 0) {
return exactTitleResult.rows[0] as TableTypeMap[T];
}
// Then try partial title match
const partialTitleQueryStr = `
SELECT * FROM ${tableName}
${baseWhereClause}${baseWhereClause ? ' AND' : 'WHERE'} LOWER(metadata->>'title') LIKE $${paramIndex}
LIMIT 1
`;
const partialTitleParams = [...params, `%${lowerQuery}%`];
const partialTitleResult = await query(partialTitleQueryStr, partialTitleParams);
if (partialTitleResult.rows.length > 0) {
return partialTitleResult.rows[0] as TableTypeMap[T];
}
// Finally try path match for backward compatibility
const pathMatchQueryStr = `
SELECT * FROM ${tableName}
${baseWhereClause}${baseWhereClause ? ' AND' : 'WHERE'} LOWER(path) LIKE $${paramIndex}
LIMIT 1
`;
const pathMatchParams = [...params, `%${lowerQuery}%`];
const pathMatchResult = await query(pathMatchQueryStr, pathMatchParams);
return pathMatchResult.rows.length > 0 ? (pathMatchResult.rows[0] as TableTypeMap[T]) : null;
} catch (error) {
logErrorAlways(`Failed to search ${tableName} for "${searchQuery}":`, error);
throw new Error(
`Failed to search ${tableName}: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
static async searchAllContent(
searchQuery: string,
pathPattern: string = 'apps/svelte.dev/content/docs/%',
limit: number = 50,
): Promise<DbContent[]> {
try {
const lowerQuery = searchQuery.toLowerCase();
// Combine all search types into one query with UNION
const combinedQueryStr = `
-- Exact title matches first
(SELECT * FROM content
WHERE path LIKE $1
AND LOWER(metadata->>'title') = $2
ORDER BY path
LIMIT $3)
UNION
-- Then partial title matches
(SELECT * FROM content
WHERE path LIKE $1
AND LOWER(metadata->>'title') LIKE $4
AND LOWER(metadata->>'title') != $2
ORDER BY path
LIMIT $3)
UNION
-- Finally path matches
(SELECT * FROM content
WHERE path LIKE $1
AND LOWER(path) LIKE $4
AND (metadata->>'title' IS NULL OR LOWER(metadata->>'title') NOT LIKE $4)
ORDER BY path
LIMIT $3)
ORDER BY path
LIMIT $3
`;
const params = [pathPattern, lowerQuery, limit, `%${lowerQuery}%`];
const result = await query(combinedQueryStr, params);
return result.rows as DbContent[];
} catch (error) {
logErrorAlways(`Failed to search all content for "${searchQuery}":`, error);
throw new Error(
`Failed to search content: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
static async getDocumentationSections(
pathPattern: string = 'apps/svelte.dev/content/docs/%',
minContentLength: number = 100,
): Promise<Array<{ path: string; metadata: Record<string, unknown>; content: string }>> {
try {
const sectionsQueryStr = `
SELECT path, metadata, content
FROM content
WHERE path LIKE $1
AND LENGTH(content) >= $2
ORDER BY path
`;
const params = [pathPattern, minContentLength];
const result = await query(sectionsQueryStr, params);
return result.rows.map((row) => ({
path: row.path,
metadata: row.metadata,
content: row.content,
}));
} catch (error) {
logErrorAlways('Failed to get documentation sections:', error);
throw new Error(
`Failed to get sections: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
static async getFilteredContent(
pathPattern: string = 'apps/svelte.dev/content/docs/%',
minContentLength: number = 200,
): Promise<DbContent[]> {
try {
const filterQueryStr = `
SELECT *
FROM content
WHERE path LIKE $1
AND LENGTH(content) >= $2
ORDER BY path
`;
const params = [pathPattern, minContentLength];
const result = await query(filterQueryStr, params);
return result.rows as DbContent[];
} catch (error) {
logErrorAlways('Failed to get filtered content:', error);
throw new Error(
`Failed to get filtered content: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
static async getContentStats(): Promise<ContentStats> {
try {
const totalResult = await query(
`SELECT
COUNT(*) as total_files,
COALESCE(SUM(size_bytes), 0) as total_size_bytes,
MAX(updated_at) as last_updated
FROM content`,
);
return {
total_files: parseInt(totalResult.rows[0].total_files),
total_size_bytes: parseInt(totalResult.rows[0].total_size_bytes),
last_updated: totalResult.rows[0].last_updated,
};
} catch (error) {
logErrorAlways('Failed to get content stats:', error);
throw new Error(
`Failed to get stats: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
static async deleteContent(path: string): Promise<boolean> {
try {
const result = await query('DELETE FROM content WHERE path = $1', [path]);
return (result.rowCount ?? 0) > 0;
} catch (error) {
logErrorAlways(`Failed to delete content ${path}:`, error);
throw new Error(
`Failed to delete content: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
static async deleteAllContent(): Promise<number> {
try {
const result = await query('DELETE FROM content');
return result.rowCount ?? 0;
} catch (error) {
logErrorAlways('Failed to delete all content:', error);
throw new Error(
`Failed to delete content: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
static async hasContentChanged(path: string, newContent: string): Promise<boolean> {
try {
const existing = await ContentDbService.getContentByPath(path);
if (!existing) return true;
return existing.content !== newContent;
} catch (error) {
logErrorAlways(`Failed to check content change for ${path}:`, error);
return true; // Assume changed on error
}
}
static async batchUpsertContent(contents: CreateContentInput[]): Promise<DbContent[]> {
try {
const results: DbContent[] = [];
// Process in chunks to avoid overwhelming the database
const chunkSize = 200;
for (let i = 0; i < contents.length; i += chunkSize) {
const chunk = contents.slice(i, i + chunkSize);
const chunkResults = await Promise.all(
chunk.map((content) => ContentDbService.upsertContent(content)),
);
results.push(...chunkResults);
}
logAlways(`Batch upserted ${results.length} content items`);
return results;
} catch (error) {
logErrorAlways('Failed to batch upsert content:', error);
throw new Error(
`Failed to batch upsert: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
static extractFrontmatter(content: string): Record<string, unknown> {
const metadata: Record<string, unknown> = {};
if (!content.startsWith('---\n')) {
return metadata;
}
const endIndex = content.indexOf('\n---\n', 4);
if (endIndex === -1) {
return metadata;
}
const frontmatter = content.substring(4, endIndex);
const lines = frontmatter.split('\n');
for (const line of lines) {
const colonIndex = line.indexOf(':');
if (colonIndex > 0) {
const key = line.substring(0, colonIndex).trim();
const value = line.substring(colonIndex + 1).trim();
// Remove quotes if present
const cleanValue = value.replace(/^["'](.*)["']$/, '$1');
// Try to parse as JSON for nested structures
try {
metadata[key] = JSON.parse(cleanValue);
} catch {
metadata[key] = cleanValue;
}
}
}
return metadata;
}
}

View File

@@ -0,0 +1,271 @@
import {
fetchRepositoryTarball,
processMarkdownFromTarball,
minimizeContent,
} from '$lib/fetchMarkdown';
import { ContentDbService } from '$lib/server/contentDb';
import type { CreateContentInput } from '$lib/types/db';
import { presets, DEFAULT_REPOSITORY } from '$lib/presets';
import { logAlways, logErrorAlways, log } from '$lib/log';
function sortFilesWithinGroup(
files: Array<{ path: string; content: string }>,
): Array<{ path: string; content: string }> {
return files.sort((a, b) => {
const aPath = a.path;
const bPath = b.path;
// Check if one path is a parent of the other
if (bPath.startsWith(aPath.replace('/index.md', '/'))) return -1;
if (aPath.startsWith(bPath.replace('/index.md', '/'))) return 1;
return aPath.localeCompare(bPath);
});
}
export class ContentSyncService {
static readonly MAX_CONTENT_AGE_MS = 24 * 60 * 60 * 1000;
static async syncRepository(
options: {
returnStats?: boolean;
} = {},
): Promise<{
success: boolean;
stats: {
total_files: number;
total_size_bytes: number;
last_updated: Date;
};
sync_details: {
upserted_files: number;
deleted_files: number;
unchanged_files: number;
};
timestamp: string;
}> {
const { returnStats = true } = options;
const { owner, repo: repoName } = DEFAULT_REPOSITORY;
logAlways(`Starting sync for repository: ${owner}/${repoName}`);
let upsertedFiles = 0;
let deletedFiles = 0;
let unchangedFiles = 0;
try {
logAlways(`Step 1: Syncing repository ${owner}/${repoName}`);
const tarballBuffer = await fetchRepositoryTarball(owner, repoName);
const filesWithPaths = (await processMarkdownFromTarball(
tarballBuffer,
{
glob: ['**/*.md', '**/*.mdx'],
ignore: [],
title: `Sync ${owner}/${repoName}`,
distilled: false,
},
true,
)) as Array<{
path: string;
content: string;
}>;
logAlways(`Found ${filesWithPaths.length} markdown files in ${owner}/${repoName}`);
const existingFiles = await ContentDbService.getAllContent();
const existingPaths = new Set(existingFiles.map((file) => file.path));
const foundPaths = new Set(filesWithPaths.map((file) => file.path));
const contentInputs: CreateContentInput[] = [];
for (const file of filesWithPaths) {
const filename = ContentDbService.extractFilename(file.path);
const sizeBytes = new TextEncoder().encode(file.content).length;
const metadata = ContentDbService.extractFrontmatter(file.content);
const hasChanged = await ContentDbService.hasContentChanged(file.path, file.content);
if (hasChanged) {
contentInputs.push({
path: file.path,
filename,
content: file.content,
size_bytes: sizeBytes,
metadata,
});
} else {
unchangedFiles++;
}
}
if (contentInputs.length > 0) {
logAlways(`Upserting ${contentInputs.length} changed files`);
await ContentDbService.batchUpsertContent(contentInputs);
upsertedFiles = contentInputs.length;
} else {
logAlways(`No file content changes detected`);
}
// Handle deletions - find files in DB that are no longer in the repository
const deletedPaths = Array.from(existingPaths).filter((path) => !foundPaths.has(path));
if (deletedPaths.length > 0) {
logAlways(`Deleting ${deletedPaths.length} files that no longer exist`);
for (const deletedPath of deletedPaths) {
logAlways(` Deleting: ${deletedPath}`);
await ContentDbService.deleteContent(deletedPath);
}
deletedFiles = deletedPaths.length;
} else {
logAlways(`No deleted files detected`);
}
let stats;
if (returnStats) {
logAlways(`Step 2: Collecting final statistics`);
stats = await ContentSyncService.getContentStats();
} else {
logAlways(`Step 2: Skipping stats collection (returnStats = false)`);
// Return minimal stats structure
stats = {
total_files: 0,
total_size_bytes: 0,
last_updated: new Date(),
};
}
logAlways(
`Sync completed successfully: ${upsertedFiles} upserted, ${deletedFiles} deleted, ${unchangedFiles} unchanged`,
);
return {
success: true,
stats,
sync_details: {
upserted_files: upsertedFiles,
deleted_files: deletedFiles,
unchanged_files: unchangedFiles,
},
timestamp: new Date().toISOString(),
};
} catch (error) {
logErrorAlways(`Failed to sync repository ${owner}/${repoName}:`, error);
throw error;
}
}
static async isRepositoryContentStale(): Promise<boolean> {
try {
const stats = await ContentDbService.getContentStats();
if (stats.total_files === 0) {
return true; // No content, consider stale
}
const lastUpdated = new Date(stats.last_updated);
const contentAge = Date.now() - lastUpdated.getTime();
const isStale = contentAge > ContentSyncService.MAX_CONTENT_AGE_MS;
if (isStale) {
logAlways(
`Repository content is stale (age: ${Math.floor(contentAge / 1000 / 60)} minutes)`,
);
}
return isStale;
} catch (error) {
logErrorAlways(`Error checking repository staleness:`, error);
return true; // On error, assume stale
}
}
static async getPresetContentFromDb(
presetKey: string,
): Promise<Array<{ path: string; content: string }> | null> {
const preset = presets[presetKey];
if (!preset) {
return null;
}
try {
const allContent = await ContentDbService.getAllContent();
if (allContent.length === 0) {
return null;
}
log(`Checking ${allContent.length} files against glob patterns for preset ${presetKey}`);
log(`Glob patterns: ${JSON.stringify(preset.glob)}`);
log(`Ignore patterns: ${JSON.stringify(preset.ignore || [])}`);
const { minimatch } = await import('minimatch');
const orderedResults: Array<{ path: string; content: string }> = [];
// Process one glob pattern at a time
for (const pattern of preset.glob) {
log(`\nProcessing glob pattern: ${pattern}`);
const matchingFiles: Array<{ path: string; content: string }> = [];
for (const dbContent of allContent) {
const shouldIgnore = preset.ignore?.some((ignorePattern) => {
const matches = minimatch(dbContent.path, ignorePattern);
if (matches) {
log(` File ${dbContent.path} ignored by pattern: ${ignorePattern}`);
}
return matches;
});
if (shouldIgnore) continue;
if (minimatch(dbContent.path, pattern)) {
log(` File ${dbContent.path} matched`);
let processedContent = dbContent.content;
if (preset.minimize && Object.keys(preset.minimize).length > 0) {
processedContent = minimizeContent(dbContent.content, preset.minimize);
}
matchingFiles.push({
path: dbContent.path,
content: processedContent,
});
}
}
const sortedFiles = sortFilesWithinGroup(matchingFiles);
log(` Found ${sortedFiles.length} files for pattern: ${pattern}`);
sortedFiles.forEach((file, i) => {
log(` ${i + 1}. ${file.path}`);
});
orderedResults.push(...sortedFiles);
}
logAlways(
`Found ${orderedResults.length} files matching preset ${presetKey} from database in natural glob order`,
);
log('\nFinal file order:');
orderedResults.forEach((file, i) => {
log(` ${i + 1}. ${file.path}`);
});
return orderedResults;
} catch (error) {
logErrorAlways(`Failed to get preset content from database for ${presetKey}:`, error);
return null;
}
}
static async getContentStats() {
return ContentDbService.getContentStats();
}
}

View File

@@ -0,0 +1,112 @@
export interface QueryConfig {
debug?: boolean;
}
// Enum for distillable preset names
export enum DistillablePreset {
SVELTE_DISTILLED = 'svelte-distilled',
SVELTEKIT_DISTILLED = 'sveltekit-distilled',
SVELTE_COMPLETE_DISTILLED = 'svelte-complete-distilled',
}
// Database table types
export interface DbDistillation {
id: number;
preset_name: DistillablePreset;
version: string; // 'latest' or '2024-01-15'
content: string;
size_kb: number;
document_count: number;
distillation_job_id: number | null;
created_at: Date;
}
export interface DbDistillationJob {
id: number;
preset_name: string;
batch_id: string | null;
status: 'pending' | 'processing' | 'completed' | 'failed';
model_used: string;
total_files: number;
processed_files: number;
successful_files: number;
minimize_applied: boolean;
total_input_tokens: number;
total_output_tokens: number;
started_at: Date | null;
completed_at: Date | null;
error_message: string | null;
metadata: Record<string, unknown>; // JSONB
created_at: Date;
updated_at: Date;
}
export interface DbContent {
id: number;
path: string;
filename: string;
content: string;
size_bytes: number;
metadata: Record<string, unknown>;
created_at: Date;
updated_at: Date;
}
export interface DbContentDistilled {
id: number;
path: string;
filename: string;
content: string;
size_bytes: number;
metadata: Record<string, unknown>;
created_at: Date;
updated_at: Date;
}
// Input types for creating/updating records
export interface CreateDistillationInput {
preset_name: DistillablePreset;
version: string;
content: string;
size_kb: number;
document_count: number;
distillation_job_id?: number;
}
export interface CreateDistillationJobInput {
preset_name: string;
batch_id?: string;
status: 'pending' | 'processing' | 'completed' | 'failed';
model_used: string;
total_files: number;
minimize_applied?: boolean;
metadata?: Record<string, unknown>;
}
export interface CreateContentInput {
path: string;
filename: string;
content: string;
size_bytes: number;
metadata?: Record<string, unknown>;
}
export interface CreateContentDistilledInput {
path: string;
filename: string;
content: string;
size_bytes: number;
metadata?: Record<string, unknown>;
}
export interface ContentFilter {
path_pattern?: string; // For glob pattern matching
}
export interface ContentStats {
total_files: number;
total_size_bytes: number;
last_updated: Date;
}

View File

@@ -0,0 +1,335 @@
import { describe, it, expect } from 'vitest';
import {
cleanDocumentationPath,
cleanTarballPath,
extractTitleFromPath,
removeFrontmatter,
} from './pathUtils.js';
describe('pathUtils', () => {
describe('cleanDocumentationPath', () => {
it('should remove apps/svelte.dev/content/ prefix', () => {
const input = 'apps/svelte.dev/content/docs/svelte/01-introduction.md';
const expected = 'docs/svelte/01-introduction.md';
expect(cleanDocumentationPath(input)).toBe(expected);
});
it('should handle paths without the prefix', () => {
const input = 'docs/svelte/01-introduction.md';
const expected = 'docs/svelte/01-introduction.md';
expect(cleanDocumentationPath(input)).toBe(expected);
});
it('should handle empty string', () => {
const input = '';
const expected = '';
expect(cleanDocumentationPath(input)).toBe(expected);
});
it('should handle partial prefix matches', () => {
const input = 'apps/svelte.dev/content-extra/docs/svelte/01-introduction.md';
const expected = 'apps/svelte.dev/content-extra/docs/svelte/01-introduction.md';
expect(cleanDocumentationPath(input)).toBe(expected);
});
it('should handle paths with similar but different prefixes', () => {
const input = 'apps/svelte.dev/contents/docs/svelte/01-introduction.md';
const expected = 'apps/svelte.dev/contents/docs/svelte/01-introduction.md';
expect(cleanDocumentationPath(input)).toBe(expected);
});
it('should handle SvelteKit documentation paths', () => {
const input = 'apps/svelte.dev/content/docs/kit/01-routing.md';
const expected = 'docs/kit/01-routing.md';
expect(cleanDocumentationPath(input)).toBe(expected);
});
it('should handle tutorial paths', () => {
const input = 'apps/svelte.dev/content/tutorial/01-introduction/01-hello-world.md';
const expected = 'tutorial/01-introduction/01-hello-world.md';
expect(cleanDocumentationPath(input)).toBe(expected);
});
});
describe('cleanTarballPath', () => {
it('should remove the first segment from tarball paths', () => {
const input = 'svelte.dev-main/apps/svelte.dev/content/docs/svelte/01-introduction.md';
const expected = 'apps/svelte.dev/content/docs/svelte/01-introduction.md';
expect(cleanTarballPath(input)).toBe(expected);
});
it('should handle paths with different repo prefixes', () => {
const input = 'svelte-12345/apps/svelte.dev/content/docs/kit/01-routing.md';
const expected = 'apps/svelte.dev/content/docs/kit/01-routing.md';
expect(cleanTarballPath(input)).toBe(expected);
});
it('should handle single segment paths', () => {
const input = 'single-segment';
const expected = '';
expect(cleanTarballPath(input)).toBe(expected);
});
it('should handle empty string', () => {
const input = '';
const expected = '';
expect(cleanTarballPath(input)).toBe(expected);
});
it('should handle paths with no segments', () => {
const input = 'just-filename.md';
const expected = '';
expect(cleanTarballPath(input)).toBe(expected);
});
it('should handle complex nested paths', () => {
const input = 'repo-name/very/deep/nested/path/to/file.md';
const expected = 'very/deep/nested/path/to/file.md';
expect(cleanTarballPath(input)).toBe(expected);
});
});
describe('extractTitleFromPath', () => {
it('should extract filename and remove .md extension and numbered prefix', () => {
const input = 'docs/svelte/01-introduction.md';
const expected = 'introduction';
expect(extractTitleFromPath(input)).toBe(expected);
});
it('should remove numbered prefixes', () => {
const input = 'docs/svelte/01-introduction.md';
const expected = 'introduction';
expect(extractTitleFromPath(input)).toBe(expected);
});
it('should handle files without numbered prefixes', () => {
const input = 'docs/svelte/reactivity.md';
const expected = 'reactivity';
expect(extractTitleFromPath(input)).toBe(expected);
});
it('should handle files without .md extension', () => {
const input = 'docs/svelte/01-introduction';
const expected = 'introduction';
expect(extractTitleFromPath(input)).toBe(expected);
});
it('should handle complex numbered prefixes', () => {
const input = 'docs/svelte/99-advanced-topics.md';
const expected = 'advanced-topics';
expect(extractTitleFromPath(input)).toBe(expected);
});
it('should handle files with multiple numbered prefixes', () => {
const input = 'docs/svelte/01-02-nested-numbering.md';
const expected = '02-nested-numbering';
expect(extractTitleFromPath(input)).toBe(expected);
});
it('should handle just a filename', () => {
const input = '01-introduction.md';
const expected = 'introduction';
expect(extractTitleFromPath(input)).toBe(expected);
});
it('should handle empty string', () => {
const input = '';
const expected = '';
expect(extractTitleFromPath(input)).toBe(expected);
});
it('should handle paths with no filename', () => {
const input = 'docs/svelte/';
const expected = '';
expect(extractTitleFromPath(input)).toBe(expected);
});
it('should handle files with hyphens but no numbers', () => {
const input = 'docs/svelte/state-management.md';
const expected = 'state-management';
expect(extractTitleFromPath(input)).toBe(expected);
});
it('should handle files with numbers in the middle', () => {
const input = 'docs/svelte/svelte5-features.md';
const expected = 'svelte5-features';
expect(extractTitleFromPath(input)).toBe(expected);
});
it('should handle tutorial paths', () => {
const input = 'tutorial/01-introduction/01-hello-world.md';
const expected = 'hello-world';
expect(extractTitleFromPath(input)).toBe(expected);
});
it('should handle SvelteKit paths', () => {
const input = 'docs/kit/01-routing.md';
const expected = 'routing';
expect(extractTitleFromPath(input)).toBe(expected);
});
});
describe('removeFrontmatter', () => {
it('should remove valid frontmatter from content', () => {
const input = `---
title: Introduction
description: Getting started guide
---
# Introduction
This is the main content.`;
const expected = `# Introduction
This is the main content.`;
expect(removeFrontmatter(input)).toBe(expected);
});
it('should handle content without frontmatter', () => {
const input = `# Introduction
This is content without frontmatter.`;
const expected = `# Introduction
This is content without frontmatter.`;
expect(removeFrontmatter(input)).toBe(expected);
});
it('should handle empty content', () => {
const input = '';
const expected = '';
expect(removeFrontmatter(input)).toBe(expected);
});
it('should handle malformed frontmatter (no closing delimiter)', () => {
const input = `---
title: Introduction
This is malformed frontmatter without closing delimiter
# Content here`;
const expected = input; // Should return original content unchanged
expect(removeFrontmatter(input)).toBe(expected);
});
it('should handle frontmatter with complex YAML', () => {
const input = `---
title: Complex Example
tags:
- svelte
- tutorial
metadata:
author: John Doe
date: 2024-01-15
---
# Complex Example
Content with complex frontmatter.`;
const expected = `# Complex Example
Content with complex frontmatter.`;
expect(removeFrontmatter(input)).toBe(expected);
});
it('should handle content that starts with --- but is not frontmatter', () => {
const input = `---
This is not YAML frontmatter, just content that starts with ---`;
const expected = input; // Should return original content unchanged
expect(removeFrontmatter(input)).toBe(expected);
});
it('should handle frontmatter with empty lines', () => {
const input = `---
title: Introduction
description: A guide
---
# Content`;
const expected = `# Content`;
expect(removeFrontmatter(input)).toBe(expected);
});
it('should trim whitespace after removing frontmatter', () => {
const input = `---
title: Introduction
---
# Content with leading whitespace`;
const expected = `# Content with leading whitespace`;
expect(removeFrontmatter(input)).toBe(expected);
});
it('should handle frontmatter at the end of content', () => {
const input = `---
title: Only Frontmatter
---`;
const expected = ``;
expect(removeFrontmatter(input)).toBe(expected);
});
});
describe('integration tests', () => {
it('should work together for typical documentation workflow', () => {
// Simulate a typical path from tarball to display
const tarball_path = 'svelte.dev-main/apps/svelte.dev/content/docs/svelte/01-introduction.md';
// Clean tarball path
const cleaned_from_tarball = cleanTarballPath(tarball_path);
expect(cleaned_from_tarball).toBe('apps/svelte.dev/content/docs/svelte/01-introduction.md');
// This would be stored in DB and later cleaned for display
const cleaned_for_display = cleanDocumentationPath(cleaned_from_tarball);
expect(cleaned_for_display).toBe('docs/svelte/01-introduction.md');
// Extract title for metadata
const title = extractTitleFromPath(cleaned_from_tarball);
expect(title).toBe('introduction');
});
it('should handle SvelteKit paths through full workflow', () => {
const tarball_path = 'svelte.dev-main/apps/svelte.dev/content/docs/kit/01-routing.md';
const cleaned_from_tarball = cleanTarballPath(tarball_path);
expect(cleaned_from_tarball).toBe('apps/svelte.dev/content/docs/kit/01-routing.md');
const cleaned_for_display = cleanDocumentationPath(cleaned_from_tarball);
expect(cleaned_for_display).toBe('docs/kit/01-routing.md');
const title = extractTitleFromPath(cleaned_from_tarball);
expect(title).toBe('routing');
});
it('should handle tutorial paths through full workflow', () => {
const tarball_path =
'svelte.dev-main/apps/svelte.dev/content/tutorial/01-introduction/01-hello-world.md';
const cleaned_from_tarball = cleanTarballPath(tarball_path);
expect(cleaned_from_tarball).toBe(
'apps/svelte.dev/content/tutorial/01-introduction/01-hello-world.md',
);
const cleaned_for_display = cleanDocumentationPath(cleaned_from_tarball);
expect(cleaned_for_display).toBe('tutorial/01-introduction/01-hello-world.md');
const title = extractTitleFromPath(cleaned_from_tarball);
expect(title).toBe('hello-world');
});
it('should handle content processing with frontmatter removal', () => {
const content = `---
title: Introduction
---
# Introduction
This is the content.`;
const content_without_frontmatter = removeFrontmatter(content);
expect(content_without_frontmatter).toBe(`# Introduction
This is the content.`);
});
});
});

View File

@@ -0,0 +1,110 @@
/**
* Unified path utilities for handling documentation paths
*/
/**
* Clean a path by removing the "apps/svelte.dev/content/" prefix
* This is used to convert database paths to display paths
*
* @param path - The path to clean
* @returns The cleaned path
*/
export function cleanDocumentationPath(path: string): string {
const prefix = 'apps/svelte.dev/content/';
if (path.startsWith(prefix)) {
return path.substring(prefix.length);
}
return path;
}
/**
* Clean a tarball path by removing the repository directory prefix (first segment)
* This is used when processing files from GitHub tarballs
*
* @param path - The path to clean
* @returns The cleaned path without the repo directory prefix
*/
export function cleanTarballPath(path: string): string {
// Remove only the repo directory prefix (first segment)
return path.split('/').slice(1).join('/');
}
/**
* Extract the title from a file path by removing prefixes and file extensions
*
* @param filePath - The file path to extract title from
* @returns The extracted title
*/
export function extractTitleFromPath(filePath: string): string {
if (!filePath) {
return '';
}
const pathParts = filePath.split('/');
const filename = pathParts[pathParts.length - 1];
// Handle empty filename (e.g., paths ending with '/')
if (!filename) {
return '';
}
// Remove .md extension and numbered prefixes
return filename.replace('.md', '').replace(/^\d+-/, '');
}
/**
* Remove frontmatter from markdown content using a tokenizer approach
* Frontmatter is YAML metadata at the beginning of files between --- delimiters
*
* @param content - The markdown content that may contain frontmatter
* @returns The content with frontmatter removed
*/
export function removeFrontmatter(content: string): string {
if (!content || content.length === 0) {
return content;
}
// Check if content starts with frontmatter delimiter
if (!content.startsWith('---\n')) {
return content;
}
let position = 4; // Start after the opening "---\n"
let insideFrontmatter = true;
let frontmatterEndOffset: number | null = null;
// Traverse the string character by character
while (position < content.length && insideFrontmatter) {
const char = content[position];
// Look for potential end of frontmatter: \n---
if (char === '\n' && position + 3 < content.length) {
const nextThree = content.substring(position + 1, position + 4);
if (nextThree === '---') {
// Check what comes after the closing ---
const afterClosing = position + 4;
if (afterClosing >= content.length) {
// End of string - this is valid frontmatter
frontmatterEndOffset = content.length;
insideFrontmatter = false;
} else if (content[afterClosing] === '\n') {
// Followed by newline - this is valid frontmatter
frontmatterEndOffset = afterClosing + 1;
insideFrontmatter = false;
}
// If followed by something else, it's not the end delimiter, continue searching
}
}
position++;
}
// If we never found the end of frontmatter, it's malformed
if (frontmatterEndOffset === null) {
return content;
}
// Return content after the frontmatter, trimmed
return content.substring(frontmatterEndOffset).trim();
}

View File

@@ -0,0 +1,165 @@
export const SVELTE_5_PROMPT =
'Always use Svelte 5 runes and Svelte 5 syntax. Runes do not need to be imported, they are globals. $state() runes are always declared using `let`, never with `const`. When passing a function to $derived, you must always use $derived.by(() => ...). Error boundaries can only catch errors during component rendering and at the top level of an $effect inside the error boundary. Error boundaries do not catch errors in onclick or other event handlers.';
export const DISTILLATION_PROMPT = `
You are an expert in web development, specifically Svelte 5 and SvelteKit. Your task is to condense and distill the Svelte documentation into a concise format while preserving the most important information.
Shorten the text information AS MUCH AS POSSIBLE while covering key concepts.
Focus on:
1. Code examples with short explanations of how they work
2. Key concepts and APIs with their usage patterns
3. Important gotchas and best practices
4. Patterns that developers commonly use
Remove:
1. Redundant explanations
2. Verbose content that can be simplified
3. Marketing language
4. Legacy or deprecated content
5. Anything else that is not strictly necessary
Keep your output in markdown format. Preserve code blocks with their language annotations.
Maintain headings but feel free to combine or restructure sections to improve clarity.
Make sure all code examples use Svelte 5 runes syntax ($state, $derived, $effect, etc.)
Keep the following Svelte 5 syntax rules in mind:
* There is no colon (:) in event modifiers. You MUST use "onclick" instead of "on:click".
* Runes do not need to be imported, they are globals.
* $state() runes are always declared using let, never with const.
* When passing a function to $derived, you must always use $derived.by(() => ...).
* Error boundaries can only catch errors during component rendering and at the top level of an $effect inside the error boundary.
* Error boundaries do not catch errors in onclick or other event handlers.
IMPORTANT: All code examples MUST come from the documentation verbatim, do NOT create new code examples. Do NOT modify existing code examples.
IMPORTANT: Because of changes in Svelte 5 syntax, do not include content from your existing knowledge, you may only use knowledge from the documentation to condense.
Here is the documentation you must condense:
`;
export const SVELTE_DEVELOPER_PROMPT = `You are an expert in web development, specifically Svelte 5 and SvelteKit, with expert-level knowledge of Svelte 5, SvelteKit, and TypeScript.
## Core Expertise:
### Svelte 5 Runes & Reactivity
- **$state**: Reactive state declaration (always use let, never const)
- **$derived**: Computed values (always use $derived.by(() => ...) for functions)
- **$effect**: Side effects and cleanup (runs after DOM updates)
- **$props**: Component props with destructuring and defaults
- **$bindable**: Two-way binding for props
### Critical Syntax Rules:
${SVELTE_5_PROMPT}
### Additional Rules:
- Props: let { count = 0, name } = $props()
- Bindable: let { value = $bindable() } = $props()
- Children: let { children } = $props()
- Cleanup: $effect(() => { return () => cleanup() })
- Context: setContext/getContext work with runes
- Snippets: {#snippet name(params)} for reusable templates
### SvelteKit Essentials:
- File-based routing with route groups and parameters
- Load functions: +page.ts (universal) vs +page.server.ts (server-only)
- Form actions in +page.server.ts with progressive enhancement
- Layout nesting and data inheritance
- Error and loading states with +error.svelte and loading UI
### TypeScript Integration:
- Always use TypeScript for type safety
- Properly type PageData, PageLoad, Actions, RequestHandler
- Generic components with proper type inference
- .svelte.ts for shared reactive state
## MCP Tool Usage Guide:
### Template Prompts (Efficient Documentation Injection):
Use these for instant access to curated documentation sets:
- **svelte-core**: Core Svelte 5 (introduction, runes, template syntax, styling)
- **svelte-advanced**: Advanced Svelte 5 (special elements, runtime, misc)
- **svelte-complete**: Complete Svelte 5 documentation
- **sveltekit-core**: Core SvelteKit (getting started, core concepts)
- **sveltekit-production**: Production SvelteKit (build/deploy, advanced, best practices)
- **sveltekit-complete**: Complete SvelteKit documentation
### Resources Access:
- **📦 Preset Resources**: Use svelte-llm://svelte-core, svelte-llm://svelte-advanced, svelte-llm://svelte-complete, svelte-llm://sveltekit-core, svelte-llm://sveltekit-production, svelte-llm://sveltekit-complete for curated documentation sets
- **📄 Individual Docs**: Use svelte-llm://doc/[path] for specific documentation files
- Access via list_resources or direct URI for browsing and reference
### When to use list_sections + get_documentation:
- **Specific Topics**: When you need particular sections not covered by presets
- **Custom Combinations**: When presets don't match the exact scope needed
- **Deep Dives**: When you need detailed information on specific APIs
- **Troubleshooting**: When investigating specific issues or edge cases
### Strategic Approach:
1. **Start with Template Prompts**: Use template prompts (svelte-core, sveltekit-core, etc.) for immediate context injection
2. **Browse via Resources**: Use preset resources for reading/reference during development
3. **Supplement with Specific Docs**: Use list_sections + get_documentation only when presets don't cover your needs
4. **Combine Efficiently**: Use multiple template prompts if you need both Svelte and SvelteKit context
### Documentation Fetching Priority:
1. **Template Prompts First**: Always try relevant template prompts before individual sections
2. **Preset Resources**: Use for browsing and reference
3. **Individual Sections**: Only when specific content not in presets is needed
4. **Multiple Sources**: Combine template prompts with specific sections as needed
## Best Practices:
- Write production-ready TypeScript code
- Include proper error handling and loading states
- Consider accessibility (ARIA, keyboard navigation)
- Optimize for performance (lazy loading, minimal reactivity)
- Use semantic HTML and proper component composition
- Implement proper cleanup in effects
- Handle edge cases and provide fallbacks`;
// eslint-disable-next-line @typescript-eslint/naming-convention, func-style
export const createSvelteDeveloperPromptWithTask = (task?: string): string => {
// eslint-disable-next-line @typescript-eslint/naming-convention
const basePrompt = SVELTE_DEVELOPER_PROMPT;
if (!task) {
return (
basePrompt +
`
## Your Approach:
When helping with Svelte/SvelteKit development:
1. **Use Template Prompts**: Start with relevant template prompts (svelte-core, sveltekit-core, etc.) for immediate context
2. **Supplement as Needed**: Use list_sections + get_documentation only for content not covered by templates
3. **Provide Complete Solutions**: Include working TypeScript code with proper types
4. **Explain Trade-offs**: Discuss architectural decisions and alternatives
5. **Optimize**: Suggest performance improvements and best practices`
);
}
return (
basePrompt +
`
## Current Task:
${task}
## Task-Specific Approach:
1. **Inject Relevant Context**: Use appropriate template prompts based on "${task.substring(0, 50)}...":
- Component tasks: Use svelte-core for runes, template syntax
- Advanced features: Use svelte-advanced for special elements, runtime
- Full applications: Use svelte-complete + sveltekit-core/complete
- Production apps: Use sveltekit-production for deployment, best practices
2. **Supplement with Specific Docs**: Use list_sections + get_documentation only if templates don't cover specific needs
3. **Design Architecture**:
- Component structure and composition
- State management approach
- TypeScript types and interfaces
- Error handling strategy
4. **Implement Solution**:
- Complete, working code
- Proper types and error boundaries
- Performance optimizations
- Accessibility considerations
5. **Explain Implementation**: Provide rationale for choices and discuss alternatives`
);
};

View File

@@ -0,0 +1,8 @@
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { fetchRepositoryTarball } from '$lib/fetchMarkdown';
export const GET: RequestHandler = async () => {
const tarball_buffer = await fetchRepositoryTarball('sveltejs', 'svelte.dev');
return json({ data: tarball_buffer });
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M416.9 93.1c-41.1-58.9-122.4-76.3-181.2-38.9L132.5 120c-28.2 17.7-47.6 46.5-53.5 79.3-4.9 27.3-.6 55.5 12.3 80-8.8 13.4-14.9 28.5-17.7 44.2-5.9 33.4 1.8 67.8 21.6 95.4 41.2 58.9 122.4 76.3 181.2 38.9L379.6 392c28.2-17.7 47.6-46.5 53.5-79.3 4.9-27.3.6-55.5-12.3-80 8.8-13.4 14.9-28.4 17.7-44.2 5.8-33.4-1.9-67.8-21.6-95.4" style="fill:#ff3e00"/><path d="M225.6 424.5c-33.3 8.6-68.4-4.4-88-32.6-11.9-16.6-16.5-37.3-13-57.4.6-3.3 1.4-6.5 2.5-9.6l1.9-5.9 5.3 3.9c12.2 9 25.9 15.8 40.4 20.2l3.8 1.2-.4 3.8c-.5 5.4 1 10.9 4.2 15.3 5.9 8.5 16.5 12.4 26.5 9.8 2.2-.6 4.4-1.5 6.3-2.8l103.2-65.8c5.1-3.2 8.6-8.4 9.7-14.4 1.1-6.1-.3-12.3-3.9-17.3-5.9-8.5-16.5-12.4-26.5-9.8-2.2.6-4.4 1.5-6.3 2.8L252 291c-6.5 4.1-13.5 7.2-21 9.2-33.3 8.6-68.4-4.4-88-32.6-11.9-16.6-16.5-37.3-13-57.4 3.5-19.7 15.2-37 32.2-47.7l103.2-65.8c6.5-4.1 13.5-7.2 21-9.2 33.3-8.6 68.4 4.4 88 32.6 11.9 16.6 16.5 37.3 13 57.4-.6 3.3-1.4 6.5-2.5 9.6L383 193l-5.3-3.9c-12.2-9-25.9-15.8-40.4-20.2l-3.8-1.2.4-3.8c.5-5.4-1-10.9-4.2-15.3-5.9-8.5-16.5-12.4-26.5-9.8-2.2.6-4.4 1.5-6.3 2.8l-103.2 65.8c-5.1 3.2-8.6 8.4-9.7 14.4-1.1 6.1.3 12.3 3.9 17.3 5.9 8.5 16.5 12.4 26.5 9.8 2.2-.6 4.4-1.5 6.3-2.8L260 221c6.5-4.1 13.5-7.2 21-9.2 33.3-8.6 68.4 4.4 88 32.6 11.9 16.6 16.5 37.3 13 57.4-3.5 19.7-15.2 37-32.2 47.7l-103.2 65.8c-6.5 4.1-13.6 7.2-21 9.2" style="fill:#fff"/></svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,44 +0,0 @@
---
title: Overview
---
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.
## Setup
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):
- [local setup](local-setup) using `@sveltejs/mcp`
- [remote setup](remote-setup) using `https://mcp.svelte.dev/mcp`
## Usage
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). This will tell the LLM which tools are available and when it's appropriate to use them.
```md
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:
## Available MCP Tools:
### 1. list-sections
Use this FIRST to discover all available documentation sections. Returns a structured list with titles, use_cases, and paths.
When asked about Svelte or SvelteKit topics, ALWAYS use this tool at the start of the chat to find relevant sections.
### 2. get-documentation
Retrieves full documentation content for specific sections. Accepts single or multiple sections.
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.
### 3. svelte-autofixer
Analyzes Svelte code and returns issues and suggestions.
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.
### 4. playground-link
Generates a Svelte Playground link with the provided code.
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.
```
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.

View File

@@ -1,3 +0,0 @@
---
title: Introduction
---

View File

@@ -1,132 +0,0 @@
---
title: Local setup
---
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`:
```bash
npx -y @sveltejs/mcp
```
Here's how to set it up in some common MCP clients:
## Claude Code
To include the local MCP version in Claude Code, simply run the following command:
```bash
claude mcp add -t stdio -s [scope] svelte -- npx -y @sveltejs/mcp
```
The `[scope]` must be `user`, `project` or `local`.
## Claude Desktop
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:
```json
{
"mcpServers": {
"svelte": {
"command": "npx",
"args": ["-y", "@sveltejs/mcp"]
}
}
}
```
## Codex CLI
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):
```toml
[mcp_servers.svelte]
command = "npx"
args = ["-y", "@sveltejs/mcp"]
```
## Gemini CLI
To include the local MCP version in Gemini CLI, simply run the following command:
```bash
gemini mcp add -t stdio -s [scope] svelte npx -y @sveltejs/mcp
```
The `[scope]` must be `user`, `project` or `local`.
## OpenCode
Run the command:
```bash
opencode mcp add
```
and follow the instructions, selecting 'Local' under the 'Select MCP server type' prompt:
```bash
opencode mcp add
┌ Add MCP server
◇ Enter MCP server name
│ svelte
◇ Select MCP server type
│ Local
◆ Enter command to run
│ npx -y @sveltejs/mcp
```
## VS Code
- Open the command palette
- Select "MCP: Add Server..."
- Select "Command (stdio)"
- Insert `npx -y @sveltejs/mcp` in the input and press `Enter`
- When prompted for a name, insert `svelte`
- Select if you want to add it as a `Global` or `Workspace` MCP server
## Cursor
- Open the command palette
- Select "View: Open MCP Settings"
- Click on "Add custom MCP"
It will open a file with your MCP servers where you can add the following configuration:
```json
{
"mcpServers": {
"svelte": {
"command": "npx",
"args": ["-y", "@sveltejs/mcp"]
}
}
}
```
## Zed
- Open the command palette
- Search and select "agent:open settings"
- In settings panel look for `Model Context Protocol (MCP) Servers`
- Click on "Add Server"
- Select: "Add Custom Server"
It will open a popup with MCP server config where you can add the following configuration:
```json
{
"svelte": {
"command": "npx",
"args": ["-y", "@sveltejs/mcp"]
}
}
```
## Other clients
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.

View File

@@ -1,122 +0,0 @@
---
title: Remote setup
---
The remote version of the MCP server is available at `https://mcp.svelte.dev/mcp`.
Here's how to set it up in some common MCP clients:
## Claude Code
To include the remote MCP version in Claude Code, simply run the following command:
```bash
claude mcp add -t http -s [scope] svelte https://mcp.svelte.dev/mcp
```
You can choose your preferred `scope` (it must be `user`, `project` or `local`) and `name`.
## Claude Desktop
- Open Settings > Connectors
- Click on Add Custom Connector
- When prompted for a name, enter `svelte`
- Under the Remote MCP server URL input, use `https://mcp.svelte.dev/mcp`
- Click Add
## Codex CLI
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):
```toml
experimental_use_rmcp_client = true
[mcp_servers.svelte]
url = "https://mcp.svelte.dev/mcp"
```
## Gemini CLI
To use the remote MCP server with Gemini CLI, simply run the following command:
```bash
gemini mcp add -t http -s [scope] svelte https://mcp.svelte.dev/mcp
```
The `[scope]` must be `user`, `project` or `local`.
## OpenCode
Run the command:
```bash
opencode mcp add
```
and follow the instructions, selecting 'Remote' under the 'Select MCP server type' prompt:
```bash
opencode mcp add
┌ Add MCP server
◇ Enter MCP server name
│ svelte
◇ Select MCP server type
│ Remote
◇ Enter MCP server URL
│ https://mcp.svelte.dev/mcp
```
## VS Code
- Open the command palette
- Select "MCP: Add Server..."
- Select "HTTP (HTTP or Server-Sent-Events)"
- Insert `https://mcp.svelte.dev/mcp` in the input and press `Enter`
- Insert your preferred name
- Select if you want to add it as a `Global` or `Workspace` MCP server
## Cursor
- Open the command palette
- Select "View: Open MCP Settings"
- Click on "Add custom MCP"
It will open a file with your MCP servers where you can add the following configuration:
```json
{
"mcpServers": {
"svelte": {
"url": "https://mcp.svelte.dev/mcp"
}
}
}
```
## GitHub Coding Agent
- Open your repository in GitHub
- Go to Settings
- Open Copilot > Coding agent
- Edit the MCP configuration
```json
{
"mcpServers": {
"svelte": {
"type": "http",
"url": "https://mcp.svelte.dev/mcp",
"tools": ["*"]
}
}
}
```
- Click _Save MCP configuration_
## Other clients
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.

View File

@@ -1,3 +0,0 @@
---
title: Setup
---

View File

@@ -1,21 +0,0 @@
---
title: Tools
---
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:
## list-sections
Provides a list of all the available documentation sections.
## get-documentation
Allows the model to get the full (and up-to-date) documentation for the requested sections directly from [svelte.dev/docs](/docs).
## svelte-autofixer
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.
## playground-link
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).

View File

@@ -1,9 +0,0 @@
---
title: Resources
---
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.
## doc-section
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.

View File

@@ -1,205 +0,0 @@
---
title: Prompts
---
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.
## svelte-task
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.
<details>
<summary>Copy the prompt</summary>
```md
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:
<available-docs>
- title: Overview, use_cases: project setup, creating new svelte apps, scaffolding, cli tools, initializing projects, path: cli/overview
- title: Frequently asked questions, use_cases: project setup, initializing new svelte projects, troubleshooting cli installation, package manager configuration, path: cli/faq
- title: sv create, use_cases: project setup, starting new sveltekit app, initializing project, creating from playground, choosing project template, path: cli/sv-create
- title: sv add, use_cases: project setup, adding features to existing projects, integrating tools, testing setup, styling setup, authentication, database setup, deployment adapters, path: cli/sv-add
- title: sv check, use_cases: code quality, ci/cd pipelines, error checking, typescript projects, pre-commit hooks, finding unused css, accessibility auditing, production builds, path: cli/sv-check
- title: sv migrate, use_cases: 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, path: cli/sv-migrate
- title: devtools-json, use_cases: development setup, chrome devtools integration, browser-based editing, local development workflow, debugging setup, path: cli/devtools-json
- title: drizzle, use_cases: database setup, sql queries, orm integration, data modeling, postgresql, mysql, sqlite, server-side data access, database migrations, type-safe queries, path: cli/drizzle
- title: eslint, use_cases: code quality, linting, error detection, project setup, code standards, team collaboration, typescript projects, path: cli/eslint
- title: lucia, use_cases: authentication, login systems, user management, registration pages, session handling, auth setup, path: cli/lucia
- title: mdsvex, use_cases: blog, content sites, markdown rendering, documentation sites, technical writing, cms integration, article pages, path: cli/mdsvex
- title: paraglide, use_cases: internationalization, multi-language sites, i18n, translation, localization, language switching, global apps, multilingual content, path: cli/paraglide
- title: playwright, use_cases: browser testing, e2e testing, integration testing, test automation, quality assurance, ci/cd pipelines, testing user flows, path: cli/playwright
- title: prettier, use_cases: code formatting, project setup, code style consistency, team collaboration, linting configuration, path: cli/prettier
- title: storybook, use_cases: component development, design systems, ui library, isolated component testing, documentation, visual testing, component showcase, path: cli/storybook
- title: sveltekit-adapter, use_cases: deployment, production builds, hosting setup, choosing deployment platform, configuring adapters, static site generation, node server, vercel, cloudflare, netlify, path: cli/sveltekit-adapter
- title: tailwindcss, use_cases: project setup, styling, css framework, rapid prototyping, utility-first css, design systems, responsive design, adding tailwind to svelte, path: cli/tailwind
- title: vitest, use_cases: testing, unit tests, component testing, test setup, quality assurance, ci/cd pipelines, test-driven development, path: cli/vitest
- title: Introduction, use_cases: learning sveltekit, project setup, understanding framework basics, choosing between svelte and sveltekit, getting started with full-stack apps, path: kit/introduction
- title: Creating a project, use_cases: project setup, starting new sveltekit app, initial development environment, first-time sveltekit users, scaffolding projects, path: kit/creating-a-project
- title: Project types, use_cases: deployment, project setup, choosing adapters, ssg, spa, ssr, serverless, mobile apps, desktop apps, pwa, offline apps, browser extensions, separate backend, docker containers, path: kit/project-types
- title: Project structure, use_cases: project setup, understanding file structure, organizing code, starting new project, learning sveltekit basics, path: kit/project-structure
- title: Web standards, use_cases: always, any sveltekit project, data fetching, forms, api routes, server-side rendering, deployment to various platforms, path: kit/web-standards
- title: Routing, use_cases: routing, navigation, multi-page apps, project setup, file structure, api endpoints, data loading, layouts, error pages, always, path: kit/routing
- title: Loading data, use_cases: data fetching, api calls, database queries, dynamic routes, page initialization, loading states, authentication checks, ssr data, form data, content rendering, path: kit/load
- title: Form actions, use_cases: forms, user input, data submission, authentication, login systems, user registration, progressive enhancement, validation errors, path: kit/form-actions
- title: Page options, use_cases: prerendering static sites, ssr configuration, spa setup, client-side rendering control, url trailing slash handling, adapter deployment config, build optimization, path: kit/page-options
- title: State management, use_cases: sveltekit, server-side rendering, ssr, state management, authentication, data persistence, load functions, context api, navigation, component lifecycle, path: kit/state-management
- title: Remote functions, use_cases: data fetching, server-side logic, database queries, type-safe client-server communication, forms, user input, mutations, authentication, crud operations, optimistic updates, path: kit/remote-functions
- title: Building your app, use_cases: production builds, deployment preparation, build process optimization, adapter configuration, preview before deployment, path: kit/building-your-app
- title: Adapters, use_cases: deployment, production builds, hosting setup, choosing deployment platform, configuring adapters, path: kit/adapters
- title: Zero-config deployments, use_cases: deployment, production builds, hosting setup, choosing deployment platform, ci/cd configuration, path: kit/adapter-auto
- title: Node servers, use_cases: deployment, production builds, node.js hosting, custom server setup, environment configuration, reverse proxy setup, docker deployment, systemd services, path: kit/adapter-node
- title: Static site generation, use_cases: static site generation, ssg, prerendering, deployment, github pages, spa mode, blogs, documentation sites, marketing sites, path: kit/adapter-static
- title: Single-page apps, use_cases: spa mode, single-page apps, client-only rendering, static hosting, mobile app wrappers, no server-side logic, adapter-static setup, fallback pages, path: kit/single-page-apps
- title: Cloudflare, use_cases: deployment, cloudflare workers, cloudflare pages, hosting setup, production builds, serverless deployment, edge computing, path: kit/adapter-cloudflare
- title: Cloudflare Workers, use_cases: deploying to cloudflare workers, cloudflare workers sites deployment, legacy cloudflare adapter, wrangler configuration, cloudflare platform bindings, path: kit/adapter-cloudflare-workers
- title: Netlify, use_cases: deployment, netlify hosting, production builds, serverless functions, edge functions, static site hosting, path: kit/adapter-netlify
- title: Vercel, use_cases: deployment, vercel hosting, production builds, serverless functions, edge functions, isr, image optimization, environment variables, path: kit/adapter-vercel
- title: Writing adapters, use_cases: custom deployment, building adapters, unsupported platforms, adapter development, custom hosting environments, path: kit/writing-adapters
- title: Advanced routing, use_cases: advanced routing, dynamic routes, file viewers, nested paths, custom 404 pages, url validation, route parameters, multi-level navigation, path: kit/advanced-routing
- title: Hooks, use_cases: authentication, logging, error tracking, request interception, api proxying, custom routing, internationalization, database initialization, middleware logic, session management, path: kit/hooks
- title: Errors, use_cases: error handling, custom error pages, 404 pages, api error responses, production error logging, error tracking, type-safe errors, path: kit/errors
- title: Link options, use_cases: routing, navigation, multi-page apps, performance optimization, link preloading, forms with get method, search functionality, focus management, scroll behavior, path: kit/link-options
- title: Service workers, use_cases: offline support, pwa, caching strategies, performance optimization, precaching assets, network resilience, progressive web apps, path: kit/service-workers
- title: Server-only modules, use_cases: api keys, environment variables, sensitive data protection, backend security, preventing data leaks, server-side code isolation, path: kit/server-only-modules
- title: Snapshots, use_cases: forms, user input, preserving form data, multi-step forms, navigation state, preventing data loss, textarea content, input fields, comment systems, surveys, path: kit/snapshots
- title: Shallow routing, use_cases: modals, dialogs, image galleries, overlays, history-driven ui, mobile-friendly navigation, photo viewers, lightboxes, drawer menus, path: kit/shallow-routing
- title: Observability, use_cases: performance monitoring, debugging, observability, tracing requests, production diagnostics, analyzing slow requests, finding bottlenecks, monitoring server-side operations, path: kit/observability
- title: Packaging, use_cases: building component libraries, publishing npm packages, creating reusable svelte components, library development, package distribution, path: kit/packaging
- title: Auth, use_cases: authentication, login systems, user management, session handling, jwt tokens, protected routes, user credentials, authorization checks, path: kit/auth
- title: Performance, use_cases: performance optimization, slow loading pages, production deployment, debugging performance issues, reducing bundle size, improving load times, path: kit/performance
- title: Icons, use_cases: icons, ui components, styling, css frameworks, tailwind, unocss, performance optimization, dependency management, path: kit/icons
- title: Images, use_cases: image optimization, responsive images, performance, hero images, product photos, galleries, cms integration, cdn setup, asset management, path: kit/images
- title: Accessibility, use_cases: always, any sveltekit project, screen reader support, keyboard navigation, multi-page apps, client-side routing, internationalization, multilingual sites, path: kit/accessibility
- title: SEO, use_cases: seo optimization, search engine ranking, content sites, blogs, marketing sites, public-facing apps, sitemaps, amp pages, meta tags, performance optimization, path: kit/seo
- title: Frequently asked questions, use_cases: troubleshooting package imports, library compatibility issues, client-side code execution, external api integration, middleware setup, database configuration, view transitions, yarn configuration, path: kit/faq
- title: Integrations, use_cases: project setup, css preprocessors, postcss, scss, sass, less, stylus, typescript setup, adding integrations, tailwind, testing, auth, linting, formatting, path: kit/integrations
- title: Breakpoint Debugging, use_cases: debugging, breakpoints, development workflow, troubleshooting issues, vscode setup, ide configuration, inspecting code execution, path: kit/debugging
- title: Migrating to SvelteKit v2, use_cases: migration, upgrading from sveltekit 1 to 2, breaking changes, version updates, path: kit/migrating-to-sveltekit-2
- title: Migrating from Sapper, use_cases: migrating from sapper, upgrading legacy projects, sapper to sveltekit conversion, project modernization, path: kit/migrating
- title: Additional resources, use_cases: troubleshooting, getting help, finding examples, learning sveltekit, project templates, common issues, community support, path: kit/additional-resources
- title: Glossary, use_cases: rendering strategies, performance optimization, deployment configuration, seo requirements, static sites, spas, server-side rendering, prerendering, edge deployment, pwa development, path: kit/glossary
- title: @sveltejs/kit, use_cases: forms, form actions, server-side validation, form submission, error handling, redirects, json responses, http errors, server utilities, path: kit/@sveltejs-kit
- title: @sveltejs/kit/hooks, use_cases: middleware, request processing, authentication chains, logging, multiple hooks, request/response transformation, path: kit/@sveltejs-kit-hooks
- title: @sveltejs/kit/node/polyfills, use_cases: node.js environments, custom servers, non-standard runtimes, ssr setup, web api compatibility, polyfill requirements, path: kit/@sveltejs-kit-node-polyfills
- title: @sveltejs/kit/node, use_cases: node.js adapter, custom server setup, http integration, streaming files, node deployment, server-side rendering with node, path: kit/@sveltejs-kit-node
- title: @sveltejs/kit/vite, use_cases: project setup, vite configuration, initial sveltekit setup, build tooling, path: kit/@sveltejs-kit-vite
- title: $app/environment, use_cases: always, conditional logic, client-side code, server-side code, build-time logic, prerendering, development vs production, environment detection, path: kit/$app-environment
- title: $app/forms, use_cases: forms, user input, data submission, progressive enhancement, custom form handling, form validation, path: kit/$app-forms
- title: $app/navigation, use_cases: routing, navigation, multi-page apps, programmatic navigation, data reloading, preloading, shallow routing, navigation lifecycle, scroll handling, view transitions, path: kit/$app-navigation
- title: $app/paths, use_cases: static assets, images, fonts, public files, base path configuration, subdirectory deployment, cdn setup, asset urls, links, navigation, path: kit/$app-paths
- title: $app/server, use_cases: remote functions, server-side logic, data fetching, form handling, api endpoints, client-server communication, prerendering, file reading, batch queries, path: kit/$app-server
- title: $app/state, use_cases: routing, navigation, multi-page apps, loading states, url parameters, form handling, error states, version updates, page metadata, shallow routing, path: kit/$app-state
- title: $app/stores, use_cases: legacy projects, sveltekit pre-2.12, migration from stores to runes, maintaining older codebases, accessing page data, navigation state, app version updates, path: kit/$app-stores
- title: $app/types, use_cases: routing, navigation, type safety, route parameters, dynamic routes, link generation, pathname validation, multi-page apps, path: kit/$app-types
- title: $env/dynamic/private, use_cases: api keys, secrets management, server-side config, environment variables, backend logic, deployment-specific settings, private data handling, path: kit/$env-dynamic-private
- title: $env/dynamic/public, use_cases: environment variables, client-side config, runtime configuration, public api keys, deployment-specific settings, multi-environment apps, path: kit/$env-dynamic-public
- title: $env/static/private, use_cases: server-side api keys, backend secrets, database credentials, private configuration, build-time optimization, server endpoints, authentication tokens, path: kit/$env-static-private
- title: $env/static/public, use_cases: environment variables, public config, client-side data, api endpoints, build-time configuration, public constants, path: kit/$env-static-public
- title: $lib, use_cases: project setup, component organization, importing shared components, reusable ui elements, code structure, path: kit/$lib
- title: $service-worker, use_cases: offline support, pwa, service workers, caching strategies, progressive web apps, offline-first apps, path: kit/$service-worker
- title: Configuration, use_cases: project setup, configuration, adapters, deployment, build settings, environment variables, routing customization, prerendering, csp security, csrf protection, path configuration, typescript setup, path: kit/configuration
- title: Command Line Interface, use_cases: project setup, typescript configuration, generated types, ./$types imports, initial project configuration, path: kit/cli
- title: Types, use_cases: typescript, type safety, route parameters, api endpoints, load functions, form actions, generated types, jsconfig setup, path: kit/types
- title: Overview, use_cases: use title and path to estimate use case, path: mcp/overview
- title: Local setup, use_cases: use title and path to estimate use case, path: mcp/local-setup
- title: Remote setup, use_cases: use title and path to estimate use case, path: mcp/remote-setup
- title: Tools, use_cases: use title and path to estimate use case, path: mcp/tools
- title: Resources, use_cases: use title and path to estimate use case, path: mcp/resources
- title: Prompts, use_cases: use title and path to estimate use case, path: mcp/prompts
- title: Overview, use_cases: always, any svelte project, getting started, learning svelte, introduction, project setup, understanding framework basics, path: svelte/overview
- title: Getting started, use_cases: project setup, starting new svelte project, initial installation, choosing between sveltekit and vite, editor configuration, path: svelte/getting-started
- title: .svelte files, use_cases: always, any svelte project, component creation, project setup, learning svelte basics, path: svelte/svelte-files
- title: .svelte.js and .svelte.ts files, use_cases: shared reactive state, reusable reactive logic, state management across components, global stores, custom reactive utilities, path: svelte/svelte-js-files
- title: What are runes?, use_cases: always, any svelte 5 project, understanding core syntax, learning svelte 5, migration from svelte 4, path: svelte/what-are-runes
- title: $state, use_cases: always, any svelte project, core reactivity, state management, counters, forms, todo apps, interactive ui, data updates, class-based components, path: svelte/$state
- title: $derived, use_cases: always, any svelte project, computed values, reactive calculations, derived data, transforming state, dependent values, path: svelte/$derived
- title: $effect, use_cases: canvas drawing, third-party library integration, dom manipulation, side effects, intervals, timers, network requests, analytics tracking, path: svelte/$effect
- title: $props, use_cases: always, any svelte project, passing data to components, component communication, reusable components, component props, path: svelte/$props
- title: $bindable, use_cases: forms, user input, two-way data binding, custom input components, parent-child communication, reusable form fields, path: svelte/$bindable
- title: $inspect, use_cases: debugging, development, tracking state changes, reactive state monitoring, troubleshooting reactivity issues, path: svelte/$inspect
- title: $host, use_cases: custom elements, web components, dispatching custom events, component library, framework-agnostic components, path: svelte/$host
- title: Basic markup, use_cases: always, any svelte project, basic markup, html templating, component structure, attributes, events, props, text rendering, path: svelte/basic-markup
- title: {#if ...}, use_cases: always, conditional rendering, showing/hiding content, dynamic ui, user permissions, loading states, error handling, form validation, path: svelte/if
- title: {#each ...}, use_cases: always, lists, arrays, iteration, product listings, todos, tables, grids, dynamic content, shopping carts, user lists, comments, feeds, path: svelte/each
- title: {#key ...}, use_cases: animations, transitions, component reinitialization, forcing component remount, value-based ui updates, resetting component state, path: svelte/key
- title: {#await ...}, use_cases: async data fetching, api calls, loading states, promises, error handling, lazy loading components, dynamic imports, path: svelte/await
- title: {#snippet ...}, use_cases: reusable markup, component composition, passing content to components, table rows, list items, conditional rendering, reducing duplication, path: svelte/snippet
- title: {@render ...}, use_cases: reusable ui patterns, component composition, conditional rendering, fallback content, layout components, slot alternatives, template reuse, path: svelte/@render
- title: {@html ...}, use_cases: rendering html strings, cms content, rich text editors, markdown to html, blog posts, wysiwyg output, sanitized html injection, dynamic html content, path: svelte/@html
- title: {@attach ...}, use_cases: tooltips, popovers, dom manipulation, third-party libraries, canvas drawing, element lifecycle, interactive ui, custom directives, wrapper components, path: svelte/@attach
- title: {@const ...}, use_cases: computed values in loops, derived calculations in blocks, local variables in each iterations, complex list rendering, path: svelte/@const
- title: {@debug ...}, use_cases: debugging, development, troubleshooting, tracking state changes, monitoring variables, reactive data inspection, path: svelte/@debug
- title: bind:, use_cases: forms, user input, two-way data binding, interactive ui, media players, file uploads, checkboxes, radio buttons, select dropdowns, contenteditable, dimension tracking, path: svelte/bind
- title: use:, use_cases: custom directives, dom manipulation, third-party library integration, tooltips, click outside, gestures, focus management, element lifecycle hooks, path: svelte/use
- title: transition:, use_cases: animations, interactive ui, modals, dropdowns, notifications, conditional content, show/hide elements, smooth state changes, path: svelte/transition
- title: in: and out:, use_cases: animation, transitions, interactive ui, conditional rendering, independent enter/exit effects, modals, tooltips, notifications, path: svelte/in-and-out
- title: animate:, use_cases: sortable lists, drag and drop, reorderable items, todo lists, kanban boards, playlist editors, priority queues, animated list reordering, path: svelte/animate
- title: style:, use_cases: dynamic styling, conditional styles, theming, dark mode, responsive design, interactive ui, component styling, path: svelte/style
- title: class, use_cases: always, conditional styling, dynamic classes, tailwind css, component styling, reusable components, responsive design, path: svelte/class
- title: await, use_cases: async data fetching, loading states, server-side rendering, awaiting promises in components, async validation, concurrent data loading, path: svelte/await-expressions
- title: Scoped styles, use_cases: always, styling components, scoped css, component-specific styles, preventing style conflicts, animations, keyframes, path: svelte/scoped-styles
- title: Global styles, use_cases: global styles, third-party libraries, css resets, animations, styling body/html, overriding component styles, shared keyframes, base styles, path: svelte/global-styles
- title: Custom properties, use_cases: theming, custom styling, reusable components, design systems, dynamic colors, component libraries, ui customization, path: svelte/custom-properties
- title: Nested <style> elements, use_cases: component styling, scoped styles, dynamic styles, conditional styling, nested style tags, custom styling logic, path: svelte/nested-style-elements
- title: <svelte:boundary>, use_cases: error handling, async data loading, loading states, error recovery, flaky components, error reporting, resilient ui, path: svelte/svelte-boundary
- title: <svelte:window>, use_cases: keyboard shortcuts, scroll tracking, window resize handling, responsive layouts, online/offline detection, viewport dimensions, global event listeners, path: svelte/svelte-window
- title: <svelte:document>, use_cases: document events, visibility tracking, fullscreen detection, pointer lock, focus management, document-level interactions, path: svelte/svelte-document
- title: <svelte:body>, use_cases: mouse tracking, hover effects, cursor interactions, global body events, drag and drop, custom cursors, interactive backgrounds, body-level actions, path: svelte/svelte-body
- title: <svelte:head>, use_cases: seo optimization, page titles, meta tags, social media sharing, dynamic head content, multi-page apps, blog posts, product pages, path: svelte/svelte-head
- title: <svelte:element>, use_cases: dynamic content, cms integration, user-generated content, configurable ui, runtime element selection, flexible components, path: svelte/svelte-element
- title: <svelte:options>, use_cases: migration, custom elements, web components, legacy mode compatibility, runes mode setup, svg components, mathml components, css injection control, path: svelte/svelte-options
- title: Stores, use_cases: shared state, cross-component data, reactive values, async data streams, manual control over updates, rxjs integration, extracting logic, path: svelte/stores
- title: Context, use_cases: shared state, avoiding prop drilling, component communication, theme providers, user context, authentication state, configuration sharing, deeply nested components, path: svelte/context
- title: Lifecycle hooks, use_cases: component initialization, cleanup tasks, timers, subscriptions, dom measurements, chat windows, autoscroll features, migration from svelte 4, path: svelte/lifecycle-hooks
- title: Imperative component API, use_cases: project setup, client-side rendering, server-side rendering, ssr, hydration, testing, programmatic component creation, tooltips, dynamic mounting, path: svelte/imperative-component-api
- title: Testing, use_cases: testing, quality assurance, unit tests, integration tests, component tests, e2e tests, vitest setup, playwright setup, test automation, path: svelte/testing
- title: TypeScript, use_cases: typescript setup, type safety, component props typing, generic components, wrapper components, dom type augmentation, project configuration, path: svelte/typescript
- title: Custom elements, use_cases: web components, custom elements, component library, design system, framework-agnostic components, embedding svelte in non-svelte apps, shadow dom, path: svelte/custom-elements
- title: Svelte 4 migration guide, use_cases: upgrading svelte 3 to 4, version migration, updating dependencies, breaking changes, legacy project maintenance, path: svelte/v4-migration-guide
- title: Svelte 5 migration guide, use_cases: migrating from svelte 4 to 5, upgrading projects, learning svelte 5 syntax changes, runes migration, event handler updates, path: svelte/v5-migration-guide
- title: Frequently asked questions, use_cases: getting started, learning svelte, beginner setup, project initialization, vs code setup, formatting, testing, routing, mobile apps, troubleshooting, community support, path: svelte/faq
- title: svelte, use_cases: migration from svelte 4 to 5, upgrading legacy code, component lifecycle hooks, context api, mounting components, event dispatchers, typescript component types, path: svelte/svelte
- title: svelte/action, use_cases: typescript types, actions, use directive, dom manipulation, element lifecycle, custom behaviors, third-party library integration, path: svelte/svelte-action
- title: svelte/animate, use_cases: animated lists, sortable items, drag and drop, reordering elements, todo lists, kanban boards, playlist management, smooth position transitions, path: svelte/svelte-animate
- title: svelte/attachments, use_cases: library development, component libraries, programmatic element manipulation, migrating from actions to attachments, spreading props onto elements, path: svelte/svelte-attachments
- title: svelte/compiler, use_cases: build tools, custom compilers, ast manipulation, preprocessors, code transformation, migration scripts, syntax analysis, bundler plugins, dev tools, path: svelte/svelte-compiler
- title: svelte/easing, use_cases: animations, transitions, custom easing, smooth motion, interactive ui, modals, dropdowns, carousels, page transitions, scroll effects, path: svelte/svelte-easing
- title: svelte/events, use_cases: window events, document events, global event listeners, event delegation, programmatic event handling, cleanup functions, media queries, path: svelte/svelte-events
- title: svelte/legacy, use_cases: migration from svelte 4 to svelte 5, upgrading legacy code, event modifiers, class components, imperative component instantiation, path: svelte/svelte-legacy
- title: svelte/motion, use_cases: animation, smooth transitions, interactive ui, sliders, counters, physics-based motion, drag gestures, accessibility, reduced motion, path: svelte/svelte-motion
- title: svelte/reactivity/window, use_cases: responsive design, viewport tracking, scroll effects, window resize handling, online/offline detection, zoom level tracking, path: svelte/svelte-reactivity-window
- title: svelte/reactivity, use_cases: reactive data structures, state management with maps/sets, game boards, selection tracking, url manipulation, query params, real-time clocks, media queries, responsive design, path: svelte/svelte-reactivity
- title: svelte/server, use_cases: server-side rendering, ssr, static site generation, seo optimization, initial page load, pre-rendering, node.js server, custom server setup, path: svelte/svelte-server
- title: svelte/store, use_cases: state management, shared data, reactive stores, cross-component communication, global state, computed values, data synchronization, legacy svelte projects, path: svelte/svelte-store
- title: svelte/transition, use_cases: animations, transitions, interactive ui, modals, dropdowns, tooltips, notifications, svg animations, list animations, page transitions, path: svelte/svelte-transition
- title: Compiler errors, use_cases: animation, transitions, keyed each blocks, list animations, path: svelte/compiler-errors
- title: Compiler warnings, use_cases: accessibility, a11y compliance, wcag standards, screen readers, keyboard navigation, aria attributes, semantic html, interactive elements, path: svelte/compiler-warnings
- title: Runtime errors, use_cases: debugging errors, error handling, troubleshooting runtime issues, migration to svelte 5, component binding, effects and reactivity, path: svelte/runtime-errors
- title: Runtime warnings, use_cases: debugging state proxies, console logging reactive values, inspecting state changes, development troubleshooting, path: svelte/runtime-warnings
- title: Overview, use_cases: migrating from svelte 3/4 to svelte 5, maintaining legacy components, understanding deprecated features, gradual upgrade process, path: svelte/legacy-overview
- title: Reactive let/var declarations, use_cases: migration, legacy svelte projects, upgrading from svelte 4, understanding old reactivity, maintaining existing code, learning runes differences, path: svelte/legacy-let
- title: Reactive $: statements, use_cases: legacy mode, migration from svelte 4, reactive statements, computed values, derived state, side effects, path: svelte/legacy-reactive-assignments
- title: export let, use_cases: legacy mode, migration from svelte 4, maintaining older projects, component props without runes, exporting component methods, renaming reserved word props, path: svelte/legacy-export-let
- title: $$props and $$restProps, use_cases: legacy mode migration, component wrappers, prop forwarding, button components, reusable ui components, spreading props to child elements, path: svelte/legacy-$$props-and-$$restProps
- title: on:, use_cases: legacy mode, event handling, button clicks, forms, user interactions, component communication, event forwarding, event modifiers, path: svelte/legacy-on
- title: <slot>, use_cases: legacy mode, migrating from svelte 4, component composition, reusable components, passing content to components, modals, layouts, wrappers, path: svelte/legacy-slots
- title: $$slots, use_cases: legacy mode, conditional slot rendering, optional content sections, checking if slots provided, migrating from legacy to runes, path: svelte/legacy-$$slots
- title: <svelte:fragment>, use_cases: named slots, component composition, layout systems, avoiding wrapper divs, legacy svelte projects, slot content organization, path: svelte/legacy-svelte-fragment
- title: <svelte:component>, use_cases: dynamic components, component switching, conditional rendering, legacy mode migration, tabbed interfaces, multi-step forms, path: svelte/legacy-svelte-component
- title: <svelte:self>, use_cases: recursive components, tree structures, nested menus, file explorers, comment threads, hierarchical data, path: svelte/legacy-svelte-self
- title: Imperative component API, use_cases: migration from svelte 3/4 to 5, legacy component api, maintaining old projects, understanding deprecated patterns, path: svelte/legacy-component-api
</available-docs>
Every time you write a Svelte component or a Svelte module you MUST invoke the `svelte-autofixer` tool providing the code. The tool will return a list of issues or suggestions. If there are any issues or suggestions you MUST fix them and call the tool again with the updated code. You MUST keep doing this until the tool returns no issues or suggestions. Only then you can return the code to the user.
This is the task you will work on:
<task>
[YOUR TASK HERE]
</task>
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.
```
</details>

View File

@@ -1,3 +0,0 @@
---
title: Capabilities
---

View File

@@ -1,3 +0,0 @@
---
title: MCP
---

View File

@@ -12,9 +12,6 @@ const gitignore_path = fileURLToPath(new URL('./.gitignore', import.meta.url));
export default /** @type {import("eslint").Linter.Config} */ ([
includeIgnoreFile(gitignore_path),
{
ignores: ['.claude/**/*', '.changeset/*'],
},
js.configs.recommended,
...ts.configs.recommended,
...svelte.configs.recommended,
@@ -37,13 +34,6 @@ export default /** @type {import("eslint").Linter.Config} */ ([
leadingUnderscore: 'allow',
},
],
'@typescript-eslint/no-unused-vars': [
'error',
{
varsIgnorePattern: '^_',
ignoreRestSiblings: true,
},
],
'func-style': ['error', 'declaration', { allowTypeAnnotation: true }],
'import/no-unresolved': 'off', // this doesn't work well with typescript path mapping
'import/extensions': [

View File

@@ -3,24 +3,17 @@
"version": "0.0.1",
"description": "The official Svelte MCP server implementation",
"type": "module",
"packageManager": "pnpm@10.19.0",
"scripts": {
"dev": "pnpm --filter mcp-remote dev",
"build": "pnpm -r run build",
"dev": "pnpm --filter @sveltejs/mcp-remote run dev",
"check": "pnpm -r run check",
"check:publint": "pnpm -r run check:publint",
"format": "prettier --write .",
"lint": "prettier --check . && eslint .",
"lint:fix": "prettier --write . && eslint . --fix",
"test:unit": "vitest",
"test": "npm run test:unit -- --run",
"test:watch": "npm run test:unit -- --watch",
"inspect": "pnpm mcp-inspector",
"generate-summaries": "pnpm --filter @sveltejs/mcp-server run generate-summaries",
"generate-prompt-docs": "node --import node-resolve-ts/register scripts/update-docs-prompts.ts",
"debug:generate-summaries": "pnpm --filter @sveltejs/mcp-server run debug:generate-summaries",
"release": "pnpm --filter @sveltejs/mcp run build && changeset publish",
"changeset:version": "changeset version && pnpm --filter @sveltejs/mcp run update:version && git add --all"
"inspect": "pnpm mcp-inspector"
},
"keywords": [
"svelte",
@@ -30,23 +23,33 @@
],
"private": true,
"devDependencies": {
"@changesets/cli": "^2.29.7",
"@eslint/compat": "^1.3.2",
"@eslint/js": "^9.36.0",
"@modelcontextprotocol/inspector": "^0.17.0",
"@svitejs/changesets-changelog-github-compact": "^1.2.0",
"eslint": "^9.36.0",
"@modelcontextprotocol/inspector": "^0.16.7",
"@sveltejs/adapter-vercel": "^5.6.3",
"@sveltejs/kit": "^2.22.0",
"@sveltejs/vite-plugin-svelte": "^6.0.0",
"@types/eslint-scope": "^8.3.2",
"@types/estree": "^1.0.8",
"@types/node": "^24.3.1",
"@types/tar-stream": "^3.1.4",
"@typescript-eslint/types": "^8.43.0",
"dotenv": "^17.2.2",
"drizzle-kit": "^0.30.2",
"drizzle-orm": "^0.40.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-svelte": "^3.12.3",
"globals": "^16.0.0",
"node-resolve-ts": "^1.0.2",
"minimatch": "^10.0.3",
"prettier": "^3.4.2",
"prettier-plugin-svelte": "^3.3.3",
"publint": "^0.3.13",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"tar-stream": "^3.1.7",
"typescript": "^5.0.0",
"typescript-eslint": "^8.44.1",
"vitest": "^4.0.0"
"vitest": "^3.2.3"
},
"pnpm": {
"onlyBuiltDependencies": [

View File

@@ -12,8 +12,9 @@
"keywords": [],
"author": "",
"license": "ISC",
"packageManager": "pnpm@10.15.1",
"type": "module",
"dependencies": {
"drizzle-orm": "^0.44.0"
"drizzle-orm": "^0.40.1"
}
}

View File

@@ -1,4 +1,4 @@
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';
import { blob, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';
import { float_32_array } from './utils.js';
/**
@@ -51,7 +51,7 @@ export const distillation_jobs = sqliteTable('distillation_jobs', {
export const content = sqliteTable('content', {
id: integer('id').primaryKey(),
path: text('path').notNull(),
path: text('path').notNull().unique(),
filename: text('filename').notNull(),
content: text('content').notNull(),
size_bytes: integer('size_bytes').notNull(),
@@ -67,7 +67,7 @@ export const content = sqliteTable('content', {
export const content_distilled = sqliteTable('content_distilled', {
id: integer('id').primaryKey(),
path: text('path').notNull(),
path: text('path').notNull().unique(),
filename: text('filename').notNull(),
content: text('content').notNull(),
size_bytes: integer('size_bytes').notNull(),
@@ -80,3 +80,17 @@ export const content_distilled = sqliteTable('content_distilled', {
.notNull()
.$defaultFn(() => new Date()),
});
export const cache = sqliteTable('cache', {
id: integer('id').primaryKey(),
cache_key: text('cache_key').notNull().unique(),
data: blob('data', { mode: 'buffer' }).notNull(),
size_bytes: integer('size_bytes').notNull(),
expires_at: integer('expires_at', { mode: 'timestamp' }).notNull(),
created_at: integer('created_at', { mode: 'timestamp' })
.notNull()
.$defaultFn(() => new Date()),
updated_at: integer('updated_at', { mode: 'timestamp' })
.notNull()
.$defaultFn(() => new Date()),
});

View File

@@ -1,2 +0,0 @@
# Anthropic API Key from: https://console.anthropic.com/
ANTHROPIC_API_KEY=your_api_key_here

View File

@@ -7,20 +7,18 @@
"keywords": [],
"author": "",
"license": "ISC",
"packageManager": "pnpm@10.15.1",
"type": "module",
"scripts": {
"test": "vitest",
"generate-summaries": "node scripts/generate-summaries.ts --experimental-strip-types",
"debug:generate-summaries": "DEBUG_MODE=1 node scripts/generate-summaries.ts --experimental-strip-types"
"test": "vitest"
},
"exports": {
".": "./src/index.ts"
},
"peerDependencies": {
"drizzle-orm": "^0.44.0"
"drizzle-orm": "^0.40.0"
},
"dependencies": {
"@mcp-ui/server": "^5.12.0",
"@sveltejs/mcp-schema": "workspace:^",
"@tmcp/adapter-valibot": "^0.1.4",
"@typescript-eslint/parser": "^8.44.0",
@@ -28,19 +26,16 @@
"eslint-plugin-svelte": "^3.12.3",
"svelte": "^5.39.2",
"svelte-eslint-parser": "^1.3.2",
"tmcp": "^1.15.3",
"ts-blank-space": "^0.6.2",
"tmcp": "^1.13.0",
"typescript-eslint": "^8.44.0",
"valibot": "^1.1.0",
"vitest": "^4.0.0",
"vitest": "^3.2.4",
"zimmerframe": "^1.1.4"
},
"devDependencies": {
"@anthropic-ai/sdk": "^0.67.0",
"@sveltejs/kit": "^2.42.2",
"@types/eslint-scope": "^8.3.2",
"@types/estree": "^1.0.8",
"@typescript-eslint/types": "^8.44.0",
"dotenv": "^17.2.3"
"@typescript-eslint/types": "^8.44.0"
}
}

View File

@@ -1,244 +0,0 @@
#!/usr/bin/env node
import 'dotenv/config';
import { writeFile, mkdir } from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
import { get_sections } from '../src/mcp/utils.ts';
import { AnthropicProvider } from '../src/lib/anthropic.ts';
import { type AnthropicBatchRequest, type SummaryData } from '../src/lib/schemas.ts';
const current_filename = fileURLToPath(import.meta.url);
const current_dirname = path.dirname(current_filename);
const USE_CASES_PROMPT = `
You are tasked with analyzing Svelte 5 and SvelteKit documentation pages to identify when they would be useful.
Your task:
1. Read the documentation page content provided
2. Identify the main use cases, scenarios, or queries where this documentation would be relevant
3. Create a VERY SHORT, comma-separated list of use cases (maximum 200 characters total)
4. Think about what a developer might be trying to build or accomplish when they need this documentation
Guidelines:
- Focus on WHEN this documentation would be needed, not WHAT it contains
- Consider specific project types (e.g., "e-commerce site", "blog", "dashboard", "social media app")
- Consider specific features (e.g., "authentication", "forms", "data fetching", "animations")
- Consider specific components (e.g., "slider", "modal", "dropdown", "card")
- Consider development stages (e.g., "project setup", "deployment", "testing", "migration")
- Use "always" for fundamental concepts that apply to virtually all Svelte projects
- Be concise but specific
- Use lowercase
- Separate multiple use cases with commas
Examples of good use_cases:
- "always, any svelte project, core reactivity"
- "authentication, login systems, user management"
- "e-commerce, product listings, shopping carts"
- "forms, user input, data submission"
- "deployment, production builds, hosting setup"
- "animation, transitions, interactive ui"
- "routing, navigation, multi-page apps"
- "blog, content sites, markdown rendering"
Requirements:
- Maximum 200 characters (including spaces and commas)
- Lowercase only
- Comma-separated list of use cases
- Focus on WHEN/WHY someone would need this, not what it is
- Be specific about project types, features, or components when applicable
- Use "always" sparingly, only for truly universal concepts
- Do not include quotes or special formatting in your response
- Respond with ONLY the use cases text, no additional text
Here is the documentation page content to analyze:
`;
async function fetch_section_content(url: string) {
const response = await fetch(url, { signal: AbortSignal.timeout(30000) });
if (!response.ok) {
throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
}
return await response.text();
}
async function main() {
console.log('🚀 Starting use cases generation...');
// Check for API key
const api_key = process.env.ANTHROPIC_API_KEY;
if (!api_key) {
console.error('❌ Error: ANTHROPIC_API_KEY environment variable is required');
console.error('Please set it in packages/mcp-server/.env file or export it:');
console.error('export ANTHROPIC_API_KEY=your_api_key_here');
process.exit(1);
}
// Get all sections
console.log('📚 Fetching documentation sections...');
let sections = await get_sections();
console.log(`Found ${sections.length} sections`);
// Debug mode: limit to 2 sections
const debug_mode = process.env.DEBUG_MODE === '1';
if (debug_mode) {
console.log('🐛 DEBUG_MODE enabled - processing only 2 sections');
sections = sections.slice(0, 2);
}
// Fetch content for each section
console.log('📥 Downloading section content...');
const sections_with_content: Array<{
section: (typeof sections)[number];
content: string;
index: number;
}> = [];
const download_errors: Array<{ section: string; error: string }> = [];
for (let i = 0; i < sections.length; i++) {
const section = sections[i]!;
try {
console.log(`Fetching ${i + 1}/${sections.length}: ${section.title}`);
const content = await fetch_section_content(section.url);
sections_with_content.push({
section,
content,
index: i,
});
} catch (error) {
const error_msg = error instanceof Error ? error.message : String(error);
console.error(`⚠️ Failed to fetch ${section.title}:`, error_msg);
download_errors.push({ section: section.title, error: error_msg });
}
}
console.log(`✅ Successfully downloaded ${sections_with_content.length} sections`);
if (sections_with_content.length === 0) {
console.error('❌ No sections were successfully downloaded');
process.exit(1);
}
// Initialize Anthropic client
console.log('🤖 Initializing Anthropic API...');
const anthropic = new AnthropicProvider('claude-sonnet-4-5-20250929', api_key);
// Prepare batch requests
console.log('📦 Preparing batch requests...');
const batch_requests: AnthropicBatchRequest[] = sections_with_content.map(
({ content, index }) => ({
custom_id: `section-${index}`,
params: {
model: anthropic.get_model_identifier(),
max_tokens: 250,
messages: [
{
role: 'user',
content: USE_CASES_PROMPT + content,
},
],
temperature: 0,
},
}),
);
// Create and process batch
console.log('🚀 Creating batch job...');
const batch_response = await anthropic.create_batch(batch_requests);
console.log(`✅ Batch created with ID: ${batch_response.id}`);
// Poll for completion
console.log('⏳ Waiting for batch to complete...');
let batch_status = await anthropic.get_batch_status(batch_response.id);
while (batch_status.processing_status === 'in_progress') {
const { succeeded, processing, errored } = batch_status.request_counts;
console.log(` Progress: ${succeeded} succeeded, ${processing} processing, ${errored} errored`);
await new Promise((resolve) => setTimeout(resolve, 5000));
batch_status = await anthropic.get_batch_status(batch_response.id);
}
console.log('✅ Batch processing completed!');
// Get results
if (!batch_status.results_url) {
throw new Error('Batch completed but no results URL available');
}
console.log('📥 Downloading results...');
const results = await anthropic.get_batch_results(batch_status.results_url);
// Process results
console.log('📊 Processing results...');
const summaries: Record<string, string> = {};
const errors: Array<{ section: string; error: string }> = [];
for (const result of results) {
const index = parseInt(result.custom_id.split('-')[1] ?? '0');
const section_data = sections_with_content.find((s) => s.index === index);
if (!section_data) {
console.warn(`⚠️ Could not find section for index ${index}`);
continue;
}
const { section } = section_data;
if (result.result.type !== 'succeeded' || !result.result.message) {
const error_msg = result.result.error?.message || 'Failed or no message';
console.error(`${section.title}: ${error_msg}`);
errors.push({ section: section.title, error: error_msg });
continue;
}
const output_content = result.result.message.content[0]?.text;
if (output_content) {
summaries[section.slug] = output_content.trim();
console.log(`${section.title}`);
}
}
// Write output to JSON file
console.log('💾 Writing results to file...');
const output_path = path.join(current_dirname, '../src/use_cases.json');
const output_dir = path.dirname(output_path);
await mkdir(output_dir, { recursive: true });
const summary_data: SummaryData = {
generated_at: new Date().toISOString(),
model: anthropic.get_model_identifier(),
total_sections: sections.length,
successful_summaries: Object.keys(summaries).length,
failed_summaries: errors.length,
summaries,
errors: errors.length > 0 ? errors : undefined,
download_errors: download_errors.length > 0 ? download_errors : undefined,
};
await writeFile(output_path, JSON.stringify(summary_data, null, 2), 'utf-8');
// Print summary
console.log('\n📊 Summary:');
console.log(` Total sections: ${sections.length}`);
console.log(` Successfully downloaded: ${sections_with_content.length}`);
console.log(` Download failures: ${download_errors.length}`);
console.log(` Successfully analyzed: ${Object.keys(summaries).length}`);
console.log(` Analysis failures: ${errors.length}`);
console.log(`\n✅ Results written to: ${output_path}`);
if (download_errors.length > 0) {
console.log('\n⚠ Some sections failed to download:');
download_errors.forEach((e) => console.log(` - ${e.section}: ${e.error}`));
}
if (errors.length > 0) {
console.log('\n⚠ Some sections failed to analyze:');
errors.forEach((e) => console.log(` - ${e.section}: ${e.error}`));
}
}
main().catch((error) => {
console.error('❌ Fatal error:', error);
process.exit(1);
});

View File

@@ -1,6 +0,0 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"allowImportingTsExtensions": true
}
}

View File

@@ -11,7 +11,6 @@ export const base_runes = [
export const nested_runes = [
'$state.raw',
'$state.snapshot',
'$state.eager',
'$effect.pre',
'$effect.tracking',
'$effect.pending',

View File

@@ -1,172 +0,0 @@
import { Anthropic } from '@anthropic-ai/sdk';
import type { Model } from '@anthropic-ai/sdk/resources/messages/messages.js';
import * as v from 'valibot';
import {
anthropic_batch_response_schema,
anthropic_batch_result_schema,
type AnthropicBatchRequest,
} from './schemas.js';
export class AnthropicProvider {
private client: Anthropic;
private modelId: Model;
private baseUrl: string;
private apiKey: string;
name = 'Anthropic';
constructor(model_id: Model, api_key: string) {
if (!api_key) {
throw new Error('ANTHROPIC_API_KEY is required');
}
this.apiKey = api_key;
this.client = new Anthropic({ apiKey: api_key, timeout: 1800000 });
this.modelId = model_id;
this.baseUrl = 'https://api.anthropic.com/v1';
}
get_client(): Anthropic {
return this.client;
}
get_model_identifier(): Model {
return this.modelId;
}
async create_batch(requests: AnthropicBatchRequest[]) {
try {
const response = await fetch(`${this.baseUrl}/messages/batches`, {
method: 'POST',
headers: {
'x-api-key': this.apiKey,
'anthropic-version': '2023-06-01',
'content-type': 'application/json',
},
body: JSON.stringify({ requests }),
});
if (!response.ok) {
const error_text = await response.text();
throw new Error(
`Failed to create batch: ${response.status} ${response.statusText} - ${error_text}`,
);
}
const json_data = await response.json();
const validated_response = v.safeParse(anthropic_batch_response_schema, json_data);
if (!validated_response.success) {
throw new Error(
`Invalid batch response from Anthropic API: ${JSON.stringify(validated_response.issues)}`,
);
}
return validated_response.output;
} catch (error) {
console.error('Error creating batch with Anthropic:', error);
throw new Error(
`Failed to create batch: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
async get_batch_status(batch_id: string, max_retries = 10, retry_delay = 30000) {
let retry_count = 0;
while (retry_count <= max_retries) {
try {
const response = await fetch(`${this.baseUrl}/messages/batches/${batch_id}`, {
method: 'GET',
headers: {
'x-api-key': this.apiKey,
'anthropic-version': '2023-06-01',
},
});
if (!response.ok) {
const error_text = await response.text();
throw new Error(
`Failed to get batch status: ${response.status} ${response.statusText} - ${error_text}`,
);
}
const json_data = await response.json();
const validated_response = v.safeParse(anthropic_batch_response_schema, json_data);
if (!validated_response.success) {
throw new Error(
`Invalid batch status response from Anthropic API: ${JSON.stringify(validated_response.issues)}`,
);
}
return validated_response.output;
} catch (error) {
retry_count++;
if (retry_count > max_retries) {
console.error(
`Error getting batch status for ${batch_id} after ${max_retries} retries:`,
error,
);
throw new Error(
`Failed to get batch status after ${max_retries} retries: ${
error instanceof Error ? error.message : String(error)
}`,
);
}
console.warn(
`Error getting batch status for ${batch_id} (attempt ${retry_count}/${max_retries}):`,
error,
);
console.log(`Retrying in ${retry_delay / 1000} seconds...`);
await new Promise((resolve) => setTimeout(resolve, retry_delay));
}
}
// This should never be reached due to the throw in the catch block, but TypeScript needs a return
throw new Error(`Failed to get batch status for ${batch_id} after ${max_retries} retries`);
}
async get_batch_results(results_url: string) {
try {
const response = await fetch(results_url, {
method: 'GET',
headers: {
'x-api-key': this.apiKey,
'anthropic-version': '2023-06-01',
},
});
if (!response.ok) {
const error_text = await response.text();
throw new Error(
`Failed to get batch results: ${response.status} ${response.statusText} - ${error_text}`,
);
}
const text = await response.text();
// Parse JSONL format (one JSON object per line)
const parsed_results = text
.split('\n')
.filter((line) => line.trim())
.map((line) => JSON.parse(line));
// Validate all results
const validated_results = v.safeParse(v.array(anthropic_batch_result_schema), parsed_results);
if (!validated_results.success) {
throw new Error(
`Invalid batch results from Anthropic API: ${JSON.stringify(validated_results.issues)}`,
);
}
return validated_results.output;
} catch (error) {
console.error(`Error getting batch results:`, error);
throw new Error(
`Failed to get batch results: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
}

View File

@@ -1,122 +0,0 @@
import * as v from 'valibot';
export const documentation_sections_schema = v.record(
v.string(),
v.object({
metadata: v.object({
title: v.string(),
use_cases: v.optional(v.string()),
}),
slug: v.string(),
}),
);
// Valibot schemas for Batch API
export const summary_data_schema = v.object({
generated_at: v.string(),
model: v.string(),
total_sections: v.number(),
successful_summaries: v.number(),
failed_summaries: v.number(),
summaries: v.record(v.string(), v.string()),
errors: v.optional(
v.array(
v.object({
section: v.string(),
error: v.string(),
}),
),
),
download_errors: v.optional(
v.array(
v.object({
section: v.string(),
error: v.string(),
}),
),
),
});
export const anthropic_batch_request_schema = v.object({
custom_id: v.string(),
params: v.object({
model: v.string(),
max_tokens: v.number(),
messages: v.array(
v.object({
role: v.union([v.literal('user'), v.literal('assistant')]),
content: v.union([
v.string(),
v.array(
v.object({
type: v.string(),
text: v.string(),
}),
),
]),
}),
),
}),
});
export const anthropic_batch_response_schema = v.object({
id: v.string(),
type: v.string(),
processing_status: v.union([v.literal('in_progress'), v.literal('ended')]),
request_counts: v.object({
processing: v.number(),
succeeded: v.number(),
errored: v.number(),
canceled: v.number(),
expired: v.number(),
}),
ended_at: v.nullable(v.string()),
created_at: v.string(),
expires_at: v.string(),
cancel_initiated_at: v.nullable(v.string()),
results_url: v.nullable(v.string()),
});
export const anthropic_batch_result_schema = v.object({
custom_id: v.string(),
result: v.object({
type: v.union([
v.literal('succeeded'),
v.literal('errored'),
v.literal('canceled'),
v.literal('expired'),
]),
message: v.optional(
v.object({
id: v.string(),
type: v.string(),
role: v.string(),
model: v.string(),
content: v.array(
v.object({
type: v.string(),
text: v.string(),
}),
),
stop_reason: v.string(),
stop_sequence: v.nullable(v.string()),
usage: v.object({
input_tokens: v.number(),
output_tokens: v.number(),
}),
}),
),
error: v.optional(
v.object({
type: v.string(),
message: v.string(),
}),
),
}),
});
// Export inferred types
export type SummaryData = v.InferOutput<typeof summary_data_schema>;
export type AnthropicBatchRequest = v.InferOutput<typeof anthropic_batch_request_schema>;
export type AnthropicBatchResponse = v.InferOutput<typeof anthropic_batch_response_schema>;
export type AnthropicBatchResult = v.InferOutput<typeof anthropic_batch_result_schema>;

View File

@@ -10,338 +10,279 @@ function run_autofixers_on_code(code: string, desired_svelte_version = 5) {
return content;
}
function with_possible_inits(title: string, fn: (args: { init: string }) => void) {
describe.each([
{ init: '$state' },
{ init: '$state.raw' },
{ init: '$derived' },
{ init: '$derived.by' },
])(title, fn);
}
describe('add_autofixers_issues', () => {
describe('assign_in_effect', () => {
with_possible_inits('($init)', ({ init }) => {
it(`should add suggestions when assigning to a stateful variable inside an effect`, () => {
const content = run_autofixers_on_code(`
<script>
const count = ${init}(0);
$effect(() => {
count = 43;
});
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
);
});
it(`should add a suggestion for each variable assigned within an effect`, () => {
const content = run_autofixers_on_code(`
<script>
const count = $state(0);
const count2 = $state(0);
$effect(() => {
count = 43;
count2 = 44;
});
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(2);
expect(content.suggestions).toContain(
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
);
expect(content.suggestions).toContain(
'The stateful variable "count2" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
);
});
it(`should not add a suggestion for variables that are not assigned within an effect`, () => {
const content = run_autofixers_on_code(`
<script>
const count = ${init}(0);
</script>
<button onclick={() => count = 43}>Increment</button>
`);
expect(content.suggestions).not.toContain(
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
);
});
it("should not add a suggestions for variables that are assigned within an effect but aren't stateful", () => {
const content = run_autofixers_on_code(`
<script>
const count = 0;
$effect(() => {
count = 43;
});
</script>`);
expect(content.suggestions).not.toContain(
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
);
});
it(`should add a suggestion for variables that are assigned within an effect with an update`, () => {
const content = run_autofixers_on_code(`
<script>
let count = ${init}(0);
$effect(() => {
count++;
});
</script>
`);
expect(content.suggestions).toContain(
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
);
});
it(`should add a suggestion for variables that are mutated within an effect`, () => {
const content = run_autofixers_on_code(`
<script>
let count = ${init}({ value: 0 });
$effect(() => {
count.value = 42;
});
</script>
`);
expect(content.suggestions).toContain(
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
);
});
it(`should add a suggestion for variables that are mutated within an effect.pre`, () => {
const content = run_autofixers_on_code(`
<script>
let count = ${init}({ value: 0 });
$effect.pre(() => {
count.value = 42;
});
</script>
`);
expect(content.suggestions).toContain(
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
);
});
});
it('should add a suggestion when calling a function inside an effect', () => {
it(`should add suggestions when assigning to a stateful variable inside an effect`, () => {
const content = run_autofixers_on_code(`
<script>
import { fetch_data } from './data.js';
const count = $state(0);
$effect(() => {
fetch_data();
count = 43;
});
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are calling the function \`fetch_data\` 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.`,
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
);
});
it('should add a suggestion when calling a function inside an effect (with non identifier callee)', () => {
it(`should add a suggestion for each variable assigned within an effect`, () => {
const content = run_autofixers_on_code(`
<script>
import { fetch_data } from './data.js';
const count = $state(0);
const count2 = $state(0);
$effect(() => {
fetch_data.fetch();
count = 43;
count2 = 44;
});
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions.length).toBeGreaterThanOrEqual(2);
expect(content.suggestions).toContain(
`You are calling a function 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.`,
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
);
expect(content.suggestions).toContain(
'The stateful variable "count2" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
);
});
it(`should not add a suggestion for variables that are not assigned within an effect`, () => {
const content = run_autofixers_on_code(`
<script>
const count = $state(0);
</script>
<button onclick={() => count = 43}>Increment</button>
`);
expect(content.suggestions).not.toContain(
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
);
});
it("should not add a suggestions for variables that are assigned within an effect but aren't stateful", () => {
const content = run_autofixers_on_code(`
<script>
const count = 0;
$effect(() => {
count = 43;
});
</script>`);
expect(content.suggestions).not.toContain(
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
);
});
it(`should add a suggestion for variables that are assigned within an effect with an update`, () => {
const content = run_autofixers_on_code(`
<script>
let count = $state(0);
$effect(() => {
count++;
});
</script>
`);
expect(content.suggestions).toContain(
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
);
});
it(`should add a suggestion for variables that are mutated within an effect`, () => {
const content = run_autofixers_on_code(`
<script>
let count = $state({ value: 0 });
$effect(() => {
count.value = 42;
});
</script>
`);
expect(content.suggestions).toContain(
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
);
});
});
with_possible_inits('($init)', ({ init }) => {
describe.each([{ method: 'set' }, { method: 'update' }])(
'wrong_property_access_state ($method)',
({ method }) => {
it(`should add suggestions when using .${method}() on a stateful variable with a literal init`, () => {
const content = run_autofixers_on_code(`
describe.each([{ method: 'set' }, { method: 'update' }])(
'wrong_property_access_state ($method)',
({ method }) => {
it(`should add suggestions when using .${method}() on a stateful variable with a literal init`, () => {
const content = run_autofixers_on_code(`
<script>
const count = ${init}(0);
const count = $state(0);
function update_count() {
count.${method}(43);
}
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them.`,
);
});
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them.`,
);
});
it(`should add suggestions when using .${method}() on a stateful variable with an array init`, () => {
const content = run_autofixers_on_code(`
it(`should add suggestions when using .${method}() on a stateful variable with an array init`, () => {
const content = run_autofixers_on_code(`
<script>
const count = ${init}([0]);
const count = $state([0]);
function update_count() {
count.${method}([1]);
}
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them.`,
);
});
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them.`,
);
});
it(`should add suggestions when using .${method}() on a stateful variable with conditional if it's not sure if the method could actually be present on the variable (${init}({}))`, () => {
const content = run_autofixers_on_code(`
it(`should add suggestions when using .${method}() on a stateful variable with conditional if it's not sure if the method could actually be present on the variable ($state({}))`, () => {
const content = run_autofixers_on_code(`
<script>
const count = ${init}({ value: 0 });
const count = $state({ value: 0 });
function update_count() {
count.${method}({ value: 43 });
}
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them. However I can't verify if "count" is a state variable of an object or a class with a "${method}" method on it. Please verify that before updating the code to use a normal assignment`,
);
});
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them. However I can't verify if "count" is a state variable of an object or a class with a "${method}" method on it. Please verify that before updating the code to use a normal assignment`,
);
});
it(`should add suggestions when using .${method}() on a stateful variable with conditional if it's not sure if the method could actually be present on the variable (${init}(new Class()))`, () => {
const content = run_autofixers_on_code(`
it(`should add suggestions when using .${method}() on a stateful variable with conditional if it's not sure if the method could actually be present on the variable ($state(new Class()))`, () => {
const content = run_autofixers_on_code(`
<script>
const count = ${init}(new Class());
const count = $state(new Class());
function update_count() {
count.${method}(new Class());
}
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them. However I can't verify if "count" is a state variable of an object or a class with a "${method}" method on it. Please verify that before updating the code to use a normal assignment`,
);
});
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them. However I can't verify if "count" is a state variable of an object or a class with a "${method}" method on it. Please verify that before updating the code to use a normal assignment`,
);
});
it(`should add suggestions when using .${method}() on a stateful variable with conditional if it's not sure if the method could actually be present on the variable (${init}(variable_name))`, () => {
const content = run_autofixers_on_code(`
it(`should add suggestions when using .${method}() on a stateful variable with conditional if it's not sure if the method could actually be present on the variable ($state(variable_name))`, () => {
const content = run_autofixers_on_code(`
<script>
const { init } = $props();
const count = ${init}(init);
const count = $state(init);
function update_count() {
count.${method}(43);
}
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them. However I can't verify if "count" is a state variable of an object or a class with a "${method}" method on it. Please verify that before updating the code to use a normal assignment`,
);
});
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them. However I can't verify if "count" is a state variable of an object or a class with a "${method}" method on it. Please verify that before updating the code to use a normal assignment`,
);
});
it(`should not add suggestions when using .${method} on a stateful variable if it's not a method call`, () => {
const content = run_autofixers_on_code(`
it(`should not add suggestions when using .${method} on a stateful variable if it's not a method call`, () => {
const content = run_autofixers_on_code(`
<script>
const count = ${init}({});
const count = $state({});
function update_count() {
console.log(count.${method});
}
</script>`);
expect(content.suggestions).not.toContain(
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them. However I can't verify if "count" is a state variable of an object or a class with a "${method}" method on it. Please verify that before updating the code to use a normal assignment`,
);
});
},
);
expect(content.suggestions).not.toContain(
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them. However I can't verify if "count" is a state variable of an object or a class with a "${method}" method on it. Please verify that before updating the code to use a normal assignment`,
);
});
},
);
describe.each([{ property: '$' }])(
'wrong_property_access_state property ($property)',
async ({ property }) => {
it(`should add suggestions when reading .${property} on a stateful variable with a literal init`, () => {
const content = run_autofixers_on_code(`
describe.each([{ property: '$' }])(
'wrong_property_access_state property ($property)',
async ({ property }) => {
it(`should add suggestions when reading .${property} on a stateful variable with a literal init`, () => {
const content = run_autofixers_on_code(`
<script>
const count = ${init}(0);
const count = $state(0);
function read_count() {
count.${property};
}
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to read the stateful variable "count" using "${property}". stateful variables should be read just by accessing them like normal variable, do not use properties to read them.`,
);
});
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to read the stateful variable "count" using "${property}". stateful variables should be read just by accessing them like normal variable, do not use properties to read them.`,
);
});
it(`should add suggestions when reading .${property} on a stateful variable with an array init`, () => {
const content = run_autofixers_on_code(`
it(`should add suggestions when reading .${property} on a stateful variable with an array init`, () => {
const content = run_autofixers_on_code(`
<script>
const count = ${init}([1]);
const count = $state([1]);
function read_count() {
count.${property};
}
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to read the stateful variable "count" using "${property}". stateful variables should be read just by accessing them like normal variable, do not use properties to read them.`,
);
});
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to read the stateful variable "count" using "${property}". stateful variables should be read just by accessing them like normal variable, do not use properties to read them.`,
);
});
it(`should add suggestions when reading .${property} on a stateful variable with conditional if it's not sure if the property could actually be present on the variable (${init}({}))`, () => {
const content = run_autofixers_on_code(`
it(`should add suggestions when reading .${property} on a stateful variable with conditional if it's not sure if the property could actually be present on the variable ($state({}))`, () => {
const content = run_autofixers_on_code(`
<script>
const count = ${init}({ value: 0 });
const count = $state({ value: 0 });
function read_count() {
count.${property};
}
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to read the stateful variable "count" using "${property}". stateful variables should be read just by accessing them like normal variable, do not use properties to read them. However I can't verify if "count" is a state variable of an object or a class with a "${property}" property on it. Please verify that before updating the code to use a normal access`,
);
});
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to read the stateful variable "count" using "${property}". stateful variables should be read just by accessing them like normal variable, do not use properties to read them. However I can't verify if "count" is a state variable of an object or a class with a "${property}" property on it. Please verify that before updating the code to use a normal access`,
);
});
it(`should add suggestions when reading .${property} on a stateful variable with conditional if it's not sure if the property could actually be present on the variable (${init}(new Class()))`, () => {
const content = run_autofixers_on_code(`
it(`should add suggestions when reading .${property} on a stateful variable with conditional if it's not sure if the property could actually be present on the variable ($state(new Class()))`, () => {
const content = run_autofixers_on_code(`
<script>
const count = ${init}(new Class());
const count = $state(new Class());
function read_count() {
count.${property};
}
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to read the stateful variable "count" using "${property}". stateful variables should be read just by accessing them like normal variable, do not use properties to read them. However I can't verify if "count" is a state variable of an object or a class with a "${property}" property on it. Please verify that before updating the code to use a normal access`,
);
});
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to read the stateful variable "count" using "${property}". stateful variables should be read just by accessing them like normal variable, do not use properties to read them. However I can't verify if "count" is a state variable of an object or a class with a "${property}" property on it. Please verify that before updating the code to use a normal access`,
);
});
it(`should add suggestions when reading .${property} on a stateful variable with conditional if it's not sure if the property could actually be present on the variable (${init}(variable_name))`, () => {
const content = run_autofixers_on_code(`
it(`should add suggestions when reading .${property} on a stateful variable with conditional if it's not sure if the property could actually be present on the variable ($state(variable_name))`, () => {
const content = run_autofixers_on_code(`
<script>
const { init } = $props();
const count = ${init}(init);
const count = $state(init);
function read_count() {
count.${property};
}
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to read the stateful variable "count" using "${property}". stateful variables should be read just by accessing them like normal variable, do not use properties to read them. However I can't verify if "count" is a state variable of an object or a class with a "${property}" property on it. Please verify that before updating the code to use a normal access`,
);
});
},
);
});
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to read the stateful variable "count" using "${property}". stateful variables should be read just by accessing them like normal variable, do not use properties to read them. However I can't verify if "count" is a state variable of an object or a class with a "${property}" property on it. Please verify that before updating the code to use a normal access`,
);
});
},
);
describe('imported_runes', () => {
describe.each([{ source: 'svelte' }, { source: 'svelte/runes' }])(
@@ -412,19 +353,6 @@ describe('add_autofixers_issues', () => {
});
},
);
describe.each(dollarless_runes)('importing $rune from external lib', ({ rune }) => {
it(`should not add suggestions when importing from packages that are not svelte`, () => {
const content = run_autofixers_on_code(`
<script>
import { ${rune} } from 'svelte-something-something';
</script>`);
expect(content.suggestions).not.toContain(
`You are importing "${rune}" from "svelte-something-something". This is not necessary, all runes are globally available. Please remove this import and use "$${rune}" directly.`,
);
});
});
});
describe('derived_with_function', () => {
@@ -527,229 +455,4 @@ describe('add_autofixers_issues', () => {
);
});
});
describe('suggest_attachments', () => {
describe('bind:this', () => {
it('should add suggestions when using bind:this on an element', () => {
const content = run_autofixers_on_code(`
<script>
let a = $state();
</script>
<a bind:this={a} />`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
'The usage of `bind:this` can often be replaced with an easier to read `action` or even better an `attachment`. Consider using the latter if possible.',
);
});
it('should not add suggestions when using bind:this on a component', () => {
const content = run_autofixers_on_code(`
<script>
import Child from './Child.svelte';
let a = $state();
</script>
<Child bind:this={a} />`);
expect(content.suggestions).not.toContain(
'The usage of `bind:this` can often be replaced with an easier to read `action` or even better an `attachment`. Consider using the latter if possible.',
);
});
it('should not add suggestions when using bind:this on a component nested in an element', () => {
const content = run_autofixers_on_code(`
<script>
import Child from './Child.svelte';
let a = $state();
</script>
<div>
<Child bind:this={a} />
</div>`);
expect(content.suggestions).not.toContain(
'The usage of `bind:this` can often be replaced with an easier to read `action` or even better an `attachment`. Consider using the latter if possible.',
);
});
it('should add suggestions but not suggest attachments when using bind:this on an element and the desired svelte version is 4', () => {
const content = run_autofixers_on_code(
`
<script>
let a;
</script>
<a bind:this={a} />`,
4,
);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
'The usage of `bind:this` can often be replaced with an easier to read `action`. Consider using the latter if possible.',
);
});
});
describe('use:', () => {
it('should add suggestions when using use: on an element and the action is declared as a function', () => {
const content = run_autofixers_on_code(
`<script>
function my_action(node) {
// do something with the node
}
</script>
<a use:my_action />`,
);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
'Consider using an `attachment` instead of an `action` for "my_action".',
);
});
it('should add suggestions when using use: on an element and the action is declared as a variable', () => {
const content = run_autofixers_on_code(
`<script>
const my_action = (node) => {
// do something with the node
}
</script>
<a use:my_action />`,
);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
'Consider using an `attachment` instead of an `action` for "my_action".',
);
});
it('should add suggestions when using use: on an element and the action is declared as an object', () => {
const content = run_autofixers_on_code(
`<script>
const my_action = {
action: (node) => {
// do something with the node
}
};
</script>
<a use:my_action.action />`,
);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
'Consider using an `attachment` instead of an `action` for "my_action".',
);
});
it('should not add suggestions when using use: on an element and the desired svelte version is 4', () => {
const content = run_autofixers_on_code(
`<script>
function my_action(node) {
// do something with the node
}
</script>
<a use:my_action />`,
4,
);
expect(content.suggestions).not.toContain(
'Consider using an `attachment` instead of an `action` for "my_action".',
);
});
it('should not add suggestions when using use: on an element and the action comes from an import', () => {
const content = run_autofixers_on_code(
`<script>
import { my_action } from './actions.js';
</script>
<a use:my_action />`,
);
expect(content.suggestions).not.toContain(
'Consider using an `attachment` instead of an `action` for "my_action".',
);
});
it('should not add suggestions when using use: on an element and the action comes from the props', () => {
const content = run_autofixers_on_code(
`<script>
const { my_action } = $props();
</script>
<a use:my_action />`,
);
expect(content.suggestions).not.toContain(
'Consider using an `attachment` instead of an `action` for "my_action".',
);
});
it('should not add suggestions when using use: on an element and the action comes from a global variable', () => {
const content = run_autofixers_on_code(`<a use:my_action />`);
expect(content.suggestions).not.toContain(
'Consider using an `attachment` instead of an `action` for "my_action".',
);
});
});
});
describe('read_state_with_dollar', () => {
with_possible_inits('($init)', ({ init }) => {
it(`should add an issue when reading a stateful variable initialized with ${init} like if it was a store`, () => {
const content = run_autofixers_on_code(`<script>
let x = ${init}(()=> 43);
$x;
</script>
`);
expect(content.issues).toContain(
`You are reading the stateful variable "$x" with a "$" prefix. Stateful variables are not stores and should be read without the "$". Please read it as a normal variable "x"`,
);
});
});
it(`should not add an issue when reading an imported variable like if it was a store`, () => {
const content = run_autofixers_on_code(`<script>
import { x } from "./my-stores.ts";
$x;
</script>
`);
expect(content.issues).not.toContain(
`You are reading the stateful variable "$x" with a "$" prefix. Stateful variables are not stores and should be read without the "$". Please read it as a normal variable "x"`,
);
});
it(`should not add an issue when reading a non-stateful variable like if it was a store`, () => {
const content = run_autofixers_on_code(`<script>
import { writable } from "svelte/store";
const x = writable(0);
$x;
</script>
`);
expect(content.issues).not.toContain(
`You are reading the stateful variable "$x" with a "$" prefix. Stateful variables are not stores and should be read without the "$". Please read it as a normal variable "x"`,
);
});
it(`should not add an issue when reading a prop like if it was a store`, () => {
const content = run_autofixers_on_code(`<script>
const { x } = $props();
$x;
</script>
`);
expect(content.issues).not.toContain(
`You are reading the stateful variable "$x" with a "$" prefix. Stateful variables are not stores and should be read without the "$". Please read it as a normal variable "x"`,
);
});
});
});

View File

@@ -1,6 +1,4 @@
import { compile as compile_component, compileModule } from 'svelte/compiler';
import { extname } from 'path';
import ts from 'ts-blank-space';
import { compile } from 'svelte/compiler';
export function add_compile_issues(
content: { issues: string[]; suggestions: string[] },
@@ -8,21 +6,6 @@ export function add_compile_issues(
desired_svelte_version: number,
filename = 'Component.svelte',
) {
let compile = compile_component;
const extension = extname(filename);
if (extension !== '.svelte') {
compile = compileModule;
// compile module doesn't accept .ts files so we need to transpile them first with ts-blank-space
// a fast and lightweight typescript transpiler that can strips types replacing them with white spaces
// so the code positions are not affected
if (extension === '.ts') {
code = ts(code, (node) => {
content.issues.push(
`The provided file is a module but it contains invalid TypeScript code: ${node.getText()} at ${node.getStart()}`,
);
});
}
}
const compilation_result = compile(code, {
filename: filename || 'Component.svelte',
generate: false,

View File

@@ -12,7 +12,7 @@ function base_config(svelte_config: Config): ESLint.Options['baseConfig'] {
return [
...svelte.configs.recommended,
{
files: ['*.svelte', '*.svelte.ts', '*.svelte.js'],
files: ['*.svelte'],
rules: {
'no-self-assign': 'warn',
'svelte/infinite-reactive-loop': 'warn',

View File

@@ -1,10 +1,4 @@
import type {
AssignmentExpression,
CallExpression,
Identifier,
Node,
UpdateExpression,
} from 'estree';
import type { AssignmentExpression, Identifier, Node, UpdateExpression } from 'estree';
import type { Autofixer, AutofixerState } from './index.js';
import { left_most_id } from '../ast/utils.js';
import type { AST } from 'svelte-eslint-parser';
@@ -17,17 +11,25 @@ function run_if_in_effect(
) {
const in_effect = path.findLast(
(node) =>
node.type === 'CallExpression' && state.parsed.is_rune(node, ['$effect', '$effect.pre']),
node.type === 'CallExpression' &&
node.callee.type === 'Identifier' &&
node.callee.name === '$effect',
);
if (in_effect) {
to_run();
if (
in_effect &&
in_effect.type === 'CallExpression' &&
(in_effect.callee.type === 'Identifier' || in_effect.callee.type === 'MemberExpression')
) {
if (state.parsed.is_rune(in_effect, ['$effect', '$effect.pre'])) {
to_run();
}
}
}
function assign_or_update_visitor(
function visitor(
node: UpdateExpression | AssignmentExpression,
{ state, path, next }: Context<Node | AST.SvelteNode, AutofixerState>,
{ state, path }: Context<Node | AST.SvelteNode, AutofixerState>,
) {
run_if_in_effect(path, state, () => {
function check_if_stateful_id(id: Identifier) {
@@ -37,7 +39,7 @@ function assign_or_update_visitor(
const init = definition.node.init;
if (
init?.type === 'CallExpression' &&
state.parsed.is_rune(init, ['$state', '$state.raw', '$derived', '$derived.by'])
state.parsed.is_rune(init, ['$state', '$state.raw'])
) {
state.output.suggestions.push(
`The stateful variable "${id.name}" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.`,
@@ -56,25 +58,9 @@ function assign_or_update_visitor(
}
}
});
next();
}
function call_expression_visitor(
node: CallExpression,
{ state, path, next }: Context<Node | AST.SvelteNode, AutofixerState>,
) {
run_if_in_effect(path, state, () => {
const function_name =
node.callee.type === 'Identifier' ? `the function \`${node.callee.name}\`` : 'a function';
state.output.suggestions.push(
`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.`,
);
});
next();
}
export const assign_in_effect: Autofixer = {
UpdateExpression: assign_or_update_visitor,
AssignmentExpression: assign_or_update_visitor,
CallExpression: call_expression_visitor,
UpdateExpression: visitor,
AssignmentExpression: visitor,
};

View File

@@ -6,7 +6,7 @@ const dollarless_runes = base_runes.map((r) => r.replace('$', ''));
export const imported_runes: Autofixer = {
ImportDeclaration(node, { state, next }) {
const source = (node.source.value || node.source.raw?.slice(1, -1))?.toString();
if (source && (source === 'svelte' || source.startsWith('svelte/'))) {
if (source && source.startsWith('svelte')) {
for (const specifier of node.specifiers) {
const id =
specifier.type === 'ImportDefaultSpecifier'

View File

@@ -16,5 +16,3 @@ export * from './wrong-property-access-state.js';
export * from './imported-runes.js';
export * from './derived-with-function.js';
export * from './use-runes-instead-of-store.js';
export * from './suggest-attachments.js';
export * from './read-state-with-dollar.js';

View File

@@ -1,22 +0,0 @@
import type { Autofixer } from './index.js';
export const read_state_with_dollar: Autofixer = {
Identifier(node, { state }) {
if (node.name.startsWith('$')) {
const reference = state.parsed.find_reference_by_id(node);
if (reference && reference.resolved && reference.resolved.defs[0]?.node?.init) {
const is_state = state.parsed.is_rune(reference.resolved.defs[0].node.init, [
'$state',
'$state.raw',
'$derived',
'$derived.by',
]);
if (is_state) {
state.output.issues.push(
`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)}"`,
);
}
}
}
},
};

View File

@@ -1,46 +0,0 @@
import type { Identifier } from 'estree';
import type { Autofixer } from './index.js';
import { left_most_id } from '../ast/utils.js';
export const suggest_attachments: Autofixer = {
SvelteDirective(node, { state, next, path }) {
if (node.kind === 'Binding' && node.key.name.name === 'this') {
const parent_element = path.findLast((p) => p.type === 'SvelteElement');
if (parent_element?.kind === 'html' && parent_element.startTag.attributes.includes(node)) {
let better_an_attachment = ` or even better an \`attachment\``;
if (state.desired_svelte_version === 4) {
better_an_attachment = ``;
}
state.output.suggestions.push(
`The usage of \`bind:this\` can often be replaced with an easier to read \`action\`${better_an_attachment}. Consider using the latter if possible.`,
);
}
} else if (node.kind === 'Action' && state.desired_svelte_version === 5) {
let id: Identifier | null = null;
if (node.key.name.type === 'Identifier') {
id = node.key.name;
} else if (node.key.name.type === 'MemberExpression') {
id = left_most_id(node.key.name);
}
if (id) {
const reference = state.parsed.find_reference_by_id(id);
const definition = reference?.resolved?.defs[0];
if (
definition &&
(definition.type === 'Variable' ||
!(definition.type === 'ImportBinding' || definition.type === 'Parameter')) &&
!(
definition.type === 'Variable' &&
definition.node.init?.type === 'CallExpression' &&
state.parsed.is_rune(definition.node.init, ['$props'])
)
) {
state.output.suggestions.push(
`Consider using an \`attachment\` instead of an \`action\` for "${id.name}".`,
);
}
}
}
next();
},
};

View File

@@ -22,7 +22,7 @@ export const wrong_property_access_state: Autofixer = {
const init = definition.node.init;
if (
init?.type === 'CallExpression' &&
state.parsed.is_rune(init, ['$state', '$state.raw', '$derived', '$derived.by'])
state.parsed.is_rune(init, ['$state', '$state.raw'])
) {
let suggestion = is_property
? `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.`

View File

@@ -1 +1 @@
export { setup_svelte_task } from './svelte-task.js';
export * from './svelte-task.js';

View File

@@ -1,19 +1,31 @@
import type { SvelteMcp } from '../../index.js';
import * as v from 'valibot';
import { format_sections_list } from '../../utils.js';
import { icons } from '../../icons/index.js';
/**
* Function that actually generates the prompt string. You can use this in the MCP server handler to generate the prompt, it can accept arguments
* if needed (it will always be invoked manually so it's up to you to provide the arguments).
*/
function svelte_task(available_docs: string, task: string) {
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:
<available-docs>
export function setup_svelte_task(server: SvelteMcp) {
server.prompt(
{
name: 'svelte-task-prompt',
title: 'Svelte Task Prompt',
description:
'Use this Prompt to ask for any svelte related task. It will automatically instruct the LLM on how to best use the autofixer and how to query for documentation pages.',
schema: v.object({
task: v.pipe(v.string(), v.description('The task to be performed')),
}),
},
async ({ task }) => {
// TODO: implement logic to fetch the available docs paths to return in the prompt
const available_docs: string[] = [];
${available_docs}
</available-docs>
return {
messages: [
{
role: 'user',
content: {
type: 'text',
text: `You are a Svelte expert tasked to build components and utilities for Svelte developers. If you need documentation for anything related to Svelte you can invoke the tool \`get_documentation\` with one of the following paths:
<available-docs-paths>
${JSON.stringify(available_docs, null, 2)}
</available-docs-paths>
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.
@@ -23,60 +35,8 @@ This is the task you will work on:
${task}
</task>
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.`;
}
/**
* 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
* 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
* be in the format [NAME_OF_THE_ARGUMENT] to signal the user that it can substitute it.
*
* The name NEEDS to be `generate_for_docs`.
*/
export async function generate_for_docs() {
const available_docs = await format_sections_list();
return svelte_task(available_docs, '[YOUR TASK HERE]');
}
/**
* Human readable description of what the prompt does. It will be included in the documentation.
*
* The name NEEDS to be `docs_description`.
*/
export const docs_description =
'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.';
export function setup_svelte_task(server: SvelteMcp) {
server.prompt(
{
name: 'svelte-task',
title: 'Svelte-Task-Prompt',
description:
'Use this Prompt to ask for any svelte related task. It will automatically instruct the LLM on how to best use the autofixer and how to query for documentation pages.',
schema: v.object({
task: v.pipe(v.string(), v.description('The task to be performed')),
}),
complete: {
task() {
return {
completion: {
values: [''],
},
};
},
},
icons,
},
async ({ task }) => {
const available_docs = await format_sections_list();
return {
messages: [
{
role: 'user',
content: {
type: 'text',
text: svelte_task(available_docs, task),
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.
`,
},
},
],

View File

@@ -1,66 +0,0 @@
import type { SvelteMcp } from '../../index.js';
import { get_sections, fetch_with_timeout } from '../../utils.js';
import { icons } from '../../icons/index.js';
export async function list_sections(server: SvelteMcp) {
const sections = await get_sections();
server.template(
{
name: 'Svelte-Doc-Section',
description: 'A single documentation section',
list() {
return sections.map((section) => {
const section_name = section.slug;
const resource_name = section_name;
const resource_uri = `svelte://${section_name}.md`;
return {
name: resource_name,
description: section.use_cases,
uri: resource_uri,
title: section.title,
};
});
},
complete: {
slug: (query) => {
const values = sections
.reduce<string[]>((acc, section) => {
const section_name = section.slug;
const resource_name = section_name;
if (section_name.includes(query.toLowerCase())) {
acc.push(resource_name);
}
return acc;
}, [])
// there's a hard limit of 100 for completions
.slice(0, 100);
return {
completion: {
values,
},
};
},
},
uri: 'svelte://{/slug*}.md',
icons,
},
async (uri, { slug }) => {
const section = sections.find((section) => {
return slug === section.slug;
});
if (!section) throw new Error(`Section not found: ${slug}`);
const response = await fetch_with_timeout(section.url);
const content = await response.text();
return {
contents: [
{
uri,
type: 'text',
text: content,
},
],
};
},
);
}

View File

@@ -1 +1 @@
export * from './doc-section.js';
export * from './list-sections.js';

View File

@@ -0,0 +1,24 @@
import type { SvelteMcp } from '../../index.js';
export function list_sections(server: SvelteMcp) {
server.resource(
{
name: 'list-sections',
description:
'The list of all the available Svelte 5 and SvelteKit documentation sections in a structured format.',
uri: 'svelte://list-sections',
title: 'Svelte Documentation Section',
},
async (uri) => {
return {
contents: [
{
uri,
type: 'text',
text: 'resource list-sections called',
},
],
};
},
);
}

View File

@@ -1,26 +1,22 @@
import type { SvelteMcp } from '../../index.js';
import * as v from 'valibot';
import { get_sections, fetch_with_timeout, format_sections_list } from '../../utils.js';
import { SECTIONS_LIST_INTRO, SECTIONS_LIST_OUTRO } from './prompts.js';
import { icons } from '../../icons/index.js';
export function get_documentation(server: SvelteMcp) {
server.tool(
{
name: 'get-documentation',
description:
'Retrieves full documentation content for Svelte 5 or SvelteKit sections. Supports flexible search by title (e.g., "$state", "routing") or file path (e.g., "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.',
'Retrieves full documentation content for Svelte 5 or SvelteKit sections. Supports flexible search by title (e.g., "$state", "routing") or file path (e.g., "docs/svelte/state.md"). Can accept a single section name or an array of sections. Before running this, make sure to analyze the users query, as well as the output from list_sections (which should be called first). Then ask for ALL relevant sections the user might require. For example, if the user asks to build anything interactive, you will need to fetch all relevant runes, and so on.',
schema: v.object({
section: v.pipe(
v.union([v.string(), v.array(v.string())]),
v.description(
'The section name(s) to retrieve. Can search by title (e.g., "$state", "load functions") or file path (e.g., "cli/overview"). Supports single string and array of strings',
'The section name(s) to retrieve. Can search by title (e.g., "$state", "load functions") or file path (e.g., "docs/svelte/state.md"). Supports single string and array of strings',
),
),
}),
icons,
},
async ({ section }) => {
({ section }) => {
let sections: string[];
if (Array.isArray(section)) {
@@ -46,69 +42,13 @@ export function get_documentation(server: SvelteMcp) {
sections = [];
}
const available_sections = await get_sections();
const settled_results = await Promise.allSettled(
sections.map(async (requested_section) => {
const matched_section = available_sections.find(
(s) =>
s.title.toLowerCase() === requested_section.toLowerCase() ||
s.slug === requested_section ||
s.url === requested_section,
);
if (matched_section) {
try {
const response = await fetch_with_timeout(matched_section.url);
if (response.ok) {
const content = await response.text();
return { success: true, content: `## ${matched_section.title}\n\n${content}` };
} else {
return {
success: false,
content: `## ${matched_section.title}\n\nError: Could not fetch documentation (HTTP ${response.status})`,
};
}
} catch (error) {
return {
success: false,
content: `## ${matched_section.title}\n\nError: Failed to fetch documentation - ${error}`,
};
}
} else {
return {
success: false,
content: `## ${requested_section}\n\nError: Section not found.`,
};
}
}),
);
const results = settled_results.map((result) => {
if (result.status === 'fulfilled') {
return result.value;
} else {
return {
success: false,
content: `Error: Couldn't fetch - ${result.reason}`,
};
}
});
const has_any_success = results.some((result) => result.success);
let final_text = results.map((r) => r.content).join('\n\n---\n\n');
if (!has_any_success) {
const formatted_sections = await format_sections_list();
final_text += `\n\n---\n\n${SECTIONS_LIST_INTRO}\n\n${formatted_sections}\n\n${SECTIONS_LIST_OUTRO}`;
}
const sections_list = sections.length > 0 ? sections.join(', ') : 'no sections';
return {
content: [
{
type: 'text',
text: final_text,
text: `called for sections: ${sections_list}`,
},
],
};

View File

@@ -1,24 +1,18 @@
import type { SvelteMcp } from '../../index.js';
import { format_sections_list } from '../../utils.js';
import { SECTIONS_LIST_INTRO, SECTIONS_LIST_OUTRO } from './prompts.js';
import { icons } from '../../icons/index.js';
export function list_sections(server: SvelteMcp) {
server.tool(
{
name: 'list-sections',
description:
'Lists all available Svelte 5 and SvelteKit documentation sections in a structured format. 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.',
icons,
'Lists all available Svelte 5 and SvelteKit documentation sections in a structured format. Returns sections as a list of "* title: [section_title], path: [file_path]" - you can use either the title or path when querying a specific section via the get_documentation tool. Always run list_sections first for any query related to Svelte development to discover available content.',
},
async () => {
const formatted_sections = await format_sections_list();
() => {
return {
content: [
{
type: 'text',
text: `${SECTIONS_LIST_INTRO}\n\n${formatted_sections}\n\n${SECTIONS_LIST_OUTRO}`,
text: 'tool list_sections called',
},
],
};

View File

@@ -1,7 +1,5 @@
import type { SvelteMcp } from '../../index.js';
import * as v from 'valibot';
import { icons } from '../../icons/index.js';
import { createUIResource } from '@mcp-ui/server';
async function compress_and_encode_text(input: string) {
const reader = new Blob([input]).stream().pipeThrough(new CompressionStream('gzip')).getReader();
@@ -56,7 +54,6 @@ export function playground_link(server: SvelteMcp) {
outputSchema: v.object({
url: v.string(),
}),
icons,
},
async ({ files, name, tailwind }) => {
const playground_base = new URL('https://svelte.dev/playground');
@@ -101,26 +98,12 @@ export function playground_link(server: SvelteMcp) {
url: playground_base.toString(),
};
// use the embed path to have a cleaner UI for mcp-ui
playground_base.pathname = '/playground/embed';
return {
content: [
{
type: 'text',
text: JSON.stringify(content),
},
createUIResource({
uri: 'ui://svelte/playground-link',
content: {
type: 'externalUrl',
iframeUrl: playground_base.toString(),
},
uiMetadata: {
'preferred-frame-size': ['100%', '1200px'],
},
encoding: 'text',
}),
],
structuredContent: content,
};

View File

@@ -1,5 +0,0 @@
export const SECTIONS_LIST_INTRO =
'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:';
export const SECTIONS_LIST_OUTRO =
"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).";

View File

@@ -1,94 +0,0 @@
import { beforeEach, describe, expect, it } from 'vitest';
import { server } from '../../index.js';
/**
* Small utility to create a JSON-RPC request without having to always specify as const
*/
function request<const T>(request: T) {
return request;
}
async function autofixer_tool_call(code: string, is_error = false, desired_svelte_version = 5) {
const result = await server.receive({
jsonrpc: '2.0',
id: 2,
method: 'tools/call',
params: {
name: 'svelte-autofixer',
arguments: {
code,
desired_svelte_version,
filename: 'App.svelte',
},
},
});
expect(result).toBeDefined();
expect(result.result).toBeDefined();
if (is_error) {
return result.result;
}
expect(result.result.structuredContent).toBeDefined();
return result.result.structuredContent;
}
describe('svelte-autofixer tool', () => {
beforeEach(async () => {
const initialize_request = request({
jsonrpc: '2.0',
id: 1,
method: 'initialize',
params: {
protocolVersion: '2025-06-18',
capabilities: {
roots: { listChanged: true },
},
clientInfo: {
name: 'test-client',
version: '1.0.0',
},
},
});
await server.receive(initialize_request, {
sessionId: 'svelte-autofixer-session',
});
});
it('should add suggestions for js parse errors', async () => {
const content = await autofixer_tool_call(`<script>
$state count = 0;
</script>`);
expect(content.issues.length).toBeGreaterThan(0);
expect(content.suggestions).toContain(
"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)`.",
);
});
it('should add suggestions for css invalid identifier', async () => {
const content = await autofixer_tool_call(`<script>
let my_color = $state('red');
</script>
<style>
.my-class {
color: {my_color};
}
</style>`);
expect(content.issues.length).toBeGreaterThan(0);
expect(content.suggestions).toContain(
"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 `<div style:--color={my_color}></div>` and then use the variable as usual in CSS with `color: var(--color)`.",
);
});
it('should error in case the passed in version is different from 4 or 5', async () => {
const content = await autofixer_tool_call(`whatever`, true, 3);
expect(content.content).toBeDefined();
expect(content.content[0]).toBeDefined();
expect(content.content[0].text).toContain(
'The desired_svelte_version MUST be either 4 or 5 but received "3"',
);
});
});

View File

@@ -4,7 +4,6 @@ import * as v from 'valibot';
import { add_compile_issues } from '../../autofixers/add-compile-issues.js';
import { add_eslint_issues } from '../../autofixers/add-eslint-issues.js';
import { add_autofixers_issues } from '../../autofixers/add-autofixers-issues.js';
import { icons } from '../../icons/index.js';
export function svelte_autofixer(server: SvelteMcp) {
server.tool(
@@ -16,7 +15,7 @@ export function svelte_autofixer(server: SvelteMcp) {
schema: v.object({
code: v.string(),
desired_svelte_version: v.pipe(
v.union([v.string(), v.number()]),
v.union([v.literal(4), v.literal(5), v.literal('4'), v.literal('5')]),
v.description(
'The desired svelte version...if possible read this from the package.json of the user project, otherwise use some hint from the wording (if the user asks for runes it wants version 5). Default to 5 in case of doubt.',
),
@@ -39,32 +38,8 @@ export function svelte_autofixer(server: SvelteMcp) {
readOnlyHint: true,
openWorldHint: false,
},
icons,
},
async ({
code,
filename: filename_or_path,
desired_svelte_version: desired_svelte_version_unchecked,
}) => {
// we validate manually because some clients don't support union in the input schema (looking at you cursor)
const parsed_version = v.safeParse(
v.union([v.literal(4), v.literal(5), v.literal('4'), v.literal('5')]),
desired_svelte_version_unchecked,
);
if (parsed_version.success === false) {
return {
isError: true,
content: [
{
type: 'text',
text: `The desired_svelte_version MUST be either 4 or 5 but received "${desired_svelte_version_unchecked}"`,
},
],
};
}
const desired_svelte_version = parsed_version.output;
async ({ code, filename: filename_or_path, desired_svelte_version }) => {
const content: {
issues: string[];
suggestions: string[];
@@ -90,10 +65,6 @@ export function svelte_autofixer(server: SvelteMcp) {
content.suggestions.push(
"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)`.",
);
} else if (error.message.includes('css_expected_identifier')) {
content.suggestions.push(
"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 `<div style:--color={my_color}></div>` and then use the variable as usual in CSS with `color: var(--color)`.",
);
}
}

View File

@@ -1,14 +0,0 @@
export const icons = [
{
src: 'https://mcp.svelte.dev/logo.svg',
mimeType: 'image/svg+xml',
},
{
src: 'https://mcp.svelte.dev/logo.png',
mimeType: 'image/png',
},
{
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',
mimeType: 'image/png',
},
];

View File

@@ -3,15 +3,12 @@ import { McpServer } from 'tmcp';
import { setup_prompts, setup_resources, setup_tools } from './handlers/index.js';
import type { LibSQLDatabase } from 'drizzle-orm/libsql';
import type { Schema } from '@sveltejs/mcp-schema';
import { icons } from './icons/index.js';
export const server = new McpServer(
{
name: 'Svelte MCP',
version: '0.0.1',
description: 'The official Svelte MCP server implementation',
websiteUrl: 'https://mcp.svelte.dev',
icons,
},
{
adapter: new ValibotJsonSchemaAdapter(),

View File

@@ -1,56 +0,0 @@
import * as v from 'valibot';
import { documentation_sections_schema } from '../lib/schemas.js';
import summary_data from '../use_cases.json' with { type: 'json' };
export async function fetch_with_timeout(
url: string,
timeout_ms: number = 10000,
): Promise<Response> {
try {
const response = await fetch(url, { signal: AbortSignal.timeout(timeout_ms) });
return response;
} catch (error) {
if (error instanceof Error && error.name === 'AbortError') {
throw new Error(`Request timed out after ${timeout_ms}ms`);
}
throw error;
}
}
const summaries = (summary_data.summaries || {}) as Record<string, string>;
export async function get_sections() {
const sections = await fetch_with_timeout(
'https://svelte.dev/docs/experimental/sections.json',
).then((res) => res.json());
const validated_sections = v.safeParse(documentation_sections_schema, sections);
if (!validated_sections.success) return [];
const mapped_sections = Object.entries(validated_sections.output).map(([, section]) => {
const original_slug = section.slug;
const cleaned_slug = original_slug.startsWith('docs/')
? original_slug.slice('docs/'.length)
: original_slug;
return {
title: section.metadata.title,
use_cases:
section.metadata.use_cases ??
summaries[original_slug] ??
summaries[cleaned_slug] ??
'use title and path to estimate use case',
slug: cleaned_slug,
// Use original slug in URL to ensure it still works
url: `https://svelte.dev/${original_slug}/llms.txt`,
};
});
return mapped_sections;
}
export async function format_sections_list() {
const sections = await get_sections();
return sections
.map((s) => `- title: ${s.title}, use_cases: ${s.use_cases}, path: ${s.slug}`)
.join('\n');
}

View File

@@ -36,18 +36,8 @@ export function parse(code: string, file_path: string) {
}
return all_scopes;
}
// walking the ast will also walk all the tokens if we don't remove them so we return them separately
// 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)
const {
ast: { tokens, ...ast },
} = parsed;
// @ts-expect-error we also have to delete it from the object or the circular reference remains
delete parsed.ast.tokens;
return {
ast,
tokens,
ast: parsed.ast,
scope_manager: parsed.scopeManager as ScopeManager,
visitor_keys: parsed.visitorKeys,
get all_scopes() {

View File

@@ -1,176 +0,0 @@
{
"generated_at": "2025-10-02T20:29:23.410Z",
"model": "claude-sonnet-4-5-20250929",
"total_sections": 167,
"successful_summaries": 167,
"failed_summaries": 0,
"summaries": {
"docs/cli/overview": "project setup, creating new svelte apps, scaffolding, cli tools, initializing projects",
"docs/cli/faq": "project setup, initializing new svelte projects, troubleshooting cli installation, package manager configuration",
"docs/cli/sv-create": "project setup, starting new sveltekit app, initializing project, creating from playground, choosing project template",
"docs/cli/sv-add": "project setup, adding features to existing projects, integrating tools, testing setup, styling setup, authentication, database setup, deployment adapters",
"docs/cli/sv-check": "code quality, ci/cd pipelines, error checking, typescript projects, pre-commit hooks, finding unused css, accessibility auditing, production builds",
"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",
"docs/cli/devtools-json": "development setup, chrome devtools integration, browser-based editing, local development workflow, debugging setup",
"docs/cli/drizzle": "database setup, sql queries, orm integration, data modeling, postgresql, mysql, sqlite, server-side data access, database migrations, type-safe queries",
"docs/cli/eslint": "code quality, linting, error detection, project setup, code standards, team collaboration, typescript projects",
"docs/cli/lucia": "authentication, login systems, user management, registration pages, session handling, auth setup",
"docs/cli/mdsvex": "blog, content sites, markdown rendering, documentation sites, technical writing, cms integration, article pages",
"docs/cli/paraglide": "internationalization, multi-language sites, i18n, translation, localization, language switching, global apps, multilingual content",
"docs/cli/playwright": "browser testing, e2e testing, integration testing, test automation, quality assurance, ci/cd pipelines, testing user flows",
"docs/cli/prettier": "code formatting, project setup, code style consistency, team collaboration, linting configuration",
"docs/cli/storybook": "component development, design systems, ui library, isolated component testing, documentation, visual testing, component showcase",
"docs/cli/sveltekit-adapter": "deployment, production builds, hosting setup, choosing deployment platform, configuring adapters, static site generation, node server, vercel, cloudflare, netlify",
"docs/cli/tailwind": "project setup, styling, css framework, rapid prototyping, utility-first css, design systems, responsive design, adding tailwind to svelte",
"docs/cli/vitest": "testing, unit tests, component testing, test setup, quality assurance, ci/cd pipelines, test-driven development",
"docs/kit/introduction": "learning sveltekit, project setup, understanding framework basics, choosing between svelte and sveltekit, getting started with full-stack apps",
"docs/kit/creating-a-project": "project setup, starting new sveltekit app, initial development environment, first-time sveltekit users, scaffolding projects",
"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",
"docs/kit/project-structure": "project setup, understanding file structure, organizing code, starting new project, learning sveltekit basics",
"docs/kit/web-standards": "always, any sveltekit project, data fetching, forms, api routes, server-side rendering, deployment to various platforms",
"docs/kit/routing": "routing, navigation, multi-page apps, project setup, file structure, api endpoints, data loading, layouts, error pages, always",
"docs/kit/load": "data fetching, api calls, database queries, dynamic routes, page initialization, loading states, authentication checks, ssr data, form data, content rendering",
"docs/kit/form-actions": "forms, user input, data submission, authentication, login systems, user registration, progressive enhancement, validation errors",
"docs/kit/page-options": "prerendering static sites, ssr configuration, spa setup, client-side rendering control, url trailing slash handling, adapter deployment config, build optimization",
"docs/kit/state-management": "sveltekit, server-side rendering, ssr, state management, authentication, data persistence, load functions, context api, navigation, component lifecycle",
"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",
"docs/kit/building-your-app": "production builds, deployment preparation, build process optimization, adapter configuration, preview before deployment",
"docs/kit/adapters": "deployment, production builds, hosting setup, choosing deployment platform, configuring adapters",
"docs/kit/adapter-auto": "deployment, production builds, hosting setup, choosing deployment platform, ci/cd configuration",
"docs/kit/adapter-node": "deployment, production builds, node.js hosting, custom server setup, environment configuration, reverse proxy setup, docker deployment, systemd services",
"docs/kit/adapter-static": "static site generation, ssg, prerendering, deployment, github pages, spa mode, blogs, documentation sites, marketing sites",
"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",
"docs/kit/adapter-cloudflare": "deployment, cloudflare workers, cloudflare pages, hosting setup, production builds, serverless deployment, edge computing",
"docs/kit/adapter-cloudflare-workers": "deploying to cloudflare workers, cloudflare workers sites deployment, legacy cloudflare adapter, wrangler configuration, cloudflare platform bindings",
"docs/kit/adapter-netlify": "deployment, netlify hosting, production builds, serverless functions, edge functions, static site hosting",
"docs/kit/adapter-vercel": "deployment, vercel hosting, production builds, serverless functions, edge functions, isr, image optimization, environment variables",
"docs/kit/writing-adapters": "custom deployment, building adapters, unsupported platforms, adapter development, custom hosting environments",
"docs/kit/advanced-routing": "advanced routing, dynamic routes, file viewers, nested paths, custom 404 pages, url validation, route parameters, multi-level navigation",
"docs/kit/hooks": "authentication, logging, error tracking, request interception, api proxying, custom routing, internationalization, database initialization, middleware logic, session management",
"docs/kit/errors": "error handling, custom error pages, 404 pages, api error responses, production error logging, error tracking, type-safe errors",
"docs/kit/link-options": "routing, navigation, multi-page apps, performance optimization, link preloading, forms with get method, search functionality, focus management, scroll behavior",
"docs/kit/service-workers": "offline support, pwa, caching strategies, performance optimization, precaching assets, network resilience, progressive web apps",
"docs/kit/server-only-modules": "api keys, environment variables, sensitive data protection, backend security, preventing data leaks, server-side code isolation",
"docs/kit/snapshots": "forms, user input, preserving form data, multi-step forms, navigation state, preventing data loss, textarea content, input fields, comment systems, surveys",
"docs/kit/shallow-routing": "modals, dialogs, image galleries, overlays, history-driven ui, mobile-friendly navigation, photo viewers, lightboxes, drawer menus",
"docs/kit/observability": "performance monitoring, debugging, observability, tracing requests, production diagnostics, analyzing slow requests, finding bottlenecks, monitoring server-side operations",
"docs/kit/packaging": "building component libraries, publishing npm packages, creating reusable svelte components, library development, package distribution",
"docs/kit/auth": "authentication, login systems, user management, session handling, jwt tokens, protected routes, user credentials, authorization checks",
"docs/kit/performance": "performance optimization, slow loading pages, production deployment, debugging performance issues, reducing bundle size, improving load times",
"docs/kit/icons": "icons, ui components, styling, css frameworks, tailwind, unocss, performance optimization, dependency management",
"docs/kit/images": "image optimization, responsive images, performance, hero images, product photos, galleries, cms integration, cdn setup, asset management",
"docs/kit/accessibility": "always, any sveltekit project, screen reader support, keyboard navigation, multi-page apps, client-side routing, internationalization, multilingual sites",
"docs/kit/seo": "seo optimization, search engine ranking, content sites, blogs, marketing sites, public-facing apps, sitemaps, amp pages, meta tags, performance optimization",
"docs/kit/faq": "troubleshooting package imports, library compatibility issues, client-side code execution, external api integration, middleware setup, database configuration, view transitions, yarn configuration",
"docs/kit/integrations": "project setup, css preprocessors, postcss, scss, sass, less, stylus, typescript setup, adding integrations, tailwind, testing, auth, linting, formatting",
"docs/kit/debugging": "debugging, breakpoints, development workflow, troubleshooting issues, vscode setup, ide configuration, inspecting code execution",
"docs/kit/migrating-to-sveltekit-2": "migration, upgrading from sveltekit 1 to 2, breaking changes, version updates",
"docs/kit/migrating": "migrating from sapper, upgrading legacy projects, sapper to sveltekit conversion, project modernization",
"docs/kit/additional-resources": "troubleshooting, getting help, finding examples, learning sveltekit, project templates, common issues, community support",
"docs/kit/glossary": "rendering strategies, performance optimization, deployment configuration, seo requirements, static sites, spas, server-side rendering, prerendering, edge deployment, pwa development",
"docs/kit/@sveltejs-kit": "forms, form actions, server-side validation, form submission, error handling, redirects, json responses, http errors, server utilities",
"docs/kit/@sveltejs-kit-hooks": "middleware, request processing, authentication chains, logging, multiple hooks, request/response transformation",
"docs/kit/@sveltejs-kit-node-polyfills": "node.js environments, custom servers, non-standard runtimes, ssr setup, web api compatibility, polyfill requirements",
"docs/kit/@sveltejs-kit-node": "node.js adapter, custom server setup, http integration, streaming files, node deployment, server-side rendering with node",
"docs/kit/@sveltejs-kit-vite": "project setup, vite configuration, initial sveltekit setup, build tooling",
"docs/kit/$app-environment": "always, conditional logic, client-side code, server-side code, build-time logic, prerendering, development vs production, environment detection",
"docs/kit/$app-forms": "forms, user input, data submission, progressive enhancement, custom form handling, form validation",
"docs/kit/$app-navigation": "routing, navigation, multi-page apps, programmatic navigation, data reloading, preloading, shallow routing, navigation lifecycle, scroll handling, view transitions",
"docs/kit/$app-paths": "static assets, images, fonts, public files, base path configuration, subdirectory deployment, cdn setup, asset urls, links, navigation",
"docs/kit/$app-server": "remote functions, server-side logic, data fetching, form handling, api endpoints, client-server communication, prerendering, file reading, batch queries",
"docs/kit/$app-state": "routing, navigation, multi-page apps, loading states, url parameters, form handling, error states, version updates, page metadata, shallow routing",
"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",
"docs/kit/$app-types": "routing, navigation, type safety, route parameters, dynamic routes, link generation, pathname validation, multi-page apps",
"docs/kit/$env-dynamic-private": "api keys, secrets management, server-side config, environment variables, backend logic, deployment-specific settings, private data handling",
"docs/kit/$env-dynamic-public": "environment variables, client-side config, runtime configuration, public api keys, deployment-specific settings, multi-environment apps",
"docs/kit/$env-static-private": "server-side api keys, backend secrets, database credentials, private configuration, build-time optimization, server endpoints, authentication tokens",
"docs/kit/$env-static-public": "environment variables, public config, client-side data, api endpoints, build-time configuration, public constants",
"docs/kit/$lib": "project setup, component organization, importing shared components, reusable ui elements, code structure",
"docs/kit/$service-worker": "offline support, pwa, service workers, caching strategies, progressive web apps, offline-first apps",
"docs/kit/configuration": "project setup, configuration, adapters, deployment, build settings, environment variables, routing customization, prerendering, csp security, csrf protection, path configuration, typescript setup",
"docs/kit/cli": "project setup, typescript configuration, generated types, ./$types imports, initial project configuration",
"docs/kit/types": "typescript, type safety, route parameters, api endpoints, load functions, form actions, generated types, jsconfig setup",
"docs/svelte/overview": "always, any svelte project, getting started, learning svelte, introduction, project setup, understanding framework basics",
"docs/svelte/getting-started": "project setup, starting new svelte project, initial installation, choosing between sveltekit and vite, editor configuration",
"docs/svelte/svelte-files": "always, any svelte project, component creation, project setup, learning svelte basics",
"docs/svelte/svelte-js-files": "shared reactive state, reusable reactive logic, state management across components, global stores, custom reactive utilities",
"docs/svelte/what-are-runes": "always, any svelte 5 project, understanding core syntax, learning svelte 5, migration from svelte 4",
"docs/svelte/$state": "always, any svelte project, core reactivity, state management, counters, forms, todo apps, interactive ui, data updates, class-based components",
"docs/svelte/$derived": "always, any svelte project, computed values, reactive calculations, derived data, transforming state, dependent values",
"docs/svelte/$effect": "canvas drawing, third-party library integration, dom manipulation, side effects, intervals, timers, network requests, analytics tracking",
"docs/svelte/$props": "always, any svelte project, passing data to components, component communication, reusable components, component props",
"docs/svelte/$bindable": "forms, user input, two-way data binding, custom input components, parent-child communication, reusable form fields",
"docs/svelte/$inspect": "debugging, development, tracking state changes, reactive state monitoring, troubleshooting reactivity issues",
"docs/svelte/$host": "custom elements, web components, dispatching custom events, component library, framework-agnostic components",
"docs/svelte/basic-markup": "always, any svelte project, basic markup, html templating, component structure, attributes, events, props, text rendering",
"docs/svelte/if": "always, conditional rendering, showing/hiding content, dynamic ui, user permissions, loading states, error handling, form validation",
"docs/svelte/each": "always, lists, arrays, iteration, product listings, todos, tables, grids, dynamic content, shopping carts, user lists, comments, feeds",
"docs/svelte/key": "animations, transitions, component reinitialization, forcing component remount, value-based ui updates, resetting component state",
"docs/svelte/await": "async data fetching, api calls, loading states, promises, error handling, lazy loading components, dynamic imports",
"docs/svelte/snippet": "reusable markup, component composition, passing content to components, table rows, list items, conditional rendering, reducing duplication",
"docs/svelte/@render": "reusable ui patterns, component composition, conditional rendering, fallback content, layout components, slot alternatives, template reuse",
"docs/svelte/@html": "rendering html strings, cms content, rich text editors, markdown to html, blog posts, wysiwyg output, sanitized html injection, dynamic html content",
"docs/svelte/@attach": "tooltips, popovers, dom manipulation, third-party libraries, canvas drawing, element lifecycle, interactive ui, custom directives, wrapper components",
"docs/svelte/@const": "computed values in loops, derived calculations in blocks, local variables in each iterations, complex list rendering",
"docs/svelte/@debug": "debugging, development, troubleshooting, tracking state changes, monitoring variables, reactive data inspection",
"docs/svelte/bind": "forms, user input, two-way data binding, interactive ui, media players, file uploads, checkboxes, radio buttons, select dropdowns, contenteditable, dimension tracking",
"docs/svelte/use": "custom directives, dom manipulation, third-party library integration, tooltips, click outside, gestures, focus management, element lifecycle hooks",
"docs/svelte/transition": "animations, interactive ui, modals, dropdowns, notifications, conditional content, show/hide elements, smooth state changes",
"docs/svelte/in-and-out": "animation, transitions, interactive ui, conditional rendering, independent enter/exit effects, modals, tooltips, notifications",
"docs/svelte/animate": "sortable lists, drag and drop, reorderable items, todo lists, kanban boards, playlist editors, priority queues, animated list reordering",
"docs/svelte/style": "dynamic styling, conditional styles, theming, dark mode, responsive design, interactive ui, component styling",
"docs/svelte/class": "always, conditional styling, dynamic classes, tailwind css, component styling, reusable components, responsive design",
"docs/svelte/await-expressions": "async data fetching, loading states, server-side rendering, awaiting promises in components, async validation, concurrent data loading",
"docs/svelte/scoped-styles": "always, styling components, scoped css, component-specific styles, preventing style conflicts, animations, keyframes",
"docs/svelte/global-styles": "global styles, third-party libraries, css resets, animations, styling body/html, overriding component styles, shared keyframes, base styles",
"docs/svelte/custom-properties": "theming, custom styling, reusable components, design systems, dynamic colors, component libraries, ui customization",
"docs/svelte/nested-style-elements": "component styling, scoped styles, dynamic styles, conditional styling, nested style tags, custom styling logic",
"docs/svelte/svelte-boundary": "error handling, async data loading, loading states, error recovery, flaky components, error reporting, resilient ui",
"docs/svelte/svelte-window": "keyboard shortcuts, scroll tracking, window resize handling, responsive layouts, online/offline detection, viewport dimensions, global event listeners",
"docs/svelte/svelte-document": "document events, visibility tracking, fullscreen detection, pointer lock, focus management, document-level interactions",
"docs/svelte/svelte-body": "mouse tracking, hover effects, cursor interactions, global body events, drag and drop, custom cursors, interactive backgrounds, body-level actions",
"docs/svelte/svelte-head": "seo optimization, page titles, meta tags, social media sharing, dynamic head content, multi-page apps, blog posts, product pages",
"docs/svelte/svelte-element": "dynamic content, cms integration, user-generated content, configurable ui, runtime element selection, flexible components",
"docs/svelte/svelte-options": "migration, custom elements, web components, legacy mode compatibility, runes mode setup, svg components, mathml components, css injection control",
"docs/svelte/stores": "shared state, cross-component data, reactive values, async data streams, manual control over updates, rxjs integration, extracting logic",
"docs/svelte/context": "shared state, avoiding prop drilling, component communication, theme providers, user context, authentication state, configuration sharing, deeply nested components",
"docs/svelte/lifecycle-hooks": "component initialization, cleanup tasks, timers, subscriptions, dom measurements, chat windows, autoscroll features, migration from svelte 4",
"docs/svelte/imperative-component-api": "project setup, client-side rendering, server-side rendering, ssr, hydration, testing, programmatic component creation, tooltips, dynamic mounting",
"docs/svelte/testing": "testing, quality assurance, unit tests, integration tests, component tests, e2e tests, vitest setup, playwright setup, test automation",
"docs/svelte/typescript": "typescript setup, type safety, component props typing, generic components, wrapper components, dom type augmentation, project configuration",
"docs/svelte/custom-elements": "web components, custom elements, component library, design system, framework-agnostic components, embedding svelte in non-svelte apps, shadow dom",
"docs/svelte/v4-migration-guide": "upgrading svelte 3 to 4, version migration, updating dependencies, breaking changes, legacy project maintenance",
"docs/svelte/v5-migration-guide": "migrating from svelte 4 to 5, upgrading projects, learning svelte 5 syntax changes, runes migration, event handler updates",
"docs/svelte/faq": "getting started, learning svelte, beginner setup, project initialization, vs code setup, formatting, testing, routing, mobile apps, troubleshooting, community support",
"docs/svelte/svelte": "migration from svelte 4 to 5, upgrading legacy code, component lifecycle hooks, context api, mounting components, event dispatchers, typescript component types",
"docs/svelte/svelte-action": "typescript types, actions, use directive, dom manipulation, element lifecycle, custom behaviors, third-party library integration",
"docs/svelte/svelte-animate": "animated lists, sortable items, drag and drop, reordering elements, todo lists, kanban boards, playlist management, smooth position transitions",
"docs/svelte/svelte-attachments": "library development, component libraries, programmatic element manipulation, migrating from actions to attachments, spreading props onto elements",
"docs/svelte/svelte-compiler": "build tools, custom compilers, ast manipulation, preprocessors, code transformation, migration scripts, syntax analysis, bundler plugins, dev tools",
"docs/svelte/svelte-easing": "animations, transitions, custom easing, smooth motion, interactive ui, modals, dropdowns, carousels, page transitions, scroll effects",
"docs/svelte/svelte-events": "window events, document events, global event listeners, event delegation, programmatic event handling, cleanup functions, media queries",
"docs/svelte/svelte-legacy": "migration from svelte 4 to svelte 5, upgrading legacy code, event modifiers, class components, imperative component instantiation",
"docs/svelte/svelte-motion": "animation, smooth transitions, interactive ui, sliders, counters, physics-based motion, drag gestures, accessibility, reduced motion",
"docs/svelte/svelte-reactivity-window": "responsive design, viewport tracking, scroll effects, window resize handling, online/offline detection, zoom level tracking",
"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",
"docs/svelte/svelte-server": "server-side rendering, ssr, static site generation, seo optimization, initial page load, pre-rendering, node.js server, custom server setup",
"docs/svelte/svelte-store": "state management, shared data, reactive stores, cross-component communication, global state, computed values, data synchronization, legacy svelte projects",
"docs/svelte/svelte-transition": "animations, transitions, interactive ui, modals, dropdowns, tooltips, notifications, svg animations, list animations, page transitions",
"docs/svelte/compiler-errors": "animation, transitions, keyed each blocks, list animations",
"docs/svelte/compiler-warnings": "accessibility, a11y compliance, wcag standards, screen readers, keyboard navigation, aria attributes, semantic html, interactive elements",
"docs/svelte/runtime-errors": "debugging errors, error handling, troubleshooting runtime issues, migration to svelte 5, component binding, effects and reactivity",
"docs/svelte/runtime-warnings": "debugging state proxies, console logging reactive values, inspecting state changes, development troubleshooting",
"docs/svelte/legacy-overview": "migrating from svelte 3/4 to svelte 5, maintaining legacy components, understanding deprecated features, gradual upgrade process",
"docs/svelte/legacy-let": "migration, legacy svelte projects, upgrading from svelte 4, understanding old reactivity, maintaining existing code, learning runes differences",
"docs/svelte/legacy-reactive-assignments": "legacy mode, migration from svelte 4, reactive statements, computed values, derived state, side effects",
"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",
"docs/svelte/legacy-$$props-and-$$restProps": "legacy mode migration, component wrappers, prop forwarding, button components, reusable ui components, spreading props to child elements",
"docs/svelte/legacy-on": "legacy mode, event handling, button clicks, forms, user interactions, component communication, event forwarding, event modifiers",
"docs/svelte/legacy-slots": "legacy mode, migrating from svelte 4, component composition, reusable components, passing content to components, modals, layouts, wrappers",
"docs/svelte/legacy-$$slots": "legacy mode, conditional slot rendering, optional content sections, checking if slots provided, migrating from legacy to runes",
"docs/svelte/legacy-svelte-fragment": "named slots, component composition, layout systems, avoiding wrapper divs, legacy svelte projects, slot content organization",
"docs/svelte/legacy-svelte-component": "dynamic components, component switching, conditional rendering, legacy mode migration, tabbed interfaces, multi-step forms",
"docs/svelte/legacy-svelte-self": "recursive components, tree structures, nested menus, file explorers, comment threads, hierarchical data",
"docs/svelte/legacy-component-api": "migration from svelte 3/4 to 5, legacy component api, maintaining old projects, understanding deprecated patterns"
}
}

View File

@@ -1,111 +0,0 @@
# @sveltejs/mcp
## 0.1.10
### Patch Changes
- fix: add icons to `server.json` ([`02c951b`](https://github.com/sveltejs/mcp/commit/02c951baa86ac8103ffc158a202c06cfe6b15c01))
- fix: add `preferred-frame-size` to UI resource ([`3fabcc0`](https://github.com/sveltejs/mcp/commit/3fabcc0f9bfee916c0deb9c2ffa931ed2168af2d))
- feat: support: `$state.eager` ([#90](https://github.com/sveltejs/mcp/pull/90))
## 0.1.9
### Patch Changes
- feat: return `mcp-ui` resource from `playground-link` ([#84](https://github.com/sveltejs/mcp/pull/84))
- feat: suggest against js variables in css ([#78](https://github.com/sveltejs/mcp/pull/78))
## 0.1.8
### Patch Changes
- fix: upgrade registry publisher cli ([`5fa2baa`](https://github.com/sveltejs/mcp/commit/5fa2baa27009f01e0e4e91cee7984b81a81c1c29))
## 0.1.7
### Patch Changes
- fix: use correct server schema version ([`579be87`](https://github.com/sveltejs/mcp/commit/579be877fa9f87f7f173450ca5bc918824d68282))
## 0.1.6
### Patch Changes
- fix: prevent `imported_runes` suggestion from being added for libs that are not svelte ([`87af64f`](https://github.com/sveltejs/mcp/commit/87af64f4bc6d07b75640eb987a33655654363997))
- feat: add svelte icon and website url for mcp server ([#75](https://github.com/sveltejs/mcp/pull/75))
- fix: use `data:` uri for local icon & add icons to tools + resources + prompts ([`cf62286`](https://github.com/sveltejs/mcp/commit/cf622869129382a97ad059bb1389f115907adc8e))
## 0.1.5
### Patch Changes
- fix: widen `desired_svelte_version` validation to accommodate some clients ([`3b301d7`](https://github.com/sveltejs/mcp/commit/3b301d7d9c2f49758023408f505bc4ca79caaff4))
- fix: minor tweaks to the prompt to allow for automatic sync ([#63](https://github.com/sveltejs/mcp/pull/63))
- feat: `read_state_with_dollar` autofixer ([#66](https://github.com/sveltejs/mcp/pull/66))
## 0.1.4
### Patch Changes
- fix: pass secrets in action and update `mcpName` ([`044f098`](https://github.com/sveltejs/mcp/commit/044f0988b935fff39911a861a648dfb276f5831a))
## 0.1.3
### Patch Changes
- fix: use DNS to publish MCP ([#59](https://github.com/sveltejs/mcp/pull/59))
## 0.1.2
### Patch Changes
- fix: publish to MCP registry (I really hope this time for real) ([`ef5241c`](https://github.com/sveltejs/mcp/commit/ef5241cbc204ad8bb84bde27db7c9d0a08280245))
## 0.1.1
### Patch Changes
- feat: publish mcp to registry (maybe for real this time) ([`132943d`](https://github.com/sveltejs/mcp/commit/132943db3b04dbbd322d08926c0880c990a61f5f))
## 0.1.0
### Minor Changes
- feat: publish to registry ([#45](https://github.com/sveltejs/mcp/pull/45))
### Patch Changes
- 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))
- feat: add bind:this -> attachment and action -> attachment autofixer #20 ([`73d7625`](https://github.com/sveltejs/mcp/commit/73d7625b3ca6a812ba91883ea668d80ff1e7c703))
## 0.0.4
### Patch Changes
- fix: allow TS `.svelte.ts` modules ([#49](https://github.com/sveltejs/mcp/pull/49))
## 0.0.3
### Patch Changes
- fix: check effect.pre in assign-in-effect ([#41](https://github.com/sveltejs/mcp/pull/41))
- feat: `use_cases` documentation metadata ([#29](https://github.com/sveltejs/mcp/pull/29))
- fix: change title names to allow for claude code to use the prompt ([`725f785`](https://github.com/sveltejs/mcp/commit/725f785766d04e9ed810a7c3f6bcfdb2e2b8234c))
- fix: enable doc tools ([`cb316c5`](https://github.com/sveltejs/mcp/commit/cb316c5b3ebc712946969d2d57236d159e796d58))
## 0.0.2
### Patch Changes
- feat: latest version ([#25](https://github.com/sveltejs/mcp/pull/25))

View File

@@ -1,25 +0,0 @@
704f15b3ccba80990fbaaeb858e2e893120dea3ebb9340e04a36065987e089c5 mcp-publisher_darwin_amd64.tar.gz
9eadadeb80998cd5b29e009dd3959634730d3754b9e201c0a3bb39dd6ae85933 mcp-publisher_darwin_amd64.tar.gz.sbom.json
f80265eadc6b052d2885a7dfa47a1ecc7ca95926c143197ced747317793dcc11 mcp-publisher_darwin_arm64.tar.gz
f063c462512eed8f2ce9610a6a125534cb552060ddb5364d8d49b32196416fbe mcp-publisher_darwin_arm64.tar.gz.sbom.json
1113b9d6bf59b000966c4f17752cf87b51db03dcc5482721421fd843ce3bf048 mcp-publisher_linux_amd64.tar.gz
d3e46855b3b906aa84571b0d16384ad6074fd08709108a47d724908e91947b9d mcp-publisher_linux_amd64.tar.gz.sbom.json
34be13ec07490ab1250194d56820af222b49b85db6062ff848d33f9cf6eb41ce mcp-publisher_linux_arm64.tar.gz
a1ff3c26d53007c98301b747f97e5e241bfdb860b6a761db8627c9ba9ba71d88 mcp-publisher_linux_arm64.tar.gz.sbom.json
9ba8b538744652c4837d08b7b83ae85d2dbea98c897cfcf4d90835df4fe075b3 mcp-publisher_windows_amd64.tar.gz
924a0adcebd9360aed4ee5959e664e4e99b8259ed871ac91be5ace5657afba7d mcp-publisher_windows_amd64.tar.gz.sbom.json
a3eeab18ee6fd1d76d7ec153779ebe1bb404d7ea561f117781174e4396d90565 mcp-publisher_windows_arm64.tar.gz
9351f3acc39f89402ce5f72be1b45f3bf1b16f8441c97496195188f276e6c227 mcp-publisher_windows_arm64.tar.gz.sbom.json
9767b6f2afe1b60c597d4c66468d491be3466b25de8b4d4e7a21e61950856ef7 registry-1.3.3.tar.gz
a7867ff2c15905a765dc77636d86241ea04a17d0a90f086913a3cea4fe899213 registry_darwin_amd64.tar.gz
f0ed44b6ebdfcba40daefe7ca556f385de0cbae95dca3077c406f75cdb6abb9b registry_darwin_amd64.tar.gz.sbom.json
b4d93d6fc2d831d287cde79953822b434cb8406eea4f38f180628df227f6569e registry_darwin_arm64.tar.gz
dde0ed7f350eda98eb6f474d19aa0e4c717dd9f120609949acf05d7986cf6fcc registry_darwin_arm64.tar.gz.sbom.json
6772a573828f3482a192c88dac4223ee71ff4f521c8819480a194757e46a8272 registry_linux_amd64.tar.gz
ac177354d5f43f86bdf7f36738837a2792ba136198209eb61d4d4dd9ab408853 registry_linux_amd64.tar.gz.sbom.json
61c1fb9126977e0694d5aad8990a9d4c2d0e933e6aa469789559fad3865e8b7f registry_linux_arm64.tar.gz
9b81dfe2c81c49f7731072b8c217f05d609f7094ea94aaf9491f33880f2dec7f registry_linux_arm64.tar.gz.sbom.json
7fb5752de50a841614c6990f20807820bda33a72eaf7be51ae6cbd4e9acb2b87 registry_windows_amd64.tar.gz
fd44139231d4725c75b2a5a96fb670d3137f628db91c8281f6348e842792d514 registry_windows_amd64.tar.gz.sbom.json
a85ba7eb1a06a9e668b8a2097e00ab010e454a2afeb4e29ebeb7b702a31cccd3 registry_windows_arm64.tar.gz
93503683d32a2971b741e87f2879cb2a3f41acd184c618e04d17a98926915f2d registry_windows_arm64.tar.gz.sbom.json

View File

@@ -1,9 +1,8 @@
{
"name": "@sveltejs/mcp",
"version": "0.1.10",
"version": "0.0.1",
"type": "module",
"license": "MIT",
"mcpName": "dev.svelte/mcp",
"homepage": "https://github.com/sveltejs/mcp#readme",
"bugs": {
"url": "https://github.com/sveltejs/mcp/issues"
@@ -23,24 +22,21 @@
"access": "public"
},
"scripts": {
"build": "tsdown && publint",
"build": "tsdown",
"dev": "tsdown --watch",
"test": "vitest",
"check": "tsc --noEmit",
"check:publint": "publint --strict",
"update:version": "node scripts/update-version.ts"
"check": "tsc --noEmit"
},
"devDependencies": {
"@sveltejs/mcp-server": "workspace:^",
"@tmcp/transport-stdio": "^0.3.1",
"@tmcp/transport-stdio": "^0.3.0",
"@types/node": "^22.15.17",
"publint": "^0.3.13",
"tsdown": "^0.15.0",
"tsdown": "^0.11.9",
"typescript": "^5.8.3",
"vitest": "^4.0.0"
"vitest": "^3.1.3"
},
"dependencies": {
"eslint": "^9.36.0",
"tmcp": "^1.15.3"
"eslint": "^9.36.0"
}
}

View File

@@ -1,14 +0,0 @@
import { readFile, writeFile } from 'node:fs/promises';
import { resolve } from 'node:path';
const package_json_string = await readFile(resolve('./package.json'), 'utf-8');
const package_json = JSON.parse(package_json_string);
const server_json_path = resolve('./server.json');
const server_json_string = await readFile(server_json_path, 'utf-8');
const server_json = JSON.parse(server_json_string);
server_json.version = package_json.version;
server_json.packages[0].version = package_json.version;
await writeFile(server_json_path, JSON.stringify(server_json, null, '\t') + '\n', 'utf-8');

View File

@@ -1,40 +0,0 @@
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"name": "dev.svelte/mcp",
"description": "The official Svelte MCP server providing docs and autofixing tools for Svelte development",
"repository": {
"id": "1054419133",
"url": "https://github.com/sveltejs/mcp",
"subfolder": "packages/mcp-stdio",
"source": "github"
},
"version": "0.1.10",
"websiteUrl": "https://svelte.dev/docs/mcp/overview",
"icons": [
{
"src": "https://mcp.svelte.dev/logo.svg",
"mimeType": "image/svg+xml"
},
{
"src": "https://mcp.svelte.dev/logo.png",
"mimeType": "image/png"
}
],
"packages": [
{
"registryType": "npm",
"identifier": "@sveltejs/mcp",
"version": "0.1.10",
"runtimeHint": "npx",
"transport": {
"type": "stdio"
}
}
],
"remotes": [
{
"url": "https://mcp.svelte.dev/mcp",
"type": "streamable-http"
}
]
}

2431
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,36 +0,0 @@
import fs from 'node:fs/promises';
import path from 'node:path';
let content = `---
title: Prompts
---
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.
`;
const prompts_generators = fs.glob('./packages/mcp-server/src/mcp/handlers/prompts/*.ts');
const filename_regex = /packages\/mcp-server\/src\/mcp\/handlers\/prompts\/(?<prompt>.+)\.ts/;
for await (const file of prompts_generators) {
const title = file.match(filename_regex)?.groups?.prompt;
if (title === 'index') continue;
const module = await import(path.resolve('./', file));
content += `## ${title}
${module.docs_description}
<details>
<summary>Copy the prompt</summary>
\`\`\`md
${await module.generate_for_docs()}
\`\`\`
</details>
`;
}
await fs.writeFile('./documentation/docs/30-capabilities/30-prompts.md', content.trim() + '\n');