Skip to content

Commit 7b5bea6

Browse files
Merge pull request #28 from sveltejs/llms-txt
feat: simplified documentation listing
2 parents 07737a8 + a36d0d1 commit 7b5bea6

File tree

13 files changed

+194
-33
lines changed

13 files changed

+194
-33
lines changed

.prettierignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,7 @@ bun.lockb
88
# Miscellaneous
99
/static/
1010
/drizzle/
11-
/**/.svelte-kit/*
11+
/**/.svelte-kit/*
12+
13+
# Claude Code
14+
.claude/

CLAUDE.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,12 @@ When connected to the svelte-llm MCP server, you have access to comprehensive Sv
9090

9191
## Available MCP Tools:
9292

93-
### 1. list_sections
93+
### 1. list-sections
9494

9595
Use this FIRST to discover all available documentation sections. Returns a structured list with titles and paths.
9696
When asked about Svelte or SvelteKit topics, ALWAYS use this tool at the start of the chat to find relevant sections.
9797

98-
### 2. get_documentation
98+
### 2. get-documentation
9999

100100
Retrieves full documentation content for specific sections. Accepts single or multiple sections.
101-
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.
101+
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.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Repo for the official Svelte MCP server.
66

77
```
88
pnpm i
9-
cp .env.example .env
9+
cp apps/mcp-remote/.env.example apps/mcp-remote/.env
1010
pnpm dev
1111
```
1212

eslint.config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ const gitignore_path = fileURLToPath(new URL('./.gitignore', import.meta.url));
1212

1313
export default /** @type {import("eslint").Linter.Config} */ ([
1414
includeIgnoreFile(gitignore_path),
15+
{
16+
ignores: ['.claude/**/*'],
17+
},
1518
js.configs.recommended,
1619
...ts.configs.recommended,
1720
...svelte.configs.recommended,

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"type": "module",
66
"scripts": {
77
"build": "pnpm -r run build",
8+
"dev": "pnpm --filter @sveltejs/mcp-remote run dev",
89
"check": "pnpm -r run check",
910
"format": "prettier --write .",
1011
"lint": "prettier --check . && eslint .",

packages/mcp-server/src/mcp/handlers/prompts/svelte-task.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { SvelteMcp } from '../../index.js';
22
import * as v from 'valibot';
3+
import { get_sections } from '../../utils.js';
34

45
export function setup_svelte_task(server: SvelteMcp) {
56
server.prompt(
@@ -13,8 +14,7 @@ export function setup_svelte_task(server: SvelteMcp) {
1314
}),
1415
},
1516
async ({ task }) => {
16-
// TODO: implement logic to fetch the available docs paths to return in the prompt
17-
const available_docs: string[] = [];
17+
const available_docs: string[] = (await get_sections()).map((s) => s.title);
1818

1919
return {
2020
messages: [

packages/mcp-server/src/mcp/handlers/resources/list-sections.ts

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,61 @@
11
import type { SvelteMcp } from '../../index.js';
2+
import { get_sections, fetch_with_timeout } from '../../utils.js';
23

3-
export function list_sections(server: SvelteMcp) {
4-
server.resource(
4+
export async function list_sections(server: SvelteMcp) {
5+
const sections = await get_sections();
6+
7+
server.template(
58
{
6-
name: 'list-sections',
7-
enabled: () => false,
8-
description:
9-
'The list of all the available Svelte 5 and SvelteKit documentation sections in a structured format.',
10-
uri: 'svelte://list-sections',
11-
title: 'Svelte Documentation Section',
9+
name: 'Svelte Doc Section',
10+
description: 'A single documentation section',
11+
list() {
12+
return sections.map((section) => {
13+
const section_name = section.slug;
14+
const resource_name = section_name;
15+
const resource_uri = `svelte://${section_name}.md`;
16+
return {
17+
name: resource_name,
18+
description: section.use_cases,
19+
uri: resource_uri,
20+
title: section.title,
21+
};
22+
});
23+
},
24+
complete: {
25+
slug: (query) => {
26+
const values = sections
27+
.reduce<string[]>((acc, section) => {
28+
const section_name = section.slug;
29+
const resource_name = section_name;
30+
if (section_name.includes(query.toLowerCase())) {
31+
acc.push(resource_name);
32+
}
33+
return acc;
34+
}, [])
35+
// there's a hard limit of 100 for completions
36+
.slice(0, 100);
37+
return {
38+
completion: {
39+
values,
40+
},
41+
};
42+
},
43+
},
44+
uri: 'svelte://{/slug*}.md',
1245
},
13-
async (uri) => {
46+
async (uri, { slug }) => {
47+
const section = sections.find((section) => {
48+
return slug === section.slug;
49+
});
50+
if (!section) throw new Error(`Section not found: ${slug}`);
51+
const response = await fetch_with_timeout(section.url);
52+
const content = await response.text();
1453
return {
1554
contents: [
1655
{
1756
uri,
1857
type: 'text',
19-
text: 'resource list-sections called',
58+
text: content,
2059
},
2160
],
2261
};

packages/mcp-server/src/mcp/handlers/tools/get-documentation.ts

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import type { SvelteMcp } from '../../index.js';
22
import * as v from 'valibot';
3+
import { get_sections, fetch_with_timeout } from '../../utils.js';
4+
import { SECTIONS_LIST_INTRO, SECTIONS_LIST_OUTRO } from './prompts.js';
35

46
export function get_documentation(server: SvelteMcp) {
57
server.tool(
68
{
79
name: 'get-documentation',
8-
enabled: () => false,
910
description:
10-
'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.',
11+
'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.',
1112
schema: v.object({
1213
section: v.pipe(
1314
v.union([v.string(), v.array(v.string())]),
@@ -17,7 +18,7 @@ export function get_documentation(server: SvelteMcp) {
1718
),
1819
}),
1920
},
20-
({ section }) => {
21+
async ({ section }) => {
2122
let sections: string[];
2223

2324
if (Array.isArray(section)) {
@@ -43,13 +44,73 @@ export function get_documentation(server: SvelteMcp) {
4344
sections = [];
4445
}
4546

46-
const sections_list = sections.length > 0 ? sections.join(', ') : 'no sections';
47+
const available_sections = await get_sections();
48+
49+
const settled_results = await Promise.allSettled(
50+
sections.map(async (requested_section) => {
51+
const matched_section = available_sections.find(
52+
(s) =>
53+
s.title.toLowerCase() === requested_section.toLowerCase() ||
54+
s.url === requested_section,
55+
);
56+
57+
if (matched_section) {
58+
try {
59+
const response = await fetch_with_timeout(matched_section.url);
60+
if (response.ok) {
61+
const content = await response.text();
62+
return { success: true, content: `## ${matched_section.title}\n\n${content}` };
63+
} else {
64+
return {
65+
success: false,
66+
content: `## ${matched_section.title}\n\nError: Could not fetch documentation (HTTP ${response.status})`,
67+
};
68+
}
69+
} catch (error) {
70+
return {
71+
success: false,
72+
content: `## ${matched_section.title}\n\nError: Failed to fetch documentation - ${error}`,
73+
};
74+
}
75+
} else {
76+
return {
77+
success: false,
78+
content: `## ${requested_section}\n\nError: Section not found.`,
79+
};
80+
}
81+
}),
82+
);
83+
84+
const results = settled_results.map((result) => {
85+
if (result.status === 'fulfilled') {
86+
return result.value;
87+
} else {
88+
return {
89+
success: false,
90+
content: `Error: Couldn't fetch - ${result.reason}`,
91+
};
92+
}
93+
});
94+
95+
const has_any_success = results.some((result) => result.success);
96+
let final_text = results.map((r) => r.content).join('\n\n---\n\n');
97+
98+
if (!has_any_success) {
99+
const formatted_sections = available_sections
100+
.map(
101+
(section) =>
102+
`* title: ${section.title}, use_cases: ${section.use_cases}, path: ${section.url}`,
103+
)
104+
.join('\n');
105+
106+
final_text += `\n\n---\n\n${SECTIONS_LIST_INTRO}\n\n${formatted_sections}\n\n${SECTIONS_LIST_OUTRO}`;
107+
}
47108

48109
return {
49110
content: [
50111
{
51112
type: 'text',
52-
text: `called for sections: ${sections_list}`,
113+
text: final_text,
53114
},
54115
],
55116
};

packages/mcp-server/src/mcp/handlers/tools/list-sections.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,28 @@
11
import type { SvelteMcp } from '../../index.js';
2+
import { get_sections } from '../../utils.js';
3+
import { SECTIONS_LIST_INTRO, SECTIONS_LIST_OUTRO } from './prompts.js';
24

35
export function list_sections(server: SvelteMcp) {
46
server.tool(
57
{
68
name: 'list-sections',
7-
enabled: () => false,
89
description:
9-
'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.',
10+
'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.',
1011
},
11-
() => {
12+
async () => {
13+
const sections = await get_sections();
14+
const formatted_sections = sections
15+
.map(
16+
(section) =>
17+
`* title: ${section.title}, use_cases: ${section.use_cases}, path: ${section.url}`,
18+
)
19+
.join('\n');
20+
1221
return {
1322
content: [
1423
{
1524
type: 'text',
16-
text: 'tool list_sections called',
25+
text: `${SECTIONS_LIST_INTRO}\n\n${formatted_sections}\n\n${SECTIONS_LIST_OUTRO}`,
1726
},
1827
],
1928
};
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export const SECTIONS_LIST_INTRO =
2+
'List of available Svelte documentation sections and its inteneded uses:';
3+
4+
export const SECTIONS_LIST_OUTRO =
5+
'Use the title or path with the get-documentation tool to get more details about a specific section.';

0 commit comments

Comments
 (0)