search_msfs_docs
Search Microsoft Flight Simulator SDK documentation by topic, category, or keyword to find development resources and technical information.
Instructions
Search MSFS SDK documentation for specific topics
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Search query for MSFS SDK documentation | |
| category | No | Optional category filter (e.g., "contents", "index", "glossary") | |
| limit | No | Maximum number of results to return |
Implementation Reference
- Core handler function that implements the 'search_msfs_docs' tool. Fetches the MSFS SDK documentation search page, parses HTML with Cheerio to extract relevant .htm links containing the query, categorizes results, and returns formatted markdown results.
async searchDocumentation( query: string, category: string = 'all', limit: number = 10 ): Promise<{ content: Array<{ type: string; text: string }> }> { try { // Normalize category const normalizedCategory = category.toLowerCase(); const agtParam = this.categories[normalizedCategory as keyof typeof this.categories] || this.categories.all; // Build search URL using the website's search functionality let searchUrl = `${this.searchBaseUrl}?rhsearch=${encodeURIComponent(query)}`; if (agtParam) { searchUrl += `&agt=${agtParam}`; } console.log(`Searching MSFS docs: ${searchUrl}`); const response = await fetch(searchUrl, { headers: { 'User-Agent': 'MSFS-SDK-MCP-Server/1.0' } }); if (!response.ok) { throw new Error(`Failed to fetch search results: ${response.status}`); } const html = await response.text(); const $ = cheerio.load(html); console.log(`Search page loaded, length: ${html.length}`); // Extract search results from the page const searchResults: DocumentationResult[] = []; console.log('Analyzing HTML structure for search results...'); // Debug: Log some of the HTML to understand the structure const bodyText = $('body').text(); if (bodyText.includes('result(s) found')) { console.log('Found search results indicator in page'); } // Try multiple approaches to find search results const possibleSelectors = [ '.search-result', '.result-item', '.topic', '.search-hit', '.result', 'li:contains("Samples, Schemas, Tutorials")', 'li:contains("How To")', 'li:contains("Content Configuration")', 'li:contains("Developer Mode")', 'div:contains("result(s) found") ~ *', 'a[href*=".htm"]' ]; // First, try to find structured search results for (const selector of possibleSelectors.slice(0, -1)) { $(selector).each((i, elem) => { if (searchResults.length >= limit) return false; const $elem = $(elem); const href = $elem.attr('href') || $elem.find('a').attr('href'); const text = $elem.text().trim(); if (href && href.includes('.htm') && text) { // Clean up the href and construct proper absolute URL let cleanHref = href; if (href.startsWith('../')) { cleanHref = href.replace(/^\.\.\//, '/html/'); } else if (!href.startsWith('/') && !href.startsWith('http')) { cleanHref = `/html/${href}`; } const absoluteUrl = cleanHref.startsWith('http') ? cleanHref : `${this.baseUrl}${cleanHref}`; // Skip if we already have this URL if (searchResults.some(r => r.url === absoluteUrl)) { return; } const categoryFromPath = this.deriveCategoryFromPath(href); searchResults.push({ title: text.substring(0, 100), // Limit title length url: absoluteUrl, description: `Documentation page containing "${query}"`, category: categoryFromPath }); console.log(`Found result via ${selector}: ${text.substring(0, 50)}...`); } }); if (searchResults.length > 0) { console.log(`Found ${searchResults.length} results using selector: ${selector}`); break; } } // If no structured results found, fall back to all links if (searchResults.length === 0) { console.log('No structured results found, trying all links...'); $('a[href*=".htm"]').each((i, elem) => { if (searchResults.length >= limit) return false; const $elem = $(elem); const href = $elem.attr('href'); const text = $elem.text().trim(); if (href && text && text.toLowerCase().includes(query.toLowerCase())) { // Clean up the href and construct proper absolute URL let cleanHref = href; if (href.startsWith('../')) { cleanHref = href.replace(/^\.\.\//, '/html/'); } else if (!href.startsWith('/') && !href.startsWith('http')) { cleanHref = `/html/${href}`; } const absoluteUrl = cleanHref.startsWith('http') ? cleanHref : `${this.baseUrl}${cleanHref}`; // Skip if we already have this URL if (searchResults.some(r => r.url === absoluteUrl)) { return; } const categoryFromPath = this.deriveCategoryFromPath(href); searchResults.push({ title: text.substring(0, 100), url: absoluteUrl, description: `Found "${query}" in link text`, category: categoryFromPath }); } }); } // If no specific search results found, try to extract any documentation links if (searchResults.length === 0) { $('a[href*=".htm"]').each((i, elem) => { if (searchResults.length >= limit) return false; const $elem = $(elem); const href = $elem.attr('href'); const text = $elem.text().trim(); if (href && text && text.toLowerCase().includes(query.toLowerCase())) { // Clean up the href and construct proper absolute URL let cleanHref = href; if (href.startsWith('../')) { cleanHref = href.replace(/^\.\.\//, '/html/'); } else if (!href.startsWith('/') && !href.startsWith('http')) { cleanHref = `/html/${href}`; } const absoluteUrl = cleanHref.startsWith('http') ? cleanHref : `${this.baseUrl}${cleanHref}`; // Skip if we already have this URL if (searchResults.some(r => r.url === absoluteUrl)) { return; } const categoryFromPath = this.deriveCategoryFromPath(href); searchResults.push({ title: text.substring(0, 100), url: absoluteUrl, description: `Found "${query}" in link text`, category: categoryFromPath }); } }); } if (searchResults.length > 0) { const formattedResults = searchResults.map((result: DocumentationResult) => `**${result.title}**\n- Category: ${result.category}\n- URL: ${result.url}\n- Description: ${result.description}\n` ).join('\n---\n'); return { content: [ { type: 'text', text: `Search results for "${query}" in category "${category}":\n\n${formattedResults}` }, ], }; } // If still no results, return a message return { content: [ { type: 'text', text: `No results found for "${query}" in category "${category}". The search was performed on the MSFS documentation website.` }, ], }; } catch (error) { console.error('Search error:', error); return { content: [ { type: 'text', text: `Error searching MSFS documentation: ${(error as Error).message}. Please try a different search term or check your internet connection.` }, ], }; } } - src/index.ts:37-62 (schema)Tool schema definition including input parameters: query (required), category (enum), limit.
{ name: 'search_msfs_docs', description: 'Search MSFS SDK documentation for specific topics', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query for MSFS SDK documentation', }, category: { type: 'string', description: 'Optional category filter (e.g., "contents", "index", "glossary")', enum: ['contents', 'index', 'glossary', 'all'] }, limit: { type: 'number', description: 'Maximum number of results to return', minimum: 1, maximum: 20, default: 10 } }, required: ['query'] } }, - src/index.ts:34-120 (registration)Registration of the tool in the ListToolsRequestHandler, including its name, description, and schema.
this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'search_msfs_docs', description: 'Search MSFS SDK documentation for specific topics', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query for MSFS SDK documentation', }, category: { type: 'string', description: 'Optional category filter (e.g., "contents", "index", "glossary")', enum: ['contents', 'index', 'glossary', 'all'] }, limit: { type: 'number', description: 'Maximum number of results to return', minimum: 1, maximum: 20, default: 10 } }, required: ['query'] } }, { name: 'get_doc_content', description: 'Get detailed content from a specific MSFS SDK documentation page', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'URL of the documentation page to retrieve', }, section: { type: 'string', description: 'Specific section to extract (e.g., "overview", "examples", "api-reference")', } }, required: ['url'] } }, { name: 'list_categories', description: 'List all available MSFS SDK documentation categories', inputSchema: { type: 'object', properties: {} } }, { name: 'natural_language_query', description: 'Process natural language queries like "Search livery op msfs sdk"', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Natural language query (e.g., "Search livery op msfs sdk")', } }, required: ['query'] } }, { name: 'list_category_items', description: 'Returns all items for a given documentation category', inputSchema: { type: 'object', properties: { category: { type: 'string', description: 'Category to list items from (index, contents, or glossary)', enum: ['index', 'contents', 'glossary'] } }, required: ['category'] } } ], }; }); - src/index.ts:127-136 (handler)Direct handler case in CallToolRequestHandler that validates input and delegates to documentationService.searchDocumentation.
case 'search_msfs_docs': if (!args?.query) { throw new Error('Query parameter is required'); } return await this.documentationService.searchDocumentation( String(args.query), String(args.category || 'all'), Number(args.limit || 10) ); - Helper function to parse natural language queries like 'Search livery op msfs sdk' into search_msfs_docs tool call. Additional pattern at lines 49-64 for category-specific searches.
static parse(command: string): { tool: string; arguments: any } | null { // Match "Search [term] op msfs sdk" const searchPattern = /^Search\s+(.+?)\s+op\s+msfs\s+sdk\s*$/i; const match = command.match(searchPattern); if (match) { const query = match[1]; return { tool: "search_msfs_docs", arguments: { query: query, category: "all", limit: 5 } }; }