UNPKG

@thelord/mcp-cloudflare

Version:

A Model Context Protocol server implementation for Cloudflare DNS that enables AI agents to manage DNS records for your domains

174 lines (173 loc) 7 kB
import { z } from "zod"; import { CloudflareApiResponse, CreateDnsRecordRequest, UpdateDnsRecordRequest } from "./types.js"; // Configuration for Cloudflare API let cloudflareConfig = { apiToken: '', zoneId: '', email: '' }; // Configure API with parameters from Smithery const configure = (config) => { cloudflareConfig.apiToken = config.cloudflareApiToken; cloudflareConfig.zoneId = config.cloudflareZoneId; cloudflareConfig.email = config.cloudflareEmail; }; // Fallback for local development with environment variables const parseEnv = () => { const parsed = z .object({ CLOUDFLARE_API_TOKEN: z.string().optional(), CLOUDFLARE_ZONE_ID: z.string().optional(), CLOUDFLARE_EMAIL: z.string().optional(), }) .safeParse(process.env); if (parsed.success && parsed.data.CLOUDFLARE_API_TOKEN && parsed.data.CLOUDFLARE_ZONE_ID) { cloudflareConfig.apiToken = parsed.data.CLOUDFLARE_API_TOKEN; cloudflareConfig.zoneId = parsed.data.CLOUDFLARE_ZONE_ID; cloudflareConfig.email = parsed.data.CLOUDFLARE_EMAIL; } }; // Initialize with environment variables if available parseEnv(); const getHeaders = () => { if (!cloudflareConfig.apiToken) { throw new Error("Cloudflare API Token not configured"); } return { 'Authorization': `Bearer ${cloudflareConfig.apiToken}`, 'Content-Type': 'application/json', }; }; const api = async (endpoint, method = 'GET', body) => { if (!cloudflareConfig.zoneId) { throw new Error("Cloudflare Zone ID not configured"); } const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 15000); // 15 second timeout try { const url = `https://api.cloudflare.com/client/v4/zones/${cloudflareConfig.zoneId}/${endpoint}`; const response = await fetch(url, { method, headers: getHeaders(), body: body ? JSON.stringify(body) : undefined, signal: controller.signal, }); clearTimeout(timeoutId); if (!response.ok) { throw new Error(`Cloudflare API error: ${response.status} ${response.statusText}`); } return response; } catch (error) { clearTimeout(timeoutId); if (error instanceof Error) { if (error.name === 'AbortError') { throw new Error('Cloudflare API request timed out'); } throw new Error(`Cloudflare API error: ${error.message}`); } throw error; } }; export const CloudflareApi = { configure, // List all DNS records listDnsRecords: async () => { const response = await api('dns_records'); const rawData = await response.json(); try { const data = CloudflareApiResponse.parse(rawData); if (!data.success) { throw new Error(`API Error: ${data.errors.map(e => e.message).join(', ')}`); } if (!data.result) { return []; } // Handle both array and single object results const records = Array.isArray(data.result) ? data.result : [data.result]; return records.filter(record => record !== null); } catch (parseError) { console.error('API Response parsing failed:', parseError); console.error('Raw API Response:', JSON.stringify(rawData, null, 2)); throw new Error(`Failed to parse API response: ${parseError instanceof Error ? parseError.message : 'Unknown error'}`); } }, // Get a specific DNS record by ID getDnsRecord: async (recordId) => { const response = await api(`dns_records/${recordId}`); const data = CloudflareApiResponse.parse(await response.json()); if (!data.success) { throw new Error(`API Error: ${data.errors.map(e => e.message).join(', ')}`); } if (!data.result || Array.isArray(data.result)) { throw new Error('DNS record not found'); } return data.result; }, // Create a new DNS record createDnsRecord: async (record) => { const validatedRecord = CreateDnsRecordRequest.parse(record); const response = await api('dns_records', 'POST', validatedRecord); const data = CloudflareApiResponse.parse(await response.json()); if (!data.success) { throw new Error(`API Error: ${data.errors.map(e => e.message).join(', ')}`); } if (!data.result || Array.isArray(data.result)) { throw new Error('Failed to create DNS record'); } return data.result; }, // Update an existing DNS record updateDnsRecord: async (recordId, updates) => { const validatedUpdates = UpdateDnsRecordRequest.parse(updates); const response = await api(`dns_records/${recordId}`, 'PATCH', validatedUpdates); const data = CloudflareApiResponse.parse(await response.json()); if (!data.success) { throw new Error(`API Error: ${data.errors.map(e => e.message).join(', ')}`); } if (!data.result || Array.isArray(data.result)) { throw new Error('Failed to update DNS record'); } return data.result; }, // Delete a DNS record deleteDnsRecord: async (recordId) => { const response = await api(`dns_records/${recordId}`, 'DELETE'); const data = CloudflareApiResponse.parse(await response.json()); if (!data.success) { throw new Error(`API Error: ${data.errors.map(e => e.message).join(', ')}`); } }, // Find DNS records by name and/or type findDnsRecords: async (name, type) => { let endpoint = 'dns_records'; const params = new URLSearchParams(); if (name) params.append('name', name); if (type) params.append('type', type); if (params.toString()) { endpoint += `?${params.toString()}`; } const response = await api(endpoint); const rawData = await response.json(); try { const data = CloudflareApiResponse.parse(rawData); if (!data.success) { throw new Error(`API Error: ${data.errors.map(e => e.message).join(', ')}`); } if (!data.result) { return []; } // Handle both array and single object results const records = Array.isArray(data.result) ? data.result : [data.result]; return records.filter(record => record !== null); } catch (parseError) { console.error('API Response parsing failed:', parseError); console.error('Raw API Response:', JSON.stringify(rawData, null, 2)); throw new Error(`Failed to parse API response: ${parseError instanceof Error ? parseError.message : 'Unknown error'}`); } }, };