@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
JavaScript
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'}`);
        }
    },
};