Compare commits

...

12 Commits

Author SHA1 Message Date
paoloricciuti
239f9613a2 docs: update prompts documentation 2025-12-17 17:27:39 +00:00
Paolo Ricciuti
b01ae9069b fix: typo again 2025-12-17 18:27:20 +01:00
Paolo Ricciuti
b6db495242 fix: changesets 2025-12-17 18:19:08 +01:00
Paolo Ricciuti
3926310107 fix: typo in documentation tool name in svelte_task 2025-12-17 18:17:30 +01:00
Paolo Ricciuti
848627549f fix: improve prompt to reduce token usage (#124)
Co-authored-by: Stanislav Khromov <stanislav.khromov+github@gmail.com>
2025-12-17 18:13:07 +01:00
renovate[bot]
283164ca7e chore(deps): update peter-evans/create-pull-request action to v8 (#121)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-17 16:48:55 +01:00
renovate[bot]
c6ee414f62 chore(deps): update all non-major dependencies (#119)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-17 16:48:38 +01:00
Matheus Ferraz
ea5bdf66dc chore(docs): fix tool name typo (#120) 2025-12-13 01:32:19 +01:00
paoloricciuti
7d707202d1 fix: update tmcp 2025-12-06 10:44:04 +01:00
renovate[bot]
f0daadfbd0 chore(deps): update all non-major dependencies (#117)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-05 23:52:14 +01:00
github-actions[bot]
15a7774da7 Version Packages (#118)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-05 22:34:53 +01:00
paoloricciuti
98efa1e09e fix: revert name change and add title 2025-12-05 22:26:33 +01:00
21 changed files with 505 additions and 356 deletions

View File

@@ -0,0 +1,5 @@
---
"@sveltejs/mcp-stdio": patch
---
fix: improve prompt to reduce token usage

View File

@@ -18,7 +18,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10.24.0
version: 10.26.0
- name: Setup Node.js
uses: actions/setup-node@v6

View File

@@ -18,7 +18,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10.24.0
version: 10.26.0
- name: Setup Node.js
uses: actions/setup-node@v6

View File

@@ -18,7 +18,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10.24.0
version: 10.26.0
- name: Setup Node.js
uses: actions/setup-node@v6

View File

@@ -55,7 +55,7 @@ jobs:
- name: Create Pull Request
if: steps.git-check.outputs.changed == 'true'
uses: peter-evans/create-pull-request@v7
uses: peter-evans/create-pull-request@v8
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'docs: update prompts documentation'

View File

@@ -12,7 +12,7 @@ This prompt should be used whenever you are asking the model to work on a Svelte
<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:
You are a Svelte expert tasked to build components and utilities for Svelte developers. If you need documentation for anything related to Svelte you can invoke the tool `get-documentation` with one of the following paths. However: before invoking the `get-documentation` tool, try to answer the users query using your own knowledge and the `svelte-autofixer` tool. Be mindful of how many section you request, since it is token-intensive!
<available-docs>
- title: Overview, use_cases: project setup, creating new svelte apps, scaffolding, cli tools, initializing projects, path: cli/overview
@@ -153,6 +153,7 @@ You are a Svelte expert tasked to build components and utilities for Svelte deve
- 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: Hydratable data, use_cases: use title and path to estimate use case, path: svelte/hydratable
- 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
@@ -192,6 +193,8 @@ You are a Svelte expert tasked to build components and utilities for Svelte deve
</available-docs>
These are the available documentation sections that `list-sections` will return, you do not need to call it again.
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:

View File

@@ -3,7 +3,7 @@
"version": "0.0.1",
"description": "The official Svelte MCP server implementation",
"type": "module",
"packageManager": "pnpm@10.24.0",
"packageManager": "pnpm@10.26.0",
"scripts": {
"build": "pnpm -r run build",
"dev": "pnpm --filter @sveltejs/mcp-remote run dev",

View File

@@ -17,12 +17,13 @@
".": "./src/index.ts"
},
"peerDependencies": {
"drizzle-orm": "^0.44.0"
"drizzle-orm": "^0.45.0"
},
"dependencies": {
"@mcp-ui/server": "catalog:ai",
"@sveltejs/mcp-schema": "workspace:^",
"@tmcp/adapter-valibot": "catalog:tmcp",
"@tmcp/transport-in-memory": "catalog:tmcp",
"@typescript-eslint/parser": "catalog:lint",
"eslint": "catalog:lint",
"eslint-plugin-svelte": "catalog:lint",

View File

@@ -2,19 +2,22 @@ import type { SvelteMcp } from '../../index.js';
import * as v from 'valibot';
import { format_sections_list } from '../../utils.js';
import { icons } from '../../icons/index.js';
import { prompt } from 'tmcp/utils';
/**
* 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:
return `You are a Svelte expert tasked to build components and utilities for Svelte developers. If you need documentation for anything related to Svelte you can invoke the tool \`get-documentation\` with one of the following paths. However: before invoking the \`get-documentation\` tool, try to answer the users query using your own knowledge and the \`svelte-autofixer\` tool. Be mindful of how many section you request, since it is token-intensive!
<available-docs>
${available_docs}
</available-docs>
These are the available documentation sections that \`list-sections\` will return, you do not need to call it again.
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:
@@ -73,17 +76,7 @@ export function setup_svelte_task(server: SvelteMcp) {
}
const available_docs = await format_sections_list();
return {
messages: [
{
role: 'user',
content: {
type: 'text',
text: svelte_task(available_docs, task),
},
},
],
};
return prompt.text(svelte_task(available_docs, task));
},
);
}

View File

@@ -1,6 +1,7 @@
import type { SvelteMcp } from '../../index.js';
import { get_sections, fetch_with_timeout } from '../../utils.js';
import { icons } from '../../icons/index.js';
import { resource } from 'tmcp/utils';
export async function list_sections(server: SvelteMcp) {
const sections = await get_sections();
@@ -59,15 +60,7 @@ export async function list_sections(server: SvelteMcp) {
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,
},
],
};
return resource.text(uri, content);
},
);
}

View File

@@ -3,13 +3,14 @@ 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';
import { tool } from 'tmcp/utils';
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., "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())]),
@@ -107,14 +108,7 @@ export function get_documentation(server: SvelteMcp) {
final_text += `\n\n---\n\n${SECTIONS_LIST_INTRO}\n\n${formatted_sections}\n\n${SECTIONS_LIST_OUTRO}`;
}
return {
content: [
{
type: 'text',
text: final_text,
},
],
};
return tool.text(final_text);
},
);
}

View File

@@ -2,6 +2,7 @@ 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';
import { tool } from 'tmcp/utils';
export function list_sections(server: SvelteMcp) {
server.tool(
@@ -17,14 +18,7 @@ export function list_sections(server: SvelteMcp) {
}
const formatted_sections = await format_sections_list();
return {
content: [
{
type: 'text',
text: `${SECTIONS_LIST_INTRO}\n\n${formatted_sections}\n\n${SECTIONS_LIST_OUTRO}`,
},
],
};
return tool.text(`${SECTIONS_LIST_INTRO}\n\n${formatted_sections}\n\n${SECTIONS_LIST_OUTRO}`);
},
);
}

View File

@@ -0,0 +1,84 @@
import { InMemoryTransport } from '@tmcp/transport-in-memory';
import { beforeEach, describe, expect, it } from 'vitest';
import { server } from '../../index.js';
const transport = new InMemoryTransport(server);
let session: ReturnType<typeof transport.session>;
describe('playground-link tool', () => {
beforeEach(async () => {
session = transport.session();
await session.initialize(
'2025-06-18',
{},
{
name: 'test-client',
version: '1.0.0',
},
);
});
it('should create a playground link if App.svelte is present', async () => {
const result = await session.callTool<{ url: string }>('playground-link', {
name: 'My Playground',
tailwind: false,
files: {
'App.svelte': `Hi there!`,
},
});
expect(result.structuredContent).toBeDefined();
expect(result.structuredContent?.url).toBeDefined();
// Verify URL structure rather than exact match (gzip compression can vary by platform)
expect(result.structuredContent?.url).toMatch(/^https:\/\/svelte\.dev\/playground#H4sIA/);
expect(result.structuredContent?.url).toContain('svelte.dev/playground');
});
it('should have a content with the stringified version of structured content and an ui resource', async () => {
const result = await session.callTool<{ url: string }>('playground-link', {
name: 'My Playground',
tailwind: false,
files: {
'App.svelte': `Hi there!`,
},
});
expect(result.structuredContent).toBeDefined();
expect(result.content).toStrictEqual(
expect.arrayContaining([
expect.objectContaining({
type: 'text',
text: JSON.stringify(result.structuredContent),
}),
]),
);
// Verify resource structure without exact URL match (gzip compression can vary by platform)
expect(result.content).toStrictEqual(
expect.arrayContaining([
expect.objectContaining({
type: 'resource',
resource: expect.objectContaining({
uri: 'ui://svelte/playground-link',
mimeType: 'text/uri-list',
_meta: { 'mcpui.dev/ui-preferred-frame-size': ['100%', '1200px'] },
text: expect.stringMatching(/^https:\/\/svelte\.dev\/playground\/embed#H4sIA/),
}),
}),
]),
);
});
it('should not create a playground link if App.svelte is missing', async () => {
const result = await session.callTool<{ url: string }>('playground-link', {
name: 'My Playground',
tailwind: false,
files: {
'Something.svelte': `Hi there!`,
},
});
expect(result.isError).toBe(true);
expect(result.content?.[0]).toStrictEqual({
type: 'text',
text: 'The files must contain an App.svelte file as the entry point',
});
});
});

View File

@@ -2,6 +2,7 @@ import type { SvelteMcp } from '../../index.js';
import * as v from 'valibot';
import { icons } from '../../icons/index.js';
import { createUIResource } from '@mcp-ui/server';
import { tool } from 'tmcp/utils';
async function compress_and_encode_text(input: string) {
const reader = new Blob([input]).stream().pipeThrough(new CompressionStream('gzip')).getReader();
@@ -82,17 +83,8 @@ export function playground_link(server: SvelteMcp) {
if (server.ctx.sessionId && server.ctx.custom?.track) {
await server.ctx.custom?.track?.(server.ctx.sessionId, 'playground-link-no-app-svelte');
}
return {
isError: true,
content: [
{
type: 'text',
text: JSON.stringify({
error: 'The files must contain an App.svelte file as the entry point',
}),
},
],
};
return tool.error('The files must contain an App.svelte file as the entry point');
}
const playground_config = {

View File

@@ -1,12 +1,11 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { InMemoryTransport } from '@tmcp/transport-in-memory';
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;
}
const transport = new InMemoryTransport(server);
let session: ReturnType<typeof transport.session>;
async function autofixer_tool_call(
code: string,
@@ -14,51 +13,34 @@ async function autofixer_tool_call(
desired_svelte_version = 5,
async = false,
) {
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',
async,
},
},
const result = await session.callTool('svelte-autofixer', {
code,
desired_svelte_version,
filename: 'App.svelte',
async,
});
expect(result).toBeDefined();
expect(result.result).toBeDefined();
if (is_error) {
return result.result;
return result as any;
}
expect(result.result.structuredContent).toBeDefined();
return result.result.structuredContent;
expect(result.structuredContent).toBeDefined();
return result.structuredContent as any;
}
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',
},
},
});
session = transport.session();
await server.receive(initialize_request, {
sessionId: 'svelte-autofixer-session',
});
session = transport.session();
await session.initialize(
'2025-06-18',
{},
{
name: 'test-client',
version: '1.0.0',
},
);
});
it('should add suggestions for js parse errors', async () => {

View File

@@ -5,6 +5,7 @@ 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';
import { tool } from 'tmcp/utils';
export function svelte_autofixer(server: SvelteMcp) {
server.tool(
@@ -65,29 +66,15 @@ export function svelte_autofixer(server: SvelteMcp) {
if (server.ctx.sessionId && server.ctx.custom?.track) {
await server.ctx.custom?.track?.(server.ctx.sessionId, 'svelte-autofixer-wrong-version');
}
return {
isError: true,
content: [
{
type: 'text',
text: `The desired_svelte_version MUST be either 4 or 5 but received "${desired_svelte_version_unchecked}"`,
},
],
};
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 {
isError: true,
content: [
{
type: 'text',
text: `The async option can only be used with Svelte version 5 or higher.`,
},
],
};
return tool.error('The async option can only be used with Svelte version 5 or higher.');
}
const content: {
@@ -126,15 +113,7 @@ export function svelte_autofixer(server: SvelteMcp) {
content.require_another_tool_call_after_fixing = true;
}
return {
content: [
{
type: 'text',
text: JSON.stringify(content),
},
],
structuredContent: content,
};
return tool.structured(content);
},
);
}

View File

@@ -1,5 +1,11 @@
# @sveltejs/mcp
## 0.1.13
### Patch Changes
- fix: revert name change and add title ([`98efa1e`](https://github.com/sveltejs/mcp/commit/98efa1e09ebcca7827b10dc6bc8e1699fc1e5171))
## 0.1.12
### Patch Changes

View File

@@ -1,9 +1,9 @@
{
"name": "@sveltejs/mcp",
"version": "0.1.12",
"version": "0.1.13",
"type": "module",
"license": "MIT",
"mcpName": "dev.svelte/svelte",
"mcpName": "dev.svelte/mcp",
"homepage": "https://github.com/sveltejs/mcp#readme",
"bugs": {
"url": "https://github.com/sveltejs/mcp/issues"

View File

@@ -1,6 +1,7 @@
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"name": "dev.svelte/svelte",
"name": "dev.svelte/mcp",
"title": "Svelte MCP",
"description": "The official Svelte MCP server providing docs and autofixing tools for Svelte development",
"repository": {
"id": "1054419133",
@@ -8,7 +9,7 @@
"subfolder": "packages/mcp-stdio",
"source": "github"
},
"version": "0.1.12",
"version": "0.1.13",
"websiteUrl": "https://svelte.dev/docs/mcp/overview",
"icons": [
{
@@ -24,7 +25,7 @@
{
"registryType": "npm",
"identifier": "@sveltejs/mcp",
"version": "0.1.12",
"version": "0.1.13",
"runtimeHint": "npx",
"transport": {
"type": "stdio"

536
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,50 +1,44 @@
packages:
- './packages/*'
- './apps/*'
useNodeVersion: 22.19.0
- ./packages/*
- ./apps/*
catalogs:
tmcp:
tmcp: '^1.16.0'
'@tmcp/transport-http': '^0.8.0'
'@tmcp/transport-stdio': '^0.4.0'
'@tmcp/adapter-valibot': '^0.1.4'
ai:
'@anthropic-ai/sdk': ^0.71.0
'@mcp-ui/server': ^5.12.0
'@modelcontextprotocol/inspector': ^0.17.0
'@modelcontextprotocol/inspector': ^0.18.0
lint:
eslint: ^9.36.0
'@types/eslint-scope': ^8.3.2
'@eslint/compat': ^2.0.0
'@eslint/js': ^9.36.0
'@types/eslint-scope': ^8.3.2
'@typescript-eslint/parser': ^8.44.0
'@typescript-eslint/types': ^8.44.0
eslint: ^9.36.0
eslint-config-prettier: ^10.0.1
eslint-plugin-import: ^2.32.0
eslint-plugin-svelte: ^3.12.5
svelte-eslint-parser: ^1.4.0
eslint-plugin-pnpm: ^1.3.0
typescript-eslint: ^8.44.0
eslint-plugin-svelte: ^3.12.5
globals: ^16.0.0
prettier: ^3.4.2
prettier-plugin-svelte: ^3.3.3
svelte-eslint-parser: ^1.4.0
typescript-eslint: ^8.44.0
orm:
'@libsql/client': ^0.15.0
drizzle-kit: ^0.31.0
drizzle-orm: ^0.44.0
drizzle-orm: ^0.45.0
svelte:
'@sveltejs/adapter-vercel': ^6.0.0
'@sveltejs/kit': ^2.42.2
'@sveltejs/vite-plugin-svelte': ^6.0.0
svelte: ^5.39.2
svelte-check: ^4.0.0
tmcp:
'@tmcp/adapter-valibot': ^0.1.4
'@tmcp/transport-http': ^0.8.3
'@tmcp/transport-in-memory': ^0.0.5
'@tmcp/transport-stdio': ^0.4.0
tmcp: ^1.19.0
tooling:
'@changesets/cli': ^2.29.7
'@svitejs/changesets-changelog-github-compact': ^1.2.0
@@ -55,10 +49,12 @@ catalogs:
node-resolve-ts: ^1.0.2
publint: ^0.3.13
ts-blank-space: ^0.6.2
tsdown: ^0.16.0
tsdown: ^0.18.0
typescript: ^5.0.0
valibot: ^1.1.0
vite: ^7.0.4
vite-plugin-devtools-json: ^1.0.0
vitest: ^4.0.0
zimmerframe: ^1.1.4
useNodeVersion: 22.19.0