Compare commits

...

55 Commits

Author SHA1 Message Date
Paolo Ricciuti
9a74df198d Merge pull request #57 from sveltejs/changeset-release/main 2025-10-09 18:34:54 +02:00
github-actions[bot]
f1280b9876 Version Packages 2025-10-09 16:33:34 +00:00
paoloricciuti
132943db3b feat: publish mcp to registry (maybe for real this time) 2025-10-09 18:30:21 +02:00
paoloricciuti
5ed1454e2c fix: use right curl param 2025-10-09 18:27:21 +02:00
Paolo Ricciuti
bd072bd324 Merge pull request #54 from sveltejs/changeset-release/main 2025-10-09 18:24:56 +02:00
github-actions[bot]
c35be898da Version Packages 2025-10-09 16:23:57 +00:00
paoloricciuti
b960301ced fix: add id-token: write permission to release.yml 2025-10-09 18:23:26 +02:00
Paolo Ricciuti
43408f5504 Merge pull request #45 from sveltejs/publish-to-registry 2025-10-09 18:18:26 +02:00
Paolo Ricciuti
bf4dcda7e1 Merge pull request #55 from mquandalle/fix-claude-code-command-docs
docs: fix CLI command syntax for Claude Code and Gemini CLI
2025-10-09 08:25:22 +02:00
Paolo Ricciuti
35691c464b docs: fix gemini 2025-10-09 08:10:38 +02:00
Paolo Ricciuti
a5decb4cf3 Merge pull request #56 from sveltejs/link-to-docs 2025-10-08 17:36:30 +02:00
paoloricciuti
cb9764c234 docs: add link to CLAUDE.md and AGENTS.md docs 2025-10-08 17:22:15 +02:00
paoloricciuti
3fbc786383 fix: verify checksum 2025-10-08 17:20:16 +02:00
Maxime Quandalle
3b680232a0 Fix CLI command syntax for Claude Code and Gemini CLI
Add missing `--` separator between CLI options and npx command.
Without this separator, the `-y` option is incorrectly parsed as a
CLI option instead of being passed to npx.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 10:22:01 +02:00
paoloricciuti
c5c08ccd13 fix: lint 2025-10-08 00:09:23 +02:00
paoloricciuti
4ac35bf258 chore: separate workflows 2025-10-08 00:06:12 +02:00
paoloricciuti
ecbbf70d98 fix: call changeset version in script 2025-10-07 23:50:10 +02:00
paoloricciuti
aaac49cdef chore: update version of server.json on release 2025-10-07 23:49:28 +02:00
paoloricciuti
558d964919 Merge branch 'main' into publish-to-registry 2025-10-07 23:35:45 +02:00
paoloricciuti
73d7625b3c chore: changesets 2025-10-07 21:34:19 +02:00
Paolo Ricciuti
c7060c8bdb Merge pull request #20 from sveltejs/attachments-autofixer 2025-10-07 21:31:50 +02:00
Paolo Ricciuti
ef2d569934 Merge pull request #26 from sveltejs/function-call-in-effect 2025-10-07 21:31:21 +02:00
Paolo Ricciuti
7ba57b45ae Merge pull request #52 from theetherGit/zed-config 2025-10-07 12:04:00 +02:00
Paolo Ricciuti
fe393bf480 docs: remove command shortcut 2025-10-07 11:59:21 +02:00
theetherGit
a63deba99d chore: Lint 2025-10-07 15:05:36 +05:30
Paolo Ricciuti
668a2e4481 Merge pull request #51 from sveltejs/renovate/all-minor-patch 2025-10-07 10:23:08 +02:00
Shivam Meena
04c82875f3 Add MCP to zed guide 2025-10-07 12:12:05 +05:30
renovate[bot]
84601f9ab0 chore(deps): update pnpm to v10.18.1 2025-10-06 14:34:35 +00:00
Paolo Ricciuti
e01a050017 Merge pull request #50 from sveltejs/changeset-release/main 2025-10-06 12:36:23 +02:00
github-actions[bot]
5920f7482f Version Packages 2025-10-06 10:30:53 +00:00
Paolo Ricciuti
fa90a2be8d Merge pull request #49 from sveltejs/allow-ts-modules 2025-10-06 12:30:23 +02:00
paoloricciuti
73bf0c3782 fix: allow TS .svelte.ts modules 2025-10-06 12:13:22 +02:00
paoloricciuti
11cd2447fc feat: publish to registry 2025-10-05 12:13:34 +02:00
Paolo Ricciuti
8785ad224c Merge pull request #40 from sveltejs/changeset-release/main 2025-10-05 11:24:27 +02:00
github-actions[bot]
75e676d928 Version Packages 2025-10-04 22:54:37 +00:00
Paolo Ricciuti
5b16bdd80b Merge pull request #41 from 43081j/effect-pre-assign
fix: check effect.pre in assign-in-effect
2025-10-05 00:54:12 +02:00
James Garbutt
f3ee4ed59c chore: run format 2025-10-04 20:12:47 +01:00
paoloricciuti
725f785766 chore: changeset 2025-10-04 16:50:04 +02:00
paoloricciuti
485e60e245 fix: change title names to allow claude code to invoke the prompt 2025-10-04 16:49:23 +02:00
Paolo Ricciuti
480bfca557 Merge pull request #38 from sveltejs/renovate/actions-setup-node-5.x 2025-10-04 16:40:42 +02:00
renovate[bot]
06f9fc6d63 chore(deps): update actions/setup-node action to v5 2025-10-04 14:39:44 +00:00
Paolo Ricciuti
f1f85d2445 Merge pull request #33 from sveltejs/renovate/actions-checkout-5.x 2025-10-04 16:39:20 +02:00
renovate[bot]
b9c0a011e2 chore(deps): update actions/checkout action to v5 2025-10-04 14:37:40 +00:00
Paolo Ricciuti
b92dd95dee Merge pull request #32 from sveltejs/renovate/all-minor-patch 2025-10-04 16:37:15 +02:00
Paolo Ricciuti
99537cfa25 chore: add changesets 2025-10-04 16:34:43 +02:00
James Garbutt
f93a6cee60 fix: check effect.pre in assign-in-effect
It seems like the intention here was to check `$effect` _and_
`$effect.pre`, but the condition (`in_effect`) was filtering those out.
The `is_rune` helper already does the same checks, so we can just remove
the ones here and delegate to that.
2025-10-04 12:18:08 +01:00
renovate[bot]
e8989db548 chore(deps): update all non-major dependencies 2025-10-04 07:55:47 +00:00
paoloricciuti
cb316c5b3e fix: enable doc tools 2025-10-04 09:54:14 +02:00
Paolo Ricciuti
7c762109b6 Merge pull request #39 from sveltejs/llms-txt 2025-10-04 01:58:09 +02:00
paoloricciuti
8483bd672d feat: add autofixer to tell the LLM to check if some function called in effect is assigning state 2025-09-26 13:04:11 +02:00
paoloricciuti
8414ffbcc8 fix: move attachments fixer 2025-09-23 23:05:53 +02:00
paoloricciuti
5798b50ceb Merge branch 'main' into attachments-autofixer 2025-09-23 23:04:38 +02:00
paoloricciuti
e560932211 Merge branch 'main' into attachments-autofixer 2025-09-23 23:03:13 +02:00
paoloricciuti
9504e6bac9 feat: autofixer action -> attachment 2025-09-23 00:49:09 +02:00
paoloricciuti
7086e8e55f feat: add bind:this -> attachment autofixer 2025-09-23 00:17:36 +02:00
28 changed files with 773 additions and 467 deletions

