@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
190 lines (189 loc) • 7.56 kB
JavaScript
;
/**
* GitLab Data Acquisition Service
*
* This service provides functionality to fetch text content (specifically llms.txt files)
* from GitLab URLs using HTTP GET requests with proper error handling and authentication.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.GitLabAuthError = exports.GitLabError = void 0;
exports.fetchGitLabFileContent = fetchGitLabFileContent;
exports.isValidGitLabUrl = isValidGitLabUrl;
exports.validateAuthenticationConfig = validateAuthenticationConfig;
exports.fetchGitLabFileContentSafe = fetchGitLabFileContentSafe;
const config_js_1 = require("../config.js");
/**
* Custom error class for GitLab-specific errors
*/
class GitLabError extends Error {
statusCode;
url;
constructor(message, statusCode, url) {
super(message);
this.statusCode = statusCode;
this.url = url;
this.name = 'GitLabError';
}
}
exports.GitLabError = GitLabError;
/**
* Authentication error class for GitLab authentication failures
*/
class GitLabAuthError extends GitLabError {
constructor(message, url) {
super(message, 401, url);
this.name = 'GitLabAuthError';
}
}
exports.GitLabAuthError = GitLabAuthError;
/**
* Creates authentication headers for GitLab requests
*
* @returns Record<string, string> - Headers object with authentication if token is available
*/
function createAuthHeaders() {
const headers = {
'User-Agent': 'awesome-components-mcp/1.0.0'
};
if (config_js_1.config.auth.gitlabToken) {
headers['Authorization'] = `Bearer ${config_js_1.config.auth.gitlabToken}`;
console.log('Using GitLab Personal Access Token for authentication');
}
else {
console.log('No GitLab token provided, attempting public access');
}
return headers;
}
/**
* Fetches text content from a GitLab URL with authentication support
*
* @param url - The GitLab URL to fetch content from
* @returns Promise<string> - The fetched content as a string
* @throws {GitLabError} - When the request fails or returns an error status
* @throws {GitLabAuthError} - When authentication fails
*/
async function fetchGitLabFileContent(url) {
try {
console.log(`Fetching content from GitLab URL: ${url}`);
const headers = createAuthHeaders();
const response = await fetch(url, { headers });
if (!response.ok) {
// Log detailed error for server-side diagnostics (without exposing sensitive info)
console.error(`GitLab request failed: ${response.status} ${response.statusText}`);
// Create specific error messages based on status codes
let errorMessage;
switch (response.status) {
case 401:
errorMessage = `Authentication failed (status: 401). Please check your GitLab Personal Access Token.`;
throw new GitLabAuthError(errorMessage, url);
case 403:
if (config_js_1.config.auth.gitlabToken) {
errorMessage = `Access forbidden (status: 403). Your token may not have sufficient permissions for this repository.`;
}
else {
errorMessage = `Access forbidden (status: 403). This repository may be private and require authentication. Please provide a GitLab Personal Access Token.`;
}
break;
case 404:
errorMessage = `File not found (status: 404). Please check if the file exists and the path is correct.`;
break;
case 500:
case 502:
case 503:
case 504:
errorMessage = `GitLab server error (status: ${response.status}). Please try again later.`;
break;
default:
errorMessage = `Failed to fetch content from GitLab (status: ${response.status}).`;
}
// Throw an error that can be caught by the tool and handled by MCP SDK
throw new GitLabError(errorMessage, response.status, url);
}
const content = await response.text();
console.log(`Successfully fetched ${content.length} characters from GitLab`);
return content;
}
catch (error) {
// If it's already a GitLabError or GitLabAuthError, re-throw it
if (error instanceof GitLabError) {
throw error;
}
// Handle network errors and other fetch-related errors
console.error(`Network or other error fetching from GitLab:`, error);
// Create a more user-friendly error message for network issues
const networkError = new GitLabError(`Network error while fetching from GitLab: ${error instanceof Error ? error.message : 'Unknown error'}`, undefined, url);
throw networkError;
}
}
/**
* Validates if a URL appears to be a valid GitLab URL
*
* @param url - The URL to validate
* @returns boolean - True if the URL appears to be a valid GitLab URL
*/
function isValidGitLabUrl(url) {
try {
const urlObj = new URL(url);
// Check if it's a valid HTTP/HTTPS URL
if (!['http:', 'https:'].includes(urlObj.protocol)) {
return false;
}
// Check if it contains GitLab-like patterns
const hostname = urlObj.hostname.toLowerCase();
const pathname = urlObj.pathname.toLowerCase();
// Common GitLab patterns - must contain 'gitlab' in hostname
const isGitLabHostname = hostname.includes('gitlab');
const hasRawPath = pathname.includes('/-/raw/');
return isGitLabHostname && hasRawPath;
}
catch {
return false;
}
}
/**
* Validates authentication configuration for private repositories
*
* @param url - The GitLab URL to check
* @returns boolean - True if authentication is properly configured or not needed
*/
function validateAuthenticationConfig(url) {
// For public repositories, authentication is optional
// For private repositories, authentication is required
if (!config_js_1.config.auth.gitlabToken) {
return {
isValid: true,
message: 'No authentication token provided. This will work for public repositories only.'
};
}
// Basic token validation
const token = config_js_1.config.auth.gitlabToken.trim();
if (token.length < 10) {
return {
isValid: false,
message: 'GitLab Personal Access Token appears to be invalid (too short).'
};
}
return {
isValid: true,
message: 'Authentication token is configured.'
};
}
/**
* Fetches content with URL validation and authentication support
*
* @param url - The GitLab URL to fetch content from
* @returns Promise<string> - The fetched content as a string
* @throws {GitLabError} - When the URL is invalid or the request fails
* @throws {GitLabAuthError} - When authentication fails
*/
async function fetchGitLabFileContentSafe(url) {
if (!isValidGitLabUrl(url)) {
throw new GitLabError(`Invalid GitLab URL format. Expected a GitLab raw file URL.`, undefined, url);
}
// Validate authentication configuration
const authValidation = validateAuthenticationConfig(url);
if (!authValidation.isValid) {
throw new GitLabAuthError(authValidation.message || 'Authentication configuration is invalid', url);
}
return fetchGitLabFileContent(url);
}