Skip to main content
Glama
mock-server.ts•17.4 kB
#!/usr/bin/env node /** * Mock APIC Server for Testing * * This creates a mock APIC server that responds to common ACI API calls * with realistic test data. Use this for development and testing when * you don't have access to a real APIC controller. */ import * as http from 'http'; import * as url from 'url'; interface MockData { tenants: any[]; applicationProfiles: any[]; endpointGroups: any[]; bridgeDomains: any[]; vrfs: any[]; contracts: any[]; faults: any[]; nodes: any[]; } class MockAPICServer { private server: http.Server; private port: number; private sessions: Map<string, { token: string; expires: number }> = new Map(); private mockData!: MockData; constructor(port: number = 8443) { this.port = port; this.initializeMockData(); this.server = http.createServer(this.handleRequest.bind(this)); } private initializeMockData() { this.mockData = { tenants: [ { fvTenant: { attributes: { name: 'common', dn: 'uni/tn-common', descr: 'Common tenant' } } }, { fvTenant: { attributes: { name: 'production', dn: 'uni/tn-production', descr: 'Production tenant' } } }, { fvTenant: { attributes: { name: 'development', dn: 'uni/tn-development', descr: 'Development tenant' } } } ], applicationProfiles: [ { fvAp: { attributes: { name: 'web-app', dn: 'uni/tn-production/ap-web-app', descr: '3-tier web application' } } }, { fvAp: { attributes: { name: 'database-app', dn: 'uni/tn-production/ap-database-app', descr: 'Database application' } } } ], endpointGroups: [ { fvAEPg: { attributes: { name: 'web-epg', dn: 'uni/tn-production/ap-web-app/epg-web-epg', descr: 'Web tier EPG' } } }, { fvAEPg: { attributes: { name: 'app-epg', dn: 'uni/tn-production/ap-web-app/epg-app-epg', descr: 'Application tier EPG' } } }, { fvAEPg: { attributes: { name: 'db-epg', dn: 'uni/tn-production/ap-web-app/epg-db-epg', descr: 'Database tier EPG' } } } ], bridgeDomains: [ { fvBD: { attributes: { name: 'web-bd', dn: 'uni/tn-production/BD-web-bd', descr: 'Web bridge domain' } } }, { fvBD: { attributes: { name: 'app-bd', dn: 'uni/tn-production/BD-app-bd', descr: 'Application bridge domain' } } } ], vrfs: [ { fvCtx: { attributes: { name: 'prod-vrf', dn: 'uni/tn-production/ctx-prod-vrf', descr: 'Production VRF' } } }, { fvCtx: { attributes: { name: 'dev-vrf', dn: 'uni/tn-development/ctx-dev-vrf', descr: 'Development VRF' } } } ], contracts: [ { vzBrCP: { attributes: { name: 'web-to-app', dn: 'uni/tn-production/brc-web-to-app', descr: 'Web to App contract' } } }, { vzBrCP: { attributes: { name: 'app-to-db', dn: 'uni/tn-production/brc-app-to-db', descr: 'App to DB contract' } } } ], faults: [ { faultInst: { attributes: { severity: 'critical', descr: 'Power supply failure on node 101', dn: 'topology/pod-1/node-101/fault-F0467', code: 'F0467', created: '2024-11-21T10:30:00.000+00:00' } } }, { faultInst: { attributes: { severity: 'warning', descr: 'High CPU utilization on node 102', dn: 'topology/pod-1/node-102/fault-F1234', code: 'F1234', created: '2024-11-21T11:15:00.000+00:00' } } }, { faultInst: { attributes: { severity: 'info', descr: 'Interface flapped on node 103', dn: 'topology/pod-1/node-103/fault-F5678', code: 'F5678', created: '2024-11-21T12:00:00.000+00:00' } } } ], nodes: [ { fabricNode: { attributes: { name: 'leaf-101', dn: 'topology/pod-1/node-101', id: '101', role: 'leaf', model: 'N9K-C93180YC-EX', serial: 'FDO12345678' } } }, { fabricNode: { attributes: { name: 'leaf-102', dn: 'topology/pod-1/node-102', id: '102', role: 'leaf', model: 'N9K-C93180YC-EX', serial: 'FDO87654321' } } }, { fabricNode: { attributes: { name: 'spine-201', dn: 'topology/pod-1/node-201', id: '201', role: 'spine', model: 'N9K-C9336C-FX2', serial: 'FDO11111111' } } } ] }; } private handleRequest(req: http.IncomingMessage, res: http.ServerResponse) { const parsedUrl = url.parse(req.url || '', true); const path = parsedUrl.pathname || ''; const method = req.method || 'GET'; console.log(`Mock APIC: ${method} ${path}`); // Set common headers res.setHeader('Content-Type', 'application/json'); res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, Cookie'); if (method === 'OPTIONS') { res.writeHead(200); res.end(); return; } try { this.routeRequest(method, path, req, res); } catch (error) { console.error('Mock APIC Error:', error); res.writeHead(500); res.end(JSON.stringify({ error: 'Internal server error' })); } } private routeRequest(method: string, path: string, req: http.IncomingMessage, res: http.ServerResponse) { // Authentication endpoints if (path === '/api/aaaLogin.json' && method === 'POST') { this.handleLogin(req, res); return; } if (path === '/api/aaaRefresh.json' && method === 'GET') { this.handleCertAuth(req, res); return; } if (path === '/api/aaaLogout.json' && method === 'POST') { this.handleLogout(req, res); return; } // Check authentication for all other requests if (!this.isAuthenticated(req)) { res.writeHead(401); res.end(JSON.stringify({ imdata: [{ error: { attributes: { code: '401', text: 'Authentication required' } } }] })); return; } // API endpoints if (path === '/api/node/class/fvTenant.json') { this.sendResponse(res, { imdata: this.mockData.tenants }); return; } if (path === '/api/node/class/fvAp.json') { this.sendResponse(res, { imdata: this.mockData.applicationProfiles }); return; } if (path === '/api/node/class/fvAEPg.json') { this.sendResponse(res, { imdata: this.mockData.endpointGroups }); return; } if (path === '/api/node/class/fvBD.json') { this.sendResponse(res, { imdata: this.mockData.bridgeDomains }); return; } if (path === '/api/node/class/fvCtx.json') { this.sendResponse(res, { imdata: this.mockData.vrfs }); return; } if (path === '/api/node/class/vzBrCP.json') { this.sendResponse(res, { imdata: this.mockData.contracts }); return; } if (path === '/api/node/class/faultInst.json') { const query = url.parse(req.url || '', true).query; let faults = this.mockData.faults; // Handle severity filter if (query['query-target-filter']) { const filter = query['query-target-filter'] as string; if (filter.includes('severity')) { const severity = filter.match(/\"([^\"]+)\"/)?.[1]; if (severity) { faults = this.mockData.faults.filter(fault => fault.faultInst.attributes.severity === severity ); } } } this.sendResponse(res, { imdata: faults }); return; } if (path === '/api/node/class/fabricNode.json') { this.sendResponse(res, { imdata: this.mockData.nodes }); return; } if (path === '/api/node/class/fabricHealthTotal.json') { this.sendResponse(res, { imdata: [{ fabricHealthTotal: { attributes: { cur: '95', dn: 'topology/health', healthAvg: '94', healthMax: '99', healthMin: '90' } } }] }); return; } if (path === '/api/node/class/topSystem.json') { this.sendResponse(res, { imdata: [{ topSystem: { attributes: { name: 'Mock APIC', version: '5.2(7f)', dn: 'topology/pod-1/node-1/sys', fabricMAC: '00:22:BD:F8:19:FF' } } }] }); return; } // Handle POST/PUT/DELETE operations if (method === 'POST' && path.includes('/api/node/mo/')) { this.handleCreateOrUpdate(req, res); return; } if (method === 'DELETE' && path.includes('/api/node/mo/')) { this.handleDelete(req, res); return; } // Default 404 res.writeHead(404); res.end(JSON.stringify({ imdata: [{ error: { attributes: { code: '404', text: `Endpoint not found: ${path}` } } }] })); } private handleLogin(req: http.IncomingMessage, res: http.ServerResponse) { this.readRequestBody(req, (body) => { try { const loginData = JSON.parse(body); const username = loginData.aaaUser?.attributes?.name; const password = loginData.aaaUser?.attributes?.pwd; if (username && password) { const token = this.generateToken(); const sessionTimeout = 600; // 10 minutes this.sessions.set(token, { token: token, expires: Date.now() + (sessionTimeout * 1000) }); // Set cookie for session management res.setHeader('Set-Cookie', `APIC-Cookie=${token}; Path=/; HttpOnly`); this.sendResponse(res, { imdata: [{ aaaLogin: { attributes: { token: token, sessionTimeoutSeconds: sessionTimeout.toString(), maximumLifetimeSeconds: '86400' } } }] }); } else { res.writeHead(401); this.sendResponse(res, { imdata: [{ error: { attributes: { code: '401', text: 'Invalid credentials' } } }] }); } } catch (error) { res.writeHead(400); this.sendResponse(res, { imdata: [{ error: { attributes: { code: '400', text: 'Invalid JSON' } } }] }); } }); } private handleCertAuth(req: http.IncomingMessage, res: http.ServerResponse) { const certName = req.headers['x-aci-certificate']; const signature = req.headers['x-aci-signature']; const timestamp = req.headers['x-aci-timestamp']; if (certName && signature && timestamp) { const token = this.generateToken(); const sessionTimeout = 600; this.sessions.set(token, { token: token, expires: Date.now() + (sessionTimeout * 1000) }); res.setHeader('Set-Cookie', `APIC-Cookie=${token}; Path=/; HttpOnly`); this.sendResponse(res, { imdata: [{ aaaLogin: { attributes: { token: token, sessionTimeoutSeconds: sessionTimeout.toString() } } }] }); } else { res.writeHead(401); this.sendResponse(res, { imdata: [{ error: { attributes: { code: '401', text: 'Certificate authentication failed' } } }] }); } } private handleLogout(req: http.IncomingMessage, res: http.ServerResponse) { const cookie = req.headers.cookie; if (cookie) { const tokenMatch = cookie.match(/APIC-Cookie=([^;]+)/); if (tokenMatch) { this.sessions.delete(tokenMatch[1]); } } this.sendResponse(res, { imdata: [] }); } private handleCreateOrUpdate(req: http.IncomingMessage, res: http.ServerResponse) { this.readRequestBody(req, (body) => { try { const data = JSON.parse(body); console.log('Mock APIC: Creating/updating object:', JSON.stringify(data, null, 2)); // Simulate successful creation this.sendResponse(res, { imdata: [] }); } catch (error) { res.writeHead(400); this.sendResponse(res, { imdata: [{ error: { attributes: { code: '400', text: 'Invalid JSON data' } } }] }); } }); } private handleDelete(req: http.IncomingMessage, res: http.ServerResponse) { console.log('Mock APIC: Deleting object:', req.url); this.sendResponse(res, { imdata: [] }); } private isAuthenticated(req: http.IncomingMessage): boolean { const cookie = req.headers.cookie; if (!cookie) return false; const tokenMatch = cookie.match(/APIC-Cookie=([^;]+)/); if (!tokenMatch) return false; const session = this.sessions.get(tokenMatch[1]); if (!session || session.expires < Date.now()) { if (session) this.sessions.delete(tokenMatch[1]); return false; } return true; } private generateToken(): string { return 'mock-token-' + Math.random().toString(36).substring(2) + Date.now(); } private readRequestBody(req: http.IncomingMessage, callback: (body: string) => void) { let body = ''; req.on('data', (chunk) => { body += chunk.toString(); }); req.on('end', () => { callback(body); }); } private sendResponse(res: http.ServerResponse, data: any) { res.writeHead(200); res.end(JSON.stringify(data, null, 2)); } public start() { this.server.listen(this.port, () => { console.log(`šŸš€ Mock APIC Server running on port ${this.port}`); console.log(`šŸ“” Base URL: http://localhost:${this.port}`); console.log(`šŸ” Use any username/password to login`); console.log(`šŸ“š Available endpoints:`); console.log(` - POST /api/aaaLogin.json (login)`); console.log(` - GET /api/node/class/fvTenant.json (tenants)`); console.log(` - GET /api/node/class/faultInst.json (faults)`); console.log(` - GET /api/node/class/fabricHealthTotal.json (health)`); console.log(` - GET /api/node/class/fabricNode.json (nodes)`); console.log(` - ... and more!`); console.log(``); console.log(`šŸ”§ Test with: curl -X POST http://localhost:${this.port}/api/aaaLogin.json \\`); console.log(` -H "Content-Type: application/json" \\`); console.log(` -d '{"aaaUser":{"attributes":{"name":"admin","pwd":"admin"}}}'`); }); this.server.on('error', (error) => { console.error('Mock APIC Server error:', error); }); } public stop() { this.server.close(); } } // Start the mock server if (import.meta.url === `file://${process.argv[1]}`) { const port = parseInt(process.env.MOCK_APIC_PORT || '8443'); const mockServer = new MockAPICServer(port); mockServer.start(); // Graceful shutdown process.on('SIGINT', () => { console.log('\nšŸ›‘ Shutting down Mock APIC Server...'); mockServer.stop(); process.exit(0); }); process.on('SIGTERM', () => { console.log('\nšŸ›‘ Shutting down Mock APIC Server...'); mockServer.stop(); process.exit(0); }); } export { MockAPICServer };

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/jim-coyne/ACI_MCP'

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