Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e7431e9
Update README.md
khromov Sep 26, 2025
dc6c87c
Merge branch 'main' into llms-txt
khromov Sep 26, 2025
972cadc
Update package.json
khromov Sep 26, 2025
917fdf6
enable
khromov Sep 26, 2025
68cf69a
wip
khromov Sep 26, 2025
19cacf7
refactor
khromov Sep 26, 2025
e314ab5
Update list-sections.ts
khromov Sep 26, 2025
1bb171c
cleanup
khromov Sep 26, 2025
d33a374
Update get-documentation.ts
khromov Sep 26, 2025
6cb97ac
Update get-documentation.ts
khromov Sep 26, 2025
c49b24d
wip
khromov Sep 26, 2025
b774b46
Update get-documentation.ts
khromov Sep 26, 2025
0f54824
Update get-documentation.ts
khromov Sep 26, 2025
bf477a6
Update get-documentation.ts
khromov Sep 26, 2025
7f9ea74
don't lint .claude dir
khromov Sep 26, 2025
c05b6c2
eslint
khromov Sep 26, 2025
8328a35
eslint
khromov Sep 26, 2025
fb2d19f
Update list-sections.ts
khromov Sep 26, 2025
b1a1964
Update list-sections.ts
khromov Sep 26, 2025
77af7eb
Update packages/mcp-server/src/mcp/handlers/tools/get-documentation.ts
khromov Sep 26, 2025
6a6417d
Use Promise.allSettled()
khromov Sep 26, 2025
0366bc7
Merge branch 'llms-txt' of https://github.com/sveltejs/mcp into llms-txt
khromov Sep 26, 2025
01d5803
reduce duplication
khromov Sep 26, 2025
54763e0
Update packages/mcp-server/src/mcp/utils.ts
khromov Sep 26, 2025
76a35f5
format
khromov Sep 26, 2025
5dd83d1
Update utils.ts
khromov Sep 26, 2025
ce0861c
chore: use mocked current version of sections
paoloricciuti Sep 28, 2025
8231966
feat: add sections to prompt
paoloricciuti Sep 28, 2025
5bc812e
fix: enable `list-sections` tool
paoloricciuti Sep 28, 2025
a281ef4
fix: use `server.template` with list and complete for docs resources
paoloricciuti Sep 28, 2025
70f14bd
feat: use endpoint to get sections
paoloricciuti Oct 2, 2025
a36d0d1
fix: validate response and default `use_cases`
paoloricciuti Oct 2, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"type": "module",
"scripts": {
"build": "pnpm -r run build",
"dev": "pnpm --filter @sveltejs/mcp-remote run dev",
"check": "pnpm -r run check",
"format": "prettier --write .",
"lint": "prettier --check . && eslint .",
Expand Down
55 changes: 50 additions & 5 deletions packages/mcp-server/src/mcp/handlers/tools/get-documentation.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import type { SvelteMcp } from '../../index.js';
import * as v from 'valibot';
import { getSections, fetchWithTimeout } from '../../utils.js';

export function get_documentation(server: SvelteMcp) {
server.tool(
{
name: 'get-documentation',
enabled: () => false,
enabled: () => true,
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())]),
Expand All @@ -17,7 +18,7 @@ export function get_documentation(server: SvelteMcp) {
),
}),
},
({ section }) => {
async ({ section }) => {
let sections: string[];

if (Array.isArray(section)) {
Expand All @@ -43,13 +44,57 @@ export function get_documentation(server: SvelteMcp) {
sections = [];
}

const sections_list = sections.length > 0 ? sections.join(', ') : 'no sections';
const availableSections = await getSections();

const results = await Promise.all(
sections.map(async (requestedSection) => {
const matchedSection = availableSections.find(
s => s.title.toLowerCase() === requestedSection.toLowerCase() ||
s.url === requestedSection
);

if (matchedSection) {
try {
const response = await fetchWithTimeout(matchedSection.url);
if (response.ok) {
const content = await response.text();
return { success: true, content: `## ${matchedSection.title}\n\n${content}` };
} else {
return { success: false, content: `## ${matchedSection.title}\n\nError: Could not fetch documentation (HTTP ${response.status})` };
}
} catch (error) {
return { success: false, content: `## ${matchedSection.title}\n\nError: Failed to fetch documentation - ${error}` };
}
} else {
return { success: false, content: `## ${requestedSection}\n\nError: Section not found.` };
}
})
);

const hasAnySuccess = results.some(result => result.success);
let finalText = results.map(r => r.content).join('\n\n---\n\n');

if (!hasAnySuccess) {
const formattedSections = availableSections
.map(
(section) =>
`* title: ${section.title}, use_cases: ${section.use_cases}, path: ${section.url}`,
Copy link
Collaborator Author

@khromov khromov Oct 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe let's not add use_cases if it's empty, and put it at the end (so it's title, path, (maybe) use_cases)?

)
.join('\n');

const introText = 'List of available Svelte documentation sections and its inteneded uses:';

const outroText =
'Use the title or path with the get-documentation tool to get more details about a specific section.';

finalText += `\n\n---\n\n${introText}\n\n${formattedSections}\n\n${outroText}`;
}

return {
content: [
{
type: 'text',
text: `called for sections: ${sections_list}`,
text: finalText,
},
],
};
Expand Down
22 changes: 18 additions & 4 deletions packages/mcp-server/src/mcp/handlers/tools/list-sections.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,33 @@
import type { SvelteMcp } from '../../index.js';
import { getSections } from '../../utils.js';

export function list_sections(server: SvelteMcp) {
server.tool(
{
name: 'list-sections',
enabled: () => false,
enabled: () => true,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
enabled: () => true,

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 getSections();
const formattedSections = sections
.map(
(section) =>
`* title: ${section.title}, use_cases: ${section.use_cases}, path: ${section.url}`,
)
.join('\n');

const introText = 'List of available Svelte documentation sections and its inteneded uses:';

const outroText =
'Use the title or path with the get-documentation tool to get more details about a specific section.';

return {
content: [
{
type: 'text',
text: 'tool list_sections called',
text: `${introText}\n\n${formattedSections}\n\n${outroText}`,
},
],
};
Expand Down
36 changes: 36 additions & 0 deletions packages/mcp-server/src/mcp/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export async function fetchWithTimeout(url: string, timeoutMs: number = 10000): Promise<Response> {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);

try {
const response = await fetch(url, { signal: controller.signal });
clearTimeout(timeoutId);
return response;
} catch (error) {
clearTimeout(timeoutId);
if (error instanceof Error && error.name === 'AbortError') {
throw new Error(`Request timed out after ${timeoutMs}ms`);
}
throw error;
}
}

export async function getSections() {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dummy data until sveltejs/svelte.dev#1556 is merged

return [
{
title: 'Overview',
url: 'https://svelte.dev/docs/svelte/overview/llms.txt',
use_cases: 'Useful when a beginner learns about Svelte',
},
{
title: 'Getting Started',
url: 'https://svelte.dev/docs/svelte/getting-started/llms.txt',
use_cases: 'Useful when a beginner is starting a new Svelte project',
},
{
title: 'Svelte Files',
url: 'https://svelte.dev/docs/svelte/svelte-files/llms.txt',
use_cases: 'Useful when a beginner is learning about Svelte files',
},
];
}
Loading