UNPKG

@yeepay/awesome-components-mcp

Version:

MCP server providing access to awesome-components documentation and integration guides with dual-mode operation: direct fetch and GitLab MCP instruction generation

304 lines (289 loc) 10.9 kB
"use strict"; /** * Get Components Guide MCP Tool * * This tool retrieves comprehensive integration guides for specific components * with intelligent path resolution and security validation. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.getComponentsGuideSchema = void 0; exports.getComponentsGuideTool = getComponentsGuideTool; const zod_1 = require("zod"); const config_js_1 = require("../config.js"); const gitlabClient_js_1 = require("../services/gitlabClient.js"); /** * Schema for get components guide tool parameters */ exports.getComponentsGuideSchema = zod_1.z.object({ path: zod_1.z.string().min(1).describe('Path to the component\'s llms.txt file (relative or absolute URL)') }); /** * Validates if a path is safe and doesn't contain dangerous characters */ function isValidPath(path) { // Check for empty path if (!path || path.trim().length === 0) { return false; } // Check for dangerous characters and patterns const dangerousPatterns = [ /\.\./, // Directory traversal /<script/i, // Script injection /<\/script/i, // Script injection /javascript:/i, // JavaScript protocol /data:/i, // Data protocol /vbscript:/i, // VBScript protocol /[<>]/, // HTML/XML tags /[\x00-\x1f]/, // Control characters ]; return !dangerousPatterns.some(pattern => pattern.test(path)); } /** * Determines if a URL is external (not matching the internal GitLab host) */ function isExternalUrl(path, internalHost) { // If path doesn't start with http/https, it's a relative path (internal) if (!path.startsWith('http://') && !path.startsWith('https://')) { return false; } // If no internal host is configured, treat all absolute URLs as external if (!internalHost) { return true; } try { const url = new URL(path); return url.hostname !== internalHost; } catch (e) { // Malformed URL, treat as external to be safe return true; } } /** * Extracts the title from content (first markdown header or filename) */ function extractTitle(content, path) { // Try to find the first markdown header const headerMatch = content.match(/^#\s+(.+)$/m); if (headerMatch) { return headerMatch[1].trim(); } // Fall back to the filename or path const filename = path.split('/').pop() || path; return filename.replace(/\.(txt|md|markdown)$/i, ''); } /** * Formats the guide content with metadata */ function formatGuideContent(content, path) { const title = extractTitle(content, path); const contentLength = content.length; return `# Component Guide: ${title} **Source Path:** \`${path}\` **Content Length:** ${contentLength} characters **Retrieved:** ${new Date().toISOString()} --- ${content}`; } /** * Fetches content from external URL using HTTP GET */ async function fetchExternalUrl(url) { try { console.log(`Fetching external URL: ${url}`); const response = await fetch(url, { headers: { 'User-Agent': 'awesome-components-mcp/1.0.0' } }); if (!response.ok) { throw new gitlabClient_js_1.GitLabError(`Failed to fetch external URL (status: ${response.status})`, response.status, url); } const content = await response.text(); console.log(`Successfully fetched ${content.length} characters from external URL`); return content; } catch (error) { if (error instanceof gitlabClient_js_1.GitLabError) { throw error; } throw new gitlabClient_js_1.GitLabError(`Network error while fetching external URL: ${error instanceof Error ? error.message : 'Unknown error'}`, undefined, url); } } /** * Validates GitLab MCP configuration */ function validateGitLabMcpConfig() { if (!config_js_1.config.internalGuidesProjectId) { return { isValid: false, error: 'GITLAB_PROJECT_ID is required for GitLab MCP instruction mode' }; } return { isValid: true }; } /** * Get Components Guide MCP Tool Implementation */ async function getComponentsGuideTool(params) { try { const { path } = params; console.log(`Getting component guide for path: ${path}`); // Validate the path for security if (!isValidPath(path)) { const errorResponse = `# Error: Invalid Component Guide Path The provided path contains invalid characters or format: \`${path}\` **Security Note:** Paths cannot contain: - Directory traversal patterns (\`../\`) - Script injection attempts (\`<script>\`) - Control characters or HTML tags - Protocol handlers (javascript:, data:, etc.) **Valid Examples:** - \`yeepay/dynamicpassword/llms.txt\` - \`common/authentication/guide.md\` - \`https://external-gitlab.com/project/-/raw/main/component/llms.txt\` **Suggestions:** - Use relative paths from the repository root - Use absolute GitLab raw file URLs for external repositories - Ensure the path points to a valid documentation file`; return { content: [{ type: 'text', text: errorResponse }], isError: true }; } // Check if this is an external URL if (isExternalUrl(path, config_js_1.config.internalGitlabHost)) { console.log(`External URL detected: ${path}`); // For external URLs, use direct HTTP GET request const content = await fetchExternalUrl(path); const formattedContent = formatGuideContent(content, path); return { content: [{ type: 'text', text: formattedContent }] }; } // For relative paths and internal URLs, generate gitlab-mcp instruction console.log(`Relative/internal path detected, generating gitlab-mcp instruction: ${path}`); // Validate GitLab MCP configuration const configValidation = validateGitLabMcpConfig(); if (!configValidation.isValid) { throw new Error(`GitLab MCP Configuration Error: ${configValidation.error}`); } // Derive file path for GitLab let filePathForGitlab = path; // If it's an internal URL, extract the path if (path.startsWith('http://') || path.startsWith('https://')) { try { const url = new URL(path); filePathForGitlab = url.pathname; if (filePathForGitlab.startsWith('/')) { filePathForGitlab = filePathForGitlab.substring(1); } } catch (e) { // If URL parsing fails, use the original path } } // Generate JSON instruction for LLM to call gitlab-mcp // project_id should be URL encoded only if it's a string (not a numeric ID) // and only if it's not already encoded let projectId = config_js_1.config.internalGuidesProjectId; if (typeof projectId === 'string' && isNaN(Number(projectId))) { // Check if it's already URL encoded by looking for % characters if (!projectId.includes('%')) { projectId = encodeURIComponent(projectId); } } const instruction = { action_type: 'mcp_call', tool_name: 'get_file_contents', parameters: { project_id: projectId, file_path: filePathForGitlab, ref: 'main' } }; const instructionOutput = `# Component Guide - GitLab MCP Instruction To retrieve the component guide for \`${path}\`, please call the gitlab-mcp server with the following instruction: \`\`\`json ${JSON.stringify(instruction, null, 2)} \`\`\` **Instructions for LLM:** 1. Use the above JSON instruction to call the gitlab-mcp server 2. Call the \`get_file_contents\` tool with the provided parameters 3. The response will contain the component guide content **Configuration:** - Project ID: \`${config_js_1.config.internalGuidesProjectId}\` - File Path: \`${filePathForGitlab}\` - Branch: \`main\` - Original Path: \`${path}\``; return { content: [{ type: 'text', text: instructionOutput }] }; } catch (error) { console.error('Get components guide failed:', error); let errorMessage = 'Unknown error occurred'; let statusInfo = ''; let suggestions = `**Suggestions:** - Check if the component path is correct - Verify the file exists in the repository - Ensure you have proper access permissions - Try using the components_discovery tool to find available components`; if (error instanceof gitlabClient_js_1.GitLabError) { errorMessage = `GitLab Error: ${error.message}`; if (error.statusCode) { statusInfo = `\n**Status Code:** ${error.statusCode}`; // Provide specific suggestions based on status code if (error.statusCode === 404) { suggestions = `**Suggestions:** - Verify the component path is correct: \`${params.path}\` - Check if the file exists in the repository - Use the components_discovery tool to see available components - Ensure the file extension is correct (e.g., llms.txt, guide.md)`; } else if (error.statusCode === 403) { suggestions = `**Suggestions:** - This repository may be private and require authentication - Check your GitLab Personal Access Token configuration - Verify your token has read access to this repository - Contact the repository administrator for access`; } } if (error.url) { statusInfo += `\n**URL:** ${error.url}`; } } else if (error instanceof Error) { errorMessage = `Error: ${error.message}`; } else { errorMessage = `Error: ${String(error)}`; } const errorTitle = error instanceof gitlabClient_js_1.GitLabError && error.statusCode === 404 ? 'Component Guide Not Found' : 'Failed to Retrieve Component Guide'; const errorResponse = `# Error: ${errorTitle} ${errorMessage}${statusInfo} ${suggestions} **Configuration:** - GitLab Base URL: ${config_js_1.config.urls.gitlabBase} - Authentication: ${config_js_1.config.auth.gitlabToken ? 'Configured' : 'Not configured'} - Requested Path: \`${params.path}\``; return { content: [{ type: 'text', text: errorResponse }], isError: true }; } }