index.ts•7.61 kB
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
Tool
} from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
import { DnsResolver } from './dns-resolver.js';
import {
DnsLookupSchema,
ReverseDnsSchema,
BatchDnsSchema,
DnsTraceSchema,
DnsLookupInput,
ReverseDnsInput,
BatchDnsInput,
DnsTraceInput
} from './tools/schemas.js';
import { DnsRecordType } from './types/dns.js';
import { ConfigManager } from './utils/config.js';
import { Logger } from './utils/logger.js';
const configManager = new ConfigManager();
const logger = new Logger(
configManager.getLoggingConfig()?.level || 'info',
configManager.getLoggingConfig()?.file
);
const server = new Server(
{
name: 'dns-mcp-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
const dnsResolver = new DnsResolver(configManager.getDnsConfig());
const TOOLS: Tool[] = [
{
name: 'dns_lookup',
description: 'Perform DNS lookup for a domain to retrieve various record types (A, AAAA, MX, TXT, etc.)',
inputSchema: {
type: 'object',
properties: {
domain: {
type: 'string',
description: 'The domain name to lookup'
},
recordType: {
type: 'string',
enum: ['A', 'AAAA', 'CNAME', 'MX', 'TXT', 'NS', 'SOA', 'PTR', 'SRV', 'CAA'],
default: 'A',
description: 'The type of DNS record to query'
},
useCustomServer: {
type: 'boolean',
description: 'Use custom DNS server if configured'
},
timeout: {
type: 'number',
description: 'Query timeout in milliseconds'
}
},
required: ['domain']
}
},
{
name: 'reverse_dns',
description: 'Perform reverse DNS lookup to find the hostname for an IP address',
inputSchema: {
type: 'object',
properties: {
ipAddress: {
type: 'string',
description: 'The IP address to perform reverse lookup on'
},
timeout: {
type: 'number',
description: 'Query timeout in milliseconds'
}
},
required: ['ipAddress']
}
},
{
name: 'batch_dns',
description: 'Perform multiple DNS lookups in a single operation',
inputSchema: {
type: 'object',
properties: {
queries: {
type: 'array',
items: {
type: 'object',
properties: {
domain: {
type: 'string'
},
recordTypes: {
type: 'array',
items: {
type: 'string',
enum: ['A', 'AAAA', 'CNAME', 'MX', 'TXT', 'NS', 'SOA', 'PTR', 'SRV', 'CAA']
}
}
},
required: ['domain', 'recordTypes']
},
description: 'Array of DNS queries to perform'
},
parallel: {
type: 'boolean',
default: true,
description: 'Execute queries in parallel'
},
timeout: {
type: 'number',
description: 'Query timeout per request'
}
},
required: ['queries']
}
},
{
name: 'dns_trace',
description: 'Trace the DNS resolution path from root servers to the final result',
inputSchema: {
type: 'object',
properties: {
domain: {
type: 'string',
description: 'The domain to trace DNS resolution path'
},
recordType: {
type: 'string',
enum: ['A', 'AAAA', 'CNAME', 'MX', 'TXT', 'NS', 'SOA', 'PTR', 'SRV', 'CAA'],
default: 'A',
description: 'The record type to trace'
}
},
required: ['domain']
}
}
];
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: TOOLS
};
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
const { name, arguments: args } = request.params;
switch (name) {
case 'dns_lookup': {
const input = DnsLookupSchema.parse(args) as DnsLookupInput;
logger.info(`DNS lookup request for ${input.domain} (${input.recordType})`);
const result = await dnsResolver.lookup(
input.domain,
input.recordType as DnsRecordType,
input.timeout
);
logger.debug('DNS lookup result', result);
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2)
}
]
};
}
case 'reverse_dns': {
const input = ReverseDnsSchema.parse(args) as ReverseDnsInput;
logger.info(`Reverse DNS lookup for ${input.ipAddress}`);
const hostnames = await dnsResolver.reverseLookup(input.ipAddress);
logger.debug('Reverse DNS result', hostnames);
return {
content: [
{
type: 'text',
text: JSON.stringify({
ipAddress: input.ipAddress,
hostnames,
timestamp: new Date().toISOString()
}, null, 2)
}
]
};
}
case 'batch_dns': {
const input = BatchDnsSchema.parse(args) as BatchDnsInput;
const startTime = Date.now();
const { results, errors } = await dnsResolver.batchLookup(
input.queries.map(q => ({
domain: q.domain,
recordTypes: q.recordTypes as DnsRecordType[]
})),
input.parallel
);
return {
content: [
{
type: 'text',
text: JSON.stringify({
results,
errors,
totalTime: Date.now() - startTime,
timestamp: new Date().toISOString()
}, null, 2)
}
]
};
}
case 'dns_trace': {
const input = DnsTraceSchema.parse(args) as DnsTraceInput;
const trace = await dnsResolver.traceDns(
input.domain,
input.recordType as DnsRecordType
);
return {
content: [
{
type: 'text',
text: JSON.stringify({
domain: input.domain,
recordType: input.recordType,
trace,
timestamp: new Date().toISOString()
}, null, 2)
}
]
};
}
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error: any) {
if (error instanceof z.ZodError) {
logger.error('Validation error', error.errors);
return {
content: [
{
type: 'text',
text: `Validation error: ${JSON.stringify(error.errors, null, 2)}`
}
],
isError: true
};
}
logger.error('Tool execution error', error);
return {
content: [
{
type: 'text',
text: `Error: ${error.message || 'An unknown error occurred'}`
}
],
isError: true
};
}
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
logger.info('DNS MCP Server started successfully');
}
main().catch((error) => {
logger.error('Failed to start DNS MCP Server:', error);
process.exit(1);
});