Skip to main content
Glama
naoto24kawa

Composer Package README MCP Server

by naoto24kawa
validators.ts3.81 kB
import { PackageReadmeMcpError } from '../types/index.js'; import { VALIDATION_CONSTANTS, PACKAGE_TYPES } from './constants.js'; export function validatePackageName(packageName: string): void { if (!packageName || typeof packageName !== 'string') { throw new PackageReadmeMcpError('Package name is required and must be a string', 'INVALID_PACKAGE_NAME'); } const trimmed = packageName.trim(); if (!trimmed) { throw new PackageReadmeMcpError('Package name cannot be empty', 'INVALID_PACKAGE_NAME'); } // Composer packages must follow vendor/package format (e.g., monolog/monolog) // This check catches common user errors like entering just the package name if (!trimmed.includes('/')) { throw new PackageReadmeMcpError( 'Package name must be in vendor/package format (e.g., "monolog/monolog")', 'INVALID_PACKAGE_NAME' ); } // Check for invalid characters including @, #, spaces if (/[^a-z0-9_.\/-]/i.test(trimmed)) { throw new PackageReadmeMcpError( 'Package name contains invalid characters. Only letters, numbers, dots, hyphens, underscores, and one slash are allowed', 'INVALID_PACKAGE_NAME' ); } const parts = trimmed.split('/'); if (parts.length !== 2) { throw new PackageReadmeMcpError( 'Package name must contain exactly one slash in vendor/package format', 'INVALID_PACKAGE_NAME' ); } const [vendor, package_name] = parts; if (!vendor || !package_name) { throw new PackageReadmeMcpError( 'Both vendor and package name must be non-empty', 'INVALID_PACKAGE_NAME' ); } } export function validateVersion(version: string): void { if (!version || typeof version !== 'string') { throw new PackageReadmeMcpError('Version must be a string', 'INVALID_VERSION'); } const trimmed = version.trim(); if (trimmed.length === 0) { throw new PackageReadmeMcpError('Version cannot be empty', 'INVALID_VERSION'); } // Allow common version patterns for Composer if (trimmed === 'latest' || trimmed === 'dev-master' || trimmed === 'dev-main') { return; } // Allow dev branches if (trimmed.startsWith('dev-')) { return; } // Validate semantic version or version constraint - more flexible for Composer const versionRegex = /^[v]?[\d\w\-\.\^~>=<*]+/; if (!versionRegex.test(trimmed)) { throw new PackageReadmeMcpError( 'Version must be a valid semantic version (e.g., 1.0.0), version constraint (e.g., ^1.0), or a valid branch reference', 'INVALID_VERSION' ); } } export function validateSearchQuery(query: string): void { if (!query || typeof query !== 'string') { throw new PackageReadmeMcpError('Search query is required and must be a string', 'INVALID_SEARCH_QUERY'); } const trimmed = query.trim(); if (trimmed.length === 0) { throw new PackageReadmeMcpError('Search query cannot be empty', 'INVALID_SEARCH_QUERY'); } if (trimmed.length > VALIDATION_CONSTANTS.MAX_SEARCH_QUERY_LENGTH) { throw new PackageReadmeMcpError(`Search query cannot exceed ${VALIDATION_CONSTANTS.MAX_SEARCH_QUERY_LENGTH} characters`, 'INVALID_SEARCH_QUERY'); } } export function validateLimit(limit: number): void { if (!Number.isInteger(limit) || limit < VALIDATION_CONSTANTS.MIN_SEARCH_LIMIT || limit > VALIDATION_CONSTANTS.MAX_SEARCH_LIMIT) { throw new PackageReadmeMcpError(`Limit must be an integer between ${VALIDATION_CONSTANTS.MIN_SEARCH_LIMIT} and ${VALIDATION_CONSTANTS.MAX_SEARCH_LIMIT}`, 'INVALID_LIMIT'); } } export function validatePackageType(type: string): void { if (!PACKAGE_TYPES.includes(type as typeof PACKAGE_TYPES[number])) { throw new PackageReadmeMcpError( `Package type must be one of: ${PACKAGE_TYPES.join(', ')}`, 'INVALID_PACKAGE_TYPE' ); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/naoto24kawa/composer-package-readme-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server