Skip to main content
Glama

Fastly NGWAF MCP Server

by purpleax
server.js59.2 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import axios from 'axios'; // Fastly NGWAF API Client class FastlyNGWAFClient { api; email = null; token = null; constructor(email, token) { this.api = axios.create({ baseURL: 'https://dashboard.signalsciences.net/api/v0', headers: { 'Content-Type': 'application/json', 'User-Agent': 'Claude-MCP-FastlyNGWAF/1.0', }, }); // Set credentials if provided if (email && token) { this.setCredentials(email, token); } } setCredentials(email, token) { this.email = email; this.token = token; this.api.defaults.headers.common['x-api-user'] = email; this.api.defaults.headers.common['x-api-token'] = token; return { success: true, authenticated: true }; } // Test API connection and validate credentials async testConnection() { try { const response = await this.api.get('/corps'); return { success: true, authenticated: true, corporationsCount: response.data.data?.length || 0, email: this.email }; } catch (error) { if (error.response?.status === 401) { throw new Error('Invalid email or API token. Please check your credentials.'); } throw new Error(`API connection failed: ${error.response?.data?.message || error.message}`); } } // Corps Management async listCorps() { const response = await this.api.get('/corps'); return response.data; } async getCorp(corpName) { const response = await this.api.get(`/corps/${corpName}`); return response.data; } async getCorpOverview(corpName, from, until) { const params = new URLSearchParams(); if (from) params.append('from', from); if (until) params.append('until', until); const response = await this.api.get(`/corps/${corpName}/reports/attacks?${params.toString()}`); return response.data; } // Sites Management async listSites(corpName, q, page, limit) { const params = new URLSearchParams(); if (q) params.append('q', q); if (page) params.append('page', page.toString()); if (limit) params.append('limit', limit.toString()); const response = await this.api.get(`/corps/${corpName}/sites?${params.toString()}`); return response.data; } async getSite(corpName, siteName) { const response = await this.api.get(`/corps/${corpName}/sites/${siteName}`); return response.data; } async createSite(corpName, siteData) { const response = await this.api.post(`/corps/${corpName}/sites`, siteData); return response.data; } async updateSite(corpName, siteName, siteData) { const response = await this.api.patch(`/corps/${corpName}/sites/${siteName}`, siteData); return response.data; } async deleteSite(corpName, siteName) { await this.api.delete(`/corps/${corpName}/sites/${siteName}`); return { success: true }; } // Rules Management async listCorpRules(corpName, type, page, limit) { const params = new URLSearchParams(); if (type) params.append('type', type); if (page) params.append('page', page.toString()); if (limit) params.append('limit', limit.toString()); const response = await this.api.get(`/corps/${corpName}/rules?${params.toString()}`); return response.data; } async listSiteRules(corpName, siteName, type, page, limit) { const params = new URLSearchParams(); if (type) params.append('type', type); if (page) params.append('page', page.toString()); if (limit) params.append('limit', limit.toString()); const response = await this.api.get(`/corps/${corpName}/sites/${siteName}/rules?${params.toString()}`); return response.data; } async createCorpRule(corpName, ruleData) { const response = await this.api.post(`/corps/${corpName}/rules`, ruleData); return response.data; } async createSiteRule(corpName, siteName, ruleData) { const response = await this.api.post(`/corps/${corpName}/sites/${siteName}/rules`, ruleData); return response.data; } async updateCorpRule(corpName, ruleId, ruleData) { const response = await this.api.put(`/corps/${corpName}/rules/${ruleId}`, ruleData); return response.data; } async updateSiteRule(corpName, siteName, ruleId, ruleData) { const response = await this.api.put(`/corps/${corpName}/sites/${siteName}/rules/${ruleId}`, ruleData); return response.data; } async deleteCorpRule(corpName, ruleId) { await this.api.delete(`/corps/${corpName}/rules/${ruleId}`); return { success: true }; } async deleteSiteRule(corpName, siteName, ruleId) { await this.api.delete(`/corps/${corpName}/sites/${siteName}/rules/${ruleId}`); return { success: true }; } // Lists Management async listCorpLists(corpName) { const response = await this.api.get(`/corps/${corpName}/lists`); return response.data; } async listSiteLists(corpName, siteName) { const response = await this.api.get(`/corps/${corpName}/sites/${siteName}/lists`); return response.data; } async createCorpList(corpName, listData) { const response = await this.api.post(`/corps/${corpName}/lists`, listData); return response.data; } async createSiteList(corpName, siteName, listData) { const response = await this.api.post(`/corps/${corpName}/sites/${siteName}/lists`, listData); return response.data; } async updateCorpList(corpName, listId, updateData) { const response = await this.api.patch(`/corps/${corpName}/lists/${listId}`, updateData); return response.data; } async updateSiteList(corpName, siteName, listId, updateData) { const response = await this.api.patch(`/corps/${corpName}/sites/${siteName}/lists/${listId}`, updateData); return response.data; } async deleteCorpList(corpName, listId) { await this.api.delete(`/corps/${corpName}/lists/${listId}`); return { success: true }; } async deleteSiteList(corpName, siteName, listId) { await this.api.delete(`/corps/${corpName}/sites/${siteName}/lists/${listId}`); return { success: true }; } // Events and Requests async searchRequests(corpName, siteName, query, page, limit) { const params = new URLSearchParams(); if (query) params.append('q', query); if (page) params.append('page', page.toString()); if (limit) params.append('limit', limit.toString()); const response = await this.api.get(`/corps/${corpName}/sites/${siteName}/requests?${params.toString()}`); return response.data; } async getRequest(corpName, siteName, requestId) { const response = await this.api.get(`/corps/${corpName}/sites/${siteName}/requests/${requestId}`); return response.data; } async listEvents(corpName, siteName, from, until, action, tag, ip) { const params = new URLSearchParams(); if (from) params.append('from', from.toString()); if (until) params.append('until', until.toString()); if (action) params.append('action', action); if (tag) params.append('tag', tag); if (ip) params.append('ip', ip); const response = await this.api.get(`/corps/${corpName}/sites/${siteName}/events?${params.toString()}`); return response.data; } async getEvent(corpName, siteName, eventId) { const response = await this.api.get(`/corps/${corpName}/sites/${siteName}/events/${eventId}`); return response.data; } async expireEvent(corpName, siteName, eventId) { const response = await this.api.post(`/corps/${corpName}/sites/${siteName}/events/${eventId}/expire`); return response.data; } // IP Management async getSuspiciousIPs(corpName, siteName, limit) { const params = new URLSearchParams(); if (limit) params.append('limit', limit.toString()); const response = await this.api.get(`/corps/${corpName}/sites/${siteName}/suspiciousIPs?${params.toString()}`); return response.data; } async getWhitelist(corpName, siteName) { const response = await this.api.get(`/corps/${corpName}/sites/${siteName}/whitelist`); return response.data; } async addToWhitelist(corpName, siteName, ipData) { const response = await this.api.put(`/corps/${corpName}/sites/${siteName}/whitelist`, ipData); return response.data; } async removeFromWhitelist(corpName, siteName, entryId) { await this.api.delete(`/corps/${corpName}/sites/${siteName}/whitelist/${entryId}`); return { success: true }; } async getBlacklist(corpName, siteName) { const response = await this.api.get(`/corps/${corpName}/sites/${siteName}/blacklist`); return response.data; } async addToBlacklist(corpName, siteName, ipData) { const response = await this.api.put(`/corps/${corpName}/sites/${siteName}/blacklist`, ipData); return response.data; } async removeFromBlacklist(corpName, siteName, entryId) { await this.api.delete(`/corps/${corpName}/sites/${siteName}/blacklist/${entryId}`); return { success: true }; } // Alerts Management async listAlerts(corpName, siteName) { const response = await this.api.get(`/corps/${corpName}/sites/${siteName}/alerts`); return response.data; } async createAlert(corpName, siteName, alertData) { const response = await this.api.post(`/corps/${corpName}/sites/${siteName}/alerts`, alertData); return response.data; } async updateAlert(corpName, siteName, alertId, alertData) { const response = await this.api.patch(`/corps/${corpName}/sites/${siteName}/alerts/${alertId}`, alertData); return response.data; } async deleteAlert(corpName, siteName, alertId) { await this.api.delete(`/corps/${corpName}/sites/${siteName}/alerts/${alertId}`); return { success: true }; } // Integrations async listCorpIntegrations(corpName) { const response = await this.api.get(`/corps/${corpName}/integrations`); return response.data; } async listSiteIntegrations(corpName, siteName) { const response = await this.api.get(`/corps/${corpName}/sites/${siteName}/integrations`); return response.data; } async createCorpIntegration(corpName, integrationData) { const response = await this.api.post(`/corps/${corpName}/integrations`, integrationData); return response.data; } async createSiteIntegration(corpName, siteName, integrationData) { const response = await this.api.post(`/corps/${corpName}/sites/${siteName}/integrations`, integrationData); return response.data; } // Analytics async getTopAttacks(corpName, siteName, from, until, groupBy, limit) { const params = new URLSearchParams(); if (from) params.append('from', from.toString()); if (until) params.append('until', until.toString()); if (groupBy) params.append('groupBy', groupBy); if (limit) params.append('limit', limit.toString()); const response = await this.api.get(`/corps/${corpName}/sites/${siteName}/top/attacks?${params.toString()}`); return response.data; } async getTimeseriesRequests(corpName, siteName, from, until, tags, rollup) { const params = new URLSearchParams(); params.append('from', from.toString()); params.append('until', until.toString()); if (tags) params.append('tags', tags); if (rollup) params.append('rollup', rollup.toString()); const response = await this.api.get(`/corps/${corpName}/sites/${siteName}/timeseries/requests?${params.toString()}`); return response.data; } // CloudWAF Management async listCloudWAFInstances(corpName) { const response = await this.api.get(`/corps/${corpName}/cloudwafInstances`); return response.data; } async createCloudWAFInstance(corpName, instanceData) { const response = await this.api.post(`/corps/${corpName}/cloudwafInstances`, instanceData); return response.data; } async getCloudWAFInstance(corpName, deploymentId) { const response = await this.api.get(`/corps/${corpName}/cloudwafInstances/${deploymentId}`); return response.data; } async updateCloudWAFInstance(corpName, deploymentId, instanceData) { const response = await this.api.put(`/corps/${corpName}/cloudwafInstances/${deploymentId}`, instanceData); return response.data; } async deleteCloudWAFInstance(corpName, deploymentId) { await this.api.delete(`/corps/${corpName}/cloudwafInstances/${deploymentId}`); return { success: true }; } // User Management async listCorpUsers(corpName) { const response = await this.api.get(`/corps/${corpName}/users`); return response.data; } async getCorpUser(corpName, userEmail) { const response = await this.api.get(`/corps/${corpName}/users/${userEmail}`); return response.data; } async updateCorpUser(corpName, userEmail, userData) { const response = await this.api.patch(`/corps/${corpName}/users/${userEmail}`, userData); return response.data; } async inviteCorpUser(corpName, userEmail, userData) { const response = await this.api.post(`/corps/${corpName}/users/${userEmail}/invite`, userData); return response.data; } async deleteCorpUser(corpName, userEmail) { await this.api.delete(`/corps/${corpName}/users/${userEmail}`); return { success: true }; } } // MCP Server Setup const server = new Server({ name: 'fastly-ngwaf-server', version: '0.1.0', }, { capabilities: { tools: {}, }, }); let client; let context = {}; // Initialize from environment variables if available const initializeFromEnv = async () => { const email = process.env.FASTLY_NGWAF_EMAIL; const token = process.env.FASTLY_NGWAF_TOKEN; const defaultCorpName = process.env.FASTLY_NGWAF_DEFAULT_CORP; const defaultSiteName = process.env.FASTLY_NGWAF_DEFAULT_SITE; if (email && token) { try { client = new FastlyNGWAFClient(email, token); const connectionTest = await client.testConnection(); // Set default context if provided if (defaultCorpName) { context.defaultCorpName = defaultCorpName; } if (defaultSiteName) { context.defaultSiteName = defaultSiteName; } console.error(`Fastly NGWAF: Auto-authenticated as ${email} (${connectionTest.corporationsCount} corps available)`); if (context.defaultCorpName) { console.error(`Default context: Corp=${context.defaultCorpName}, Site=${context.defaultSiteName || 'not set'}`); } } catch (error) { console.error(`Auto-authentication failed: ${error.message}`); } } }; // Helper function to resolve corp and site names function resolveContext(args) { const corpName = args.corpName || context.defaultCorpName; const siteName = args.siteName || context.defaultSiteName; if (!corpName) { throw new Error('Corporation name is required. Please set context or provide corpName parameter.'); } return { corpName, siteName }; } // Tool definitions const tools = [ { name: 'set_credentials', description: 'Set Fastly NGWAF API credentials (email and access token)', inputSchema: { type: 'object', properties: { email: { type: 'string', description: 'Your Fastly NGWAF email address' }, token: { type: 'string', description: 'Your Fastly NGWAF API access token' }, }, required: ['email', 'token'], }, }, { name: 'test_connection', description: 'Test the API connection and validate credentials', inputSchema: { type: 'object', properties: {}, }, }, { name: 'set_context', description: 'Set the default corporation and site context for subsequent operations', inputSchema: { type: 'object', properties: { corpName: { type: 'string', description: 'Default corporation name' }, siteName: { type: 'string', description: 'Default site name' }, }, required: ['corpName'], }, }, { name: 'get_context', description: 'Get the current context (corp and site names)', inputSchema: { type: 'object', properties: {}, }, }, { name: 'discover_environment', description: 'Discover available corporations and sites for the authenticated user', inputSchema: { type: 'object', properties: { corpName: { type: 'string', description: 'Specific corporation to explore (optional)' }, }, }, }, { name: 'list_corps', description: 'List all corporations accessible to the authenticated user', inputSchema: { type: 'object', properties: {}, }, }, { name: 'get_corp_overview', description: 'Get attack overview for a corporation', inputSchema: { type: 'object', properties: { corpName: { type: 'string', description: 'Corporation name (uses context default if not provided)' }, from: { type: 'string', description: 'Start date (e.g., "-7d")' }, until: { type: 'string', description: 'End date (e.g., "-1d")' }, }, }, }, { name: 'list_sites', description: 'List sites in a corporation', inputSchema: { type: 'object', properties: { corpName: { type: 'string', description: 'Corporation name (uses context default if not provided)' }, query: { type: 'string', description: 'Search query to filter sites' }, page: { type: 'number', description: 'Page number' }, limit: { type: 'number', description: 'Results per page' }, }, }, }, { name: 'get_site', description: 'Get details of a specific site', inputSchema: { type: 'object', properties: { corpName: { type: 'string', description: 'Corporation name (uses context default if not provided)' }, siteName: { type: 'string', description: 'Site name (uses context default if not provided)' }, }, }, }, { name: 'create_site', description: 'Create a new site in a corporation', inputSchema: { type: 'object', properties: { corpName: { type: 'string', description: 'Corporation name (uses context default if not provided)' }, siteName: { type: 'string', description: 'Site name' }, displayName: { type: 'string', description: 'Display name' }, agentLevel: { type: 'string', enum: ['block', 'log', 'off'], description: 'Agent action level' }, blockDurationSeconds: { type: 'number', description: 'Block duration in seconds' }, blockHTTPCode: { type: 'number', description: 'HTTP response code for blocked requests' }, blockRedirectURL: { type: 'string', description: 'Redirect URL for blocked requests' }, }, required: ['siteName'], }, }, { name: 'update_site', description: 'Update site configuration', inputSchema: { type: 'object', properties: { corpName: { type: 'string', description: 'Corporation name (uses context default if not provided)' }, siteName: { type: 'string', description: 'Site name (uses context default if not provided)' }, displayName: { type: 'string', description: 'Display name' }, agentLevel: { type: 'string', enum: ['block', 'log', 'off'], description: 'Agent action level' }, blockDurationSeconds: { type: 'number', description: 'Block duration in seconds' }, blockHTTPCode: { type: 'number', description: 'HTTP response code for blocked requests' }, blockRedirectURL: { type: 'string', description: 'Redirect URL for blocked requests' }, }, }, }, { name: 'delete_site', description: 'Delete a site', inputSchema: { type: 'object', properties: { corpName: { type: 'string', description: 'Corporation name (uses context default if not provided)' }, siteName: { type: 'string', description: 'Site name (uses context default if not provided)' }, }, }, }, { name: 'list_corp_rules', description: 'List rules at corporation level', inputSchema: { type: 'object', properties: { corpName: { type: 'string', description: 'Corporation name (uses context default if not provided)' }, type: { type: 'string', enum: ['request', 'signal'], description: 'Rule type' }, page: { type: 'number', description: 'Page number' }, limit: { type: 'number', description: 'Results per page' }, }, }, }, { name: 'list_site_rules', description: 'List rules for a specific site', inputSchema: { type: 'object', properties: { corpName: { type: 'string', description: 'Corporation name (uses context default if not provided)' }, siteName: { type: 'string', description: 'Site name (uses context default if not provided)' }, type: { type: 'string', enum: ['request', 'signal', 'rateLimit'], description: 'Rule type' }, page: { type: 'number', description: 'Page number' }, limit: { type: 'number', description: 'Results per page' }, }, }, }, { name: 'create_corp_rule', description: 'Create a corporation-level rule', inputSchema: { type: 'object', properties: { corpName: { type: 'string', description: 'Corporation name (uses context default if not provided)' }, type: { type: 'string', enum: ['request', 'signal'], description: 'Rule type' }, enabled: { type: 'boolean', description: 'Whether rule is enabled' }, groupOperator: { type: 'string', enum: ['all', 'any'], description: 'Condition group operator' }, conditions: { type: 'array', description: 'Rule conditions' }, actions: { type: 'array', description: 'Rule actions' }, reason: { type: 'string', description: 'Description of the rule' }, signal: { type: 'string', description: 'Signal ID for exclusion rules' }, corpScope: { type: 'string', enum: ['global', 'specificSites'], description: 'Rule scope' }, siteNames: { type: 'array', items: { type: 'string' }, description: 'Site names for specific scope' }, }, required: ['type', 'groupOperator', 'conditions', 'actions'], }, }, { name: 'create_site_rule', description: 'Create a site-level rule', inputSchema: { type: 'object', properties: { corpName: { type: 'string', description: 'Corporation name (uses context default if not provided)' }, siteName: { type: 'string', description: 'Site name (uses context default if not provided)' }, type: { type: 'string', enum: ['request', 'signal', 'rateLimit'], description: 'Rule type' }, enabled: { type: 'boolean', description: 'Whether rule is enabled' }, groupOperator: { type: 'string', enum: ['all', 'any'], description: 'Condition group operator' }, conditions: { type: 'array', description: 'Rule conditions' }, actions: { type: 'array', description: 'Rule actions' }, reason: { type: 'string', description: 'Description of the rule' }, signal: { type: 'string', description: 'Signal ID for exclusion/rate limit rules' }, thresholdCount: { type: 'number', description: 'Threshold count for rate limit rules' }, thresholdInterval: { type: 'number', description: 'Threshold interval for rate limit rules' }, blockDurationSeconds: { type: 'number', description: 'Block duration for rate limit rules' }, }, required: ['type', 'groupOperator', 'conditions', 'actions'], }, }, { name: 'delete_corp_rule', description: 'Delete a corporation-level rule', inputSchema: { type: 'object', properties: { corpName: { type: 'string', description: 'Corporation name (uses context default if not provided)' }, ruleId: { type: 'string', description: 'Rule ID to delete' }, }, required: ['ruleId'], }, }, { name: 'delete_site_rule', description: 'Delete a site-level rule', inputSchema: { type: 'object', properties: { corpName: { type: 'string', description: 'Corporation name (uses context default if not provided)' }, siteName: { type: 'string', description: 'Site name (uses context default if not provided)' }, ruleId: { type: 'string', description: 'Rule ID to delete' }, }, required: ['ruleId'], }, }, { name: 'search_requests', description: 'Search requests with advanced filtering', inputSchema: { type: 'object', properties: { corpName: { type: 'string', description: 'Corporation name (uses context default if not provided)' }, siteName: { type: 'string', description: 'Site name (uses context default if not provided)' }, query: { type: 'string', description: 'Search query (e.g., "tag:SQLI")' }, page: { type: 'number', description: 'Page number' }, limit: { type: 'number', description: 'Results per page' }, }, }, }, { name: 'list_events', description: 'List security events (attacks, blocks, etc.)', inputSchema: { type: 'object', properties: { corpName: { type: 'string', description: 'Corporation name (uses context default if not provided)' }, siteName: { type: 'string', description: 'Site name (uses context default if not provided)' }, from: { type: 'number', description: 'Unix timestamp start' }, until: { type: 'number', description: 'Unix timestamp end' }, action: { type: 'string', enum: ['flagged', 'info'], description: 'Filter by action' }, tag: { type: 'string', description: 'Filter by tag' }, ip: { type: 'string', description: 'Filter by IP address' }, }, }, }, { name: 'expire_event', description: 'Manually expire an event (unblock IP)', inputSchema: { type: 'object', properties: { corpName: { type: 'string', description: 'Corporation name (uses context default if not provided)' }, siteName: { type: 'string', description: 'Site name (uses context default if not provided)' }, eventId: { type: 'string', description: 'Event ID' }, }, required: ['eventId'], }, }, { name: 'get_suspicious_ips', description: 'Get list of suspicious IP addresses', inputSchema: { type: 'object', properties: { corpName: { type: 'string', description: 'Corporation name (uses context default if not provided)' }, siteName: { type: 'string', description: 'Site name (uses context default if not provided)' }, limit: { type: 'number', description: 'Maximum number of IPs to return' }, }, }, }, { name: 'manage_whitelist', description: 'Manage IP whitelist (allowlist)', inputSchema: { type: 'object', properties: { corpName: { type: 'string', description: 'Corporation name (uses context default if not provided)' }, siteName: { type: 'string', description: 'Site name (uses context default if not provided)' }, action: { type: 'string', enum: ['list', 'add', 'remove'], description: 'Action to perform' }, ip: { type: 'string', description: 'IP address (for add action)' }, note: { type: 'string', description: 'Note for IP (for add action)' }, expires: { type: 'string', description: 'Expiration date (RFC3339 format)' }, entryId: { type: 'string', description: 'Entry ID (for remove action)' }, }, required: ['action'], }, }, { name: 'manage_blacklist', description: 'Manage IP blacklist (blocklist)', inputSchema: { type: 'object', properties: { corpName: { type: 'string', description: 'Corporation name (uses context default if not provided)' }, siteName: { type: 'string', description: 'Site name (uses context default if not provided)' }, action: { type: 'string', enum: ['list', 'add', 'remove'], description: 'Action to perform' }, ip: { type: 'string', description: 'IP address (for add action)' }, note: { type: 'string', description: 'Note for IP (for add action)' }, expires: { type: 'string', description: 'Expiration date (RFC3339 format)' }, entryId: { type: 'string', description: 'Entry ID (for remove action)' }, }, required: ['action'], }, }, { name: 'manage_lists', description: 'Manage custom lists (IP, country, string, etc.)', inputSchema: { type: 'object', properties: { corpName: { type: 'string', description: 'Corporation name (uses context default if not provided)' }, siteName: { type: 'string', description: 'Site name (optional for corp-level lists, uses context default if not provided)' }, action: { type: 'string', enum: ['list', 'create', 'update', 'delete'], description: 'Action to perform' }, listId: { type: 'string', description: 'List ID (for update/delete actions)' }, name: { type: 'string', description: 'List name (for create action)' }, type: { type: 'string', enum: ['ip', 'country', 'string', 'wildcard', 'signal'], description: 'List type' }, description: { type: 'string', description: 'List description' }, entries: { type: 'array', items: { type: 'string' }, description: 'List entries' }, additions: { type: 'array', items: { type: 'string' }, description: 'Entries to add (for update)' }, deletions: { type: 'array', items: { type: 'string' }, description: 'Entries to remove (for update)' }, }, required: ['action'], }, }, { name: 'manage_alerts', description: 'Manage alerts for monitoring attack patterns', inputSchema: { type: 'object', properties: { corpName: { type: 'string', description: 'Corporation name (uses context default if not provided)' }, siteName: { type: 'string', description: 'Site name (uses context default if not provided)' }, action: { type: 'string', enum: ['list', 'create', 'update', 'delete'], description: 'Action to perform' }, alertId: { type: 'string', description: 'Alert ID (for update/delete actions)' }, tagName: { type: 'string', description: 'Tag name to monitor' }, longName: { type: 'string', description: 'Alert description' }, interval: { type: 'number', enum: [1, 10, 60], description: 'Time interval in minutes' }, threshold: { type: 'number', description: 'Threshold count' }, enabled: { type: 'boolean', description: 'Whether alert is enabled' }, action_type: { type: 'string', enum: ['info', 'flagged'], description: 'Action when triggered' }, }, required: ['action'], }, }, { name: 'get_analytics', description: 'Get analytics data (top attacks, timeseries, etc.)', inputSchema: { type: 'object', properties: { corpName: { type: 'string', description: 'Corporation name (uses context default if not provided)' }, siteName: { type: 'string', description: 'Site name (uses context default if not provided)' }, type: { type: 'string', enum: ['top_attacks', 'timeseries'], description: 'Analytics type' }, from: { type: 'number', description: 'Unix timestamp start' }, until: { type: 'number', description: 'Unix timestamp end' }, groupBy: { type: 'string', enum: ['remoteCountryCode', 'remoteIP', 'path', 'userAgent'], description: 'Group by field (for top attacks)' }, tags: { type: 'string', description: 'Filter by tags (for timeseries)' }, rollup: { type: 'number', description: 'Rollup interval in seconds (for timeseries)' }, limit: { type: 'number', description: 'Maximum results' }, }, required: ['type'], }, }, { name: 'manage_cloudwaf', description: 'Manage CloudWAF instances', inputSchema: { type: 'object', properties: { corpName: { type: 'string', description: 'Corporation name (uses context default if not provided)' }, action: { type: 'string', enum: ['list', 'create', 'get', 'update', 'delete'], description: 'Action to perform' }, deploymentId: { type: 'string', description: 'Deployment ID (for get/update/delete)' }, name: { type: 'string', description: 'Instance name' }, description: { type: 'string', description: 'Instance description' }, region: { type: 'string', description: 'AWS region' }, tlsMinVersion: { type: 'string', enum: ['1.0', '1.2'], description: 'Minimum TLS version' }, siteName: { type: 'string', description: 'Site name for configuration' }, domains: { type: 'array', items: { type: 'string' }, description: 'Domains to protect' }, origin: { type: 'string', description: 'Origin server URL' }, }, required: ['action'], }, }, { name: 'manage_users', description: 'Manage corporation users', inputSchema: { type: 'object', properties: { corpName: { type: 'string', description: 'Corporation name (uses context default if not provided)' }, action: { type: 'string', enum: ['list', 'get', 'update', 'invite', 'delete'], description: 'Action to perform' }, userEmail: { type: 'string', description: 'User email address' }, role: { type: 'string', enum: ['owner', 'admin', 'user', 'observer'], description: 'User role' }, memberships: { type: 'array', description: 'Site memberships' }, }, required: ['action'], }, }, ]; // Request handlers server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; // Ensure args exists and is an object if (!args || typeof args !== 'object') { return { content: [ { type: 'text', text: 'Invalid arguments provided.', }, ], isError: true, }; } // Type assertion for args const typedArgs = args; if (!client && name !== 'set_credentials') { return { content: [ { type: 'text', text: 'Please set your credentials first using the set_credentials tool.', }, ], isError: true, }; } try { let result; switch (name) { case 'set_credentials': client = new FastlyNGWAFClient(); result = client.setCredentials(typedArgs.email, typedArgs.token); // Test the connection to validate the credentials try { const connectionTest = await client.testConnection(); result.message = `Credentials set and validated successfully. Access to ${connectionTest.corporationsCount} corporations.`; result.email = connectionTest.email; } catch (error) { result.success = false; result.error = error.message; } break; case 'test_connection': if (!client) { throw new Error('Please set credentials first using the set_credentials tool.'); } result = await client.testConnection(); break; case 'set_context': context.defaultCorpName = typedArgs.corpName; context.defaultSiteName = typedArgs.siteName; result = { success: true, context: { defaultCorpName: context.defaultCorpName, defaultSiteName: context.defaultSiteName, }, }; break; case 'get_context': result = { context: { defaultCorpName: context.defaultCorpName, defaultSiteName: context.defaultSiteName, }, }; break; case 'discover_environment': if (typedArgs.corpName) { const { corpName } = resolveContext(typedArgs); const sites = await client.listSites(corpName); result = { corporation: corpName, sites }; } else { const corps = await client.listCorps(); result = { corporations: corps }; } break; case 'list_corps': result = await client.listCorps(); break; case 'get_corp_overview': const { corpName: corpForOverview } = resolveContext(typedArgs); result = await client.getCorpOverview(corpForOverview, typedArgs.from, typedArgs.until); break; case 'list_sites': const { corpName: corpForSites } = resolveContext(typedArgs); result = await client.listSites(corpForSites, typedArgs.query, typedArgs.page, typedArgs.limit); break; case 'get_site': const { corpName: corpForSite, siteName: siteForGet } = resolveContext(typedArgs); if (!siteForGet) { throw new Error('Site name is required. Please set context or provide siteName parameter.'); } result = await client.getSite(corpForSite, siteForGet); break; case 'create_site': const { corpName: corpForCreate } = resolveContext(typedArgs); const siteData = { name: typedArgs.siteName, displayName: typedArgs.displayName, agentLevel: typedArgs.agentLevel, blockDurationSeconds: typedArgs.blockDurationSeconds, blockHTTPCode: typedArgs.blockHTTPCode, blockRedirectURL: typedArgs.blockRedirectURL, }; result = await client.createSite(corpForCreate, siteData); break; case 'update_site': const { corpName: corpForUpdate, siteName: siteForUpdate } = resolveContext(typedArgs); if (!siteForUpdate) { throw new Error('Site name is required. Please set context or provide siteName parameter.'); } const updateData = { displayName: typedArgs.displayName, agentLevel: typedArgs.agentLevel, blockDurationSeconds: typedArgs.blockDurationSeconds, blockHTTPCode: typedArgs.blockHTTPCode, blockRedirectURL: typedArgs.blockRedirectURL, }; result = await client.updateSite(corpForUpdate, siteForUpdate, updateData); break; case 'delete_site': const { corpName: corpForDelete, siteName: siteForDelete } = resolveContext(typedArgs); if (!siteForDelete) { throw new Error('Site name is required. Please set context or provide siteName parameter.'); } result = await client.deleteSite(corpForDelete, siteForDelete); break; case 'list_corp_rules': const { corpName: corpForCorpRules } = resolveContext(typedArgs); result = await client.listCorpRules(corpForCorpRules, typedArgs.type, typedArgs.page, typedArgs.limit); break; case 'list_site_rules': const { corpName: corpForSiteRules, siteName: siteForRules } = resolveContext(typedArgs); if (!siteForRules) { throw new Error('Site name is required. Please set context or provide siteName parameter.'); } result = await client.listSiteRules(corpForSiteRules, siteForRules, typedArgs.type, typedArgs.page, typedArgs.limit); break; case 'create_corp_rule': const { corpName: corpForCorpRule } = resolveContext(typedArgs); const corpRuleData = { type: typedArgs.type, enabled: typedArgs.enabled, groupOperator: typedArgs.groupOperator, conditions: typedArgs.conditions, actions: typedArgs.actions, reason: typedArgs.reason, signal: typedArgs.signal, corpScope: typedArgs.corpScope, siteNames: typedArgs.siteNames, }; result = await client.createCorpRule(corpForCorpRule, corpRuleData); break; case 'create_site_rule': const { corpName: corpForSiteRule, siteName: siteForRule } = resolveContext(typedArgs); if (!siteForRule) { throw new Error('Site name is required. Please set context or provide siteName parameter.'); } const siteRuleData = { type: typedArgs.type, enabled: typedArgs.enabled, groupOperator: typedArgs.groupOperator, conditions: typedArgs.conditions, actions: typedArgs.actions, reason: typedArgs.reason, signal: typedArgs.signal, thresholdCount: typedArgs.thresholdCount, thresholdInterval: typedArgs.thresholdInterval, blockDurationSeconds: typedArgs.blockDurationSeconds, }; result = await client.createSiteRule(corpForSiteRule, siteForRule, siteRuleData); break; case 'delete_corp_rule': const { corpName: corpForDeleteRule } = resolveContext(typedArgs); result = await client.deleteCorpRule(corpForDeleteRule, typedArgs.ruleId); break; case 'delete_site_rule': const { corpName: corpForDeleteSiteRule, siteName: siteForDeleteRule } = resolveContext(typedArgs); if (!siteForDeleteRule) { throw new Error('Site name is required. Please set context or provide siteName parameter.'); } result = await client.deleteSiteRule(corpForDeleteSiteRule, siteForDeleteRule, typedArgs.ruleId); break; case 'search_requests': const { corpName: corpForSearch, siteName: siteForSearch } = resolveContext(typedArgs); if (!siteForSearch) { throw new Error('Site name is required. Please set context or provide siteName parameter.'); } result = await client.searchRequests(corpForSearch, siteForSearch, typedArgs.query, typedArgs.page, typedArgs.limit); break; case 'list_events': const { corpName: corpForEvents, siteName: siteForEvents } = resolveContext(typedArgs); if (!siteForEvents) { throw new Error('Site name is required. Please set context or provide siteName parameter.'); } result = await client.listEvents(corpForEvents, siteForEvents, typedArgs.from, typedArgs.until, typedArgs.action, typedArgs.tag, typedArgs.ip); break; case 'expire_event': const { corpName: corpForExpire, siteName: siteForExpire } = resolveContext(typedArgs); if (!siteForExpire) { throw new Error('Site name is required. Please set context or provide siteName parameter.'); } result = await client.expireEvent(corpForExpire, siteForExpire, typedArgs.eventId); break; case 'get_suspicious_ips': const { corpName: corpForSuspicious, siteName: siteForSuspicious } = resolveContext(typedArgs); if (!siteForSuspicious) { throw new Error('Site name is required. Please set context or provide siteName parameter.'); } result = await client.getSuspiciousIPs(corpForSuspicious, siteForSuspicious, typedArgs.limit); break; case 'manage_whitelist': const { corpName: corpForWhitelist, siteName: siteForWhitelist } = resolveContext(typedArgs); if (!siteForWhitelist) { throw new Error('Site name is required. Please set context or provide siteName parameter.'); } if (typedArgs.action === 'list') { result = await client.getWhitelist(corpForWhitelist, siteForWhitelist); } else if (typedArgs.action === 'add') { const ipData = { source: typedArgs.ip, note: typedArgs.note, expires: typedArgs.expires }; result = await client.addToWhitelist(corpForWhitelist, siteForWhitelist, ipData); } else if (typedArgs.action === 'remove') { result = await client.removeFromWhitelist(corpForWhitelist, siteForWhitelist, typedArgs.entryId); } break; case 'manage_blacklist': const { corpName: corpForBlacklist, siteName: siteForBlacklist } = resolveContext(typedArgs); if (!siteForBlacklist) { throw new Error('Site name is required. Please set context or provide siteName parameter.'); } if (typedArgs.action === 'list') { result = await client.getBlacklist(corpForBlacklist, siteForBlacklist); } else if (typedArgs.action === 'add') { const ipData = { source: typedArgs.ip, note: typedArgs.note, expires: typedArgs.expires }; result = await client.addToBlacklist(corpForBlacklist, siteForBlacklist, ipData); } else if (typedArgs.action === 'remove') { result = await client.removeFromBlacklist(corpForBlacklist, siteForBlacklist, typedArgs.entryId); } break; case 'manage_lists': const { corpName: corpForLists, siteName: siteForLists } = resolveContext(typedArgs); if (typedArgs.action === 'list') { result = siteForLists ? await client.listSiteLists(corpForLists, siteForLists) : await client.listCorpLists(corpForLists); } else if (typedArgs.action === 'create') { const listData = { name: typedArgs.name, type: typedArgs.type, description: typedArgs.description, entries: typedArgs.entries, }; result = siteForLists ? await client.createSiteList(corpForLists, siteForLists, listData) : await client.createCorpList(corpForLists, listData); } else if (typedArgs.action === 'update') { const updateData = { description: typedArgs.description, entries: { additions: typedArgs.additions, deletions: typedArgs.deletions, }, }; result = siteForLists ? await client.updateSiteList(corpForLists, siteForLists, typedArgs.listId, updateData) : await client.updateCorpList(corpForLists, typedArgs.listId, updateData); } else if (typedArgs.action === 'delete') { result = siteForLists ? await client.deleteSiteList(corpForLists, siteForLists, typedArgs.listId) : await client.deleteCorpList(corpForLists, typedArgs.listId); } break; case 'manage_alerts': const { corpName: corpForAlerts, siteName: siteForAlerts } = resolveContext(typedArgs); if (!siteForAlerts) { throw new Error('Site name is required. Please set context or provide siteName parameter.'); } if (typedArgs.action === 'list') { result = await client.listAlerts(corpForAlerts, siteForAlerts); } else if (typedArgs.action === 'create') { const alertData = { tagName: typedArgs.tagName, longName: typedArgs.longName, interval: typedArgs.interval, threshold: typedArgs.threshold, enabled: typedArgs.enabled, action: typedArgs.action_type, }; result = await client.createAlert(corpForAlerts, siteForAlerts, alertData); } else if (typedArgs.action === 'update') { const alertData = { tagName: typedArgs.tagName, longName: typedArgs.longName, interval: typedArgs.interval, threshold: typedArgs.threshold, enabled: typedArgs.enabled, action: typedArgs.action_type, }; result = await client.updateAlert(corpForAlerts, siteForAlerts, typedArgs.alertId, alertData); } else if (typedArgs.action === 'delete') { result = await client.deleteAlert(corpForAlerts, siteForAlerts, typedArgs.alertId); } break; case 'get_analytics': const { corpName: corpForAnalytics, siteName: siteForAnalytics } = resolveContext(typedArgs); if (!siteForAnalytics) { throw new Error('Site name is required. Please set context or provide siteName parameter.'); } if (typedArgs.type === 'top_attacks') { result = await client.getTopAttacks(corpForAnalytics, siteForAnalytics, typedArgs.from, typedArgs.until, typedArgs.groupBy, typedArgs.limit); } else if (typedArgs.type === 'timeseries') { result = await client.getTimeseriesRequests(corpForAnalytics, siteForAnalytics, typedArgs.from, typedArgs.until, typedArgs.tags, typedArgs.rollup); } break; case 'manage_cloudwaf': const { corpName: corpForCloudWAF } = resolveContext(typedArgs); if (typedArgs.action === 'list') { result = await client.listCloudWAFInstances(corpForCloudWAF); } else if (typedArgs.action === 'create') { const instanceData = { name: typedArgs.name, description: typedArgs.description, region: typedArgs.region, tlsMinVersion: typedArgs.tlsMinVersion, workspaceConfigs: [{ siteName: typedArgs.siteName, instanceLocation: "direct", listenerProtocols: ["https"], routes: [{ domains: typedArgs.domains, origin: typedArgs.origin, passHostHeader: false, connectionPooling: true, trustProxyHeaders: false, }], }], }; result = await client.createCloudWAFInstance(corpForCloudWAF, instanceData); } else if (typedArgs.action === 'get') { result = await client.getCloudWAFInstance(corpForCloudWAF, typedArgs.deploymentId); } else if (typedArgs.action === 'update') { const instanceData = { name: typedArgs.name, description: typedArgs.description, region: typedArgs.region, tlsMinVersion: typedArgs.tlsMinVersion, }; result = await client.updateCloudWAFInstance(corpForCloudWAF, typedArgs.deploymentId, instanceData); } else if (typedArgs.action === 'delete') { result = await client.deleteCloudWAFInstance(corpForCloudWAF, typedArgs.deploymentId); } break; case 'manage_users': const { corpName: corpForUsers } = resolveContext(typedArgs); if (typedArgs.action === 'list') { result = await client.listCorpUsers(corpForUsers); } else if (typedArgs.action === 'get') { result = await client.getCorpUser(corpForUsers, typedArgs.userEmail); } else if (typedArgs.action === 'update') { const userData = { role: typedArgs.role, memberships: typedArgs.memberships, }; result = await client.updateCorpUser(corpForUsers, typedArgs.userEmail, userData); } else if (typedArgs.action === 'invite') { const userData = { role: typedArgs.role, memberships: typedArgs.memberships, }; result = await client.inviteCorpUser(corpForUsers, typedArgs.userEmail, userData); } else if (typedArgs.action === 'delete') { result = await client.deleteCorpUser(corpForUsers, typedArgs.userEmail); } break; default: throw new Error(`Unknown tool: ${name}`); } return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error: ${error.message}`, }, ], isError: true, }; } }); async function main() { // Initialize from environment variables await initializeFromEnv(); const transport = new StdioServerTransport(); await server.connect(transport); console.error('Fastly NGWAF MCP Server running on stdio'); } main().catch((error) => { console.error('Server error:', error); process.exit(1); });

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/purpleax/FastlyMCP'

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