Skip to main content
Glama
index.ts5.89 kB
/** * Main module for TeamCity Swagger spec management */ import { getConfig } from '@/config'; import { info, error as logError, warn } from '@/utils/logger'; import { SwaggerCache } from './swagger-cache'; import { SwaggerFetcher, type SwaggerSpec } from './swagger-fetcher'; import { SwaggerValidator } from './swagger-validator'; export * from './swagger-fetcher'; export * from './swagger-validator'; export * from './swagger-cache'; export interface SwaggerManagerConfig { baseUrl?: string; token?: string; cacheDir?: string; cacheTTL?: number; forceRefresh?: boolean; } export class SwaggerManager { private fetcher!: SwaggerFetcher; private validator: SwaggerValidator; private cache!: SwaggerCache; private config: SwaggerManagerConfig; private initialized: Promise<void>; constructor(config: SwaggerManagerConfig = {}) { this.config = config; this.validator = new SwaggerValidator(); // Initialize synchronously this.initialize(); this.initialized = Promise.resolve(); } private initialize(): void { // Get config from environment if not provided const envConfig = getConfig(); this.config = { baseUrl: this.config.baseUrl ?? envConfig.teamcity?.url ?? '', token: this.config.token ?? envConfig.teamcity?.token ?? '', cacheDir: this.config.cacheDir ?? '.cache', cacheTTL: this.config.cacheTTL ?? 24 * 60 * 60 * 1000, // 24 hours forceRefresh: this.config.forceRefresh ?? false, }; if (this.config.baseUrl === '' || this.config.token === '') { throw new Error('TeamCity base URL and token are required'); } const baseUrl = this.config.baseUrl ?? ''; const token = this.config.token ?? ''; this.fetcher = new SwaggerFetcher({ baseUrl, token, }); this.cache = new SwaggerCache({ cacheDir: this.config.cacheDir ?? '.cache', ttl: this.config.cacheTTL ?? 24 * 60 * 60 * 1000, }); info('SwaggerManager initialized'); } /** * Get TeamCity Swagger specification (with caching) */ async getSpec(): Promise<SwaggerSpec> { await this.initialized; const cacheKey = this.getCacheKey(); // Check cache first unless force refresh is requested if (this.config.forceRefresh !== true) { const cached = await this.cache.get(cacheKey); if (cached) { info('Using cached Swagger specification'); return cached; } } // Fetch fresh spec info('Fetching fresh Swagger specification from TeamCity'); const spec = await this.fetcher.fetchSpec(); // Validate the spec const validation = this.validator.validateSpec(spec); if (!validation.isValid) { throw new Error(`Invalid Swagger specification: ${validation.errors?.join(', ')}`); } if (validation.warnings && validation.warnings.length > 0) { warn('Swagger specification has warnings', { warnings: validation.warnings }); } // Check TeamCity version compatibility if (validation.teamCityVersion !== undefined && validation.teamCityVersion !== '') { const isSupported = this.validator.isVersionSupported(validation.teamCityVersion); if (!isSupported) { warn('TeamCity version may not be fully supported', { version: validation.teamCityVersion, minSupported: '2020.1', }); } else { info('TeamCity version is supported', { version: validation.teamCityVersion, }); } } // Cache the validated spec try { await this.cache.set(cacheKey, spec); } catch (err) { // Caching failure is not critical warn('Failed to cache Swagger spec', { error: err }); } return spec; } /** * Validate a Swagger specification */ async validateSpec(spec?: SwaggerSpec): Promise<{ isValid: boolean; version?: string; teamCityVersion?: string; errors?: string[]; warnings?: string[]; }> { const specToValidate = spec ?? (await this.getSpec()); return this.validator.validateSpec(specToValidate); } /** * Get TeamCity server information */ async getServerInfo(): Promise<{ version: string | null; connected: boolean; specVersion?: string; }> { await this.initialized; const connected = await this.fetcher.testConnection(); const version = await this.fetcher.getServerVersion(); let specVersion: string | undefined; try { const spec = await this.getSpec(); specVersion = spec.swagger ?? spec.openapi; } catch (err) { logError('Failed to get spec version', err instanceof Error ? err : new Error(String(err))); } const result: { version: string | null; connected: boolean; specVersion?: string; } = { version, connected, }; if (specVersion !== undefined) { result.specVersion = specVersion; } return result; } /** * Clear cache */ async clearCache(): Promise<void> { await this.cache.clear(); info('Swagger cache cleared'); } /** * Get cache statistics */ async getCacheStats(): Promise<{ size: number; files: number; oldestFile?: Date; newestFile?: Date; }> { return await this.cache.getStats(); } /** * Generate cache key based on TeamCity URL */ private getCacheKey(): string { // Create a cache key based on the TeamCity URL const baseUrl = this.config.baseUrl ?? ''; const url = new URL(baseUrl); return `teamcity-swagger-${url.hostname}`; } } /** * Singleton instance for convenience */ let managerInstance: SwaggerManager | null = null; /** * Get or create SwaggerManager instance */ export function getSwaggerManager(config?: SwaggerManagerConfig): SwaggerManager { if (!managerInstance || config) { managerInstance = new SwaggerManager(config); } return managerInstance; }

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/Daghis/teamcity-mcp'

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