mirror of
https://github.com/sveltejs/ai-tools.git
synced 2026-07-04 03:19:38 +08:00
Compare commits
101 Commits
@sveltejs/
...
@sveltejs/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cdfbb907b6 | ||
|
|
2eb2b18008 | ||
|
|
e8554f3d8f | ||
|
|
0213bd951f | ||
|
|
2245cb2dc9 | ||
|
|
2a5f7d6314 | ||
|
|
8518e627d3 | ||
|
|
9e824da9e2 | ||
|
|
b38f2c11da | ||
|
|
ea37c7120c | ||
|
|
099e939f79 | ||
|
|
69b36eefdc | ||
|
|
15ad554f53 | ||
|
|
74477448ce | ||
|
|
6f538265d1 | ||
|
|
71295bc11f | ||
|
|
b5040ff5cf | ||
|
|
45c961417f | ||
|
|
57e2d1def1 | ||
|
|
398a703580 | ||
|
|
75c802a115 | ||
|
|
53a634cdb0 | ||
|
|
bcdc33e7a5 | ||
|
|
825ae33427 | ||
|
|
ccf940cc45 | ||
|
|
b2f195fb7b | ||
|
|
d8e4b18bff | ||
|
|
6a2198b433 | ||
|
|
2ce60c6110 | ||
|
|
0f8987fdcf | ||
|
|
42911e2631 | ||
|
|
655eb85eba | ||
|
|
89403a7d0c | ||
|
|
3747623d55 | ||
|
|
c1f230455f | ||
|
|
9dfb4dedb4 | ||
|
|
b891e4860b | ||
|
|
c9e8508dd9 | ||
|
|
9896406aff | ||
|
|
b01ae9069b | ||
|
|
b6db495242 | ||
|
|
3926310107 | ||
|
|
848627549f | ||
|
|
283164ca7e | ||
|
|
c6ee414f62 | ||
|
|
ea5bdf66dc | ||
|
|
7d707202d1 | ||
|
|
f0daadfbd0 | ||
|
|
15a7774da7 | ||
|
|
98efa1e09e | ||
|
|
e20cf2974d | ||
|
|
b2ee968a3f | ||
|
|
60297b3c49 | ||
|
|
39076da8ce | ||
|
|
fba733646a | ||
|
|
7fcd4705a5 | ||
|
|
af7d341ba5 | ||
|
|
52546551ff | ||
|
|
1f0a5f1519 | ||
|
|
0bf04bad2e | ||
|
|
f001918925 | ||
|
|
67487c324a | ||
|
|
5beeef5543 | ||
|
|
e1a03fdb85 | ||
|
|
384c1fd209 | ||
|
|
b3027fd815 | ||
|
|
849bf2ad49 | ||
|
|
314538c8e7 | ||
|
|
a9994310c0 | ||
|
|
9fb1a403b7 | ||
|
|
3c7b5033a4 | ||
|
|
b911a00bb7 | ||
|
|
f6ce89ff34 | ||
|
|
846514858e | ||
|
|
b69ea052bd | ||
|
|
e56159dda6 | ||
|
|
1e83c35faa | ||
|
|
31edfe1b5f | ||
|
|
3fabcc0f9b | ||
|
|
deb5f2670c | ||
|
|
02c951baa8 | ||
|
|
41ceb83838 | ||
|
|
3c3a26f031 | ||
|
|
6589c7e250 | ||
|
|
e09b8cd0b9 | ||
|
|
7f52a2b1be | ||
|
|
4eecd75759 | ||
|
|
9015753f77 | ||
|
|
4d6a9cb333 | ||
|
|
60aa30397f | ||
|
|
17ed3a3e23 | ||
|
|
f49bd06fbd | ||
|
|
b98c042ae3 | ||
|
|
917a93d3fd | ||
|
|
371e96befc | ||
|
|
bdfd5a109f | ||
|
|
1c6c0a9fa7 | ||
|
|
a321244543 | ||
|
|
ed25933466 | ||
|
|
e639e3ad5c | ||
|
|
d0bed3e8f0 |
@@ -7,5 +7,5 @@
|
|||||||
"access": "public",
|
"access": "public",
|
||||||
"baseBranch": "main",
|
"baseBranch": "main",
|
||||||
"updateInternalDependencies": "patch",
|
"updateInternalDependencies": "patch",
|
||||||
"ignore": ["!@sveltejs/mcp"]
|
"ignore": ["!@sveltejs/mcp", "!@sveltejs/opencode"]
|
||||||
}
|
}
|
||||||
|
|||||||
22
.claude-plugin/marketplace.json
Normal file
22
.claude-plugin/marketplace.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "svelte",
|
||||||
|
"owner": {
|
||||||
|
"name": "Svelte"
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "svelte",
|
||||||
|
"source": "./plugins/svelte",
|
||||||
|
"description": "A plugin for all things Svelte development, MCP, skills, and more.",
|
||||||
|
"lspServers": {
|
||||||
|
"svelte": {
|
||||||
|
"command": "svelte-language-server",
|
||||||
|
"args": ["--stdio"],
|
||||||
|
"extensionToLanguage": {
|
||||||
|
".svelte": "svelte"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
open_collective: svelte
|
||||||
8
.github/workflows/check.yml
vendored
8
.github/workflows/check.yml
vendored
@@ -13,17 +13,17 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: 10.18.3
|
version: 10.28.2
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version: '22'
|
node-version: '24'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
|||||||
8
.github/workflows/lint.yml
vendored
8
.github/workflows/lint.yml
vendored
@@ -13,17 +13,17 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: 10.18.3
|
version: 10.28.2
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version: '22'
|
node-version: '24'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
|||||||
23
.github/workflows/publish-any-commit.yml
vendored
Normal file
23
.github/workflows/publish-any-commit.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
name: Publish Any Commit
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- run: corepack enable
|
||||||
|
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
|
with:
|
||||||
|
node-version: 24
|
||||||
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install
|
||||||
|
|
||||||
|
- run: pnpm dlx pkg-pr-new publish --compact './packages/mcp-stdio' './packages/opencode' --pnpm
|
||||||
11
.github/workflows/publish-mcp.yml
vendored
11
.github/workflows/publish-mcp.yml
vendored
@@ -5,6 +5,7 @@ on:
|
|||||||
secrets:
|
secrets:
|
||||||
MCP_KEY:
|
MCP_KEY:
|
||||||
required: true
|
required: true
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish-mcp:
|
publish-mcp:
|
||||||
@@ -12,19 +13,19 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: checkout
|
- name: checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Publish to MCP Registry
|
- name: Publish to MCP Registry
|
||||||
working-directory: packages/mcp-stdio
|
working-directory: packages/mcp-stdio
|
||||||
env:
|
env:
|
||||||
MCP_KEY: ${{ secrets.MCP_KEY }}
|
MCP_KEY: ${{ secrets.MCP_KEY }}
|
||||||
run: |
|
run: |
|
||||||
NAME=mcp-publisher_1.3.3_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz
|
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.3.3 using latest https for security and save it to a file named mcp-publisher.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.3.3/$NAME" -O
|
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
|
# Verify the SHA256 checksum of the downloaded file
|
||||||
sha256sum --ignore-missing -c ./checksums/registry_1.3.3_checksums.txt
|
sha256sum --ignore-missing -c ./checksums/registry_1.4.0_checksums.txt
|
||||||
|
|
||||||
# Extract the tarball
|
# Extract the tarball
|
||||||
mkdir tmp
|
mkdir tmp
|
||||||
|
|||||||
45
.github/workflows/release-svelte-skill.yml
vendored
Normal file
45
.github/workflows/release-svelte-skill.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
name: Release Svelte Code Writer Skill
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- 'plugins/svelte/skills/svelte-code-writer/**'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
# prevents this action from running on forks
|
||||||
|
if: github.repository == 'sveltejs/mcp'
|
||||||
|
name: Release Svelte Code Writer Skill
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: checkout
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Get version from date
|
||||||
|
id: version
|
||||||
|
run: echo "version=$(date +'%Y.%m.%d-%H%M%S')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Create zip
|
||||||
|
run: |
|
||||||
|
cd plugins/svelte/skills
|
||||||
|
zip -r svelte-code-writer.zip svelte-code-writer/
|
||||||
|
|
||||||
|
- name: Create Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
tag_name: svelte-code-writer-v${{ steps.version.outputs.version }}
|
||||||
|
name: Svelte Code Writer Skill v${{ steps.version.outputs.version }}
|
||||||
|
body: |
|
||||||
|
Automated release of the Svelte Code Writer skill.
|
||||||
|
|
||||||
|
This release was triggered by changes to the `plugins/svelte/skills/svelte-code-writer/` directory.
|
||||||
|
files: plugins/svelte/skills/svelte-code-writer.zip
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
9
.github/workflows/release.yml
vendored
9
.github/workflows/release.yml
vendored
@@ -25,11 +25,11 @@ jobs:
|
|||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
steps:
|
steps:
|
||||||
- name: checkout
|
- name: checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
# This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
|
# This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: actions/setup-node@v6
|
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node }}
|
node-version: ${{ matrix.node }}
|
||||||
package-manager-cache: false # pnpm is not installed yet
|
package-manager-cache: false # pnpm is not installed yet
|
||||||
@@ -39,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)
|
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
|
echo installing pnpm version $PNPM_VER
|
||||||
npm i -g pnpm@$PNPM_VER
|
npm i -g pnpm@$PNPM_VER
|
||||||
- uses: actions/setup-node@v6
|
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node }}
|
node-version: ${{ matrix.node }}
|
||||||
package-manager-cache: true # caches pnpm via packageManager field in package.json
|
package-manager-cache: true # caches pnpm via packageManager field in package.json
|
||||||
|
cache: 'pnpm'
|
||||||
- name: install
|
- name: install
|
||||||
run: pnpm install --frozen-lockfile --prefer-offline --ignore-scripts
|
run: pnpm install --frozen-lockfile --prefer-offline --ignore-scripts
|
||||||
- name: build
|
- name: build
|
||||||
@@ -51,7 +52,7 @@ jobs:
|
|||||||
- name: Create Release Pull Request or Publish to npm
|
- name: Create Release Pull Request or Publish to npm
|
||||||
id: changesets
|
id: changesets
|
||||||
# pinned for security, always review third party action code before updating
|
# 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:
|
with:
|
||||||
# This expects you to have a script called changeset:version version that calls changeset version and updated what it needs to be updated
|
# 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
|
version: pnpm changeset:version
|
||||||
|
|||||||
84
.github/workflows/sync-opencode-skills.yml
vendored
Normal file
84
.github/workflows/sync-opencode-skills.yml
vendored
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
name: Sync OpenCode Skills
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- 'plugins/svelte/skills/**'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
sync-skills:
|
||||||
|
# prevents this action from running on forks
|
||||||
|
if: github.repository == 'sveltejs/mcp'
|
||||||
|
name: Sync Skills to OpenCode Package and Update Docs
|
||||||
|
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 skills folder
|
||||||
|
run: pnpm sync-opencode-skills
|
||||||
|
|
||||||
|
- name: Generate skills documentation
|
||||||
|
run: pnpm generate-skill-docs
|
||||||
|
|
||||||
|
- name: Check for changes
|
||||||
|
id: git-check
|
||||||
|
run: |
|
||||||
|
git diff --exit-code packages/opencode/skills documentation/docs/60-skills/10-skills.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: 'chore: sync skills and update documentation'
|
||||||
|
branch: chore/sync-opencode-skills
|
||||||
|
delete-branch: true
|
||||||
|
title: 'chore: sync skills and update documentation'
|
||||||
|
body: |
|
||||||
|
## Summary
|
||||||
|
Automatically synced skills and updated documentation.
|
||||||
|
|
||||||
|
This PR was triggered by changes to the skills folder in `plugins/svelte/skills/`.
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
- Synced `packages/opencode/skills/` with latest skill definitions
|
||||||
|
- Updated `documentation/docs/60-skills/10-skills.md` with latest skill documentation
|
||||||
|
|
||||||
|
## Generated by
|
||||||
|
GitHub Action: Sync OpenCode Skills
|
||||||
|
labels: |
|
||||||
|
chore
|
||||||
|
documentation
|
||||||
|
automated
|
||||||
8
.github/workflows/test.yml
vendored
8
.github/workflows/test.yml
vendored
@@ -13,17 +13,17 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: 10.18.3
|
version: 10.28.2
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version: '22'
|
node-version: '24'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
|||||||
78
.github/workflows/update-opencode-jsonschema.yml
vendored
Normal file
78
.github/workflows/update-opencode-jsonschema.yml
vendored
Normal 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/mcp'
|
||||||
|
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
|
||||||
9
.github/workflows/update-prompt-docs.yml
vendored
9
.github/workflows/update-prompt-docs.yml
vendored
@@ -19,12 +19,12 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 24
|
||||||
package-manager-cache: false # pnpm is not installed yet
|
package-manager-cache: false # pnpm is not installed yet
|
||||||
@@ -37,10 +37,11 @@ jobs:
|
|||||||
npm i -g pnpm@$PNPM_VER
|
npm i -g pnpm@$PNPM_VER
|
||||||
|
|
||||||
- name: Setup Node.js with pnpm cache
|
- name: Setup Node.js with pnpm cache
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 24
|
||||||
package-manager-cache: true # caches pnpm via packageManager field in package.json
|
package-manager-cache: true # caches pnpm via packageManager field in package.json
|
||||||
|
cache: 'pnpm'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile --prefer-offline --ignore-scripts
|
run: pnpm install --frozen-lockfile --prefer-offline --ignore-scripts
|
||||||
@@ -55,7 +56,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
if: steps.git-check.outputs.changed == 'true'
|
if: steps.git-check.outputs.changed == 'true'
|
||||||
uses: peter-evans/create-pull-request@v7
|
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
commit-message: 'docs: update prompts documentation'
|
commit-message: 'docs: update prompts documentation'
|
||||||
|
|||||||
@@ -11,4 +11,7 @@ bun.lockb
|
|||||||
/**/.svelte-kit/*
|
/**/.svelte-kit/*
|
||||||
|
|
||||||
# Claude Code
|
# Claude Code
|
||||||
.claude/
|
.claude/
|
||||||
|
.changeset/
|
||||||
|
|
||||||
|
/packages/opencode/schema.json
|
||||||
@@ -37,34 +37,35 @@
|
|||||||
],
|
],
|
||||||
"private": true,
|
"private": true,
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/compat": "^1.3.2",
|
"@eslint/compat": "catalog:lint",
|
||||||
"@eslint/js": "^9.36.0",
|
"@eslint/js": "catalog:lint",
|
||||||
"@libsql/client": "^0.15.0",
|
"@libsql/client": "catalog:orm",
|
||||||
"@modelcontextprotocol/inspector": "^0.17.0",
|
"@modelcontextprotocol/inspector": "catalog:ai",
|
||||||
"@sveltejs/adapter-vercel": "^6.0.0",
|
"@sveltejs/adapter-vercel": "catalog:svelte",
|
||||||
"@sveltejs/kit": "^2.22.0",
|
"@sveltejs/kit": "catalog:svelte",
|
||||||
"@sveltejs/vite-plugin-svelte": "^6.0.0",
|
"@sveltejs/vite-plugin-svelte": "catalog:svelte",
|
||||||
"@types/node": "^24.3.1",
|
"@types/node": "catalog:tooling",
|
||||||
"@typescript-eslint/parser": "^8.44.0",
|
"@typescript-eslint/parser": "catalog:lint",
|
||||||
"drizzle-kit": "^0.31.0",
|
"drizzle-kit": "catalog:orm",
|
||||||
"drizzle-orm": "^0.44.0",
|
"drizzle-orm": "catalog:orm",
|
||||||
"eslint-config-prettier": "^10.0.1",
|
"eslint-config-prettier": "catalog:lint",
|
||||||
"eslint-plugin-svelte": "^3.12.3",
|
"eslint-plugin-svelte": "catalog:lint",
|
||||||
"globals": "^16.0.0",
|
"globals": "catalog:lint",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "catalog:lint",
|
||||||
"prettier-plugin-svelte": "^3.3.3",
|
"prettier-plugin-svelte": "catalog:lint",
|
||||||
"svelte": "^5.0.0",
|
"svelte": "catalog:svelte",
|
||||||
"svelte-check": "^4.0.0",
|
"svelte-check": "catalog:svelte",
|
||||||
"svelte-eslint-parser": "^1.3.2",
|
"svelte-eslint-parser": "catalog:lint",
|
||||||
"typescript": "^5.0.0",
|
"typescript": "catalog:tooling",
|
||||||
"vite": "^7.0.4",
|
"vite": "catalog:tooling",
|
||||||
"vite-plugin-devtools-json": "^1.0.0",
|
"vite-plugin-devtools-json": "catalog:tooling",
|
||||||
"vitest": "^3.2.3"
|
"vitest": "catalog:tooling"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sveltejs/mcp-schema": "workspace:^",
|
"@sveltejs/mcp-schema": "workspace:^",
|
||||||
"@sveltejs/mcp-server": "workspace:^",
|
"@sveltejs/mcp-server": "workspace:^",
|
||||||
"@tmcp/transport-http": "^0.6.3",
|
"@tmcp/transport-http": "catalog:tmcp",
|
||||||
"tmcp": "^1.15.0"
|
"@vercel/analytics": "catalog:tooling",
|
||||||
|
"tmcp": "catalog:tmcp"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
import { dev } from '$app/environment';
|
||||||
import { http_transport } from '$lib/mcp/index.js';
|
import { http_transport } from '$lib/mcp/index.js';
|
||||||
import { db } from '$lib/server/db/index.js';
|
import { db } from '$lib/server/db/index.js';
|
||||||
import { redirect } from '@sveltejs/kit';
|
import { redirect } from '@sveltejs/kit';
|
||||||
|
import { track } from '@vercel/analytics/server';
|
||||||
|
|
||||||
export async function handle({ event, resolve }) {
|
export async function handle({ event, resolve }) {
|
||||||
if (event.request.method === 'GET') {
|
if (event.request.method === 'GET') {
|
||||||
@@ -16,20 +18,12 @@ export async function handle({ event, resolve }) {
|
|||||||
}
|
}
|
||||||
const mcp_response = await http_transport.respond(event.request, {
|
const mcp_response = await http_transport.respond(event.request, {
|
||||||
db,
|
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);
|
return mcp_response ?? resolve(event);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,4 +3,10 @@ import { HttpTransport } from '@tmcp/transport-http';
|
|||||||
|
|
||||||
export const http_transport = new HttpTransport(server, {
|
export const http_transport = new HttpTransport(server, {
|
||||||
cors: true,
|
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,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { createClient } from '@libsql/client';
|
import { createClient } from '@libsql/client';
|
||||||
import { drizzle } from 'drizzle-orm/libsql';
|
import { drizzle } from 'drizzle-orm/libsql';
|
||||||
import * as schema from './schema.js';
|
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';
|
import { DATABASE_TOKEN, DATABASE_URL } from '$env/static/private';
|
||||||
if (!DATABASE_URL) throw new Error('DATABASE_URL is not set');
|
if (!DATABASE_URL) throw new Error('DATABASE_URL is not set');
|
||||||
if (!DATABASE_TOKEN) throw new Error('DATABASE_TOKEN is not set');
|
if (!DATABASE_TOKEN) throw new Error('DATABASE_TOKEN is not set');
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ The setup varies based on the version of the MCP you prefer — remote or local
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
To get the most out of the MCP server we recommend including the following prompt in your [`AGENTS.md`](https://agents.md) (or [`CLAUDE.md`](https://docs.claude.com/en/docs/claude-code/memory#claude-md-imports), if using Claude Code). This will tell the LLM which tools are available and when it's appropriate to use them.
|
To get the most out of the MCP server we recommend including the following prompt in your [`AGENTS.md`](https://agents.md) (or [`CLAUDE.md`](https://docs.claude.com/en/docs/claude-code/memory#claude-md-imports), if using Claude Code. Or [`GEMINI.md`](https://geminicli.com/docs/cli/gemini-md/), if using GEMINI). This will tell the LLM which tools are available and when it's appropriate to use them.
|
||||||
|
|
||||||
|
> [!NOTE] This is already setup for you when using `npx sv add mcp`
|
||||||
|
|
||||||
```md
|
```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:
|
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:
|
||||||
|
|||||||
@@ -45,6 +45,27 @@ command = "npx"
|
|||||||
args = ["-y", "@sveltejs/mcp"]
|
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
|
## Gemini CLI
|
||||||
|
|
||||||
To include the local MCP version in Gemini CLI, simply run the following command:
|
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
|
## 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
|
```bash
|
||||||
opencode mcp add
|
opencode mcp add
|
||||||
@@ -110,6 +131,12 @@ It will open a file with your MCP servers where you can add the following config
|
|||||||
|
|
||||||
## Zed
|
## Zed
|
||||||
|
|
||||||
|
Install the [Svelte MCP Server extension](https://zed.dev/extensions/svelte-mcp).
|
||||||
|
|
||||||
|
<details>
|
||||||
|
|
||||||
|
<summary>Configure Manually</summary>
|
||||||
|
|
||||||
- Open the command palette
|
- Open the command palette
|
||||||
- Search and select "agent:open settings"
|
- Search and select "agent:open settings"
|
||||||
- In settings panel look for `Model Context Protocol (MCP) Servers`
|
- In settings panel look for `Model Context Protocol (MCP) Servers`
|
||||||
@@ -127,6 +154,8 @@ It will open a popup with MCP server config where you can add the following conf
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## Other clients
|
## 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.
|
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.
|
||||||
|
|||||||
@@ -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`.
|
You can choose your preferred `scope` (it must be `user`, `project` or `local`) and `name`.
|
||||||
|
|
||||||
|
If you prefer you can also install the `svelte` plugin in [the Svelte Claude Code Marketplace](plugin) that will give you both the remote server and useful [skills](skills).
|
||||||
|
|
||||||
## Claude Desktop
|
## Claude Desktop
|
||||||
|
|
||||||
- Open Settings > Connectors
|
- Open Settings > Connectors
|
||||||
@@ -34,6 +36,26 @@ experimental_use_rmcp_client = true
|
|||||||
url = "https://mcp.svelte.dev/mcp"
|
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
|
## Gemini CLI
|
||||||
|
|
||||||
To use the remote MCP server with Gemini CLI, simply run the following command:
|
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
|
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
|
## 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
|
```bash
|
||||||
opencode mcp add
|
opencode mcp add
|
||||||
@@ -96,6 +118,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
|
## 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.
|
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.
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ This prompt should be used whenever you are asking the model to work on a Svelte
|
|||||||
<summary>Copy the prompt</summary>
|
<summary>Copy the prompt</summary>
|
||||||
|
|
||||||
```md
|
```md
|
||||||
You are a Svelte expert tasked to build components and utilities for Svelte developers. If you need documentation for anything related to Svelte you can invoke the tool `get_documentation` with one of the following paths:
|
You are a Svelte expert tasked to build components and utilities for Svelte developers. If you need documentation for anything related to Svelte you can invoke the tool `get-documentation` with one of the following paths. However: before invoking the `get-documentation` tool, try to answer the users query using your own knowledge and the `svelte-autofixer` tool. Be mindful of how many section you request, since it is token-intensive!
|
||||||
<available-docs>
|
<available-docs>
|
||||||
|
|
||||||
- title: Overview, use_cases: project setup, creating new svelte apps, scaffolding, cli tools, initializing projects, path: cli/overview
|
- title: Overview, use_cases: project setup, creating new svelte apps, scaffolding, cli tools, initializing projects, path: cli/overview
|
||||||
@@ -25,6 +25,7 @@ You are a Svelte expert tasked to build components and utilities for Svelte deve
|
|||||||
- 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: 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: eslint, use_cases: code quality, linting, error detection, project setup, code standards, team collaboration, typescript projects, path: cli/eslint
|
||||||
- title: lucia, use_cases: authentication, login systems, user management, registration pages, session handling, auth setup, path: cli/lucia
|
- title: lucia, use_cases: authentication, login systems, user management, registration pages, session handling, auth setup, path: cli/lucia
|
||||||
|
- 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: 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: 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: playwright, use_cases: browser testing, e2e testing, integration testing, test automation, quality assurance, ci/cd pipelines, testing user flows, path: cli/playwright
|
||||||
@@ -152,6 +153,7 @@ You are a Svelte expert tasked to build components and utilities for Svelte deve
|
|||||||
- title: Context, use_cases: shared state, avoiding prop drilling, component communication, theme providers, user context, authentication state, configuration sharing, deeply nested components, path: svelte/context
|
- title: 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: 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: Imperative component API, use_cases: project setup, client-side rendering, server-side rendering, ssr, hydration, testing, programmatic component creation, tooltips, dynamic mounting, path: svelte/imperative-component-api
|
||||||
|
- title: Hydratable data, use_cases: use title and path to estimate use case, path: svelte/hydratable
|
||||||
- title: Testing, use_cases: testing, quality assurance, unit tests, integration tests, component tests, e2e tests, vitest setup, playwright setup, test automation, path: svelte/testing
|
- title: 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: 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: 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
|
||||||
@@ -191,6 +193,8 @@ You are a Svelte expert tasked to build components and utilities for Svelte deve
|
|||||||
|
|
||||||
</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.
|
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:
|
This is the task you will work on:
|
||||||
|
|||||||
3
documentation/docs/40-claude-plugin/index.md
Normal file
3
documentation/docs/40-claude-plugin/index.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
title: Claude Code Plugin
|
||||||
|
---
|
||||||
23
documentation/docs/40-claude-plugin/plugin.md
Normal file
23
documentation/docs/40-claude-plugin/plugin.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
title: Overview
|
||||||
|
---
|
||||||
|
|
||||||
|
The open source [repository](https://github.com/sveltejs/mcp) containing the code for the MCP server is also a Claude Code Marketplace plugin.
|
||||||
|
|
||||||
|
The marketplace 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 as it helps save context by handling Svelte-specific tasks more efficiently.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
To add the repository as a marketplace, launch Claude Code and type the following:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/plugin marketplace add sveltejs/mcp
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, install the Svelte plugin:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/plugin install svelte
|
||||||
|
```
|
||||||
11
documentation/docs/40-claude-plugin/subagent.md
Normal file
11
documentation/docs/40-claude-plugin/subagent.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
title: Subagent
|
||||||
|
---
|
||||||
|
|
||||||
|
The Svelte plugin includes a specialized subagent called `svelte-file-editor` designed for creating, editing, and reviewing Svelte files.
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
The subagent has access to its own context window, allowing it to fetch the documentation, iterate with the `svelte-autofixer` tool and write to the file system without wasting context in the main agent.
|
||||||
|
|
||||||
|
The delegation should happen automatically when appropriate, but you can also explicitly request the subagent be used for Svelte-related tasks.
|
||||||
3
documentation/docs/50-opencode-plugin/index.md
Normal file
3
documentation/docs/50-opencode-plugin/index.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
title: OpenCode Plugin
|
||||||
|
---
|
||||||
42
documentation/docs/50-opencode-plugin/opencode-plugin.md
Normal file
42
documentation/docs/50-opencode-plugin/opencode-plugin.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
title: Overview
|
||||||
|
---
|
||||||
|
|
||||||
|
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 in OpenCode you can edit your [OpenCode 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, [skills](skills), and the [file editor subagent](opencode-subagent) configured for you.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The default configuration for the Svelte OpenCode plugin looks like this...
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/sveltejs/mcp/refs/heads/main/packages/opencode/schema.json",
|
||||||
|
"mcp": {
|
||||||
|
"type": "remote",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"subagent": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"skills": {
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
...but if you prefer, you can enable only the subagent, only the MCP, only the skills, or configure the kind of MCP server you want to use (`local` or `remote`).
|
||||||
|
|
||||||
|
You can place this file in `~/.config/opencode/svelte.json` or, if you have an `OPENCODE_CONFIG_DIR` environment variable specified, at `$OPENCODE_CONFIG_DIR/svelte.json`.
|
||||||
11
documentation/docs/50-opencode-plugin/opencode-subagent.md
Normal file
11
documentation/docs/50-opencode-plugin/opencode-subagent.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
title: Subagent
|
||||||
|
---
|
||||||
|
|
||||||
|
The Svelte plugin includes a specialized subagent called `svelte-file-editor` designed for creating, editing, and reviewing Svelte files.
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
The subagent has access to its own context window, allowing it to fetch the documentation, iterate with the `svelte-autofixer` tool and write to the file system without wasting context in the main agent.
|
||||||
|
|
||||||
|
The delegation should happen automatically when appropriate, but you can also explicitly request the subagent be used for Svelte-related tasks.
|
||||||
86
documentation/docs/60-skills/10-skills.md
Normal file
86
documentation/docs/60-skills/10-skills.md
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
---
|
||||||
|
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/mcp/releases) or find them in the [`plugins/svelte/skills`](https://github.com/sveltejs/mcp/tree/main/plugins/svelte/skills) folder.
|
||||||
|
|
||||||
|
## `svelte-code-writer`
|
||||||
|
|
||||||
|
CLI tools for Svelte 5 documentation lookup and code analysis. MUST be used whenever creating or editing any Svelte component (.svelte) or Svelte module (.svelte.ts/.svelte.js). If possible, this skill should be executed within the svelte-file-editor agent for optimal results.
|
||||||
|
|
||||||
|
<a href="https://github.com/sveltejs/mcp/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>
|
||||||
3
documentation/docs/60-skills/index.md
Normal file
3
documentation/docs/60-skills/index.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
title: Skills
|
||||||
|
---
|
||||||
@@ -7,13 +7,20 @@ import { fileURLToPath } from 'node:url';
|
|||||||
import ts from 'typescript-eslint';
|
import ts from 'typescript-eslint';
|
||||||
import svelteConfig from './apps/mcp-remote/svelte.config.js';
|
import svelteConfig from './apps/mcp-remote/svelte.config.js';
|
||||||
import eslint_plugin_import from 'eslint-plugin-import';
|
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));
|
const gitignore_path = fileURLToPath(new URL('./.gitignore', import.meta.url));
|
||||||
|
|
||||||
export default /** @type {import("eslint").Linter.Config} */ ([
|
export default /** @type {import("eslint").Linter.Config} */ ([
|
||||||
includeIgnoreFile(gitignore_path),
|
includeIgnoreFile(gitignore_path),
|
||||||
{
|
{
|
||||||
ignores: ['.claude/**/*'],
|
ignores: [
|
||||||
|
'.claude/**/*',
|
||||||
|
'.changeset/*',
|
||||||
|
'.github/**/*.yml',
|
||||||
|
'.github/**/*.yaml',
|
||||||
|
'**/pnpm-lock.yaml',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
js.configs.recommended,
|
js.configs.recommended,
|
||||||
...ts.configs.recommended,
|
...ts.configs.recommended,
|
||||||
@@ -48,13 +55,17 @@ export default /** @type {import("eslint").Linter.Config} */ ([
|
|||||||
'import/no-unresolved': 'off', // this doesn't work well with typescript path mapping
|
'import/no-unresolved': 'off', // this doesn't work well with typescript path mapping
|
||||||
'import/extensions': [
|
'import/extensions': [
|
||||||
'error',
|
'error',
|
||||||
'ignorePackages',
|
|
||||||
{
|
{
|
||||||
js: 'always',
|
ignorePackages: true,
|
||||||
mjs: 'always',
|
pattern: {
|
||||||
cjs: 'always',
|
js: 'always',
|
||||||
ts: 'always',
|
mjs: 'always',
|
||||||
svelte: 'always',
|
cjs: 'always',
|
||||||
|
ts: 'always',
|
||||||
|
svelte: 'always',
|
||||||
|
svg: 'always',
|
||||||
|
json: 'always',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -70,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,
|
||||||
]);
|
]);
|
||||||
|
|||||||
50
package.json
50
package.json
@@ -3,7 +3,7 @@
|
|||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"description": "The official Svelte MCP server implementation",
|
"description": "The official Svelte MCP server implementation",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "pnpm@10.18.3",
|
"packageManager": "pnpm@10.28.2",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "pnpm -r run build",
|
"build": "pnpm -r run build",
|
||||||
"dev": "pnpm --filter @sveltejs/mcp-remote run dev",
|
"dev": "pnpm --filter @sveltejs/mcp-remote run dev",
|
||||||
@@ -12,15 +12,20 @@
|
|||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"lint": "prettier --check . && eslint .",
|
"lint": "prettier --check . && eslint .",
|
||||||
"lint:fix": "prettier --write . && eslint . --fix",
|
"lint:fix": "prettier --write . && eslint . --fix",
|
||||||
|
"lint:inspect": "pnpm dlx @eslint/config-inspector",
|
||||||
|
"node:inspect": "pnpm dlx node-modules-inspector",
|
||||||
"test:unit": "vitest",
|
"test:unit": "vitest",
|
||||||
"test": "npm run test:unit -- --run",
|
"test": "npm run test:unit -- --run",
|
||||||
"test:watch": "npm run test:unit -- --watch",
|
"test:watch": "npm run test:unit -- --watch",
|
||||||
"inspect": "pnpm mcp-inspector",
|
"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-summaries": "pnpm --filter @sveltejs/mcp-server run generate-summaries",
|
||||||
"generate-prompt-docs": "node --import node-resolve-ts/register scripts/update-docs-prompts.ts",
|
"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",
|
||||||
"debug:generate-summaries": "pnpm --filter @sveltejs/mcp-server run debug:generate-summaries",
|
"debug:generate-summaries": "pnpm --filter @sveltejs/mcp-server run debug:generate-summaries",
|
||||||
"release": "pnpm --filter @sveltejs/mcp run build && changeset publish",
|
"release": "pnpm --filter @sveltejs/mcp run build && changeset publish",
|
||||||
"changeset:version": "changeset version && pnpm --filter @sveltejs/mcp run update:version && git add --all"
|
"changeset:version": "changeset version && pnpm --filter @sveltejs/mcp run update:version && git add --all",
|
||||||
|
"sync-opencode-skills": "rm -rf packages/opencode/skills && cp -r plugins/svelte/skills packages/opencode/skills"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"svelte",
|
"svelte",
|
||||||
@@ -30,27 +35,24 @@
|
|||||||
],
|
],
|
||||||
"private": true,
|
"private": true,
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@changesets/cli": "^2.29.7",
|
"@changesets/cli": "catalog:tooling",
|
||||||
"@eslint/compat": "^1.3.2",
|
"@eslint/compat": "catalog:lint",
|
||||||
"@eslint/js": "^9.36.0",
|
"@eslint/js": "catalog:lint",
|
||||||
"@modelcontextprotocol/inspector": "^0.17.0",
|
"@modelcontextprotocol/inspector": "catalog:ai",
|
||||||
"@svitejs/changesets-changelog-github-compact": "^1.2.0",
|
"@sveltejs/adapter-vercel": "catalog:svelte",
|
||||||
"eslint": "^9.36.0",
|
"@svitejs/changesets-changelog-github-compact": "catalog:tooling",
|
||||||
"eslint-config-prettier": "^10.0.1",
|
"eslint": "catalog:lint",
|
||||||
"eslint-plugin-import": "^2.32.0",
|
"eslint-config-prettier": "catalog:lint",
|
||||||
"eslint-plugin-svelte": "^3.12.3",
|
"eslint-plugin-import": "catalog:lint",
|
||||||
"globals": "^16.0.0",
|
"eslint-plugin-pnpm": "catalog:lint",
|
||||||
"node-resolve-ts": "^1.0.2",
|
"eslint-plugin-svelte": "catalog:lint",
|
||||||
"prettier": "^3.4.2",
|
"globals": "catalog:lint",
|
||||||
"prettier-plugin-svelte": "^3.3.3",
|
"node-resolve-ts": "catalog:tooling",
|
||||||
"publint": "^0.3.13",
|
"prettier": "catalog:lint",
|
||||||
"typescript": "^5.0.0",
|
"prettier-plugin-svelte": "catalog:lint",
|
||||||
"typescript-eslint": "^8.44.1",
|
"publint": "catalog:tooling",
|
||||||
"vitest": "^3.2.3"
|
"typescript": "catalog:tooling",
|
||||||
},
|
"typescript-eslint": "catalog:lint",
|
||||||
"pnpm": {
|
"vitest": "catalog:tooling"
|
||||||
"onlyBuiltDependencies": [
|
|
||||||
"esbuild"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,6 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"drizzle-orm": "^0.44.0"
|
"drizzle-orm": "catalog:orm"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,32 +14,35 @@
|
|||||||
"debug:generate-summaries": "DEBUG_MODE=1 node scripts/generate-summaries.ts --experimental-strip-types"
|
"debug:generate-summaries": "DEBUG_MODE=1 node scripts/generate-summaries.ts --experimental-strip-types"
|
||||||
},
|
},
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts"
|
".": "./src/index.ts",
|
||||||
|
"./handlers": "./src/mcp/handlers/tools/handlers.ts"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"drizzle-orm": "^0.44.0"
|
"drizzle-orm": "^0.45.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@mcp-ui/server": "catalog:ai",
|
||||||
"@sveltejs/mcp-schema": "workspace:^",
|
"@sveltejs/mcp-schema": "workspace:^",
|
||||||
"@tmcp/adapter-valibot": "^0.1.4",
|
"@tmcp/adapter-valibot": "catalog:tmcp",
|
||||||
"@typescript-eslint/parser": "^8.44.0",
|
"@tmcp/transport-in-memory": "catalog:tmcp",
|
||||||
"eslint": "^9.36.0",
|
"@typescript-eslint/parser": "catalog:lint",
|
||||||
"eslint-plugin-svelte": "^3.12.3",
|
"eslint": "catalog:lint",
|
||||||
"svelte": "^5.39.2",
|
"eslint-plugin-svelte": "catalog:lint",
|
||||||
"svelte-eslint-parser": "^1.3.2",
|
"svelte": "catalog:svelte",
|
||||||
"tmcp": "^1.15.0",
|
"svelte-eslint-parser": "catalog:lint",
|
||||||
"ts-blank-space": "^0.6.2",
|
"tmcp": "catalog:tmcp",
|
||||||
"typescript-eslint": "^8.44.0",
|
"ts-blank-space": "catalog:tooling",
|
||||||
"valibot": "^1.1.0",
|
"typescript-eslint": "catalog:lint",
|
||||||
"vitest": "^3.2.4",
|
"valibot": "catalog:tooling",
|
||||||
"zimmerframe": "^1.1.4"
|
"vitest": "catalog:tooling",
|
||||||
|
"zimmerframe": "catalog:tooling"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@anthropic-ai/sdk": "^0.67.0",
|
"@anthropic-ai/sdk": "catalog:ai",
|
||||||
"@sveltejs/kit": "^2.42.2",
|
"@sveltejs/kit": "catalog:svelte",
|
||||||
"@types/eslint-scope": "^8.3.2",
|
"@types/eslint-scope": "catalog:lint",
|
||||||
"@types/estree": "^1.0.8",
|
"@types/estree": "catalog:tooling",
|
||||||
"@typescript-eslint/types": "^8.44.0",
|
"@typescript-eslint/types": "catalog:lint",
|
||||||
"dotenv": "^17.2.3"
|
"dotenv": "catalog:tooling"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export const base_runes = [
|
|||||||
export const nested_runes = [
|
export const nested_runes = [
|
||||||
'$state.raw',
|
'$state.raw',
|
||||||
'$state.snapshot',
|
'$state.snapshot',
|
||||||
|
'$state.eager',
|
||||||
'$effect.pre',
|
'$effect.pre',
|
||||||
'$effect.tracking',
|
'$effect.tracking',
|
||||||
'$effect.pending',
|
'$effect.pending',
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export function add_autofixers_issues(
|
|||||||
code: string,
|
code: string,
|
||||||
desired_svelte_version: number,
|
desired_svelte_version: number,
|
||||||
filename = 'Component.svelte',
|
filename = 'Component.svelte',
|
||||||
|
async = false,
|
||||||
) {
|
) {
|
||||||
const parsed = parse(code, filename);
|
const parsed = parse(code, filename);
|
||||||
|
|
||||||
@@ -15,7 +16,7 @@ export function add_autofixers_issues(
|
|||||||
for (const autofixer of Object.values(autofixers)) {
|
for (const autofixer of Object.values(autofixers)) {
|
||||||
walk(
|
walk(
|
||||||
parsed.ast as unknown as Node,
|
parsed.ast as unknown as Node,
|
||||||
{ output: content, parsed, desired_svelte_version },
|
{ output: content, parsed, desired_svelte_version, async },
|
||||||
autofixer,
|
autofixer,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export function add_compile_issues(
|
|||||||
code: string,
|
code: string,
|
||||||
desired_svelte_version: number,
|
desired_svelte_version: number,
|
||||||
filename = 'Component.svelte',
|
filename = 'Component.svelte',
|
||||||
|
async = false,
|
||||||
) {
|
) {
|
||||||
let compile = compile_component;
|
let compile = compile_component;
|
||||||
const extension = extname(filename);
|
const extension = extname(filename);
|
||||||
@@ -27,6 +28,7 @@ export function add_compile_issues(
|
|||||||
filename: filename || 'Component.svelte',
|
filename: filename || 'Component.svelte',
|
||||||
generate: false,
|
generate: false,
|
||||||
runes: desired_svelte_version >= 5,
|
runes: desired_svelte_version >= 5,
|
||||||
|
experimental: { async },
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const warning of compilation_result.warnings) {
|
for (const warning of compilation_result.warnings) {
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ function base_config(svelte_config: Config): ESLint.Options['baseConfig'] {
|
|||||||
'svelte/prefer-writable-derived': 'warn',
|
'svelte/prefer-writable-derived': 'warn',
|
||||||
'svelte/require-event-dispatcher-types': 'warn',
|
'svelte/require-event-dispatcher-types': 'warn',
|
||||||
'svelte/require-store-reactive-access': 'warn',
|
'svelte/require-store-reactive-access': 'warn',
|
||||||
|
'svelte/no-inspect': 'off',
|
||||||
},
|
},
|
||||||
|
|
||||||
languageOptions: {
|
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) {
|
if (version < 5) {
|
||||||
return (svelte_4_linter ??= new ESLint({
|
return (svelte_4_linter ??= new ESLint({
|
||||||
overrideConfigFile: true,
|
overrideConfigFile: true,
|
||||||
@@ -67,6 +68,7 @@ function get_linter(version: number) {
|
|||||||
baseConfig: base_config({
|
baseConfig: base_config({
|
||||||
compilerOptions: {
|
compilerOptions: {
|
||||||
runes: true,
|
runes: true,
|
||||||
|
experimental: { async },
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
@@ -77,8 +79,9 @@ export async function add_eslint_issues(
|
|||||||
code: string,
|
code: string,
|
||||||
desired_svelte_version: number,
|
desired_svelte_version: number,
|
||||||
filename = 'Component.svelte',
|
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' });
|
const results = await eslint.lintText(code, { filePath: filename || './Component.svelte' });
|
||||||
|
|
||||||
for (const message of results[0]?.messages ?? []) {
|
for (const message of results[0]?.messages ?? []) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export type AutofixerState = {
|
|||||||
output: { issues: string[]; suggestions: string[] };
|
output: { issues: string[]; suggestions: string[] };
|
||||||
parsed: ParseResult;
|
parsed: ParseResult;
|
||||||
desired_svelte_version: number;
|
desired_svelte_version: number;
|
||||||
|
async?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Autofixer = Visitors<Node | AST.SvelteNode, AutofixerState>;
|
export type Autofixer = Visitors<Node | AST.SvelteNode, AutofixerState>;
|
||||||
|
|||||||
@@ -2,19 +2,22 @@ import type { SvelteMcp } from '../../index.js';
|
|||||||
import * as v from 'valibot';
|
import * as v from 'valibot';
|
||||||
import { format_sections_list } from '../../utils.js';
|
import { format_sections_list } from '../../utils.js';
|
||||||
import { icons } from '../../icons/index.js';
|
import { icons } from '../../icons/index.js';
|
||||||
|
import { prompt } from 'tmcp/utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function that actually generates the prompt string. You can use this in the MCP server handler to generate the prompt, it can accept arguments
|
* 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).
|
* 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) {
|
function svelte_task(available_docs: string, task: string) {
|
||||||
return `You are a Svelte expert tasked to build components and utilities for Svelte developers. If you need documentation for anything related to Svelte you can invoke the tool \`get_documentation\` with one of the following paths:
|
return `You are a Svelte expert tasked to build components and utilities for Svelte developers. If you need documentation for anything related to Svelte you can invoke the tool \`get-documentation\` with one of the following paths. However: before invoking the \`get-documentation\` tool, try to answer the users query using your own knowledge and the \`svelte-autofixer\` tool. Be mindful of how many section you request, since it is token-intensive!
|
||||||
<available-docs>
|
<available-docs>
|
||||||
|
|
||||||
${available_docs}
|
${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.
|
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:
|
This is the task you will work on:
|
||||||
@@ -68,19 +71,12 @@ export function setup_svelte_task(server: SvelteMcp) {
|
|||||||
icons,
|
icons,
|
||||||
},
|
},
|
||||||
async ({ task }) => {
|
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();
|
const available_docs = await format_sections_list();
|
||||||
|
|
||||||
return {
|
return prompt.text(svelte_task(available_docs, task));
|
||||||
messages: [
|
|
||||||
{
|
|
||||||
role: 'user',
|
|
||||||
content: {
|
|
||||||
type: 'text',
|
|
||||||
text: svelte_task(available_docs, task),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { SvelteMcp } from '../../index.js';
|
import type { SvelteMcp } from '../../index.js';
|
||||||
import { get_sections, fetch_with_timeout } from '../../utils.js';
|
import { get_sections, fetch_with_timeout } from '../../utils.js';
|
||||||
import { icons } from '../../icons/index.js';
|
import { icons } from '../../icons/index.js';
|
||||||
|
import { resource } from 'tmcp/utils';
|
||||||
|
|
||||||
export async function list_sections(server: SvelteMcp) {
|
export async function list_sections(server: SvelteMcp) {
|
||||||
const sections = await get_sections();
|
const sections = await get_sections();
|
||||||
@@ -46,21 +47,20 @@ export async function list_sections(server: SvelteMcp) {
|
|||||||
icons,
|
icons,
|
||||||
},
|
},
|
||||||
async (uri, { slug }) => {
|
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) => {
|
const section = sections.find((section) => {
|
||||||
return slug === section.slug;
|
return slug === section.slug;
|
||||||
});
|
});
|
||||||
if (!section) throw new Error(`Section not found: ${slug}`);
|
if (!section) throw new Error(`Section not found: ${slug}`);
|
||||||
const response = await fetch_with_timeout(section.url);
|
const response = await fetch_with_timeout(section.url);
|
||||||
const content = await response.text();
|
const content = await response.text();
|
||||||
return {
|
return resource.text(uri, content);
|
||||||
contents: [
|
|
||||||
{
|
|
||||||
uri,
|
|
||||||
type: 'text',
|
|
||||||
text: content,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,115 +3,139 @@ import * as v from 'valibot';
|
|||||||
import { get_sections, fetch_with_timeout, format_sections_list } from '../../utils.js';
|
import { get_sections, fetch_with_timeout, format_sections_list } from '../../utils.js';
|
||||||
import { SECTIONS_LIST_INTRO, SECTIONS_LIST_OUTRO } from './prompts.js';
|
import { SECTIONS_LIST_INTRO, SECTIONS_LIST_OUTRO } from './prompts.js';
|
||||||
import { icons } from '../../icons/index.js';
|
import { icons } from '../../icons/index.js';
|
||||||
|
import { tool } from 'tmcp/utils';
|
||||||
|
|
||||||
|
const get_documentation_schema = v.object({
|
||||||
|
section: v.pipe(
|
||||||
|
v.union([v.string(), v.array(v.string())]),
|
||||||
|
v.description(
|
||||||
|
'The section name(s) to retrieve. Can search by title (e.g., "$state", "load functions") or file path (e.g., "cli/overview"). Supports single string and array of strings',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function get_documentation_handler({
|
||||||
|
section,
|
||||||
|
}: v.InferInput<typeof get_documentation_schema>) {
|
||||||
|
let sections: string[];
|
||||||
|
|
||||||
|
if (Array.isArray(section)) {
|
||||||
|
sections = section.filter((s): s is string => typeof s === 'string');
|
||||||
|
} else if (
|
||||||
|
typeof section === 'string' &&
|
||||||
|
section.trim().startsWith('[') &&
|
||||||
|
section.trim().endsWith(']')
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(section);
|
||||||
|
if (Array.isArray(parsed)) {
|
||||||
|
sections = parsed.filter((s): s is string => typeof s === 'string');
|
||||||
|
} else {
|
||||||
|
sections = [section];
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
sections = [section];
|
||||||
|
}
|
||||||
|
} else if (typeof section === 'string') {
|
||||||
|
sections = [section];
|
||||||
|
} else {
|
||||||
|
sections = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const available_sections = await get_sections();
|
||||||
|
|
||||||
|
const settled_results = await Promise.allSettled(
|
||||||
|
sections.map(async (requested_section) => {
|
||||||
|
const matched_section = available_sections.find(
|
||||||
|
(s) =>
|
||||||
|
s.title.toLowerCase() === requested_section.toLowerCase() ||
|
||||||
|
s.slug === requested_section ||
|
||||||
|
s.url === requested_section,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (matched_section) {
|
||||||
|
try {
|
||||||
|
const response = await fetch_with_timeout(matched_section.url);
|
||||||
|
if (response.ok) {
|
||||||
|
const content = await response.text();
|
||||||
|
return { success: true, content: `## ${matched_section.title}\n\n${content}` };
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
content: `## ${matched_section.title}\n\nError: Could not fetch documentation (HTTP ${response.status})`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
content: `## ${matched_section.title}\n\nError: Failed to fetch documentation - ${error}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
content: `## ${requested_section}\n\nError: Section not found.`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const results = settled_results.map((result) => {
|
||||||
|
if (result.status === 'fulfilled') {
|
||||||
|
return result.value;
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
content: `Error: Couldn't fetch - ${result.reason}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const has_any_success = results.some((result) => result.success);
|
||||||
|
let final_text = results.map((r) => r.content).join('\n\n---\n\n');
|
||||||
|
|
||||||
|
if (!has_any_success) {
|
||||||
|
const formatted_sections = await format_sections_list();
|
||||||
|
|
||||||
|
final_text += `\n\n---\n\n${SECTIONS_LIST_INTRO}\n\n${formatted_sections}\n\n${SECTIONS_LIST_OUTRO}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return final_text;
|
||||||
|
}
|
||||||
|
|
||||||
export function get_documentation(server: SvelteMcp) {
|
export function get_documentation(server: SvelteMcp) {
|
||||||
server.tool(
|
server.tool(
|
||||||
{
|
{
|
||||||
name: 'get-documentation',
|
name: 'get-documentation',
|
||||||
description:
|
description:
|
||||||
'Retrieves full documentation content for Svelte 5 or SvelteKit sections. Supports flexible search by title (e.g., "$state", "routing") or file path (e.g., "cli/overview"). Can accept a single section name or an array of sections. Before running this, make sure to analyze the users query, as well as the output from list-sections (which should be called first). Then ask for ALL relevant sections the user might require. For example, if the user asks to build anything interactive, you will need to fetch all relevant runes, and so on.',
|
'Retrieves full documentation content for Svelte 5 or SvelteKit sections. Supports flexible search by title (e.g., "$state", "routing") or file path (e.g., "cli/overview"). Can accept a single section name or an array of sections. Before running this, make sure to analyze the users query, as well as the output from list-sections (which should be called first). Then ask for ALL relevant sections the user might require. For example, if the user asks to build anything interactive, you will need to fetch all relevant runes, and so on. Before calling this tool, try to implement Svelte components using your own knowledge and the `svelte-autofixer` tool, since calling this tool is token intensive.',
|
||||||
schema: v.object({
|
schema: get_documentation_schema,
|
||||||
section: v.pipe(
|
annotations: {
|
||||||
v.union([v.string(), v.array(v.string())]),
|
title: 'Get Documentation',
|
||||||
v.description(
|
destructiveHint: false,
|
||||||
'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',
|
readOnlyHint: true,
|
||||||
),
|
openWorldHint: false,
|
||||||
),
|
},
|
||||||
}),
|
|
||||||
icons,
|
icons,
|
||||||
},
|
},
|
||||||
async ({ section }) => {
|
async ({ section }) => {
|
||||||
let sections: string[];
|
if (server.ctx.sessionId && server.ctx.custom?.track) {
|
||||||
|
await server.ctx.custom?.track?.(server.ctx.sessionId, 'get-documentation');
|
||||||
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 = [];
|
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
const available_sections = await get_sections();
|
const content = await get_documentation_handler({ section });
|
||||||
|
return tool.text(content);
|
||||||
const settled_results = await Promise.allSettled(
|
} catch (e) {
|
||||||
sections.map(async (requested_section) => {
|
const error = e as Error;
|
||||||
const matched_section = available_sections.find(
|
if (server.ctx.sessionId && server.ctx.custom?.track) {
|
||||||
(s) =>
|
await server.ctx.custom?.track?.(
|
||||||
s.title.toLowerCase() === requested_section.toLowerCase() ||
|
server.ctx.sessionId,
|
||||||
s.slug === requested_section ||
|
'get-documentation-error',
|
||||||
s.url === requested_section,
|
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}`,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
});
|
return tool.error(error.message);
|
||||||
|
|
||||||
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 {
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: 'text',
|
|
||||||
text: final_text,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
4
packages/mcp-server/src/mcp/handlers/tools/handlers.ts
Normal file
4
packages/mcp-server/src/mcp/handlers/tools/handlers.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export { get_documentation_handler } from './get-documentation.js';
|
||||||
|
export { list_sections_handler } from './list-sections.js';
|
||||||
|
export { svelte_autofixer_handler } from './svelte-autofixer.js';
|
||||||
|
export { playground_link_handler } from './playground-link.js';
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export * from './get-documentation.js';
|
export { get_documentation } from './get-documentation.js';
|
||||||
export * from './list-sections.js';
|
export { list_sections } from './list-sections.js';
|
||||||
export * from './svelte-autofixer.js';
|
export { svelte_autofixer } from './svelte-autofixer.js';
|
||||||
export * from './playground-link.js';
|
export { playground_link } from './playground-link.js';
|
||||||
|
|||||||
@@ -2,6 +2,13 @@ import type { SvelteMcp } from '../../index.js';
|
|||||||
import { format_sections_list } from '../../utils.js';
|
import { format_sections_list } from '../../utils.js';
|
||||||
import { SECTIONS_LIST_INTRO, SECTIONS_LIST_OUTRO } from './prompts.js';
|
import { SECTIONS_LIST_INTRO, SECTIONS_LIST_OUTRO } from './prompts.js';
|
||||||
import { icons } from '../../icons/index.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) {
|
export function list_sections(server: SvelteMcp) {
|
||||||
server.tool(
|
server.tool(
|
||||||
@@ -9,19 +16,32 @@ export function list_sections(server: SvelteMcp) {
|
|||||||
name: 'list-sections',
|
name: 'list-sections',
|
||||||
description:
|
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.',
|
'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,
|
icons,
|
||||||
},
|
},
|
||||||
async () => {
|
async () => {
|
||||||
const formatted_sections = await format_sections_list();
|
if (server.ctx.sessionId && server.ctx.custom?.track) {
|
||||||
|
await server.ctx.custom?.track?.(server.ctx.sessionId, 'list-sections');
|
||||||
return {
|
}
|
||||||
content: [
|
try {
|
||||||
{
|
const content = await list_sections_handler();
|
||||||
type: 'text',
|
return tool.text(content);
|
||||||
text: `${SECTIONS_LIST_INTRO}\n\n${formatted_sections}\n\n${SECTIONS_LIST_OUTRO}`,
|
} 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);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,6 +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 * as v from 'valibot';
|
||||||
import { icons } from '../../icons/index.js';
|
import { icons } from '../../icons/index.js';
|
||||||
|
import type { SvelteMcp } from '../../index.js';
|
||||||
|
|
||||||
async function compress_and_encode_text(input: string) {
|
async function compress_and_encode_text(input: string) {
|
||||||
const reader = new Blob([input]).stream().pipeThrough(new CompressionStream('gzip')).getReader();
|
const reader = new Blob([input]).stream().pipeThrough(new CompressionStream('gzip')).getReader();
|
||||||
@@ -28,87 +30,232 @@ type File = {
|
|||||||
text: boolean;
|
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) {
|
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(
|
server.tool(
|
||||||
{
|
{
|
||||||
name: 'playground-link',
|
name: 'playground-link',
|
||||||
description:
|
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.',
|
'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({
|
schema: playground_link_schema,
|
||||||
name: v.pipe(
|
outputSchema: playground_link_output_schema,
|
||||||
v.string(),
|
annotations: {
|
||||||
v.description('The name of the Playground, it should reflect the user task'),
|
title: 'Playground Link',
|
||||||
),
|
destructiveHint: false,
|
||||||
tailwind: v.pipe(
|
readOnlyHint: true,
|
||||||
v.boolean(),
|
openWorldHint: false,
|
||||||
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(),
|
|
||||||
}),
|
|
||||||
icons,
|
icons,
|
||||||
|
// For MCP Apps hosts - points to the registered resource
|
||||||
|
_meta: {
|
||||||
|
ui: {
|
||||||
|
resourceUri: playground_ui_resource.resource.uri,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
async ({ files, name, tailwind }) => {
|
async ({ files, name, tailwind }) => {
|
||||||
const playground_base = new URL('https://svelte.dev/playground');
|
if (server.ctx.sessionId && server.ctx.custom?.track) {
|
||||||
const playground_files: File[] = [];
|
await server.ctx.custom?.track?.(server.ctx.sessionId, 'playground-link');
|
||||||
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
if (!has_app_svelte) {
|
const result = await playground_link_handler({ files, name, tailwind });
|
||||||
return {
|
return {
|
||||||
isError: true,
|
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'text',
|
||||||
text: JSON.stringify({
|
text: JSON.stringify({ url: result.url }),
|
||||||
error: 'The files must contain an App.svelte file as the entry point',
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
|
// 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,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import { InMemoryTransport } from '@tmcp/transport-in-memory';
|
||||||
|
import { beforeEach, describe, expect, it } from 'vitest';
|
||||||
|
import { server } from '../../index.js';
|
||||||
|
|
||||||
|
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,
|
||||||
|
) {
|
||||||
|
const result = await session.callTool('svelte-autofixer', {
|
||||||
|
code,
|
||||||
|
desired_svelte_version,
|
||||||
|
filename: 'App.svelte',
|
||||||
|
async,
|
||||||
|
});
|
||||||
|
|
||||||
|
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"',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -5,6 +5,103 @@ import { add_compile_issues } from '../../autofixers/add-compile-issues.js';
|
|||||||
import { add_eslint_issues } from '../../autofixers/add-eslint-issues.js';
|
import { add_eslint_issues } from '../../autofixers/add-eslint-issues.js';
|
||||||
import { add_autofixers_issues } from '../../autofixers/add-autofixers-issues.js';
|
import { add_autofixers_issues } from '../../autofixers/add-autofixers-issues.js';
|
||||||
import { icons } from '../../icons/index.js';
|
import { icons } from '../../icons/index.js';
|
||||||
|
import { tool } from 'tmcp/utils';
|
||||||
|
|
||||||
|
const autofixer_schema = v.object({
|
||||||
|
code: v.string(),
|
||||||
|
desired_svelte_version: v.pipe(
|
||||||
|
v.union([v.string(), v.number()]),
|
||||||
|
v.description(
|
||||||
|
'The desired svelte version...if possible read this from the package.json of the user project, otherwise use some hint from the wording (if the user asks for runes it wants version 5). Default to 5 in case of doubt.',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
async: v.pipe(
|
||||||
|
v.optional(v.boolean()),
|
||||||
|
v.description(
|
||||||
|
'If true the code is an async component/module and might use await in the markup or top-level awaits in the script tag. If possible check the svelte.config.js/svelte.config.ts to check if the option is enabled otherwise asks the user if they prefer using it or not. You can only use this option if the version is 5.',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
filename: v.pipe(
|
||||||
|
v.optional(v.string()),
|
||||||
|
v.description(
|
||||||
|
'The filename of the component if available, it MUST be only the Component name with .svelte or .svelte.ts extension and not the entire path.',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
const autofixer_output_schema = v.object({
|
||||||
|
issues: v.array(v.string()),
|
||||||
|
suggestions: v.array(v.string()),
|
||||||
|
require_another_tool_call_after_fixing: v.boolean(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function svelte_autofixer_handler({
|
||||||
|
code,
|
||||||
|
desired_svelte_version: desired_svelte_version_unchecked,
|
||||||
|
async,
|
||||||
|
filename: filename_or_path,
|
||||||
|
}: v.InferInput<typeof autofixer_schema>) {
|
||||||
|
// we validate manually because some clients don't support union in the input schema (looking at you cursor)
|
||||||
|
const parsed_version = v.safeParse(
|
||||||
|
v.union([v.literal(4), v.literal(5), v.literal('4'), v.literal('5')]),
|
||||||
|
desired_svelte_version_unchecked,
|
||||||
|
);
|
||||||
|
if (parsed_version.success === false) {
|
||||||
|
throw new Error(
|
||||||
|
`The desired_svelte_version MUST be either 4 or 5 but received "${desired_svelte_version_unchecked}"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const desired_svelte_version = parsed_version.output;
|
||||||
|
|
||||||
|
if (async && +desired_svelte_version < 5) {
|
||||||
|
throw new Error('The async option can only be used with Svelte version 5 or higher.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const content: {
|
||||||
|
issues: string[];
|
||||||
|
suggestions: string[];
|
||||||
|
require_another_tool_call_after_fixing: boolean;
|
||||||
|
} = { issues: [], suggestions: [], require_another_tool_call_after_fixing: false };
|
||||||
|
try {
|
||||||
|
// just in case the LLM sends a full path we extract the filename...it's not really needed
|
||||||
|
// but it's nice to have a filename in the errors
|
||||||
|
|
||||||
|
const filename = filename_or_path ? basename(filename_or_path) : 'Component.svelte';
|
||||||
|
|
||||||
|
add_compile_issues(content, code, +desired_svelte_version, filename, async);
|
||||||
|
|
||||||
|
add_autofixers_issues(content, code, +desired_svelte_version, filename, async);
|
||||||
|
|
||||||
|
await add_eslint_issues(content, code, +desired_svelte_version, filename, async);
|
||||||
|
} catch (e: unknown) {
|
||||||
|
const error = e as Error & { start?: { line: number; column: number }; 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) {
|
export function svelte_autofixer(server: SvelteMcp) {
|
||||||
server.tool(
|
server.tool(
|
||||||
@@ -13,26 +110,8 @@ export function svelte_autofixer(server: SvelteMcp) {
|
|||||||
title: 'Svelte Autofixer',
|
title: 'Svelte Autofixer',
|
||||||
description:
|
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',
|
'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({
|
schema: autofixer_schema,
|
||||||
code: v.string(),
|
outputSchema: autofixer_output_schema,
|
||||||
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.',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
filename: v.pipe(
|
|
||||||
v.optional(v.string()),
|
|
||||||
v.description(
|
|
||||||
'The filename of the component if available, it MUST be only the Component name with .svelte or .svelte.ts extension and not the entire path.',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
outputSchema: v.object({
|
|
||||||
issues: v.array(v.string()),
|
|
||||||
suggestions: v.array(v.string()),
|
|
||||||
require_another_tool_call_after_fixing: v.boolean(),
|
|
||||||
}),
|
|
||||||
annotations: {
|
annotations: {
|
||||||
title: 'Svelte Autofixer',
|
title: 'Svelte Autofixer',
|
||||||
destructiveHint: false,
|
destructiveHint: false,
|
||||||
@@ -45,67 +124,30 @@ export function svelte_autofixer(server: SvelteMcp) {
|
|||||||
code,
|
code,
|
||||||
filename: filename_or_path,
|
filename: filename_or_path,
|
||||||
desired_svelte_version: desired_svelte_version_unchecked,
|
desired_svelte_version: desired_svelte_version_unchecked,
|
||||||
|
async,
|
||||||
}) => {
|
}) => {
|
||||||
// we validate manually because some clients don't support union in the input schema (looking at you cursor)
|
if (server.ctx.sessionId && server.ctx.custom?.track) {
|
||||||
const parsed_version = v.safeParse(
|
await server.ctx.custom?.track?.(server.ctx.sessionId, 'svelte-autofixer');
|
||||||
v.union([v.literal(4), v.literal(5), v.literal('4'), v.literal('5')]),
|
|
||||||
desired_svelte_version_unchecked,
|
|
||||||
);
|
|
||||||
if (parsed_version.success === false) {
|
|
||||||
return {
|
|
||||||
isError: true,
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: 'text',
|
|
||||||
text: `The desired_svelte_version MUST be either 4 or 5 but received "${desired_svelte_version_unchecked}"`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const desired_svelte_version = parsed_version.output;
|
|
||||||
|
|
||||||
const content: {
|
|
||||||
issues: string[];
|
|
||||||
suggestions: string[];
|
|
||||||
require_another_tool_call_after_fixing: boolean;
|
|
||||||
} = { issues: [], suggestions: [], require_another_tool_call_after_fixing: false };
|
|
||||||
try {
|
try {
|
||||||
// just in case the LLM sends a full path we extract the filename...it's not really needed
|
const content = await svelte_autofixer_handler({
|
||||||
// but it's nice to have a filename in the errors
|
code,
|
||||||
|
desired_svelte_version: desired_svelte_version_unchecked,
|
||||||
const filename = filename_or_path ? basename(filename_or_path) : 'Component.svelte';
|
async,
|
||||||
|
filename: filename_or_path,
|
||||||
add_compile_issues(content, code, +desired_svelte_version, filename);
|
});
|
||||||
|
return tool.structured(content);
|
||||||
add_autofixers_issues(content, code, +desired_svelte_version, filename);
|
} catch (e) {
|
||||||
|
const error = e as Error;
|
||||||
await add_eslint_issues(content, code, +desired_svelte_version, filename);
|
if (server.ctx.sessionId && server.ctx.custom?.track) {
|
||||||
} catch (e: unknown) {
|
await server.ctx.custom?.track?.(
|
||||||
const error = e as Error & { start?: { line: number; column: number } };
|
server.ctx.sessionId,
|
||||||
content.issues.push(
|
'svelte-autofixer-error',
|
||||||
`${error.message} at line ${error.start?.line}, column ${error.start?.column}`,
|
error.message,
|
||||||
);
|
|
||||||
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)`.",
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return tool.error(error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (content.issues.length > 0 || content.suggestions.length > 0) {
|
|
||||||
content.require_another_tool_call_after_fixing = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: 'text',
|
|
||||||
text: JSON.stringify(content),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
structuredContent: content,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,10 +24,18 @@ export const server = new McpServer(
|
|||||||
instructions:
|
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.',
|
'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<{
|
||||||
|
db: LibSQLDatabase<Schema>;
|
||||||
|
track?: (sessionId: string, event: string, extra?: string) => Promise<void>;
|
||||||
|
}>();
|
||||||
|
|
||||||
export type SvelteMcp = typeof server;
|
export type SvelteMcp = typeof server;
|
||||||
|
|
||||||
setup_tools(server);
|
setup_tools(server);
|
||||||
setup_resources(server);
|
setup_resources(server);
|
||||||
setup_prompts(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);
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,5 +1,87 @@
|
|||||||
# @sveltejs/mcp
|
# @sveltejs/mcp
|
||||||
|
|
||||||
|
## 0.1.20
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- fix: turn off no-inspect in eslint for mcp ([`2245cb2`](https://github.com/sveltejs/mcp/commit/2245cb2dc9e2d217869b6a800795ce59ffb40c51))
|
||||||
|
|
||||||
|
## 0.1.19
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- chore: update svelte ([`7447744`](https://github.com/sveltejs/mcp/commit/74477448cea44ec21684ea4d39f2c5c7133b5150))
|
||||||
|
|
||||||
|
## 0.1.18
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- feat: expose playground link as MCP App ([#138](https://github.com/sveltejs/mcp/pull/138))
|
||||||
|
|
||||||
|
## 0.1.17
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- fix: add suggestion for snippets declared in script tag ([#132](https://github.com/sveltejs/mcp/pull/132))
|
||||||
|
|
||||||
|
## 0.1.16
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- feat: expose tools as JS api + cli ([#128](https://github.com/sveltejs/mcp/pull/128))
|
||||||
|
|
||||||
|
## 0.1.15
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- fix: server.json version + update publisher ([`9dfb4de`](https://github.com/sveltejs/mcp/commit/9dfb4dedb42837c40c4e660f0f816d7cf9081fc4))
|
||||||
|
|
||||||
|
## 0.1.14
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- fix: improve prompt to reduce token usage ([#124](https://github.com/sveltejs/mcp/pull/124))
|
||||||
|
|
||||||
|
## 0.1.13
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- fix: revert name change and add title ([`98efa1e`](https://github.com/sveltejs/mcp/commit/98efa1e09ebcca7827b10dc6bc8e1699fc1e5171))
|
||||||
|
|
||||||
|
## 0.1.12
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- fix: update server name on mcp registry ([`60297b3`](https://github.com/sveltejs/mcp/commit/60297b3c49bf110b48908e61b5d5d902ea1bdf39))
|
||||||
|
|
||||||
|
- chore: update tmcp ([#99](https://github.com/sveltejs/mcp/pull/99))
|
||||||
|
|
||||||
|
## 0.1.11
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- fix: add `async` parameter to `svelte-autofixer` ([#94](https://github.com/sveltejs/mcp/pull/94))
|
||||||
|
|
||||||
|
- fix: install latest eslint svelte packages to support `$state.eager` ([`f6ce89f`](https://github.com/sveltejs/mcp/commit/f6ce89ff34faabc3d746a350ea347298ecfed2ec))
|
||||||
|
|
||||||
|
## 0.1.10
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- fix: add icons to `server.json` ([`02c951b`](https://github.com/sveltejs/mcp/commit/02c951baa86ac8103ffc158a202c06cfe6b15c01))
|
||||||
|
|
||||||
|
- fix: add `preferred-frame-size` to UI resource ([`3fabcc0`](https://github.com/sveltejs/mcp/commit/3fabcc0f9bfee916c0deb9c2ffa931ed2168af2d))
|
||||||
|
|
||||||
|
- feat: support: `$state.eager` ([#90](https://github.com/sveltejs/mcp/pull/90))
|
||||||
|
|
||||||
|
## 0.1.9
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- feat: return `mcp-ui` resource from `playground-link` ([#84](https://github.com/sveltejs/mcp/pull/84))
|
||||||
|
|
||||||
|
- feat: suggest against js variables in css ([#78](https://github.com/sveltejs/mcp/pull/78))
|
||||||
|
|
||||||
## 0.1.8
|
## 0.1.8
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
704f15b3ccba80990fbaaeb858e2e893120dea3ebb9340e04a36065987e089c5 mcp-publisher_darwin_amd64.tar.gz
|
|
||||||
9eadadeb80998cd5b29e009dd3959634730d3754b9e201c0a3bb39dd6ae85933 mcp-publisher_darwin_amd64.tar.gz.sbom.json
|
|
||||||
f80265eadc6b052d2885a7dfa47a1ecc7ca95926c143197ced747317793dcc11 mcp-publisher_darwin_arm64.tar.gz
|
|
||||||
f063c462512eed8f2ce9610a6a125534cb552060ddb5364d8d49b32196416fbe mcp-publisher_darwin_arm64.tar.gz.sbom.json
|
|
||||||
1113b9d6bf59b000966c4f17752cf87b51db03dcc5482721421fd843ce3bf048 mcp-publisher_linux_amd64.tar.gz
|
|
||||||
d3e46855b3b906aa84571b0d16384ad6074fd08709108a47d724908e91947b9d mcp-publisher_linux_amd64.tar.gz.sbom.json
|
|
||||||
34be13ec07490ab1250194d56820af222b49b85db6062ff848d33f9cf6eb41ce mcp-publisher_linux_arm64.tar.gz
|
|
||||||
a1ff3c26d53007c98301b747f97e5e241bfdb860b6a761db8627c9ba9ba71d88 mcp-publisher_linux_arm64.tar.gz.sbom.json
|
|
||||||
9ba8b538744652c4837d08b7b83ae85d2dbea98c897cfcf4d90835df4fe075b3 mcp-publisher_windows_amd64.tar.gz
|
|
||||||
924a0adcebd9360aed4ee5959e664e4e99b8259ed871ac91be5ace5657afba7d mcp-publisher_windows_amd64.tar.gz.sbom.json
|
|
||||||
a3eeab18ee6fd1d76d7ec153779ebe1bb404d7ea561f117781174e4396d90565 mcp-publisher_windows_arm64.tar.gz
|
|
||||||
9351f3acc39f89402ce5f72be1b45f3bf1b16f8441c97496195188f276e6c227 mcp-publisher_windows_arm64.tar.gz.sbom.json
|
|
||||||
9767b6f2afe1b60c597d4c66468d491be3466b25de8b4d4e7a21e61950856ef7 registry-1.3.3.tar.gz
|
|
||||||
a7867ff2c15905a765dc77636d86241ea04a17d0a90f086913a3cea4fe899213 registry_darwin_amd64.tar.gz
|
|
||||||
f0ed44b6ebdfcba40daefe7ca556f385de0cbae95dca3077c406f75cdb6abb9b registry_darwin_amd64.tar.gz.sbom.json
|
|
||||||
b4d93d6fc2d831d287cde79953822b434cb8406eea4f38f180628df227f6569e registry_darwin_arm64.tar.gz
|
|
||||||
dde0ed7f350eda98eb6f474d19aa0e4c717dd9f120609949acf05d7986cf6fcc registry_darwin_arm64.tar.gz.sbom.json
|
|
||||||
6772a573828f3482a192c88dac4223ee71ff4f521c8819480a194757e46a8272 registry_linux_amd64.tar.gz
|
|
||||||
ac177354d5f43f86bdf7f36738837a2792ba136198209eb61d4d4dd9ab408853 registry_linux_amd64.tar.gz.sbom.json
|
|
||||||
61c1fb9126977e0694d5aad8990a9d4c2d0e933e6aa469789559fad3865e8b7f registry_linux_arm64.tar.gz
|
|
||||||
9b81dfe2c81c49f7731072b8c217f05d609f7094ea94aaf9491f33880f2dec7f registry_linux_arm64.tar.gz.sbom.json
|
|
||||||
7fb5752de50a841614c6990f20807820bda33a72eaf7be51ae6cbd4e9acb2b87 registry_windows_amd64.tar.gz
|
|
||||||
fd44139231d4725c75b2a5a96fb670d3137f628db91c8281f6348e842792d514 registry_windows_amd64.tar.gz.sbom.json
|
|
||||||
a85ba7eb1a06a9e668b8a2097e00ab010e454a2afeb4e29ebeb7b702a31cccd3 registry_windows_arm64.tar.gz
|
|
||||||
93503683d32a2971b741e87f2879cb2a3f41acd184c618e04d17a98926915f2d registry_windows_arm64.tar.gz.sbom.json
|
|
||||||
25
packages/mcp-stdio/checksums/registry_1.4.0_checksums.txt
Normal file
25
packages/mcp-stdio/checksums/registry_1.4.0_checksums.txt
Normal 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
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@sveltejs/mcp",
|
"name": "@sveltejs/mcp",
|
||||||
"version": "0.1.8",
|
"version": "0.1.20",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"mcpName": "dev.svelte/mcp",
|
"mcpName": "dev.svelte/mcp",
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
"url": "https://github.com/sveltejs/mcp/issues"
|
"url": "https://github.com/sveltejs/mcp/issues"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"svelte-mcp": "./dist/index.js"
|
"svelte-mcp": "./dist/index.mjs"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -19,6 +19,12 @@
|
|||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/handlers.d.mts",
|
||||||
|
"import": "./dist/handlers.mjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
@@ -32,15 +38,16 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/mcp-server": "workspace:^",
|
"@sveltejs/mcp-server": "workspace:^",
|
||||||
"@tmcp/transport-stdio": "^0.3.1",
|
"@tmcp/transport-stdio": "catalog:tmcp",
|
||||||
"@types/node": "^22.15.17",
|
"@types/node": "catalog:tooling",
|
||||||
"publint": "^0.3.13",
|
"publint": "catalog:tooling",
|
||||||
"tsdown": "^0.15.0",
|
"tsdown": "catalog:tooling",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "catalog:tooling",
|
||||||
"vitest": "^3.1.3"
|
"vitest": "catalog:tooling"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"eslint": "^9.36.0",
|
"eslint": "catalog:lint",
|
||||||
"tmcp": "^1.15.0"
|
"sade": "catalog:tooling",
|
||||||
|
"tmcp": "catalog:tmcp"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
||||||
"name": "dev.svelte/mcp",
|
"name": "dev.svelte/mcp",
|
||||||
|
"title": "Svelte MCP",
|
||||||
"description": "The official Svelte MCP server providing docs and autofixing tools for Svelte development",
|
"description": "The official Svelte MCP server providing docs and autofixing tools for Svelte development",
|
||||||
"repository": {
|
"repository": {
|
||||||
"id": "1054419133",
|
"id": "1054419133",
|
||||||
@@ -8,13 +9,23 @@
|
|||||||
"subfolder": "packages/mcp-stdio",
|
"subfolder": "packages/mcp-stdio",
|
||||||
"source": "github"
|
"source": "github"
|
||||||
},
|
},
|
||||||
"version": "0.1.8",
|
"version": "0.1.20",
|
||||||
"websiteUrl": "https://svelte.dev/docs/mcp/overview",
|
"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": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"registryType": "npm",
|
"registryType": "npm",
|
||||||
"identifier": "@sveltejs/mcp",
|
"identifier": "@sveltejs/mcp",
|
||||||
"version": "0.1.8",
|
"version": "0.1.20",
|
||||||
"runtimeHint": "npx",
|
"runtimeHint": "npx",
|
||||||
"transport": {
|
"transport": {
|
||||||
"type": "stdio"
|
"type": "stdio"
|
||||||
|
|||||||
6
packages/mcp-stdio/src/handlers.ts
Normal file
6
packages/mcp-stdio/src/handlers.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export {
|
||||||
|
list_sections_handler as listSections,
|
||||||
|
get_documentation_handler as getDocumentation,
|
||||||
|
svelte_autofixer_handler as svelteAutofixer,
|
||||||
|
playground_link_handler as playgroundLink,
|
||||||
|
} from '@sveltejs/mcp-server/handlers';
|
||||||
@@ -1,7 +1,65 @@
|
|||||||
#! /usr/bin/env node
|
#! /usr/bin/env node
|
||||||
import { server } from '@sveltejs/mcp-server';
|
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 { StdioTransport } from '@tmcp/transport-stdio';
|
||||||
|
import sade from 'sade';
|
||||||
|
|
||||||
const transport = new StdioTransport(server);
|
const cli = sade('svelte-mcp');
|
||||||
|
|
||||||
transport.listen();
|
cli.command('__mcp', '', { default: true }).action(() => {
|
||||||
|
const transport = new StdioTransport(server);
|
||||||
|
transport.listen();
|
||||||
|
});
|
||||||
|
|
||||||
|
cli
|
||||||
|
.command('list-sections')
|
||||||
|
.describe('List all the available documentation sections')
|
||||||
|
.action(async () => {
|
||||||
|
console.log(await list_sections_handler());
|
||||||
|
});
|
||||||
|
|
||||||
|
cli
|
||||||
|
.command('get-documentation <sections>')
|
||||||
|
.describe('Get documentation for specified sections, separated by commas')
|
||||||
|
.action(async (sections) => {
|
||||||
|
console.log(await get_documentation_handler({ section: sections.split(',') }));
|
||||||
|
});
|
||||||
|
|
||||||
|
cli
|
||||||
|
.command('svelte-autofixer <code_or_path>')
|
||||||
|
.describe(
|
||||||
|
'Detect and suggest fixes for Svelte code issues, because the terminal will substitute variables `$` should be correctly escaped',
|
||||||
|
)
|
||||||
|
.option('--async', 'Wether the project is using async svelte or not', false)
|
||||||
|
.option('--svelte-version', 'Which version of svelte to use...it can be 4 or 5', 5)
|
||||||
|
.action(async (code_or_path, { async, 'svelte-version': version }) => {
|
||||||
|
let code = code_or_path;
|
||||||
|
|
||||||
|
let is_path = false;
|
||||||
|
|
||||||
|
if (existsSync(code_or_path)) {
|
||||||
|
console.log('Detected file path, reading file...');
|
||||||
|
code = await readFile(code_or_path, 'utf-8');
|
||||||
|
is_path = true;
|
||||||
|
} else {
|
||||||
|
console.log('File not found, treating input as code...');
|
||||||
|
}
|
||||||
|
|
||||||
|
const desired_svelte_version = +version;
|
||||||
|
|
||||||
|
const result = await svelte_autofixer_handler({
|
||||||
|
code,
|
||||||
|
async: Boolean(async),
|
||||||
|
desired_svelte_version,
|
||||||
|
filename: is_path ? code_or_path : undefined,
|
||||||
|
});
|
||||||
|
console.log(result);
|
||||||
|
});
|
||||||
|
|
||||||
|
cli.parse(process.argv);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { defineConfig } from 'tsdown';
|
|||||||
|
|
||||||
export default defineConfig([
|
export default defineConfig([
|
||||||
{
|
{
|
||||||
entry: ['./src/index.ts'],
|
entry: ['./src/index.ts', './src/handlers.ts'],
|
||||||
platform: 'node',
|
platform: 'node',
|
||||||
define: {
|
define: {
|
||||||
// some eslint-plugin-svelte code expects __filename to exists but in an ESM environment it does not.
|
// 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.
|
// the require would fail once executed in a project without eslint installed.
|
||||||
external: ['eslint'],
|
external: ['eslint'],
|
||||||
publint: true,
|
publint: true,
|
||||||
dts: false,
|
dts: {
|
||||||
|
eager: true,
|
||||||
|
},
|
||||||
treeshake: true,
|
treeshake: true,
|
||||||
clean: true,
|
clean: true,
|
||||||
target: 'esnext',
|
target: 'esnext',
|
||||||
|
inlineOnly: false,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|||||||
19
packages/opencode/CHANGELOG.md
Normal file
19
packages/opencode/CHANGELOG.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# @sveltejs/opencode
|
||||||
|
|
||||||
|
## 0.1.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- feat: distribute skills through opencode plugin ([#151](https://github.com/sveltejs/mcp/pull/151))
|
||||||
|
|
||||||
|
## 0.0.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- chore: add README to opencode plugin ([`71295bc`](https://github.com/sveltejs/mcp/commit/71295bc11fb7bac6703e655f5fddead29967353c))
|
||||||
|
|
||||||
|
## 0.0.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- fix: show toast after a few seconds to avoid race condition ([`57e2d1d`](https://github.com/sveltejs/mcp/commit/57e2d1def1f5590d0a3dd6d269ac39f6397ffecf))
|
||||||
75
packages/opencode/README.md
Normal file
75
packages/opencode/README.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# @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
|
||||||
|
|
||||||
|
The default configuration:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/sveltejs/mcp/refs/heads/main/packages/opencode/schema.json",
|
||||||
|
"mcp": {
|
||||||
|
"type": "remote",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"subagent": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"instructions": {
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration Options
|
||||||
|
|
||||||
|
| Option | Type | Default | Description |
|
||||||
|
| ---------------------- | ----------------------- | ---------- | -------------------------------------------------------------------------------- |
|
||||||
|
| `mcp.type` | `"remote"` \| `"local"` | `"remote"` | Use the remote server at `mcp.svelte.dev` or run locally via `npx @sveltejs/mcp` |
|
||||||
|
| `mcp.enabled` | `boolean` | `true` | Enable/disable the MCP server |
|
||||||
|
| `subagent.enabled` | `boolean` | `true` | Enable/disable the Svelte file editor subagent |
|
||||||
|
| `instructions.enabled` | `boolean` | `true` | Enable/disable agent instructions injection |
|
||||||
|
|
||||||
|
### Config File Location
|
||||||
|
|
||||||
|
Place your configuration at one of these locations:
|
||||||
|
|
||||||
|
- `~/.config/opencode/svelte.json` (global)
|
||||||
|
- `$OPENCODE_CONFIG_DIR/svelte.json` (if `OPENCODE_CONFIG_DIR` is set, takes priority)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
159
packages/opencode/config.ts
Normal file
159
packages/opencode/config.ts
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
import type { PluginInput } from '@opencode-ai/plugin';
|
||||||
|
import { existsSync, readFileSync } from 'fs';
|
||||||
|
import { homedir } from 'os';
|
||||||
|
import { join } from 'path';
|
||||||
|
import * as v from 'valibot';
|
||||||
|
|
||||||
|
const default_config = {
|
||||||
|
mcp: {
|
||||||
|
type: 'remote' as 'remote' | 'local',
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
subagent: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
instructions: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
skills: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const config_schema = v.object({
|
||||||
|
mcp: v.optional(
|
||||||
|
v.object({
|
||||||
|
type: v.optional(v.picklist(['remote', 'local'])),
|
||||||
|
enabled: v.optional(v.boolean()),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
subagent: v.optional(
|
||||||
|
v.object({
|
||||||
|
enabled: v.optional(v.boolean()),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
instructions: v.optional(
|
||||||
|
v.object({
|
||||||
|
enabled: v.optional(v.boolean()),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
skills: v.optional(
|
||||||
|
v.object({
|
||||||
|
enabled: v.optional(v.boolean()),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type McpConfig = v.InferInput<typeof config_schema>;
|
||||||
|
|
||||||
|
const GLOBAL_CONFIG_DIR = join(homedir(), '.config', 'opencode');
|
||||||
|
const GLOBAL_CONFIG_PATH = join(GLOBAL_CONFIG_DIR, 'svelte.json');
|
||||||
|
|
||||||
|
interface ConfigLoadResult {
|
||||||
|
data: Record<string, unknown> | null;
|
||||||
|
parse_error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_config_paths() {
|
||||||
|
// Global: ~/.config/opencode/svelte.json
|
||||||
|
let global_path: string | null = null;
|
||||||
|
if (existsSync(GLOBAL_CONFIG_PATH)) {
|
||||||
|
global_path = GLOBAL_CONFIG_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom config directory: $OPENCODE_CONFIG_DIR/svelte.json
|
||||||
|
let config_dir_path: string | null = null;
|
||||||
|
const opencode_config_dir = process.env.OPENCODE_CONFIG_DIR;
|
||||||
|
if (opencode_config_dir) {
|
||||||
|
const config_json = join(opencode_config_dir, 'svelte.json');
|
||||||
|
if (existsSync(config_json)) {
|
||||||
|
config_dir_path = config_json;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// returning config_dir first so it has higher priority
|
||||||
|
return [config_dir_path, global_path];
|
||||||
|
}
|
||||||
|
|
||||||
|
function load_config_file(config_path: string): ConfigLoadResult {
|
||||||
|
let file_content: string;
|
||||||
|
try {
|
||||||
|
file_content = readFileSync(config_path, 'utf-8');
|
||||||
|
} catch {
|
||||||
|
// File doesn't exist or can't be read
|
||||||
|
return { data: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(file_content);
|
||||||
|
if (parsed === undefined || parsed === null) {
|
||||||
|
return { data: null, parse_error: 'Config file is empty or invalid' };
|
||||||
|
}
|
||||||
|
return { data: parsed };
|
||||||
|
} catch (error: unknown) {
|
||||||
|
return {
|
||||||
|
data: null,
|
||||||
|
parse_error: error instanceof Error ? error.message : 'Failed to parse config',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function merge_with_defaults(user_config: Partial<McpConfig>): McpConfig {
|
||||||
|
return {
|
||||||
|
mcp: {
|
||||||
|
...default_config.mcp,
|
||||||
|
...user_config.mcp,
|
||||||
|
},
|
||||||
|
subagent: {
|
||||||
|
...default_config.subagent,
|
||||||
|
...user_config.subagent,
|
||||||
|
},
|
||||||
|
instructions: {
|
||||||
|
...default_config.instructions,
|
||||||
|
...user_config.instructions,
|
||||||
|
},
|
||||||
|
skills: {
|
||||||
|
...default_config.skills,
|
||||||
|
...user_config.skills,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function get_mcp_config(ctx: PluginInput) {
|
||||||
|
const config_paths = get_config_paths();
|
||||||
|
for (const path of config_paths) {
|
||||||
|
if (path && existsSync(path)) {
|
||||||
|
const result = load_config_file(path);
|
||||||
|
if (result.parse_error) {
|
||||||
|
setTimeout(() => {
|
||||||
|
ctx.client.tui.showToast({
|
||||||
|
body: {
|
||||||
|
title: 'Svelte: Invalid opencode plugin config',
|
||||||
|
message: `${result.parse_error}\nUsing default values`,
|
||||||
|
variant: 'warning',
|
||||||
|
duration: 7000,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, 7000);
|
||||||
|
return default_config;
|
||||||
|
}
|
||||||
|
const parsed = v.safeParse(config_schema, result.data);
|
||||||
|
if (parsed.success) {
|
||||||
|
return merge_with_defaults(parsed.output);
|
||||||
|
} else {
|
||||||
|
setTimeout(() => {
|
||||||
|
ctx.client.tui.showToast({
|
||||||
|
body: {
|
||||||
|
title: 'Svelte: Invalid opencode plugin config',
|
||||||
|
message: `${result.parse_error}\nUsing default values`,
|
||||||
|
variant: 'warning',
|
||||||
|
duration: 7000,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, 7000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return default_config;
|
||||||
|
}
|
||||||
80
packages/opencode/index.ts
Normal file
80
packages/opencode/index.ts
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import type { Plugin } from '@opencode-ai/plugin';
|
||||||
|
import { readdir } from 'node:fs/promises';
|
||||||
|
import { dirname, join } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { get_mcp_config } from './config.js';
|
||||||
|
|
||||||
|
const current_dir = dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
|
export const svelte_plugin: Plugin = async (ctx) => {
|
||||||
|
return {
|
||||||
|
async config(input) {
|
||||||
|
input.agent ??= {};
|
||||||
|
input.mcp ??= {};
|
||||||
|
input.instructions ??= [];
|
||||||
|
// @ts-expect-error -- types are wrong in the opencode package...will fix there and remove this
|
||||||
|
input.skills ??= [];
|
||||||
|
// by default we use svelte as the name for the svelte MCP server
|
||||||
|
let svelte_mcp_name = 'svelte';
|
||||||
|
// we loop over every mcp server to see if any of them is already the svelte MCP server
|
||||||
|
for (const name in input.mcp) {
|
||||||
|
const mcp = input.mcp[name];
|
||||||
|
if (
|
||||||
|
(mcp?.type === 'remote' && mcp.url.includes('https://mcp.svelte.dev/mcp')) ||
|
||||||
|
(mcp?.type === 'local' &&
|
||||||
|
mcp.command.some((cmd: string) => cmd.includes('@sveltejs/mcp')))
|
||||||
|
) {
|
||||||
|
// if we found the svelte MCP server, we store its name and break
|
||||||
|
svelte_mcp_name = name;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const mcp_config = get_mcp_config(ctx);
|
||||||
|
|
||||||
|
if (mcp_config.instructions?.enabled !== false) {
|
||||||
|
const instructions_dir = join(current_dir, 'instructions');
|
||||||
|
const instructions_paths = await readdir(instructions_dir);
|
||||||
|
input.instructions.push(...instructions_paths.map((file) => join(instructions_dir, file)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mcp_config.skills?.enabled !== false) {
|
||||||
|
const skills_dir = join(current_dir, 'skills');
|
||||||
|
// @ts-expect-error -- skills is a new opencode feature
|
||||||
|
input.skills.push(skills_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the user doesn't have the MCP server already we add one based on config
|
||||||
|
if (!input.mcp[svelte_mcp_name] && mcp_config.mcp?.enabled !== false) {
|
||||||
|
if (mcp_config.mcp?.type === 'remote') {
|
||||||
|
input.mcp[svelte_mcp_name] = {
|
||||||
|
type: 'remote',
|
||||||
|
url: 'https://mcp.svelte.dev/mcp',
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
input.mcp[svelte_mcp_name] = {
|
||||||
|
type: 'local',
|
||||||
|
command: ['npx', '-y', '@sveltejs/mcp'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mcp_config.subagent?.enabled !== false) {
|
||||||
|
// we add the editor subagent that will be used when editing Svelte files to prevent wasting context on the main agent
|
||||||
|
input.agent['svelte-file-editor'] = {
|
||||||
|
color: '#ff3e00',
|
||||||
|
mode: 'subagent',
|
||||||
|
prompt: `You are a specialized Svelte coder. 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 solve them before summarizing the changes for the main agent.`,
|
||||||
|
description:
|
||||||
|
'Specialized Svelte 5 code editor. MUST BE USED PROACTIVELY when creating, editing, or reviewing any .svelte file or .svelte.ts/.svelte.js module and MUST use the tools from the MCP server. Fetches relevant documentation and validates code using the Svelte MCP server tools.',
|
||||||
|
permission: {
|
||||||
|
bash: 'ask',
|
||||||
|
edit: 'allow',
|
||||||
|
webfetch: 'ask',
|
||||||
|
},
|
||||||
|
tools: {
|
||||||
|
[`${svelte_mcp_name}_*`]: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
23
packages/opencode/instructions/opencode-agents.md
Normal file
23
packages/opencode/instructions/opencode-agents.md
Normal 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 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.
|
||||||
36
packages/opencode/package.json
Normal file
36
packages/opencode/package.json
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "@sveltejs/opencode",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"type": "module",
|
||||||
|
"license": "MIT",
|
||||||
|
"homepage": "https://github.com/sveltejs/mcp#readme",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/sveltejs/mcp/issues"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"check": "tsc --noEmit",
|
||||||
|
"generate-schema": "node --import node-resolve-ts/register scripts/generate-schema.ts"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"index.ts",
|
||||||
|
"config.ts",
|
||||||
|
"instructions",
|
||||||
|
"skills"
|
||||||
|
],
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/sveltejs/mcp.git",
|
||||||
|
"path": "packages/opencode"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"valibot": "catalog:tooling"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@opencode-ai/plugin": "catalog:ai",
|
||||||
|
"@valibot/to-json-schema": "catalog:tooling",
|
||||||
|
"@types/node": "catalog:tooling"
|
||||||
|
}
|
||||||
|
}
|
||||||
49
packages/opencode/schema.json
Normal file
49
packages/opencode/schema.json
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"mcp": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"enum": [
|
||||||
|
"remote",
|
||||||
|
"local"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": []
|
||||||
|
},
|
||||||
|
"subagent": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": []
|
||||||
|
},
|
||||||
|
"instructions": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": []
|
||||||
|
},
|
||||||
|
"skills": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [],
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#"
|
||||||
|
}
|
||||||
8
packages/opencode/scripts/generate-schema.ts
Normal file
8
packages/opencode/scripts/generate-schema.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { toJsonSchema } from '@valibot/to-json-schema';
|
||||||
|
import { config_schema } from '../config.js';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
const json_schema = toJsonSchema(config_schema);
|
||||||
|
|
||||||
|
fs.writeFileSync(path.resolve('./schema.json'), JSON.stringify(json_schema, null, '\t'));
|
||||||
66
packages/opencode/skills/svelte-code-writer/SKILL.md
Normal file
66
packages/opencode/skills/svelte-code-writer/SKILL.md
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
---
|
||||||
|
name: svelte-code-writer
|
||||||
|
description: CLI tools for Svelte 5 documentation lookup and code analysis. MUST be used whenever creating or editing any Svelte component (.svelte) or Svelte module (.svelte.ts/.svelte.js). If possible, this skill should be executed within the svelte-file-editor agent for optimal results.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Svelte 5 Code Writer
|
||||||
|
|
||||||
|
## CLI Tools
|
||||||
|
|
||||||
|
You have access to `@sveltejs/mcp` CLI for Svelte-specific assistance. Use these commands via `npx`:
|
||||||
|
|
||||||
|
### List Documentation Sections
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx @sveltejs/mcp list-sections
|
||||||
|
```
|
||||||
|
|
||||||
|
Lists all available Svelte 5 and SvelteKit documentation sections with titles and paths.
|
||||||
|
|
||||||
|
### Get Documentation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx @sveltejs/mcp get-documentation "<section1>,<section2>,..."
|
||||||
|
```
|
||||||
|
|
||||||
|
Retrieves full documentation for specified sections. Use after `list-sections` to fetch relevant docs.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx @sveltejs/mcp get-documentation "$state,$derived,$effect"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Svelte Autofixer
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx @sveltejs/mcp svelte-autofixer "<code_or_path>" [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
Analyzes Svelte code and suggests fixes for common issues.
|
||||||
|
|
||||||
|
**Options:**
|
||||||
|
|
||||||
|
- `--async` - Enable async Svelte mode (default: false)
|
||||||
|
- `--svelte-version` - Target version: 4 or 5 (default: 5)
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Analyze inline code (escape $ as \$)
|
||||||
|
npx @sveltejs/mcp svelte-autofixer '<script>let count = \$state(0);</script>'
|
||||||
|
|
||||||
|
# Analyze a file
|
||||||
|
npx @sveltejs/mcp svelte-autofixer ./src/lib/Component.svelte
|
||||||
|
|
||||||
|
# Target Svelte 4
|
||||||
|
npx @sveltejs/mcp svelte-autofixer ./Component.svelte --svelte-version 4
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important:** When passing code with runes (`$state`, `$derived`, etc.) via the terminal, escape the `$` character as `\$` to prevent shell variable substitution.
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. **Uncertain about syntax?** Run `list-sections` then `get-documentation` for relevant topics
|
||||||
|
2. **Reviewing/debugging?** Run `svelte-autofixer` on the code to detect issues
|
||||||
|
3. **Always validate** - Run `svelte-autofixer` before finalizing any Svelte component
|
||||||
5
packages/opencode/tsconfig.json
Normal file
5
packages/opencode/tsconfig.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"include": ["index.ts", "config.ts", "scripts/*"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
17
plugins/svelte/.claude-plugin/plugin.json
Normal file
17
plugins/svelte/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "svelte",
|
||||||
|
"description": "A plugin for all things related to Svelte development, MCP, skills, and more.",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "Svelte"
|
||||||
|
},
|
||||||
|
"lspServers": {
|
||||||
|
"svelte": {
|
||||||
|
"command": "svelte-language-server",
|
||||||
|
"args": ["--stdio"],
|
||||||
|
"extensionToLanguage": {
|
||||||
|
".svelte": "svelte"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
plugins/svelte/.mcp.json
Normal file
8
plugins/svelte/.mcp.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"svelte": {
|
||||||
|
"type": "http",
|
||||||
|
"url": "https://mcp.svelte.dev/mcp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
64
plugins/svelte/agents/svelte-file-editor.md
Normal file
64
plugins/svelte/agents/svelte-file-editor.md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
---
|
||||||
|
name: svelte-file-editor
|
||||||
|
description: Specialized Svelte 5 code editor. MUST BE USED PROACTIVELY when creating, editing, or reviewing any .svelte file or .svelte.ts/.svelte.js module and MUST use the tools from the MCP server. Fetches relevant documentation and validates code using the Svelte MCP server tools.
|
||||||
|
permissionMode: acceptEdits
|
||||||
|
---
|
||||||
|
|
||||||
|
You are a Svelte 5 expert responsible for writing, editing, and validating Svelte components and modules. You have access to the Svelte MCP server which provides documentation and code analysis tools. Always use the tools from the svelte MCP server to fetch documentation with `get_documentation` and validating the code with `svelte_autofixer`. If the autofixer returns any issue or suggestions try to solve them.
|
||||||
|
|
||||||
|
## Available MCP Tools
|
||||||
|
|
||||||
|
### 1. list-sections
|
||||||
|
|
||||||
|
Lists all available Svelte 5 and SvelteKit documentation sections with titles and paths. Use this first to discover what documentation is available.
|
||||||
|
|
||||||
|
### 2. get-documentation
|
||||||
|
|
||||||
|
Retrieves full documentation for specified sections. Accepts a single section name or an array of section names. Use after `list-sections` to fetch relevant docs for the task at hand.
|
||||||
|
|
||||||
|
**Example sections:** `$state`, `$derived`, `$effect`, `$props`, `$bindable`, `snippets`, `routing`, `load functions`
|
||||||
|
|
||||||
|
### 3. svelte-autofixer
|
||||||
|
|
||||||
|
Analyzes Svelte code and returns suggestions to fix issues. Pass the component code directly to this tool. It will detect common mistakes like:
|
||||||
|
|
||||||
|
- Using `$effect` instead of `$derived` for computations
|
||||||
|
- Missing cleanup in effects
|
||||||
|
- Svelte 4 syntax (`on:click`, `export let`, `<slot>`)
|
||||||
|
- Missing keys in `{#each}` blocks
|
||||||
|
- And more
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
When invoked to work on a Svelte file:
|
||||||
|
|
||||||
|
### 1. Gather Context (if needed)
|
||||||
|
|
||||||
|
If you're uncertain about Svelte 5 syntax or patterns, use the MCP tools:
|
||||||
|
|
||||||
|
1. Call `list-sections` to see available documentation
|
||||||
|
2. Call `get-documentation` with relevant section names
|
||||||
|
|
||||||
|
### 2. Read the Target File
|
||||||
|
|
||||||
|
Read the file to understand the current implementation.
|
||||||
|
|
||||||
|
### 3. Make Changes
|
||||||
|
|
||||||
|
Apply edits following Svelte 5 best practices:
|
||||||
|
|
||||||
|
### 4. Validate Changes
|
||||||
|
|
||||||
|
After editing, ALWAYS call `svelte-autofixer` with the updated code to check for issues.
|
||||||
|
|
||||||
|
### 5. Fix Any Issues
|
||||||
|
|
||||||
|
If the autofixer reports problems, fix them and re-validate until no issues remain.
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
After completing your work, provide:
|
||||||
|
|
||||||
|
1. Summary of changes made
|
||||||
|
2. Any issues found and fixed by the autofixer
|
||||||
|
3. Recommendations for further improvements (if any)
|
||||||
66
plugins/svelte/skills/svelte-code-writer/SKILL.md
Normal file
66
plugins/svelte/skills/svelte-code-writer/SKILL.md
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
---
|
||||||
|
name: svelte-code-writer
|
||||||
|
description: CLI tools for Svelte 5 documentation lookup and code analysis. MUST be used whenever creating or editing any Svelte component (.svelte) or Svelte module (.svelte.ts/.svelte.js). If possible, this skill should be executed within the svelte-file-editor agent for optimal results.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Svelte 5 Code Writer
|
||||||
|
|
||||||
|
## CLI Tools
|
||||||
|
|
||||||
|
You have access to `@sveltejs/mcp` CLI for Svelte-specific assistance. Use these commands via `npx`:
|
||||||
|
|
||||||
|
### List Documentation Sections
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx @sveltejs/mcp list-sections
|
||||||
|
```
|
||||||
|
|
||||||
|
Lists all available Svelte 5 and SvelteKit documentation sections with titles and paths.
|
||||||
|
|
||||||
|
### Get Documentation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx @sveltejs/mcp get-documentation "<section1>,<section2>,..."
|
||||||
|
```
|
||||||
|
|
||||||
|
Retrieves full documentation for specified sections. Use after `list-sections` to fetch relevant docs.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx @sveltejs/mcp get-documentation "$state,$derived,$effect"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Svelte Autofixer
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx @sveltejs/mcp svelte-autofixer "<code_or_path>" [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
Analyzes Svelte code and suggests fixes for common issues.
|
||||||
|
|
||||||
|
**Options:**
|
||||||
|
|
||||||
|
- `--async` - Enable async Svelte mode (default: false)
|
||||||
|
- `--svelte-version` - Target version: 4 or 5 (default: 5)
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Analyze inline code (escape $ as \$)
|
||||||
|
npx @sveltejs/mcp svelte-autofixer '<script>let count = \$state(0);</script>'
|
||||||
|
|
||||||
|
# Analyze a file
|
||||||
|
npx @sveltejs/mcp svelte-autofixer ./src/lib/Component.svelte
|
||||||
|
|
||||||
|
# Target Svelte 4
|
||||||
|
npx @sveltejs/mcp svelte-autofixer ./Component.svelte --svelte-version 4
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important:** When passing code with runes (`$state`, `$derived`, etc.) via the terminal, escape the `$` character as `\$` to prevent shell variable substitution.
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. **Uncertain about syntax?** Run `list-sections` then `get-documentation` for relevant topics
|
||||||
|
2. **Reviewing/debugging?** Run `svelte-autofixer` on the code to detect issues
|
||||||
|
3. **Always validate** - Run `svelte-autofixer` before finalizing any Svelte component
|
||||||
3828
pnpm-lock.yaml
generated
3828
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,63 @@
|
|||||||
packages:
|
packages:
|
||||||
- './packages/*'
|
- ./packages/*
|
||||||
- './apps/*'
|
- ./apps/*
|
||||||
|
|
||||||
|
catalogs:
|
||||||
|
ai:
|
||||||
|
'@anthropic-ai/sdk': ^0.71.0
|
||||||
|
'@mcp-ui/server': ^6.0.0
|
||||||
|
'@modelcontextprotocol/inspector': ^0.19.0
|
||||||
|
'@opencode-ai/plugin': ^1.1.44
|
||||||
|
lint:
|
||||||
|
'@eslint/compat': ^2.0.0
|
||||||
|
'@eslint/js': ^9.36.0
|
||||||
|
'@types/eslint-scope': ^8.3.2
|
||||||
|
'@typescript-eslint/parser': ^8.44.0
|
||||||
|
'@typescript-eslint/types': ^8.44.0
|
||||||
|
eslint: ^9.36.0
|
||||||
|
eslint-config-prettier: ^10.0.1
|
||||||
|
eslint-plugin-import: ^2.32.0
|
||||||
|
eslint-plugin-pnpm: ^1.3.0
|
||||||
|
eslint-plugin-svelte: ^3.12.5
|
||||||
|
globals: ^17.0.0
|
||||||
|
prettier: ^3.4.2
|
||||||
|
prettier-plugin-svelte: ^3.3.3
|
||||||
|
svelte-eslint-parser: ^1.4.0
|
||||||
|
typescript-eslint: ^8.44.0
|
||||||
|
orm:
|
||||||
|
'@libsql/client': ^0.17.0
|
||||||
|
drizzle-kit: ^0.31.0
|
||||||
|
drizzle-orm: ^0.45.0
|
||||||
|
svelte:
|
||||||
|
'@sveltejs/adapter-vercel': ^6.0.0
|
||||||
|
'@sveltejs/kit': ^2.42.2
|
||||||
|
'@sveltejs/vite-plugin-svelte': ^6.0.0
|
||||||
|
svelte: ^5.47.0
|
||||||
|
svelte-check: ^4.0.0
|
||||||
|
tmcp:
|
||||||
|
'@tmcp/adapter-valibot': ^0.1.4
|
||||||
|
'@tmcp/transport-http': ^0.8.4
|
||||||
|
'@tmcp/transport-in-memory': ^0.0.5
|
||||||
|
'@tmcp/transport-stdio': ^0.4.0
|
||||||
|
tmcp: ^1.19.0
|
||||||
|
tooling:
|
||||||
|
'@changesets/cli': ^2.29.7
|
||||||
|
'@svitejs/changesets-changelog-github-compact': ^1.2.0
|
||||||
|
'@types/estree': ^1.0.8
|
||||||
|
'@types/node': ^24.3.1
|
||||||
|
'@valibot/to-json-schema': ^1.5.0
|
||||||
|
'@vercel/analytics': ^1.5.0
|
||||||
|
dotenv: ^17.2.3
|
||||||
|
node-resolve-ts: ^1.0.2
|
||||||
|
publint: ^0.3.13
|
||||||
|
sade: 1.8.1
|
||||||
|
ts-blank-space: ^0.7.0
|
||||||
|
tsdown: ^0.20.0
|
||||||
|
typescript: ^5.0.0
|
||||||
|
valibot: ^1.2.0
|
||||||
|
vite: ^7.0.4
|
||||||
|
vite-plugin-devtools-json: ^1.0.0
|
||||||
|
vitest: ^4.0.0
|
||||||
|
zimmerframe: ^1.1.4
|
||||||
|
|
||||||
useNodeVersion: 22.19.0
|
useNodeVersion: 22.19.0
|
||||||
|
|||||||
100
scripts/update-docs-skills.ts
Normal file
100
scripts/update-docs-skills.ts
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import fs from 'node:fs/promises';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
interface SkillFrontmatter {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_backtick_fence(content: string): string {
|
||||||
|
const backtick_pattern = /`{3,}/g;
|
||||||
|
let max_backticks = 3;
|
||||||
|
|
||||||
|
let match;
|
||||||
|
while ((match = backtick_pattern.exec(content)) !== null) {
|
||||||
|
max_backticks = Math.max(max_backticks, match[0].length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return '`'.repeat(max_backticks + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse_frontmatter(
|
||||||
|
content: string,
|
||||||
|
): { frontmatter: SkillFrontmatter; body: string } | null {
|
||||||
|
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
||||||
|
if (!match) return null;
|
||||||
|
|
||||||
|
const frontmatter_str = match[1];
|
||||||
|
const body = match[2];
|
||||||
|
|
||||||
|
if (!frontmatter_str || body === undefined) return null;
|
||||||
|
|
||||||
|
const frontmatter: Record<string, string> = {};
|
||||||
|
|
||||||
|
for (const line of frontmatter_str.split('\n')) {
|
||||||
|
const [key, ...value_parts] = line.split(':');
|
||||||
|
if (key && value_parts.length > 0) {
|
||||||
|
frontmatter[key.trim()] = value_parts.join(':').trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
frontmatter: frontmatter as unknown as SkillFrontmatter,
|
||||||
|
body: body.trim(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = `---
|
||||||
|
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/mcp/releases) or find them in the [\`plugins/svelte/skills\`](https://github.com/sveltejs/mcp/tree/main/plugins/svelte/skills) folder.
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
|
const skills_dir = './plugins/svelte/skills';
|
||||||
|
const skill_dirs = (await fs.readdir(skills_dir)).filter((name) => !name.startsWith('.'));
|
||||||
|
|
||||||
|
for (const skill_name of skill_dirs) {
|
||||||
|
const skill_path = path.join(skills_dir, skill_name, 'SKILL.md');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const skill_content = await fs.readFile(skill_path, 'utf-8');
|
||||||
|
const parsed = parse_frontmatter(skill_content);
|
||||||
|
|
||||||
|
if (!parsed) {
|
||||||
|
console.warn(`Warning: Could not parse frontmatter for ${skill_name}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { frontmatter, body } = parsed;
|
||||||
|
const fence = get_backtick_fence(body);
|
||||||
|
|
||||||
|
content += `## \`${frontmatter.name}\`
|
||||||
|
|
||||||
|
${frontmatter.description}
|
||||||
|
|
||||||
|
<a href="https://github.com/sveltejs/mcp/releases?q=${frontmatter.name}" target="_blank" rel="noopener noreferrer">Open Releases page</a>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>View skill content</summary>
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
|
${fence}markdown
|
||||||
|
${body}
|
||||||
|
${fence}
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
`;
|
||||||
|
} catch {
|
||||||
|
console.warn(`Warning: Could not read skill at ${skill_path}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await fs.writeFile('./documentation/docs/60-skills/10-skills.md', content.trim() + '\n');
|
||||||
Reference in New Issue
Block a user