Compare commits

..

130 Commits

Author SHA1 Message Date
Paolo Ricciuti
1f296e5277 Merge pull request #35 from sveltejs/changeset-release/main 2025-10-03 22:42:11 +02:00
github-actions[bot]
121395e98e Version Packages 2025-10-03 20:38:35 +00:00
paoloricciuti
8e7c881838 fix: just build we can figure out later if we want to avoid it 2025-10-03 22:38:08 +02:00
Paolo Ricciuti
91c396e675 Merge pull request #25 from sveltejs/setup-changesets 2025-10-03 22:35:50 +02:00
paoloricciuti
baad760634 chore: add changeset to trigger release 2025-10-03 22:34:55 +02:00
paoloricciuti
b2275587ee fix: remove package manager from inner packages 2025-10-03 22:33:56 +02:00
Paolo Ricciuti
4964303100 chore: apply suggestions from code review
Co-authored-by: Dominik G. <dominik.goepel@gmx.de>
2025-10-03 22:31:46 +02:00
paoloricciuti
81901b2564 fix: small configs 2025-10-03 22:03:59 +02:00
paoloricciuti
5aa1aa401a Merge branch 'main' into setup-changesets 2025-10-03 21:52:45 +02:00
Rich Harris
4201627f53 Merge pull request #31 from sveltejs/docs
docs: add documentation for svelte.dev
2025-10-03 15:22:59 -04:00
paoloricciuti
9a70fbe3aa feat: redirect to docs in case someone visits via browser 2025-10-03 21:12:42 +02:00
Paolo Ricciuti
dc16a42c65 chore: apply suggestions from code review
Co-authored-by: Rich Harris <hello@rich-harris.dev>
2025-10-03 19:11:14 +02:00
Paolo Ricciuti
3b50014b09 chore: apply suggestions from code review
Co-authored-by: Rich Harris <hello@rich-harris.dev>
2025-10-03 18:43:56 +02:00
Paolo Ricciuti
e9214bc470 chore: apply suggestions from code review
Co-authored-by: Rich Harris <hello@rich-harris.dev>
2025-10-03 18:33:31 +02:00
Paolo Ricciuti
3106305902 chore: apply suggestions from code review
Co-authored-by: Rich Harris <hello@rich-harris.dev>
2025-10-03 18:24:35 +02:00
paoloricciuti
3c14872068 fix: LLM -> model 2025-10-03 17:20:38 +02:00
paoloricciuti
216a470bd2 docs: add documentation for svelte.dev 2025-10-03 16:49:43 +02:00
paoloricciuti
b5a88c454d fix: small typos 2025-10-03 16:49:24 +02:00
Paolo Ricciuti
7b5bea6549 Merge pull request #28 from sveltejs/llms-txt
feat: simplified documentation listing
2025-10-02 11:46:33 +02:00
paoloricciuti
a36d0d17a8 fix: validate response and default use_cases 2025-10-02 11:29:35 +02:00
paoloricciuti
70f14bddca feat: use endpoint to get sections 2025-10-02 08:03:03 +02:00
paoloricciuti
a281ef4b66 fix: use server.template with list and complete for docs resources 2025-09-28 12:19:43 +02:00
paoloricciuti
5bc812e4db fix: enable list-sections tool 2025-09-28 12:19:21 +02:00
paoloricciuti
82319661dd feat: add sections to prompt 2025-09-28 12:19:03 +02:00
paoloricciuti
ce0861c1ca chore: use mocked current version of sections 2025-09-28 12:18:52 +02:00
Stanislav Khromov
5dd83d151e Update utils.ts 2025-09-27 00:14:00 +02:00
Stanislav Khromov
76a35f5dc8 format 2025-09-27 00:13:37 +02:00
Stanislav Khromov
54763e0f55 Update packages/mcp-server/src/mcp/utils.ts
Co-authored-by: Paolo Ricciuti <ricciutipaolo@gmail.com>
2025-09-27 00:07:18 +02:00
Stanislav Khromov
01d5803b5d reduce duplication 2025-09-27 00:06:37 +02:00
Stanislav Khromov
0366bc785b Merge branch 'llms-txt' of https://github.com/sveltejs/mcp into llms-txt 2025-09-27 00:03:49 +02:00
Stanislav Khromov
6a6417d3a5 Use Promise.allSettled() 2025-09-27 00:03:40 +02:00
Stanislav Khromov
77af7ebcc6 Update packages/mcp-server/src/mcp/handlers/tools/get-documentation.ts
Co-authored-by: Paolo Ricciuti <ricciutipaolo@gmail.com>
2025-09-27 00:01:50 +02:00
Stanislav Khromov
b1a196497d Update list-sections.ts 2025-09-27 00:00:15 +02:00
Stanislav Khromov
fb2d19fd07 Update list-sections.ts 2025-09-26 23:59:18 +02:00
Stanislav Khromov
8328a3572b eslint 2025-09-26 23:51:58 +02:00
Stanislav Khromov
c05b6c257a eslint 2025-09-26 23:51:48 +02:00
Stanislav Khromov
7f9ea742d8 don't lint .claude dir 2025-09-26 23:48:15 +02:00
Stanislav Khromov
bf477a6ccf Update get-documentation.ts 2025-09-26 23:46:07 +02:00
Stanislav Khromov
0f5482477a Update get-documentation.ts 2025-09-26 22:44:28 +02:00
Stanislav Khromov
b774b463fe Update get-documentation.ts 2025-09-26 22:42:42 +02:00
Stanislav Khromov
c49b24d36a wip 2025-09-26 22:39:52 +02:00
Stanislav Khromov
6cb97ac11d Update get-documentation.ts 2025-09-26 22:36:30 +02:00
Stanislav Khromov
d33a374417 Update get-documentation.ts 2025-09-26 21:56:38 +02:00
Stanislav Khromov
1bb171cea7 cleanup 2025-09-26 20:44:41 +02:00
Stanislav Khromov
e314ab57b2 Update list-sections.ts 2025-09-26 20:43:04 +02:00
Stanislav Khromov
19cacf7ed9 refactor 2025-09-26 20:39:22 +02:00
Stanislav Khromov
68cf69a117 wip 2025-09-26 20:38:17 +02:00
Stanislav Khromov
917fdf63b1 enable 2025-09-26 20:34:57 +02:00
Stanislav Khromov
972cadc410 Update package.json 2025-09-26 20:30:16 +02:00
Stanislav Khromov
dc6c87ce37 Merge branch 'main' into llms-txt 2025-09-26 20:30:11 +02:00
Stanislav Khromov
e7431e9024 Update README.md 2025-09-26 20:29:55 +02:00
Stanislav Khromov
07737a8edd Add Claude Code .mcp.json config 2025-09-26 20:22:50 +02:00
Stanislav Khromov
fdb7689992 fix stdio path 2025-09-26 20:21:43 +02:00
paoloricciuti
47fa0a4382 fix: add $derived and $derived.by to wrong-property-access-state 2025-09-26 12:39:40 +02:00
paoloricciuti
4c6232a44f fix: add $derived and $derived.by to the reassignment list 2025-09-26 12:33:33 +02:00
paoloricciuti
8edbf2f36b fix: disable todos tools 2025-09-26 12:15:11 +02:00
paoloricciuti
6e54719f88 chore: update deps to fix tmcp issue 2025-09-26 10:01:45 +02:00
paoloricciuti
1a283f60bc chore: disable unimplemented tools/resources 2025-09-24 22:11:21 +02:00
paoloricciuti
bb16ccca3a fix: pnpm versions and package.json 2025-09-24 16:06:32 +02:00
paoloricciuti
23ddaf9495 fix: update pnpm version 2025-09-24 15:59:34 +02:00
paoloricciuti
4228302ed0 fix: package.json's 2025-09-24 15:57:17 +02:00
Paolo Ricciuti
0bc4d75e13 chore: use the right repo name 🤦
Co-authored-by: Dominik G. <dominik.goepel@gmx.de>
2025-09-24 15:53:12 +02:00
paoloricciuti
4679549401 chore: setup changesets 2025-09-24 15:43:42 +02:00
paoloricciuti
6b15eb0790 feat: add .$ property access autofixer 2025-09-24 15:07:00 +02:00
Paolo Ricciuti
a7041a4c5e Merge pull request #23 from sveltejs/setup-cli-project 2025-09-24 15:04:08 +02:00
paoloricciuti
023bea317f Merge branch 'main' into setup-cli-project 2025-09-24 10:45:48 +02:00
paoloricciuti
7a6cba8772 fix: add suggestion for $state variable_name 2025-09-24 10:45:15 +02:00
paoloricciuti
fd32b67442 fix: lint and check 2025-09-24 10:44:41 +02:00
paoloricciuti
0e3b1ba22f fix: right replacement for __filename 2025-09-24 10:13:19 +02:00
paoloricciuti
0aad39d076 fix: package.json setup and tsdown config 2025-09-24 10:09:42 +02:00
paoloricciuti
2fec290d54 fix: lint and check 2025-09-24 10:05:24 +02:00
paoloricciuti
4e59ef751a fix: rename project 2025-09-24 10:05:15 +02:00
paoloricciuti
c87c9e0715 feat: setup cli project 2025-09-24 09:43:06 +02:00
Paolo Ricciuti
12f8d84852 Merge pull request #22 from sveltejs/great-monorepo-restructure 2025-09-23 23:02:37 +02:00
paoloricciuti
bde37da5d5 chore: add .editorconfig, .gitattributes and renovate.json 2025-09-23 23:01:26 +02:00
paoloricciuti
a50844e388 fix: remove double gitignore 2025-09-23 22:57:14 +02:00
paoloricciuti
6a71229d56 fix: remove ts.config 2025-09-23 18:17:26 +02:00
paoloricciuti
92d8532c8a chore: add DATABASE_TOKEN 2025-09-23 18:10:35 +02:00
paoloricciuti
c8300bc62e fix: lint and typecheck 2025-09-23 18:02:10 +02:00
paoloricciuti
de78f7663f chore: add check recursive script 2025-09-23 16:56:45 +02:00
paoloricciuti
e57b76324f chore: add build script 2025-09-23 16:51:38 +02:00
paoloricciuti
09331e2c2b chore: add eslint locally 2025-09-23 16:50:58 +02:00
paoloricciuti
f1aef9ca2f chore: great monorepo restructuring 2025-09-23 16:45:05 +02:00
paoloricciuti
6c072534ea fix: use installed inspector and remove DANGEROUSLY_OMIT_AUTH 2025-09-23 11:22:26 +02:00
paoloricciuti
2f8165f1d7 chore: add issue templates 2025-09-23 09:28:36 +02:00
Paolo Ricciuti
d93d3a3507 Merge pull request #17 from sveltejs/playground-link-tool 2025-09-22 23:54:41 +02:00
paoloricciuti
039718f1a5 fix: ask the user to generate instead of generate by default 2025-09-22 23:48:24 +02:00
paoloricciuti
ac287a2c83 feat: playground-link tool 2025-09-22 15:57:59 +02:00
paoloricciuti
224d630a32 chore: add explicit extensions eslint rule 2025-09-22 15:11:38 +02:00
Paolo Ricciuti
4a9afb5ee1 Merge pull request #16 from sveltejs/restructure 2025-09-22 10:46:42 +02:00
Paolo Ricciuti
e68067e995 Merge pull request #15 from sveltejs/no-lib-eslint-rul 2025-09-22 10:35:04 +02:00
paoloricciuti
8258a1c9ba fix: lint 2025-09-22 10:33:35 +02:00
paoloricciuti
5aa2827c91 chore: add eslint to prevent importing with $lib inthe lib folder 2025-09-22 10:00:37 +02:00
paoloricciuti
a35d72cc6b fix: update deps 2025-09-22 09:44:18 +02:00
paoloricciuti
0c35883074 Merge branch 'main' into init-docs 2025-09-21 16:22:26 +02:00
paoloricciuti
d82c20acd6 fix: update to latest transport 2025-09-21 16:22:06 +02:00
paoloricciuti
cc3ea75c7f fix: lint 2025-09-21 14:52:11 +02:00
paoloricciuti
68724731c7 chore: add snippets to create autofixers and stup functions 2025-09-21 14:50:01 +02:00
paoloricciuti
bf1a4178bf fix: move handler in their own folder while keeping TS 2025-09-21 14:49:44 +02:00
paoloricciuti
050e588709 Merge branch 'main' into init-docs 2025-09-21 13:54:14 +02:00
Paolo Ricciuti
731b4f6548 Merge pull request #14 from sveltejs/close-sse-stream 2025-09-21 13:50:22 +02:00
paoloricciuti
582e0e1dea fix: close SSE stream immediately 2025-09-20 23:53:42 +02:00
Paolo Ricciuti
bb9a6e07ea Merge pull request #13 from sveltejs/import-stores-autofixer
feat: add use runes instead of stores autofixer
2025-09-20 09:19:36 +02:00
Stanislav Khromov
0d17b81948 Merge pull request #12 from sveltejs/eslint-conventions
chore: add eslint rules for naming convention and function declaration
2025-09-20 02:18:16 +02:00
paoloricciuti
81640c9a16 fix: describe filename and accept string version 2025-09-20 01:39:17 +02:00
paoloricciuti
8587bc8625 fix: wat? 2025-09-20 01:22:48 +02:00
paoloricciuti
0475e3b0f9 fix: us ts parser in svelte parser 2025-09-19 22:47:36 +02:00
paoloricciuti
4e1a42ab52 fix: remove from recommended 2025-09-19 22:21:10 +02:00
paoloricciuti
862f614afc fix: duh 2025-09-19 22:09:59 +02:00
paoloricciuti
a92ae954bd fix: nuke rules completely 2025-09-19 22:08:11 +02:00
paoloricciuti
e3b5188c6d fix: maybe this? 2025-09-19 22:02:13 +02:00
paoloricciuti
6b5f2092b5 fix: again 2025-09-19 21:56:32 +02:00
paoloricciuti
089e690f3e fix: what about this? 2025-09-19 21:53:04 +02:00
paoloricciuti
1c60e350a6 fix: what about this? 2025-09-19 20:38:30 +02:00
paoloricciuti
a93a6554b5 fix: is this the issue? 2025-09-19 20:37:07 +02:00
paoloricciuti
94f7d65db3 fix: latest plugin svelte? 2025-09-19 19:48:56 +02:00
paoloricciuti
d7492bb1cb fix: more stuff to deps? 2025-09-19 19:43:30 +02:00
paoloricciuti
e1e2bf68ae fix: move typescript eslint to the dependencies? 2025-09-19 19:39:41 +02:00
paoloricciuti
0ff628f5b4 fix: latest eslint maybe? 2025-09-19 19:32:40 +02:00
paoloricciuti
ea35d600e4 fix: lock 2025-09-19 19:18:49 +02:00
paoloricciuti
dcbcd5b690 fix: move eslint deps to dependencies 2025-09-19 19:16:38 +02:00
paoloricciuti
d81d6a3d95 chore: add eslint rules for naming convention and function declaration 2025-09-19 12:30:44 +02:00
Stanislav Khromov
dedfd0b3b7 Move auto fixer handler 2025-09-19 01:20:04 +02:00
Stanislav Khromov
5d50518c3c types 2025-09-19 01:17:02 +02:00
Stanislav Khromov
74d2fb8f0e Update getDocumentationHandler.ts 2025-09-19 01:15:58 +02:00
Stanislav Khromov
86675ea1d7 Update index.ts 2025-09-19 01:08:03 +02:00
Stanislav Khromov
830fd73ab1 move handlers to separate directory 2025-09-19 01:06:53 +02:00
Stanislav Khromov
da995bdc69 Update index.ts 2025-09-19 01:05:19 +02:00
Stanislav Khromov
26b3986740 wip 2025-09-19 01:01:46 +02:00
Stanislav Khromov
8d53f56151 add tmcp docs 2025-09-19 00:55:36 +02:00
103 changed files with 5229 additions and 1283 deletions