View File

@@ -1,5 +0,0 @@
---
'@sveltejs/mcp': patch
---
feat: `use_cases` documentation metadata

View File

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

View File

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

37
.github/workflows/publish-mcp.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: Publish to MCP Registry
on:
workflow_call:
permissions:
id-token: write # OpenID Connect token needed for MCP registry authentication
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
run: |
NAME=mcp-publisher_1.2.3_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz
# Download MCP Publisher pinned to v1.2.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.2.3/$NAME" -O
# Verify the SHA256 checksum of the downloaded file
sha256sum --ignore-missing -c ./checksums/registry_1.2.3_checksums.txt
# Extract the tarball
tar -xzf $NAME --no-same-owner --no-same-permissions -C tmp
# Install the MCP Publisher binary
install -m 0755 tmp/mcp-publisher
# Login using GitHub OIDC
./mcp-publisher login github-oidc
# Publish to MCP Registry
./mcp-publisher publish

View File

@@ -16,6 +16,8 @@ jobs:
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
@@ -51,8 +53,17 @@ jobs:
# 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'
permissions:
id-token: write
uses: ./.github/workflows/publish-mcp.yml

View File

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

View File

@@ -39,15 +39,15 @@
"devDependencies": {
"@eslint/compat": "^1.3.2",
"@eslint/js": "^9.36.0",
"@libsql/client": "^0.14.0",
"@modelcontextprotocol/inspector": "^0.16.7",
"@libsql/client": "^0.15.0",
"@modelcontextprotocol/inspector": "^0.17.0",
"@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.30.2",
"drizzle-orm": "^0.40.0",
"drizzle-kit": "^0.31.0",
"drizzle-orm": "^0.44.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-svelte": "^3.12.3",
"globals": "^16.0.0",

View File

@@ -13,7 +13,7 @@ The setup varies based on the version of the MCP you prefer — remote or local
## Usage
To get the most out of the MCP server we recommend including the following prompt in your `AGENTS.md` (or `CLAUDE.md`, if using Claude Code). This will tell the LLM which tools are available and when it's appropriate to use them.
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:

View File

@@ -15,7 +15,7 @@ Here's how to set it up in some common MCP clients:
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
claude mcp add -t stdio -s [scope] svelte -- npx -y @sveltejs/mcp
```
The `[scope]` must be `user`, `project` or `local`.
@@ -108,6 +108,25 @@ It will open a file with your MCP servers where you can add the following config
}
```
## 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

@@ -3,7 +3,7 @@
"version": "0.0.1",
"description": "The official Svelte MCP server implementation",
"type": "module",
"packageManager": "pnpm@10.17.1",
"packageManager": "pnpm@10.18.1",
"scripts": {
"build": "pnpm -r run build",
"dev": "pnpm --filter @sveltejs/mcp-remote run dev",
@@ -18,7 +18,8 @@
"inspect": "pnpm mcp-inspector",
"generate-summaries": "pnpm --filter @sveltejs/mcp-server run generate-summaries",
"debug:generate-summaries": "pnpm --filter @sveltejs/mcp-server run debug:generate-summaries",
"release": "pnpm --filter @sveltejs/mcp run build && changeset publish"
"release": "pnpm --filter @sveltejs/mcp run build && changeset publish",
"changeset:version": "changeset version && pnpm --filter @sveltejs/mcp run update:version && git add --all"
},
"keywords": [
"svelte",
@@ -31,7 +32,7 @@
"@changesets/cli": "^2.29.7",
"@eslint/compat": "^1.3.2",
"@eslint/js": "^9.36.0",
"@modelcontextprotocol/inspector": "^0.16.7",
"@modelcontextprotocol/inspector": "^0.17.0",
"@svitejs/changesets-changelog-github-compact": "^1.2.0",
"eslint": "^9.36.0",
"eslint-config-prettier": "^10.0.1",

View File

@@ -14,6 +14,6 @@
"license": "ISC",
"type": "module",
"dependencies": {
"drizzle-orm": "^0.40.1"
"drizzle-orm": "^0.44.0"
}
}

View File

@@ -17,7 +17,7 @@
".": "./src/index.ts"
},
"peerDependencies": {
"drizzle-orm": "^0.40.0"
"drizzle-orm": "^0.44.0"
},
"dependencies": {
"@sveltejs/mcp-schema": "workspace:^",
@@ -28,6 +28,7 @@
"svelte": "^5.39.2",
"svelte-eslint-parser": "^1.3.2",
"tmcp": "^1.13.0",
"ts-blank-space": "^0.6.2",
"typescript-eslint": "^8.44.0",
"valibot": "^1.1.0",
"vitest": "^3.2.4",
@@ -35,10 +36,10 @@
},
"devDependencies": {
"@anthropic-ai/sdk": "^0.65.0",
"dotenv": "^17.2.3",
"@sveltejs/kit": "^2.42.2",
"@types/eslint-scope": "^8.3.2",
"@types/estree": "^1.0.8",
"@typescript-eslint/types": "^8.44.0"
"@typescript-eslint/types": "^8.44.0",
"dotenv": "^17.2.3"
}
}

