-
Notifications
You must be signed in to change notification settings - Fork 3
feat: simplified documentation listing #28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 13 commits
e7431e9
dc6c87c
972cadc
917fdf6
68cf69a
19cacf7
e314ab5
1bb171c
d33a374
6cb97ac
c49b24d
b774b46
0f54824
bf477a6
7f9ea74
c05b6c2
8328a35
fb2d19f
b1a1964
77af7eb
6a6417d
0366bc7
01d5803
54763e0
76a35f5
5dd83d1
ce0861c
8231966
5bc812e
a281ef4
70f14bd
a36d0d1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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())]), | ||
|
@@ -17,7 +18,7 @@ export function get_documentation(server: SvelteMcp) { | |
), | ||
}), | ||
}, | ||
({ section }) => { | ||
async ({ section }) => { | ||
let sections: string[]; | ||
|
||
if (Array.isArray(section)) { | ||
|
@@ -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( | ||
khromov marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
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}`, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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}`; | ||
} | ||
khromov marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
||
return { | ||
content: [ | ||
{ | ||
type: 'text', | ||
text: `called for sections: ${sections_list}`, | ||
text: finalText, | ||
}, | ||
], | ||
}; | ||
|
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, | ||||
|
enabled: () => true, |
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; | ||
} | ||
} | ||
khromov marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
||
export async function getSections() { | ||
|
||
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', | ||
}, | ||
]; | ||
} |
Uh oh!
There was an error while loading. Please reload this page.