8
.changeset/README.md Normal file
View File

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

11
.changeset/config.json Normal file
View File

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

2
.cocoignore Normal file
View File

@@ -0,0 +1,2 @@
.claude
.github

9
.editorconfig Normal file
View File

@@ -0,0 +1,9 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
indent_style = tab
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true

3
.gitattributes vendored Normal file
View File

@@ -0,0 +1,3 @@
* text=auto eol=lf
/packages/**/test/** -linguist-detectable
/packages/**/fixtures/** -linguist-detectable

View File

@@ -0,0 +1,36 @@
name: 'Autofixer Request'
description: Request a new Autofixer for the MCP
title: '[Autofixer Request] '
labels: [enhancement, autofixer]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to request A new autofixer!
- type: textarea
id: problem
attributes:
label: Describe the problematic code
description: Please provide a clear and concise description the problem. Is much better if you can provide a code snippet the AI constantly get's wrong.
placeholder: The AI keeps messing with...
validations:
required: true
- type: textarea
id: suggestion
attributes:
label: Describe what the autofixer should suggest
description: If you were looking at this code, what would you suggest to the AI for it to fix it?
placeholder: You should never do this, instead do that...
validations:
required: true
- type: dropdown
id: importance
attributes:
label: Importance
description: How important is this feature to you?
options:
- nice to have
- would make my life easier
- the MCP is useless to me without it
validations:
required: true

52
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,52 @@
name: "\U0001F41E Bug report"
description: Report an issue with Svelte
title: '[Bug] '
labels: ['triage: bug']
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: textarea
id: bug-description
attributes:
label: Describe the bug
description: A clear and concise description of what the bug is. If you intend to submit a PR for this issue, tell us in the description. Thanks!
placeholder: Bug description
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Reproduction
description: Please provide a link to a repo or REPL that can reproduce the problem you ran into. If a report is vague (e.g. just a generic error message) and has no reproduction, it will receive a "need reproduction" label. If no reproduction is provided within a reasonable time-frame, the issue will be closed.
placeholder: Reproduction
validations:
required: true
- type: textarea
id: logs
attributes:
label: Logs
description: 'Please provide some logs or screenshot of the agentic workflow failing.'
render: shell
- type: input
id: mcp-client
attributes:
label: MCP Client
description: Which MCP client are you using?
render: shell
placeholder: claude-code, codex, opencode
validations:
required: true
- type: dropdown
id: severity
attributes:
label: Severity
description: Select the severity of this issue
options:
- annoyance
- minor functionality loss
- major functionality loss
- blocking all usage of the mcp
validations:
required: true

View File

@@ -0,0 +1,36 @@
name: 'Feature Request'
description: Request a new MCP feature
title: '[Feature Request] '
labels: [enhancement]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to request this feature! If your feature request is complex or substantial enough to warrant in-depth discussion, maintainers may close the issue and ask you to open an [RFC](https://github.com/sveltejs/rfcs).
- type: textarea
id: problem
attributes:
label: Describe the problem
description: Please provide a clear and concise description the problem this feature would solve. The more information you can provide here, the better.
placeholder: I'm always frustrated when...
validations:
required: true
- type: textarea
id: solution
attributes:
label: Describe the proposed solution
description: Please provide a clear and concise description of what you would like to happen.
placeholder: I would like to see...
validations:
required: true
- type: dropdown
id: importance
attributes:
label: Importance
description: How important is this feature to you?
options:
- nice to have
- would make my life easier
- the MCP is useless to me without it
validations:
required: true

View File

@@ -18,7 +18,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10
version: 10.17.1
- name: Setup Node.js
uses: actions/setup-node@v4
@@ -33,4 +33,5 @@ jobs:
run: pnpm run check
env:
DATABASE_URL: file:test.db
DATABASE_TOKEN: dummy-key
VOYAGE_API_KEY: dummy-key

View File

@@ -18,7 +18,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10
version: 10.17.1
- name: Setup Node.js
uses: actions/setup-node@v4
@@ -34,3 +34,4 @@ jobs:
env:
DATABASE_URL: file:test.db
VOYAGE_API_KEY: dummy-key
DATABASE_TOKEN: dummy-key

58
.github/workflows/release.yml vendored Normal file
View File

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

View File

@@ -18,7 +18,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10
version: 10.17.1
- name: Setup Node.js
uses: actions/setup-node@v4
@@ -34,9 +34,11 @@ jobs:
env:
DATABASE_URL: file:test.db
VOYAGE_API_KEY: dummy-key
DATABASE_TOKEN: dummy-key
- name: Run tests
run: pnpm run test
env:
DATABASE_URL: file:test.db
VOYAGE_API_KEY: dummy-key
DATABASE_TOKEN: dummy-key

29
.gitignore vendored
View File

@@ -1,12 +1,16 @@
node_modules
/apps/**/node_modules
/packages/**/node_modules
# Output
.output
.vercel
.netlify
.wrangler
/.svelte-kit
/build
/apps/**/.output
/apps/**/.vercel
/apps/**/.netlify
/apps/**/.wrangler
/**/.svelte-kit
/apps/**/build
/apps/**/dist
/packages/**/dist
# OS
.DS_Store
@@ -14,14 +18,25 @@ Thumbs.db
# Env
.env
/apps/**/.env
/packages/**/.env
.env.*
/apps/**/.env.*
/packages/**/.env.*
!.env.example
/apps/**/!.env.example
/packages/**/!.env.example
!.env.test
/apps/**/!.env.test
/packages/**/!.env.test
# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
/apps/**/vite.config.js.timestamp-*
/apps/**/vite.config.ts.timestamp-*
# SQLite
*.db
dist
/apps/**/*.db
/packages/**/*.db

View File

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

View File

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

33
.vscode/mcp-snippets.code-snippets vendored Normal file
View File

@@ -0,0 +1,33 @@
{
// Place your svelte-mcp workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
// Placeholders with the same ids are connected.
// Example:
"Setup Function": {
"scope": "javascript,typescript",
"prefix": "!setup-mcp",
"body": [
"import type { SvelteMcp } from '../../index.js';",
"import * as v from 'valibot';",
"",
"export function ${1:function_name}(server: SvelteMcp) {",
"\t$0",
"}",
],
"description": "Create a setup function for a tool/resource/prompt handler",
},
"Autofixer": {
"scope": "javascript,typescript",
"prefix": "!autofixer",
"body": [
"import type { Autofixer } from '.';",
"export const ${1:autofixer_name}: Autofixer = {",
"\t$0",
"};",
],
"description": "Create a setup export for an autofixer",
},
}

2
.vscode/mcp.json vendored
View File

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

View File

@@ -90,12 +90,12 @@ When connected to the svelte-llm MCP server, you have access to comprehensive Sv
## Available MCP Tools:
### 1. list_sections
### 1. list-sections
Use this FIRST to discover all available documentation sections. Returns a structured list with titles and paths.
When asked about Svelte or SvelteKit topics, ALWAYS use this tool at the start of the chat to find relevant sections.
### 2. get_documentation
### 2. get-documentation
Retrieves full documentation content for specific sections. Accepts single or multiple sections.
After calling the list_sections tool, you MUST analyze the returned documentation sections and then use the get_documentation tool to fetch ALL documentation sections that are relevant for the users task.
After calling the list-sections tool, you MUST analyze the returned documentation sections and then use the get_documentation tool to fetch ALL documentation sections that are relevant for the users task.

View File

@@ -6,12 +6,15 @@ Repo for the official Svelte MCP server.
```
pnpm i
cp .env.example .env
cp apps/mcp-remote/.env.example apps/mcp-remote/.env
pnpm dev
```
1. Set the VOYAGE_API_KEY for embeddings support
> [!NOTE]
> Currently to prevent having a bunch of Timeout logs on vercel we shut down the SSE channel immediately. This means that we can't use `server.log` and we are not sending `list-changed` notifications. We can use elicitation and sampling since those are sent on the same stream of the POST request
### Local dev tools
#### MCP inspector

View File

@@ -0,0 +1,70 @@
{
"name": "@sveltejs/mcp-remote",
"version": "0.0.1",
"description": "The official Svelte MCP server implementation",
"type": "module",
"main": "src/index.js",
"bin": {
"svelte-mcp": "./dist/lib/stdio.js"
},
"scripts": {
"start": "node src/index.js",
"dev": "vite dev",
"build": "vite build",
"build:mcp": "tsc --project tsconfig.build.json",
"prepublishOnly": "pnpm build:mcp",
"preview": "vite preview",
"prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"format": "prettier --write .",
"lint": "prettier --check . && eslint .",
"lint:fix": "prettier --write . && eslint . --fix",
"test:unit": "vitest",
"test": "npm run test:unit -- --run",
"test:watch": "npm run test:unit -- --watch",
"db:push": "drizzle-kit push",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:studio": "drizzle-kit studio",
"inspect": "pnpm mcp-inspector"
},
"keywords": [
"svelte",
"tmcp",
"mcp",
"server"
],
"private": true,
"devDependencies": {
"@eslint/compat": "^1.3.2",
"@eslint/js": "^9.36.0",
"@libsql/client": "^0.14.0",
"@modelcontextprotocol/inspector": "^0.16.7",
"@sveltejs/adapter-vercel": "^5.6.3",
"@sveltejs/kit": "^2.22.0",
"@sveltejs/vite-plugin-svelte": "^6.0.0",
"@types/node": "^24.3.1",
"@typescript-eslint/parser": "^8.44.0",
"drizzle-kit": "^0.30.2",
"drizzle-orm": "^0.40.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-svelte": "^3.12.3",
"globals": "^16.0.0",
"prettier": "^3.4.2",
"prettier-plugin-svelte": "^3.3.3",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"svelte-eslint-parser": "^1.3.2",
"typescript": "^5.0.0",
"vite": "^7.0.4",
"vite-plugin-devtools-json": "^1.0.0",
"vitest": "^3.2.3"
},
"dependencies": {
"@sveltejs/mcp-schema": "workspace:^",
"@sveltejs/mcp-server": "workspace:^",
"@tmcp/transport-http": "^0.6.3",
"tmcp": "^1.14.0"
}
}

