@thelord/mcp-cloudflare
Version:
A Model Context Protocol server implementation for Cloudflare DNS that enables AI agents to manage DNS records for your domains
342 lines (341 loc) ⢠14 kB
JavaScript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
import { CloudflareApi } from "./api.js";
import { DnsRecordType } from "./types.js";
// Configuration schema for Smithery
export const configSchema = z.object({
cloudflareApiToken: z.string().describe("Your Cloudflare API Token with Zone:Edit permissions"),
cloudflareZoneId: z.string().describe("The Zone ID of your domain in Cloudflare"),
cloudflareEmail: z.string().optional().describe("Your Cloudflare account email (only needed for legacy API keys)")
});
// Export default function for Smithery
export default function createServer({ config }) {
const server = new Server({
name: "mcp-cloudflare",
version: "1.0.0",
}, {
capabilities: {
tools: {},
},
});
// Helper function to configure API only when needed
const configureApiIfNeeded = () => {
try {
if (config?.cloudflareApiToken && config?.cloudflareZoneId) {
CloudflareApi.configure(config);
return true;
}
return false;
}
catch (error) {
console.error('Error configuring Cloudflare API:', error);
return false;
}
};
// Register available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "list_dns_records",
description: "List all DNS records for the configured zone",
inputSchema: {
type: "object",
properties: {
name: {
type: "string",
description: "Filter by record name (optional)",
},
type: {
type: "string",
enum: ["A", "AAAA", "CNAME", "MX", "TXT", "NS", "SRV", "CAA", "PTR"],
description: "Filter by record type (optional)",
},
},
},
},
{
name: "get_dns_record",
description: "Get a specific DNS record by ID",
inputSchema: {
type: "object",
properties: {
recordId: {
type: "string",
description: "The DNS record ID",
},
},
required: ["recordId"],
},
},
{
name: "create_dns_record",
description: "Create a new DNS record",
inputSchema: {
type: "object",
properties: {
type: {
type: "string",
enum: ["A", "AAAA", "CNAME", "MX", "TXT", "NS", "SRV", "CAA", "PTR"],
description: "DNS record type",
},
name: {
type: "string",
description: "DNS record name",
},
content: {
type: "string",
description: "DNS record content",
},
ttl: {
type: "number",
description: "Time to live (TTL) in seconds (default: 1 for auto)",
minimum: 1,
},
priority: {
type: "number",
description: "Priority (for MX records)",
},
proxied: {
type: "boolean",
description: "Whether the record should be proxied through Cloudflare",
},
},
required: ["type", "name", "content"],
},
},
{
name: "update_dns_record",
description: "Update an existing DNS record",
inputSchema: {
type: "object",
properties: {
recordId: {
type: "string",
description: "The DNS record ID to update",
},
type: {
type: "string",
enum: ["A", "AAAA", "CNAME", "MX", "TXT", "NS", "SRV", "CAA", "PTR"],
description: "DNS record type",
},
name: {
type: "string",
description: "DNS record name",
},
content: {
type: "string",
description: "DNS record content",
},
ttl: {
type: "number",
description: "Time to live (TTL) in seconds",
minimum: 1,
},
priority: {
type: "number",
description: "Priority (for MX records)",
},
proxied: {
type: "boolean",
description: "Whether the record should be proxied through Cloudflare",
},
},
required: ["recordId"],
},
},
{
name: "delete_dns_record",
description: "Delete a DNS record",
inputSchema: {
type: "object",
properties: {
recordId: {
type: "string",
description: "The DNS record ID to delete",
},
},
required: ["recordId"],
},
},
],
};
});
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === "list_dns_records") {
return await handleListDnsRecords(args);
}
if (name === "get_dns_record") {
return await handleGetDnsRecord(args);
}
if (name === "create_dns_record") {
return await handleCreateDnsRecord(args);
}
if (name === "update_dns_record") {
return await handleUpdateDnsRecord(args);
}
if (name === "delete_dns_record") {
return await handleDeleteDnsRecord(args);
}
throw new Error(`Unknown tool: ${name}`);
});
// Tool handlers
const handleListDnsRecords = async (args) => {
try {
if (!configureApiIfNeeded()) {
return {
content: [{ type: "text", text: "ā Configuration incomplete. Please configure Cloudflare API Token and Zone ID first." }],
};
}
const records = await CloudflareApi.findDnsRecords(args.name, args.type);
if (records.length === 0) {
return {
content: [{ type: "text", text: "No DNS records found matching the criteria." }],
};
}
const recordsText = records.map(record => `š¹ ${record.name} (${record.type}) ā ${record.content} [ID: ${record.id}]${record.proxied ? ' š Proxied' : ''}`).join('\n');
return {
content: [{
type: "text",
text: `ā
Found ${records.length} DNS record(s):\n\n${recordsText}`
}],
};
}
catch (error) {
return {
content: [{ type: "text", text: `ā Error listing DNS records: ${error instanceof Error ? error.message : 'Unknown error'}` }],
};
}
};
const handleGetDnsRecord = async (args) => {
try {
if (!configureApiIfNeeded()) {
return {
content: [{ type: "text", text: "ā Configuration incomplete. Please configure Cloudflare API Token and Zone ID first." }],
};
}
const record = await CloudflareApi.getDnsRecord(args.recordId);
return {
content: [{
type: "text",
text: `ā
DNS Record Details:
š¹ Name: ${record.name}
š¹ Type: ${record.type}
š¹ Content: ${record.content}
š¹ TTL: ${record.ttl}
š¹ Proxied: ${record.proxied ? 'Yes' : 'No'}
${record.priority ? `š¹ Priority: ${record.priority}` : ''}
š¹ ID: ${record.id}
š¹ Created: ${new Date(record.created_on).toLocaleString()}
š¹ Modified: ${new Date(record.modified_on).toLocaleString()}`
}],
};
}
catch (error) {
return {
content: [{ type: "text", text: `ā Error getting DNS record: ${error instanceof Error ? error.message : 'Unknown error'}` }],
};
}
};
const handleCreateDnsRecord = async (args) => {
try {
if (!configureApiIfNeeded()) {
return {
content: [{ type: "text", text: "ā Configuration incomplete. Please configure Cloudflare API Token and Zone ID first." }],
};
}
const recordData = {
type: DnsRecordType.parse(args.type),
name: args.name,
content: args.content,
};
if (args.ttl !== undefined)
recordData.ttl = args.ttl;
if (args.priority !== undefined)
recordData.priority = args.priority;
if (args.proxied !== undefined)
recordData.proxied = args.proxied;
const record = await CloudflareApi.createDnsRecord(recordData);
return {
content: [{
type: "text",
text: `ā
DNS record created successfully!
š¹ Name: ${record.name}
š¹ Type: ${record.type}
š¹ Content: ${record.content}
š¹ ID: ${record.id}
${record.proxied ? 'š Proxied through Cloudflare' : ''}`
}],
};
}
catch (error) {
return {
content: [{ type: "text", text: `ā Error creating DNS record: ${error instanceof Error ? error.message : 'Unknown error'}` }],
};
}
};
const handleUpdateDnsRecord = async (args) => {
try {
if (!configureApiIfNeeded()) {
return {
content: [{ type: "text", text: "ā Configuration incomplete. Please configure Cloudflare API Token and Zone ID first." }],
};
}
const updates = {};
if (args.type)
updates.type = DnsRecordType.parse(args.type);
if (args.name)
updates.name = args.name;
if (args.content)
updates.content = args.content;
if (args.ttl !== undefined)
updates.ttl = args.ttl;
if (args.priority !== undefined)
updates.priority = args.priority;
if (args.proxied !== undefined)
updates.proxied = args.proxied;
const record = await CloudflareApi.updateDnsRecord(args.recordId, updates);
return {
content: [{
type: "text",
text: `ā
DNS record updated successfully!
š¹ Name: ${record.name}
š¹ Type: ${record.type}
š¹ Content: ${record.content}
š¹ ID: ${record.id}
${record.proxied ? 'š Proxied through Cloudflare' : ''}`
}],
};
}
catch (error) {
return {
content: [{ type: "text", text: `ā Error updating DNS record: ${error instanceof Error ? error.message : 'Unknown error'}` }],
};
}
};
const handleDeleteDnsRecord = async (args) => {
try {
if (!configureApiIfNeeded()) {
return {
content: [{ type: "text", text: "ā Configuration incomplete. Please configure Cloudflare API Token and Zone ID first." }],
};
}
await CloudflareApi.deleteDnsRecord(args.recordId);
return {
content: [{
type: "text",
text: `ā
DNS record deleted successfully! (ID: ${args.recordId})`
}],
};
}
catch (error) {
return {
content: [{ type: "text", text: `ā Error deleting DNS record: ${error instanceof Error ? error.message : 'Unknown error'}` }],
};
}
};
return server;
}