mirror of
https://github.com/sveltejs/ai-tools.git
synced 2026-07-04 03:19:38 +08:00
Compare commits
22 Commits
@sveltejs/
...
claude-plu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e94cb5ffa | ||
|
|
45a0362f12 | ||
|
|
7a269f9134 | ||
|
|
af5c90b3d7 | ||
|
|
baa3de3556 | ||
|
|
c52e2879be | ||
|
|
eb38793643 | ||
|
|
ee56b09681 | ||
|
|
5b0773dfb1 | ||
|
|
8934e1dc04 | ||
|
|
41a80310ee | ||
|
|
b7fabff038 | ||
|
|
6815bee685 | ||
|
|
2ce60c6110 | ||
|
|
0f8987fdcf | ||
|
|
42911e2631 | ||
|
|
655eb85eba | ||
|
|
89403a7d0c | ||
|
|
3747623d55 | ||
|
|
dc53ba05e2 | ||
|
|
ddfb0fe3aa | ||
|
|
9998507a08 |
13
.claude-plugin/marketplace.json
Normal file
13
.claude-plugin/marketplace.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "svelte",
|
||||
"owner": {
|
||||
"name": "Svelte"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "svelte",
|
||||
"source": "./plugins/svelte",
|
||||
"description": "A plugin for all things Svelte development, MCP, skills, and more."
|
||||
}
|
||||
]
|
||||
}
|
||||
2
.github/workflows/check.yml
vendored
2
.github/workflows/check.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.26.0
|
||||
version: 10.26.2
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
|
||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.26.0
|
||||
version: 10.26.2
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
|
||||
23
.github/workflows/publish-any-commit.yml
vendored
Normal file
23
.github/workflows/publish-any-commit.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
name: Publish Any Commit
|
||||
on: [push, pull_request]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- run: pnpm dlx pkg-pr-new publish --compact './packages/mcp-stdio' --pnpm
|
||||
45
.github/workflows/release-svelte-skill.yml
vendored
Normal file
45
.github/workflows/release-svelte-skill.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
name: Release Svelte Code Writer Skill
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'plugins/svelte/skills/svelte-code-writer/**'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
release:
|
||||
permissions:
|
||||
contents: write
|
||||
# prevents this action from running on forks
|
||||
if: github.repository == 'sveltejs/mcp'
|
||||
name: Release Svelte Code Writer Skill
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Get version from date
|
||||
id: version
|
||||
run: echo "version=$(date +'%Y.%m.%d-%H%M%S')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create zip
|
||||
run: |
|
||||
cd plugins/svelte/skills
|
||||
zip -r svelte-code-writer.zip svelte-code-writer/
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: svelte-code-writer-v${{ steps.version.outputs.version }}
|
||||
name: Svelte Code Writer Skill v${{ steps.version.outputs.version }}
|
||||
body: |
|
||||
Automated release of the Svelte Code Writer skill.
|
||||
|
||||
This release was triggered by changes to the `plugins/svelte/skills/svelte-code-writer/` directory.
|
||||
files: plugins/svelte/skills/svelte-code-writer.zip
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.26.0
|
||||
version: 10.26.2
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
|
||||
@@ -16,6 +16,8 @@ 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`.
|
||||
|
||||
If you prefer you can also install the `svelte` plugin in [the Svelte Claude Code Marketplace](plugin) that will give you both the remote server and a useful [skill](https://docs.claude.com/en/docs/agents-and-tools/agent-skills/overview).
|
||||
|
||||
## Claude Desktop
|
||||
|
||||
- Open Settings > Connectors
|
||||
|
||||
3
documentation/docs/40-claude-plugin/index.md
Normal file
3
documentation/docs/40-claude-plugin/index.md
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Claude Code Plugin
|
||||
---
|
||||
23
documentation/docs/40-claude-plugin/plugin.md
Normal file
23
documentation/docs/40-claude-plugin/plugin.md
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
title: Overview
|
||||
---
|
||||
|
||||
The open source [repository](https://github.com/sveltejs/mcp) containing the code for the MCP server is also a Claude Code Marketplace plugin.
|
||||
|
||||
The marketplace allow you to install the `svelte` plugin which will give you both the remote MCP server, a [skill](https://docs.claude.com/en/docs/agents-and-tools/agent-skills/overview) to instruct the LLM on how to properly write Svelte 5 code, and a specialized agent for editing Svelte files.
|
||||
|
||||
If possible is recommended to instruct the LLM to execute MCP calls with the agent (you can explicitly mention an agent in your message to delegate work to it) when creating or editing `.svelte` files or `.svelte.ts`/`.svelte.js` modules as it helps save context by handling Svelte-specific tasks more efficiently.
|
||||
|
||||
## Installation
|
||||
|
||||
To add the repository as a marketplace launch claude code and type
|
||||
|
||||
```bash
|
||||
/plugin marketplace add sveltejs/mcp
|
||||
```
|
||||
|
||||
once you do that you can install the svelte skill doing
|
||||
|
||||
```bash
|
||||
/plugin install svelte
|
||||
```
|
||||
11
documentation/docs/40-claude-plugin/skill.md
Normal file
11
documentation/docs/40-claude-plugin/skill.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Skill
|
||||
---
|
||||
|
||||
Claude Skills are a set of Markdown files that live in your `.claude` folder (or that you can upload in your Claude web/desktop). They are automatically loaded by Claude when it thinks they are appropriate for the current task.
|
||||
|
||||
With those markdown files you can steer the agent behaviours and, in our case, we teach him how to properly write Svelte 5 code. The advantage over the MCP server is that the relevant tokens are only loaded when they are needed (so if you ask the LLM to write a Typescript utility in a Svelte project it will not load the skill in the context).
|
||||
|
||||
You can find the skill inside the [`sveltejs/mcp`](https://github.com/sveltejs/mcp/tree/main/plugins/svelte/skills) repo (it's in the `/plugins/svelte/skills` folder). You can also download the latest zip file from the [releases page](https://github.com/sveltejs/mcp/releases?q=svelte-code-writer) to load it in your Claude web/desktop or to extract it inside your `.claude` folder.
|
||||
|
||||
If you are using Claude Code you can also install it through the [Svelte marketplace](plugin).
|
||||
11
documentation/docs/40-claude-plugin/subagent.md
Normal file
11
documentation/docs/40-claude-plugin/subagent.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Subagent
|
||||
---
|
||||
|
||||
The Svelte plugin includes a specialized subagent called `svelte-file-editor` designed for creating, editing, and reviewing Svelte files.
|
||||
|
||||
## Benefits
|
||||
|
||||
The subagent is executed in a separate "agent" that has access to it's own context window. This allows the agent to fetch the documentation, iterate with the `svelte-autofixer` tool and write to the file system without wasting context in the main agent.
|
||||
|
||||
The delegation should happen automatically when appropriate, but you can also explicitly request the subagent be used for Svelte-related tasks.
|
||||
@@ -3,7 +3,7 @@
|
||||
"version": "0.0.1",
|
||||
"description": "The official Svelte MCP server implementation",
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@10.26.0",
|
||||
"packageManager": "pnpm@10.26.2",
|
||||
"scripts": {
|
||||
"build": "pnpm -r run build",
|
||||
"dev": "pnpm --filter @sveltejs/mcp-remote run dev",
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
"debug:generate-summaries": "DEBUG_MODE=1 node scripts/generate-summaries.ts --experimental-strip-types"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
".": "./src/index.ts",
|
||||
"./handlers": "./src/mcp/handlers/tools/handlers.ts"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"drizzle-orm": "^0.45.0"
|
||||
|
||||
@@ -5,110 +5,137 @@ import { SECTIONS_LIST_INTRO, SECTIONS_LIST_OUTRO } from './prompts.js';
|
||||
import { icons } from '../../icons/index.js';
|
||||
import { tool } from 'tmcp/utils';
|
||||
|
||||
const get_documentation_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',
|
||||
),
|
||||
),
|
||||
});
|
||||
|
||||
export async function get_documentation_handler({
|
||||
section,
|
||||
}: v.InferInput<typeof get_documentation_schema>) {
|
||||
let sections: string[];
|
||||
|
||||
if (Array.isArray(section)) {
|
||||
sections = section.filter((s): s is string => typeof s === 'string');
|
||||
} else if (
|
||||
typeof section === 'string' &&
|
||||
section.trim().startsWith('[') &&
|
||||
section.trim().endsWith(']')
|
||||
) {
|
||||
try {
|
||||
const parsed = JSON.parse(section);
|
||||
if (Array.isArray(parsed)) {
|
||||
sections = parsed.filter((s): s is string => typeof s === 'string');
|
||||
} else {
|
||||
sections = [section];
|
||||
}
|
||||
} catch {
|
||||
sections = [section];
|
||||
}
|
||||
} else if (typeof section === 'string') {
|
||||
sections = [section];
|
||||
} else {
|
||||
sections = [];
|
||||
}
|
||||
|
||||
const 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}`;
|
||||
}
|
||||
|
||||
return final_text;
|
||||
}
|
||||
|
||||
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. Before calling this tool, try to implement Svelte components using your own knowledge and the `svelte-autofixer` tool, since calling this tool is token intensive.',
|
||||
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',
|
||||
),
|
||||
),
|
||||
}),
|
||||
schema: get_documentation_schema,
|
||||
annotations: {
|
||||
title: 'Get Documentation',
|
||||
destructiveHint: false,
|
||||
readOnlyHint: true,
|
||||
openWorldHint: false,
|
||||
},
|
||||
icons,
|
||||
},
|
||||
async ({ section }) => {
|
||||
if (server.ctx.sessionId && server.ctx.custom?.track) {
|
||||
await server.ctx.custom?.track?.(server.ctx.sessionId, 'get-documentation');
|
||||
}
|
||||
let sections: string[];
|
||||
|
||||
if (Array.isArray(section)) {
|
||||
sections = section.filter((s): s is string => typeof s === 'string');
|
||||
} else if (
|
||||
typeof section === 'string' &&
|
||||
section.trim().startsWith('[') &&
|
||||
section.trim().endsWith(']')
|
||||
) {
|
||||
try {
|
||||
const parsed = JSON.parse(section);
|
||||
if (Array.isArray(parsed)) {
|
||||
sections = parsed.filter((s): s is string => typeof s === 'string');
|
||||
} else {
|
||||
sections = [section];
|
||||
}
|
||||
} catch {
|
||||
sections = [section];
|
||||
}
|
||||
} else if (typeof section === 'string') {
|
||||
sections = [section];
|
||||
} else {
|
||||
sections = [];
|
||||
}
|
||||
|
||||
const 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,
|
||||
try {
|
||||
const content = await get_documentation_handler({ section });
|
||||
return tool.text(content);
|
||||
} catch (e) {
|
||||
const error = e as Error;
|
||||
if (server.ctx.sessionId && server.ctx.custom?.track) {
|
||||
await server.ctx.custom?.track?.(
|
||||
server.ctx.sessionId,
|
||||
'get-documentation-error',
|
||||
error.message,
|
||||
);
|
||||
|
||||
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}`;
|
||||
return tool.error(error.message);
|
||||
}
|
||||
|
||||
return tool.text(final_text);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
4
packages/mcp-server/src/mcp/handlers/tools/handlers.ts
Normal file
4
packages/mcp-server/src/mcp/handlers/tools/handlers.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export { get_documentation_handler } from './get-documentation.js';
|
||||
export { list_sections_handler } from './list-sections.js';
|
||||
export { svelte_autofixer_handler } from './svelte-autofixer.js';
|
||||
export { playground_link_handler } from './playground-link.js';
|
||||
@@ -1,4 +1,4 @@
|
||||
export * from './get-documentation.js';
|
||||
export * from './list-sections.js';
|
||||
export * from './svelte-autofixer.js';
|
||||
export * from './playground-link.js';
|
||||
export { get_documentation } from './get-documentation.js';
|
||||
export { list_sections } from './list-sections.js';
|
||||
export { svelte_autofixer } from './svelte-autofixer.js';
|
||||
export { playground_link } from './playground-link.js';
|
||||
|
||||
@@ -4,21 +4,44 @@ import { SECTIONS_LIST_INTRO, SECTIONS_LIST_OUTRO } from './prompts.js';
|
||||
import { icons } from '../../icons/index.js';
|
||||
import { tool } from 'tmcp/utils';
|
||||
|
||||
export async function list_sections_handler() {
|
||||
const formatted_sections = await format_sections_list();
|
||||
|
||||
return `${SECTIONS_LIST_INTRO}\n\n${formatted_sections}\n\n${SECTIONS_LIST_OUTRO}`;
|
||||
}
|
||||
|
||||
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.',
|
||||
annotations: {
|
||||
title: 'List Sections',
|
||||
destructiveHint: false,
|
||||
readOnlyHint: true,
|
||||
openWorldHint: false,
|
||||
},
|
||||
icons,
|
||||
},
|
||||
async () => {
|
||||
if (server.ctx.sessionId && server.ctx.custom?.track) {
|
||||
await server.ctx.custom?.track?.(server.ctx.sessionId, 'list-sections');
|
||||
}
|
||||
const formatted_sections = await format_sections_list();
|
||||
|
||||
return tool.text(`${SECTIONS_LIST_INTRO}\n\n${formatted_sections}\n\n${SECTIONS_LIST_OUTRO}`);
|
||||
try {
|
||||
const content = await list_sections_handler();
|
||||
return tool.text(content);
|
||||
} catch (e) {
|
||||
const error = e as Error;
|
||||
if (server.ctx.sessionId && server.ctx.custom?.track) {
|
||||
await server.ctx.custom?.track?.(
|
||||
server.ctx.sessionId,
|
||||
'list-sections-error',
|
||||
error.message,
|
||||
);
|
||||
}
|
||||
return tool.error(error.message);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -30,98 +30,126 @@ type File = {
|
||||
text: boolean;
|
||||
};
|
||||
|
||||
const playground_link_schema = v.object({
|
||||
name: v.pipe(
|
||||
v.string(),
|
||||
v.description('The name of the Playground, it should reflect the user task'),
|
||||
),
|
||||
tailwind: v.pipe(
|
||||
v.boolean(),
|
||||
v.description(
|
||||
"If the code requires Tailwind CSS to work...only send true if it it's using tailwind classes in the code",
|
||||
),
|
||||
),
|
||||
files: v.pipe(
|
||||
v.record(v.string(), v.string()),
|
||||
v.description(
|
||||
"An object where all the keys are the filenames (with extensions) and the values are the file content. For example: { 'Component.svelte': '<script>...</script>', 'utils.js': 'export function ...' }. The playground accept multiple files so if are importing from other files just include them all at the root level.",
|
||||
),
|
||||
),
|
||||
});
|
||||
|
||||
const playground_link_output_schema = v.object({
|
||||
url: v.string(),
|
||||
});
|
||||
|
||||
export async function playground_link_handler({
|
||||
files,
|
||||
name,
|
||||
tailwind,
|
||||
}: v.InferInput<typeof playground_link_schema>) {
|
||||
const playground_base = new URL('https://svelte.dev/playground');
|
||||
const playground_files: File[] = [];
|
||||
|
||||
let has_app_svelte = false;
|
||||
|
||||
for (const [filename, contents] of Object.entries(files)) {
|
||||
if (filename === 'App.svelte') has_app_svelte = true;
|
||||
playground_files.push({
|
||||
type: 'file',
|
||||
name: filename,
|
||||
basename: filename.replace(/^.*[\\/]/, ''),
|
||||
contents,
|
||||
text: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (!has_app_svelte) {
|
||||
throw new Error('The files must contain an App.svelte file as the entry point');
|
||||
}
|
||||
|
||||
const playground_config = {
|
||||
name,
|
||||
tailwind: tailwind ?? false,
|
||||
files: playground_files,
|
||||
};
|
||||
|
||||
playground_base.hash = await compress_and_encode_text(JSON.stringify(playground_config));
|
||||
|
||||
const url = playground_base.toString();
|
||||
|
||||
// use the embed path to have a cleaner UI for mcp-ui
|
||||
playground_base.pathname = '/playground/embed';
|
||||
|
||||
return {
|
||||
url,
|
||||
iframe_url: playground_base.toString(),
|
||||
};
|
||||
}
|
||||
|
||||
export function playground_link(server: SvelteMcp) {
|
||||
server.tool(
|
||||
{
|
||||
name: 'playground-link',
|
||||
description:
|
||||
'Generates a Playground link given a Svelte code snippet. Once you have the final version of the code you want to send to the user, ALWAYS ask the user if it wants a playground link to allow it to quickly check the code in the playground before calling this tool. NEVER use this tool if you have written the component to a file in the user project. The playground accept multiple files so if are importing from other files just include them all at the root level.',
|
||||
schema: v.object({
|
||||
name: v.pipe(
|
||||
v.string(),
|
||||
v.description('The name of the Playground, it should reflect the user task'),
|
||||
),
|
||||
tailwind: v.pipe(
|
||||
v.boolean(),
|
||||
v.description(
|
||||
"If the code requires Tailwind CSS to work...only send true if it it's using tailwind classes in the code",
|
||||
),
|
||||
),
|
||||
files: v.pipe(
|
||||
v.record(v.string(), v.string()),
|
||||
v.description(
|
||||
"An object where all the keys are the filenames (with extensions) and the values are the file content. For example: { 'Component.svelte': '<script>...</script>', 'utils.js': 'export function ...' }. The playground accept multiple files so if are importing from other files just include them all at the root level.",
|
||||
),
|
||||
),
|
||||
}),
|
||||
outputSchema: v.object({
|
||||
url: v.string(),
|
||||
}),
|
||||
schema: playground_link_schema,
|
||||
outputSchema: playground_link_output_schema,
|
||||
annotations: {
|
||||
title: 'Playground Link',
|
||||
destructiveHint: false,
|
||||
readOnlyHint: true,
|
||||
openWorldHint: false,
|
||||
},
|
||||
icons,
|
||||
},
|
||||
async ({ files, name, tailwind }) => {
|
||||
if (server.ctx.sessionId && server.ctx.custom?.track) {
|
||||
await server.ctx.custom?.track?.(server.ctx.sessionId, 'playground-link');
|
||||
}
|
||||
const playground_base = new URL('https://svelte.dev/playground');
|
||||
const playground_files: File[] = [];
|
||||
|
||||
let has_app_svelte = false;
|
||||
|
||||
for (const [filename, contents] of Object.entries(files)) {
|
||||
if (filename === 'App.svelte') has_app_svelte = true;
|
||||
playground_files.push({
|
||||
type: 'file',
|
||||
name: filename,
|
||||
basename: filename.replace(/^.*[\\/]/, ''),
|
||||
contents,
|
||||
text: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (!has_app_svelte) {
|
||||
try {
|
||||
const result = await playground_link_handler({ files, name, tailwind });
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({ url: result.url }),
|
||||
},
|
||||
createUIResource({
|
||||
uri: 'ui://svelte/playground-link',
|
||||
content: {
|
||||
type: 'externalUrl',
|
||||
iframeUrl: result.iframe_url,
|
||||
},
|
||||
uiMetadata: {
|
||||
'preferred-frame-size': ['100%', '1200px'],
|
||||
},
|
||||
encoding: 'text',
|
||||
}),
|
||||
],
|
||||
structuredContent: { url: result.url },
|
||||
};
|
||||
} catch (e) {
|
||||
const error = e as Error;
|
||||
if (server.ctx.sessionId && server.ctx.custom?.track) {
|
||||
await server.ctx.custom?.track?.(server.ctx.sessionId, 'playground-link-no-app-svelte');
|
||||
await server.ctx.custom?.track?.(
|
||||
server.ctx.sessionId,
|
||||
'playground-link-error',
|
||||
error.message,
|
||||
);
|
||||
}
|
||||
|
||||
return tool.error('The files must contain an App.svelte file as the entry point');
|
||||
return tool.error(error.message);
|
||||
}
|
||||
|
||||
const playground_config = {
|
||||
name,
|
||||
tailwind: tailwind ?? false,
|
||||
files: playground_files,
|
||||
};
|
||||
|
||||
playground_base.hash = await compress_and_encode_text(JSON.stringify(playground_config));
|
||||
|
||||
const content = {
|
||||
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,
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,95 @@ import { add_autofixers_issues } from '../../autofixers/add-autofixers-issues.js
|
||||
import { icons } from '../../icons/index.js';
|
||||
import { tool } from 'tmcp/utils';
|
||||
|
||||
const autofixer_schema = v.object({
|
||||
code: v.string(),
|
||||
desired_svelte_version: v.pipe(
|
||||
v.union([v.string(), v.number()]),
|
||||
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.',
|
||||
),
|
||||
),
|
||||
async: v.pipe(
|
||||
v.optional(v.boolean()),
|
||||
v.description(
|
||||
'If true the code is an async component/module and might use await in the markup or top-level awaits in the script tag. If possible check the svelte.config.js/svelte.config.ts to check if the option is enabled otherwise asks the user if they prefer using it or not. You can only use this option if the version is 5.',
|
||||
),
|
||||
),
|
||||
filename: v.pipe(
|
||||
v.optional(v.string()),
|
||||
v.description(
|
||||
'The filename of the component if available, it MUST be only the Component name with .svelte or .svelte.ts extension and not the entire path.',
|
||||
),
|
||||
),
|
||||
});
|
||||
|
||||
const autofixer_output_schema = v.object({
|
||||
issues: v.array(v.string()),
|
||||
suggestions: v.array(v.string()),
|
||||
require_another_tool_call_after_fixing: v.boolean(),
|
||||
});
|
||||
|
||||
export async function svelte_autofixer_handler({
|
||||
code,
|
||||
desired_svelte_version: desired_svelte_version_unchecked,
|
||||
async,
|
||||
filename: filename_or_path,
|
||||
}: v.InferInput<typeof autofixer_schema>) {
|
||||
// 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) {
|
||||
throw new Error(
|
||||
`The desired_svelte_version MUST be either 4 or 5 but received "${desired_svelte_version_unchecked}"`,
|
||||
);
|
||||
}
|
||||
|
||||
const desired_svelte_version = parsed_version.output;
|
||||
|
||||
if (async && +desired_svelte_version < 5) {
|
||||
throw new Error('The async option can only be used with Svelte version 5 or higher.');
|
||||
}
|
||||
|
||||
const content: {
|
||||
issues: string[];
|
||||
suggestions: string[];
|
||||
require_another_tool_call_after_fixing: boolean;
|
||||
} = { issues: [], suggestions: [], require_another_tool_call_after_fixing: false };
|
||||
try {
|
||||
// just in case the LLM sends a full path we extract the filename...it's not really needed
|
||||
// but it's nice to have a filename in the errors
|
||||
|
||||
const filename = filename_or_path ? basename(filename_or_path) : 'Component.svelte';
|
||||
|
||||
add_compile_issues(content, code, +desired_svelte_version, filename, async);
|
||||
|
||||
add_autofixers_issues(content, code, +desired_svelte_version, filename, async);
|
||||
|
||||
await add_eslint_issues(content, code, +desired_svelte_version, filename, async);
|
||||
} catch (e: unknown) {
|
||||
const error = e as Error & { start?: { line: number; column: number } };
|
||||
content.issues.push(
|
||||
`${error.message} at line ${error.start?.line}, column ${error.start?.column}`,
|
||||
);
|
||||
if (error.message.includes('js_parse_error')) {
|
||||
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)`.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (content.issues.length > 0 || content.suggestions.length > 0) {
|
||||
content.require_another_tool_call_after_fixing = true;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
export function svelte_autofixer(server: SvelteMcp) {
|
||||
server.tool(
|
||||
{
|
||||
@@ -14,32 +103,8 @@ export function svelte_autofixer(server: SvelteMcp) {
|
||||
title: 'Svelte Autofixer',
|
||||
description:
|
||||
'Given a svelte component or module returns a list of suggestions to fix any issues it has. This tool MUST be used whenever the user is asking to write svelte code before sending the code back to the user',
|
||||
schema: v.object({
|
||||
code: v.string(),
|
||||
desired_svelte_version: v.pipe(
|
||||
v.union([v.string(), v.number()]),
|
||||
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.',
|
||||
),
|
||||
),
|
||||
async: v.pipe(
|
||||
v.optional(v.boolean()),
|
||||
v.description(
|
||||
'If true the code is an async component/module and might use await in the markup or top-level awaits in the script tag. If possible check the svelte.config.js/svelte.config.ts to check if the option is enabled otherwise asks the user if they prefer using it or not. You can only use this option if the version is 5.',
|
||||
),
|
||||
),
|
||||
filename: v.pipe(
|
||||
v.optional(v.string()),
|
||||
v.description(
|
||||
'The filename of the component if available, it MUST be only the Component name with .svelte or .svelte.ts extension and not the entire path.',
|
||||
),
|
||||
),
|
||||
}),
|
||||
outputSchema: v.object({
|
||||
issues: v.array(v.string()),
|
||||
suggestions: v.array(v.string()),
|
||||
require_another_tool_call_after_fixing: v.boolean(),
|
||||
}),
|
||||
schema: autofixer_schema,
|
||||
outputSchema: autofixer_output_schema,
|
||||
annotations: {
|
||||
title: 'Svelte Autofixer',
|
||||
destructiveHint: false,
|
||||
@@ -57,63 +122,25 @@ export function svelte_autofixer(server: SvelteMcp) {
|
||||
if (server.ctx.sessionId && server.ctx.custom?.track) {
|
||||
await server.ctx.custom?.track?.(server.ctx.sessionId, 'svelte-autofixer');
|
||||
}
|
||||
// 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) {
|
||||
if (server.ctx.sessionId && server.ctx.custom?.track) {
|
||||
await server.ctx.custom?.track?.(server.ctx.sessionId, 'svelte-autofixer-wrong-version');
|
||||
}
|
||||
return tool.error(
|
||||
`The desired_svelte_version MUST be either 4 or 5 but received "${desired_svelte_version_unchecked}"`,
|
||||
);
|
||||
}
|
||||
|
||||
const desired_svelte_version = parsed_version.output;
|
||||
|
||||
if (async && +desired_svelte_version < 5) {
|
||||
return tool.error('The async option can only be used with Svelte version 5 or higher.');
|
||||
}
|
||||
|
||||
const content: {
|
||||
issues: string[];
|
||||
suggestions: string[];
|
||||
require_another_tool_call_after_fixing: boolean;
|
||||
} = { issues: [], suggestions: [], require_another_tool_call_after_fixing: false };
|
||||
try {
|
||||
// just in case the LLM sends a full path we extract the filename...it's not really needed
|
||||
// but it's nice to have a filename in the errors
|
||||
|
||||
const filename = filename_or_path ? basename(filename_or_path) : 'Component.svelte';
|
||||
|
||||
add_compile_issues(content, code, +desired_svelte_version, filename, async);
|
||||
|
||||
add_autofixers_issues(content, code, +desired_svelte_version, filename, async);
|
||||
|
||||
await add_eslint_issues(content, code, +desired_svelte_version, filename, async);
|
||||
} catch (e: unknown) {
|
||||
const error = e as Error & { start?: { line: number; column: number } };
|
||||
content.issues.push(
|
||||
`${error.message} at line ${error.start?.line}, column ${error.start?.column}`,
|
||||
);
|
||||
if (error.message.includes('js_parse_error')) {
|
||||
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)`.",
|
||||
const content = await svelte_autofixer_handler({
|
||||
code,
|
||||
desired_svelte_version: desired_svelte_version_unchecked,
|
||||
async,
|
||||
filename: filename_or_path,
|
||||
});
|
||||
return tool.structured(content);
|
||||
} catch (e) {
|
||||
const error = e as Error;
|
||||
if (server.ctx.sessionId && server.ctx.custom?.track) {
|
||||
await server.ctx.custom?.track?.(
|
||||
server.ctx.sessionId,
|
||||
'svelte-autofixer-error',
|
||||
error.message,
|
||||
);
|
||||
}
|
||||
return tool.error(error.message);
|
||||
}
|
||||
|
||||
if (content.issues.length > 0 || content.suggestions.length > 0) {
|
||||
content.require_another_tool_call_after_fixing = true;
|
||||
}
|
||||
|
||||
return tool.structured(content);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @sveltejs/mcp
|
||||
|
||||
## 0.1.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- feat: expose tools as JS api + cli ([#128](https://github.com/sveltejs/mcp/pull/128))
|
||||
|
||||
## 0.1.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sveltejs/mcp",
|
||||
"version": "0.1.15",
|
||||
"version": "0.1.16",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"mcpName": "dev.svelte/mcp",
|
||||
@@ -19,6 +19,12 @@
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/handlers.d.mts",
|
||||
"import": "./dist/handlers.mjs"
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
@@ -41,6 +47,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"eslint": "catalog:lint",
|
||||
"sade": "catalog:tooling",
|
||||
"tmcp": "catalog:tmcp"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"subfolder": "packages/mcp-stdio",
|
||||
"source": "github"
|
||||
},
|
||||
"version": "0.1.15",
|
||||
"version": "0.1.16",
|
||||
"websiteUrl": "https://svelte.dev/docs/mcp/overview",
|
||||
"icons": [
|
||||
{
|
||||
@@ -25,7 +25,7 @@
|
||||
{
|
||||
"registryType": "npm",
|
||||
"identifier": "@sveltejs/mcp",
|
||||
"version": "0.1.15",
|
||||
"version": "0.1.16",
|
||||
"runtimeHint": "npx",
|
||||
"transport": {
|
||||
"type": "stdio"
|
||||
|
||||
6
packages/mcp-stdio/src/handlers.ts
Normal file
6
packages/mcp-stdio/src/handlers.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export {
|
||||
list_sections_handler as listSections,
|
||||
get_documentation_handler as getDocumentation,
|
||||
svelte_autofixer_handler as svelteAutofixer,
|
||||
playground_link_handler as playgroundLink,
|
||||
} from '@sveltejs/mcp-server/handlers';
|
||||
@@ -1,7 +1,65 @@
|
||||
#! /usr/bin/env node
|
||||
import { server } from '@sveltejs/mcp-server';
|
||||
import {
|
||||
list_sections_handler,
|
||||
get_documentation_handler,
|
||||
svelte_autofixer_handler,
|
||||
} from '@sveltejs/mcp-server/handlers';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { StdioTransport } from '@tmcp/transport-stdio';
|
||||
import sade from 'sade';
|
||||
|
||||
const transport = new StdioTransport(server);
|
||||
const cli = sade('svelte-mcp');
|
||||
|
||||
transport.listen();
|
||||
cli.command('__mcp', '', { default: true }).action(() => {
|
||||
const transport = new StdioTransport(server);
|
||||
transport.listen();
|
||||
});
|
||||
|
||||
cli
|
||||
.command('list-sections')
|
||||
.describe('List all the available documentation sections')
|
||||
.action(async () => {
|
||||
console.log(await list_sections_handler());
|
||||
});
|
||||
|
||||
cli
|
||||
.command('get-documentation <sections>')
|
||||
.describe('Get documentation for specified sections, separated by commas')
|
||||
.action(async (sections) => {
|
||||
console.log(await get_documentation_handler({ section: sections.split(',') }));
|
||||
});
|
||||
|
||||
cli
|
||||
.command('svelte-autofixer <code_or_path>')
|
||||
.describe(
|
||||
'Detect and suggest fixes for Svelte code issues, because the terminal will substitute variables `$` should be correctly escaped',
|
||||
)
|
||||
.option('--async', 'Wether the project is using async svelte or not', false)
|
||||
.option('--svelte-version', 'Which version of svelte to use...it can be 4 or 5', 5)
|
||||
.action(async (code_or_path, { async, 'svelte-version': version }) => {
|
||||
let code = code_or_path;
|
||||
|
||||
let is_path = false;
|
||||
|
||||
if (existsSync(code_or_path)) {
|
||||
console.log('Detected file path, reading file...');
|
||||
code = await readFile(code_or_path, 'utf-8');
|
||||
is_path = true;
|
||||
} else {
|
||||
console.log('File not found, treating input as code...');
|
||||
}
|
||||
|
||||
const desired_svelte_version = +version;
|
||||
|
||||
const result = await svelte_autofixer_handler({
|
||||
code,
|
||||
async: Boolean(async),
|
||||
desired_svelte_version,
|
||||
filename: is_path ? code_or_path : undefined,
|
||||
});
|
||||
console.log(result);
|
||||
});
|
||||
|
||||
cli.parse(process.argv);
|
||||
|
||||
@@ -2,7 +2,7 @@ import { defineConfig } from 'tsdown';
|
||||
|
||||
export default defineConfig([
|
||||
{
|
||||
entry: ['./src/index.ts'],
|
||||
entry: ['./src/index.ts', './src/handlers.ts'],
|
||||
platform: 'node',
|
||||
define: {
|
||||
// some eslint-plugin-svelte code expects __filename to exists but in an ESM environment it does not.
|
||||
@@ -13,7 +13,9 @@ export default defineConfig([
|
||||
// the require would fail once executed in a project without eslint installed.
|
||||
external: ['eslint'],
|
||||
publint: true,
|
||||
dts: false,
|
||||
dts: {
|
||||
eager: true,
|
||||
},
|
||||
treeshake: true,
|
||||
clean: true,
|
||||
target: 'esnext',
|
||||
|
||||
8
plugins/svelte/.claude-plugin/plugin.json
Normal file
8
plugins/svelte/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "svelte",
|
||||
"description": "A plugin for all things related to Svelte development, MCP, skills, and more.",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "Svelte"
|
||||
}
|
||||
}
|
||||
8
plugins/svelte/.mcp.json
Normal file
8
plugins/svelte/.mcp.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"svelte": {
|
||||
"type": "http",
|
||||
"url": "https://mcp.svelte.dev/mcp"
|
||||
}
|
||||
}
|
||||
}
|
||||
63
plugins/svelte/agents/svelte-file-editor.md
Normal file
63
plugins/svelte/agents/svelte-file-editor.md
Normal file
@@ -0,0 +1,63 @@
|
||||
---
|
||||
name: svelte-file-editor
|
||||
description: Specialized Svelte 5 code editor. MUST BE USED PROACTIVELY when creating, editing, or reviewing any .svelte file or .svelte.ts/.svelte.js module and MUST use the tools from the MCP server. Fetches relevant documentation and validates code using the Svelte MCP server tools.
|
||||
---
|
||||
|
||||
You are a Svelte 5 expert responsible for writing, editing, and validating Svelte components and modules. You have access to the Svelte MCP server which provides documentation and code analysis tools. Always use the tools from the svelte MCP server to fetch documentation with `get_documentation` and validating the code with `svelte_autofixer`. If the autofixer returns any issue or suggestions try to solve them.
|
||||
|
||||
## Available MCP Tools
|
||||
|
||||
### 1. list-sections
|
||||
|
||||
Lists all available Svelte 5 and SvelteKit documentation sections with titles and paths. Use this first to discover what documentation is available.
|
||||
|
||||
### 2. get-documentation
|
||||
|
||||
Retrieves full documentation for specified sections. Accepts a single section name or an array of section names. Use after `list-sections` to fetch relevant docs for the task at hand.
|
||||
|
||||
**Example sections:** `$state`, `$derived`, `$effect`, `$props`, `$bindable`, `snippets`, `routing`, `load functions`
|
||||
|
||||
### 3. svelte-autofixer
|
||||
|
||||
Analyzes Svelte code and returns suggestions to fix issues. Pass the component code directly to this tool. It will detect common mistakes like:
|
||||
|
||||
- Using `$effect` instead of `$derived` for computations
|
||||
- Missing cleanup in effects
|
||||
- Svelte 4 syntax (`on:click`, `export let`, `<slot>`)
|
||||
- Missing keys in `{#each}` blocks
|
||||
- And more
|
||||
|
||||
## Workflow
|
||||
|
||||
When invoked to work on a Svelte file:
|
||||
|
||||
### 1. Gather Context (if needed)
|
||||
|
||||
If you're uncertain about Svelte 5 syntax or patterns, use the MCP tools:
|
||||
|
||||
1. Call `list-sections` to see available documentation
|
||||
2. Call `get-documentation` with relevant section names
|
||||
|
||||
### 2. Read the Target File
|
||||
|
||||
Read the file to understand the current implementation.
|
||||
|
||||
### 3. Make Changes
|
||||
|
||||
Apply edits following Svelte 5 best practices:
|
||||
|
||||
### 4. Validate Changes
|
||||
|
||||
After editing, ALWAYS call `svelte-autofixer` with the updated code to check for issues.
|
||||
|
||||
### 5. Fix Any Issues
|
||||
|
||||
If the autofixer reports problems, fix them and re-validate until no issues remain.
|
||||
|
||||
## Output Format
|
||||
|
||||
After completing your work, provide:
|
||||
|
||||
1. Summary of changes made
|
||||
2. Any issues found and fixed by the autofixer
|
||||
3. Recommendations for further improvements (if any)
|
||||
66
plugins/svelte/skills/svelte-code-writer/SKILL.md
Normal file
66
plugins/svelte/skills/svelte-code-writer/SKILL.md
Normal file
@@ -0,0 +1,66 @@
|
||||
---
|
||||
name: svelte-code-writer
|
||||
description: CLI tools for Svelte 5 documentation lookup and code analysis. MUST be used whenever creating or editing any Svelte component (.svelte) or Svelte module (.svelte.ts/.svelte.js). If possible, this skill should be executed within the svelte-file-editor agent for optimal results.
|
||||
---
|
||||
|
||||
# Svelte 5 Code Writer
|
||||
|
||||
## CLI Tools
|
||||
|
||||
You have access to `@sveltejs/mcp` CLI for Svelte-specific assistance. Use these commands via `npx`:
|
||||
|
||||
### List Documentation Sections
|
||||
|
||||
```bash
|
||||
npx @sveltejs/mcp list-sections
|
||||
```
|
||||
|
||||
Lists all available Svelte 5 and SvelteKit documentation sections with titles and paths.
|
||||
|
||||
### Get Documentation
|
||||
|
||||
```bash
|
||||
npx @sveltejs/mcp get-documentation "<section1>,<section2>,..."
|
||||
```
|
||||
|
||||
Retrieves full documentation for specified sections. Use after `list-sections` to fetch relevant docs.
|
||||
|
||||
**Example:**
|
||||
|
||||
```bash
|
||||
npx @sveltejs/mcp get-documentation "$state,$derived,$effect"
|
||||
```
|
||||
|
||||
### Svelte Autofixer
|
||||
|
||||
```bash
|
||||
npx @sveltejs/mcp svelte-autofixer "<code_or_path>" [options]
|
||||
```
|
||||
|
||||
Analyzes Svelte code and suggests fixes for common issues.
|
||||
|
||||
**Options:**
|
||||
|
||||
- `--async` - Enable async Svelte mode (default: false)
|
||||
- `--svelte-version` - Target version: 4 or 5 (default: 5)
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# Analyze inline code (escape $ as \$)
|
||||
npx @sveltejs/mcp svelte-autofixer '<script>let count = \$state(0);</script>'
|
||||
|
||||
# Analyze a file
|
||||
npx @sveltejs/mcp svelte-autofixer ./src/lib/Component.svelte
|
||||
|
||||
# Target Svelte 4
|
||||
npx @sveltejs/mcp svelte-autofixer ./Component.svelte --svelte-version 4
|
||||
```
|
||||
|
||||
**Important:** When passing code with runes (`$state`, `$derived`, etc.) via the terminal, escape the `$` character as `\$` to prevent shell variable substitution.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Uncertain about syntax?** Run `list-sections` then `get-documentation` for relevant topics
|
||||
2. **Reviewing/debugging?** Run `svelte-autofixer` on the code to detect issues
|
||||
3. **Always validate** - Run `svelte-autofixer` before finalizing any Svelte component
|
||||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
@@ -128,6 +128,9 @@ catalogs:
|
||||
publint:
|
||||
specifier: ^0.3.13
|
||||
version: 0.3.13
|
||||
sade:
|
||||
specifier: 1.8.1
|
||||
version: 1.8.1
|
||||
ts-blank-space:
|
||||
specifier: ^0.6.2
|
||||
version: 0.6.2
|
||||
@@ -384,6 +387,9 @@ importers:
|
||||
eslint:
|
||||
specifier: catalog:lint
|
||||
version: 9.36.0(jiti@2.6.0)
|
||||
sade:
|
||||
specifier: catalog:tooling
|
||||
version: 1.8.1
|
||||
tmcp:
|
||||
specifier: catalog:tmcp
|
||||
version: 1.19.0(typescript@5.9.2)
|
||||
|
||||
@@ -48,6 +48,7 @@ catalogs:
|
||||
dotenv: ^17.2.3
|
||||
node-resolve-ts: ^1.0.2
|
||||
publint: ^0.3.13
|
||||
sade: 1.8.1
|
||||
ts-blank-space: ^0.6.2
|
||||
tsdown: ^0.18.0
|
||||
typescript: ^5.0.0
|
||||
|
||||
Reference in New Issue
Block a user