View File

@@ -0,0 +1,35 @@
import { http_transport } from '$lib/mcp/index.js';
import { db } from '$lib/server/db/index.js';
import { redirect } from '@sveltejs/kit';
export async function handle({ event, resolve }) {
if (event.request.method === 'GET') {
const accept = event.request.headers.get('accept');
if (accept) {
const accepts = accept.split(',');
if (!accepts.includes('text/event-stream')) {
// the request it's a browser request, not an MCP client request
// it means someone probably opened it from the docs...we should redirect to the docs
redirect(302, 'https://svelte.dev/docs/mcp/overview');
}
}
}
const mcp_response = await http_transport.respond(event.request, {
db,
});
// we are deploying on vercel the SSE connection will timeout after 5 minutes...for
// the moment we are not sending back any notifications (logs, or list changed notifications)
// so it's a waste of resources to keep a connection open that will error
// after 5 minutes making the logs dirty. For this reason if we have a response from
// the MCP server and it's a GET request we just return an empty response (it has to be
// 200 or the MCP client will complain)
if (mcp_response && event.request.method === 'GET') {
try {
await mcp_response.body?.cancel();
} catch {
// ignore
}
return new Response('', { status: 200 });
}
return mcp_response ?? resolve(event);
}

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

View File

@@ -0,0 +1,6 @@
import { server } from '@sveltejs/mcp-server';
import { HttpTransport } from '@tmcp/transport-http';
export const http_transport = new HttpTransport(server, {
cors: true,
});

View File

@@ -0,0 +1,15 @@
import { createClient } from '@libsql/client';
import { drizzle } from 'drizzle-orm/libsql';
import * as schema from './schema.js';
// let's disable it for the moment...i can't figure out a way to make it wotk with eslint
// eslint-disable-next-line import/extensions
import { DATABASE_TOKEN, DATABASE_URL } from '$env/static/private';
if (!DATABASE_URL) throw new Error('DATABASE_URL is not set');
if (!DATABASE_TOKEN) throw new Error('DATABASE_TOKEN is not set');
const client = createClient({
url: DATABASE_URL,
authToken: DATABASE_TOKEN,
});
export const db = drizzle(client, { schema, logger: true });

View File

@@ -0,0 +1,2 @@
// we need to re-export from here to allow for the drizzle config to pick them up for migrations
export * from '@sveltejs/mcp-schema/schema';

View File

@@ -0,0 +1,19 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
//
// To make changes to top-level options such as include and exclude, we recommend extending
// the generated config; see https://svelte.dev/docs/kit/configuration#typescript
}

View File

@@ -0,0 +1,22 @@
import devtoolsJson from 'vite-plugin-devtools-json';
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit(), devtoolsJson()],
// we don't have tests yet so we just comment this out for now
// test: {
// expect: { requireAssertions: true },
// projects: [
// {
// extends: './vite.config.ts',
// test: {
// name: 'server',
// environment: 'node',
// include: ['src/**/*.{test,spec}.{js,ts}'],
// exclude: ['src/**/*.svelte.{test,spec}.{js,ts}'],
// },
// },
// ],
// },
});

320
docs/tmcp.md Normal file
View File

@@ -0,0 +1,320 @@
> [!WARNING]
> Unfortunately i published the 1.0 by mistake...this package is currently under heavy development so there will be breaking changes in minors...threat this `1.x` as the `0.x` of any other package. Sorry for the disservice, every breaking will be properly labeled in the PR name.
# tmcp
A lightweight, schema-agnostic Model Context Protocol (MCP) server implementation with unified API design.
## Why tmcp?
tmcp offers significant advantages over the official MCP SDK:
- **🔄 Schema Agnostic**: Works with any validation library through adapters
- **📦 No Weird Dependencies**: Minimal footprint with only essential dependencies (looking at you `express`)
- **🎯 Unified API**: Consistent, intuitive interface across all MCP capabilities
- **🔌 Extensible**: Easy to add support for new schema libraries
- **⚡ Lightweight**: No bloat, just what you need
## Supported Schema Libraries
tmcp works with all major schema validation libraries through its adapter system:
- **Zod** - `@tmcp/adapter-zod`
- **Valibot** - `@tmcp/adapter-valibot`
- **ArkType** - `@tmcp/adapter-arktype`
- **Effect Schema** - `@tmcp/adapter-effect`
- **Zod v3** - `@tmcp/adapter-zod-v3`
## Installation
```bash
pnpm install tmcp
# Choose your preferred schema library adapter
pnpm install @tmcp/adapter-zod zod
# Choose your preferred transport
pnpm install @tmcp/transport-stdio # For CLI/desktop apps
pnpm install @tmcp/transport-http # For web-based clients
```
## Quick Start
### Standard I/O Transport (CLI/Desktop)
```javascript
import { McpServer } from 'tmcp';
import { ZodJsonSchemaAdapter } from '@tmcp/adapter-zod';
import { StdioTransport } from '@tmcp/transport-stdio';
import { z } from 'zod';
const adapter = new ZodJsonSchemaAdapter();
const server = new McpServer(
{
name: 'my-server',
version: '1.0.0',
description: 'My awesome MCP server',
},
{
adapter,
capabilities: {
tools: { listChanged: true },
prompts: { listChanged: true },
resources: { listChanged: true },
},
},
);
// Define a tool with type-safe schema
server.tool(
{
name: 'calculate',
description: 'Perform mathematical calculations',
schema: z.object({
operation: z.enum(['add', 'subtract', 'multiply', 'divide']),
a: z.number(),
b: z.number(),
}),
},
async ({ operation, a, b }) => {
switch (operation) {
case 'add':
return a + b;
case 'subtract':
return a - b;
case 'multiply':
return a * b;
case 'divide':
return a / b;
}
},
);
// Start the server with stdio transport
const transport = new StdioTransport(server);
transport.listen();
```
### HTTP Transport (Web-based)
```javascript
import { McpServer } from 'tmcp';
import { ZodJsonSchemaAdapter } from '@tmcp/adapter-zod';
import { HttpTransport } from '@tmcp/transport-http';
import { z } from 'zod';
const adapter = new ZodJsonSchemaAdapter();
const server = new McpServer(/* ... same server config ... */);
// Add tools as above...
// Create HTTP transport
const transport = new HttpTransport(server);
// Use with your preferred HTTP server (Bun example)
Bun.serve({
port: 3000,
async fetch(req) {
const response = await transport.respond(req);
if (response === null) {
return new Response('Not Found', { status: 404 });
}
return response;
},
});
```
## API Reference
### McpServer
The main server class that handles MCP protocol communications.
#### Constructor
```javascript
new McpServer(serverInfo, options);
```
- `serverInfo`: Server metadata (name, version, description)
- `options`: Configuration object with adapter and capabilities
#### Methods
##### `tool(definition, handler)`
Register a tool with optional schema validation.
```javascript
server.tool(
{
name: 'tool-name',
description: 'Tool description',
schema: yourSchema, // optional
},
async (input) => {
// Tool implementation
return result;
},
);
```
##### `prompt(definition, handler)`
Register a prompt template with optional schema validation.
```javascript
server.prompt(
{
name: 'prompt-name',
description: 'Prompt description',
schema: yourSchema, // optional
complete: (arg, context) => ['completion1', 'completion2'] // optional
},
async (input) => {
// Prompt implementation
return { messages: [...] };
}
);
```
##### `resource(definition, handler)`
Register a static resource.
```javascript
server.resource(
{
name: 'resource-name',
description: 'Resource description',
uri: 'file://path/to/resource'
},
async (uri, params) => {
// Resource implementation
return { contents: [...] };
}
);
```
##### `template(definition, handler)`
Register a URI template for dynamic resources.
```javascript
server.template(
{
name: 'template-name',
description: 'Template description',
uri: 'file://path/{id}/resource',
complete: (arg, context) => ['id1', 'id2'] // optional
},
async (uri, params) => {
// Template implementation using params.id
return { contents: [...] };
}
);
```
##### `receive(request)`
Process an incoming MCP request.
```javascript
const response = server.receive(jsonRpcRequest);
```
## Advanced Examples
### Multiple Schema Libraries
```javascript
// Use different schemas for different tools
import { z } from 'zod';
import * as v from 'valibot';
server.tool(
{
name: 'zod-tool',
schema: z.object({ name: z.string() }),
},
async ({ name }) => `Hello ${name}`,
);
server.tool(
{
name: 'valibot-tool',
schema: v.object({ age: v.number() }),
},
async ({ age }) => `Age: ${age}`,
);
```
### Resource Templates with Completion
```javascript
server.template(
{
name: 'user-profile',
description: 'Get user profile by ID',
uri: 'users/{userId}/profile',
complete: (arg, context) => {
// Provide completions for userId parameter
return ['user1', 'user2', 'user3'];
},
},
async (uri, params) => {
const user = await getUserById(params.userId);
return {
contents: [
{
uri,
mimeType: 'application/json',
text: JSON.stringify(user),
},
],
};
},
);
```
### Complex Validation
```javascript
const complexSchema = z.object({
user: z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().min(18).max(120),
}),
preferences: z
.object({
theme: z.enum(['light', 'dark']),
notifications: z.boolean(),
})
.optional(),
tags: z.array(z.string()).default([]),
});
server.tool(
{
name: 'create-user',
description: 'Create a new user with preferences',
schema: complexSchema,
},
async (input) => {
// Input is fully typed and validated
const { user, preferences, tags } = input;
return await createUser(user, preferences, tags);
},
);
```
## Contributing
Contributions are welcome! Please see our [contributing guidelines](../../CONTRIBUTING.md) for details.
## Acknowledgments
Huge thanks to Sean O'Bannon that provided us with the `@tmcp` scope on npm.
## License
MIT © Paolo Ricciuti

View File

