mirror of
https://github.com/sveltejs/ai-tools.git
synced 2026-07-04 11:42:22 +08:00
Compare commits
47 Commits
wip-docs-w
...
setup-chan
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
baad760634 | ||
|
|
b2275587ee | ||
|
|
4964303100 | ||
|
|
81901b2564 | ||
|
|
5aa1aa401a | ||
|
|
b5a88c454d | ||
|
|
7b5bea6549 | ||
|
|
a36d0d17a8 | ||
|
|
70f14bddca | ||
|
|
a281ef4b66 | ||
|
|
5bc812e4db | ||
|
|
82319661dd | ||
|
|
ce0861c1ca | ||
|
|
5dd83d151e | ||
|
|
76a35f5dc8 | ||
|
|
54763e0f55 | ||
|
|
01d5803b5d | ||
|
|
0366bc785b | ||
|
|
6a6417d3a5 | ||
|
|
77af7ebcc6 | ||
|
|
b1a196497d | ||
|
|
fb2d19fd07 | ||
|
|
8328a3572b | ||
|
|
c05b6c257a | ||
|
|
7f9ea742d8 | ||
|
|
bf477a6ccf | ||
|
|
0f5482477a | ||
|
|
b774b463fe | ||
|
|
c49b24d36a | ||
|
|
6cb97ac11d | ||
|
|
d33a374417 | ||
|
|
1bb171cea7 | ||
|
|
e314ab57b2 | ||
|
|
19cacf7ed9 | ||
|
|
68cf69a117 | ||
|
|
917fdf63b1 | ||
|
|
972cadc410 | ||
|
|
dc6c87ce37 | ||
|
|
e7431e9024 | ||
|
|
07737a8edd | ||
|
|
fdb7689992 | ||
|
|
1a283f60bc | ||
|
|
bb16ccca3a | ||
|
|
23ddaf9495 | ||
|
|
4228302ed0 | ||
|
|
0bc4d75e13 | ||
|
|
4679549401 |
8
.changeset/README.md
Normal file
8
.changeset/README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Changesets
|
||||
|
||||
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
|
||||
with multi-package repos, or single-package repos to help you version and publish your code. You can
|
||||
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
|
||||
|
||||
We have a quick list of common questions to get you started engaging with this project in
|
||||
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
|
||||
11
.changeset/config.json
Normal file
11
.changeset/config.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
|
||||
"changelog": ["@svitejs/changesets-changelog-github-compact", { "repo": "sveltejs/mcp" }],
|
||||
"commit": false,
|
||||
"fixed": [],
|
||||
"linked": [],
|
||||
"access": "public",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": []
|
||||
}
|
||||
5
.changeset/shy-peaches-laugh.md
Normal file
5
.changeset/shy-peaches-laugh.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@sveltejs/mcp': patch
|
||||
---
|
||||
|
||||
feat: latest version
|
||||
2
.github/workflows/check.yml
vendored
2
.github/workflows/check.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
version: 10.17.1
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
version: 10.17.1
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
||||
58
.github/workflows/release.yml
vendored
Normal file
58
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
release:
|
||||
permissions:
|
||||
contents: write # to create release (changesets/action)
|
||||
id-token: write # OpenID Connect token needed for provenance
|
||||
pull-requests: write # to create pull request (changesets/action)
|
||||
# prevents this action from running on forks
|
||||
if: github.repository == 'sveltejs/mcp'
|
||||
name: Release
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
# pseudo-matrix for convenience, NEVER use more than a single combination
|
||||
node: [24]
|
||||
os: [ubuntu-latest]
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
# This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
package-manager-cache: false # pnpm is not installed yet
|
||||
- name: install pnpm
|
||||
shell: bash
|
||||
run: |
|
||||
PNPM_VER=$(jq -r '.packageManager | if .[0:5] == "pnpm@" then .[5:] else "packageManager in package.json does not start with pnpm@\n" | halt_error(1) end' package.json)
|
||||
echo installing pnpm version $PNPM_VER
|
||||
npm i -g pnpm@$PNPM_VER
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
package-manager-cache: true # caches pnpm via packageManager field in package.json
|
||||
- name: install
|
||||
run: pnpm install --frozen-lockfile --prefer-offline --ignore-scripts
|
||||
- name: publint
|
||||
run: pnpm check:publint
|
||||
|
||||
- name: Create Release Pull Request or Publish to npm
|
||||
id: changesets
|
||||
# pinned for security, always review third party action code before updating
|
||||
uses: changesets/action@e0145edc7d9d8679003495b11f87bd8ef63c0cba # v1.5.3
|
||||
with:
|
||||
# This expects you to have a script called release which does a build for your packages and calls changeset publish
|
||||
publish: pnpm release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NPM_CONFIG_PROVENANCE: true
|
||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
version: 10.17.1
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"svelte-llm": {
|
||||
"type": "http",
|
||||
"url": "https://svelte-llm.stanislav.garden/mcp/mcp"
|
||||
"svelte": {
|
||||
"type": "stdio",
|
||||
"command": "node",
|
||||
"args": ["packages/mcp-stdio/dist/index.js"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,4 +8,7 @@ bun.lockb
|
||||
# Miscellaneous
|
||||
/static/
|
||||
/drizzle/
|
||||
/**/.svelte-kit/*
|
||||
/**/.svelte-kit/*
|
||||
|
||||
# Claude Code
|
||||
.claude/
|
||||
2
.vscode/mcp.json
vendored
2
.vscode/mcp.json
vendored
@@ -3,7 +3,7 @@
|
||||
"Svelte MCP": {
|
||||
"type": "stdio",
|
||||
"command": "node",
|
||||
"args": ["dist/lib/stdio.js"]
|
||||
"args": ["packages/mcp-stdio/dist/index.js"]
|
||||
}
|
||||
},
|
||||
"inputs": []
|
||||
|
||||
@@ -90,12 +90,12 @@ When connected to the svelte-llm MCP server, you have access to comprehensive Sv
|
||||
|
||||
## Available MCP Tools:
|
||||
|
||||
### 1. list_sections
|
||||
### 1. list-sections
|
||||
|
||||
Use this FIRST to discover all available documentation sections. Returns a structured list with titles and paths.
|
||||
When asked about Svelte or SvelteKit topics, ALWAYS use this tool at the start of the chat to find relevant sections.
|
||||
|
||||
### 2. get_documentation
|
||||
### 2. get-documentation
|
||||
|
||||
Retrieves full documentation content for specific sections. Accepts single or multiple sections.
|
||||
After calling the list_sections tool, you MUST analyze the returned documentation sections and then use the get_documentation tool to fetch ALL documentation sections that are relevant for the users task.
|
||||
After calling the list-sections tool, you MUST analyze the returned documentation sections and then use the get_documentation tool to fetch ALL documentation sections that are relevant for the users task.
|
||||
|
||||
@@ -6,7 +6,7 @@ Repo for the official Svelte MCP server.
|
||||
|
||||
```
|
||||
pnpm i
|
||||
cp .env.example .env
|
||||
cp apps/mcp-remote/.env.example apps/mcp-remote/.env
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
DATABASE_URL=file:test.db
|
||||
DATABASE_TOKEN=needs_to_be_set_but_it_can_be_anything
|
||||
VOYAGE_API_KEY=your_actual_api_key_here
|
||||
GITHUB_WEBHOOK_SECRET=some_secret
|
||||
VOYAGE_API_KEY=your_actual_api_key_here
|
||||
@@ -65,7 +65,6 @@
|
||||
"@sveltejs/mcp-schema": "workspace:^",
|
||||
"@sveltejs/mcp-server": "workspace:^",
|
||||
"@tmcp/transport-http": "^0.6.3",
|
||||
"tmcp": "^1.14.0",
|
||||
"valibot": "^1.1.0"
|
||||
"tmcp": "^1.14.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
// not the full schema but it contains the information we need
|
||||
export const github_webhook_schema = v.object({
|
||||
action: v.union([v.literal('closed')]),
|
||||
pull_request: v.object({
|
||||
patch_url: v.string(),
|
||||
merged: v.boolean(),
|
||||
user: v.object({
|
||||
login: v.string(),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const github_content_schema = v.object({
|
||||
name: v.string(),
|
||||
path: v.string(),
|
||||
sha: v.string(),
|
||||
size: v.number(),
|
||||
url: v.string(),
|
||||
html_url: v.string(),
|
||||
git_url: v.string(),
|
||||
download_url: v.nullable(v.string()),
|
||||
type: v.literal('file'),
|
||||
content: v.string(),
|
||||
encoding: v.literal('base64'),
|
||||
_links: v.object({
|
||||
self: v.string(),
|
||||
git: v.string(),
|
||||
html: v.string(),
|
||||
}),
|
||||
});
|
||||
@@ -1,54 +0,0 @@
|
||||
import { github_content_schema, github_webhook_schema } from '$lib/schemas/index.js';
|
||||
import * as v from 'valibot';
|
||||
|
||||
export async function POST({ request, fetch }) {
|
||||
const body = await request.json();
|
||||
// TODO add secret validation
|
||||
const validated_pull_request = v.safeParse(github_webhook_schema, body);
|
||||
if (!validated_pull_request.success) {
|
||||
return new Response('Invalid payload', { status: 400 });
|
||||
}
|
||||
|
||||
const { pull_request } = validated_pull_request.output;
|
||||
|
||||
if (!pull_request.merged) {
|
||||
return new Response(null, { status: 204 });
|
||||
}
|
||||
|
||||
const patch = await fetch(pull_request.patch_url);
|
||||
if (!patch.ok) {
|
||||
return new Response('Failed to fetch patch', { status: 500 });
|
||||
}
|
||||
|
||||
const patch_text = await patch.text();
|
||||
const files = [
|
||||
...patch_text.matchAll(
|
||||
/^diff --git\sa\/(?<file>.+?)\sb\/\1\n(?:(?<action>deleted|new)\sfile mode)?/gm,
|
||||
),
|
||||
].map((res) => ({ file: res.groups!.file, action: res.groups?.action ?? 'modified' }));
|
||||
|
||||
for (const file of files) {
|
||||
if (file.action === 'deleted') {
|
||||
// delete path from db
|
||||
continue;
|
||||
}
|
||||
const new_file_content = await fetch(
|
||||
`https://api.github.com/repos/sveltejs/svelte.dev/contents/${file.file}`,
|
||||
);
|
||||
if (!new_file_content.ok) {
|
||||
// push file in queue and try again later?
|
||||
continue;
|
||||
}
|
||||
const new_file_json = await new_file_content.json();
|
||||
const validated_content = v.safeParse(github_content_schema, new_file_json);
|
||||
if (!validated_content.success) {
|
||||
// push file in queue and try again later?
|
||||
continue;
|
||||
}
|
||||
const content = Buffer.from(validated_content.output.content, 'base64').toString('utf-8');
|
||||
// save content and distilled content in the db
|
||||
console.log({ content, file: file.file });
|
||||
}
|
||||
|
||||
return new Response(null, { status: 204 });
|
||||
}
|
||||
@@ -12,6 +12,9 @@ const gitignore_path = fileURLToPath(new URL('./.gitignore', import.meta.url));
|
||||
|
||||
export default /** @type {import("eslint").Linter.Config} */ ([
|
||||
includeIgnoreFile(gitignore_path),
|
||||
{
|
||||
ignores: ['.claude/**/*'],
|
||||
},
|
||||
js.configs.recommended,
|
||||
...ts.configs.recommended,
|
||||
...svelte.configs.recommended,
|
||||
|
||||
@@ -3,16 +3,20 @@
|
||||
"version": "0.0.1",
|
||||
"description": "The official Svelte MCP server implementation",
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@10.17.1",
|
||||
"scripts": {
|
||||
"build": "pnpm -r run build",
|
||||
"dev": "pnpm --filter @sveltejs/mcp-remote run dev",
|
||||
"check": "pnpm -r run check",
|
||||
"check:publint": "pnpm -r run check:publint",
|
||||
"format": "prettier --write .",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"lint:fix": "prettier --write . && eslint . --fix",
|
||||
"test:unit": "vitest",
|
||||
"test": "npm run test:unit -- --run",
|
||||
"test:watch": "npm run test:unit -- --watch",
|
||||
"inspect": "pnpm mcp-inspector"
|
||||
"inspect": "pnpm mcp-inspector",
|
||||
"release": "pnpm --filter @sveltejs/mcp run build && changeset publish"
|
||||
},
|
||||
"keywords": [
|
||||
"svelte",
|
||||
@@ -22,9 +26,11 @@
|
||||
],
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@changesets/cli": "^2.29.7",
|
||||
"@eslint/compat": "^1.3.2",
|
||||
"@eslint/js": "^9.36.0",
|
||||
"@modelcontextprotocol/inspector": "^0.16.7",
|
||||
"@svitejs/changesets-changelog-github-compact": "^1.2.0",
|
||||
"eslint": "^9.36.0",
|
||||
"eslint-config-prettier": "^10.0.1",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
@@ -32,6 +38,7 @@
|
||||
"globals": "^16.0.0",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"publint": "^0.3.13",
|
||||
"typescript": "^5.0.0",
|
||||
"typescript-eslint": "^8.44.1",
|
||||
"vitest": "^3.2.3"
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"packageManager": "pnpm@10.15.1",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"drizzle-orm": "^0.40.1"
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"packageManager": "pnpm@10.15.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "vitest"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { SvelteMcp } from '../../index.js';
|
||||
import * as v from 'valibot';
|
||||
import { get_sections } from '../../utils.js';
|
||||
|
||||
export function setup_svelte_task(server: SvelteMcp) {
|
||||
server.prompt(
|
||||
@@ -13,8 +14,7 @@ export function setup_svelte_task(server: SvelteMcp) {
|
||||
}),
|
||||
},
|
||||
async ({ task }) => {
|
||||
// TODO: implement logic to fetch the available docs paths to return in the prompt
|
||||
const available_docs: string[] = [];
|
||||
const available_docs: string[] = (await get_sections()).map((s) => s.title);
|
||||
|
||||
return {
|
||||
messages: [
|
||||
@@ -22,7 +22,7 @@ export function setup_svelte_task(server: SvelteMcp) {
|
||||
role: 'user',
|
||||
content: {
|
||||
type: 'text',
|
||||
text: `You are a Svelte expert tasked to build components and utilities for Svelte developers. If you need documentation for anything related to Svelte you can invoke the tool \`get_documentation\` with one of the following paths:
|
||||
text: `You are a Svelte expert tasked to build components and utilities for Svelte developers. If you need documentation for anything related to Svelte you can invoke the tool \`get-documentation\` with one of the following paths:
|
||||
<available-docs-paths>
|
||||
${JSON.stringify(available_docs, null, 2)}
|
||||
</available-docs-paths>
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import type { SvelteMcp } from '../../index.js';
|
||||
import { get_sections, fetch_with_timeout } from '../../utils.js';
|
||||
|
||||
export async function list_sections(server: SvelteMcp) {
|
||||
const sections = await get_sections();
|
||||
|
||||
server.template(
|
||||
{
|
||||
name: 'Svelte Doc Section',
|
||||
description: 'A single documentation section',
|
||||
list() {
|
||||
return sections.map((section) => {
|
||||
const section_name = section.slug;
|
||||
const resource_name = section_name;
|
||||
const resource_uri = `svelte://${section_name}.md`;
|
||||
return {
|
||||
name: resource_name,
|
||||
description: section.use_cases,
|
||||
uri: resource_uri,
|
||||
title: section.title,
|
||||
};
|
||||
});
|
||||
},
|
||||
complete: {
|
||||
slug: (query) => {
|
||||
const values = sections
|
||||
.reduce<string[]>((acc, section) => {
|
||||
const section_name = section.slug;
|
||||
const resource_name = section_name;
|
||||
if (section_name.includes(query.toLowerCase())) {
|
||||
acc.push(resource_name);
|
||||
}
|
||||
return acc;
|
||||
}, [])
|
||||
// there's a hard limit of 100 for completions
|
||||
.slice(0, 100);
|
||||
return {
|
||||
completion: {
|
||||
values,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
uri: 'svelte://{/slug*}.md',
|
||||
},
|
||||
async (uri, { slug }) => {
|
||||
const section = sections.find((section) => {
|
||||
return slug === section.slug;
|
||||
});
|
||||
if (!section) throw new Error(`Section not found: ${slug}`);
|
||||
const response = await fetch_with_timeout(section.url);
|
||||
const content = await response.text();
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri,
|
||||
type: 'text',
|
||||
text: content,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
export * from './list-sections.js';
|
||||
export * from './doc-section.js';
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import type { SvelteMcp } from '../../index.js';
|
||||
|
||||
export function list_sections(server: SvelteMcp) {
|
||||
server.resource(
|
||||
{
|
||||
name: 'list-sections',
|
||||
enabled: () => false,
|
||||
description:
|
||||
'The list of all the available Svelte 5 and SvelteKit documentation sections in a structured format.',
|
||||
uri: 'svelte://list-sections',
|
||||
title: 'Svelte Documentation Section',
|
||||
},
|
||||
async (uri) => {
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri,
|
||||
type: 'text',
|
||||
text: 'resource list-sections called',
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { SvelteMcp } from '../../index.js';
|
||||
import * as v from 'valibot';
|
||||
import { get_sections, fetch_with_timeout } from '../../utils.js';
|
||||
import { SECTIONS_LIST_INTRO, SECTIONS_LIST_OUTRO } from './prompts.js';
|
||||
|
||||
export function get_documentation(server: SvelteMcp) {
|
||||
server.tool(
|
||||
@@ -7,7 +9,7 @@ export function get_documentation(server: SvelteMcp) {
|
||||
name: 'get-documentation',
|
||||
enabled: () => false,
|
||||
description:
|
||||
'Retrieves full documentation content for Svelte 5 or SvelteKit sections. Supports flexible search by title (e.g., "$state", "routing") or file path (e.g., "docs/svelte/state.md"). Can accept a single section name or an array of sections. Before running this, make sure to analyze the users query, as well as the output from list_sections (which should be called first). Then ask for ALL relevant sections the user might require. For example, if the user asks to build anything interactive, you will need to fetch all relevant runes, and so on.',
|
||||
'Retrieves full documentation content for Svelte 5 or SvelteKit sections. Supports flexible search by title (e.g., "$state", "routing") or file path (e.g., "docs/svelte/state.md"). Can accept a single section name or an array of sections. Before running this, make sure to analyze the users query, as well as the output from list-sections (which should be called first). Then ask for ALL relevant sections the user might require. For example, if the user asks to build anything interactive, you will need to fetch all relevant runes, and so on.',
|
||||
schema: v.object({
|
||||
section: v.pipe(
|
||||
v.union([v.string(), v.array(v.string())]),
|
||||
@@ -17,7 +19,7 @@ export function get_documentation(server: SvelteMcp) {
|
||||
),
|
||||
}),
|
||||
},
|
||||
({ section }) => {
|
||||
async ({ section }) => {
|
||||
let sections: string[];
|
||||
|
||||
if (Array.isArray(section)) {
|
||||
@@ -43,13 +45,73 @@ export function get_documentation(server: SvelteMcp) {
|
||||
sections = [];
|
||||
}
|
||||
|
||||
const sections_list = sections.length > 0 ? sections.join(', ') : 'no sections';
|
||||
const available_sections = await get_sections();
|
||||
|
||||
const settled_results = await Promise.allSettled(
|
||||
sections.map(async (requested_section) => {
|
||||
const matched_section = available_sections.find(
|
||||
(s) =>
|
||||
s.title.toLowerCase() === requested_section.toLowerCase() ||
|
||||
s.url === requested_section,
|
||||
);
|
||||
|
||||
if (matched_section) {
|
||||
try {
|
||||
const response = await fetch_with_timeout(matched_section.url);
|
||||
if (response.ok) {
|
||||
const content = await response.text();
|
||||
return { success: true, content: `## ${matched_section.title}\n\n${content}` };
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
content: `## ${matched_section.title}\n\nError: Could not fetch documentation (HTTP ${response.status})`,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
content: `## ${matched_section.title}\n\nError: Failed to fetch documentation - ${error}`,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
content: `## ${requested_section}\n\nError: Section not found.`,
|
||||
};
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
const results = settled_results.map((result) => {
|
||||
if (result.status === 'fulfilled') {
|
||||
return result.value;
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
content: `Error: Couldn't fetch - ${result.reason}`,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const has_any_success = results.some((result) => result.success);
|
||||
let final_text = results.map((r) => r.content).join('\n\n---\n\n');
|
||||
|
||||
if (!has_any_success) {
|
||||
const formatted_sections = available_sections
|
||||
.map(
|
||||
(section) =>
|
||||
`* title: ${section.title}, use_cases: ${section.use_cases}, path: ${section.url}`,
|
||||
)
|
||||
.join('\n');
|
||||
|
||||
final_text += `\n\n---\n\n${SECTIONS_LIST_INTRO}\n\n${formatted_sections}\n\n${SECTIONS_LIST_OUTRO}`;
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `called for sections: ${sections_list}`,
|
||||
text: final_text,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import type { SvelteMcp } from '../../index.js';
|
||||
import { get_sections } from '../../utils.js';
|
||||
import { SECTIONS_LIST_INTRO, SECTIONS_LIST_OUTRO } from './prompts.js';
|
||||
|
||||
export function list_sections(server: SvelteMcp) {
|
||||
server.tool(
|
||||
@@ -6,14 +8,22 @@ export function list_sections(server: SvelteMcp) {
|
||||
name: 'list-sections',
|
||||
enabled: () => false,
|
||||
description:
|
||||
'Lists all available Svelte 5 and SvelteKit documentation sections in a structured format. Returns sections as a list of "* title: [section_title], path: [file_path]" - you can use either the title or path when querying a specific section via the get_documentation tool. Always run list_sections first for any query related to Svelte development to discover available content.',
|
||||
'Lists all available Svelte 5 and SvelteKit documentation sections in a structured format. Returns sections as a list of "* title: [section_title], use_cases: [use_cases], path: [file_path]" - you can use either the title or path when querying a specific section via the get_documentation tool. Always run list-sections first for any query related to Svelte development to discover available content.',
|
||||
},
|
||||
() => {
|
||||
async () => {
|
||||
const sections = await get_sections();
|
||||
const formatted_sections = sections
|
||||
.map(
|
||||
(section) =>
|
||||
`* title: ${section.title}, use_cases: ${section.use_cases}, path: ${section.url}`,
|
||||
)
|
||||
.join('\n');
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'tool list_sections called',
|
||||
text: `${SECTIONS_LIST_INTRO}\n\n${formatted_sections}\n\n${SECTIONS_LIST_OUTRO}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
5
packages/mcp-server/src/mcp/handlers/tools/prompts.ts
Normal file
5
packages/mcp-server/src/mcp/handlers/tools/prompts.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export const SECTIONS_LIST_INTRO =
|
||||
'List of available Svelte documentation sections and its inteneded uses:';
|
||||
|
||||
export const SECTIONS_LIST_OUTRO =
|
||||
'Use the title or path with the get-documentation tool to get more details about a specific section.';
|
||||
12
packages/mcp-server/src/mcp/schemas/index.ts
Normal file
12
packages/mcp-server/src/mcp/schemas/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
export const documentation_sections_schema = v.record(
|
||||
v.string(),
|
||||
v.object({
|
||||
metadata: v.object({
|
||||
title: v.string(),
|
||||
use_cases: v.optional(v.string()),
|
||||
}),
|
||||
slug: v.string(),
|
||||
}),
|
||||
);
|
||||
31
packages/mcp-server/src/mcp/utils.ts
Normal file
31
packages/mcp-server/src/mcp/utils.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import * as v from 'valibot';
|
||||
import { documentation_sections_schema } from '../mcp/schemas/index.js';
|
||||
|
||||
export async function fetch_with_timeout(
|
||||
url: string,
|
||||
timeout_ms: number = 10000,
|
||||
): Promise<Response> {
|
||||
try {
|
||||
const response = await fetch(url, { signal: AbortSignal.timeout(timeout_ms) });
|
||||
return response;
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.name === 'AbortError') {
|
||||
throw new Error(`Request timed out after ${timeout_ms}ms`);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function get_sections() {
|
||||
const sections = await fetch_with_timeout(
|
||||
'https://svelte.dev/docs/experimental/sections.json',
|
||||
).then((res) => res.json());
|
||||
const validated_sections = v.safeParse(documentation_sections_schema, sections);
|
||||
if (!validated_sections.success) return [];
|
||||
return Object.entries(validated_sections.output).map(([, section]) => ({
|
||||
title: section.metadata.title,
|
||||
use_cases: section.metadata.use_cases ?? 'read document for use cases',
|
||||
slug: section.slug,
|
||||
url: `https://svelte.dev/${section.slug}/llms.txt`,
|
||||
}));
|
||||
}
|
||||
@@ -22,10 +22,11 @@
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsdown",
|
||||
"build": "tsdown && publint",
|
||||
"dev": "tsdown --watch",
|
||||
"test": "vitest",
|
||||
"check": "tsc --noEmit"
|
||||
"check": "tsc --noEmit",
|
||||
"check:publint": "publint --strict"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/mcp-server": "workspace:^",
|
||||
|
||||
575
pnpm-lock.yaml
generated
575
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user