mirror of
https://github.com/sveltejs/ai-tools.git
synced 2026-07-04 11:42:22 +08:00
Compare commits
2 Commits
setup-chan
...
feat/to-re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
543fff6207 | ||
|
|
9de930dddc |
@@ -1,8 +0,0 @@
|
||||
# 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)
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"$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": []
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@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.17.1
|
||||
version: 10
|
||||
|
||||
- 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.17.1
|
||||
version: 10
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
||||
58
.github/workflows/release.yml
vendored
58
.github/workflows/release.yml
vendored
@@ -1,58 +0,0 @@
|
||||
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.17.1
|
||||
version: 10
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
||||
@@ -8,7 +8,4 @@ bun.lockb
|
||||
# Miscellaneous
|
||||
/static/
|
||||
/drizzle/
|
||||
/**/.svelte-kit/*
|
||||
|
||||
# Claude Code
|
||||
.claude/
|
||||
/**/.svelte-kit/*
|
||||
@@ -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 apps/mcp-remote/.env.example apps/mcp-remote/.env
|
||||
cp .env.example .env
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
"devDependencies": {
|
||||
"@eslint/compat": "^1.3.2",
|
||||
"@eslint/js": "^9.36.0",
|
||||
"@libsql/client": "^0.14.0",
|
||||
"@modelcontextprotocol/inspector": "^0.16.7",
|
||||
"@sveltejs/adapter-vercel": "^5.6.3",
|
||||
"@sveltejs/kit": "^2.22.0",
|
||||
@@ -53,6 +52,7 @@
|
||||
"globals": "^16.0.0",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"remult": "3.3.0-next.1",
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
"svelte-eslint-parser": "^1.3.2",
|
||||
@@ -62,6 +62,7 @@
|
||||
"vitest": "^3.2.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@libsql/client": "^0.14.0",
|
||||
"@sveltejs/mcp-schema": "workspace:^",
|
||||
"@sveltejs/mcp-server": "workspace:^",
|
||||
"@tmcp/transport-http": "^0.6.3",
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { http_transport } from '$lib/mcp/index.js';
|
||||
import { db } from '$lib/server/db/index.js';
|
||||
import { db, entities } from '$lib/server/db/index.js';
|
||||
|
||||
export async function handle({ event, resolve }) {
|
||||
const mcp_response = await http_transport.respond(event.request, {
|
||||
db,
|
||||
entities,
|
||||
});
|
||||
// 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)
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import { createClient } from '@libsql/client';
|
||||
import { drizzle } from 'drizzle-orm/libsql';
|
||||
import * as schema from './schema.js';
|
||||
import { remult, SqlDatabase } from 'remult';
|
||||
import { TursoDataProvider } from 'remult/remult-turso';
|
||||
// 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 { Content } from '../../../shared/entities/Content.js';
|
||||
import { ContentDistilled } from '../../../shared/entities/ContentDistilled.js';
|
||||
import { Distillation } from '../../../shared/entities/Distillation.js';
|
||||
import { DistillationJob } from '../../../shared/entities/DistillationJob.js';
|
||||
if (!DATABASE_URL) throw new Error('DATABASE_URL is not set');
|
||||
if (!DATABASE_TOKEN) throw new Error('DATABASE_TOKEN is not set');
|
||||
|
||||
@@ -13,3 +19,13 @@ const client = createClient({
|
||||
});
|
||||
|
||||
export const db = drizzle(client, { schema, logger: true });
|
||||
|
||||
remult.dataProvider = new SqlDatabase(new TursoDataProvider(client));
|
||||
SqlDatabase.LogToConsole = true;
|
||||
|
||||
export const entities = {
|
||||
Content,
|
||||
ContentDistilled,
|
||||
Distillation,
|
||||
DistillationJob,
|
||||
};
|
||||
|
||||
19
apps/mcp-remote/src/shared/FieldVector.ts
Normal file
19
apps/mcp-remote/src/shared/FieldVector.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Fields, type FieldOptions } from 'remult';
|
||||
|
||||
export function FieldVector<entityType = unknown>(
|
||||
...options: (FieldOptions<entityType, number[]> & { dimensions?: number })[]
|
||||
) {
|
||||
const dimensions = options[0].dimensions ?? 1024;
|
||||
|
||||
return Fields.object<entityType, number[]>(
|
||||
{
|
||||
valueConverter: {
|
||||
fieldTypeInDb: `F32_BLOB(${dimensions})`,
|
||||
toDb: (val) => JSON.stringify(val),
|
||||
toDbSql: (val) => `vector32(${val})`,
|
||||
fromDb: (val: Buffer) => Array.from(new Float32Array(val)),
|
||||
},
|
||||
},
|
||||
...options,
|
||||
);
|
||||
}
|
||||
35
apps/mcp-remote/src/shared/entities/Content.ts
Normal file
35
apps/mcp-remote/src/shared/entities/Content.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Entity, Fields } from 'remult';
|
||||
import { FieldVector } from '../FieldVector';
|
||||
|
||||
@Entity<Content>('content', {
|
||||
allowApiCrud: true,
|
||||
dbName: 'content',
|
||||
})
|
||||
export class Content {
|
||||
@Fields.integer()
|
||||
id!: number;
|
||||
|
||||
@Fields.string()
|
||||
path!: string;
|
||||
|
||||
@Fields.string()
|
||||
filename!: string;
|
||||
|
||||
@Fields.string()
|
||||
content!: string;
|
||||
|
||||
@Fields.integer()
|
||||
size_bytes!: number;
|
||||
|
||||
@FieldVector({ allowNull: true })
|
||||
embeddings: number[] | null = null;
|
||||
|
||||
@Fields.string()
|
||||
metadata = '{}';
|
||||
|
||||
@Fields.integer()
|
||||
created_at!: number;
|
||||
|
||||
@Fields.integer()
|
||||
updated_at!: number;
|
||||
}
|
||||
35
apps/mcp-remote/src/shared/entities/ContentDistilled.ts
Normal file
35
apps/mcp-remote/src/shared/entities/ContentDistilled.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Entity, Fields } from 'remult';
|
||||
import { FieldVector } from '../FieldVector';
|
||||
|
||||
@Entity<ContentDistilled>('content_distilled', {
|
||||
allowApiCrud: true,
|
||||
dbName: 'content_distilled',
|
||||
})
|
||||
export class ContentDistilled {
|
||||
@Fields.integer()
|
||||
id!: number;
|
||||
|
||||
@Fields.string()
|
||||
path!: string;
|
||||
|
||||
@Fields.string()
|
||||
filename!: string;
|
||||
|
||||
@Fields.string()
|
||||
content!: string;
|
||||
|
||||
@Fields.integer()
|
||||
size_bytes!: number;
|
||||
|
||||
@FieldVector({ allowNull: true })
|
||||
embeddings: number[] | null = null;
|
||||
|
||||
@Fields.string()
|
||||
metadata = '{}';
|
||||
|
||||
@Fields.integer()
|
||||
created_at!: number;
|
||||
|
||||
@Fields.integer()
|
||||
updated_at!: number;
|
||||
}
|
||||
36
apps/mcp-remote/src/shared/entities/Distillation.ts
Normal file
36
apps/mcp-remote/src/shared/entities/Distillation.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Entity, Field, Fields } from "remult"
|
||||
import { Relations } from "remult"
|
||||
import { DistillationJob } from "./DistillationJob.js"
|
||||
|
||||
@Entity<Distillation>("distillations", {
|
||||
allowApiCrud: true,
|
||||
dbName: "distillations",
|
||||
})
|
||||
export class Distillation {
|
||||
@Fields.integer()
|
||||
id!: number
|
||||
|
||||
@Fields.string()
|
||||
preset_name!: string
|
||||
|
||||
@Fields.string()
|
||||
version!: string
|
||||
|
||||
@Fields.string()
|
||||
content!: string
|
||||
|
||||
@Fields.integer()
|
||||
size_kb!: number
|
||||
|
||||
@Fields.integer()
|
||||
document_count!: number
|
||||
|
||||
@Fields.integer({ allowNull: true })
|
||||
distillation_job_id?: number
|
||||
|
||||
@Relations.toOne(() => DistillationJob, { field: "distillation_job_id" })
|
||||
distillation_job?: DistillationJob
|
||||
|
||||
@Fields.integer()
|
||||
created_at!: number
|
||||
}
|
||||
64
apps/mcp-remote/src/shared/entities/DistillationJob.ts
Normal file
64
apps/mcp-remote/src/shared/entities/DistillationJob.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { Entity, Fields } from 'remult';
|
||||
import { Relations } from 'remult';
|
||||
import { Distillation } from './Distillation.js';
|
||||
|
||||
@Entity<DistillationJob>('distillation_jobs', {
|
||||
allowApiCrud: true,
|
||||
dbName: 'distillation_jobs',
|
||||
})
|
||||
export class DistillationJob {
|
||||
@Fields.integer()
|
||||
id!: number;
|
||||
|
||||
@Fields.string()
|
||||
preset_name!: string;
|
||||
|
||||
@Fields.string({ allowNull: true })
|
||||
batch_id?: string;
|
||||
|
||||
@Fields.string()
|
||||
status!: string;
|
||||
|
||||
@Fields.string()
|
||||
model_used!: string;
|
||||
|
||||
@Fields.integer()
|
||||
total_files!: number;
|
||||
|
||||
@Fields.integer()
|
||||
processed_files = 0;
|
||||
|
||||
@Fields.integer()
|
||||
successful_files = 0;
|
||||
|
||||
@Fields.boolean()
|
||||
minimize_applied = false;
|
||||
|
||||
@Fields.integer()
|
||||
total_input_tokens = 0;
|
||||
|
||||
@Fields.integer()
|
||||
total_output_tokens = 0;
|
||||
|
||||
@Fields.integer({ allowNull: true })
|
||||
started_at?: number;
|
||||
|
||||
@Fields.integer({ allowNull: true })
|
||||
completed_at?: number;
|
||||
|
||||
@Fields.string({ allowNull: true })
|
||||
error_message?: string;
|
||||
|
||||
@Fields.string()
|
||||
metadata = '{}';
|
||||
|
||||
@Fields.integer()
|
||||
created_at!: number;
|
||||
|
||||
@Fields.integer()
|
||||
updated_at!: number;
|
||||
|
||||
// Relations toMany
|
||||
@Relations.toMany(() => Distillation)
|
||||
distillations?: Distillation[];
|
||||
}
|
||||
@@ -9,7 +9,8 @@
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler"
|
||||
"moduleResolution": "bundler",
|
||||
"experimentalDecorators": true
|
||||
}
|
||||
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
|
||||
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
|
||||
|
||||
@@ -12,9 +12,6 @@ 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,20 +3,16 @@
|
||||
"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",
|
||||
"release": "pnpm --filter @sveltejs/mcp run build && changeset publish"
|
||||
"inspect": "pnpm mcp-inspector"
|
||||
},
|
||||
"keywords": [
|
||||
"svelte",
|
||||
@@ -26,11 +22,9 @@
|
||||
],
|
||||
"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",
|
||||
@@ -38,7 +32,6 @@
|
||||
"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,6 +12,7 @@
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"packageManager": "pnpm@10.15.1",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"drizzle-orm": "^0.40.1"
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"packageManager": "pnpm@10.15.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "vitest"
|
||||
@@ -35,6 +36,7 @@
|
||||
"@sveltejs/kit": "^2.42.2",
|
||||
"@types/eslint-scope": "^8.3.2",
|
||||
"@types/estree": "^1.0.8",
|
||||
"@typescript-eslint/types": "^8.44.0"
|
||||
"@typescript-eslint/types": "^8.44.0",
|
||||
"remult": "3.3.0-next.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
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(
|
||||
@@ -14,7 +13,8 @@ export function setup_svelte_task(server: SvelteMcp) {
|
||||
}),
|
||||
},
|
||||
async ({ task }) => {
|
||||
const available_docs: string[] = (await get_sections()).map((s) => s.title);
|
||||
// TODO: implement logic to fetch the available docs paths to return in the prompt
|
||||
const available_docs: string[] = [];
|
||||
|
||||
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>
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
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 './doc-section.js';
|
||||
export * from './list-sections.js';
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
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,7 +1,5 @@
|
||||
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(
|
||||
@@ -9,7 +7,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())]),
|
||||
@@ -19,7 +17,7 @@ export function get_documentation(server: SvelteMcp) {
|
||||
),
|
||||
}),
|
||||
},
|
||||
async ({ section }) => {
|
||||
({ section }) => {
|
||||
let sections: string[];
|
||||
|
||||
if (Array.isArray(section)) {
|
||||
@@ -45,73 +43,13 @@ export function get_documentation(server: SvelteMcp) {
|
||||
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}`;
|
||||
}
|
||||
const sections_list = sections.length > 0 ? sections.join(', ') : 'no sections';
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: final_text,
|
||||
text: `called for sections: ${sections_list}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
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(
|
||||
@@ -8,22 +6,14 @@ 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], 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.',
|
||||
'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.',
|
||||
},
|
||||
async () => {
|
||||
const sections = await get_sections();
|
||||
const formatted_sections = sections
|
||||
.map(
|
||||
(section) =>
|
||||
`* title: ${section.title}, use_cases: ${section.use_cases}, path: ${section.url}`,
|
||||
)
|
||||
.join('\n');
|
||||
|
||||
() => {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `${SECTIONS_LIST_INTRO}\n\n${formatted_sections}\n\n${SECTIONS_LIST_OUTRO}`,
|
||||
text: 'tool list_sections called',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
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.';
|
||||
@@ -3,6 +3,7 @@ import { McpServer } from 'tmcp';
|
||||
import { setup_prompts, setup_resources, setup_tools } from './handlers/index.js';
|
||||
import type { LibSQLDatabase } from 'drizzle-orm/libsql';
|
||||
import type { Schema } from '@sveltejs/mcp-schema';
|
||||
import type { ClassType } from 'remult';
|
||||
|
||||
export const server = new McpServer(
|
||||
{
|
||||
@@ -21,7 +22,7 @@ export const server = new McpServer(
|
||||
instructions:
|
||||
'This is the official Svelte MCP server. It MUST be used whenever svelte development is involved. It can provide official documentation, code examples and correct your code. After you correct the component call this tool again to confirm all the issues are fixed.',
|
||||
},
|
||||
).withContext<{ db: LibSQLDatabase<Schema> }>();
|
||||
).withContext<{ db: LibSQLDatabase<Schema>; entities: Record<string, ClassType<unknown>> }>();
|
||||
|
||||
export type SvelteMcp = typeof server;
|
||||
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
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(),
|
||||
}),
|
||||
);
|
||||
@@ -1,31 +0,0 @@
|
||||
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,11 +22,10 @@
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsdown && publint",
|
||||
"build": "tsdown",
|
||||
"dev": "tsdown --watch",
|
||||
"test": "vitest",
|
||||
"check": "tsc --noEmit",
|
||||
"check:publint": "publint --strict"
|
||||
"check": "tsc --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/mcp-server": "workspace:^",
|
||||
|
||||
602
pnpm-lock.yaml
generated
602
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user