Skip to main content
Glama
aciApi.ts11.4 kB
import * as https from 'https'; import * as crypto from 'crypto'; import axios, { AxiosInstance, InternalAxiosRequestConfig, AxiosResponse } from 'axios'; export interface ACIConfig { apicUrl: string; username: string; password?: string; certificateName?: string; privateKey?: string; validateCerts?: boolean; timeout?: number; } export interface ACISession { token: string; refreshToken?: string; expires: number; } export class ACIApiService { private client: AxiosInstance; private config: ACIConfig; private session: ACISession | null = null; constructor(config: ACIConfig) { this.config = { validateCerts: false, timeout: 30000, ...config }; // Create axios instance with custom config this.client = axios.create({ baseURL: this.config.apicUrl, timeout: this.config.timeout, httpsAgent: new https.Agent({ rejectUnauthorized: this.config.validateCerts }), headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' } }); // Add request interceptor for authentication this.client.interceptors.request.use(async (config: InternalAxiosRequestConfig) => { if (!this.isAuthenticated() && !config.url?.includes('/api/aaaLogin')) { await this.authenticate(); } if (this.session?.token && config.headers) { config.headers['Cookie'] = `APIC-Cookie=${this.session.token}`; } return config; }); // Add response interceptor for error handling this.client.interceptors.response.use( (response) => response, async (error) => { if (error.response?.status === 401 || error.response?.status === 403) { this.session = null; // Retry the request after re-authentication if (!error.config.url?.includes('/api/aaaLogin')) { await this.authenticate(); return this.client.request(error.config); } } throw error; } ); } private isAuthenticated(): boolean { return this.session !== null && Date.now() < this.session.expires; } public async authenticate(): Promise<void> { try { if (this.config.privateKey && this.config.certificateName) { await this.authenticateWithCertificate(); } else if (this.config.password) { await this.authenticateWithPassword(); } else { throw new Error('No authentication method configured'); } } catch (error) { console.error('Authentication failed:', error); throw error; } } private async authenticateWithPassword(): Promise<void> { const loginData = { aaaUser: { attributes: { name: this.config.username, pwd: this.config.password } } }; const response = await this.client.post('/api/aaaLogin.json', loginData); const loginResult = response.data?.imdata?.[0]?.aaaLogin?.attributes; if (!loginResult?.token) { throw new Error('Authentication failed: No token received'); } this.session = { token: loginResult.token, refreshToken: loginResult.refreshToken, expires: Date.now() + (parseInt(loginResult.refreshTimeoutSeconds || '600') * 1000) }; } private async authenticateWithCertificate(): Promise<void> { const payload = `GET/api/aaaRefresh.json`; const signature = crypto.sign('sha256', Buffer.from(payload), this.config.privateKey!); const encodedSignature = Buffer.from(signature).toString('base64'); const headers = { 'Cookie': `APIC-Request-Signature=${encodedSignature}; APIC-Certificate-Algorithm=v1.0; APIC-Certificate-Fingerprint=fingerprint; APIC-Certificate-DN=uni/userext/user-${this.config.username}/usercert-${this.config.certificateName}` }; const response = await this.client.get('/api/aaaRefresh.json', { headers }); const refreshResult = response.data?.imdata?.[0]?.aaaRefresh?.attributes; if (!refreshResult?.token) { throw new Error('Certificate authentication failed'); } this.session = { token: refreshResult.token, expires: Date.now() + (parseInt(refreshResult.refreshTimeoutSeconds || '600') * 1000) }; } async get(path: string, params?: Record<string, any>): Promise<any> { const response = await this.client.get(path, { params }); return response.data; } async post(path: string, data: any): Promise<any> { const response = await this.client.post(path, data); return response.data; } async put(path: string, data: any): Promise<any> { const response = await this.client.put(path, data); return response.data; } async delete(path: string): Promise<any> { const response = await this.client.delete(path); return response.data; } // ACI-specific methods async getTenants(): Promise<any> { return this.get('/api/node/class/fvTenant.json'); } async getApplicationProfiles(tenant?: string): Promise<any> { const filter = tenant ? `?query-target-filter=wcard(fvAp.dn,"${tenant}")` : ''; return this.get(`/api/node/class/fvAp.json${filter}`); } async getEndpointGroups(tenant?: string, appProfile?: string): Promise<any> { let filter = ''; if (tenant && appProfile) { filter = `?query-target-filter=wcard(fvAEPg.dn,"${tenant}/ap-${appProfile}")`; } else if (tenant) { filter = `?query-target-filter=wcard(fvAEPg.dn,"${tenant}")`; } return this.get(`/api/node/class/fvAEPg.json${filter}`); } async getBridgeDomains(tenant?: string): Promise<any> { const filter = tenant ? `?query-target-filter=wcard(fvBD.dn,"${tenant}")` : ''; return this.get(`/api/node/class/fvBD.json${filter}`); } async getVRFs(tenant?: string): Promise<any> { const filter = tenant ? `?query-target-filter=wcard(fvCtx.dn,"${tenant}")` : ''; return this.get(`/api/node/class/fvCtx.json${filter}`); } async getContracts(tenant?: string): Promise<any> { const filter = tenant ? `?query-target-filter=wcard(vzBrCP.dn,"${tenant}")` : ''; return this.get(`/api/node/class/vzBrCP.json${filter}`); } async getFilters(tenant?: string): Promise<any> { const filter = tenant ? `?query-target-filter=wcard(vzFilter.dn,"${tenant}")` : ''; return this.get(`/api/node/class/vzFilter.json${filter}`); } async getPhysicalDomains(): Promise<any> { return this.get('/api/node/class/physDomP.json'); } async getVlanPools(): Promise<any> { return this.get('/api/node/class/fvnsVlanInstP.json'); } async getNodes(): Promise<any> { return this.get('/api/node/class/fabricNode.json'); } async getInterfaces(): Promise<any> { return this.get('/api/node/class/l1PhysIf.json'); } async getFabricHealth(): Promise<any> { return this.get('/api/node/class/fabricHealthTotal.json'); } async getFaults(): Promise<any> { return this.get('/api/node/class/faultSummary.json'); } async createTenant(name: string, description?: string): Promise<any> { const tenantData = { fvTenant: { attributes: { name: name, descr: description || '', dn: `uni/tn-${name}` } } }; return this.post('/api/node/mo/uni.json', tenantData); } async createApplicationProfile(tenant: string, name: string, description?: string): Promise<any> { const apData = { fvAp: { attributes: { name: name, descr: description || '', dn: `uni/tn-${tenant}/ap-${name}` } } }; return this.post(`/api/node/mo/uni/tn-${tenant}.json`, apData); } async createEndpointGroup(tenant: string, appProfile: string, name: string, bridgeDomain?: string): Promise<any> { const epgData: any = { fvAEPg: { attributes: { name: name, dn: `uni/tn-${tenant}/ap-${appProfile}/epg-${name}` } } }; if (bridgeDomain) { epgData.fvAEPg.children = [{ fvRsBd: { attributes: { tnFvBDName: bridgeDomain } } }]; } return this.post(`/api/node/mo/uni/tn-${tenant}/ap-${appProfile}.json`, epgData); } async createBridgeDomain(tenant: string, name: string, vrf?: string): Promise<any> { const bdData: any = { fvBD: { attributes: { name: name, dn: `uni/tn-${tenant}/BD-${name}` } } }; if (vrf) { bdData.fvBD.children = [{ fvRsCtx: { attributes: { tnFvCtxName: vrf } } }]; } return this.post(`/api/node/mo/uni/tn-${tenant}.json`, bdData); } async createVRF(tenant: string, name: string, description?: string): Promise<any> { const vrfData = { fvCtx: { attributes: { name: name, descr: description || '', dn: `uni/tn-${tenant}/ctx-${name}` } } }; return this.post(`/api/node/mo/uni/tn-${tenant}.json`, vrfData); } async createContract(tenant: string, name: string, scope: string = 'context'): Promise<any> { const contractData = { vzBrCP: { attributes: { name: name, scope: scope, dn: `uni/tn-${tenant}/brc-${name}` } } }; return this.post(`/api/node/mo/uni/tn-${tenant}.json`, contractData); } async createFilter(tenant: string, name: string): Promise<any> { const filterData = { vzFilter: { attributes: { name: name, dn: `uni/tn-${tenant}/flt-${name}` } } }; return this.post(`/api/node/mo/uni/tn-${tenant}.json`, filterData); } async deleteObject(dn: string): Promise<any> { return this.delete(`/api/node/mo/${dn}.json`); } // Add convenience methods for the server async listTenants(): Promise<any> { return this.getTenants(); } async getTenant(name: string): Promise<any> { const tenants = await this.getTenants(); const tenant = tenants.imdata?.find((t: any) => t.fvTenant?.attributes?.name === name); return tenant || null; } async deleteTenant(name: string): Promise<void> { await this.deleteObject(`uni/tn-${name}`); } async listApplicationProfiles(tenant: string): Promise<any> { return this.getApplicationProfiles(tenant); } async listEndpointGroups(tenant: string, appProfile: string): Promise<any> { return this.getEndpointGroups(tenant, appProfile); } async listBridgeDomains(tenant: string): Promise<any> { return this.getBridgeDomains(tenant); } async listVRFs(tenant: string): Promise<any> { return this.getVRFs(tenant); } async listContracts(tenant: string): Promise<any> { return this.getContracts(tenant); } async listFaults(severity?: string): Promise<any> { let url = '/api/node/class/faultInst.json'; if (severity) { url += `?query-target-filter=eq(faultInst.severity,"${severity}")`; } return this.get(url); } async listNodes(): Promise<any> { return this.getNodes(); } async getSystemInfo(): Promise<any> { const response = await this.get('/api/node/class/topSystem.json'); return response.imdata?.[0] || null; } async logout(): Promise<void> { try { await this.post('/api/aaaLogout.json', {}); } catch (error) { console.warn('Logout request failed:', error); } } }

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