Skip to main content
Glama
IMPLEMENTATION_WORKFLOW.md28.6 kB
# F5 Status MCP Server - Implementation Workflow ## Overview This document provides a comprehensive, step-by-step implementation plan for building the F5 Status MCP Server based on the architecture design. **Timeline**: 5 weeks **Strategy**: Phased implementation with progressive feature delivery **Approach**: Test-driven development with continuous integration ## Prerequisites ### Required Tools - Node.js 18+ and npm/yarn - TypeScript 5+ - Git for version control - Claude Desktop for MCP testing ### Required Knowledge - TypeScript/JavaScript - MCP Protocol fundamentals - REST APIs and web scraping - Testing frameworks (Jest) ### Environment Setup Checklist - [ ] Node.js and npm installed - [ ] TypeScript compiler installed globally - [ ] Git repository initialized - [ ] IDE/editor configured (VS Code recommended) - [ ] Claude Desktop installed for testing --- ## Phase 1: Project Foundation (Days 1-3) ### Objective Set up project infrastructure and core dependencies ### Tasks #### 1.1 Initialize Project Structure **Priority**: Critical **Estimated Time**: 2 hours ```bash # Create project directory mkdir f5cloudstatus-mcp cd f5cloudstatus-mcp # Initialize npm project npm init -y # Initialize TypeScript npx tsc --init # Initialize Git git init echo "node_modules/" > .gitignore echo "dist/" >> .gitignore echo ".env" >> .gitignore ``` **Deliverable**: Basic project skeleton with package.json and tsconfig.json --- #### 1.2 Install Core Dependencies **Priority**: Critical **Estimated Time**: 1 hour ```bash # MCP SDK npm install @modelcontextprotocol/sdk # HTTP client npm install axios # Web scraping (fallback) npm install playwright # Utilities npm install dotenv # Development dependencies npm install -D typescript @types/node npm install -D jest @types/jest ts-jest npm install -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin npm install -D prettier ``` **Deliverable**: Complete package.json with all dependencies --- #### 1.3 Configure TypeScript **Priority**: Critical **Estimated Time**: 30 minutes Update `tsconfig.json`: ```json { "compilerOptions": { "target": "ES2020", "module": "commonjs", "lib": ["ES2020"], "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "declaration": true, "declarationMap": true, "sourceMap": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist", "**/*.test.ts"] } ``` **Deliverable**: Configured TypeScript compiler --- #### 1.4 Create Directory Structure **Priority**: Critical **Estimated Time**: 30 minutes ```bash mkdir -p src/{server,services,data-access,cache,tools,types,utils} mkdir -p tests/{unit,integration,e2e,fixtures} mkdir -p docs ``` **Deliverable**: Complete directory structure matching architecture --- #### 1.5 Setup Testing Framework **Priority**: Critical **Estimated Time**: 1 hour Create `jest.config.js`: ```javascript module.exports = { preset: 'ts-jest', testEnvironment: 'node', roots: ['<rootDir>/tests'], testMatch: ['**/*.test.ts'], collectCoverageFrom: [ 'src/**/*.ts', '!src/**/*.d.ts', '!src/types/**' ], coverageThreshold: { global: { branches: 70, functions: 70, lines: 70, statements: 70 } } }; ``` Add test scripts to `package.json`: ```json { "scripts": { "test": "jest", "test:watch": "jest --watch", "test:coverage": "jest --coverage", "build": "tsc", "dev": "tsc --watch", "lint": "eslint src/**/*.ts", "format": "prettier --write src/**/*.ts" } } ``` **Deliverable**: Working test infrastructure --- #### 1.6 Create Type Definitions **Priority**: High **Estimated Time**: 2 hours **File**: `src/types/domain.ts` ```typescript export type StatusLevel = "operational" | "degraded" | "partial_outage" | "major_outage"; export type IndicatorLevel = "none" | "minor" | "major" | "critical"; export interface OverallStatus { status: StatusLevel; indicator: IndicatorLevel; description: string; updated_at: string; } export interface Component { id: string; name: string; status: string; group: string; uptime?: string; description?: string; updated_at: string; } export interface Incident { id: string; name: string; status: string; impact: string; created_at: string; updated_at: string; shortlink: string; updates: IncidentUpdate[]; } export interface IncidentUpdate { status: string; body: string; created_at: string; } export interface Maintenance { id: string; name: string; scheduled_for: string; scheduled_until: string; status: string; impact: string; components: string[]; updates: IncidentUpdate[]; } export interface UptimeData { percentage: number; startDate: string; endDate: string; dailyStats: DailyUptimeStat[]; } export interface DailyUptimeStat { date: string; uptime: number; incidents: number; } ``` **File**: `src/types/api.ts` ```typescript // Raw API response types export interface RawStatusResponse { page: { id: string; name: string; url: string; updated_at: string; }; status: { indicator: string; description: string; }; } export interface RawComponent { id: string; name: string; status: string; created_at: string; updated_at: string; position: number; description: string | null; showcase: boolean; group_id: string | null; page_id: string; group: boolean; only_show_if_degraded: boolean; } export interface RawIncident { id: string; name: string; status: string; created_at: string; updated_at: string; monitoring_at: string | null; resolved_at: string | null; impact: string; shortlink: string; started_at: string; page_id: string; incident_updates: RawIncidentUpdate[]; } export interface RawIncidentUpdate { id: string; status: string; body: string; incident_id: string; created_at: string; updated_at: string; display_at: string; affected_components: any[]; } ``` **Deliverable**: Complete type definitions for domain and API models --- #### 1.7 Configuration Management **Priority**: High **Estimated Time**: 1 hour **File**: `src/utils/config.ts` ```typescript export interface Config { server: { name: string; version: string; }; api: { baseUrl: string; timeout: number; retryAttempts: number; retryDelay: number; }; scraper: { baseUrl: string; timeout: number; headless: boolean; }; cache: { ttl: { status: number; components: number; incidents: number; maintenance: number; }; }; logging: { level: 'debug' | 'info' | 'warn' | 'error'; }; } export const defaultConfig: Config = { server: { name: "f5-cloud-status", version: "1.0.0" }, api: { baseUrl: process.env.API_BASE_URL || "https://www.f5cloudstatus.com/api/v2", timeout: parseInt(process.env.API_TIMEOUT || "10000"), retryAttempts: parseInt(process.env.API_RETRY_ATTEMPTS || "3"), retryDelay: parseInt(process.env.API_RETRY_DELAY || "1000") }, scraper: { baseUrl: process.env.SCRAPER_BASE_URL || "https://www.f5cloudstatus.com", timeout: parseInt(process.env.SCRAPER_TIMEOUT || "30000"), headless: process.env.SCRAPER_HEADLESS !== "false" }, cache: { ttl: { status: parseInt(process.env.CACHE_TTL_STATUS || "30000"), components: parseInt(process.env.CACHE_TTL_COMPONENTS || "60000"), incidents: parseInt(process.env.CACHE_TTL_INCIDENTS || "120000"), maintenance: parseInt(process.env.CACHE_TTL_MAINTENANCE || "300000") } }, logging: { level: (process.env.LOG_LEVEL as any) || 'info' } }; export function loadConfig(): Config { return defaultConfig; } ``` Create `.env.example`: ```bash # API Configuration API_BASE_URL=https://www.f5cloudstatus.com/api/v2 API_TIMEOUT=10000 API_RETRY_ATTEMPTS=3 API_RETRY_DELAY=1000 # Scraper Configuration SCRAPER_BASE_URL=https://www.f5cloudstatus.com SCRAPER_TIMEOUT=30000 SCRAPER_HEADLESS=true # Cache TTL (milliseconds) CACHE_TTL_STATUS=30000 CACHE_TTL_COMPONENTS=60000 CACHE_TTL_INCIDENTS=120000 CACHE_TTL_MAINTENANCE=300000 # Logging LOG_LEVEL=info ``` **Deliverable**: Configuration system with environment variable support --- #### 1.8 Error Handling Foundation **Priority**: High **Estimated Time**: 1 hour **File**: `src/utils/errors.ts` ```typescript export class F5StatusError extends Error { constructor( message: string, public code: string, public statusCode?: number ) { super(message); this.name = 'F5StatusError'; Error.captureStackTrace(this, this.constructor); } } export class APIError extends F5StatusError { constructor(message: string, statusCode: number) { super(message, 'API_ERROR', statusCode); } } export class ScraperError extends F5StatusError { constructor(message: string) { super(message, 'SCRAPER_ERROR'); } } export class CacheError extends F5StatusError { constructor(message: string) { super(message, 'CACHE_ERROR'); } } export class ValidationError extends F5StatusError { constructor(message: string) { super(message, 'VALIDATION_ERROR'); } } export class NotFoundError extends F5StatusError { constructor(resource: string) { super(`${resource} not found`, 'NOT_FOUND', 404); } } ``` **Deliverable**: Custom error classes for error handling --- #### 1.9 Logging Utility **Priority**: Medium **Estimated Time**: 1 hour **File**: `src/utils/logger.ts` ```typescript export enum LogLevel { DEBUG = 0, INFO = 1, WARN = 2, ERROR = 3 } export class Logger { private level: LogLevel; constructor(level: LogLevel = LogLevel.INFO) { this.level = level; } setLevel(level: LogLevel): void { this.level = level; } debug(message: string, meta?: any): void { if (this.level <= LogLevel.DEBUG) { console.log(`[DEBUG] ${new Date().toISOString()} - ${message}`, meta || ''); } } info(message: string, meta?: any): void { if (this.level <= LogLevel.INFO) { console.log(`[INFO] ${new Date().toISOString()} - ${message}`, meta || ''); } } warn(message: string, meta?: any): void { if (this.level <= LogLevel.WARN) { console.warn(`[WARN] ${new Date().toISOString()} - ${message}`, meta || ''); } } error(message: string, error?: Error, meta?: any): void { if (this.level <= LogLevel.ERROR) { console.error(`[ERROR] ${new Date().toISOString()} - ${message}`, { error: error?.message, stack: error?.stack, ...meta }); } } } // Global logger instance export const logger = new Logger(); ``` **Deliverable**: Logging utility with level support --- ### Phase 1 Validation **Checklist**: - [ ] Project initialized with all dependencies - [ ] TypeScript compiles without errors - [ ] Test framework runs successfully - [ ] Directory structure matches architecture - [ ] All type definitions created - [ ] Configuration system functional - [ ] Error classes defined - [ ] Logger utility working **Command to validate**: ```bash npm run build npm test npm run lint ``` --- ## Phase 2: Data Access Layer (Days 4-7) ### Objective Implement API client and web scraper with caching ### Tasks #### 2.1 Implement Cache Service **Priority**: Critical **Estimated Time**: 3 hours **File**: `src/cache/cache-service.ts` ```typescript import { CacheError } from '../utils/errors'; import { logger } from '../utils/logger'; export interface CacheEntry<T> { data: T; expiry: number; } export class CacheService { private cache = new Map<string, CacheEntry<any>>(); async get<T>( key: string, ttl: number, fetcher: () => Promise<T> ): Promise<T> { try { const cached = this.cache.get(key); if (cached && cached.expiry > Date.now()) { logger.debug(`Cache hit for key: ${key}`); return cached.data; } logger.debug(`Cache miss for key: ${key}`); const data = await fetcher(); this.cache.set(key, { data, expiry: Date.now() + ttl }); return data; } catch (error) { throw new CacheError(`Failed to get cached data for ${key}: ${error.message}`); } } invalidate(key: string): void { this.cache.delete(key); logger.debug(`Cache invalidated for key: ${key}`); } clear(): void { this.cache.clear(); logger.info('Cache cleared'); } getStats(): { size: number; keys: string[] } { return { size: this.cache.size, keys: Array.from(this.cache.keys()) }; } } ``` **Test**: `tests/unit/cache/cache-service.test.ts` ```typescript import { CacheService } from '../../../src/cache/cache-service'; describe('CacheService', () => { let cache: CacheService; beforeEach(() => { cache = new CacheService(); }); test('returns cached data on cache hit', async () => { const fetcher = jest.fn().mockResolvedValue({ data: 'test' }); const result1 = await cache.get('test-key', 1000, fetcher); const result2 = await cache.get('test-key', 1000, fetcher); expect(result1).toEqual({ data: 'test' }); expect(result2).toEqual({ data: 'test' }); expect(fetcher).toHaveBeenCalledTimes(1); }); test('fetches fresh data on cache miss', async () => { const fetcher = jest.fn().mockResolvedValue({ data: 'fresh' }); const result = await cache.get('new-key', 1000, fetcher); expect(result).toEqual({ data: 'fresh' }); expect(fetcher).toHaveBeenCalledTimes(1); }); test('invalidates cache entry', async () => { const fetcher = jest.fn().mockResolvedValue({ data: 'test' }); await cache.get('test-key', 1000, fetcher); cache.invalidate('test-key'); await cache.get('test-key', 1000, fetcher); expect(fetcher).toHaveBeenCalledTimes(2); }); }); ``` **Deliverable**: Functional cache service with tests --- #### 2.2 Implement API Client **Priority**: Critical **Estimated Time**: 4 hours **File**: `src/data-access/api-client.ts` ```typescript import axios, { AxiosInstance } from 'axios'; import { APIError } from '../utils/errors'; import { logger } from '../utils/logger'; import { Config } from '../utils/config'; import { RawStatusResponse, RawComponent, RawIncident } from '../types/api'; export class APIClient { private client: AxiosInstance; private config: Config['api']; constructor(config: Config['api']) { this.config = config; this.client = axios.create({ baseURL: config.baseUrl, timeout: config.timeout, headers: { 'Accept': 'application/json', 'User-Agent': 'F5StatusMCP/1.0' } }); } async fetchStatus(): Promise<RawStatusResponse> { return this.request<RawStatusResponse>('/status.json'); } async fetchComponents(): Promise<RawComponent[]> { const response = await this.request<{ components: RawComponent[] }>('/components.json'); return response.components; } async fetchIncidents(): Promise<RawIncident[]> { const response = await this.request<{ incidents: RawIncident[] }>('/incidents.json'); return response.incidents; } async fetchScheduledMaintenances(): Promise<any[]> { const response = await this.request<{ scheduled_maintenances: any[] }>( '/scheduled-maintenances.json' ); return response.scheduled_maintenances; } async fetchSummary(): Promise<any> { return this.request<any>('/summary.json'); } private async request<T>(endpoint: string): Promise<T> { return this.retry( async () => { try { logger.debug(`API request: ${endpoint}`); const response = await this.client.get<T>(endpoint); logger.debug(`API response received for: ${endpoint}`); return response.data; } catch (error) { if (axios.isAxiosError(error)) { const statusCode = error.response?.status || 500; throw new APIError( `API request failed: ${error.message}`, statusCode ); } throw error; } }, this.config.retryAttempts ); } private async retry<T>( fn: () => Promise<T>, attempts: number ): Promise<T> { let lastError: Error; for (let i = 0; i < attempts; i++) { try { return await fn(); } catch (error) { lastError = error; if (i < attempts - 1) { const delay = this.config.retryDelay * Math.pow(2, i); logger.warn(`Request failed, retrying in ${delay}ms...`, { attempt: i + 1, maxAttempts: attempts }); await new Promise(resolve => setTimeout(resolve, delay)); } } } throw lastError!; } } ``` **Test**: `tests/unit/data-access/api-client.test.ts` ```typescript import axios from 'axios'; import { APIClient } from '../../../src/data-access/api-client'; import { APIError } from '../../../src/utils/errors'; jest.mock('axios'); const mockedAxios = axios as jest.Mocked<typeof axios>; describe('APIClient', () => { let client: APIClient; beforeEach(() => { const mockCreate = jest.fn().mockReturnValue({ get: jest.fn() }); mockedAxios.create = mockCreate; client = new APIClient({ baseUrl: 'https://example.com/api', timeout: 5000, retryAttempts: 2, retryDelay: 100 }); }); test('fetchStatus returns status data', async () => { const mockData = { status: { indicator: 'none', description: 'All Systems Operational' } }; (client as any).client.get = jest.fn().mockResolvedValue({ data: mockData }); const result = await client.fetchStatus(); expect(result).toEqual(mockData); }); test('retries on failure', async () => { const mockGet = jest.fn() .mockRejectedValueOnce(new Error('Network error')) .mockResolvedValueOnce({ data: { status: 'ok' } }); (client as any).client.get = mockGet; const result = await client.fetchStatus(); expect(mockGet).toHaveBeenCalledTimes(2); expect(result).toEqual({ status: 'ok' }); }); }); ``` **Deliverable**: API client with retry logic and tests --- #### 2.3 Implement Web Scraper (Fallback) **Priority**: High **Estimated Time**: 6 hours **File**: `src/data-access/web-scraper.ts` ```typescript import { chromium, Browser, Page } from 'playwright'; import { ScraperError } from '../utils/errors'; import { logger } from '../utils/logger'; import { Config } from '../utils/config'; import { RawStatusResponse, RawComponent } from '../types/api'; export class WebScraper { private config: Config['scraper']; private browser: Browser | null = null; constructor(config: Config['scraper']) { this.config = config; } async scrapeStatus(): Promise<RawStatusResponse> { const page = await this.getPage(); try { await page.goto(this.config.baseUrl, { waitUntil: 'networkidle', timeout: this.config.timeout }); const data = await page.evaluate(() => { const statusEl = document.querySelector('h2'); const status = statusEl?.textContent?.trim() || ''; return { page: { id: 'scraped', name: 'F5 Status', url: window.location.href, updated_at: new Date().toISOString() }, status: { indicator: status.includes('Operational') ? 'none' : 'major', description: status } }; }); logger.debug('Scraped status data'); return data; } catch (error) { throw new ScraperError(`Failed to scrape status: ${error.message}`); } finally { await page.close(); } } async scrapeComponents(): Promise<RawComponent[]> { const page = await this.getPage(); try { await page.goto(this.config.baseUrl, { waitUntil: 'networkidle', timeout: this.config.timeout }); const components = await page.evaluate(() => { const containers = document.querySelectorAll('[data-component-id]'); const results: any[] = []; containers.forEach(comp => { const id = comp.getAttribute('data-component-id') || ''; const nameEl = comp.querySelector('.name'); const statusEl = comp.querySelector('.component-status'); results.push({ id, name: nameEl?.textContent?.trim() || '', status: statusEl?.textContent?.trim() || '', created_at: new Date().toISOString(), updated_at: new Date().toISOString(), position: 0, description: null, showcase: false, group_id: null, page_id: 'scraped', group: false, only_show_if_degraded: false }); }); return results; }); logger.debug(`Scraped ${components.length} components`); return components; } catch (error) { throw new ScraperError(`Failed to scrape components: ${error.message}`); } finally { await page.close(); } } private async getPage(): Promise<Page> { if (!this.browser) { this.browser = await chromium.launch({ headless: this.config.headless }); } return this.browser.newPage(); } async close(): Promise<void> { if (this.browser) { await this.browser.close(); this.browser = null; } } } ``` **Deliverable**: Web scraper with Playwright --- #### 2.4 Data Access Facade **Priority**: Critical **Estimated Time**: 2 hours **File**: `src/data-access/index.ts` ```typescript import { APIClient } from './api-client'; import { WebScraper } from './web-scraper'; import { logger } from '../utils/logger'; import { RawStatusResponse, RawComponent, RawIncident } from '../types/api'; export class DataAccessLayer { constructor( private apiClient: APIClient, private webScraper: WebScraper, private preferAPI: boolean = true ) {} async fetchStatus(): Promise<RawStatusResponse> { if (this.preferAPI) { try { return await this.apiClient.fetchStatus(); } catch (error) { logger.warn('API failed, falling back to scraper for status'); } } return await this.webScraper.scrapeStatus(); } async fetchComponents(): Promise<RawComponent[]> { if (this.preferAPI) { try { return await this.apiClient.fetchComponents(); } catch (error) { logger.warn('API failed, falling back to scraper for components'); } } return await this.webScraper.scrapeComponents(); } async fetchIncidents(): Promise<RawIncident[]> { return await this.apiClient.fetchIncidents(); } async fetchScheduledMaintenances(): Promise<any[]> { return await this.apiClient.fetchScheduledMaintenances(); } async close(): Promise<void> { await this.webScraper.close(); } } ``` **Deliverable**: Data access facade with fallback logic --- ### Phase 2 Validation **Checklist**: - [ ] Cache service implemented and tested - [ ] API client implemented with retry logic - [ ] Web scraper implemented with Playwright - [ ] Data access facade provides fallback - [ ] All unit tests passing - [ ] Integration tests for API client working **Command to validate**: ```bash npm test -- tests/unit/cache npm test -- tests/unit/data-access npm run build ``` --- ## Phase 3: Business Logic (Days 8-12) ### Objective Implement core services (Status, Component, Incident) ### Tasks #### 3.1 Status Service **Priority**: Critical **Estimated Time**: 3 hours **File**: `src/services/status-service.ts` ```typescript import { DataAccessLayer } from '../data-access'; import { CacheService } from '../cache/cache-service'; import { OverallStatus, StatusLevel, IndicatorLevel } from '../types/domain'; import { Config } from '../utils/config'; export class StatusService { constructor( private dataAccess: DataAccessLayer, private cache: CacheService, private config: Config ) {} async getOverallStatus(): Promise<OverallStatus> { return this.cache.get( 'overall-status', this.config.cache.ttl.status, async () => { const rawStatus = await this.dataAccess.fetchStatus(); return { status: this.mapStatus(rawStatus.status.indicator), indicator: this.mapIndicator(rawStatus.status.indicator), description: rawStatus.status.description, updated_at: rawStatus.page.updated_at }; } ); } private mapStatus(indicator: string): StatusLevel { switch (indicator.toLowerCase()) { case 'none': return 'operational'; case 'minor': return 'degraded'; case 'major': return 'partial_outage'; case 'critical': return 'major_outage'; default: return 'operational'; } } private mapIndicator(indicator: string): IndicatorLevel { const normalized = indicator.toLowerCase(); if (['none', 'minor', 'major', 'critical'].includes(normalized)) { return normalized as IndicatorLevel; } return 'none'; } } ``` **Test**: `tests/unit/services/status-service.test.ts` **Deliverable**: Status service with caching --- #### 3.2 Component Service **Priority**: Critical **Estimated Time**: 5 hours **File**: `src/services/component-service.ts` **Deliverable**: Component service with filtering and uptime --- #### 3.3 Incident Service **Priority**: Critical **Estimated Time**: 5 hours **File**: `src/services/incident-service.ts` **Deliverable**: Incident service with maintenance support --- ### Phase 3 Validation **Checklist**: - [ ] All three services implemented - [ ] Services properly use caching - [ ] Data transformation logic correct - [ ] Unit tests for all services passing - [ ] Integration tests working --- ## Phase 4: MCP Integration (Days 13-17) ### Objective Implement MCP server and tool handlers ### Tasks #### 4.1 Tool Definitions **Priority**: Critical **Estimated Time**: 2 hours **File**: `src/tools/index.ts` **Deliverable**: All 6 tool definitions --- #### 4.2 Tool Handler **Priority**: Critical **Estimated Time**: 4 hours **File**: `src/tools/handler.ts` **Deliverable**: Tool execution logic --- #### 4.3 MCP Server **Priority**: Critical **Estimated Time**: 6 hours **File**: `src/server/index.ts` **Deliverable**: Complete MCP server --- ### Phase 4 Validation **Checklist**: - [ ] MCP server starts successfully - [ ] All tools registered - [ ] Tool handler processes requests - [ ] Responses formatted correctly - [ ] E2E tests passing --- ## Phase 5: Testing & Documentation (Days 18-25) ### Objective Complete test coverage and documentation ### Tasks #### 5.1 Integration Tests **Priority**: High **Estimated Time**: 8 hours **Deliverable**: Complete integration test suite --- #### 5.2 E2E Tests **Priority**: High **Estimated Time**: 6 hours **Deliverable**: E2E tests with real MCP protocol --- #### 5.3 Documentation **Priority**: Medium **Estimated Time**: 6 hours **Files**: - README.md - API.md - CONTRIBUTING.md - CHANGELOG.md **Deliverable**: Complete documentation --- #### 5.4 Performance Testing **Priority**: Medium **Estimated Time**: 4 hours **Deliverable**: Performance benchmarks --- ## Deployment Checklist - [ ] Build process works (`npm run build`) - [ ] All tests pass (`npm test`) - [ ] Linting passes (`npm run lint`) - [ ] Code formatted (`npm run format`) - [ ] Documentation complete - [ ] package.json metadata updated - [ ] README with installation instructions - [ ] Claude Desktop configuration documented - [ ] Example usage provided - [ ] Error scenarios tested --- ## Success Criteria ### Functional Requirements ✅ All 6 MCP tools implemented and working ✅ API client with retry logic ✅ Web scraper fallback functional ✅ Caching reduces API calls ✅ Error handling comprehensive ### Quality Requirements ✅ Test coverage > 70% ✅ No TypeScript errors ✅ Linting passes ✅ Documentation complete ### Performance Requirements ✅ Overall status < 1s response time ✅ Component list < 2s response time ✅ Cache hit rate > 80% --- ## Post-Implementation ### Maintenance Tasks - Monitor error logs - Update dependencies - Respond to API changes - Add new features as requested ### Future Enhancements - Historical data storage - Webhook notifications - Dashboard visualization - Multi-region support

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/robinmordasiewicz/f5cloudstatus-mcp'

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