View File

@@ -61,7 +61,7 @@ describe('add_autofixers_issues', () => {
<script>
const count = ${init}(0);
</script>
<button onclick={() => count = 43}>Increment</button>
`);
@@ -74,7 +74,7 @@ describe('add_autofixers_issues', () => {
const content = run_autofixers_on_code(`
<script>
const count = 0;
$effect(() => {
count = 43;
});
@@ -89,7 +89,7 @@ describe('add_autofixers_issues', () => {
const content = run_autofixers_on_code(`
<script>
let count = ${init}(0);
$effect(() => {
count++;
});
@@ -105,7 +105,7 @@ describe('add_autofixers_issues', () => {
const content = run_autofixers_on_code(`
<script>
let count = ${init}({ value: 0 });
$effect(() => {
count.value = 42;
});
@@ -116,6 +116,52 @@ describe('add_autofixers_issues', () => {
'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', () => {
const content = run_autofixers_on_code(`
<script>
import { fetch_data } from './data.js';
$effect(() => {
fetch_data();
});
</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.`,
);
});
it('should add a suggestion when calling a function inside an effect (with non identifier callee)', () => {
const content = run_autofixers_on_code(`
<script>
import { fetch_data } from './data.js';
$effect(() => {
fetch_data.fetch();
});
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
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.`,
);
});
});
@@ -468,4 +514,177 @@ 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".',
);
});
});
});
});

