Compare commits

..

261 Commits

Author SHA1 Message Date
paoloricciuti
9571736279 fix: remove restrictive permissions from subagent 2026-05-22 08:19:28 +02:00
Elliott Johnson
88cea9e539 Revert "chore: Supply chain hardening" (#216) 2026-05-20 08:53:28 -06:00
Elliott Johnson
fdb1bc7370 chore: Supply chain hardening 2026-05-20 08:46:41 -06:00
Tee Ming
5b4d3aa68a docs: fix broken skills link (#211)
Co-authored-by: Paolo Ricciuti <ricciutipaolo@gmail.com>
2026-05-15 21:51:07 +02:00
github-actions[bot]
04c52f2b72 Version Packages (#197)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-13 20:51:10 +02:00
Paolo Ricciuti
0ea2a617ef docs: overhaul docs focus on all ai tools (#188)
Co-authored-by: jyc.dev <jycouet@gmail.com>
Co-authored-by: Tee Ming <chewteeming01@gmail.com>
Co-authored-by: Rich Harris <rich.harris@vercel.com>
2026-05-13 20:50:40 +02:00
Paolo Ricciuti
484453e5f8 feat: allow stdio mcp to read the content of the file directly (#198) 2026-05-13 20:26:18 +02:00
Paolo Ricciuti
841af5b3a4 fix: handle non call expressions passed to is_rune (#201)
Co-authored-by: Copilot <copilot@github.com>
2026-04-28 17:12:58 +02:00
Paolo Ricciuti
1ce957ac72 chore: add LICENSE 2026-04-28 14:48:50 +02:00
Paolo Ricciuti
e429cd7839 chore: remove db requirement (#196) 2026-04-20 12:50:45 +02:00
github-actions[bot]
2ded13539a Version Packages (#192)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-10 18:39:19 +02:00
paoloricciuti
96c50acae2 fix: add server export to opencode plugin 2026-04-10 18:37:49 +02:00
github-actions[bot]
4920088e5c Version Packages (#191)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-10 16:57:38 +02:00
Paolo Ricciuti
8557f0af6f fix: import ts files directly (#190) 2026-04-10 16:56:23 +02:00
renovate[bot]
972d0f0bb2 chore(deps): update dependency @vercel/analytics to v2 (#177)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-27 10:15:32 +01:00
renovate[bot]
7bf364a9d5 chore(deps): update pnpm/action-setup action to v5 (#186)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-27 10:14:49 +01:00
Paolo Ricciuti
d06d758b81 chore: sync agents with the opencode plugin too (#187) 2026-03-25 09:43:52 +01:00
github-actions[bot]
e5ce7437c4 Version Packages (#179)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-03-13 23:07:32 +01:00
Paolo Ricciuti
2f422ee190 fix: broaden checks for imported runes because LLMs are unhinged (#185) 2026-03-13 22:59:52 +01:00
paoloricciuti
14f087cd7a chore: simplify sync script/action 2026-03-13 22:22:19 +01:00
Chris Tsongas
1ef5ddf605 fix: update svelte-file-editor agent to use proper name (#183)
Co-authored-by: paoloricciuti <ricciutipaolo@gmail.com>
2026-03-13 10:37:43 +01:00
github-actions[bot]
0e55ee792d chore: sync skills from svelte.dev (#178)
Co-authored-by: svelte-docs-bot[bot] <196124396+svelte-docs-bot[bot]@users.noreply.github.com>
Co-authored-by: Paolo Ricciuti <ricciutipaolo@gmail.com>
2026-03-12 18:32:21 +01:00
paoloricciuti
84ec24b6f6 fix: don't format code blocks in references 2026-03-12 18:19:43 +01:00
paoloricciuti
710cebe539 docs: opencode config link 2026-03-12 17:59:32 +01:00
paoloricciuti
b2a380c4ce docs: fix markdown blocks 2026-03-12 17:57:45 +01:00
paoloricciuti
eef0a9b4d9 docs: update opencode docs 2026-03-11 21:31:43 +01:00
Paolo Ricciuti
260b36e8af chore: undo rename for publish to registry check 2026-03-11 14:11:15 +01:00
github-actions[bot]
6df3ebe568 Version Packages (#173)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-03-11 13:08:21 +01:00
Momcilo Miladinovic
27a2fc5653 fix: merge user-configured svelte-file-editor agent settings (#176)
Co-authored-by: Momochiro <x@Mac.sweet.opnsense>
Co-authored-by: Paolo Ricciuti <ricciutipaolo@gmail.com>
2026-03-11 12:59:01 +01:00
Paolo Ricciuti
5cd99d8234 feat: allow enabling a specific skill in opencode plugin (#174) 2026-03-10 17:38:04 +01:00
Paolo Ricciuti
e9f19199cb feat: add sync skill docs (#172) 2026-03-09 17:49:18 +01:00
Willow (GHOST)
480b46df0a feat(opencode): mcp enabled option is passed to opencode (#171)
Co-authored-by: Paolo Ricciuti <ricciutipaolo@gmail.com>
2026-03-07 12:38:45 +01:00
jyc.dev
5f5fb27977 feat: display similar result & error at the end (#161) 2026-03-07 12:20:18 +01:00
Paolo Ricciuti
29cfa77c39 chore: restructure repo to have single source of truth for tools (#170) 2026-03-03 21:43:34 +01:00
github-actions[bot]
249923b1e5 Version Packages (#169)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-27 22:24:34 +01:00
Paolo Ricciuti
8c67cae90f feat: svelte-core-bestpractices skill (#162) 2026-02-27 22:19:11 +01:00
Paolo Ricciuti
4aad2e0cfe fix: rename repos in file (#168) 2026-02-27 20:01:15 +01:00
github-actions[bot]
02935b00a6 Version Packages (#164)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-22 15:05:33 +01:00
Paolo Ricciuti
1ea98adacd feat: cursor plugin (#166) 2026-02-22 15:02:31 +01:00
Paolo Ricciuti
494409cc42 fix: better subagent instructions to use MCP or skill (#163) 2026-02-11 22:57:09 +01:00
Paolo Ricciuti
46d8f6cce8 fix: only run "Publish to MCP registry" when MCP is published (#160) 2026-02-07 22:50:08 +01:00
github-actions[bot]
8dc63dca08 chore: sync skills and update documentation (#158)
Co-authored-by: paoloricciuti <26281609+paoloricciuti@users.noreply.github.com>
2026-02-06 18:41:18 +01:00
paoloricciuti
19fedcd35f docs: add instructions to default schema` 2026-02-06 18:40:55 +01:00
István Pató
b4eb5cc960 Update description for svelte-code-writer skill (#149) 2026-02-06 18:37:40 +01:00
paoloricciuti
6676fd8116 docs: add local opencode configuration 2026-02-06 18:37:03 +01:00
github-actions[bot]
a755a33a5f Version Packages (#157)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-06 17:30:56 +01:00
Paolo Ricciuti
556f96cfaf feat: allow for local opencode config (#156) 2026-02-06 17:25:33 +01:00
github-actions[bot]
77a3340c2f chore: sync AGENTS.md to opencode package and documentation (#155)
Co-authored-by: paoloricciuti <26281609+paoloricciuti@users.noreply.github.com>
2026-02-06 17:11:38 +01:00
Paolo Ricciuti
9ac8fd51e7 chore: sync AGENTS.md with opencode and docs (#154)
Co-authored-by: jyc.dev <jycouet@gmail.com>
2026-02-06 17:10:42 +01:00
paoloricciuti
c764308d79 fix: update version of claude plugin 2026-01-31 10:02:31 +01:00
paoloricciuti
01a7e6a8d3 fix: correct command for svelte language server 2026-01-31 09:52:57 +01:00
Paolo Ricciuti
d8ed686e3a chore: use @include in docs (#152) 2026-01-30 16:30:52 -05:00
github-actions[bot]
6f0390d0a9 Version Packages (#153)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-30 16:23:09 +01:00
paoloricciuti
c2c1b3e5e7 fix: actually push skills to right config path 2026-01-30 16:20:36 +01:00
github-actions[bot]
cdfbb907b6 Version Packages (#147)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-30 16:03:12 +01:00
Paolo Ricciuti
2eb2b18008 feat: distribute skills through opencode plugin (#151) 2026-01-30 15:52:35 +01:00
renovate[bot]
e8554f3d8f chore(deps): update all non-major dependencies (#137)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: paoloricciuti <ricciutipaolo@gmail.com>
2026-01-27 22:19:34 +01:00
Christopher Harrison
0213bd951f Add Copilot CLI setup instructions (#148) 2026-01-27 22:05:30 +01:00
paoloricciuti
2245cb2dc9 fix: turn off no-inspect in eslint for mcp 2026-01-27 16:25:30 +01:00
renovate[bot]
2a5f7d6314 chore(deps): update dependency @mcp-ui/server to v6 (#146)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: paoloricciuti <ricciutipaolo@gmail.com>
2026-01-27 16:11:09 +01:00
paoloricciuti
8518e627d3 chore: remove logs 2026-01-23 18:41:14 +01:00
paoloricciuti
9e824da9e2 chore: better debug logs 2026-01-23 18:32:54 +01:00
paoloricciuti
b38f2c11da fix: debug log 2026-01-23 18:28:37 +01:00
paoloricciuti
ea37c7120c chore: use new disableSee option in tmcp 2026-01-23 07:58:36 +01:00
paoloricciuti
099e939f79 fix: remove log duh 2026-01-22 23:50:37 +01:00
paoloricciuti
69b36eefdc fix: return 405 for SSE stream instead of closing immediately 2026-01-22 23:47:59 +01:00
github-actions[bot]
15ad554f53 Version Packages (#145)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-19 15:56:05 +01:00
paoloricciuti
74477448ce chore: update svelte 2026-01-19 15:52:46 +01:00
github-actions[bot]
6f538265d1 Version Packages (#144)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-17 15:21:24 +01:00
paoloricciuti
71295bc11f chore: add README to opencode plugin 2026-01-17 15:20:11 +01:00
Rich Harris
b5040ff5cf docs: tweak Claude Code documentation (#143)
Co-authored-by: Paolo Ricciuti <ricciutipaolo@gmail.com>
2026-01-17 01:04:32 +01:00
github-actions[bot]
45c961417f Version Packages (#141)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-16 19:26:42 +01:00
paoloricciuti
57e2d1def1 fix: show toast after a few seconds to avoid race condition 2026-01-16 19:21:42 +01:00
Paolo Ricciuti
398a703580 feat: opencode plugin (#142)
Co-authored-by: Rich Harris <hello@rich-harris.dev>
2026-01-16 12:41:02 -05:00
Paolo Ricciuti
75c802a115 feat: add lsp config for claude plugin (#140) 2026-01-15 15:00:38 +01:00
Paolo Ricciuti
53a634cdb0 feat: expose playground link as MCP App (#138) 2026-01-15 14:59:46 +01:00
paoloricciuti
bcdc33e7a5 fix: automatically accept edits in the svelte file editor agent 2026-01-05 22:18:46 +01:00
github-actions[bot]
825ae33427 Version Packages (#136)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-05 09:43:10 +01:00
Paolo Ricciuti
ccf940cc45 feat: claude skill + marketplace (#110)
Co-authored-by: Stanislav Khromov <stanislav.khromov+github@gmail.com>
2026-01-04 23:00:03 +01:00
Paolo Ricciuti
b2f195fb7b fix: add suggestion for snippets declared in script tag (#132) 2026-01-04 12:00:33 +01:00
renovate[bot]
d8e4b18bff chore(deps): update pnpm to v10.27.0 (#133)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-03 15:40:19 +01:00
renovate[bot]
6a2198b433 chore(deps): update dependency globals to v17 (#134)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-03 15:40:10 +01:00
renovate[bot]
2ce60c6110 chore(deps): update pnpm to v10.26.2 (#131)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-30 14:26:52 +01:00
github-actions[bot]
0f8987fdcf Version Packages (#130)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-22 00:18:38 +01:00
Paolo Ricciuti
42911e2631 feat: expose tools as JS api + cli (#128) 2025-12-22 00:16:36 +01:00
paoloricciuti
655eb85eba fix: use pnpm pack for pkg.pr.new publish 2025-12-19 18:26:45 +01:00
renovate[bot]
89403a7d0c chore(deps): update pnpm to v10.26.1 (#129)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-19 17:23:59 +01:00
paoloricciuti
3747623d55 chore: setup pkg.pr.new 2025-12-18 20:00:30 +01:00
github-actions[bot]
c1f230455f Version Packages (#127)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-17 19:48:37 +01:00
paoloricciuti
9dfb4dedb4 fix: server.json version + update publisher 2025-12-17 19:46:59 +01:00
github-actions[bot]
b891e4860b Version Packages (#126)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-17 18:36:08 +01:00
Paolo Ricciuti
c9e8508dd9 fix: changesets 2025-12-17 18:31:39 +01:00
github-actions[bot]
9896406aff docs: update prompt documentation (#125)
Co-authored-by: paoloricciuti <26281609+paoloricciuti@users.noreply.github.com>
2025-12-17 18:29:54 +01:00
Paolo Ricciuti
b01ae9069b fix: typo again 2025-12-17 18:27:20 +01:00
Paolo Ricciuti
b6db495242 fix: changesets 2025-12-17 18:19:08 +01:00
Paolo Ricciuti
3926310107 fix: typo in documentation tool name in svelte_task 2025-12-17 18:17:30 +01:00
Paolo Ricciuti
848627549f fix: improve prompt to reduce token usage (#124)
Co-authored-by: Stanislav Khromov <stanislav.khromov+github@gmail.com>
2025-12-17 18:13:07 +01:00
renovate[bot]
283164ca7e chore(deps): update peter-evans/create-pull-request action to v8 (#121)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-17 16:48:55 +01:00
renovate[bot]
c6ee414f62 chore(deps): update all non-major dependencies (#119)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-17 16:48:38 +01:00
Matheus Ferraz
ea5bdf66dc chore(docs): fix tool name typo (#120) 2025-12-13 01:32:19 +01:00
paoloricciuti
7d707202d1 fix: update tmcp 2025-12-06 10:44:04 +01:00
renovate[bot]
f0daadfbd0 chore(deps): update all non-major dependencies (#117)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-05 23:52:14 +01:00
github-actions[bot]
15a7774da7 Version Packages (#118)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-05 22:34:53 +01:00
paoloricciuti
98efa1e09e fix: revert name change and add title 2025-12-05 22:26:33 +01:00
github-actions[bot]
e20cf2974d Version Packages (#100)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-03 10:37:26 +01:00
paoloricciuti
b2ee968a3f chore: update publisher 2025-12-03 10:36:46 +01:00
paoloricciuti
60297b3c49 fix: update server name on mcp registry 2025-12-03 10:34:39 +01:00
renovate[bot]
39076da8ce chore(deps): update dependency @eslint/compat to v2 (#111)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-03 10:34:15 +01:00
renovate[bot]
fba733646a chore(deps): update all non-major dependencies (#112)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-28 10:02:16 +01:00
renovate[bot]
7fcd4705a5 chore(deps): update actions/checkout action to v6 (#114)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-28 10:02:00 +01:00
renovate[bot]
af7d341ba5 chore(deps): update pnpm to v10.22.0 (#107)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-13 15:29:04 +01:00
Lucas Terracino
52546551ff Update valid scope options in remote setup documentation for Gemini CLI (#108) 2025-11-12 23:01:54 +01:00
renovate[bot]
1f0a5f1519 chore(deps): update all non-major dependencies (#98)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: paoloricciuti <ricciutipaolo@gmail.com>
2025-11-11 12:39:11 +01:00
Scott Wu
0bf04bad2e docs: add reference to GEMINI.md and CLI change (#106) 2025-11-11 12:19:12 +01:00
Paolo Ricciuti
f001918925 chore: add initialize analytics event (#104) 2025-11-04 11:21:49 +01:00
paoloricciuti
67487c324a fix: remove log 2025-11-04 11:03:23 +01:00
jyc.dev
5beeef5543 moving to pnpm catalogs (#101) 2025-11-01 00:10:54 +01:00
Willow (GHOST)
e1a03fdb85 docs: update zed docs to use svelte-mcp extension (#102) 2025-10-31 17:11:46 +01:00
Paolo Ricciuti
384c1fd209 chore: update tmcp (#99) 2025-10-30 09:34:03 +01:00
renovate[bot]
b3027fd815 chore(deps): update dependency @anthropic-ai/sdk to ^0.68.0 (#97)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-29 08:27:08 +01:00
renovate[bot]
849bf2ad49 chore(deps): update dependency node to v24 (#95)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-28 19:11:08 +01:00
renovate[bot]
314538c8e7 chore(deps): update pnpm to v10.20.0 (#96)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-28 19:05:19 +01:00
github-actions[bot]
a9994310c0 Version Packages (#92)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-10-25 17:07:07 +02:00
Paolo Ricciuti
9fb1a403b7 fix: add async parameter to svelte-autofixer (#94) 2025-10-25 17:04:15 +02:00
github-actions[bot]
3c7b5033a4 docs: update prompt documentation (#93)
Co-authored-by: paoloricciuti <26281609+paoloricciuti@users.noreply.github.com>
2025-10-24 23:12:06 +02:00
Paolo Ricciuti
b911a00bb7 feat: analytics (#88) 2025-10-24 23:06:58 +02:00
paoloricciuti
f6ce89ff34 fix: install latest eslint svelte packages to support $state.eager 2025-10-24 21:02:25 +02:00
github-actions[bot]
846514858e Version Packages (#86)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-10-24 10:42:04 +02:00
Yuichiro Yamashita
b69ea052bd feat: support: $state.eager (#90)
Co-authored-by: Paolo Ricciuti <ricciutipaolo@gmail.com>
2025-10-24 10:34:00 +02:00
Tee Ming
e56159dda6 chore: add FUNDING.yml (#89) 2025-10-24 16:37:32 +09:00
paoloricciuti
1e83c35faa fix: update transport and cancel request only after sending it 2025-10-23 19:10:34 +02:00
Paolo Ricciuti
31edfe1b5f Merge pull request #85 from sveltejs/renovate/major-vitest-monorepo 2025-10-23 10:07:20 +02:00
paoloricciuti
3fabcc0f9b fix: add preferred-frame-size to UI resource 2025-10-23 09:57:51 +02:00
renovate[bot]
deb5f2670c chore(deps): update dependency vitest to v4 2025-10-22 19:36:46 +00:00
paoloricciuti
02c951baa8 fix: add icons to server.json 2025-10-22 21:35:01 +02:00
Paolo Ricciuti
41ceb83838 Merge pull request #79 from sveltejs/changeset-release/main 2025-10-22 17:23:40 +02:00
github-actions[bot]
3c3a26f031 Version Packages 2025-10-22 15:22:27 +00:00
Paolo Ricciuti
6589c7e250 Merge pull request #83 from sveltejs/renovate/all-minor-patch 2025-10-22 17:21:52 +02:00
Paolo Ricciuti
e09b8cd0b9 Merge pull request #84 from sveltejs/mcp-ui-playground 2025-10-22 17:21:33 +02:00
paoloricciuti
7f52a2b1be fix: use embed for mcp-ui embed 2025-10-22 14:56:24 +02:00
paoloricciuti
4eecd75759 chore: changesets 2025-10-22 09:59:39 +02:00
paoloricciuti
9015753f77 feat: return mcp ui resource for playground 2025-10-22 09:47:57 +02:00
renovate[bot]
4d6a9cb333 chore(deps): update pnpm to v10.19.0 2025-10-21 23:14:53 +00:00
Paolo Ricciuti
60aa30397f Merge pull request #80 from UltimateStarCoder/patch-1 2025-10-19 11:11:12 +02:00
paoloricciuti
17ed3a3e23 fix: lint 2025-10-19 11:08:57 +02:00
Paolo Ricciuti
f49bd06fbd chore: revert spacing 2025-10-19 11:05:50 +02:00
UltimateStarCoder
b98c042ae3 Correct JSON structure in remote setup documentation
Fixed JSON syntax for mcpServers configuration.
2025-10-18 17:43:58 -06:00
paoloricciuti
917a93d3fd fix: update deps and add path 2025-10-18 16:20:25 +02:00
Paolo Ricciuti
371e96befc Merge pull request #72 from adamtegen/patch-2 2025-10-17 18:58:29 +02:00
Paolo Ricciuti
bdfd5a109f fix: lint 2025-10-17 17:31:09 +02:00
Paolo Ricciuti
1c6c0a9fa7 Merge pull request #78 from sveltejs/invalid-css-autofixer
feat: suggest against js variables in css
2025-10-17 16:33:44 +02:00
paoloricciuti
a321244543 feat: suggest against js variables in css 2025-10-17 15:31:35 +02:00
paoloricciuti
ed25933466 fix: use right url + add manual triggering 2025-10-17 08:24:31 +02:00
Paolo Ricciuti
72f91dfb7b Merge pull request #77 from sveltejs/changeset-release/main 2025-10-17 08:12:59 +02:00
github-actions[bot]
d36855c447 Version Packages 2025-10-17 06:12:43 +00:00
paoloricciuti
5fa2baa270 fix: upgrade registry publisher cli 2025-10-17 08:12:09 +02:00
Paolo Ricciuti
6543150a5b Merge pull request #76 from sveltejs/changeset-release/main 2025-10-17 08:00:47 +02:00
github-actions[bot]
4c7f7feeba Version Packages 2025-10-17 05:59:44 +00:00
paoloricciuti
579be877fa fix: use correct server schema version 2025-10-17 07:59:10 +02:00
Paolo Ricciuti
0d55c0f61a Merge pull request #68 from sveltejs/changeset-release/main 2025-10-17 07:56:24 +02:00
github-actions[bot]
7d7b08610d Version Packages 2025-10-17 05:56:00 +00:00
Paolo Ricciuti
c08d8d4df7 Merge pull request #74 from sveltejs/renovate/all-minor-patch 2025-10-17 07:55:28 +02:00
paoloricciuti
12dd3c16ac fix: update deps 2025-10-17 01:26:35 +02:00
paoloricciuti
ca17a18677 fix: remove sizes 2025-10-17 00:42:47 +02:00
paoloricciuti
cf62286912 fix: use data: uri for local icon & add icons to tools + resources + prompts 2025-10-17 00:31:23 +02:00
Paolo Ricciuti
a4dfaab1c6 Merge pull request #75 from sveltejs/icon
feat: add svelte icon for mcp server
2025-10-16 23:32:18 +02:00
paoloricciuti
7b396ad63f fix: we need to serve them from the same domain 2025-10-16 23:24:52 +02:00
paoloricciuti
a9653f9c74 fix: svg is not supported, add also png 2025-10-16 23:21:19 +02:00
paoloricciuti
cabf1fd96a feat: add website url 2025-10-16 23:14:58 +02:00
renovate[bot]
2d50ffd38c chore(deps): update dependency @anthropic-ai/sdk to ^0.67.0 2025-10-16 21:14:38 +00:00
paoloricciuti
66c9056e0f feat: add svelte icon for mcp server 2025-10-16 23:13:34 +02:00
Paolo Ricciuti
e639e3ad5c chore: apply suggestions from code review
Co-authored-by: Willow (GHOST) <ghostdevbusiness@gmail.com>
2025-10-16 16:34:25 +02:00
Paolo Ricciuti
322f416c3d Merge pull request #71 from sveltejs/renovate/all-minor-patch 2025-10-16 16:32:49 +02:00
Paolo Ricciuti
c40a3fcb5c Merge pull request #73 from sveltejs/renovate/sveltejs-adapter-vercel-6.x 2025-10-16 16:31:58 +02:00
renovate[bot]
a282623cc7 chore(deps): update dependency @sveltejs/adapter-vercel to v6 2025-10-15 17:39:29 +00:00
renovate[bot]
8f6abc6192 chore(deps): update all non-major dependencies 2025-10-15 17:39:12 +00:00
Adam Tegen
d0bed3e8f0 Add GitHub Coding Agent setup instructions
Added instructions for configuring GitHub Coding Agent.
2025-10-14 18:30:27 -05:00
Paolo Ricciuti
4c98732f5f Merge pull request #69 from sveltejs/renovate/actions-setup-node-6.x 2025-10-14 10:05:27 +02:00
renovate[bot]
1f88817cf0 chore(deps): update actions/setup-node action to v6 2025-10-14 05:50:23 +00:00
paoloricciuti
87af64f4bc fix: prevent imported_runes suggestion from being added for libs that are not svelte 2025-10-13 23:54:01 +02:00
Paolo Ricciuti
c1678b2f36 Merge pull request #64 from sveltejs/changeset-release/main
Version Packages
2025-10-11 00:23:42 +02:00
github-actions[bot]
a8af3c72ca Version Packages 2025-10-10 22:22:02 +00:00
Paolo Ricciuti
534a72cae7 Merge pull request #66 from sveltejs/read-state-with-dollar
feat: `read_state_with_dollar` autofixer
2025-10-11 00:21:36 +02:00
paoloricciuti
3b301d7d9c fix: widen desired_svelte_version validation to accommodate some clients 2025-10-10 23:27:49 +02:00
paoloricciuti
33a0e17ee1 fix: remove .only duh 2025-10-10 16:06:05 +02:00
paoloricciuti
bf5f31c867 feat: read_state_with_dollar autofixer 2025-10-10 16:00:19 +02:00
paoloricciuti
62abaacbd3 chore: fix autofixer snippet 2025-10-10 15:59:52 +02:00
paoloricciuti
87fab2d884 chore: exclude packages from changesets config 2025-10-10 15:59:39 +02:00
paoloricciuti
9cf50c9b62 chore: config no-unused-vars 2025-10-10 15:59:18 +02:00
Paolo Ricciuti
9a43aaf100 Merge pull request #62 from sveltejs/renovate/all-minor-patch 2025-10-10 13:03:57 +02:00
Paolo Ricciuti
c0477385b8 Merge pull request #65 from sveltejs/docs/update-prompt-docs 2025-10-10 13:02:45 +02:00
paoloricciuti
db283da593 docs: update prompts documentation 2025-10-10 10:39:22 +00:00
Paolo Ricciuti
7d7547f6e2 Merge pull request #63 from sveltejs/automatic-prompt-sync-with-docs 2025-10-10 12:31:16 +02:00
paoloricciuti
0360d00955 chore: remove changes to test action 2025-10-10 12:30:14 +02:00
paoloricciuti
65d403af1e feat: automatic prompt sync with docs 2025-10-10 10:38:41 +02:00
renovate[bot]
d794d6bec3 chore(deps): update pnpm to v10.18.2 2025-10-10 01:58:24 +00:00
Paolo Ricciuti
ec9c5b3415 Merge pull request #61 from sveltejs/changeset-release/main 2025-10-09 22:43:36 +02:00
github-actions[bot]
b508a4ea49 Version Packages 2025-10-09 20:42:32 +00:00
paoloricciuti
044f0988b9 fix: pass secrets in action and update mcpName 2025-10-09 22:41:56 +02:00
Paolo Ricciuti
c5c6ba6580 Merge pull request #60 from sveltejs/changeset-release/main 2025-10-09 22:31:24 +02:00
github-actions[bot]
fb2240d60c Version Packages 2025-10-09 20:30:38 +00:00
Paolo Ricciuti
469b2071e7 Merge pull request #59 from sveltejs/use-dns-mcp-publish 2025-10-09 22:30:06 +02:00
paoloricciuti
fc39b44859 fix: use DNS to publish MCP 2025-10-09 21:53:39 +02:00
Paolo Ricciuti
e12a0c90ab Merge pull request #58 from sveltejs/changeset-release/main 2025-10-09 18:44:05 +02:00
github-actions[bot]
7234e64967 Version Packages 2025-10-09 16:43:04 +00:00
paoloricciuti
ef5241cbc2 fix: publish to MCP registry (I really hope this time for real) 2025-10-09 18:42:26 +02:00
Paolo Ricciuti
9a74df198d Merge pull request #57 from sveltejs/changeset-release/main 2025-10-09 18:34:54 +02:00
github-actions[bot]
f1280b9876 Version Packages 2025-10-09 16:33:34 +00:00
paoloricciuti
132943db3b feat: publish mcp to registry (maybe for real this time) 2025-10-09 18:30:21 +02:00
paoloricciuti
5ed1454e2c fix: use right curl param 2025-10-09 18:27:21 +02:00
Paolo Ricciuti
bd072bd324 Merge pull request #54 from sveltejs/changeset-release/main 2025-10-09 18:24:56 +02:00
github-actions[bot]
c35be898da Version Packages 2025-10-09 16:23:57 +00:00
paoloricciuti
b960301ced fix: add id-token: write permission to release.yml 2025-10-09 18:23:26 +02:00
Paolo Ricciuti
43408f5504 Merge pull request #45 from sveltejs/publish-to-registry 2025-10-09 18:18:26 +02:00
Paolo Ricciuti
bf4dcda7e1 Merge pull request #55 from mquandalle/fix-claude-code-command-docs
docs: fix CLI command syntax for Claude Code and Gemini CLI
2025-10-09 08:25:22 +02:00
Paolo Ricciuti
35691c464b docs: fix gemini 2025-10-09 08:10:38 +02:00
Paolo Ricciuti
a5decb4cf3 Merge pull request #56 from sveltejs/link-to-docs 2025-10-08 17:36:30 +02:00
paoloricciuti
cb9764c234 docs: add link to CLAUDE.md and AGENTS.md docs 2025-10-08 17:22:15 +02:00
paoloricciuti
3fbc786383 fix: verify checksum 2025-10-08 17:20:16 +02:00
Maxime Quandalle
3b680232a0 Fix CLI command syntax for Claude Code and Gemini CLI
Add missing `--` separator between CLI options and npx command.
Without this separator, the `-y` option is incorrectly parsed as a
CLI option instead of being passed to npx.

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

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

View File

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

View File

@@ -0,0 +1,5 @@
---
'@sveltejs/opencode': patch
---
fix: remove restrictive permissions from subagent

View File

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

View File

@@ -0,0 +1,22 @@
{
"name": "svelte",
"owner": {
"name": "Svelte"
},
"plugins": [
{
"name": "svelte",
"source": "./plugins/claude/svelte",
"description": "A plugin for all things Svelte development, MCP, skills, and more.",
"lspServers": {
"svelte": {
"command": "svelteserver",
"args": ["--stdio"],
"extensionToLanguage": {
".svelte": "svelte"
}
}
}
}
]
}

View File

@@ -0,0 +1,13 @@
{
"name": "svelte",
"owner": {
"name": "Svelte"
},
"plugins": [
{
"name": "svelte",
"source": "./plugins/cursor/svelte",
"description": "A plugin for all things Svelte development, MCP, skills, and more."
}
]
}

1
.github/FUNDING.yml vendored Normal file
View File

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

View File

@@ -13,17 +13,17 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Setup pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v5
with:
version: 10.17.1
version: 10.28.2
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: '22'
node-version: '24'
cache: 'pnpm'
- name: Install dependencies
@@ -32,6 +32,4 @@ jobs:
- name: Run type check
run: pnpm run check
env:
DATABASE_URL: file:test.db
DATABASE_TOKEN: dummy-key
VOYAGE_API_KEY: dummy-key

View File

@@ -13,17 +13,17 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Setup pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v5
with:
version: 10.17.1
version: 10.28.2
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: '22'
node-version: '24'
cache: 'pnpm'
- name: Install dependencies
@@ -32,6 +32,4 @@ jobs:
- name: Run linting
run: pnpm run lint
env:
DATABASE_URL: file:test.db
VOYAGE_API_KEY: dummy-key
DATABASE_TOKEN: dummy-key

View File

@@ -0,0 +1,26 @@
name: Publish Any Commit
on: [push, pull_request]
permissions: {}
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
- run: corepack enable
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: 24
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
# Opencode doesn't have a build step
- name: Build Stdio
run: pnpm --filter @sveltejs/mcp run build
- run: pnpm dlx pkg-pr-new publish --compact './packages/mcp-stdio' './packages/opencode' --pnpm

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

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

View File

@@ -0,0 +1,66 @@
name: Release Svelte Skills
on:
push:
branches:
- main
paths:
- 'tools/skills/**'
workflow_dispatch:
permissions: {}
jobs:
detect-skills:
permissions:
contents: read
if: github.repository == 'sveltejs/ai-tools'
name: Detect changed skills
runs-on: ubuntu-latest
outputs:
skills: ${{ steps.find-skills.outputs.skills }}
steps:
- name: checkout
uses: actions/checkout@v6
- name: Find all skills
id: find-skills
run: |
skills=$(ls -d tools/skills/*/ | xargs -I {} basename {} | jq -R -s -c 'split("\n") | map(select(length > 0))')
echo "skills=$skills" >> $GITHUB_OUTPUT
release:
needs: detect-skills
if: needs.detect-skills.outputs.skills != '[]'
permissions:
contents: write
name: Release ${{ matrix.skill }}
runs-on: ubuntu-latest
strategy:
matrix:
skill: ${{ fromJson(needs.detect-skills.outputs.skills) }}
steps:
- name: checkout
uses: actions/checkout@v6
- name: Get version from date
id: version
run: echo "version=$(date +'%Y.%m.%d-%H%M%S')" >> $GITHUB_OUTPUT
- name: Create zip
run: |
cd tools/skills
zip -r ${{ matrix.skill }}.zip ${{ matrix.skill }}/
- name: Create Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ matrix.skill }}-v${{ steps.version.outputs.version }}
name: ${{ matrix.skill }} v${{ steps.version.outputs.version }}
body: |
Automated release of the ${{ matrix.skill }} skill.
This release was triggered by changes to the `tools/skills/${{ matrix.skill }}/` directory.
files: tools/skills/${{ matrix.skill }}.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -13,9 +13,11 @@ jobs:
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'
if: github.repository == 'sveltejs/ai-tools'
name: Release
runs-on: ${{ matrix.os }}
outputs:
publishedPackages: ${{ steps.changesets.outputs.publishedPackages }}
strategy:
matrix:
# pseudo-matrix for convenience, NEVER use more than a single combination
@@ -23,11 +25,11 @@ jobs:
os: [ubuntu-latest]
steps:
- name: checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
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
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: ${{ matrix.node }}
package-manager-cache: false # pnpm is not installed yet
@@ -37,10 +39,11 @@ jobs:
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
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: ${{ matrix.node }}
package-manager-cache: true # caches pnpm via packageManager field in package.json
cache: 'pnpm'
- name: install
run: pnpm install --frozen-lockfile --prefer-offline --ignore-scripts
- name: build
@@ -49,10 +52,19 @@ jobs:
- 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
uses: changesets/action@c48e67d110a68bc90ccf1098e9646092baacaa87 # v1.6.0
with:
# This expects you to have a script called changeset:version version that calls changeset version and updated what it needs to be updated
version: pnpm changeset:version
# This expects you to have a script called release which does a build for your packages and calls changeset publish
publish: pnpm release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_CONFIG_PROVENANCE: true
publish-mcp:
needs: release
if: contains(needs.release.outputs.publishedPackages, '"@sveltejs/mcp"')
uses: ./.github/workflows/publish-mcp.yml
secrets:
MCP_KEY: ${{ secrets.MCP_KEY }}

116
.github/workflows/sync-docs-skills.yml vendored Normal file
View File

@@ -0,0 +1,116 @@
name: Sync Skills
on:
workflow_dispatch:
permissions:
contents: write
pull-requests: write
actions: write
jobs:
sync-skills:
if: github.repository == 'sveltejs/ai-tools'
name: Sync skills from svelte.dev
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: 24
package-manager-cache: false # pnpm is not installed yet
- name: Install pnpm
shell: bash
run: |
PNPM_VER=$(jq -r '.packageManager | if .[0:5] == "pnpm@" then .[5:] else "packageManager in package.json does not start with pnpm@\n" | halt_error(1) end' package.json)
echo installing pnpm version "$PNPM_VER"
npm i -g "pnpm@$PNPM_VER"
- name: Setup Node.js with pnpm cache
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: 24
package-manager-cache: true # caches pnpm via packageManager field in package.json
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile --prefer-offline --ignore-scripts
- name: Clone svelte.dev
run: git clone --depth 2 https://github.com/sveltejs/svelte.dev.git "${{ runner.temp }}/svelte.dev"
- name: Discover changed skill files
id: discover
env:
SVELTE_DEV_ROOT: ${{ runner.temp }}/svelte.dev
run: |
skill_files=$(git -C "$SVELTE_DEV_ROOT" diff --name-only --diff-filter=ACMR HEAD~1 HEAD | grep '^apps/svelte.dev/content/docs/.*\.md$' | xargs -I{} grep -l '^skill: *true' "$SVELTE_DEV_ROOT/{}" || true)
echo "skill_files=$skill_files" >> "$GITHUB_OUTPUT"
- name: Sync skills
if: steps.discover.outputs.skill_files != ''
env:
SVELTE_DEV_ROOT: ${{ runner.temp }}/svelte.dev
DOCS_PREFIX: apps/svelte.dev/content/docs/
run: |
for full_path in ${{ steps.discover.outputs.skill_files }}; do
file="${full_path#$SVELTE_DEV_ROOT/}"
name=$(grep '^name: ' "$full_path" | head -1 | sed 's/^name: *//')
repo="${file#$DOCS_PREFIX}"
repo="${repo#/}"
repo="${repo%%/*}"
output_dir="tools/skills/$name"
rm -rf "$output_dir"
mkdir -p "$output_dir"
pnpm resolve-references --file "$full_path" --repo "$repo" --output "$output_dir"
done
- name: Sync plugins
if: steps.discover.outputs.skill_files != ''
run: |
pnpm sync-claude-plugin
pnpm sync-cursor-plugin
pnpm sync-opencode-plugin
pnpm generate-skill-docs
pnpm bump-plugin-versions
- name: Check for changes
id: git-check
run: |
git diff --exit-code -- tools/skills/ plugins/ packages/opencode/ documentation/docs/ || echo "changed=true" >> "$GITHUB_OUTPUT"
- name: Create Pull Request
if: steps.git-check.outputs.changed == 'true'
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'chore: sync skills from svelte.dev'
branch: chore/sync-skills
delete-branch: true
title: 'chore: sync skills from svelte.dev'
body: |
## Summary
Automatically synced skill markdown from `sveltejs/svelte.dev` into `tools/skills/`.
## Changes
- Cloned `sveltejs/svelte.dev`
- Filtered markdown files with `skill: true` frontmatter
- Rebuilt synced skill folders with `scripts/resolve-references.ts`
- Synced `plugins/claude/svelte/` (skills, agents)
- Synced `plugins/cursor/svelte/` (skills, agents, rules)
- Synced `packages/opencode/` (skills, instructions)
- Updated documentation
## Generated by
GitHub Action: Sync Skills
labels: |
chore
automated

95
.github/workflows/sync-plugins.yml vendored Normal file
View File

@@ -0,0 +1,95 @@
name: Sync Plugins
on:
push:
branches:
- main
paths:
- 'tools/**'
permissions:
contents: write
pull-requests: write
jobs:
sync-plugins:
# prevents this action from running on forks
if: github.repository == 'sveltejs/ai-tools'
name: Sync Plugins from tools/
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: 24
package-manager-cache: false # pnpm is not installed yet
- name: Install pnpm
shell: bash
run: |
PNPM_VER=$(jq -r '.packageManager | if .[0:5] == "pnpm@" then .[5:] else "packageManager in package.json does not start with pnpm@\n" | halt_error(1) end' package.json)
echo installing pnpm version $PNPM_VER
npm i -g pnpm@$PNPM_VER
- name: Setup Node.js with pnpm cache
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: 24
package-manager-cache: true # caches pnpm via packageManager field in package.json
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile --prefer-offline --ignore-scripts
- name: Sync plugins
run: pnpm sync-plugins
- name: Generate skills documentation
run: pnpm generate-skill-docs
- name: Generate subagent documentation
run: pnpm generate-subagent-docs
- name: Check for changes
id: git-check
run: |
git diff --exit-code \
plugins/claude/svelte/ \
plugins/cursor/svelte/ \
packages/opencode/skills/ \
packages/opencode/instructions/ \
packages/opencode/schema.json \
documentation/docs/ \
|| echo "changed=true" >> $GITHUB_OUTPUT
- name: Create Pull Request
if: steps.git-check.outputs.changed == 'true'
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'chore: sync plugins from tools/'
branch: chore/sync-plugins
delete-branch: true
title: 'chore: sync plugins from tools/'
body: |
## Summary
Automatically synced all plugins from the `tools/` source of truth.
This PR was triggered by changes to `tools/**`.
## Changes
- Synced `plugins/claude/svelte/` (skills, agents with `permissionMode`)
- Synced `plugins/cursor/svelte/` (skills, agents, rules)
- Synced `packages/opencode/` (skills, instructions, schema)
- Updated documentation
## Generated by
GitHub Action: Sync Plugins
labels: |
chore
automated

View File

@@ -13,17 +13,17 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Setup pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v5
with:
version: 10.17.1
version: 10.28.2
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: '22'
node-version: '24'
cache: 'pnpm'
- name: Install dependencies
@@ -32,13 +32,9 @@ jobs:
- name: Build project
run: pnpm run build
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

View File

@@ -0,0 +1,78 @@
name: Update OpenCode JSON Schema
on:
push:
branches:
- main
paths:
- 'packages/opencode/config.ts'
permissions:
contents: write
pull-requests: write
jobs:
update-docs:
# prevents this action from running on forks
if: github.repository == 'sveltejs/ai-tools'
name: Update OpenCode JSON Schema
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: 24
package-manager-cache: false # pnpm is not installed yet
- name: Install pnpm
shell: bash
run: |
PNPM_VER=$(jq -r '.packageManager | if .[0:5] == "pnpm@" then .[5:] else "packageManager in package.json does not start with pnpm@\n" | halt_error(1) end' package.json)
echo installing pnpm version $PNPM_VER
npm i -g pnpm@$PNPM_VER
- name: Setup Node.js with pnpm cache
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: 24
package-manager-cache: true # caches pnpm via packageManager field in package.json
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile --prefer-offline --ignore-scripts
- name: Generate opencode JSON schema
run: pnpm generate-opencode-jsonschema
- name: Check for changes
id: git-check
run: |
git diff --exit-code packages/opencode/schema.json || echo "changed=true" >> $GITHUB_OUTPUT
- name: Create Pull Request
if: steps.git-check.outputs.changed == 'true'
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'docs: update opencode JSON schema'
branch: docs/update-opencode-jsonschema
delete-branch: true
title: 'docs: update opencode JSON schema'
body: |
## Summary
Automatically generated update for OpenCode JSON schema.
This PR was triggered by changes to the OpenCode configuration file `packages/opencode/config.ts`.
## Changes
- Updated `packages/opencode/schema.json` with latest JSON schema
## Generated by
GitHub Action: Update OpenCode JSON Schema
labels: |
automated

View File

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

View File

@@ -11,4 +11,7 @@ bun.lockb
/**/.svelte-kit/*
# Claude Code
.claude/
.claude/
.changeset/
/packages/opencode/schema.json

View File

@@ -10,6 +10,12 @@
"options": {
"parser": "svelte"
}
},
{
"files": "**/references/*.md",
"options": {
"embeddedLanguageFormatting": "off"
}
}
]
}

View File

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

View File

@@ -83,7 +83,6 @@ Located in `src/lib/server/analyze/`:
Required environment variables:
- `DATABASE_URL`: SQLite database path (default: `file:test.db`)
- `VOYAGE_API_KEY`: API key for embeddings support (optional)
When connected to the svelte-llm MCP server, you have access to comprehensive Svelte 5 and SvelteKit documentation. Here's how to use the available tools effectively:

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 [Svelte Contributors](https://github.com/sveltejs/ai-tools/graphs/contributors)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,3 +1 @@
DATABASE_URL=file:test.db
DATABASE_TOKEN=needs_to_be_set_but_it_can_be_anything
VOYAGE_API_KEY=your_actual_api_key_here

View File

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

View File

@@ -23,10 +23,6 @@
"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": [
@@ -37,34 +33,31 @@
],
"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"
"@eslint/compat": "catalog:lint",
"@eslint/js": "catalog:lint",
"@modelcontextprotocol/inspector": "catalog:ai",
"@sveltejs/adapter-vercel": "catalog:svelte",
"@sveltejs/kit": "catalog:svelte",
"@sveltejs/vite-plugin-svelte": "catalog:svelte",
"@types/node": "catalog:tooling",
"@typescript-eslint/parser": "catalog:lint",
"eslint-config-prettier": "catalog:lint",
"eslint-plugin-svelte": "catalog:lint",
"globals": "catalog:lint",
"prettier": "catalog:lint",
"prettier-plugin-svelte": "catalog:lint",
"svelte": "catalog:svelte",
"svelte-check": "catalog:svelte",
"svelte-eslint-parser": "catalog:lint",
"typescript": "catalog:tooling",
"vite": "catalog:tooling",
"vite-plugin-devtools-json": "catalog:tooling",
"vitest": "catalog:tooling"
},
"dependencies": {
"@sveltejs/mcp-schema": "workspace:^",
"@sveltejs/mcp-server": "workspace:^",
"@tmcp/transport-http": "^0.6.3",
"tmcp": "^1.14.0"
"@tmcp/transport-http": "catalog:tmcp",
"@vercel/analytics": "catalog:tooling",
"tmcp": "catalog:tmcp"
}
}

View File

@@ -1,6 +1,7 @@
import { dev } from '$app/environment';
import { http_transport } from '$lib/mcp/index.js';
import { db } from '$lib/server/db/index.js';
import { redirect } from '@sveltejs/kit';
import { track } from '@vercel/analytics/server';
export async function handle({ event, resolve }) {
if (event.request.method === 'GET') {
@@ -15,21 +16,12 @@ export async function handle({ event, resolve }) {
}
}
const mcp_response = await http_transport.respond(event.request, {
db,
// only add analytics in production
track: dev
? undefined
: async (session_id, event, extra) => {
await track(event, { session_id, ...(extra ? { extra } : {}) });
},
});
// 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

@@ -3,4 +3,10 @@ import { HttpTransport } from '@tmcp/transport-http';
export const http_transport = new HttpTransport(server, {
cors: true,
path: '/mcp',
// 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.
disableSse: true,
});

View File

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

@@ -1,2 +0,0 @@
// 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';

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

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

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -2,43 +2,11 @@
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.
The following pages will help you set up and use the AI tools officially maintained by the Svelte team.
## Setup
There are four tools, designed to help your agent write correct, robust Svelte code. They are designed to work together, but each can be used individually:
The setup varies based on the version of the MCP you prefer — remote or local — and your chosen MCP client (e.g. Claude Code, Codex CLI or GitHub Copilot):
- [local setup](local-setup) using `@sveltejs/mcp`
- [remote setup](remote-setup) using `https://mcp.svelte.dev/mcp`
## Usage
To get the most out of the MCP server we recommend including the following prompt in your `AGENTS.md` (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.
- [Instructions](instructions): small prompt always injected into your session to make your agent more aware of the available tools
- [MCP Server](mcp): with tools, prompts and resources to give your agent more context, by pulling directly from the official Svelte documentation and using static analysis to correct common generative AI pitfalls
- [Skills](skills): lazy-loaded descriptions that teach your agent Svelte best practices, and how to use the [`@sveltejs/mcp` cli](cli)
- [Subagents](subagent): focused agents that can be invoked in parallel to execute atomic operations in a separate context window

View File

@@ -0,0 +1,23 @@
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 Svelte 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.

View File

@@ -0,0 +1,13 @@
---
title: AGENTS.md
---
To get the most out of the [MCP server](mcp) and [skills](skills) we recommend including the following prompt in your [`AGENTS.md`](https://agents.md) (or [`CLAUDE.md`](https://docs.claude.com/en/docs/claude-code/memory#claude-md-imports) or [`GEMINI.md`](https://geminicli.com/docs/cli/gemini-md/), if using Claude Code or Gemini). This will tell your agent which tools are available and when it is appropriate to use them.
> [!NOTE] This is already setup for you when using `npx sv add mcp`
<!-- prettier-ignore-start -->
````markdown
@include .generated/agents.md
````
<!-- prettier-ignore-end -->

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,16 @@
---
title: Overview
---
The Svelte MCP ([Model Context Protocol](https://modelcontextprotocol.io/docs/getting-started/intro)) server can help your agent write better Svelte code. It works by providing relevant documentation, and statically analysing generated code so that it can suggest fixes and best practices.
## Setup
The setup varies based on the version of the MCP you prefer — remote or local — and your chosen MCP client (e.g. Claude Code, Codex CLI or GitHub Copilot):
- [local setup](local-setup) using `@sveltejs/mcp`
- [remote setup](remote-setup) using `https://mcp.svelte.dev/mcp`
## Usage
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

@@ -15,7 +15,7 @@ Here's how to set it up in some common MCP clients:
To include the local MCP version in Claude Code, simply run the following command:
```bash
claude mcp add -t stdio -s [scope] svelte npx -y @sveltejs/mcp
claude mcp add -t stdio -s [scope] svelte -- npx -y @sveltejs/mcp
```
The `[scope]` must be `user`, `project` or `local`.
@@ -45,6 +45,27 @@ command = "npx"
args = ["-y", "@sveltejs/mcp"]
```
## Copilot CLI
Use the Copilot CLI to interactively add the MCP server:
```bash
/mcp add
```
Alternatively, create or edit `~/.copilot/mcp-config.json` and add the following configuration:
```json
{
"mcpServers": {
"svelte": {
"command": "npx",
"args": ["-y", "@sveltejs/mcp"]
}
}
}
```
## Gemini CLI
To include the local MCP version in Gemini CLI, simply run the following command:
@@ -57,7 +78,7 @@ The `[scope]` must be `user`, `project` or `local`.
## OpenCode
Run the command:
You can automatically configure the MCP server using the [OpenCode plugin](opencode-plugin) (recommended). If you prefer to configure the MCP server manually, run:
```bash
opencode mcp add
@@ -91,6 +112,8 @@ opencode mcp add
## Cursor
You can automatically configure the MCP server using the [Cursor plugin](cursor-plugin) (recommended). If you prefer to configure the MCP server manually you can:
- Open the command palette
- Select "View: Open MCP Settings"
- Click on "Add custom MCP"
@@ -108,6 +131,33 @@ It will open a file with your MCP servers where you can add the following config
}
```
## Zed
Install the [Svelte MCP Server extension](https://zed.dev/extensions/svelte-mcp).
<details>
<summary>Configure Manually</summary>
- Open the command palette
- Search and select "agent:open settings"
- In settings panel look for `Model Context Protocol (MCP) Servers`
- Click on "Add Server"
- Select: "Add Custom Server"
It will open a popup with MCP server config where you can add the following configuration:
```json
{
"svelte": {
"command": "npx",
"args": ["-y", "@sveltejs/mcp"]
}
}
```
</details>
## 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

@@ -16,6 +16,8 @@ claude mcp add -t http -s [scope] svelte https://mcp.svelte.dev/mcp
You can choose your preferred `scope` (it must be `user`, `project` or `local`) and `name`.
If you prefer you can also install the `svelte` plugin in [the Svelte Claude Code Marketplace](claude-plugin) that will give you both the remote server and useful [skills](skills).
## Claude Desktop
- Open Settings > Connectors
@@ -34,6 +36,26 @@ experimental_use_rmcp_client = true
url = "https://mcp.svelte.dev/mcp"
```
## Copilot CLI
Use the Copilot CLI to interactively add the MCP server:
```bash
/mcp add
```
Alternatively, create or edit `~/.copilot/mcp-config.json` and add the following configuration:
```json
{
"mcpServers": {
"svelte": {
"url": "https://mcp.svelte.dev/mcp"
}
}
}
```
## Gemini CLI
To use the remote MCP server with Gemini CLI, simply run the following command:
@@ -42,11 +64,11 @@ To use the remote MCP server with Gemini CLI, simply run the following command:
gemini mcp add -t http -s [scope] svelte https://mcp.svelte.dev/mcp
```
The `[scope]` must be `user`, `project` or `local`.
The `[scope]` must be `user` or `project`.
## OpenCode
Run the command:
You can automatically configure the MCP server using the [OpenCode plugin](opencode-plugin) (recommended). If you prefer to configure the MCP server manually, run:
```bash
opencode mcp add
@@ -80,6 +102,8 @@ opencode mcp add
## Cursor
You can automatically configure the MCP server using the [Cursor plugin](cursor-plugin) (recommended). If you prefer to configure the MCP server manually you can:
- Open the command palette
- Select "View: Open MCP Settings"
- Click on "Add custom MCP"
@@ -96,6 +120,27 @@ It will open a file with your MCP servers where you can add the following config
}
```
## GitHub Coding Agent
- Open your repository in GitHub
- Go to Settings
- Open Copilot > Coding agent
- Edit the MCP configuration
```json
{
"mcpServers": {
"svelte": {
"type": "http",
"url": "https://mcp.svelte.dev/mcp",
"tools": ["*"]
}
}
}
```
- Click _Save MCP configuration_
## Other clients
If we didn't include the MCP client you are using, refer to their documentation for `remote` servers and use `https://mcp.svelte.dev/mcp` as the URL.

View File

@@ -4,6 +4,4 @@ title: Prompts
This is the list of available prompts provided by the MCP server. Prompts are selected by the user and are sent as a user message. They can be useful to write repetitive instructions for the LLM on how to properly use the MCP server.
## svelte-task
This prompt should be used whenever you are asking the model to work on a Svelte-related task. It will instruct the LLM which documentation sections are available, which tools to invoke, when to invoke them, and how to interpret the results.
@include .generated/prompts.md

View File

@@ -0,0 +1,78 @@
---
title: CLI
---
The `@sveltejs/mcp` npm package normally launches the local `stdio` MCP server:
```bash
npx -y @sveltejs/mcp
```
If you invoke it with a subcommand, it behaves like a regular CLI and prints the result directly in your terminal instead. This is useful for agents, scripts and quick manual checks.
## Usage
```bash
npx -y @sveltejs/mcp <command> [options]
```
Available commands:
- `list-sections`
- `get-documentation <sections>`
- `svelte-autofixer <code_or_path>`
You can learn more about the commands with
```bash
npx -y @sveltejs/mcp --help
npx -y @sveltejs/mcp <command> --help
npx -y @sveltejs/mcp --version
```
## `list-sections`
Lists all available Svelte and SvelteKit documentation sections.
```bash
npx -y @sveltejs/mcp list-sections
```
The output is a structured text list of sections, including each section's title, `use_cases`, and documentation path. This is the same catalog the MCP tool uses before calling `get-documentation`.
## `get-documentation`
Fetches the full documentation for one or more sections.
```bash
npx -y @sveltejs/mcp get-documentation 'svelte/$state'
# or
npx -y @sveltejs/mcp get-documentation 'svelte/$state,svelte/await-expressions'
```
Each section can be matched by title or by documentation path. If a section cannot be found, the CLI returns an error plus similar matches when available.
## `svelte-autofixer`
Runs the Svelte autofixer against either inline code or a file path:
```bash
npx -y @sveltejs/mcp svelte-autofixer 'src/routes/+page.svelte'
```
If the argument is an existing path, the CLI reads the file automatically. Otherwise it treats the argument as raw Svelte code.
Because most shells expand `$`, inline code should be quoted or escaped correctly. In practice, passing a file path is usually easier than passing source directly.
Available options:
- `--svelte-version <4|5>` - choose which Svelte version to validate against (defaults to `5`)
- `--async` - enable async Svelte analysis for Svelte 5 projects
The command prints an object with:
- `issues`
- `suggestions`
- `require_another_tool_call_after_fixing`
This makes it easy to use in an agentic loop: run the autofixer, apply fixes, then run it again until it reports no remaining issues or suggestions.

View File

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

View File

@@ -0,0 +1,263 @@
## `svelte-code-writer`
CLI tools for Svelte 5 documentation lookup and code analysis. MUST be used whenever creating, editing or analyzing any Svelte component (.svelte) or Svelte module (.svelte.ts/.svelte.js). If possible, this skill should be executed within the svelte-file-editor agent for optimal results.
<a href="https://github.com/sveltejs/ai-tools/releases?q=svelte-code-writer" target="_blank" rel="noopener noreferrer">Open Releases page</a>
<details>
<summary>View skill content</summary>
<!-- prettier-ignore-start -->
````markdown
# Svelte 5 Code Writer
## CLI Tools
You have access to `@sveltejs/mcp` CLI for Svelte-specific assistance. Use these commands via `npx`:
### List Documentation Sections
```bash
npx @sveltejs/mcp list-sections
```
Lists all available Svelte 5 and SvelteKit documentation sections with titles and paths.
### Get Documentation
```bash
npx @sveltejs/mcp get-documentation "<section1>,<section2>,..."
```
Retrieves full documentation for specified sections. Use after `list-sections` to fetch relevant docs.
**Example:**
```bash
npx @sveltejs/mcp get-documentation "$state,$derived,$effect"
```
### Svelte Autofixer
```bash
npx @sveltejs/mcp svelte-autofixer "<code_or_path>" [options]
```
Analyzes Svelte code and suggests fixes for common issues.
**Options:**
- `--async` - Enable async Svelte mode (default: false)
- `--svelte-version` - Target version: 4 or 5 (default: 5)
**Examples:**
```bash
# Analyze inline code (escape $ as \$)
npx @sveltejs/mcp svelte-autofixer '<script>let count = \$state(0);</script>'
# Analyze a file
npx @sveltejs/mcp svelte-autofixer ./src/lib/Component.svelte
# Target Svelte 4
npx @sveltejs/mcp svelte-autofixer ./Component.svelte --svelte-version 4
```
**Important:** When passing code with runes (`$state`, `$derived`, etc.) via the terminal, escape the `$` character as `\$` to prevent shell variable substitution.
## Workflow
1. **Uncertain about syntax?** Run `list-sections` then `get-documentation` for relevant topics
2. **Reviewing/debugging?** Run `svelte-autofixer` on the code to detect issues
3. **Always validate** - Run `svelte-autofixer` before finalizing any Svelte component
````
<!-- prettier-ignore-end -->
</details>
## `svelte-core-bestpractices`
Guidance on writing fast, robust, modern Svelte code. Load this skill whenever in a Svelte project and asked to write/edit or analyze a Svelte component or module. Covers reactivity, event handling, styling, integration with libraries and more.
<a href="https://github.com/sveltejs/ai-tools/releases?q=svelte-core-bestpractices" target="_blank" rel="noopener noreferrer">Open Releases page</a>
<details>
<summary>View skill content</summary>
<!-- prettier-ignore-start -->
````markdown
## `$state`
Only use the `$state` rune for variables that should be _reactive_ — in other words, variables that cause an `$effect`, `$derived` or template expression to update. Everything else can be a normal variable.
Objects and arrays (`$state({...})` or `$state([...])`) are made deeply reactive, meaning mutation will trigger updates. This has a trade-off: in exchange for fine-grained reactivity, the objects must be proxied, which has performance overhead. In cases where you're dealing with large objects that are only ever reassigned (rather than mutated), use `$state.raw` instead. This is often the case with API responses, for example.
## `$derived`
To compute something from state, use `$derived` rather than `$effect`:
```js
// do this
let square = $derived(num * num);
// don't do this
let square;
$effect(() => {
square = num * num;
});
```
> [!NOTE] `$derived` is given an expression, _not_ a function. If you need to use a function (because the expression is complex, for example) use `$derived.by`.
Deriveds are writable — you can assign to them, just like `$state`, except that they will re-evaluate when their expression changes.
If the derived expression is an object or array, it will be returned as-is — it is _not_ made deeply reactive. You can, however, use `$state` inside `$derived.by` in the rare cases that you need this.
## `$effect`
Effects are an escape hatch and should mostly be avoided. In particular, avoid updating state inside effects.
- If you need to sync state to an external library such as D3, it is often neater to use [`{@attach ...}`](references/@attach.md)
- If you need to run some code in response to user interaction, put the code directly in an event handler or use a [function binding](references/bind.md) as appropriate
- If you need to log values for debugging purposes, use [`$inspect`](references/$inspect.md)
- If you need to observe something external to Svelte, use [`createSubscriber`](references/svelte-reactivity.md)
Never wrap the contents of an effect in `if (browser) {...}` or similar — effects do not run on the server.
## `$props`
Treat props as though they will change. For example, values that depend on props should usually use `$derived`:
```js
// @errors: 2451
let { type } = $props();
// do this
let color = $derived(type === 'danger' ? 'red' : 'green');
// don't do this — `color` will not update if `type` changes
let color = type === 'danger' ? 'red' : 'green';
```
## `$inspect.trace`
`$inspect.trace` is a debugging tool for reactivity. If something is not updating properly or running more than it should you can add `$inspect.trace(label)` as the first line of an `$effect` or `$derived.by` (or any function they call) to trace their dependencies and discover which one triggered an update.
## Events
Any element attribute starting with `on` is treated as an event listener:
```svelte
<button onclick={() => {...}}>click me</button>
<!-- attribute shorthand also works -->
<button {onclick}>...</button>
<!-- so do spread attributes -->
<button {...props}>...</button>
```
If you need to attach listeners to `window` or `document` you can use `<svelte:window>` and `<svelte:document>`:
```svelte
<svelte:window onkeydown={...} />
<svelte:document onvisibilitychange={...} />
```
Avoid using `onMount` or `$effect` for this.
## Snippets
[Snippets](references/snippet.md) are a way to define reusable chunks of markup that can be instantiated with the [`{@render ...}`](references/@render.md) tag, or passed to components as props. They must be declared within the template.
```svelte
{#snippet greeting(name)}
<p>hello {name}!</p>
{/snippet}
{@render greeting('world')}
```
> [!NOTE] Snippets declared at the top level of a component (i.e. not inside elements or blocks) can be referenced inside `<script>`. A snippet that doesn't reference component state is also available in a `<script module>`, in which case it can be exported for use by other components.
## Each blocks
Prefer to use [keyed each blocks](references/each.md) — this improves performance by allowing Svelte to surgically insert or remove items rather than updating the DOM belonging to existing items.
> [!NOTE] The key _must_ uniquely identify the object. Do not use the index as a key.
Avoid destructuring if you need to mutate the item (with something like `bind:value={item.count}`, for example).
## Using JavaScript variables in CSS
If you have a JS variable that you want to use inside CSS you can set a CSS custom property with the `style:` directive.
```svelte
<div style:--columns={columns}>...</div>
```
You can then reference `var(--columns)` inside the component's `<style>`.
## Styling child components
The CSS in a component's `<style>` is scoped to that component. If a parent component needs to control the child's styles, the preferred way is to use CSS custom properties:
```svelte
<!-- Parent.svelte -->
<Child --color="red" />
<!-- Child.svelte -->
<h1>Hello</h1>
<style>
h1 {
color: var(--color);
}
</style>
```
If this is impossible (for example, the child component comes from a library) you can use `:global` to override styles:
```svelte
<div>
<Child />
</div>
<style>
div :global {
h1 {
color: red;
}
}
</style>
```
## Context
Consider using context instead of declaring state in a shared module. This will scope the state to the part of the app that needs it, and eliminate the possibility of it leaking between users when server-side rendering.
Use `createContext` rather than `setContext` and `getContext`, as it provides type safety.
## Async Svelte
If using version 5.36 or higher, you can use [await expressions](references/await-expressions.md) and [hydratable](references/hydratable.md) to use promises directly inside components. Note that these require the `experimental.async` option to be enabled in `svelte.config.js` as they are not yet considered fully stable.
## Avoid legacy features
Always use runes mode for new code, and avoid features that have more modern replacements:
- use `$state` instead of implicit reactivity (e.g. `let count = 0; count += 1`)
- use `$derived` and `$effect` instead of `$:` assignments and statements (but only use effects when there is no better solution)
- use `$props` instead of `export let`, `$$props` and `$$restProps`
- use `onclick={...}` instead of `on:click={...}`
- use `{#snippet ...}` and `{@render ...}` instead of `<slot>` and `$$slots` and `<svelte:fragment>`
- use `<DynamicComponent>` instead of `<svelte:component this={DynamicComponent}>`
- use `import Self from './ThisComponent.svelte'` and `<Self>` instead of `<svelte:self>`
- use classes with `$state` fields to share reactivity between components, instead of using stores
- use `{@attach ...}` instead of `use:action`
- use clsx-style arrays and objects in `class` attributes, instead of the `class:` directive
````
<!-- prettier-ignore-end -->
</details>

View File

@@ -0,0 +1,11 @@
---
title: Overview
---
This is the list of available skills provided by the Svelte MCP package. Skills are sets of instructions that AI agents can load on-demand to help with specific tasks.
Skills are available in both the Claude Code plugin (installed via the marketplace) and the OpenCode plugin (`@sveltejs/opencode`). They can also be manually installed in your `.claude/skills` or `.opencode/skills` folder.
You can download the latest skills from the [releases page](https://github.com/sveltejs/ai-tools/releases) of the repo, or find them in the [`tools/skills`](https://github.com/sveltejs/ai-tools/tree/main/tools/skills) folder.
@include .generated/skills.md

View File

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

View File

@@ -0,0 +1,67 @@
---
name: svelte-file-editor
description: Specialized Svelte 5 code editor. MUST BE USED PROACTIVELY when creating, editing, or reviewing any .svelte file or .svelte.ts/.svelte.js module and MUST use the tools from the MCP server or the `svelte-file-editor` skill if they are available. Fetches relevant documentation and validates code using the Svelte MCP server tools.
---
You are a Svelte 5 expert responsible for writing, editing, and validating Svelte components and modules. You have access to the Svelte MCP server which provides documentation and code analysis tools. Always use the tools from the svelte MCP server to fetch documentation with `get_documentation` and validating the code with `svelte_autofixer`. If the autofixer returns any issue or suggestions try to solve them.
If the MCP tools are not available you can use the `svelte-code-writer` skill to learn how to use the `@sveltejs/mcp` cli to access the same tools.
If the skill is not available you can run `npx @sveltejs/mcp@latest -y --help` to learn how to use it.
## Available MCP Tools
### 1. list-sections
Lists all available Svelte 5 and SvelteKit documentation sections with titles and paths. Use this first to discover what documentation is available.
### 2. get-documentation
Retrieves full documentation for specified sections. Accepts a single section name or an array of section names. Use after `list-sections` to fetch relevant docs for the task at hand.
**Example sections:** `$state`, `$derived`, `$effect`, `$props`, `$bindable`, `snippets`, `routing`, `load functions`
### 3. svelte-autofixer
Analyzes Svelte code and returns suggestions to fix issues. Pass the component code directly to this tool. It will detect common mistakes like:
- Using `$effect` instead of `$derived` for computations
- Missing cleanup in effects
- Svelte 4 syntax (`on:click`, `export let`, `<slot>`)
- Missing keys in `{#each}` blocks
- And more
## Workflow
When invoked to work on a Svelte file:
### 1. Gather Context (if needed)
If you're uncertain about Svelte 5 syntax or patterns, use the MCP tools:
1. Call `list-sections` to see available documentation
2. Call `get-documentation` with relevant section names
### 2. Read the Target File
Read the file to understand the current implementation.
### 3. Make Changes
Apply edits following Svelte 5 best practices:
### 4. Validate Changes
After editing, ALWAYS call `svelte-autofixer` with the updated code to check for issues.
### 5. Fix Any Issues
If the autofixer reports problems, fix them and re-validate until no issues remain.
## Output Format
After completing your work, provide:
1. Summary of changes made
2. Any issues found and fixed by the autofixer
3. Recommendations for further improvements (if any)

View File

@@ -0,0 +1,20 @@
---
title: Overview
---
Since creating, editing or analyzing a Svelte file is an atomic operation we recommend creating a subagent that your main agent can invoke whenever it needs to interact with a Svelte component. Subagents use a separate context window, allowing them to fetch documentation, iterate with [`svelte-autofixer`](tools#svelte-autofixer) and write to the filesystem without wasting context in the main agent.
Delegation should happen automatically when appropriate, but you can also explicitly request the subagent be used for Svelte-related tasks.
You can write your own or take inspiration from the one available in the [`sveltejs/ai-tools`](https://github.com/sveltejs/ai-tools/tree/main/tools/agents) repository: a specialized subagent called `svelte-file-editor` designed for creating, editing, and reviewing Svelte files.
<details>
<summary>View subagent definition</summary>
<!-- prettier-ignore-start -->
````markdown
@include .generated/subagent.md
````
<!-- prettier-ignore-end -->
</details>

View File

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

View File

@@ -0,0 +1,23 @@
---
title: Claude Code
---
The open source [repository](https://github.com/sveltejs/ai-tools) containing the code for the MCP server is also a Claude Code [plugin marketplace](https://code.claude.com/docs/en/discover-plugins).
The marketplace allows you to install the `svelte` plugin which will give you the remote MCP server, [skills](skills) to instruct the LLM on how to properly write Svelte 5 code, and a specialized agent for editing Svelte files.
If possible, we recommend that you instruct the LLM to execute MCP calls with the agent (you can explicitly mention an agent in your message to delegate work to it) when creating or editing `.svelte` files or `.svelte.ts`/`.svelte.js` modules — this will help save context by handling Svelte-specific tasks more efficiently.
## Installation
To add the repository as a marketplace, launch Claude Code and type the following:
```bash
/plugin marketplace add sveltejs/ai-tools
```
Then, install the Svelte plugin:
```bash
/plugin install svelte
```

View File

@@ -0,0 +1,54 @@
---
title: OpenCode
---
OpenCode has a [plugin system](https://opencode.ai/docs/plugins/) that allows developers to add MCP servers, agents and commands programmatically. Svelte has an OpenCode plugin published under `@sveltejs/opencode`.
## Installation
To install the plugin you can edit your [OpenCode config](https://opencode.ai/docs/config/) (either the global or the local one), adding `@sveltejs/opencode` to the list of plugins.
```json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["@sveltejs/opencode"]
}
```
That's it! You now have the Svelte [MCP server](mcp), [skills](skills), and the `svelte-file-editor` [subagent](subagent) configured for you.
## Configuration
By default, everything is enabled, but you can configure the plugin by adding a configuration file:
- locally, in `.opencode/svelte.json`
- globally, in `~/.config/opencode/svelte.json` (or, if you have specified the environment variable, in `$OPENCODE_CONFIG_DIR/svelte.json`)
```json
{
"$schema": "https://svelte.dev/opencode/schema.json",
"mcp": {
"type": "remote", // or "local" — defaults to remote
"enabled": true
},
"subagent": {
"enabled": true,
"agents": {
"svelte-file-editor": {
"model": "<other-model>", // defaults to the same as main agent
"temperature": 1, // defaults to unset
"top_p": 0.7, // defaults to unset
"maxSteps": 20 // defaults to unlimited
}
}
},
"skills": {
// this can be `true`, or an array of skills to enable
// e.g. ["svelte-core-bestpractices"]
"enabled": true
},
"instructions": {
"enabled": true
}
}
```

View File

@@ -0,0 +1,25 @@
---
title: Cursor
---
Cursor has a [plugin system](https://cursor.com/docs/plugins) that can bundle rules, skills, agents, commands, MCP servers, and hooks.
The Svelte plugin gives you the remote Svelte MCP server, Cursor [skills](skills), an always-on rule that tells the model how to use the Svelte MCP tools correctly, and the `svelte-file-editor` subagent for working on `.svelte` files and `.svelte.ts`/`.svelte.js` modules. The source is available in the [`sveltejs/ai-tools`](https://github.com/sveltejs/ai-tools/tree/main/plugins/cursor/svelte) repo.
## Installation
Install the plugin from the [Cursor Marketplace](https://cursor.com/marketplace/svelte) with the following command:
```
/add-plugin svelte
```
Plugins can be installed either for the current project or at user level.
Once installed, Cursor will discover the plugin components automatically:
- the Svelte MCP server is added from the plugin's `.mcp.json`
- rules and skills appear in Cursor's rules UI
- the `svelte-file-editor` agent becomes available in chat
> [!NOTE] The Cursor CLI does not support plugins yet. Plugin support in [Cloud Agents](https://cursor.com/docs/cloud-agent) is limited to MCP servers.

View File

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

View File

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

View File

@@ -7,13 +7,20 @@ import { fileURLToPath } from 'node:url';
import ts from 'typescript-eslint';
import svelteConfig from './apps/mcp-remote/svelte.config.js';
import eslint_plugin_import from 'eslint-plugin-import';
import { configs as pnpm } from 'eslint-plugin-pnpm';
const gitignore_path = fileURLToPath(new URL('./.gitignore', import.meta.url));
export default /** @type {import("eslint").Linter.Config} */ ([
includeIgnoreFile(gitignore_path),
{
ignores: ['.claude/**/*'],
ignores: [
'.claude/**/*',
'.changeset/*',
'.github/**/*.yml',
'.github/**/*.yaml',
'**/pnpm-lock.yaml',
],
},
js.configs.recommended,
...ts.configs.recommended,
@@ -37,17 +44,28 @@ export default /** @type {import("eslint").Linter.Config} */ ([
leadingUnderscore: 'allow',
},
],
'@typescript-eslint/no-unused-vars': [
'error',
{
varsIgnorePattern: '^_',
ignoreRestSiblings: true,
},
],
'func-style': ['error', 'declaration', { allowTypeAnnotation: true }],
'import/no-unresolved': 'off', // this doesn't work well with typescript path mapping
'import/extensions': [
'error',
'ignorePackages',
{
js: 'always',
mjs: 'always',
cjs: 'always',
ts: 'always',
svelte: 'always',
ignorePackages: true,
pattern: {
js: 'always',
mjs: 'always',
cjs: 'always',
ts: 'always',
svelte: 'always',
svg: 'always',
json: 'always',
},
},
],
},
@@ -63,4 +81,16 @@ export default /** @type {import("eslint").Linter.Config} */ ([
},
},
},
{
name: 'pnpm/exclude-some-rules',
files: ['**/*.json', '**/*.yaml', '**/*.yml', 'pnpm-workspace.yaml'],
rules: {
'@typescript-eslint/naming-convention': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-unused-expressions': 'off',
'func-style': 'off',
},
},
...pnpm.json,
...pnpm.yaml,
]);

View File

@@ -3,7 +3,7 @@
"version": "0.0.1",
"description": "The official Svelte MCP server implementation",
"type": "module",
"packageManager": "pnpm@10.17.1",
"packageManager": "pnpm@10.28.2",
"scripts": {
"build": "pnpm -r run build",
"dev": "pnpm --filter @sveltejs/mcp-remote run dev",
@@ -12,13 +12,28 @@
"format": "prettier --write .",
"lint": "prettier --check . && eslint .",
"lint:fix": "prettier --write . && eslint . --fix",
"lint:inspect": "pnpm dlx @eslint/config-inspector",
"node:inspect": "pnpm dlx node-modules-inspector",
"test:unit": "vitest",
"test": "npm run test:unit -- --run",
"test:watch": "npm run test:unit -- --watch",
"inspect": "pnpm mcp-inspector",
"generate-opencode-jsonschema": "pnpm --filter @sveltejs/opencode run generate-schema",
"generate-summaries": "pnpm --filter @sveltejs/mcp-server run generate-summaries",
"generate-prompt-docs": "node --import node-resolve-ts/register scripts/update-docs-prompts.ts",
"generate-skill-docs": "node --import node-resolve-ts/register scripts/update-docs-skills.ts",
"generate-subagent-docs": "cp ./tools/agents/svelte-file-editor.md ./documentation/docs/50-subagents/.generated/subagent.md",
"debug:generate-summaries": "pnpm --filter @sveltejs/mcp-server run debug:generate-summaries",
"release": "pnpm --filter @sveltejs/mcp run build && changeset publish"
"release": "pnpm --filter @sveltejs/mcp run build && changeset publish",
"changeset:version": "changeset version && pnpm --filter @sveltejs/mcp run update:version && git add --all",
"sync-plugins": "pnpm sync-claude-plugin && pnpm sync-cursor-plugin && pnpm sync-opencode-plugin && pnpm bump-plugin-versions",
"sync-claude-plugin": "node scripts/sync-claude-plugin.ts",
"sync-cursor-plugin": "node scripts/sync-cursor-plugin.ts",
"sync-opencode-plugin": "node scripts/sync-opencode-plugin.ts && pnpm generate-opencode-jsonschema",
"bump-plugin-versions": "node scripts/bump-plugin-versions.ts",
"postbump-plugin-versions": "pnpm format",
"resolve-references": "node scripts/resolve-references.ts",
"postresolve-references": "pnpm format"
},
"keywords": [
"svelte",
@@ -28,26 +43,24 @@
],
"private": true,
"devDependencies": {
"@changesets/cli": "^2.29.7",
"@eslint/compat": "^1.3.2",
"@eslint/js": "^9.36.0",
"@modelcontextprotocol/inspector": "^0.16.7",
"@svitejs/changesets-changelog-github-compact": "^1.2.0",
"eslint": "^9.36.0",
"eslint-config-prettier": "^10.0.1",
"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",
"publint": "^0.3.13",
"typescript": "^5.0.0",
"typescript-eslint": "^8.44.1",
"vitest": "^3.2.3"
},
"pnpm": {
"onlyBuiltDependencies": [
"esbuild"
]
"@changesets/cli": "catalog:tooling",
"@eslint/compat": "catalog:lint",
"@eslint/js": "catalog:lint",
"@modelcontextprotocol/inspector": "catalog:ai",
"@sveltejs/adapter-vercel": "catalog:svelte",
"@svitejs/changesets-changelog-github-compact": "catalog:tooling",
"eslint": "catalog:lint",
"eslint-config-prettier": "catalog:lint",
"eslint-plugin-import": "catalog:lint",
"eslint-plugin-pnpm": "catalog:lint",
"eslint-plugin-svelte": "catalog:lint",
"globals": "catalog:lint",
"node-resolve-ts": "catalog:tooling",
"prettier": "catalog:lint",
"prettier-plugin-svelte": "catalog:lint",
"publint": "catalog:tooling",
"typescript": "catalog:tooling",
"typescript-eslint": "catalog:lint",
"vitest": "catalog:tooling"
}
}

View File

@@ -1,19 +0,0 @@
{
"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

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

View File

@@ -1,82 +0,0 @@
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';
import { float_32_array } from './utils.js';
/**
* NOTE: if you modify a schema adding a vector column you need to manually add this
*
* CREATE INDEX IF NOT EXISTS name_of_the_index
* ON `name_of_the_table` (
* libsql_vector_idx(name_of_the_column, 'metric=cosine')
* )
*
* to the generated migration file
*/
export const distillations = sqliteTable('distillations', {
id: integer('id').primaryKey(),
preset_name: text('preset_name').notNull(),
version: text('version').notNull(),
content: text('content').notNull(),
size_kb: integer('size_kb').notNull(),
document_count: integer('document_count').notNull(),
distillation_job_id: integer('distillation_job_id').references(() => distillation_jobs.id),
created_at: integer('created_at', { mode: 'timestamp' })
.notNull()
.$defaultFn(() => new Date()),
});
export const distillation_jobs = sqliteTable('distillation_jobs', {
id: integer('id').primaryKey(),
preset_name: text('preset_name').notNull(),
batch_id: text('batch_id'),
status: text('status', { enum: ['pending', 'processing', 'completed', 'failed'] }).notNull(),
model_used: text('model_used').notNull(),
total_files: integer('total_files').notNull(),
processed_files: integer('processed_files').notNull().default(0),
successful_files: integer('successful_files').notNull().default(0),
minimize_applied: integer('minimize_applied', { mode: 'boolean' }).notNull().default(false),
total_input_tokens: integer('total_input_tokens').notNull().default(0),
total_output_tokens: integer('total_output_tokens').notNull().default(0),
started_at: integer('started_at', { mode: 'timestamp' }),
completed_at: integer('completed_at', { mode: 'timestamp' }),
error_message: text('error_message'),
metadata: text('metadata', { mode: 'json' }).notNull().default({}),
created_at: integer('created_at', { mode: 'timestamp' })
.notNull()
.$defaultFn(() => new Date()),
updated_at: integer('updated_at', { mode: 'timestamp' })
.notNull()
.$defaultFn(() => new Date()),
});
export const content = sqliteTable('content', {
id: integer('id').primaryKey(),
path: text('path').notNull(),
filename: text('filename').notNull(),
content: text('content').notNull(),
size_bytes: integer('size_bytes').notNull(),
embeddings: float_32_array('embeddings', { dimensions: 1024 }),
metadata: text('metadata', { mode: 'json' }).notNull().default({}),
created_at: integer('created_at', { mode: 'timestamp' })
.notNull()
.$defaultFn(() => new Date()),
updated_at: integer('updated_at', { mode: 'timestamp' })
.notNull()
.$defaultFn(() => new Date()),
});
export const content_distilled = sqliteTable('content_distilled', {
id: integer('id').primaryKey(),
path: text('path').notNull(),
filename: text('filename').notNull(),
content: text('content').notNull(),
size_bytes: integer('size_bytes').notNull(),
embeddings: float_32_array('embeddings', { dimensions: 1024 }),
metadata: text('metadata', { mode: 'json' }).notNull().default({}),
created_at: integer('created_at', { mode: 'timestamp' })
.notNull()
.$defaultFn(() => new Date()),
updated_at: integer('updated_at', { mode: 'timestamp' })
.notNull()
.$defaultFn(() => new Date()),
});

View File

@@ -1,65 +0,0 @@
/**
* @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 {number[]} arr The embeddings array.
*/
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} 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({
* id: vector_table.id,
* text: vector_table.text,
* distance: distance(vector_table.vector, await get_embeddings(sentence)),
* })
* .from(vector_table)
* .orderBy(sql`distance`)
* .execute();
*/
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,
);
}
/**
* Custom drizzle type to use the LibSQL vector column type.
*/
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})`;
},
/**
* @param {Buffer} value
*/
fromDriver(value) {
return Array.from(new Float32Array(value.buffer));
},
/**
*
* @param {number[]} value
* @returns
*/
toDriver(value) {
return vector(value);
},
});

View File

@@ -14,31 +14,31 @@
"debug:generate-summaries": "DEBUG_MODE=1 node scripts/generate-summaries.ts --experimental-strip-types"
},
"exports": {
".": "./src/index.ts"
},
"peerDependencies": {
"drizzle-orm": "^0.40.0"
".": "./src/index.ts",
"./handlers": "./src/mcp/handlers/tools/handlers.ts"
},
"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"
"@mcp-ui/server": "catalog:ai",
"@tmcp/adapter-valibot": "catalog:tmcp",
"@tmcp/transport-in-memory": "catalog:tmcp",
"@typescript-eslint/parser": "catalog:lint",
"eslint": "catalog:lint",
"eslint-plugin-svelte": "catalog:lint",
"svelte": "catalog:svelte",
"svelte-eslint-parser": "catalog:lint",
"tmcp": "catalog:tmcp",
"ts-blank-space": "catalog:tooling",
"typescript-eslint": "catalog:lint",
"valibot": "catalog:tooling",
"vitest": "catalog:tooling",
"zimmerframe": "catalog:tooling"
},
"devDependencies": {
"@anthropic-ai/sdk": "^0.65.0",
"dotenv": "^17.2.3",
"@sveltejs/kit": "^2.42.2",
"@types/eslint-scope": "^8.3.2",
"@types/estree": "^1.0.8",
"@typescript-eslint/types": "^8.44.0"
"@anthropic-ai/sdk": "catalog:ai",
"@sveltejs/kit": "catalog:svelte",
"@types/eslint-scope": "catalog:lint",
"@types/estree": "catalog:tooling",
"@typescript-eslint/types": "catalog:lint",
"dotenv": "catalog:tooling"
}
}

View File

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

View File

@@ -61,7 +61,7 @@ describe('add_autofixers_issues', () => {
<script>
const count = ${init}(0);
</script>
<button onclick={() => count = 43}>Increment</button>
`);
@@ -74,7 +74,7 @@ describe('add_autofixers_issues', () => {
const content = run_autofixers_on_code(`
<script>
const count = 0;
$effect(() => {
count = 43;
});
@@ -89,7 +89,7 @@ describe('add_autofixers_issues', () => {
const content = run_autofixers_on_code(`
<script>
let count = ${init}(0);
$effect(() => {
count++;
});
@@ -105,7 +105,7 @@ describe('add_autofixers_issues', () => {
const content = run_autofixers_on_code(`
<script>
let count = ${init}({ value: 0 });
$effect(() => {
count.value = 42;
});
@@ -116,6 +116,52 @@ describe('add_autofixers_issues', () => {
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
);
});
it(`should add a suggestion for variables that are mutated within an effect.pre`, () => {
const content = run_autofixers_on_code(`
<script>
let count = ${init}({ value: 0 });
$effect.pre(() => {
count.value = 42;
});
</script>
`);
expect(content.suggestions).toContain(
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
);
});
});
it('should add a suggestion when calling a function inside an effect', () => {
const content = run_autofixers_on_code(`
<script>
import { fetch_data } from './data.js';
$effect(() => {
fetch_data();
});
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are calling the function \`fetch_data\` inside an $effect. Please check if the function is reassigning a stateful variable because that's considered malpractice and check if it could use \`$derived\` instead. Ignore this suggestion if you are sure this function is not assigning any stateful variable or if you can't check if it does.`,
);
});
it('should add a suggestion when calling a function inside an effect (with non identifier callee)', () => {
const content = run_autofixers_on_code(`
<script>
import { fetch_data } from './data.js';
$effect(() => {
fetch_data.fetch();
});
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are calling a function inside an $effect. Please check if the function is reassigning a stateful variable because that's considered malpractice and check if it could use \`$derived\` instead. Ignore this suggestion if you are sure this function is not assigning any stateful variable or if you can't check if it does.`,
);
});
});
@@ -298,74 +344,111 @@ describe('add_autofixers_issues', () => {
});
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(`
describe.each([
{ source: 'svelte' },
{ source: 'svelte/runes' },
{ source: '@sveltejs/runes' },
{ source: '@sveltejs/vite-plugin-svelte' },
])('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.`,
);
});
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(`
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.`,
);
});
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(`
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.`,
);
});
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(`
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.`,
);
});
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(`
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.`,
);
});
},
);
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.each(dollarless_runes)('importing $rune from external lib', ({ rune }) => {
it(`should not add suggestions when importing from packages whose name doesn't contain svelte`, () => {
const content = run_autofixers_on_code(`
<script>
import { ${rune} } from 'something-something';
</script>`);
expect(content.suggestions).not.toContain(
`You are importing "${rune}" from "something-something". This is not necessary, all runes are globally available. Please remove this import and use "$${rune}" directly.`,
);
});
it(`should add suggestions with a different hint when importing from packages whose name contains svelte but it's not official`, () => {
const content = run_autofixers_on_code(`
<script>
import { ${rune} } from 'svelte-something-something';
</script>`);
expect(content.suggestions).toContain(
`You are importing "${rune}" from "svelte-something-something". If you are trying to import runes to use them this is not necessary, all runes are globally available. Please remove this import and use "$${rune}" directly. If you are importing the function from a separate library ignore this suggestion.`,
);
});
});
it('should not add the imported_runes suggestion when importing derived from svelte/store', () => {
const content = run_autofixers_on_code(`
<script>
import { derived } from 'svelte/store';
</script>`);
expect(content.suggestions).not.toContain(
'You are importing "derived" from "svelte/store". This is not necessary, all runes are globally available. Please remove this import and use "$derived" directly.',
);
});
});
describe('derived_with_function', () => {
@@ -468,4 +551,240 @@ describe('add_autofixers_issues', () => {
);
});
});
describe('suggest_attachments', () => {
describe('bind:this', () => {
it('should add suggestions when using bind:this on an element', () => {
const content = run_autofixers_on_code(`
<script>
let a = $state();
</script>
<a bind:this={a} />`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
'The usage of `bind:this` can often be replaced with an easier to read `action` or even better an `attachment`. Consider using the latter if possible.',
);
});
it('should not add suggestions when using bind:this on a component', () => {
const content = run_autofixers_on_code(`
<script>
import Child from './Child.svelte';
let a = $state();
</script>
<Child bind:this={a} />`);
expect(content.suggestions).not.toContain(
'The usage of `bind:this` can often be replaced with an easier to read `action` or even better an `attachment`. Consider using the latter if possible.',
);
});
it('should not add suggestions when using bind:this on a component nested in an element', () => {
const content = run_autofixers_on_code(`
<script>
import Child from './Child.svelte';
let a = $state();
</script>
<div>
<Child bind:this={a} />
</div>`);
expect(content.suggestions).not.toContain(
'The usage of `bind:this` can often be replaced with an easier to read `action` or even better an `attachment`. Consider using the latter if possible.',
);
});
it('should add suggestions but not suggest attachments when using bind:this on an element and the desired svelte version is 4', () => {
const content = run_autofixers_on_code(
`
<script>
let a;
</script>
<a bind:this={a} />`,
4,
);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
'The usage of `bind:this` can often be replaced with an easier to read `action`. Consider using the latter if possible.',
);
});
});
describe('use:', () => {
it('should add suggestions when using use: on an element and the action is declared as a function', () => {
const content = run_autofixers_on_code(
`<script>
function my_action(node) {
// do something with the node
}
</script>
<a use:my_action />`,
);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
'Consider using an `attachment` instead of an `action` for "my_action".',
);
});
it('should add suggestions when using use: on an element and the action is declared as a variable', () => {
const content = run_autofixers_on_code(
`<script>
const my_action = (node) => {
// do something with the node
}
</script>
<a use:my_action />`,
);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
'Consider using an `attachment` instead of an `action` for "my_action".',
);
});
it('should add suggestions when using use: on an element and the action is declared as an object', () => {
const content = run_autofixers_on_code(
`<script>
const my_action = {
action: (node) => {
// do something with the node
}
};
</script>
<a use:my_action.action />`,
);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
'Consider using an `attachment` instead of an `action` for "my_action".',
);
});
it('should not add suggestions when using use: on an element and the desired svelte version is 4', () => {
const content = run_autofixers_on_code(
`<script>
function my_action(node) {
// do something with the node
}
</script>
<a use:my_action />`,
4,
);
expect(content.suggestions).not.toContain(
'Consider using an `attachment` instead of an `action` for "my_action".',
);
});
it('should not add suggestions when using use: on an element and the action comes from an import', () => {
const content = run_autofixers_on_code(
`<script>
import { my_action } from './actions.js';
</script>
<a use:my_action />`,
);
expect(content.suggestions).not.toContain(
'Consider using an `attachment` instead of an `action` for "my_action".',
);
});
it('should not add suggestions when using use: on an element and the action comes from the props', () => {
const content = run_autofixers_on_code(
`<script>
const { my_action } = $props();
</script>
<a use:my_action />`,
);
expect(content.suggestions).not.toContain(
'Consider using an `attachment` instead of an `action` for "my_action".',
);
});
it('should not add suggestions when using use: on an element and the action comes from a global variable', () => {
const content = run_autofixers_on_code(`<a use:my_action />`);
expect(content.suggestions).not.toContain(
'Consider using an `attachment` instead of an `action` for "my_action".',
);
});
});
});
describe('read_state_with_dollar', () => {
with_possible_inits('($init)', ({ init }) => {
it(`should add an issue when reading a stateful variable initialized with ${init} like if it was a store`, () => {
const content = run_autofixers_on_code(`<script>
let x = ${init}(()=> 43);
$x;
</script>
`);
expect(content.issues).toContain(
`You are reading the stateful variable "$x" with a "$" prefix. Stateful variables are not stores and should be read without the "$". Please read it as a normal variable "x"`,
);
});
});
it(`should not add an issue when reading an imported variable like if it was a store`, () => {
const content = run_autofixers_on_code(`<script>
import { x } from "./my-stores.ts";
$x;
</script>
`);
expect(content.issues).not.toContain(
`You are reading the stateful variable "$x" with a "$" prefix. Stateful variables are not stores and should be read without the "$". Please read it as a normal variable "x"`,
);
});
it(`should not add an issue when reading a non-stateful variable like if it was a store`, () => {
const content = run_autofixers_on_code(`<script>
import { writable } from "svelte/store";
const x = writable(0);
$x;
</script>
`);
expect(content.issues).not.toContain(
`You are reading the stateful variable "$x" with a "$" prefix. Stateful variables are not stores and should be read without the "$". Please read it as a normal variable "x"`,
);
});
it(`should not add an issue when reading a prop like if it was a store`, () => {
const content = run_autofixers_on_code(`<script>
const { x } = $props();
$x;
</script>
`);
expect(content.issues).not.toContain(
`You are reading the stateful variable "$x" with a "$" prefix. Stateful variables are not stores and should be read without the "$". Please read it as a normal variable "x"`,
);
});
// https://github.com/sveltejs/ai-tools/issues/200
it('should not crash when reading $-prefixed identifier for variable initialized with member expression', () => {
expect(() =>
run_autofixers_on_code(`<div>{$x}</div>
<script>
const x = foo.bar;
</script>`),
).not.toThrow();
});
});
});

View File

@@ -8,6 +8,7 @@ export function add_autofixers_issues(
code: string,
desired_svelte_version: number,
filename = 'Component.svelte',
async = false,
) {
const parsed = parse(code, filename);
@@ -15,7 +16,7 @@ export function add_autofixers_issues(
for (const autofixer of Object.values(autofixers)) {
walk(
parsed.ast as unknown as Node,
{ output: content, parsed, desired_svelte_version },
{ output: content, parsed, desired_svelte_version, async },
autofixer,
);
}

View File

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

View File

@@ -12,7 +12,7 @@ function base_config(svelte_config: Config): ESLint.Options['baseConfig'] {
return [
...svelte.configs.recommended,
{
files: ['*.svelte'],
files: ['*.svelte', '*.svelte.ts', '*.svelte.js'],
rules: {
'no-self-assign': 'warn',
'svelte/infinite-reactive-loop': 'warn',
@@ -35,6 +35,7 @@ function base_config(svelte_config: Config): ESLint.Options['baseConfig'] {
'svelte/prefer-writable-derived': 'warn',
'svelte/require-event-dispatcher-types': 'warn',
'svelte/require-store-reactive-access': 'warn',
'svelte/no-inspect': 'off',
},
languageOptions: {
@@ -51,7 +52,7 @@ function base_config(svelte_config: Config): ESLint.Options['baseConfig'] {
];
}
function get_linter(version: number) {
function get_linter(version: number, async = false) {
if (version < 5) {
return (svelte_4_linter ??= new ESLint({
overrideConfigFile: true,
@@ -67,6 +68,7 @@ function get_linter(version: number) {
baseConfig: base_config({
compilerOptions: {
runes: true,
experimental: { async },
},
}),
}));
@@ -77,8 +79,9 @@ export async function add_eslint_issues(
code: string,
desired_svelte_version: number,
filename = 'Component.svelte',
async = false,
) {
const eslint = get_linter(desired_svelte_version);
const eslint = get_linter(desired_svelte_version, async);
const results = await eslint.lintText(code, { filePath: filename || './Component.svelte' });
for (const message of results[0]?.messages ?? []) {

View File

@@ -1,4 +1,10 @@
import type { AssignmentExpression, Identifier, Node, UpdateExpression } from 'estree';
import type {
AssignmentExpression,
CallExpression,
Identifier,
Node,
UpdateExpression,
} from 'estree';
import type { Autofixer, AutofixerState } from './index.js';
import { left_most_id } from '../ast/utils.js';
import type { AST } from 'svelte-eslint-parser';
@@ -11,25 +17,17 @@ function run_if_in_effect(
) {
const in_effect = path.findLast(
(node) =>
node.type === 'CallExpression' &&
node.callee.type === 'Identifier' &&
node.callee.name === '$effect',
node.type === 'CallExpression' && state.parsed.is_rune(node, ['$effect', '$effect.pre']),
);
if (
in_effect &&
in_effect.type === 'CallExpression' &&
(in_effect.callee.type === 'Identifier' || in_effect.callee.type === 'MemberExpression')
) {
if (state.parsed.is_rune(in_effect, ['$effect', '$effect.pre'])) {
to_run();
}
if (in_effect) {
to_run();
}
}
function visitor(
function assign_or_update_visitor(
node: UpdateExpression | AssignmentExpression,
{ state, path }: Context<Node | AST.SvelteNode, AutofixerState>,
{ state, path, next }: Context<Node | AST.SvelteNode, AutofixerState>,
) {
run_if_in_effect(path, state, () => {
function check_if_stateful_id(id: Identifier) {
@@ -58,9 +56,25 @@ function visitor(
}
}
});
next();
}
function call_expression_visitor(
node: CallExpression,
{ state, path, next }: Context<Node | AST.SvelteNode, AutofixerState>,
) {
run_if_in_effect(path, state, () => {
const function_name =
node.callee.type === 'Identifier' ? `the function \`${node.callee.name}\`` : 'a function';
state.output.suggestions.push(
`You are calling ${function_name} inside an $effect. Please check if the function is reassigning a stateful variable because that's considered malpractice and check if it could use \`$derived\` instead. Ignore this suggestion if you are sure this function is not assigning any stateful variable or if you can't check if it does.`,
);
});
next();
}
export const assign_in_effect: Autofixer = {
UpdateExpression: visitor,
AssignmentExpression: visitor,
UpdateExpression: assign_or_update_visitor,
AssignmentExpression: assign_or_update_visitor,
CallExpression: call_expression_visitor,
};

View File

@@ -3,10 +3,20 @@ import type { Autofixer } from './index.js';
const dollarless_runes = base_runes.map((r) => r.replace('$', ''));
function should_suggest_for_source(source: string, rune: string) {
if (!source.includes('svelte')) {
return false;
}
if (source === 'svelte/store' && rune === 'derived') {
return false;
}
return true;
}
export const imported_runes: Autofixer = {
ImportDeclaration(node, { state, next }) {
const source = (node.source.value || node.source.raw?.slice(1, -1))?.toString();
if (source && source.startsWith('svelte')) {
if (source) {
for (const specifier of node.specifiers) {
const id =
specifier.type === 'ImportDefaultSpecifier'
@@ -16,10 +26,25 @@ export const imported_runes: Autofixer = {
: specifier.type === 'ImportSpecifier'
? specifier.imported
: null;
if (id && id.type === 'Identifier' && dollarless_runes.includes(id.name)) {
state.output.suggestions.push(
`You are importing "${id.name}" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$${id.name}" directly.`,
);
if (
id &&
id.type === 'Identifier' &&
dollarless_runes.includes(id.name) &&
should_suggest_for_source(source, id.name)
) {
if (
source === 'svelte' ||
source.startsWith('svelte/') ||
source.startsWith('@sveltejs')
) {
state.output.suggestions.push(
`You are importing "${id.name}" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$${id.name}" directly.`,
);
} else {
state.output.suggestions.push(
`You are importing "${id.name}" from "${source}". If you are trying to import runes to use them this is not necessary, all runes are globally available. Please remove this import and use "$${id.name}" directly. If you are importing the function from a separate library ignore this suggestion.`,
);
}
}
}
}

View File

@@ -7,6 +7,7 @@ export type AutofixerState = {
output: { issues: string[]; suggestions: string[] };
parsed: ParseResult;
desired_svelte_version: number;
async?: boolean;
};
export type Autofixer = Visitors<Node | AST.SvelteNode, AutofixerState>;
@@ -16,3 +17,5 @@ export * from './wrong-property-access-state.js';
export * from './imported-runes.js';
export * from './derived-with-function.js';
export * from './use-runes-instead-of-store.js';
export * from './suggest-attachments.js';
export * from './read-state-with-dollar.js';

View File

@@ -0,0 +1,22 @@
import type { Autofixer } from './index.js';
export const read_state_with_dollar: Autofixer = {
Identifier(node, { state }) {
if (node.name.startsWith('$')) {
const reference = state.parsed.find_reference_by_id(node);
if (reference && reference.resolved && reference.resolved.defs[0]?.node?.init) {
const is_state = state.parsed.is_rune(reference.resolved.defs[0].node.init, [
'$state',
'$state.raw',
'$derived',
'$derived.by',
]);
if (is_state) {
state.output.issues.push(
`You are reading the stateful variable "${node.name}" with a "$" prefix. Stateful variables are not stores and should be read without the "$". Please read it as a normal variable "${node.name.substring(1)}"`,
);
}
}
}
},
};

View File

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

View File

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

View File

@@ -1,32 +1,23 @@
import type { SvelteMcp } from '../../index.js';
import * as v from 'valibot';
import { format_sections_list } from '../../utils.js';
import { icons } from '../../icons/index.js';
import { prompt } from 'tmcp/utils';
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 = await format_sections_list();
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:
/**
* Function that actually generates the prompt string. You can use this in the MCP server handler to generate the prompt, it can accept arguments
* if needed (it will always be invoked manually so it's up to you to provide the arguments).
*/
function svelte_task(available_docs: string, task: string) {
return `You are a Svelte expert tasked to build components and utilities for Svelte developers. If you need documentation for anything related to Svelte you can invoke the tool \`get-documentation\` with one of the following paths. However: before invoking the \`get-documentation\` tool, try to answer the users query using your own knowledge and the \`svelte-autofixer\` tool. Be mindful of how many section you request, since it is token-intensive!
<available-docs>
${available_docs}
</available-docs>
These are the available documentation sections that \`list-sections\` will return, you do not need to call it again.
Every time you write a Svelte component or a Svelte module you MUST invoke the \`svelte-autofixer\` tool providing the code. The tool will return a list of issues or suggestions. If there are any issues or suggestions you MUST fix them and call the tool again with the updated code. You MUST keep doing this until the tool returns no issues or suggestions. Only then you can return the code to the user.
This is the task you will work on:
@@ -35,12 +26,57 @@ This is the task you will work on:
${task}
</task>
If you are not writing the code into a file, once you have the final version of the code ask the user if it wants to generate a playground link to quickly check the code in it and if it answer yes call the \`playground-link\` tool and return the url to the user nicely formatted. The playground link MUST be generated only once you have the final version of the code and you are ready to share it, it MUST include an entry point file called \`App.svelte\` where the main component should live. If you have multiple files to include in the playground link you can include them all at the root.
`,
If you are not writing the code into a file, once you have the final version of the code ask the user if it wants to generate a playground link to quickly check the code in it and if it answer yes call the \`playground-link\` tool and return the url to the user nicely formatted. The playground link MUST be generated only once you have the final version of the code and you are ready to share it, it MUST include an entry point file called \`App.svelte\` where the main component should live. If you have multiple files to include in the playground link you can include them all at the root.`;
}
/**
* This function is used to generate the prompt to update the docs in the script `/scripts/update-docs-prompts.ts` it should use the default export
* function and pass in the arguments. Since it will be included in the documentation if it's an argument that the MCP will expose it should
* be in the format [NAME_OF_THE_ARGUMENT] to signal the user that it can substitute it.
*
* The name NEEDS to be `generate_for_docs`.
*/
export async function generate_for_docs() {
const available_docs = await format_sections_list();
return svelte_task(available_docs, '[YOUR TASK HERE]');
}
/**
* Human readable description of what the prompt does. It will be included in the documentation.
*
* The name NEEDS to be `docs_description`.
*/
export const docs_description =
'This prompt should be used whenever you are asking the model to work on a Svelte-related task. It will instruct the LLM which documentation sections are available, which tools to invoke, when to invoke them, and how to interpret the results.';
export function setup_svelte_task(server: SvelteMcp) {
server.prompt(
{
name: 'svelte-task',
title: 'Svelte-Task-Prompt',
description:
'Use this Prompt to ask for any svelte related task. It will automatically instruct the LLM on how to best use the autofixer and how to query for documentation pages.',
schema: v.object({
task: v.pipe(v.string(), v.description('The task to be performed')),
}),
complete: {
task() {
return {
completion: {
values: [''],
},
},
],
};
};
},
},
icons,
},
async ({ task }) => {
if (server.ctx.sessionId && server.ctx.custom?.track) {
await server.ctx.custom?.track?.(server.ctx.sessionId, 'svelte-task');
}
const available_docs = await format_sections_list();
return prompt.text(svelte_task(available_docs, task));
},
);
}

View File

@@ -1,12 +1,14 @@
import type { SvelteMcp } from '../../index.js';
import { get_sections, fetch_with_timeout } from '../../utils.js';
import { icons } from '../../icons/index.js';
import { resource } from 'tmcp/utils';
export async function list_sections(server: SvelteMcp) {
const sections = await get_sections();
server.template(
{
name: 'Svelte Doc Section',
name: 'Svelte-Doc-Section',
description: 'A single documentation section',
list() {
return sections.map((section) => {
@@ -42,23 +44,23 @@ export async function list_sections(server: SvelteMcp) {
},
},
uri: 'svelte://{/slug*}.md',
icons,
},
async (uri, { slug }) => {
if (server.ctx.sessionId && server.ctx.custom?.track) {
await server.ctx.custom?.track?.(
server.ctx.sessionId,
'svelte-doc-section',
Array.isArray(slug) ? slug.join(',') : 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,
},
],
};
return resource.text(uri, content);
},
);
}

View File

@@ -2,115 +2,180 @@ import type { SvelteMcp } from '../../index.js';
import * as v from 'valibot';
import { get_sections, fetch_with_timeout, format_sections_list } from '../../utils.js';
import { SECTIONS_LIST_INTRO, SECTIONS_LIST_OUTRO } from './prompts.js';
import { icons } from '../../icons/index.js';
import { tool } from 'tmcp/utils';
const get_documentation_schema = v.object({
section: v.pipe(
v.union([v.string(), v.array(v.string())]),
v.description(
'The section name(s) to retrieve. Can search by title (e.g., "$state", "load functions") or file path (e.g., "cli/overview"). Supports single string and array of strings',
),
),
});
export async function get_documentation_handler({
section,
}: v.InferInput<typeof get_documentation_schema>) {
let sections: string[];
if (Array.isArray(section)) {
sections = section.filter((s): s is string => typeof s === 'string');
} else if (
typeof section === 'string' &&
section.trim().startsWith('[') &&
section.trim().endsWith(']')
) {
try {
const parsed = JSON.parse(section);
if (Array.isArray(parsed)) {
sections = parsed.filter((s): s is string => typeof s === 'string');
} else {
sections = [section];
}
} catch {
sections = [section];
}
} else if (typeof section === 'string') {
sections = [section];
} else {
sections = [];
}
const available_sections = await get_sections();
const settled_results = await Promise.allSettled(
sections.map(async (requested_section) => {
const matched_section = available_sections.find(
(s) =>
s.title.toLowerCase() === requested_section.toLowerCase() ||
s.slug === requested_section ||
s.url === requested_section,
);
if (matched_section) {
try {
const response = await fetch_with_timeout(matched_section.url);
if (response.ok) {
const content = await response.text();
return { success: true, content: `## ${matched_section.title}\n\n${content}` };
} else {
return {
success: false,
content: `## ${matched_section.title}\n\nError: Could not fetch documentation (HTTP ${response.status})`,
};
}
} catch (error) {
return {
success: false,
content: `## ${matched_section.title}\n\nError: Failed to fetch documentation - ${error}`,
};
}
} else {
return {
success: false,
content: `## ${requested_section}\n\nError: Section not found.`,
};
}
}),
);
const results = settled_results.map((result) => {
if (result.status === 'fulfilled') {
return result.value;
} else {
return {
success: false,
content: `Error: Couldn't fetch - ${result.reason}`,
};
}
});
const successes = results.filter((r) => r.success);
const failed_sections = sections.filter(
(s) =>
!available_sections.some(
(a) => a.title.toLowerCase() === s.toLowerCase() || a.slug === s || a.url === s,
),
);
if (successes.length > 0 && failed_sections.length === 0) {
return successes.map((r) => r.content).join('\n\n---\n\n');
}
const parts: string[] = [];
if (successes.length > 0) {
parts.push(successes.map((r) => r.content).join('\n\n---\n\n'));
}
const fuzzy_results = failed_sections.map((requested) => {
const lower = requested.toLowerCase();
const matches = available_sections.filter(
(a) =>
a.title.toLowerCase().includes(lower) ||
a.slug.includes(lower) ||
lower.includes(a.slug.split('/').pop() ?? '') ||
a.use_cases.toLowerCase().includes(lower),
);
return { requested, matches };
});
const has_fuzzy = fuzzy_results.some((r) => r.matches.length > 0);
// Full list only when no successes and no fuzzy matches
if (successes.length === 0 && !has_fuzzy) {
const formatted_sections = await format_sections_list();
parts.push(`${SECTIONS_LIST_INTRO}\n\n${formatted_sections}\n\n${SECTIONS_LIST_OUTRO}`);
}
// Similar results then errors
for (const { requested, matches } of fuzzy_results) {
if (matches.length > 0) {
const match_list = matches.map((m) => `- title: ${m.title}, section: ${m.slug}`).join('\n');
parts.push(
`${matches.length} similar result${matches.length > 1 ? 's' : ''} for "${requested}":\n${match_list}`,
);
}
parts.push(`Section not found: "${requested}".`);
}
return parts.join('\n\n---\n\n');
}
export function get_documentation(server: SvelteMcp) {
server.tool(
{
name: 'get-documentation',
enabled: () => false,
description:
'Retrieves full documentation content for Svelte 5 or SvelteKit sections. Supports flexible search by title (e.g., "$state", "routing") or file path (e.g., "cli/overview"). Can accept a single section name or an array of sections. Before running this, make sure to analyze the users query, as well as the output from list-sections (which should be called first). Then ask for ALL relevant sections the user might require. For example, if the user asks to build anything interactive, you will need to fetch all relevant runes, and so on.',
schema: v.object({
section: v.pipe(
v.union([v.string(), v.array(v.string())]),
v.description(
'The section name(s) to retrieve. Can search by title (e.g., "$state", "load functions") or file path (e.g., "cli/overview"). Supports single string and array of strings',
),
),
}),
'Retrieves full documentation content for Svelte 5 or SvelteKit sections. Supports flexible search by title (e.g., "$state", "routing") or file path (e.g., "cli/overview"). Can accept a single section name or an array of sections. Before running this, make sure to analyze the users query, as well as the output from list-sections (which should be called first). Then ask for ALL relevant sections the user might require. For example, if the user asks to build anything interactive, you will need to fetch all relevant runes, and so on. Before calling this tool, try to implement Svelte components using your own knowledge and the `svelte-autofixer` tool, since calling this tool is token intensive.',
schema: get_documentation_schema,
annotations: {
title: 'Get Documentation',
destructiveHint: false,
readOnlyHint: true,
openWorldHint: false,
},
icons,
},
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 = [];
if (server.ctx.sessionId && server.ctx.custom?.track) {
await server.ctx.custom?.track?.(server.ctx.sessionId, 'get-documentation');
}
const available_sections = await get_sections();
const settled_results = await Promise.allSettled(
sections.map(async (requested_section) => {
const matched_section = available_sections.find(
(s) =>
s.title.toLowerCase() === requested_section.toLowerCase() ||
s.slug === requested_section ||
s.url === requested_section,
try {
const content = await get_documentation_handler({ section });
return tool.text(content);
} catch (e) {
const error = e as Error;
if (server.ctx.sessionId && server.ctx.custom?.track) {
await server.ctx.custom?.track?.(
server.ctx.sessionId,
'get-documentation-error',
error.message,
);
if (matched_section) {
try {
const response = await fetch_with_timeout(matched_section.url);
if (response.ok) {
const content = await response.text();
return { success: true, content: `## ${matched_section.title}\n\n${content}` };
} else {
return {
success: false,
content: `## ${matched_section.title}\n\nError: Could not fetch documentation (HTTP ${response.status})`,
};
}
} catch (error) {
return {
success: false,
content: `## ${matched_section.title}\n\nError: Failed to fetch documentation - ${error}`,
};
}
} else {
return {
success: false,
content: `## ${requested_section}\n\nError: Section not found.`,
};
}
}),
);
const results = settled_results.map((result) => {
if (result.status === 'fulfilled') {
return result.value;
} else {
return {
success: false,
content: `Error: Couldn't fetch - ${result.reason}`,
};
}
});
const has_any_success = results.some((result) => result.success);
let final_text = results.map((r) => r.content).join('\n\n---\n\n');
if (!has_any_success) {
const formatted_sections = await format_sections_list();
final_text += `\n\n---\n\n${SECTIONS_LIST_INTRO}\n\n${formatted_sections}\n\n${SECTIONS_LIST_OUTRO}`;
return tool.error(error.message);
}
return {
content: [
{
type: 'text',
text: final_text,
},
],
};
},
);
}

View File

@@ -0,0 +1,4 @@
export { get_documentation_handler } from './get-documentation.js';
export { list_sections_handler } from './list-sections.js';
export { svelte_autofixer_handler } from './svelte-autofixer.js';
export { playground_link_handler } from './playground-link.js';

View File

@@ -1,4 +1,4 @@
export * from './get-documentation.js';
export * from './list-sections.js';
export * from './svelte-autofixer.js';
export * from './playground-link.js';
export { get_documentation } from './get-documentation.js';
export { list_sections } from './list-sections.js';
export { svelte_autofixer } from './svelte-autofixer.js';
export { playground_link } from './playground-link.js';

View File

@@ -1,26 +1,47 @@
import type { SvelteMcp } from '../../index.js';
import { format_sections_list } from '../../utils.js';
import { SECTIONS_LIST_INTRO, SECTIONS_LIST_OUTRO } from './prompts.js';
import { icons } from '../../icons/index.js';
import { tool } from 'tmcp/utils';
export async function list_sections_handler() {
const formatted_sections = await format_sections_list();
return `${SECTIONS_LIST_INTRO}\n\n${formatted_sections}\n\n${SECTIONS_LIST_OUTRO}`;
}
export function list_sections(server: SvelteMcp) {
server.tool(
{
name: 'list-sections',
enabled: () => false,
description:
'Lists all available Svelte 5 and SvelteKit documentation sections in a structured format. Each section includes a "use_cases" field that describes WHEN this documentation would be useful. You should carefully analyze the use_cases field to determine which sections are relevant for the user\'s query. The use_cases contain comma-separated keywords describing project types (e.g., "e-commerce", "blog"), features (e.g., "authentication", "forms"), components (e.g., "slider", "modal"), development stages (e.g., "deployment", "testing"), or "always" for fundamental concepts. Match these use_cases against the user\'s intent - for example, if building an e-commerce site, fetch sections with use_cases containing "e-commerce", "product listings", "shopping cart", etc. If building a slider, look for "slider", "carousel", "animation", etc. Returns sections as "* title: [section_title], use_cases: [use_cases], path: [file_path]". Always run list-sections FIRST for any Svelte query, then analyze ALL use_cases to identify relevant sections, and finally use get_documentation to fetch ALL relevant sections at once.',
annotations: {
title: 'List Sections',
destructiveHint: false,
readOnlyHint: true,
openWorldHint: false,
},
icons,
},
async () => {
const formatted_sections = await format_sections_list();
return {
content: [
{
type: 'text',
text: `${SECTIONS_LIST_INTRO}\n\n${formatted_sections}\n\n${SECTIONS_LIST_OUTRO}`,
},
],
};
if (server.ctx.sessionId && server.ctx.custom?.track) {
await server.ctx.custom?.track?.(server.ctx.sessionId, 'list-sections');
}
try {
const content = await list_sections_handler();
return tool.text(content);
} catch (e) {
const error = e as Error;
if (server.ctx.sessionId && server.ctx.custom?.track) {
await server.ctx.custom?.track?.(
server.ctx.sessionId,
'list-sections-error',
error.message,
);
}
return tool.error(error.message);
}
},
);
}

View File

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

View File

@@ -1,5 +1,8 @@
import type { SvelteMcp } from '../../index.js';
import { createUIResource } from '@mcp-ui/server';
import { tool } from 'tmcp/utils';
import * as v from 'valibot';
import { icons } from '../../icons/index.js';
import type { SvelteMcp } from '../../index.js';
async function compress_and_encode_text(input: string) {
const reader = new Blob([input]).stream().pipeThrough(new CompressionStream('gzip')).getReader();
@@ -27,86 +30,232 @@ type File = {
text: boolean;
};
const playground_link_schema = v.object({
name: v.pipe(
v.string(),
v.description('The name of the Playground, it should reflect the user task'),
),
tailwind: v.pipe(
v.boolean(),
v.description(
"If the code requires Tailwind CSS to work...only send true if it it's using tailwind classes in the code",
),
),
files: v.pipe(
v.record(v.string(), v.string()),
v.description(
"An object where all the keys are the filenames (with extensions) and the values are the file content. For example: { 'Component.svelte': '<script>...</script>', 'utils.js': 'export function ...' }. The playground accept multiple files so if are importing from other files just include them all at the root level.",
),
),
});
const playground_link_output_schema = v.object({
url: v.string(),
});
export async function playground_link_handler({
files,
name,
tailwind,
}: v.InferInput<typeof playground_link_schema>) {
const playground_base = new URL('https://svelte.dev/playground');
const playground_files: File[] = [];
let has_app_svelte = false;
for (const [filename, contents] of Object.entries(files)) {
if (filename === 'App.svelte') has_app_svelte = true;
playground_files.push({
type: 'file',
name: filename,
basename: filename.replace(/^.*[\\/]/, ''),
contents,
text: true,
});
}
if (!has_app_svelte) {
throw new Error('The files must contain an App.svelte file as the entry point');
}
const playground_config = {
name,
tailwind: tailwind ?? false,
files: playground_files,
};
playground_base.hash = await compress_and_encode_text(JSON.stringify(playground_config));
const url = playground_base.toString();
// use the embed path to have a cleaner UI for mcp-ui
playground_base.pathname = '/playground/embed';
return {
url,
iframe_url: playground_base.toString(),
};
}
// Create the UI resource for MCP Apps hosts (with adapter)
// This will be registered as a resource that MCP Apps hosts can fetch
const playground_ui_resource = createUIResource({
uri: 'ui://svelte/playground-link',
encoding: 'text',
resourceProps: {
_meta: {
ui: {
csp: {
connectDomains: ['https://svelte.dev'],
resourceDomains: ['https://svelte.dev'],
frameDomains: ['https://svelte.dev'],
baseUriDomains: ['https://svelte.dev'],
},
},
},
},
content: {
type: 'rawHtml',
// This is a placeholder HTML - the actual iframe URL will be set per-request
// MCP Apps hosts receive the tool input/output via postMessage
htmlString: `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; }
iframe { width: 100%; height: 100%; border: none; display: none; }
.loading { display: flex; align-items: center; justify-content: center; height: 100%; font-family: system-ui, sans-serif; color: #666; }
</style>
</head>
<body>
<div class="loading" id="loading">Loading playground...</div>
<iframe id="playground" allow="clipboard-write"></iframe>
<script>
function size_changed() {
const width = document.body.scrollWidth;
window.parent.postMessage({
jsonrpc: '2.0',
method: 'ui/notifications/size-changed',
params: {
width,
height: 800
}
}, '*');
}
// Signal that the widget is ready
window.parent.postMessage({ type: 'ui-lifecycle-iframe-ready' }, '*');
// Listen for render data from the adapter (for MCP Apps hosts)
window.addEventListener('message', (event) => {
if (event.data.type === 'ui-lifecycle-iframe-render-data') {
const renderData = event.data.payload.renderData || {};
const toolOutput = renderData.toolOutput;
// The tool output contains the iframe URL
if (toolOutput && toolOutput.structuredContent && toolOutput.structuredContent.url) {
const iframe = document.getElementById('playground');
const loading = document.getElementById('loading');
// Convert the URL to embed URL
const embedUrl = toolOutput.structuredContent.url.replace('/playground#', '/playground/embed#');
iframe.src = embedUrl;
iframe.style.display = 'block';
iframe.addEventListener("load", () => {
size_changed();
});
loading.style.display = 'none';
}
}
});
</script>
</body>
</html>`,
},
uiMetadata: {
'preferred-frame-size': ['100%', '1200px'],
},
adapters: {
mcpApps: { enabled: true },
},
});
export function playground_link(server: SvelteMcp) {
// Register the UI resource so MCP Apps hosts can fetch it
server.resource(
{
name: 'playground-link-ui',
description: 'UI resource for the Svelte Playground widget',
uri: playground_ui_resource.resource.uri,
icons,
},
() => {
return {
contents: [playground_ui_resource.resource],
};
},
);
server.tool(
{
name: 'playground-link',
description:
'Generates a Playground link given a Svelte code snippet. Once you have the final version of the code you want to send to the user, ALWAYS ask the user if it wants a playground link to allow it to quickly check the code in the playground before calling this tool. NEVER use this tool if you have written the component to a file in the user project. The playground accept multiple files so if are importing from other files just include them all at the root level.',
schema: v.object({
name: v.pipe(
v.string(),
v.description('The name of the Playground, it should reflect the user task'),
),
tailwind: v.pipe(
v.boolean(),
v.description(
"If the code requires Tailwind CSS to work...only send true if it it's using tailwind classes in the code",
),
),
files: v.pipe(
v.record(v.string(), v.string()),
v.description(
"An object where all the keys are the filenames (with extensions) and the values are the file content. For example: { 'Component.svelte': '<script>...</script>', 'utils.js': 'export function ...' }. The playground accept multiple files so if are importing from other files just include them all at the root level.",
),
),
}),
outputSchema: v.object({
url: v.string(),
}),
schema: playground_link_schema,
outputSchema: playground_link_output_schema,
annotations: {
title: 'Playground Link',
destructiveHint: false,
readOnlyHint: true,
openWorldHint: false,
},
icons,
// For MCP Apps hosts - points to the registered resource
_meta: {
ui: {
resourceUri: playground_ui_resource.resource.uri,
},
},
},
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 (server.ctx.sessionId && server.ctx.custom?.track) {
await server.ctx.custom?.track?.(server.ctx.sessionId, 'playground-link');
}
if (!has_app_svelte) {
try {
const result = await playground_link_handler({ files, name, tailwind });
return {
isError: true,
content: [
{
type: 'text',
text: JSON.stringify({
error: 'The files must contain an App.svelte file as the entry point',
}),
text: JSON.stringify({ url: result.url }),
},
// Embedded resource for MCP-UI hosts (no adapter, uses externalUrl)
createUIResource({
uri: 'ui://svelte/playground-link',
content: {
type: 'externalUrl',
iframeUrl: result.iframe_url,
},
uiMetadata: {
'preferred-frame-size': ['100%', '1200px'],
},
encoding: 'text',
}),
],
structuredContent: { url: result.url },
};
} catch (e) {
const error = e as Error;
if (server.ctx.sessionId && server.ctx.custom?.track) {
await server.ctx.custom?.track?.(
server.ctx.sessionId,
'playground-link-error',
error.message,
);
}
return tool.error(error.message);
}
const playground_config = {
name,
tailwind: tailwind ?? false,
files: playground_files,
};
playground_base.hash = await compress_and_encode_text(JSON.stringify(playground_config));
const content = {
url: playground_base.toString(),
};
return {
content: [
{
type: 'text',
text: JSON.stringify(content),
},
],
structuredContent: content,
};
},
);
}

View File

@@ -0,0 +1,193 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { writeFileSync, unlinkSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { InMemoryTransport } from '@tmcp/transport-in-memory';
import { beforeEach, describe, expect, it } from 'vitest';
import { server } from '../../index.js';
const transport = new InMemoryTransport(server);
let session: ReturnType<typeof transport.session>;
async function autofixer_tool_call(
code: string,
is_error = false,
desired_svelte_version = 5,
async = false,
ctx?: { stdio?: boolean },
) {
const result = await session.callTool(
'svelte-autofixer',
{
code,
desired_svelte_version,
filename: 'App.svelte',
async,
},
ctx,
);
expect(result).toBeDefined();
if (is_error) {
return result as any;
}
expect(result.structuredContent).toBeDefined();
return result.structuredContent as any;
}
describe('svelte-autofixer tool', () => {
beforeEach(async () => {
session = transport.session();
session = transport.session();
await session.initialize(
'2025-06-18',
{},
{
name: 'test-client',
version: '1.0.0',
},
);
});
it('should add suggestions for js parse errors', async () => {
const content = await autofixer_tool_call(`<script>
$state count = 0;
</script>`);
expect(content.issues.length).toBeGreaterThan(0);
expect(content.suggestions).toContain(
"The code can't be compiled because a Javascript parse error. In case you are using runes like this `$state variable_name = 3;` or `$derived variable_name = 3 * count` that's not how runes are used. You need to use them as function calls without importing them: `const variable_name = $state(3)` and `const variable_name = $derived(3 * count)`.",
);
});
it('should add suggestions for snippets declared in script tag', async () => {
const content = await autofixer_tool_call(`<script>
{#snippet my_snippet()}
some content
{/snippet}
</script>`);
expect(content.issues.length).toBeGreaterThan(0);
expect(content.suggestions).toContain(
"The code can't be compiled because a Javascript parse error. The error suggests you have a `{#snippet ...}` block inside the `<script>` tag. Snippets are template syntax and should be declared in the markup section of the component, not in the script. Move the snippet outside of the `<script>` tag. Snippets declared in the markup can also be accessed in the script tag in case you need them.",
);
});
it('should error out if async is true with a version less than 5', async () => {
const content = await autofixer_tool_call(
`<script>
$state count = 0;
</script>`,
true,
4,
true,
);
expect(content.isError).toBeTruthy();
expect(content.content[0]).toBeDefined();
expect(content.content[0].text).toBe(
'The async option can only be used with Svelte version 5 or higher.',
);
});
it('should not add suggestion/issues if async is true and await is used in the template/derived', async () => {
const content = await autofixer_tool_call(
`<script>
import { slow_double } from './utils.js';
let count = $state(0);
let double = $derived(await slow_double(count));
</script>
{double}
{await slow_double(count)}`,
false,
5,
true,
);
expect(content.issues).toHaveLength(0);
expect(content.suggestions).toHaveLength(0);
expect(content.require_another_tool_call_after_fixing).toBeFalsy();
});
it('should add suggestion/issues if async is false and await is used in the template/derived', async () => {
const content = await autofixer_tool_call(
`<script>
import { slow_double } from './utils.js';
let count = $state(0);
let double = $derived(await slow_double(count));
</script>
{double}
{await slow_double(count)}`,
false,
5,
);
expect(content.issues.length).toBeGreaterThanOrEqual(1);
expect(content.issues).toEqual(
expect.arrayContaining([expect.stringContaining('experimental_async')]),
);
expect(content.require_another_tool_call_after_fixing).toBeTruthy();
});
it('should add suggestions for css invalid identifier', async () => {
const content = await autofixer_tool_call(`<script>
let my_color = $state('red');
</script>
<style>
.my-class {
color: {my_color};
}
</style>`);
expect(content.issues.length).toBeGreaterThan(0);
expect(content.suggestions).toContain(
"The code can't be compiled because a valid CSS identifier is expected. This sometimes means you are trying to use a variable in CSS like this: `color: {my_color}` but Svelte doesn't support that. You can use inline CSS variables for that `<div style:--color={my_color}></div>` and then use the variable as usual in CSS with `color: var(--color)`.",
);
});
it('should error in case the passed in version is different from 4 or 5', async () => {
const content = await autofixer_tool_call(`whatever`, true, 3);
expect(content.content).toBeDefined();
expect(content.content[0]).toBeDefined();
expect(content.content[0].text).toContain(
'The desired_svelte_version MUST be either 4 or 5 but received "3"',
);
});
it('should read file content from path when stdio context is true', async () => {
const tmp_file = join(tmpdir(), `svelte-autofixer-test-${Date.now()}.svelte`);
const file_content = `<script>
$state count = 0;
</script>`;
writeFileSync(tmp_file, file_content, 'utf-8');
try {
// with stdio: true, the file is read from disk and parsed, producing issues
const content = await autofixer_tool_call(tmp_file, false, 5, false, { stdio: true });
expect(content.issues.length).toBeGreaterThan(0);
expect(content.suggestions.length).toBeGreaterThan(0);
} finally {
unlinkSync(tmp_file);
}
});
it('should treat file path as code when stdio context is not set', async () => {
const tmp_file = join(tmpdir(), `svelte-autofixer-test-${Date.now()}.svelte`);
const file_content = `<script>
$state count = 0;
</script>`;
writeFileSync(tmp_file, file_content, 'utf-8');
try {
// without stdio context, the path string is treated as raw code (plain text), no issues
const content = await autofixer_tool_call(tmp_file, false, 5, false);
expect(content.issues).toHaveLength(0);
expect(content.suggestions).toHaveLength(0);
} finally {
unlinkSync(tmp_file);
}
});
});

View File

@@ -1,9 +1,121 @@
import { basename } from 'node:path';
import type { SvelteMcp } from '../../index.js';
import { tool } from 'tmcp/utils';
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';
import { add_autofixers_issues } from '../../autofixers/add-autofixers-issues.js';
import { icons } from '../../icons/index.js';
import { type SvelteMcp } from '../../index.js';
let cached_schema: ReturnType<typeof get_autofixer_schema> | null = null;
function get_autofixer_schema(stdio: boolean) {
let code = v.string();
if (stdio) {
// we only add the description if we are running in stdio, this saves a few tokens for the remote server
code = v.pipe(
v.string(),
v.description(
"The code to be processed by the autofixer. It can also be a path to a file containing the code. If the file doesn't exists the string will be treated as the code",
),
);
}
return v.object({
code,
desired_svelte_version: v.pipe(
v.union([v.string(), v.number()]),
v.description(
'The desired svelte version...if possible read this from the package.json of the user project, otherwise use some hint from the wording (if the user asks for runes it wants version 5). Default to 5 in case of doubt.',
),
),
async: v.pipe(
v.optional(v.boolean()),
v.description(
'If true the code is an async component/module and might use await in the markup or top-level awaits in the script tag. If possible check the svelte.config.js/svelte.config.ts to check if the option is enabled otherwise asks the user if they prefer using it or not. You can only use this option if the version is 5.',
),
),
filename: v.pipe(
v.optional(v.string()),
v.description(
'The filename of the component if available, it MUST be only the Component name with .svelte or .svelte.ts extension and not the entire path.',
),
),
});
}
const autofixer_output_schema = v.object({
issues: v.array(v.string()),
suggestions: v.array(v.string()),
require_another_tool_call_after_fixing: v.boolean(),
});
export async function svelte_autofixer_handler({
code,
desired_svelte_version: desired_svelte_version_unchecked,
async,
filename: filename_or_path,
}: v.InferInput<ReturnType<typeof get_autofixer_schema>>) {
// we validate manually because some clients don't support union in the input schema (looking at you cursor)
const parsed_version = v.safeParse(
v.union([v.literal(4), v.literal(5), v.literal('4'), v.literal('5')]),
desired_svelte_version_unchecked,
);
if (parsed_version.success === false) {
throw new Error(
`The desired_svelte_version MUST be either 4 or 5 but received "${desired_svelte_version_unchecked}"`,
);
}
const desired_svelte_version = parsed_version.output;
if (async && +desired_svelte_version < 5) {
throw new Error('The async option can only be used with Svelte version 5 or higher.');
}
const content: {
issues: string[];
suggestions: string[];
require_another_tool_call_after_fixing: boolean;
} = { issues: [], suggestions: [], require_another_tool_call_after_fixing: false };
try {
// just in case the LLM sends a full path we extract the filename...it's not really needed
// but it's nice to have a filename in the errors
const filename = filename_or_path ? basename(filename_or_path) : 'Component.svelte';
add_compile_issues(content, code, +desired_svelte_version, filename, async);
add_autofixers_issues(content, code, +desired_svelte_version, filename, async);
await add_eslint_issues(content, code, +desired_svelte_version, filename, async);
} catch (e: unknown) {
const error = e as Error & { start?: { line: number; column: number }; frame?: string };
content.issues.push(
`${error.message} at line ${error.start?.line}, column ${error.start?.column}`,
);
if (error.message.includes('js_parse_error')) {
// Check if the error frame contains template syntax that was incorrectly placed in the script tag
if (error.frame?.includes('{#snippet')) {
content.suggestions.push(
"The code can't be compiled because a Javascript parse error. The error suggests you have a `{#snippet ...}` block inside the `<script>` tag. Snippets are template syntax and should be declared in the markup section of the component, not in the script. Move the snippet outside of the `<script>` tag. Snippets declared in the markup can also be accessed in the script tag in case you need them.",
);
} else {
content.suggestions.push(
"The code can't be compiled because a Javascript parse error. In case you are using runes like this `$state variable_name = 3;` or `$derived variable_name = 3 * count` that's not how runes are used. You need to use them as function calls without importing them: `const variable_name = $state(3)` and `const variable_name = $derived(3 * count)`.",
);
}
} else if (error.message.includes('css_expected_identifier')) {
content.suggestions.push(
"The code can't be compiled because a valid CSS identifier is expected. This sometimes means you are trying to use a variable in CSS like this: `color: {my_color}` but Svelte doesn't support that. You can use inline CSS variables for that `<div style:--color={my_color}></div>` and then use the variable as usual in CSS with `color: var(--color)`.",
);
}
}
if (content.issues.length > 0 || content.suggestions.length > 0) {
content.require_another_tool_call_after_fixing = true;
}
return content;
}
export function svelte_autofixer(server: SvelteMcp) {
server.tool(
@@ -12,75 +124,60 @@ export function svelte_autofixer(server: SvelteMcp) {
title: 'Svelte Autofixer',
description:
'Given a svelte component or module returns a list of suggestions to fix any issues it has. This tool MUST be used whenever the user is asking to write svelte code before sending the code back to the user',
schema: v.object({
code: v.string(),
desired_svelte_version: v.pipe(
v.union([v.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(),
}),
get schema() {
return (
cached_schema ?? (cached_schema = get_autofixer_schema(server.ctx.custom?.stdio ?? false))
);
},
outputSchema: autofixer_output_schema,
annotations: {
title: 'Svelte Autofixer',
destructiveHint: false,
readOnlyHint: true,
openWorldHint: false,
},
icons,
},
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
async ({
code,
filename: filename_or_path,
desired_svelte_version: desired_svelte_version_unchecked,
async,
}) => {
if (server.ctx.sessionId && server.ctx.custom?.track) {
await server.ctx.custom?.track?.(server.ctx.sessionId, 'svelte-autofixer');
}
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)`.",
);
// we only do this if we know we are running in stdio mode (only stdio pass the context as true)
if (server.ctx.custom?.stdio) {
const [exists_sync, read_file] = await Promise.all([
import('node:fs').then((mod) => mod.existsSync),
import('node:fs/promises').then((mod) => mod.readFile),
]);
if (exists_sync(code)) {
code = await read_file(code, 'utf-8');
}
}
if (content.issues.length > 0 || content.suggestions.length > 0) {
content.require_another_tool_call_after_fixing = true;
try {
const content = await svelte_autofixer_handler({
code,
desired_svelte_version: desired_svelte_version_unchecked,
async,
filename: filename_or_path,
});
return tool.structured(content);
} catch (e) {
const error = e as Error;
if (server.ctx.sessionId && server.ctx.custom?.track) {
await server.ctx.custom?.track?.(
server.ctx.sessionId,
'svelte-autofixer-error',
error.message,
);
}
return tool.error(error.message);
}
return {
content: [
{
type: 'text',
text: JSON.stringify(content),
},
],
structuredContent: content,
};
},
);
}

View File

@@ -0,0 +1,14 @@
export const icons = [
{
src: 'https://mcp.svelte.dev/logo.svg',
mimeType: 'image/svg+xml',
},
{
src: 'https://mcp.svelte.dev/logo.png',
mimeType: 'image/png',
},
{
src: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAACvdJREFUeJztXQuQVMUVHT5GCYmSDwaVMhQWmpSRRIEkJqmdFVQCRuRjCiVq8MMGKKTYGESEREAFhI2iSGkCFURCJKASimgpSviVrApEPoKLIGEF0aAIAgLCwsk927NxmZ2duf36zesZnFN1qqCU926fO6/79u3b3bFYAQUUUEABBRRQQAF5DsRjp6Eo1lbYW1gqHCcsE05J/HmYsI+wg/y/Z/i2N+8hQjYQni3sJZwmXC/8SHhciHrI/7ZHuEk4WzhIeL6wse/25BVEsFbCB4UfpxFby0PCZcJOvtuV8xCR2gj/IjwagvCpyK+or3RRp/hua05BRGkqLBFWZkn42jwinCe82He7cwIixLeF5Rn69mxwr/BXvtvvDdL4ZsIJiV9klMIncwYjLN96RAZpcEMOiMJXhMc8i4+EDXPECV/3rU3WIY1kXz9ZWJUDwidzwUk7OEvjGgtvEu4MTbDOXwH6tAFuuhC44QLg6m8C8Qauz53tW6vQIY1qLZwJE48HF+fy04D+PwJmjgVWvQzs2Azsfh/Yswv4+ANg51Zg7TLg7w8CQ7sAXc4I2h39VtjAt27OSPzq+yKMydTgOPDW60BVFdTYtQN4aCDQ8RTb930qbOdbPyeA+Zii2HJn4W9rB6xcqBc9FXZsAe7savvuDcLTfetoDRotHC7c5SQ8+/eHB5suJgx8ug+YcgdQ3MjGjjt862kFMbi9cI3zr/7G7wIVq8IRvjaOH5Px434ZqBtqbdkuzP2oSIw8S/g4XCdU3ZoD86YARw6HL35tJ/BL0Nt1v29964UY10jYDSbBFVz4yxoDv+tsBtkocOgAMOhnWvsqhE19a10HYlQT4QiYiCG4+F1OB2aNBw4fjEb8GqxbrrWRE8af+tb7BIhBLYQrnIRnaFj2GxO/+wJDVJ29E31r/n+IMecKVzqJz0H2pVnS13/mLiKjm+2bhG8DBz6x+7dvrwY6naqxeTPisUa+ta8JMVc59fX81dsKVR8WzQZ6tzZfE3md/PnFJ/X//ugRoO/3dLbHY+f6Fb+4esB9IbD4HPTCGmT/swEo7VT/ux4t1T/rjwO0bejiT3yzOM7JlX3quGszYMYYYO9H7sIzemGep+c56d95RRNg02rdM5+frm3LAJ8OYFXBfmvxB/7EJMzCwNb1QL/2+ndrv4KNr2mfOcKP+MXViycvWwl/zbeAF2YAVUfdhf9kN/DIEPuE2ujrdc9/f5v2mQ/4cUBR7DLYrNXe8n3T1x8/7iY8nff6i8CtFwfL8Zd0AFa+BOzbnf49lRXaZ07w5YD56kbf+J1w4vr9e4CxfW0TZ6nZq6VJPdTniNWLtM+6L3rx47Fz5MWH1Q3lgogL+Kt/9lGgewt34ZNJZ47pA7xbceI7nxitfUZp9A4oit2jMo7xPfv8oGB3RWFG9Q7nV5+OjKCemggc3G/erV8nuC5a8YurQ89ylXFMpLn86v/xmMkJZVP4ZN5eBLyzzuZr+2G0DojHmkO7qBJ0xWrNUjNYRil88perG+D3iR5NonVAUewH0JSO8JP+7JCd8FxEf2KMmaT5Et+O8yMVP+GAa1TGPT7MTvwN5SYZ519UG/b24YABKuOWPK0Xf84koNOXggtxZVNgeDfgr+OApc+YnD7nHOtfkb8/K4PrBDOQc2UtPPHZDTf34YA7VQZufFUn/tY3TY4miAgMcaeOALasybxceazKVEHMnWQmhfr13/r4ZOTiJxzwB5WBzExqwF+tbeMp3sR+wSd3HJuemWy+nGDiH5bBt5UvBwxVGckkmQZ/Gm7XeKavtV9XJux8BxhwaRAHjPMifsIB/VVGsh/WYMFUXaMZGU0baSKlMMGvKN0aQl0yAmzl0wHdVIZqZ8AH9gK/+Eb6Z/VrF176OhWYD2JyT++Enj4dcBE084Cxv9YLwGKra1vWfUaPs8JLX2fCB5XmfToHLPXpgDOFH2Y08qqvfZ5X0aDyLTN3GCLdwZCOJlO5baN7+toGC/6sjY5YaHaBHwfEq3NButKTxXOiEy8s6Auzyrw4oNoJRbFRKiOZz8k3cOKmc8C/4WuTt7z4PGjWA/g587OOCuyu2G2x+2I3xu6M3Rq7Ny3YbbL7zOwA7sxv4cUBCScsVv1SOLBxgMs2OFBzwE41kHLGbFNVzQAic9sYiPjbTywv76r8VIGb2wK7tmdN++r09W2XpLeBoa525rxwprYb+qVPB7SEzY5GTnbCrvfkpIyTM236mpM+DfQFuoN8OmC8WvwaMt3MhFgYeHOFffpamyJnglD3zLt8iX8htIvyyWQCbPooU+EQBFwj5iJ6kGzm7DLdO5hI1D1zlC8HTA0kfg0pHlPCTA3ziziWYYcjU81MOTP13CvFjFlDprz5y9aAyb5c/QJkIsaZ8H+dHJD8RXDH49S7zWIKF1G4mLJW+uElc4HHhppB3H5L6efkYg9rPbXge3XPvj16BxTFrg9N/CgYpPp6cqn2+d19OGCud1E1rKm+tk1fc7EmU5W1ISvCo920zQMr5KUHMxrHkg6WdvgSv/+Pg6evy5/XvodrwmdG6wCzyz2zcT3ONpHEyB7RCh9G+npwsfZ9K1ikFrUDrlUZx7I+gnkVlvvpPungZNkiqx4Yorqkr+k8/Zd7b6TiJxxQqjKOu85rg7tguIiejfrOHi2AZfPMRmsXcDMfj7XRvfeodMctfThAl4Z+Y0nqRnLpj1UQXGJ0FZ5hKUPUoBO62uBchOcK6d8/J3LxEw54QGVgZUX6BtMR3CQRpP6TAzzXb7lJw3Wpkt0VQ1ROCu3s8HPOqNoB3N6jAftt21/99NHh7J6vqb7++VdtxV8obOjLAcNURm5Q1u0wOaZt+F1XA9uUxV6ZwPQ1T9iy7/p4nOX5XsRPOKBEZah22s8to5nKEhlBcQsqt6K6gsFA8OprTrzu4Zq4TwdcrjJ2YolelGm/r/85DGc/fM9deMK9+np+5HF/HQfEqxdhMhvL0wptzvZZPNfkbJiYu+LLZpfK8nnhCM+VuPtucJ2ZLxI28yp+wgE8lmBzRoN50IV2R3oNeE4EY3EesMGDNlzBAz948If7noM34LMUMRlizL0qw8ff4i5iUHDpc2TPMCZ+i4W5deGDGHQptGvBa5UFumGB4SkPeXLf2MfVPk46o93/pQHMcfIbVQ1hvx5G9KIBJ1TclemeheWxlN2RyzdrQLtJg2RE5JqnSQcO9jwxN/hmixqy3nMS8uFMUDGS6wJbVA3j+i8r1bJR4Vz+XFgb+9jXd/CtqxWgzYySHAzphDCiG4KHtvLwVh7i6ib8buHdyIUQ0xZi9KnCdVYNLmlvQk0XrPhn8MqIE/kvYWvfOjoBpj5oj1XDmVDjiScs/dAunrCfZ9WyvnQ8HXkXDe8Uy91BVguYI8sGI8jNRjxCnkfJM8/Do+V5ogrjdy6is4vhmi6PoucAy8QZj6h3E55H5D+V97/6VIA5nthNIKYguCLFzdQspHXZuF2X7wk7w1caOduAuQ/A3Qnhk6HlaOTjIGsLxKtvPpqF3Ll85zXhlSftrz4VYA7tfigHfvWcKH5xrp9KBsxe4h0exOcteOf5bn9OACZEfRrRXMb2rnAgcvE4eZ+AuZitI8yOwmwIz/CXdwZHf2xMvgHmhjzOPt2urDLk4vgjyKWFknyAREoMV9skuou/wVyuzJl0pouYuSWUt3Hw8mbems1jM/P/Pi/f4PXiMAW/TA2w5GVcokspS/y5NCF42y/UpZoFFFBAAQUUUEABJy3+B6BFBuObiHkkAAAAAElFTkSuQmCC',
mimeType: 'image/png',
},
];

View File

@@ -1,14 +1,15 @@
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';
import { icons } from './icons/index.js';
export const server = new McpServer(
{
name: 'Svelte MCP',
version: '0.0.1',
description: 'The official Svelte MCP server implementation',
websiteUrl: 'https://mcp.svelte.dev',
icons,
},
{
adapter: new ValibotJsonSchemaAdapter(),
@@ -21,10 +22,18 @@ export const server = new McpServer(
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> }>();
).withContext<{
track?: (sessionId: string, event: string, extra?: string) => Promise<void>;
stdio?: boolean;
}>();
export type SvelteMcp = typeof server;
setup_tools(server);
setup_resources(server);
setup_prompts(server);
server.on('initialize', async ({ clientInfo: client_info }) => {
if (!server.ctx.custom?.track || !server.ctx.sessionId) return;
server.ctx.custom.track(server.ctx.sessionId, 'initialize', client_info.name);
});

View File

@@ -51,6 +51,6 @@ export async function get_sections() {
export async function format_sections_list() {
const sections = await get_sections();
return sections
.map((s) => `* title: ${s.title}, use_cases: ${s.use_cases}, path: ${s.slug}`)
.map((s) => `- title: ${s.title}, use_cases: ${s.use_cases}, path: ${s.slug}`)
.join('\n');
}

View File

@@ -36,8 +36,18 @@ export function parse(code: string, file_path: string) {
}
return all_scopes;
}
// walking the ast will also walk all the tokens if we don't remove them so we return them separately
// we also remove the parent which as a circular reference to the ast itself (and it's not needed since we use zimmerframe to walk the ast)
const {
ast: { tokens, ...ast },
} = parsed;
// @ts-expect-error we also have to delete it from the object or the circular reference remains
delete parsed.ast.tokens;
return {
ast: parsed.ast,
ast,
tokens,
scope_manager: parsed.scopeManager as ScopeManager,
visitor_keys: parsed.visitorKeys,
get all_scopes() {
@@ -59,6 +69,8 @@ export function parse(code: string, file_path: string) {
return this.all_references.find((r) => r.identifier === id);
},
is_rune(call: CallExpression, rune?: (typeof runes)[number][]) {
// it should always be a call expression but we check just in case because sometimes it's any and TS doesn't complain
if (call.type !== 'CallExpression') return false;
if (call.callee.type !== 'Identifier' && call.callee.type !== 'MemberExpression')
return false;
const id = call.callee.type === 'Identifier' ? call.callee : call.callee.object;

View File

@@ -1,7 +1,197 @@
# @sveltejs/mcp
## 0.1.23
### Patch Changes
- feat: allow stdio mcp to read the content of the file directly ([#198](https://github.com/sveltejs/ai-tools/pull/198))
- fix: handle non call expressions passed to `is_rune` ([#201](https://github.com/sveltejs/ai-tools/pull/201))
- chore: remove db requirement ([#196](https://github.com/sveltejs/ai-tools/pull/196))
## 0.1.22
### Patch Changes
- fix: broaden checks for imported runes because LLMs are unhinged ([#185](https://github.com/sveltejs/ai-tools/pull/185))
## 0.1.21
### Patch Changes
- feat: display similar result & error at the end ([#161](https://github.com/sveltejs/ai-tools/pull/161))
## 0.1.20
### Patch Changes
- fix: turn off no-inspect in eslint for mcp ([`2245cb2`](https://github.com/sveltejs/ai-tools/commit/2245cb2dc9e2d217869b6a800795ce59ffb40c51))
## 0.1.19
### Patch Changes
- chore: update svelte ([`7447744`](https://github.com/sveltejs/ai-tools/commit/74477448cea44ec21684ea4d39f2c5c7133b5150))
## 0.1.18
### Patch Changes
- feat: expose playground link as MCP App ([#138](https://github.com/sveltejs/ai-tools/pull/138))
## 0.1.17
### Patch Changes
- fix: add suggestion for snippets declared in script tag ([#132](https://github.com/sveltejs/ai-tools/pull/132))
## 0.1.16
### Patch Changes
- feat: expose tools as JS api + cli ([#128](https://github.com/sveltejs/ai-tools/pull/128))
## 0.1.15
### Patch Changes
- fix: server.json version + update publisher ([`9dfb4de`](https://github.com/sveltejs/ai-tools/commit/9dfb4dedb42837c40c4e660f0f816d7cf9081fc4))
## 0.1.14
### Patch Changes
- fix: improve prompt to reduce token usage ([#124](https://github.com/sveltejs/ai-tools/pull/124))
## 0.1.13
### Patch Changes
- fix: revert name change and add title ([`98efa1e`](https://github.com/sveltejs/ai-tools/commit/98efa1e09ebcca7827b10dc6bc8e1699fc1e5171))
## 0.1.12
### Patch Changes
- fix: update server name on mcp registry ([`60297b3`](https://github.com/sveltejs/ai-tools/commit/60297b3c49bf110b48908e61b5d5d902ea1bdf39))
- chore: update tmcp ([#99](https://github.com/sveltejs/ai-tools/pull/99))
## 0.1.11
### Patch Changes
- fix: add `async` parameter to `svelte-autofixer` ([#94](https://github.com/sveltejs/ai-tools/pull/94))
- fix: install latest eslint svelte packages to support `$state.eager` ([`f6ce89f`](https://github.com/sveltejs/ai-tools/commit/f6ce89ff34faabc3d746a350ea347298ecfed2ec))
## 0.1.10
### Patch Changes
- fix: add icons to `server.json` ([`02c951b`](https://github.com/sveltejs/ai-tools/commit/02c951baa86ac8103ffc158a202c06cfe6b15c01))
- fix: add `preferred-frame-size` to UI resource ([`3fabcc0`](https://github.com/sveltejs/ai-tools/commit/3fabcc0f9bfee916c0deb9c2ffa931ed2168af2d))
- feat: support: `$state.eager` ([#90](https://github.com/sveltejs/ai-tools/pull/90))
## 0.1.9
### Patch Changes
- feat: return `mcp-ui` resource from `playground-link` ([#84](https://github.com/sveltejs/ai-tools/pull/84))
- feat: suggest against js variables in css ([#78](https://github.com/sveltejs/ai-tools/pull/78))
## 0.1.8
### Patch Changes
- fix: upgrade registry publisher cli ([`5fa2baa`](https://github.com/sveltejs/ai-tools/commit/5fa2baa27009f01e0e4e91cee7984b81a81c1c29))
## 0.1.7
### Patch Changes
- fix: use correct server schema version ([`579be87`](https://github.com/sveltejs/ai-tools/commit/579be877fa9f87f7f173450ca5bc918824d68282))
## 0.1.6
### Patch Changes
- fix: prevent `imported_runes` suggestion from being added for libs that are not svelte ([`87af64f`](https://github.com/sveltejs/ai-tools/commit/87af64f4bc6d07b75640eb987a33655654363997))
- feat: add svelte icon and website url for mcp server ([#75](https://github.com/sveltejs/ai-tools/pull/75))
- fix: use `data:` uri for local icon & add icons to tools + resources + prompts ([`cf62286`](https://github.com/sveltejs/ai-tools/commit/cf622869129382a97ad059bb1389f115907adc8e))
## 0.1.5
### Patch Changes
- fix: widen `desired_svelte_version` validation to accommodate some clients ([`3b301d7`](https://github.com/sveltejs/ai-tools/commit/3b301d7d9c2f49758023408f505bc4ca79caaff4))
- fix: minor tweaks to the prompt to allow for automatic sync ([#63](https://github.com/sveltejs/ai-tools/pull/63))
- feat: `read_state_with_dollar` autofixer ([#66](https://github.com/sveltejs/ai-tools/pull/66))
## 0.1.4
### Patch Changes
- fix: pass secrets in action and update `mcpName` ([`044f098`](https://github.com/sveltejs/ai-tools/commit/044f0988b935fff39911a861a648dfb276f5831a))
## 0.1.3
### Patch Changes
- fix: use DNS to publish MCP ([#59](https://github.com/sveltejs/ai-tools/pull/59))
## 0.1.2
### Patch Changes
- fix: publish to MCP registry (I really hope this time for real) ([`ef5241c`](https://github.com/sveltejs/ai-tools/commit/ef5241cbc204ad8bb84bde27db7c9d0a08280245))
## 0.1.1
### Patch Changes
- feat: publish mcp to registry (maybe for real this time) ([`132943d`](https://github.com/sveltejs/ai-tools/commit/132943db3b04dbbd322d08926c0880c990a61f5f))
## 0.1.0
### Minor Changes
- feat: publish to registry ([#45](https://github.com/sveltejs/ai-tools/pull/45))
### Patch Changes
- feat: add autofixer to tell the LLM to check if some function called in effect is assigning state #26 ([`73d7625`](https://github.com/sveltejs/ai-tools/commit/73d7625b3ca6a812ba91883ea668d80ff1e7c703))
- feat: add bind:this -> attachment and action -> attachment autofixer #20 ([`73d7625`](https://github.com/sveltejs/ai-tools/commit/73d7625b3ca6a812ba91883ea668d80ff1e7c703))
## 0.0.4
### Patch Changes
- fix: allow TS `.svelte.ts` modules ([#49](https://github.com/sveltejs/ai-tools/pull/49))
## 0.0.3
### Patch Changes
- fix: check effect.pre in assign-in-effect ([#41](https://github.com/sveltejs/ai-tools/pull/41))
- feat: `use_cases` documentation metadata ([#29](https://github.com/sveltejs/ai-tools/pull/29))
- fix: change title names to allow for claude code to use the prompt ([`725f785`](https://github.com/sveltejs/ai-tools/commit/725f785766d04e9ed810a7c3f6bcfdb2e2b8234c))
- fix: enable doc tools ([`cb316c5`](https://github.com/sveltejs/ai-tools/commit/cb316c5b3ebc712946969d2d57236d159e796d58))
## 0.0.2
### Patch Changes
- feat: latest version ([#25](https://github.com/sveltejs/mcp/pull/25))
- feat: latest version ([#25](https://github.com/sveltejs/ai-tools/pull/25))

View File

@@ -0,0 +1,25 @@
eb5f89b76fc45a97070fa481eb03977584a78e3dff2781402d43482f114e4d6a mcp-publisher_darwin_amd64.tar.gz
29fc1b46a6f6be2580129369a9b681204b11b425d9a44147d79c8c658c7b8474 mcp-publisher_darwin_amd64.tar.gz.sbom.json
9eddbbb95efd54b9503f6c0668f43bab3f04c856946d3c7164f6daead232402f mcp-publisher_darwin_arm64.tar.gz
a8349b0ea7916f34cf4ee4e1ced5b91bc1ded6d100312cbb2095da018710da04 mcp-publisher_darwin_arm64.tar.gz.sbom.json
c4b402b43a85166c3f840641ca1c9e6de5bfa1cf533c22576d663ccbda0711bb mcp-publisher_linux_amd64.tar.gz
6f21cf917055be885f16b93154e06379540236599cfad6af4404a8323bde74b7 mcp-publisher_linux_amd64.tar.gz.sbom.json
ba5d486f86b2cef48ea506e8314d901a5169dcd56a5d6e9daf18d41244316235 mcp-publisher_linux_arm64.tar.gz
8a0096a407b916ac7270732df017a26f6112c73a066252f6556b8956c492a0b4 mcp-publisher_linux_arm64.tar.gz.sbom.json
59ee8c4a997f94794db8db13f8809666631686a70a8d89a9f0fea993f9aede0f mcp-publisher_windows_amd64.tar.gz
57d211fd9181f698d126bd773b55c98b92454d19b1e32e77860766179a8a2e8e mcp-publisher_windows_amd64.tar.gz.sbom.json
1410952b0a5968cbe89590e7b4ee6105147ef7267cf0cd50095c9bec2ee3b0d7 mcp-publisher_windows_arm64.tar.gz
6cb93e118a89ed1419135bfbaa7401bd3b7a7c5680a0d8fd7c78728f9d860630 mcp-publisher_windows_arm64.tar.gz.sbom.json
ebc17c3b7a5b86f9c036acf1d44fb904bb363bad0ac1ac37b7979eb17cf3d218 registry-1.4.0.tar.gz
5fffe8b078513fa5fbb625a213d164bb391c7c85e216e541cab789517bc6365b registry_darwin_amd64.tar.gz
10a61cf4173d8b5be63044af0a10e6c809eebc1006c0c1643753a252db808ddd registry_darwin_amd64.tar.gz.sbom.json
437746a1045f093266ad7298a47be41dc44cc33ecaeec145449c4eefeddf8880 registry_darwin_arm64.tar.gz
4c0cb24bcef0658540fb044d771294efd662edecd2f7fae7b1ca7ca2ae68f83a registry_darwin_arm64.tar.gz.sbom.json
bd77fafcc881714a63a375a9bdb53a761a2b8e367a9d2759835126c993df2356 registry_linux_amd64.tar.gz
d62a461b174089fbcaa4f1ac096bca491d18bc7f70e93ce0824fe89dbe42e974 registry_linux_amd64.tar.gz.sbom.json
38cb9e6112ff11544ba8ec88c5c0f44d4c851504ec2e33d497e25616a6f7a21e registry_linux_arm64.tar.gz
84e5a004929e7231ae35350a1fe8fb08668fc05183e13d1d78004abf08a25f3b registry_linux_arm64.tar.gz.sbom.json
77ca9243d2f744f282b39d07625f802d77f593850a0392debabe407643a8579c registry_windows_amd64.tar.gz
bb043e8a6a8d187ffab8987d36d0018024115d9217af6d2ddd233f94aea880ea registry_windows_amd64.tar.gz.sbom.json
e4ee50e05a95f288b874f5e24c7716a5032548feeabc83b715193298cec06890 registry_windows_arm64.tar.gz
49f189b615bce3d09ea24ae5d80e0816ac69225b3c8901dd7be19ef9ca06830c registry_windows_arm64.tar.gz.sbom.json

View File

@@ -1,23 +1,30 @@
{
"name": "@sveltejs/mcp",
"version": "0.0.2",
"version": "0.1.23",
"type": "module",
"license": "MIT",
"homepage": "https://github.com/sveltejs/mcp#readme",
"mcpName": "dev.svelte/mcp",
"homepage": "https://github.com/sveltejs/ai-tools#readme",
"bugs": {
"url": "https://github.com/sveltejs/mcp/issues"
"url": "https://github.com/sveltejs/ai-tools/issues"
},
"bin": {
"svelte-mcp": "./dist/index.js"
"svelte-mcp": "./dist/index.mjs"
},
"repository": {
"type": "git",
"url": "git+https://github.com/sveltejs/mcp.git",
"url": "git+https://github.com/sveltejs/ai-tools.git",
"path": "packages/mcp-stdio"
},
"files": [
"dist"
],
"exports": {
".": {
"types": "./dist/handlers.d.mts",
"import": "./dist/handlers.mjs"
}
},
"publishConfig": {
"access": "public"
},
@@ -26,18 +33,21 @@
"dev": "tsdown --watch",
"test": "vitest",
"check": "tsc --noEmit",
"check:publint": "publint --strict"
"check:publint": "publint --strict",
"update:version": "node scripts/update-version.ts"
},
"devDependencies": {
"@sveltejs/mcp-server": "workspace:^",
"@tmcp/transport-stdio": "^0.3.1",
"@types/node": "^22.15.17",
"publint": "^0.3.13",
"tsdown": "^0.11.9",
"typescript": "^5.8.3",
"vitest": "^3.1.3"
"@tmcp/transport-stdio": "catalog:tmcp",
"@types/node": "catalog:tooling",
"publint": "catalog:tooling",
"tsdown": "catalog:tooling",
"typescript": "catalog:tooling",
"vitest": "catalog:tooling"
},
"dependencies": {
"eslint": "^9.36.0"
"eslint": "catalog:lint",
"sade": "catalog:tooling",
"tmcp": "catalog:tmcp"
}
}

View File

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

View File

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

View File

@@ -0,0 +1,6 @@
export {
list_sections_handler as listSections,
get_documentation_handler as getDocumentation,
svelte_autofixer_handler as svelteAutofixer,
playground_link_handler as playgroundLink,
} from '@sveltejs/mcp-server/handlers';

View File

@@ -1,7 +1,67 @@
#! /usr/bin/env node
import { server } from '@sveltejs/mcp-server';
import {
list_sections_handler,
get_documentation_handler,
svelte_autofixer_handler,
} from '@sveltejs/mcp-server/handlers';
import { readFile } from 'node:fs/promises';
import { existsSync } from 'node:fs';
import { StdioTransport } from '@tmcp/transport-stdio';
import sade from 'sade';
const transport = new StdioTransport(server);
const cli = sade('svelte-mcp');
transport.listen();
cli.command('__mcp', '', { default: true }).action(() => {
const transport = new StdioTransport(server);
transport.listen({
stdio: true,
});
});
cli
.command('list-sections')
.describe('List all the available documentation sections')
.action(async () => {
console.log(await list_sections_handler());
});
cli
.command('get-documentation <sections>')
.describe('Get documentation for specified sections, separated by commas')
.action(async (sections) => {
console.log(await get_documentation_handler({ section: sections.split(',') }));
});
cli
.command('svelte-autofixer <code_or_path>')
.describe(
'Detect and suggest fixes for Svelte code issues, because the terminal will substitute variables `$` should be correctly escaped',
)
.option('--async', 'Wether the project is using async svelte or not', false)
.option('--svelte-version', 'Which version of svelte to use...it can be 4 or 5', 5)
.action(async (code_or_path, { async, 'svelte-version': version }) => {
let code = code_or_path;
let is_path = false;
if (existsSync(code_or_path)) {
console.log('Detected file path, reading file...');
code = await readFile(code_or_path, 'utf-8');
is_path = true;
} else {
console.log('File not found, treating input as code...');
}
const desired_svelte_version = +version;
const result = await svelte_autofixer_handler({
code,
async: Boolean(async),
desired_svelte_version,
filename: is_path ? code_or_path : undefined,
});
console.log(result);
});
cli.parse(process.argv);

View File

@@ -2,7 +2,7 @@ import { defineConfig } from 'tsdown';
export default defineConfig([
{
entry: ['./src/index.ts'],
entry: ['./src/index.ts', './src/handlers.ts'],
platform: 'node',
define: {
// some eslint-plugin-svelte code expects __filename to exists but in an ESM environment it does not.
@@ -13,9 +13,12 @@ export default defineConfig([
// the require would fail once executed in a project without eslint installed.
external: ['eslint'],
publint: true,
dts: false,
dts: {
eager: true,
},
treeshake: true,
clean: true,
target: 'esnext',
inlineOnly: false,
},
]);

View File

@@ -0,0 +1,73 @@
# @sveltejs/opencode
## 0.1.8
### Patch Changes
- fix: add `server` export to opencode plugin ([`96c50ac`](https://github.com/sveltejs/ai-tools/commit/96c50acae2b4131a6c72d3579a73c44ab9158b18))
## 0.1.7
### Patch Changes
- fix: import `ts` files directly ([#190](https://github.com/sveltejs/ai-tools/pull/190))
## 0.1.6
### Patch Changes
- chore: sync skills from svelte.dev ([#178](https://github.com/sveltejs/ai-tools/pull/178))
- fix: update svelte-file-editor agent to use proper name ([#183](https://github.com/sveltejs/ai-tools/pull/183))
## 0.1.5
### Patch Changes
- fix: merge user-configured svelte-file-editor agent settings ([#176](https://github.com/sveltejs/ai-tools/pull/176))
- feat(opencode): mcp enabled option is passed to opencode ([#171](https://github.com/sveltejs/ai-tools/pull/171))
- feat: allow enabling a specific skill in opencode plugin ([#174](https://github.com/sveltejs/ai-tools/pull/174))
## 0.1.4
### Patch Changes
- feat: `svelte-core-bestpractices` skill ([#162](https://github.com/sveltejs/ai-tools/pull/162))
## 0.1.3
### Patch Changes
- fix: better subagent instructions to use MCP or skill ([#163](https://github.com/sveltejs/ai-tools/pull/163))
## 0.1.2
### Patch Changes
- feat: allow for local opencode config ([#156](https://github.com/sveltejs/ai-tools/pull/156))
## 0.1.1
### Patch Changes
- fix: actually push skills to right config path ([`c2c1b3e`](https://github.com/sveltejs/ai-tools/commit/c2c1b3e5e788b14eea17cd37a83ca55433cc4072))
## 0.1.0
### Minor Changes
- feat: distribute skills through opencode plugin ([#151](https://github.com/sveltejs/ai-tools/pull/151))
## 0.0.3
### Patch Changes
- chore: add README to opencode plugin ([`71295bc`](https://github.com/sveltejs/ai-tools/commit/71295bc11fb7bac6703e655f5fddead29967353c))
## 0.0.2
### Patch Changes
- fix: show toast after a few seconds to avoid race condition ([`57e2d1d`](https://github.com/sveltejs/ai-tools/commit/57e2d1def1f5590d0a3dd6d269ac39f6397ffecf))

112
packages/opencode/README.md Normal file
View File

@@ -0,0 +1,112 @@
# @sveltejs/opencode
OpenCode plugin for Svelte that provides the Svelte MCP server, a specialized file editor subagent and instruction files.
## Installation
Add `@sveltejs/opencode` to your OpenCode config (either global or local):
```json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["@sveltejs/opencode"]
}
```
That's it! You now have the Svelte MCP server and the file editor subagent configured automatically.
## Features
### Svelte MCP Server
The plugin automatically configures the [Svelte MCP server](https://mcp.svelte.dev) which provides:
- **list-sections** - Discover available Svelte 5 and SvelteKit documentation sections
- **get-documentation** - Retrieve full documentation content for specific sections
- **svelte-autofixer** - Analyze Svelte code and get issues/suggestions
- **playground-link** - Generate Svelte Playground links with provided code
### Svelte File Editor Subagent
A specialized subagent (`svelte-file-editor`) that is automatically used when creating, editing, or reviewing `.svelte`, `.svelte.ts`, or `.svelte.js` files. It fetches relevant documentation and validates code using the Svelte MCP server tools.
### Agent Instructions
The plugin injects instructions that teach the agent how to effectively use the Svelte MCP tools.
## Configuration
Create `svelte.json` to customize how the plugin configures MCP, the Svelte subagent, instructions, and skills.
```json
{
"$schema": "https://svelte.dev/opencode/schema.json",
"mcp": {
"type": "remote",
"enabled": true
},
"subagent": {
"enabled": true,
"agents": {
"svelte-file-editor": {
"model": "anthropic/claude-sonnet-4-20250514",
"temperature": 0.7,
"top_p": 0.9,
"maxSteps": 20
}
}
},
"instructions": {
"enabled": true
},
"skills": {
"enabled": ["svelte-code-writer", "svelte-core-bestpractices"]
}
}
```
### Defaults
If omitted, the plugin uses these defaults:
- `mcp.type`: `"remote"`
- `mcp.enabled`: `true`
- `subagent.enabled`: `true`
- `subagent.agents`: `{}`
- `instructions.enabled`: `true`
- `skills.enabled`: `true`
### Configuration Options
| Option | Type | Default | Description |
| ------------------------------------------------ | --------------------- | ---------- | ---------------------------------------------------------------------------------------------- |
| `mcp.type` | `"remote" \| "local"` | `"remote"` | Use `https://mcp.svelte.dev/mcp` (`remote`) or run `@sveltejs/mcp` via `npx` (`local`). |
| `mcp.enabled` | `boolean` | `true` | Enable or disable the Svelte MCP server entry. |
| `subagent.enabled` | `boolean` | `true` | Enable or disable registration of the `svelte-file-editor` subagent. |
| `subagent.agents.svelte-file-editor.model` | `string` | main agent | Override the model used by the Svelte file editor subagent. |
| `subagent.agents.svelte-file-editor.temperature` | `number` | unset | Set temperature for the subagent. |
| `subagent.agents.svelte-file-editor.top_p` | `number` | unset | Set top-p sampling for the subagent. |
| `subagent.agents.svelte-file-editor.maxSteps` | `number` | unlimited | Limit the number of steps the subagent can execute. |
| `instructions.enabled` | `boolean` | `true` | Enable or disable automatic instruction-file injection. |
| `skills.enabled` | `boolean \| string[]` | `true` | Enable all skills (`true`), disable all skills (`false`), or enable only specific skill names. |
### Supported Skill Names
When using `skills.enabled` as an array, these built-in names are currently available:
- `svelte-code-writer`
- `svelte-core-bestpractices`
### Config File Locations and Precedence
The plugin reads from these files (lowest priority first, highest priority last):
- `~/.config/opencode/svelte.json`
- `$OPENCODE_CONFIG_DIR/svelte.json` (when `OPENCODE_CONFIG_DIR` is set)
- `.opencode/svelte.json` in the current project
If the same key is defined in multiple files, the later location overrides earlier ones.
## License
MIT

View File

@@ -0,0 +1,11 @@
// This file is auto-generated by scripts/sync-opencode-plugin.ts
// Do not edit manually — edit the markdown files in tools/agents/ instead.
export const agents = {
'svelte-file-editor': {
description:
'Specialized Svelte 5 code editor. MUST BE USED PROACTIVELY when creating, editing, or reviewing any .svelte file or .svelte.ts/.svelte.js module and MUST use the tools from the MCP server or the `svelte-file-editor` skill if they are available. Fetches relevant documentation and validates code using the Svelte MCP server tools.',
prompt:
"You are a Svelte 5 expert responsible for writing, editing, and validating Svelte components and modules. You have access to the Svelte MCP server which provides documentation and code analysis tools. Always use the tools from the svelte MCP server to fetch documentation with `get_documentation` and validating the code with `svelte_autofixer`. If the autofixer returns any issue or suggestions try to solve them.\n\nIf the MCP tools are not available you can use the `svelte-code-writer` skill to learn how to use the `@sveltejs/mcp` cli to access the same tools.\n\nIf the skill is not available you can run `npx @sveltejs/mcp@latest -y --help` to learn how to use it.\n\n## Available MCP Tools\n\n### 1. list-sections\n\nLists all available Svelte 5 and SvelteKit documentation sections with titles and paths. Use this first to discover what documentation is available.\n\n### 2. get-documentation\n\nRetrieves full documentation for specified sections. Accepts a single section name or an array of section names. Use after `list-sections` to fetch relevant docs for the task at hand.\n\n**Example sections:** `$state`, `$derived`, `$effect`, `$props`, `$bindable`, `snippets`, `routing`, `load functions`\n\n### 3. svelte-autofixer\n\nAnalyzes Svelte code and returns suggestions to fix issues. Pass the component code directly to this tool. It will detect common mistakes like:\n\n- Using `$effect` instead of `$derived` for computations\n- Missing cleanup in effects\n- Svelte 4 syntax (`on:click`, `export let`, `<slot>`)\n- Missing keys in `{#each}` blocks\n- And more\n\n## Workflow\n\nWhen invoked to work on a Svelte file:\n\n### 1. Gather Context (if needed)\n\nIf you're uncertain about Svelte 5 syntax or patterns, use the MCP tools:\n\n1. Call `list-sections` to see available documentation\n2. Call `get-documentation` with relevant section names\n\n### 2. Read the Target File\n\nRead the file to understand the current implementation.\n\n### 3. Make Changes\n\nApply edits following Svelte 5 best practices:\n\n### 4. Validate Changes\n\nAfter editing, ALWAYS call `svelte-autofixer` with the updated code to check for issues.\n\n### 5. Fix Any Issues\n\nIf the autofixer reports problems, fix them and re-validate until no issues remain.\n\n## Output Format\n\nAfter completing your work, provide:\n\n1. Summary of changes made\n2. Any issues found and fixed by the autofixer\n3. Recommendations for further improvements (if any)",
},
} as const;

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