@@ -0,0 +1,44 @@
---
title: Overview
---
The Svelte MCP ([Model Context Protocol](https://modelcontextprotocol.io/docs/getting-started/intro)) server can help your LLM or agent of choice write better Svelte code. It works by providing documentation relevant to the task at hand, and statically analysing generated code so that it can suggest fixes and best practices.
## Setup
The setup varies based on the version of the MCP you prefer — remote or local — and your chosen MCP client (e.g. Claude Code, Codex CLI or GitHub Copilot):
- [local setup](local-setup) using `@sveltejs/mcp`
- [remote setup](remote-setup) using [mcp.svelte.dev/mcp](https://mcp.svelte.dev/mcp)
## Usage
To get the most out of the MCP server we recommend including the following prompt in your `AGENTS.md` (or `CLAUDE.md`, if using Claude Code). This will tell the LLM which tools are available and when it's appropriate to use them.
```md
You are able to use the Svelte MCP server, where you have access to comprehensive Svelte 5 and SvelteKit documentation. Here's how to use the available tools effectively:
## Available MCP Tools:
### 1. list-sections
Use this FIRST to discover all available documentation sections. Returns a structured list with titles, use_cases, and paths.
When asked about Svelte or SvelteKit topics, ALWAYS use this tool at the start of the chat to find relevant sections.
### 2. get-documentation
Retrieves full documentation content for specific sections. Accepts single or multiple sections.
After calling the list-sections tool, you MUST analyze the returned documentation sections (especially the use_cases field) and then use the get-documentation tool to fetch ALL documentation sections that are relevant for the user's task.
### 3. svelte-autofixer
Analyzes Svelte code and returns issues and suggestions.
You MUST use this tool whenever writing Svelte code before sending it to the user. Keep calling it until no issues or suggestions are returned.
### 4. playground-link
Generates a Svelte Playground link with the provided code.
After completing the code, ask the user if they want a playground link. Only call this tool after user confirmation and NEVER if code was written to files in their project.
```
If your MCP client supports it, we also recommend using the [svelte-task](prompts#svelte-task) prompt to instruct the LLM on the best way to use the MCP server.

View File

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

View File

@@ -0,0 +1,113 @@
---
title: Local setup
---
The local (or stdio) version of the MCP server is available via the [`@sveltejs/mcp`](https://www.npmjs.com/package/@sveltejs/mcp) npm package. You can either install it globally and then reference it in your configuration or run it with `npx`:
```bash
npx -y @sveltejs/mcp
```
Here's how to set it up in some common MCP clients:
## Claude Code
To include the local MCP version in Claude Code, simply run the following command:
```bash
claude mcp add -t stdio -s [scope] svelte npx -y @sveltejs/mcp
```
The `[scope]` must be `user`, `project` or `local`.
## Claude Desktop
In the Settings > Developer section, click on Edit Config. It will open the folder with a `claude_desktop_config.json` file in it. Edit the file to include the following configuration:
```json
{
"mcpServers": {
"svelte": {
"command": "npx",
"args": ["-y", "@sveltejs/mcp"]
}
}
}
```
## Codex CLI
Add the following to your `config.toml` (which defaults to `~/.codex/config.toml`, but refer to [the configuration documentation](https://github.com/openai/codex/blob/main/docs/config.md) for more advanced setups):
```toml
[mcp_servers.svelte]
command = "npx"
args = ["-y", "@sveltejs/mcp"]
```
## Gemini CLI
To include the local MCP version in Gemini CLI, simply run the following command:
```bash
gemini mcp add -t stdio -s [scope] svelte npx -y @sveltejs/mcp
```
The `[scope]` must be `user`, `project` or `local`.
## OpenCode
Run the command:
```bash
opencode mcp add
```
and follow the instructions, selecting 'Local' under the 'Select MCP server type' prompt:
```bash
opencode mcp add
┌ Add MCP server
◇ Enter MCP server name
│ svelte
◇ Select MCP server type
│ Local
◆ Enter command to run
│ npx -y @sveltejs/mcp
```
## VS Code
- Open the command palette
- Select "MCP: Add Server..."
- Select "Command (stdio)"
- Insert `npx -y @sveltejs/mcp` in the input and press `Enter`
- When prompted for a name, insert `svelte`
- Select if you want to add it as a `Global` or `Workspace` MCP server
## Cursor
- Open the command palette
- Select "View: Open MCP Settings"
- Click on "Add custom MCP"
It will open a file with your MCP servers where you can add the following configuration:
```json
{
"mcpServers": {
"svelte": {
"command": "npx",
"args": ["-y", "@sveltejs/mcp"]
}
}
}
```
## 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

@@ -0,0 +1,101 @@
---
title: Remote setup
---
The remote version of the MCP server is available on `https://mcp.svelte.dev/mcp`.
Here's how to set it up in some common MCP clients:
## Claude Code
To include the remote MCP version in Claude Code, simply run the following command:
```bash
claude mcp add -t http -s [scope] svelte https://mcp.svelte.dev/mcp
```
You can choose your preferred `scope` (it must be `user`, `project` or `local`) and `name`.
## Claude Desktop
- Open Settings > Connectors
- Click on Add Custom Connector
- When prompted for a name, enter `svelte`
- Under the Remote MCP server URL input, use `https://mcp.svelte.dev/mcp`
- Click Add
## Codex CLI
Add the following to your `config.toml` (which defaults to `~/.codex/config.toml`, but refer to [the configuration documentation](https://github.com/openai/codex/blob/main/docs/config.md) for more advanced setups):
```toml
experimental_use_rmcp_client = true
[mcp_servers.svelte]
url = "https://mcp.svelte.dev/mcp"
```
## Gemini CLI
To use the remote MCP server with Gemini CLI, simply run the following command:
```bash
gemini mcp add -t http -s [scope] svelte https://mcp.svelte.dev/mcp
```
The `[scope]` must be `user`, `project` or `local`.
## OpenCode
Run the command:
```bash
opencode mcp add
```
and follow the instructions, selecting 'Remote' under the 'Select MCP server type' prompt:
```bash
opencode mcp add
┌ Add MCP server
◇ Enter MCP server name
│ svelte
◇ Select MCP server type
│ Remote
◇ Enter MCP server URL
│ https://mcp.svelte.dev/mcp
```
## VS Code
- Open the command palette
- Select "MCP: Add Server..."
- Select "HTTP (HTTP or Server-Sent-Events)"
- Insert `https://mcp.svelte.dev/mcp` in the input and press `Enter`
- Insert your preferred name
- Select if you want to add it as a `Global` or `Workspace` MCP server
## Cursor
- Open the command palette
- Select "View: Open MCP Settings"
- Click on "Add custom MCP"
It will open a file with your MCP servers where you can add the following configuration:
```json
{
"mcpServers": {
"svelte": {
"url": "https://mcp.svelte.dev/mcp"
}
}
}
```
## Other clients
If we didn't include the MCP client you are using, refer to their documentation for `remote` servers and use `https://mcp.svelte.dev/mcp` as the URL.

View File

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

View File

@@ -0,0 +1,21 @@
---
title: Tools
---
The following tools are provided by the MCP server to the model, which can decide to call one or more of them during a session:
## list-sections
Provides a list of all the available documentation sections.
## get-documentation
Allows the model to get the full (and up-to-date) documentation for the requested sections directly from [svelte.dev/docs](/docs).
## svelte-autofixer
Uses static analysis to provide suggestions for the generated code. It should be invoked in a loop by the model until all issues and suggestions are resolved.
## playground-link
Generates an ephemeral playground link with the generated code. It's useful when the generated code is not written to a file in your project and you want to quickly test the generated solution. The code is not stored anywhere except the URL itself (which will often, as a consequence, be quite large).

View File

@@ -0,0 +1,9 @@
---
title: Resources
---
This is the list of available resources provided by the MCP server. Resources are included by the user (not by the LLM) and are useful if you want to include specific knowledge in your session. For example, if you know that the component will need to use transitions you can include the transition documentation directly without asking the LLM to do it for you.
## doc-section
This dynamic resource allows you to add every section of the Svelte documentation as a resource. The URI looks like this `svelte://slug-of-the-docs.md` and the returned resource will contain the `llms.txt` version of the specific page you selected.

View File

@@ -0,0 +1,26 @@
---
title: Prompts
---
This is the list of available prompts provided by the MCP server. Prompts are selected by the user and are sent as a user message. They can be useful to write repetitive instructions for the LLM on how to properly use the MCP server.
## svelte-task
This prompt should be used whenever you are asking the model to work on some Svelte-related task. It will instruct the LLM on which documentation sections are available, which tool to invoke, when to invoke it, and how to interpret the result. It will ask you for the description of the task and the returned value will look like this:
```
You are a Svelte expert tasked to build components and utilities for Svelte developers. If you need documentation for anything related to Svelte you can invoke the tool \`get-documentation\` with one of the following paths:
<available-docs-paths>
[all available docs]
</available-docs-paths>
Every time you write a Svelte component or a Svelte module you MUST invoke the \`svelte-autofixer\` tool providing the code. The tool will return a list of issues or suggestions. If there are any issues or suggestions you MUST fix them and call the tool again with the updated code. You MUST keep doing this until the tool returns no issues or suggestions. Only then you can return the code to the user.
This is the task you will work on:
<task>
[your task here]
</task>
If you are not writing the code into a file, once you have the final version of the code ask the user if they want to generate a playground link to quickly check the code in it and if they answer yes call the \`playground-link\` tool and return the url to the user nicely formatted. The playground link MUST be generated only once you have the final version of the code and you are ready to share it, it MUST include an entry point file called \`App.svelte\` where the main component should live. If you have multiple files to include in the playground link you can include them all at the root.
```

View File

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

View File

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

View File

@@ -5,15 +5,20 @@ import svelte from 'eslint-plugin-svelte';
import globals from 'globals';
import { fileURLToPath } from 'node:url';
import ts from 'typescript-eslint';
import svelteConfig from './svelte.config.js';
import svelteConfig from './apps/mcp-remote/svelte.config.js';
import eslint_plugin_import from 'eslint-plugin-import';
const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url));
const gitignore_path = fileURLToPath(new URL('./.gitignore', import.meta.url));
export default ts.config(
includeIgnoreFile(gitignorePath),
export default /** @type {import("eslint").Linter.Config} */ ([
includeIgnoreFile(gitignore_path),
{
ignores: ['.claude/**/*'],
},
js.configs.recommended,
...ts.configs.recommended,
...svelte.configs.recommended,
eslint_plugin_import.flatConfigs.recommended,
prettier,
...svelte.configs.prettier,
{
@@ -24,6 +29,27 @@ export default ts.config(
// typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects.
// see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
'no-undef': 'off',
'@typescript-eslint/naming-convention': [
'error',
{
selector: ['variableLike'],
format: ['snake_case', 'UPPER_CASE'],
leadingUnderscore: 'allow',
},
],
'func-style': ['error', 'declaration', { allowTypeAnnotation: true }],
'import/no-unresolved': 'off', // this doesn't work well with typescript path mapping
'import/extensions': [
'error',
'ignorePackages',
{
js: 'always',
mjs: 'always',
cjs: 'always',
ts: 'always',
svelte: 'always',
},
],
},
},
{
@@ -37,4 +63,4 @@ export default ts.config(
},
},
},
);
]);

View File

@@ -1,33 +1,22 @@
{
"name": "@sveltejs/mcp",
"name": "@sveltejs/mcp-mono",
"version": "0.0.1",
"description": "The official Svelte MCP server implementation",
"type": "module",
"main": "src/index.js",
"bin": {
"svelte-mcp": "./dist/lib/stdio.js"
},
"packageManager": "pnpm@10.17.1",
"scripts": {
"start": "node src/index.js",
"dev": "vite dev",
"build": "vite build",
"build:mcp": "tsc --project tsconfig.build.json",
"prepublishOnly": "pnpm build:mcp",
"preview": "vite preview",
"prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"build": "pnpm -r run build",
"dev": "pnpm --filter @sveltejs/mcp-remote run dev",
"check": "pnpm -r run check",
"check:publint": "pnpm -r run check:publint",
"format": "prettier --write .",
"lint": "prettier --check . && eslint .",
"lint:fix": "prettier --write . && eslint . --fix",
"test:unit": "vitest",
"test": "npm run test:unit -- --run",
"test:watch": "npm run test:unit -- --watch",
"db:push": "drizzle-kit push",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:studio": "drizzle-kit studio",
"inspect": "DANGEROUSLY_OMIT_AUTH=true npx @modelcontextprotocol/inspector"
"inspect": "pnpm mcp-inspector",
"release": "pnpm --filter @sveltejs/mcp run build && changeset publish"
},
"keywords": [
"svelte",
@@ -37,44 +26,23 @@
],
"private": true,
"devDependencies": {
"@eslint/compat": "^1.2.5",
"@eslint/js": "^9.18.0",
"@libsql/client": "^0.14.0",
"@changesets/cli": "^2.29.7",
"@eslint/compat": "^1.3.2",
"@eslint/js": "^9.36.0",
"@modelcontextprotocol/inspector": "^0.16.7",
"@sveltejs/adapter-vercel": "^5.6.3",
"@sveltejs/kit": "^2.22.0",
"@sveltejs/vite-plugin-svelte": "^6.0.0",
"@types/eslint-scope": "^8.3.2",
"@types/estree": "^1.0.8",
"@types/node": "^24.3.1",
"@typescript-eslint/types": "^8.43.0",
"dotenv": "^17.2.2",
"drizzle-kit": "^0.30.2",
"drizzle-orm": "^0.40.0",
"eslint": "^9.18.0",
"@svitejs/changesets-changelog-github-compact": "^1.2.0",
"eslint": "^9.36.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-svelte": "^3.0.0",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-svelte": "^3.12.3",
"globals": "^16.0.0",
"prettier": "^3.4.2",
"prettier-plugin-svelte": "^3.3.3",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"publint": "^0.3.13",
"typescript": "^5.0.0",
"typescript-eslint": "^8.20.0",
"vite": "^7.0.4",
"vite-plugin-devtools-json": "^1.0.0",
"typescript-eslint": "^8.44.1",
"vitest": "^3.2.3"
},
"dependencies": {
"@tmcp/adapter-valibot": "^0.1.4",
"@tmcp/transport-http": "^0.6.0",
"@tmcp/transport-stdio": "^0.1.3",
"@typescript-eslint/parser": "^8.43.0",
"svelte-eslint-parser": "^1.3.2",
"tmcp": "^1.12.2",
"valibot": "^1.1.0",
"zimmerframe": "^1.1.4"
},
"pnpm": {
"onlyBuiltDependencies": [
"esbuild"

View File

@@ -0,0 +1,19 @@
{
"name": "@sveltejs/mcp-schema",
"version": "0.0.1",
"private": true,
"description": "",
"main": "index.js",
"exports": {
".": "./src/index.js",
"./utils": "./src/utils.js",
"./schema": "./src/schema.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "module",
"dependencies": {
"drizzle-orm": "^0.40.1"
}
}

View File

@@ -0,0 +1,8 @@
/**
* @import * as schema from './schema.js'
*/
export * from './schema.js';
/**
* @typedef {typeof schema} Schema
*/

View File

@@ -1,5 +1,5 @@
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';
import { float_32_array } from './utils';
import { float_32_array } from './utils.js';
/**
* NOTE: if you modify a schema adding a vector column you need to manually add this
@@ -40,10 +40,7 @@ export const distillation_jobs = sqliteTable('distillation_jobs', {
started_at: integer('started_at', { mode: 'timestamp' }),
completed_at: integer('completed_at', { mode: 'timestamp' }),
error_message: text('error_message'),
metadata: text('metadata', { mode: 'json' })
.$type<Record<string, unknown>>()
.notNull()
.default({}),
metadata: text('metadata', { mode: 'json' }).notNull().default({}),
created_at: integer('created_at', { mode: 'timestamp' })
.notNull()
.$defaultFn(() => new Date()),
@@ -59,10 +56,7 @@ export const content = sqliteTable('content', {
content: text('content').notNull(),
size_bytes: integer('size_bytes').notNull(),
embeddings: float_32_array('embeddings', { dimensions: 1024 }),
metadata: text('metadata', { mode: 'json' })
.$type<Record<string, unknown>>()
.notNull()
.default({}),
metadata: text('metadata', { mode: 'json' }).notNull().default({}),
created_at: integer('created_at', { mode: 'timestamp' })
.notNull()
.$defaultFn(() => new Date()),
@@ -78,10 +72,7 @@ export const content_distilled = sqliteTable('content_distilled', {
content: text('content').notNull(),
size_bytes: integer('size_bytes').notNull(),
embeddings: float_32_array('embeddings', { dimensions: 1024 }),
metadata: text('metadata', { mode: 'json' })
.$type<Record<string, unknown>>()
.notNull()
.default({}),
metadata: text('metadata', { mode: 'json' }).notNull().default({}),
created_at: integer('created_at', { mode: 'timestamp' })
.notNull()
.$defaultFn(() => new Date()),

View File

@@ -1,19 +1,22 @@
import { sql, Column } from 'drizzle-orm';
/**
* @import { Column } from 'drizzle-orm';
*/
import { sql } from 'drizzle-orm';
import { customType } from 'drizzle-orm/sqlite-core';
/**
* Helper function to convert an array of embeddings into a format that can be inserted into a LibSQL vector column.
* @param arr The embeddings array.
* @param {number[]} arr The embeddings array.
*/
export function vector(arr: number[]) {
export function vector(arr) {
return sql`vector32(${JSON.stringify(arr)})`;
}
/**
* Helper function to calculate the distance between a vector column and an array of embeddings and return it as a columns.
* @param column The drizzle column representing the vector.
* @param arr The embeddings array.
* @param as The name of the returned column. Default is 'distance'.
* @param {Column} column The drizzle column representing the vector.
* @param {number} arr The embeddings array.
* @param {string} as The name of the returned column. Default is 'distance'.
*
* @example
* await db.select({
@@ -25,8 +28,10 @@ export function vector(arr: number[]) {
* .orderBy(sql`distance`)
* .execute();
*/
export function distance(column: Column, arr: number[], as = 'distance') {
return sql<number>`CASE ${column} ISNULL WHEN 1 THEN 1 ELSE vector_distance_cos(${column}, vector32(${JSON.stringify(arr)})) END`.as(
export function distance(column, arr, as = 'distance') {
return /** @type {typeof sql<number>} */ (
sql
)`CASE ${column} ISNULL WHEN 1 THEN 1 ELSE vector_distance_cos(${column}, vector32(${JSON.stringify(arr)})) END`.as(
as,
);
}
@@ -34,19 +39,27 @@ export function distance(column: Column, arr: number[], as = 'distance') {
/**
* Custom drizzle type to use the LibSQL vector column type.
*/
export const float_32_array = customType<{
export const float_32_array = /** @type {typeof customType<{
data: number[];
config: { dimensions: number };
configRequired: true;
driverData: Buffer;
}>({
}>} */ (customType)({
dataType(config) {
return `F32_BLOB(${config.dimensions})`;
},
fromDriver(value: Buffer) {
/**
* @param {Buffer} value
*/
fromDriver(value) {
return Array.from(new Float32Array(value.buffer));
},
toDriver(value: number[]) {
/**
*
* @param {number[]} value
* @returns
*/
toDriver(value) {
return vector(value);
},
});

View File

@@ -0,0 +1,40 @@
{
"name": "@sveltejs/mcp-server",
"version": "0.0.1",
"private": true,
"description": "",
"main": "index.js",
"keywords": [],
"author": "",
"license": "ISC",
"type": "module",
"scripts": {
"test": "vitest"
},
"exports": {
".": "./src/index.ts"
},
"peerDependencies": {
"drizzle-orm": "^0.40.0"
},
"dependencies": {
"@sveltejs/mcp-schema": "workspace:^",
"@tmcp/adapter-valibot": "^0.1.4",
"@typescript-eslint/parser": "^8.44.0",
"eslint": "^9.36.0",
"eslint-plugin-svelte": "^3.12.3",
"svelte": "^5.39.2",
"svelte-eslint-parser": "^1.3.2",
"tmcp": "^1.13.0",
"typescript-eslint": "^8.44.0",
"valibot": "^1.1.0",
"vitest": "^3.2.4",
"zimmerframe": "^1.1.4"
},
"devDependencies": {
"@sveltejs/kit": "^2.42.2",
"@types/eslint-scope": "^8.3.2",
"@types/estree": "^1.0.8",
"@typescript-eslint/types": "^8.44.0"
}
}

View File

@@ -0,0 +1 @@
export { server, type SvelteMcp } from './mcp/index.js';

View File

@@ -0,0 +1,471 @@
import { describe, expect, it } from 'vitest';
import { add_autofixers_issues } from './add-autofixers-issues.js';
import { base_runes } from '../../constants.js';
const dollarless_runes = base_runes.map((r) => ({ rune: r.replace('$', '') }));
function run_autofixers_on_code(code: string, desired_svelte_version = 5) {
const content = { issues: [], suggestions: [] };
add_autofixers_issues(content, code, desired_svelte_version);
return content;
}
function with_possible_inits(title: string, fn: (args: { init: string }) => void) {
describe.each([
{ init: '$state' },
{ init: '$state.raw' },
{ init: '$derived' },
{ init: '$derived.by' },
])(title, fn);
}
describe('add_autofixers_issues', () => {
describe('assign_in_effect', () => {
with_possible_inits('($init)', ({ init }) => {
it(`should add suggestions when assigning to a stateful variable inside an effect`, () => {
const content = run_autofixers_on_code(`
<script>
const count = ${init}(0);
$effect(() => {
count = 43;
});
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
);
});
it(`should add a suggestion for each variable assigned within an effect`, () => {
const content = run_autofixers_on_code(`
<script>
const count = $state(0);
const count2 = $state(0);
$effect(() => {
count = 43;
count2 = 44;
});
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(2);
expect(content.suggestions).toContain(
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
);
expect(content.suggestions).toContain(
'The stateful variable "count2" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
);
});
it(`should not add a suggestion for variables that are not assigned within an effect`, () => {
const content = run_autofixers_on_code(`
<script>
const count = ${init}(0);
</script>
<button onclick={() => count = 43}>Increment</button>
`);
expect(content.suggestions).not.toContain(
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
);
});
it("should not add a suggestions for variables that are assigned within an effect but aren't stateful", () => {
const content = run_autofixers_on_code(`
<script>
const count = 0;
$effect(() => {
count = 43;
});
</script>`);
expect(content.suggestions).not.toContain(
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
);
});
it(`should add a suggestion for variables that are assigned within an effect with an update`, () => {
const content = run_autofixers_on_code(`
<script>
let count = ${init}(0);
$effect(() => {
count++;
});
</script>
`);
expect(content.suggestions).toContain(
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
);
});
it(`should add a suggestion for variables that are mutated within an effect`, () => {
const content = run_autofixers_on_code(`
<script>
let count = ${init}({ value: 0 });
$effect(() => {
count.value = 42;
});
</script>
`);
expect(content.suggestions).toContain(
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
);
});
});
});
with_possible_inits('($init)', ({ init }) => {
describe.each([{ method: 'set' }, { method: 'update' }])(
'wrong_property_access_state ($method)',
({ method }) => {
it(`should add suggestions when using .${method}() on a stateful variable with a literal init`, () => {
const content = run_autofixers_on_code(`
<script>
const count = ${init}(0);
function update_count() {
count.${method}(43);
}
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them.`,
);
});
it(`should add suggestions when using .${method}() on a stateful variable with an array init`, () => {
const content = run_autofixers_on_code(`
<script>
const count = ${init}([0]);
function update_count() {
count.${method}([1]);
}
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them.`,
);
});
it(`should add suggestions when using .${method}() on a stateful variable with conditional if it's not sure if the method could actually be present on the variable (${init}({}))`, () => {
const content = run_autofixers_on_code(`
<script>
const count = ${init}({ value: 0 });
function update_count() {
count.${method}({ value: 43 });
}
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them. However I can't verify if "count" is a state variable of an object or a class with a "${method}" method on it. Please verify that before updating the code to use a normal assignment`,
);
});
it(`should add suggestions when using .${method}() on a stateful variable with conditional if it's not sure if the method could actually be present on the variable (${init}(new Class()))`, () => {
const content = run_autofixers_on_code(`
<script>
const count = ${init}(new Class());
function update_count() {
count.${method}(new Class());
}
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them. However I can't verify if "count" is a state variable of an object or a class with a "${method}" method on it. Please verify that before updating the code to use a normal assignment`,
);
});
it(`should add suggestions when using .${method}() on a stateful variable with conditional if it's not sure if the method could actually be present on the variable (${init}(variable_name))`, () => {
const content = run_autofixers_on_code(`
<script>
const { init } = $props();
const count = ${init}(init);
function update_count() {
count.${method}(43);
}
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them. However I can't verify if "count" is a state variable of an object or a class with a "${method}" method on it. Please verify that before updating the code to use a normal assignment`,
);
});
it(`should not add suggestions when using .${method} on a stateful variable if it's not a method call`, () => {
const content = run_autofixers_on_code(`
<script>
const count = ${init}({});
function update_count() {
console.log(count.${method});
}
</script>`);
expect(content.suggestions).not.toContain(
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them. However I can't verify if "count" is a state variable of an object or a class with a "${method}" method on it. Please verify that before updating the code to use a normal assignment`,
);
});
},
);
describe.each([{ property: '$' }])(
'wrong_property_access_state property ($property)',
async ({ property }) => {
it(`should add suggestions when reading .${property} on a stateful variable with a literal init`, () => {
const content = run_autofixers_on_code(`
<script>
const count = ${init}(0);
function read_count() {
count.${property};
}
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to read the stateful variable "count" using "${property}". stateful variables should be read just by accessing them like normal variable, do not use properties to read them.`,
);
});
it(`should add suggestions when reading .${property} on a stateful variable with an array init`, () => {
const content = run_autofixers_on_code(`
<script>
const count = ${init}([1]);
function read_count() {
count.${property};
}
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to read the stateful variable "count" using "${property}". stateful variables should be read just by accessing them like normal variable, do not use properties to read them.`,
);
});
it(`should add suggestions when reading .${property} on a stateful variable with conditional if it's not sure if the property could actually be present on the variable (${init}({}))`, () => {
const content = run_autofixers_on_code(`
<script>
const count = ${init}({ value: 0 });
function read_count() {
count.${property};
}
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to read the stateful variable "count" using "${property}". stateful variables should be read just by accessing them like normal variable, do not use properties to read them. However I can't verify if "count" is a state variable of an object or a class with a "${property}" property on it. Please verify that before updating the code to use a normal access`,
);
});
it(`should add suggestions when reading .${property} on a stateful variable with conditional if it's not sure if the property could actually be present on the variable (${init}(new Class()))`, () => {
const content = run_autofixers_on_code(`
<script>
const count = ${init}(new Class());
function read_count() {
count.${property};
}
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to read the stateful variable "count" using "${property}". stateful variables should be read just by accessing them like normal variable, do not use properties to read them. However I can't verify if "count" is a state variable of an object or a class with a "${property}" property on it. Please verify that before updating the code to use a normal access`,
);
});
it(`should add suggestions when reading .${property} on a stateful variable with conditional if it's not sure if the property could actually be present on the variable (${init}(variable_name))`, () => {
const content = run_autofixers_on_code(`
<script>
const { init } = $props();
const count = ${init}(init);
function read_count() {
count.${property};
}
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to read the stateful variable "count" using "${property}". stateful variables should be read just by accessing them like normal variable, do not use properties to read them. However I can't verify if "count" is a state variable of an object or a class with a "${property}" property on it. Please verify that before updating the code to use a normal access`,
);
});
},
);
});
describe('imported_runes', () => {
describe.each([{ source: 'svelte' }, { source: 'svelte/runes' }])(
'from "$source"',
({ source }) => {
describe.each(dollarless_runes)('single import ($rune)', ({ rune }) => {
it(`should add suggestions when importing '${rune}' from '${source}'`, () => {
const content = run_autofixers_on_code(`
<script>
import { ${rune} } from '${source}';
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are importing "${rune}" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$${rune}" directly.`,
);
});
it(`should add suggestions when importing "${rune}" as the default export from '${source}'`, () => {
const content = run_autofixers_on_code(`
<script>
import ${rune} from '${source}';
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are importing "${rune}" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$${rune}" directly.`,
);
});
it(`should add suggestions when importing '${rune}' as the namespace export from '${source}'`, () => {
const content = run_autofixers_on_code(`
<script>
import * as ${rune} from '${source}';
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are importing "${rune}" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$${rune}" directly.`,
);
});
});
it(`should add suggestions when importing multiple runes from '${source}'`, () => {
const content = run_autofixers_on_code(`
<script>
import { onMount, state, effect } from '${source}';
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(2);
expect(content.suggestions).toContain(
`You are importing "state" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$state" directly.`,
);
expect(content.suggestions).toContain(
`You are importing "effect" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$effect" directly.`,
);
});
it(`should not add suggestions when importing other identifiers from '${source}'`, () => {
const content = run_autofixers_on_code(`
<script>
import { onMount } from '${source}';
</script>`);
expect(content.suggestions).not.toContain(
`You are importing "onMount" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$onMount" directly.`,
);
});
},
);
});
describe('derived_with_function', () => {
it(`should add suggestions when using a function as the first argument to $derived`, () => {
const content = run_autofixers_on_code(`
<script>
const value = $derived(() => {
return 43;
});
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
'You are passing a function to $derived when declaring "value" but $derived expects an expression. You can use $derived.by instead.',
);
});
it(`should add suggestions when using a function as the first argument to $derived in classes`, () => {
const content = run_autofixers_on_code(`
<script>
class Double {
value = $derived(() => 43);
}
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
'You are passing a function to $derived when declaring "value" but $derived expects an expression. You can use $derived.by instead.',
);
});
it(`should add suggestions when using a function as the first argument to $derived in classes constructors`, () => {
const content = run_autofixers_on_code(`
<script>
class Double {
value;
constructor(){
this.value = $derived(function() { return 44; });
}
}
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
'You are passing a function to $derived when declaring "value" but $derived expects an expression. You can use $derived.by instead.',
);
});
it(`should add suggestions when using a function as the first argument to $derived without the declaring part if it's not an identifier`, () => {
const content = run_autofixers_on_code(`
<script>
const { destructured } = $derived(() => 43);
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
'You are passing a function to $derived but $derived expects an expression. You can use $derived.by instead.',
);
});
it(`should add suggestions when using a function as the first argument to $derived.by`, () => {
const content = run_autofixers_on_code(`
<script>
const { destructured } = $derived.by(() => 43);
</script>`);
expect(content.suggestions).not.toContain(
'You are passing a function to $derived but $derived expects an expression. You can use $derived.by instead.',
);
});
});
describe('use_runes_instead_of_store', () => {
describe.each([{ import: 'derived' }, { import: 'writable' }, { import: 'readable' }])(
'importing $import from svelte/store',
({ import: imported }) => {
it(`should add suggestions when importing '${imported}' from 'svelte/store'`, () => {
const content = run_autofixers_on_code(`
<script>
import { ${imported} } from 'svelte/store';
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are importing "${imported}" from "svelte/store". Unless the user specifically asked for stores or it's required because some library/component requires a store as input consider using runes like \`$state\` or \`$derived\` instead, all runes are globally available.`,
);
});
},
);
it(`should not add suggestions when importing other identifiers from 'svelte/store'`, () => {
const content = run_autofixers_on_code(`
<script>
import { get } from 'svelte/store';
</script>`);
expect(content.suggestions).not.toContain(
`You are importing "get" from "svelte/store". Unless the user specifically asked for stores or it's required because some library/component requires a store as input consider using runes like \`$state\` or \`$derived\` instead, all runes are globally available.`,
);
});
});
});

View File

@@ -1,5 +1,5 @@
import { parse } from '../../parse/parse.js';
import { walk } from '../../index.js';
import { walk } from '../../mcp/autofixers/ast/walk.js';
import type { Node } from 'estree';
import * as autofixers from './visitors/index.js';

View File

@@ -2,6 +2,7 @@ import { ESLint } from 'eslint';
import svelte_parser from 'svelte-eslint-parser';
import svelte from 'eslint-plugin-svelte';
import type { Config } from '@sveltejs/kit';
import ts from 'typescript-eslint';
let svelte_5_linter: ESLint | undefined;
@@ -41,6 +42,8 @@ function base_config(svelte_config: Config): ESLint.Options['baseConfig'] {
sourceType: 'module',
parser: svelte_parser,
parserOptions: {
extraFileExtensions: ['.svelte'],
parser: ts.parser,
svelteConfig: svelte_config,
},
},
@@ -78,7 +81,7 @@ export async function add_eslint_issues(
const eslint = get_linter(desired_svelte_version);
const results = await eslint.lintText(code, { filePath: filename || './Component.svelte' });
for (const message of results[0].messages) {
for (const message of results[0]?.messages ?? []) {
if (message.severity === 2) {
content.issues.push(`${message.message} at line ${message.line}, column ${message.column}`);
} else if (message.severity === 1) {

View File

@@ -1,10 +1,14 @@
import type { AssignmentExpression, Identifier, Node, UpdateExpression } from 'estree';
import type { Autofixer, AutofixerState } from '.';
import type { Autofixer, AutofixerState } from './index.js';
import { left_most_id } from '../ast/utils.js';
import type { SvelteNode } from 'svelte-eslint-parser/lib/ast';
import type { AST } from 'svelte-eslint-parser';
import type { Context } from 'zimmerframe';
function run_if_in_effect(path: (Node | SvelteNode)[], state: AutofixerState, to_run: () => void) {
function run_if_in_effect(
path: (Node | AST.SvelteNode)[],
state: AutofixerState,
to_run: () => void,
) {
const in_effect = path.findLast(
(node) =>
node.type === 'CallExpression' &&
@@ -25,7 +29,7 @@ function run_if_in_effect(path: (Node | SvelteNode)[], state: AutofixerState, to
function visitor(
node: UpdateExpression | AssignmentExpression,
{ state, path }: Context<Node | SvelteNode, AutofixerState>,
{ state, path }: Context<Node | AST.SvelteNode, AutofixerState>,
) {
run_if_in_effect(path, state, () => {
function check_if_stateful_id(id: Identifier) {
@@ -35,7 +39,7 @@ function visitor(
const init = definition.node.init;
if (
init?.type === 'CallExpression' &&
state.parsed.is_rune(init, ['$state', '$state.raw'])
state.parsed.is_rune(init, ['$state', '$state.raw', '$derived', '$derived.by'])
) {
state.output.suggestions.push(
`The stateful variable "${id.name}" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.`,

View File

@@ -1,5 +1,5 @@
import type { Identifier, PrivateIdentifier } from 'estree';
import type { Autofixer } from '.';
import type { Autofixer } from './index.js';
export const derived_with_function: Autofixer = {
CallExpression(node, { state, path }) {
@@ -7,15 +7,15 @@ export const derived_with_function: Autofixer = {
node.callee.type === 'Identifier' &&
node.callee.name === '$derived' &&
state.parsed.is_rune(node, ['$derived']) &&
(node.arguments[0].type === 'ArrowFunctionExpression' ||
node.arguments[0].type === 'FunctionExpression')
(node.arguments[0]?.type === 'ArrowFunctionExpression' ||
node.arguments[0]?.type === 'FunctionExpression')
) {
const parent = path[path.length - 1];
let variable_id: Identifier | PrivateIdentifier | undefined;
if (parent.type === 'VariableDeclarator' && parent.id.type === 'Identifier') {
if (parent?.type === 'VariableDeclarator' && parent.id.type === 'Identifier') {
// const something = $derived(...)
variable_id = parent.id;
} else if (parent.type === 'PropertyDefinition') {
} else if (parent?.type === 'PropertyDefinition') {
// class X { something = $derived(...) }
variable_id =
parent.key.type === 'Identifier'
@@ -23,7 +23,7 @@ export const derived_with_function: Autofixer = {
: parent.key.type === 'PrivateIdentifier'
? parent.key
: undefined;
} else if (parent.type === 'AssignmentExpression') {
} else if (parent?.type === 'AssignmentExpression') {
// this.something = $derived(...)
variable_id =
parent.left.type === 'MemberExpression'

View File

@@ -1,5 +1,5 @@
import { base_runes } from '../../../constants.js';
import type { Autofixer } from '.';
import type { Autofixer } from './index.js';
const dollarless_runes = base_runes.map((r) => r.replace('$', ''));

View File

@@ -12,7 +12,7 @@ export type AutofixerState = {
export type Autofixer = Visitors<Node | AST.SvelteNode, AutofixerState>;
export * from './assign-in-effect.js';
export * from './set-or-update-state.js';
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';

View File

@@ -1,4 +1,4 @@
import type { Autofixer } from '.';
import type { Autofixer } from './index.js';
export const use_runes_instead_of_store: Autofixer = {
ImportDeclaration(node, { state, next }) {

View File

@@ -0,0 +1,41 @@
import type { Autofixer } from './index.js';
import { left_most_id } from '../ast/utils.js';
const UPDATE_PROPERTIES = new Set(['set', 'update', '$']);
const METHODS = new Set(['set', 'update']);
export const wrong_property_access_state: Autofixer = {
MemberExpression(node, { state, next, path }) {
const parent = path[path.length - 1];
let is_property = false;
if (
node.property.type === 'Identifier' &&
((is_property = !METHODS.has(node.property.name)) ||
(parent?.type === 'CallExpression' && parent.callee === node)) &&
UPDATE_PROPERTIES.has(node.property.name)
) {
const id = left_most_id(node);
if (id) {
const reference = state.parsed.find_reference_by_id(id);
const definition = reference?.resolved?.defs[0];
if (definition && definition.type === 'Variable') {
const init = definition.node.init;
if (
init?.type === 'CallExpression' &&
state.parsed.is_rune(init, ['$state', '$state.raw', '$derived', '$derived.by'])
) {
let suggestion = is_property
? `You are trying to read the stateful variable "${id.name}" using "${node.property.name}". stateful variables should be read just by accessing them like normal variable, do not use properties to read them.`
: `You are trying to update the stateful variable "${id.name}" using "${node.property.name}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them.`;
const argument = init.arguments[0];
if (!argument || (argument.type !== 'Literal' && argument.type !== 'ArrayExpression')) {
suggestion += ` However I can't verify if "${id.name}" is a state variable of an object or a class with a "${node.property.name}" ${is_property ? 'property' : 'method'} on it. Please verify that before updating the code to use a normal ${is_property ? 'access' : 'assignment'}`;
}
state.output.suggestions.push(suggestion);
}
}
}
}
next();
},
};

View File

@@ -0,0 +1,22 @@
import type { SvelteMcp } from '../index.js';
import * as prompts from './prompts/index.js';
import * as tools from './tools/index.js';
import * as resources from './resources/index.js';
export function setup_tools(server: SvelteMcp) {
for (const tool in tools) {
tools[tool as keyof typeof tools](server);
}
}
export function setup_prompts(server: SvelteMcp) {
for (const prompt in prompts) {
prompts[prompt as keyof typeof prompts](server);
}
}
export function setup_resources(server: SvelteMcp) {
for (const resource in resources) {
resources[resource as keyof typeof resources](server);
}
}

View File

@@ -0,0 +1 @@
export * from './svelte-task.js';

View File

@@ -0,0 +1,46 @@
import type { SvelteMcp } from '../../index.js';
import * as v from 'valibot';
import { get_sections } from '../../utils.js';
export function setup_svelte_task(server: SvelteMcp) {
server.prompt(
{
name: 'svelte-task-prompt',
title: 'Svelte Task Prompt',
description:
'Use this Prompt to ask for any svelte related task. It will automatically instruct the LLM on how to best use the autofixer and how to query for documentation pages.',
schema: v.object({
task: v.pipe(v.string(), v.description('The task to be performed')),
}),
},
async ({ task }) => {
const available_docs: string[] = (await get_sections()).map((s) => s.title);
return {
messages: [
{
role: 'user',
content: {
type: 'text',
text: `You are a Svelte expert tasked to build components and utilities for Svelte developers. If you need documentation for anything related to Svelte you can invoke the tool \`get-documentation\` with one of the following paths:
<available-docs-paths>
${JSON.stringify(available_docs, null, 2)}
</available-docs-paths>
Every time you write a Svelte component or a Svelte module you MUST invoke the \`svelte-autofixer\` tool providing the code. The tool will return a list of issues or suggestions. If there are any issues or suggestions you MUST fix them and call the tool again with the updated code. You MUST keep doing this until the tool returns no issues or suggestions. Only then you can return the code to the user.
This is the task you will work on:
<task>
${task}
</task>
If you are not writing the code into a file, once you have the final version of the code ask the user if it wants to generate a playground link to quickly check the code in it and if it answer yes call the \`playground-link\` tool and return the url to the user nicely formatted. The playground link MUST be generated only once you have the final version of the code and you are ready to share it, it MUST include an entry point file called \`App.svelte\` where the main component should live. If you have multiple files to include in the playground link you can include them all at the root.
`,
},
},
],
};
},
);
}

View File

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

View File

@@ -0,0 +1 @@
export * from './doc-section.js';

View File

@@ -0,0 +1,120 @@
import type { SvelteMcp } from '../../index.js';
import * as v from 'valibot';
import { get_sections, fetch_with_timeout } from '../../utils.js';
import { SECTIONS_LIST_INTRO, SECTIONS_LIST_OUTRO } from './prompts.js';
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., "docs/svelte/state.md"). Can accept a single section name or an array of sections. Before running this, make sure to analyze the users query, as well as the output from list-sections (which should be called first). Then ask for ALL relevant sections the user might require. For example, if the user asks to build anything interactive, you will need to fetch all relevant runes, and so on.',
schema: v.object({
section: v.pipe(
v.union([v.string(), v.array(v.string())]),
v.description(
'The section name(s) to retrieve. Can search by title (e.g., "$state", "load functions") or file path (e.g., "docs/svelte/state.md"). Supports single string and array of strings',
),
),
}),
},
async ({ section }) => {
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.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 = available_sections
.map(
(section) =>
`* title: ${section.title}, use_cases: ${section.use_cases}, path: ${section.url}`,
)
.join('\n');
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,
},
],
};
},
);
}

View File

@@ -0,0 +1,4 @@
export * from './get-documentation.js';
export * from './list-sections.js';
export * from './svelte-autofixer.js';
export * from './playground-link.js';

View File

@@ -0,0 +1,32 @@
import type { SvelteMcp } from '../../index.js';
import { get_sections } from '../../utils.js';
import { SECTIONS_LIST_INTRO, SECTIONS_LIST_OUTRO } from './prompts.js';
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. Returns sections as a list of "* title: [section_title], use_cases: [use_cases], path: [file_path]" - you can use either the title or path when querying a specific section via the get_documentation tool. Always run list-sections first for any query related to Svelte development to discover available content.',
},
async () => {
const sections = await get_sections();
const formatted_sections = sections
.map(
(section) =>
`* title: ${section.title}, use_cases: ${section.use_cases}, path: ${section.url}`,
)
.join('\n');
return {
content: [
{
type: 'text',
text: `${SECTIONS_LIST_INTRO}\n\n${formatted_sections}\n\n${SECTIONS_LIST_OUTRO}`,
},
],
};
},
);
}

View File

@@ -0,0 +1,112 @@
import type { SvelteMcp } from '../../index.js';
import * as v from 'valibot';
async function compress_and_encode_text(input: string) {
const reader = new Blob([input]).stream().pipeThrough(new CompressionStream('gzip')).getReader();
let buffer = '';
for (;;) {
const { done, value } = await reader.read();
if (done) {
reader.releaseLock();
// Some sites like discord don't like it when links end with =
return btoa(buffer).replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, '');
} else {
for (let i = 0; i < value.length; i++) {
// decoding as utf-8 will make btoa reject the string
buffer += String.fromCharCode(value[i]!);
}
}
}
}
type File = {
type: 'file';
name: string;
basename: string;
contents: string;
text: boolean;
};
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(),
}),
},
async ({ files, name, tailwind }) => {
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) {
return {
isError: true,
content: [
{
type: 'text',
text: JSON.stringify({
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 content = {
url: playground_base.toString(),
};
return {
content: [
{
type: 'text',
text: JSON.stringify(content),
},
],
structuredContent: content,
};
},
);
}

View File

@@ -0,0 +1,5 @@
export const SECTIONS_LIST_INTRO =
'List of available Svelte documentation sections and its inteneded uses:';
export const SECTIONS_LIST_OUTRO =
'Use the title or path with the get-documentation tool to get more details about a specific section.';

View File

@@ -0,0 +1,86 @@
import { basename } from 'node:path';
import type { SvelteMcp } from '../../index.js';
import * as v from 'valibot';
import { add_compile_issues } from '../../autofixers/add-compile-issues.js';
import { add_eslint_issues } from '../../autofixers/add-eslint-issues.js';
import { add_autofixers_issues } from '../../autofixers/add-autofixers-issues.js';
export function svelte_autofixer(server: SvelteMcp) {
server.tool(
{
name: 'svelte-autofixer',
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.literal(4), v.literal(5), v.literal('4'), v.literal('5')]),
v.description(
'The desired svelte version...if possible read this from the package.json of the user project, otherwise use some hint from the wording (if the user asks for runes it wants version 5). Default to 5 in case of doubt.',
),
),
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(),
}),
annotations: {
title: 'Svelte Autofixer',
destructiveHint: false,
readOnlyHint: true,
openWorldHint: false,
},
},
async ({ code, filename: filename_or_path, desired_svelte_version }) => {
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);
add_autofixers_issues(content, code, +desired_svelte_version, filename);
await add_eslint_issues(content, code, +desired_svelte_version, filename);
} 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)`.",
);
}
}
if (content.issues.length > 0 || content.suggestions.length > 0) {
content.require_another_tool_call_after_fixing = true;
}
return {
content: [
{
type: 'text',
text: JSON.stringify(content),
},
],
structuredContent: content,
};
},
);
}

View File

@@ -0,0 +1,30 @@
import { ValibotJsonSchemaAdapter } from '@tmcp/adapter-valibot';
import { McpServer } from 'tmcp';
import { setup_prompts, setup_resources, setup_tools } from './handlers/index.js';
import type { LibSQLDatabase } from 'drizzle-orm/libsql';
import type { Schema } from '@sveltejs/mcp-schema';
export const server = new McpServer(
{
name: 'Svelte MCP',
version: '0.0.1',
description: 'The official Svelte MCP server implementation',
},
{
adapter: new ValibotJsonSchemaAdapter(),
capabilities: {
tools: {},
prompts: {},
resources: {},
completions: {},
},
instructions:
'This is the official Svelte MCP server. It MUST be used whenever svelte development is involved. It can provide official documentation, code examples and correct your code. After you correct the component call this tool again to confirm all the issues are fixed.',
},
).withContext<{ db: LibSQLDatabase<Schema> }>();
export type SvelteMcp = typeof server;
setup_tools(server);
setup_resources(server);
setup_prompts(server);

View File

@@ -0,0 +1,12 @@
import * as v from 'valibot';
export const documentation_sections_schema = v.record(
v.string(),
v.object({
metadata: v.object({
title: v.string(),
use_cases: v.optional(v.string()),
}),
slug: v.string(),
}),
);

View File

@@ -0,0 +1,31 @@
import * as v from 'valibot';
import { documentation_sections_schema } from '../mcp/schemas/index.js';
export async function fetch_with_timeout(
url: string,
timeout_ms: number = 10000,
): Promise<Response> {
try {
const response = await fetch(url, { signal: AbortSignal.timeout(timeout_ms) });
return response;
} catch (error) {
if (error instanceof Error && error.name === 'AbortError') {
throw new Error(`Request timed out after ${timeout_ms}ms`);
}
throw error;
}
}
export async function get_sections() {
const sections = await fetch_with_timeout(
'https://svelte.dev/docs/experimental/sections.json',
).then((res) => res.json());
const validated_sections = v.safeParse(documentation_sections_schema, sections);
if (!validated_sections.success) return [];
return Object.entries(validated_sections.output).map(([, section]) => ({
title: section.metadata.title,
use_cases: section.metadata.use_cases ?? 'read document for use cases',
slug: section.slug,
url: `https://svelte.dev/${section.slug}/llms.txt`,
}));
}

View File

@@ -1,6 +1,6 @@
import type { TSESTree } from '@typescript-eslint/types';
import { describe, expect, it } from 'vitest';
import { walk } from '$lib/index.js';
import { walk } from '../mcp/autofixers/ast/walk.js';
import { parse, type ParseResult } from './parse.js';
// ----------------------------------------------------------------------
@@ -57,8 +57,8 @@ function assert_svelte_file(result: ParseResult) {
const name_var = variable_declaration_from_id(result, declaration_id);
expect(Array.isArray(name_var.defs)).toBe(true);
expect(name_var.defs.length).toBeGreaterThan(0);
expect(name_var.defs[0].type).toBe('Variable');
expect(name_var.defs[0].name && name_var.defs[0].name.name).toBe('name');
expect(name_var.defs[0]?.type).toBe('Variable');
expect(name_var.defs[0]?.name && name_var.defs[0].name.name).toBe('name');
const references_to_name = all_references.filter((rf) => rf.resolved === name_var);
expect(references_to_name.length).toBeGreaterThan(0);
@@ -76,7 +76,7 @@ function assert_sveltejs_file(result: ParseResult) {
const v_var = variable_declaration_from_id(result, declaration_id);
expect(Array.isArray(v_var.defs)).toBe(true);
expect(v_var.defs.length).toBeGreaterThan(0);
expect(v_var.defs[0].type).toBeTruthy();
expect(v_var.defs[0]?.type).toBeTruthy();
const references_to_v = all_references.filter((rf) => rf.resolved === v_var);
expect(references_to_v.length).toBeGreaterThanOrEqual(2);

View File

@@ -0,0 +1,7 @@
# @sveltejs/mcp
## 0.0.2
### Patch Changes
- feat: latest version ([#25](https://github.com/sveltejs/mcp/pull/25))

View File

@@ -0,0 +1,16 @@
# @sveltejs/mcp
The CLI version of the Svelte MCP.
You can run it directly with
```bash
npx @sveltejs/mcp
```
or install it and then run it
```bash
pnpm i @sveltejs/mcp
pnpm svelte-mcp
```

View File

@@ -0,0 +1,43 @@
{
"name": "@sveltejs/mcp",
"version": "0.0.2",
"type": "module",
"license": "MIT",
"homepage": "https://github.com/sveltejs/mcp#readme",
"bugs": {
"url": "https://github.com/sveltejs/mcp/issues"
},
"bin": {
"svelte-mcp": "./dist/index.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/sveltejs/mcp.git",
"path": "packages/mcp-stdio"
},
"files": [
"dist"
],
"publishConfig": {
"access": "public"
},
"scripts": {
"build": "tsdown && publint",
"dev": "tsdown --watch",
"test": "vitest",
"check": "tsc --noEmit",
"check:publint": "publint --strict"
},
"devDependencies": {
"@sveltejs/mcp-server": "workspace:^",
"@tmcp/transport-stdio": "^0.3.1",
"@types/node": "^22.15.17",
"publint": "^0.3.13",
"tsdown": "^0.11.9",
"typescript": "^5.8.3",
"vitest": "^3.1.3"
},
"dependencies": {
"eslint": "^9.36.0"
}
}

View File

@@ -0,0 +1,7 @@
#! /usr/bin/env node
import { server } from '@sveltejs/mcp-server';
import { StdioTransport } from '@tmcp/transport-stdio';
const transport = new StdioTransport(server);
transport.listen();

View File

@@ -0,0 +1,5 @@
{
"extends": "../../tsconfig.json",
"include": ["src"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,21 @@
import { defineConfig } from 'tsdown';
export default defineConfig([
{
entry: ['./src/index.ts'],
platform: 'node',
define: {
// some eslint-plugin-svelte code expects __filename to exists but in an ESM environment it does not.
__filename: 'import.meta.filename',
},
// we need eslint at runtime but the bundler doesn't bundle `require`'s which `eslint-plugin-svelte` uses to require
// `eslint/use-at-your-own-risk`. If we didn't have `eslint` as an actual dependency and didn't externalize it
// the require would fail once executed in a project without eslint installed.
external: ['eslint'],
publint: true,
dts: false,
treeshake: true,
clean: true,
target: 'esnext',
},
]);

View File

@@ -0,0 +1,3 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({});

3267
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
packages:
- '*'
- './packages/*'
- './apps/*'
useNodeVersion: 22.19.0

19
renovate.json Normal file
View File

@@ -0,0 +1,19 @@
{
"extends": [
"config:recommended",
":preserveSemverRanges",
"group:allNonMajor",
":semanticCommitTypeAll(chore)"
],
"pin": {
"enabled": false
},
"ignoreDeps": ["@types/node", "esbuild", "rollup", "typescript"],
"packageRules": [
{
"matchPackageNames": ["vite"],
"matchUpdateTypes": ["major"],
"enabled": false
}
]
}

View File

@@ -1,5 +0,0 @@
import { http_transport } from '$lib/mcp';
export async function handle({ event, resolve }) {
return (await http_transport.respond(event.request)) ?? resolve(event);
}

View File

@@ -1,377 +0,0 @@
import { describe, expect, it } from 'vitest';
import { add_autofixers_issues } from './add-autofixers-issues.js';
import { base_runes } from '../../constants.js';
const dollarless_runes = base_runes.map((r) => ({ rune: r.replace('$', '') }));
function run_autofixers_on_code(code: string, desired_svelte_version = 5) {
const content = { issues: [], suggestions: [] };
add_autofixers_issues(content, code, desired_svelte_version);
return content;
}
describe('add_autofixers_issues', () => {
describe('assign_in_effect', () => {
it(`should add suggestions when assigning to a stateful variable inside an effect`, () => {
const content = run_autofixers_on_code(`
<script>
const count = $state(0);
$effect(() => {
count = 43;
});
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
);
});
it(`should add a suggestion for each variable assigned within an effect`, () => {
const content = run_autofixers_on_code(`
<script>
const count = $state(0);
const count2 = $state(0);
$effect(() => {
count = 43;
count2 = 44;
});
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(2);
expect(content.suggestions).toContain(
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
);
expect(content.suggestions).toContain(
'The stateful variable "count2" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
);
});
it(`should not add a suggestion for variables that are not assigned within an effect`, () => {
const content = run_autofixers_on_code(`
<script>
const count = $state(0);
</script>
<button onclick={() => count = 43}>Increment</button>
`);
expect(content.suggestions).not.toContain(
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
);
});
it("should not add a suggestions for variables that are assigned within an effect but aren't stateful", () => {
const content = run_autofixers_on_code(`
<script>
const count = 0;
$effect(() => {
count = 43;
});
</script>`);
expect(content.suggestions).not.toContain(
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
);
});
it(`should add a suggestion for variables that are assigned within an effect with an update`, () => {
const content = run_autofixers_on_code(`
<script>
let count = $state(0);
$effect(() => {
count++;
});
</script>
`);
expect(content.suggestions).toContain(
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
);
});
it(`should add a suggestion for variables that are mutated within an effect`, () => {
const content = run_autofixers_on_code(`
<script>
let count = $state({ value: 0 });
$effect(() => {
count.value = 42;
});
</script>
`);
expect(content.suggestions).toContain(
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
);
});
});
describe.each([{ method: 'set' }, { method: 'update' }])(
'set_or_update_state ($method)',
({ method }) => {
it(`should add suggestions when using .${method}() on a stateful variable with a literal init`, () => {
const content = run_autofixers_on_code(`
<script>
const count = $state(0);
function update_count() {
count.${method}(43);
}
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them.`,
);
});
it(`should add suggestions when using .${method}() on a stateful variable with an array init`, () => {
const content = run_autofixers_on_code(`
<script>
const count = $state([0]);
function update_count() {
count.${method}([1]);
}
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them.`,
);
});
it(`should add suggestions when using .${method}() on a stateful variable with conditional if it's not sure if the method could actually be present on the variable ($state({}))`, () => {
const content = run_autofixers_on_code(`
<script>
const count = $state({ value: 0 });
function update_count() {
count.${method}({ value: 43 });
}
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them. However I can't verify if "count" is a state variable of an object or a class with a "${method}" method on it. Please verify that before updating the code to use a normal assignment`,
);
});
it(`should add suggestions when using .${method}() on a stateful variable with conditional if it's not sure if the method could actually be present on the variable ($state(new Class()))`, () => {
const content = run_autofixers_on_code(`
<script>
const count = $state(new Class());
function update_count() {
count.${method}(new Class());
}
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them. However I can't verify if "count" is a state variable of an object or a class with a "${method}" method on it. Please verify that before updating the code to use a normal assignment`,
);
});
it(`should add suggestions when using .${method}() on a stateful variable with conditional if it's not sure if the method could actually be present on the variable ($state(variable_name))`, () => {
const content = run_autofixers_on_code(`
<script>
const { init } = $props();
const count = $state(init);
function update_count() {
count.${method}(43);
}
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them. However I can't verify if "count" is a state variable of an object or a class with a "${method}" method on it. Please verify that before updating the code to use a normal assignment`,
);
});
it(`should not add suggestions when using .${method} on a stateful variable if it's not a method call`, () => {
const content = run_autofixers_on_code(`
<script>
const count = $state({});
function update_count() {
console.log(count.${method});
}
</script>`);
expect(content.suggestions).not.toContain(
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them. However I can't verify if "count" is a state variable of an object or a class with a "${method}" method on it. Please verify that before updating the code to use a normal assignment`,
);
});
},
);
describe('imported_runes', () => {
describe.each([{ source: 'svelte' }, { source: 'svelte/runes' }])(
'from "$source"',
({ source }) => {
describe.each(dollarless_runes)('single import ($rune)', ({ rune }) => {
it(`should add suggestions when importing '${rune}' from '${source}'`, () => {
const content = run_autofixers_on_code(`
<script>
import { ${rune} } from '${source}';
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are importing "${rune}" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$${rune}" directly.`,
);
});
it(`should add suggestions when importing "${rune}" as the default export from '${source}'`, () => {
const content = run_autofixers_on_code(`
<script>
import ${rune} from '${source}';
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are importing "${rune}" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$${rune}" directly.`,
);
});
it(`should add suggestions when importing '${rune}' as the namespace export from '${source}'`, () => {
const content = run_autofixers_on_code(`
<script>
import * as ${rune} from '${source}';
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are importing "${rune}" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$${rune}" directly.`,
);
});
});
it(`should add suggestions when importing multiple runes from '${source}'`, () => {
const content = run_autofixers_on_code(`
<script>
import { onMount, state, effect } from '${source}';
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(2);
expect(content.suggestions).toContain(
`You are importing "state" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$state" directly.`,
);
expect(content.suggestions).toContain(
`You are importing "effect" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$effect" directly.`,
);
});
it(`should not add suggestions when importing other identifiers from '${source}'`, () => {
const content = run_autofixers_on_code(`
<script>
import { onMount } from '${source}';
</script>`);
expect(content.suggestions).not.toContain(
`You are importing "onMount" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$onMount" directly.`,
);
});
},
);
});
describe('derived_with_function', () => {
it(`should add suggestions when using a function as the first argument to $derived`, () => {
const content = run_autofixers_on_code(`
<script>
const value = $derived(() => {
return 43;
});
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
'You are passing a function to $derived when declaring "value" but $derived expects an expression. You can use $derived.by instead.',
);
});
it(`should add suggestions when using a function as the first argument to $derived in classes`, () => {
const content = run_autofixers_on_code(`
<script>
class Double {
value = $derived(() => 43);
}
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
'You are passing a function to $derived when declaring "value" but $derived expects an expression. You can use $derived.by instead.',
);
});
it(`should add suggestions when using a function as the first argument to $derived in classes constructors`, () => {
const content = run_autofixers_on_code(`
<script>
class Double {
value;
constructor(){
this.value = $derived(function() { return 44; });
}
}
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
'You are passing a function to $derived when declaring "value" but $derived expects an expression. You can use $derived.by instead.',
);
});
it(`should add suggestions when using a function as the first argument to $derived without the declaring part if it's not an identifier`, () => {
const content = run_autofixers_on_code(`
<script>
const { destructured } = $derived(() => 43);
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
'You are passing a function to $derived but $derived expects an expression. You can use $derived.by instead.',
);
});
it(`should add suggestions when using a function as the first argument to $derived.by`, () => {
const content = run_autofixers_on_code(`
<script>
const { destructured } = $derived.by(() => 43);
</script>`);
expect(content.suggestions).not.toContain(
'You are passing a function to $derived but $derived expects an expression. You can use $derived.by instead.',
);
});
});
describe('use_runes_instead_of_store', () => {
describe.each([{ import: 'derived' }, { import: 'writable' }, { import: 'readable' }])(
'importing $import from svelte/store',
({ import: imported }) => {
it(`should add suggestions when importing '${imported}' from 'svelte/store'`, () => {
const content = run_autofixers_on_code(`
<script>
import { ${imported} } from 'svelte/store';
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are importing "${imported}" from "svelte/store". Unless the user specifically asked for stores or it's required because some library/component requires a store as input consider using runes like \`$state\` or \`$derived\` instead, all runes are globally available.`,
);
});
},
);
it(`should not add suggestions when importing other identifiers from 'svelte/store'`, () => {
const content = run_autofixers_on_code(`
<script>
import { get } from 'svelte/store';
</script>`);
expect(content.suggestions).not.toContain(
`You are importing "get" from "svelte/store". Unless the user specifically asked for stores or it's required because some library/component requires a store as input consider using runes like \`$state\` or \`$derived\` instead, all runes are globally available.`,
);
});
});
});

View File

@@ -1,37 +0,0 @@
import type { Autofixer } from '.';
import { left_most_id } from '../ast/utils.js';
const UPDATE_PROPERTIES = ['set', 'update'];
export const set_or_update_state: Autofixer = {
MemberExpression(node, { state, next, path }) {
const parent = path[path.length - 1];
if (
parent.type === 'CallExpression' &&
parent.callee === node &&
node.property.type === 'Identifier' &&
UPDATE_PROPERTIES.includes(node.property.name)
) {
const id = left_most_id(node);
if (id) {
const reference = state.parsed.find_reference_by_id(id);
const definition = reference?.resolved?.defs[0];
if (definition && definition.type === 'Variable') {
const init = definition.node.init;
if (
init?.type === 'CallExpression' &&
state.parsed.is_rune(init, ['$state', '$state.raw'])
) {
let suggestion = `You are trying to update the stateful variable "${id.name}" using "${node.property.name}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them.`;
const argument = init.arguments[0];
if (!argument || (argument.type !== 'Literal' && argument.type !== 'ArrayExpression')) {
suggestion += ` However I can't verify if "${id.name}" is a state variable of an object or a class with a "${node.property.name}" method on it. Please verify that before updating the code to use a normal assignment`;
}
state.output.suggestions.push(suggestion);
}
}
}
}
next();
},
};

View File

@@ -1,135 +0,0 @@
import { ValibotJsonSchemaAdapter } from '@tmcp/adapter-valibot';
import { HttpTransport } from '@tmcp/transport-http';
import { StdioTransport } from '@tmcp/transport-stdio';
import { McpServer } from 'tmcp';
import * as v from 'valibot';
import { add_autofixers_issues } from './autofixers/add-autofixers-issues.js';
import { add_compile_issues } from './autofixers/add-compile-issues.js';
import { add_eslint_issues } from './autofixers/add-eslint-issues.js';
const server = new McpServer(
{
name: 'Svelte MCP',
version: '0.0.1',
description: 'The official Svelte MCP server implementation',
},
{
adapter: new ValibotJsonSchemaAdapter(),
capabilities: {
tools: {},
prompts: {},
resources: {},
completions: {},
},
instructions:
'This is the official Svelte MCP server. It MUST be used whenever svelte development is involved. It can provide official documentation, code examples and correct your code. After you correct the component call this tool again to confirm all the issues are fixed.',
},
);
server.tool(
{
name: 'svelte-autofixer',
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.literal(4), v.literal(5)]),
v.description(
'The desired svelte version...if possible read this from the package.json of the user project, otherwise use some hint from the wording (if the user asks for runes it wants version 5). Default to 5 in case of doubt.',
),
),
filename: v.optional(v.string()),
}),
outputSchema: v.object({
issues: v.array(v.string()),
suggestions: v.array(v.string()),
require_another_tool_call_after_fixing: v.boolean(),
}),
annotations: {
title: 'Svelte Autofixer',
destructiveHint: false,
readOnlyHint: true,
openWorldHint: false,
},
},
async ({ code, filename, desired_svelte_version }) => {
const content: {
issues: string[];
suggestions: string[];
require_another_tool_call_after_fixing: boolean;
} = { issues: [], suggestions: [], require_another_tool_call_after_fixing: false };
try {
add_compile_issues(content, code, desired_svelte_version, filename);
add_autofixers_issues(content, code, desired_svelte_version, filename);
await add_eslint_issues(content, code, desired_svelte_version, filename);
} 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 (content.issues.length > 0 || content.suggestions.length > 0) {
content.require_another_tool_call_after_fixing = true;
}
return {
content: [
{
type: 'text',
text: JSON.stringify(content),
},
],
structuredContent: content,
};
},
);
server.prompt(
{
name: 'svelte-task-prompt',
title: 'Svelte Task Prompt',
description:
'Use this Prompt to ask for any svelte related task. It will automatically instruct the LLM on how to best use the autofixer and how to query for documentation pages.',
schema: v.object({
task: v.pipe(v.string(), v.description('The task to be performed')),
}),
},
async ({ task }) => {
// TODO: implement logic to fetch the available docs paths to return in the prompt
const available_docs: string[] = [];
return {
messages: [
{
role: 'user',
content: {
type: 'text',
text: `You are a Svelte expert tasked to build components and utilities for Svelte developers. If you need documentation for anything related to Svelte you can invoke the tool \`get_documentation\` with one of the following paths:
<available-docs-paths>
${JSON.stringify(available_docs, null, 2)}
</available-docs-paths>
Every time you write a Svelte component or a Svelte module you MUST invoke the \`svelte-autofixer\` tool providing the code. The tool will return a list of issues or suggestions. If there are any issues or suggestions you MUST fix them and call the tool again with the updated code. You MUST keep doing this until the tool returns no issues or suggestions. Only then you can return the code to the user.
This is the task you will work on:
<task>
${task}
</task>
`,
},
},
],
};
},
);
export const http_transport = new HttpTransport(server, {
cors: true,
});
export const stdio_transport = new StdioTransport(server);

View File

@@ -1,13 +0,0 @@
import { createClient } from '@libsql/client';
import { drizzle } from 'drizzle-orm/libsql';
import * as schema from './schema';
if (!process.env.DATABASE_URL) throw new Error('DATABASE_URL is not set');
if (!process.env.DATABASE_TOKEN) throw new Error('DATABASE_TOKEN is not set');
const client = createClient({
url: process.env.DATABASE_URL,
authToken: process.env.DATABASE_TOKEN,
});
export const db = drizzle(client, { schema, logger: true });

View File

@@ -1,4 +0,0 @@
#!/usr/bin/env node
import { stdio_transport } from './mcp/index.js';
stdio_transport.listen();

Some files were not shown because too many files have changed in this diff Show More