View File

@@ -1,4 +1,6 @@
import { compile } from 'svelte/compiler';
import { compile as compile_component, compileModule } from 'svelte/compiler';
import { extname } from 'path';
import ts from 'ts-blank-space';
export function add_compile_issues(
content: { issues: string[]; suggestions: string[] },
@@ -6,6 +8,21 @@ 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'],
files: ['*.svelte', '*.svelte.ts', '*.svelte.js'],
rules: {
'no-self-assign': 'warn',
'svelte/infinite-reactive-loop': 'warn',

View File

@@ -1,4 +1,10 @@
import type { AssignmentExpression, Identifier, Node, UpdateExpression } from 'estree';
import type {
AssignmentExpression,
CallExpression,
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';
@@ -11,25 +17,17 @@ function run_if_in_effect(
) {
const in_effect = path.findLast(
(node) =>
node.type === 'CallExpression' &&
node.callee.type === 'Identifier' &&
node.callee.name === '$effect',
node.type === 'CallExpression' && state.parsed.is_rune(node, ['$effect', '$effect.pre']),
);
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();
}
if (in_effect) {
to_run();
}
}
function visitor(
function assign_or_update_visitor(
node: UpdateExpression | AssignmentExpression,
{ state, path }: Context<Node | AST.SvelteNode, AutofixerState>,
{ state, path, next }: Context<Node | AST.SvelteNode, AutofixerState>,
) {
run_if_in_effect(path, state, () => {
function check_if_stateful_id(id: Identifier) {
@@ -58,9 +56,25 @@ function 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: visitor,
AssignmentExpression: visitor,
UpdateExpression: assign_or_update_visitor,
AssignmentExpression: assign_or_update_visitor,
CallExpression: call_expression_visitor,
};

View File

@@ -16,3 +16,4 @@ 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';

View File

@@ -0,0 +1,47 @@
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];
console.log(definition);
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

@@ -6,7 +6,7 @@ export function setup_svelte_task(server: SvelteMcp) {
server.prompt(
{
name: 'svelte-task-prompt',
title: '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({

View File

@@ -6,7 +6,7 @@ export async function list_sections(server: SvelteMcp) {
server.template(
{
name: 'Svelte Doc Section',
name: 'Svelte-Doc-Section',
description: 'A single documentation section',
list() {
return sections.map((section) => {

View File

@@ -7,7 +7,6 @@ export function get_documentation(server: SvelteMcp) {
server.tool(
{
name: 'get-documentation',
enabled: () => false,
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.',
schema: v.object({

View File

@@ -6,7 +6,6 @@ export function list_sections(server: SvelteMcp) {
server.tool(
{
name: 'list-sections',
enabled: () => false,
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.',
},

View File

@@ -1,5 +1,41 @@
# @sveltejs/mcp
## 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

View File

@@ -0,0 +1,25 @@
4e7c92f289d903d4649c59f82df6e7b014caebb49d8b5c1d2466667fd6caf68c mcp-publisher_1.2.3_darwin_amd64.tar.gz
09637600871bec7cce25e098d6ce23b7c7577a8e537f618d24a5630d2d782afa mcp-publisher_1.2.3_darwin_amd64.tar.gz.sbom.json
9ac90d3182bae9af4b48fb3cd19c3cade3e52c8ea25d7aa14327326a209bcf71 mcp-publisher_1.2.3_darwin_arm64.tar.gz
0b2cedf3f42c5a7b33bdd3f72ce7f4c938ff4fe2aca5cabd60094dc4b855c1f6 mcp-publisher_1.2.3_darwin_arm64.tar.gz.sbom.json
868a54268f21580ec97ec9dfc4fc3442a7f69c241ee7c745c7032f2e43cdf47b mcp-publisher_1.2.3_linux_amd64.tar.gz
a7bce190ea1c1b5682d1b5069b20dd9c67f3688e02dbc93e7273e942097dac4d mcp-publisher_1.2.3_linux_amd64.tar.gz.sbom.json
319cdefb4c4f19fa35eeb0337e30be3169dbf3107a8c160651691a6342a6783b mcp-publisher_1.2.3_linux_arm64.tar.gz
bc9309ab843531e811990dcf637b9912c470fc38eddc13a5af0512fcef498944 mcp-publisher_1.2.3_linux_arm64.tar.gz.sbom.json
298026e0252547046f50dcb7817239d44107f5a3ae64f4e43b6a31d0c5b89f9b mcp-publisher_1.2.3_windows_amd64.tar.gz
971b835ce4df2b37ddcf8591d4400dc94d1e3f730b181b325c905261b75b9e6e mcp-publisher_1.2.3_windows_amd64.tar.gz.sbom.json
64da67b451d0fba12f15f7b0f6ab8d71c718bb2263f90f198ff1fe15a5ff2024 mcp-publisher_1.2.3_windows_arm64.tar.gz
f591b2153f1277cb67817763a23de9e77188fa94b893fac00e91d13160d696e3 mcp-publisher_1.2.3_windows_arm64.tar.gz.sbom.json
265377b343500898e3c3fea798926ff2695fe7500c1225c8a97f390a5892d849 registry-1.2.3.tar.gz
a9c03a84d2e30176d6ed7b493e7e3a8a05d446f1611a35bad6007fbe3ba80657 registry_1.2.3_darwin_amd64.tar.gz
4db4e8a0b1f3d0ca193e5356fef89b74c0666cdf56d6f3c8fc2d0aba1cac3b55 registry_1.2.3_darwin_amd64.tar.gz.sbom.json
2933a11990db16035e4896f394ada389166046911e6868f79b84636048dfe6fb registry_1.2.3_darwin_arm64.tar.gz
edd36e62f4b1c5b857da1862f20cbcf6dd3dfd9717888c08bd5c25740d8b11f6 registry_1.2.3_darwin_arm64.tar.gz.sbom.json
59bdd9977795891220e6fdb8f79dd7d14f48166362e94699bee59e745f7c7df6 registry_1.2.3_linux_amd64.tar.gz
7f7efb180aa312f674377e32a92ac3600e50c62e37d697d2d16acfe60227a2fd registry_1.2.3_linux_amd64.tar.gz.sbom.json
7fef1088a664850dd75e54a699fce6a5fb32e8ebbdc0d2869b6b34945727ba9c registry_1.2.3_linux_arm64.tar.gz
ca0488e5cbb04c8ee515e1b80e08b755a55d6af1784368a2f55e429137359a25 registry_1.2.3_linux_arm64.tar.gz.sbom.json
5bc29310aacc08437dcc973cfc1e1129f7065d2050396012f5ed98c4eb0ea75b registry_1.2.3_windows_amd64.tar.gz
e918c604f23e6d33f9952f18eddf0e50e50d7ce3e87ca5258bf78f4f1da243ab registry_1.2.3_windows_amd64.tar.gz.sbom.json
394cad1e6f5e37a583defdd522edbff854de5035d6e025a195664db31b58c0c0 registry_1.2.3_windows_arm64.tar.gz
c93df66f3257ba4b9c29f7bfded4cf04d712ec220b044ddfa019da3e4bd6475f registry_1.2.3_windows_arm64.tar.gz.sbom.json

View File

@@ -1,8 +1,9 @@
{
"name": "@sveltejs/mcp",
"version": "0.0.2",
"version": "0.1.1",
"type": "module",
"license": "MIT",
"mcpName": "io.github.sveltejs/svelte",
"homepage": "https://github.com/sveltejs/mcp#readme",
"bugs": {
"url": "https://github.com/sveltejs/mcp/issues"
@@ -26,14 +27,15 @@
"dev": "tsdown --watch",
"test": "vitest",
"check": "tsc --noEmit",
"check:publint": "publint --strict"
"check:publint": "publint --strict",
"update:version": "node scripts/update-version.ts"
},
"devDependencies": {
"@sveltejs/mcp-server": "workspace:^",
"@tmcp/transport-stdio": "^0.3.1",
"@types/node": "^22.15.17",
"publint": "^0.3.13",
"tsdown": "^0.11.9",
"tsdown": "^0.15.0",
"typescript": "^5.8.3",
"vitest": "^3.1.3"
},

View File

@@ -0,0 +1,14 @@
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

@@ -0,0 +1,30 @@
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json",
"name": "io.github.sveltejs/svelte",
"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.1",
"websiteUrl": "https://svelte.dev/docs/mcp/overview",
"packages": [
{
"registryType": "npm",
"identifier": "@sveltejs/mcp",
"version": "0.1.1",
"runtimeHint": "npx",
"transport": {
"type": "stdio"
}
}
],
"remotes": [
{
"url": "https://mcp.svelte.dev/mcp",
"type": "streamable-http"
}
]
}

661
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff