DNS Server Architecture & MCP Management Specification
This report details the proposed architecture for a modern, Go‑based authoritative DNS server with SQLite‑backed zones and an embedded MCP (Model Context Protocol) control interface. It outlines core features, data model, MCP tool set, and component interactions.
---
Design Goals
- Simplicity: Single static binary, no external database server.
- Automation‑friendly: MCP tools allow AI agents to manage zones and records programmatically.
- Performance: Load zones into memory for fast query responses; handle hundreds of QPS on modest VPS.
- Security: Restrict MCP access to authorized Unix user or token; zone transfers optional (TSIG).
- Maintainability: Clear codebase using `miekg/dns` library; easy to extend.
---
Core Features
Authoritative DNS Serving
- Handles standard query types: A, AAAA, CNAME, MX, TXT, NS, PTR, SOA.
- Supports both IPv4 and IPv6.
- Zones are loaded from SQLite at startup; updates via MCP are written to SQLite and trigger an in‑memory reload.
- Implements basic RFC‑compliant DNS responses (no recursion, not a resolver).
SQLite‑Backed Zone Storage
- Two primary tables: `zones` and `records`.
- `zones`: zone name (origin), default TTL.
- `records`: zone_id, owner name, rtype, value, TTL.
- Supports multiple zones in one database.
- Atomic updates: MCP tools write within transactions; zone reload swaps entire in‑memory representation.
MCP Management Interface
- MCP server runs in stdio mode (subprocess communication).
- Exposes tools for zone and record management, plus statistics.
- Tools are idempotent where appropriate and return structured results.
---
Data Model (SQLite)
sql
-- Zones
CREATE TABLE zones (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL UNIQUE, -- e.g., "example.com."
ttl INTEGER DEFAULT 3600
);
-- Records
CREATE TABLE records (
id INTEGER PRIMARY KEY,
zone_id INTEGER NOT NULL,
name TEXT NOT NULL, -- relative to zone origin (e.g., "www" for www.example.com)
rtype TEXT NOT NULL, -- "A", "AAAA", "CNAME", "MX", "TXT", "NS"
value TEXT NOT NULL, -- formatted string; for MX: "10 mail.example.com."
ttl INTEGER,
FOREIGN KEY(zone_id) REFERENCES zones(id) ON DELETE CASCADE
);
Indexes:
- `CREATE INDEX idx_records_zone_name ON records(zone_id, name);`
- `CREATE INDEX idx_zones_name ON zones(name);`
---
In‑Memory Representation
On startup (and after each MCP‑triggered reload), the server:
1. Reads all zones from SQLite.
2. For each zone, builds an in‑memory map:
- `zoneOrigin → zoneData`
- `zoneData` contains:
- `zoneRecords`: map[recordKey][]record, where `recordKey` = normalized owner name + type.
- `defaultTTL`: from zone or 3600.
Reload strategy: To avoid downtime during updates, MCP writes to SQLite first, then sends a `SIGHUP` or calls an internal `Reload()` function that atomically swaps the new zone map after a successful read. Queries continue using the old map until swap; after swap, new queries use fresh data.
---
MCP Tool Specification
All tools follow JSON‑RPC 2.0 over stdio.
Zone Management
- `dns.add_zone(name: string, ttl?: int) → {zone_id: int}`
- Creates a new zone. Fails if zone exists.
- `dns.delete_zone(name: string) → {deleted: bool}`
- Deletes zone and all its records.
- `dns.list_zones() → [{id, name, ttl}]`
Record Management
- `dns.add_record(zone_name: string, name: string, rtype: string, value: string, ttl?: int) → {record_id: int}`
- Adds a record to the zone. `name` is relative to zone origin (or absolute if ends with zone name? We'll normalize). For MX, `value` format: `"pref host"` (e.g., `"10 mail.example.com."`).
- `dns.delete_record(zone_name: string, name: string, rtype: string, value?: string) → {deleted: bool}`
- Deletes matching records. If `value` omitted, deletes all matching `(zone, name, type)`.
- `dns.update_record(record_id: int, new_value?: string, new_ttl?: int) → {updated: bool}`
- Update value and/or TTL for a specific record (by primary key).
- `dns.list_records(zone_name: string, filter?: {name?, rtype?}) → [{id, name, rtype, value, ttl}]`
Zone Reload & Stats
- `dns.reload_zone(zone_name: string) → {reloaded: bool}`
- Forces reload of a single zone from SQLite (or all if omitted). Normally MCP tools auto‑reload; this is manual.
- `dns.get_stats() → {uptime: int, queries_total: int, zones_count: int, records_count: int, errors_total: int}`
- Simple runtime metrics.
Utility
- `dns.check_zone(zone_name: string) → {valid: bool, errors: [string]}`
- Perform basic sanity checks (SOA presence, duplicate names, TTL positivity).
---
Component Diagram
+---------------------+ +-------------------+
| MCP Client (e.g., | ----> | MCP Server | (stdio)
| ChatGPT, CLI) | <---- | (goroutine) |
+---------------------+ +-------------------+
|
v
+---------------------+ +-------------------+
| DNS Server | <---- | Zone Manager | (in‑memory cache +
| (miekg/dns) | | + SQLite writer | SQLite persistence)
+---------------------+ +-------------------+
The DNS server listens on UDP/TCP (port 53). The MCP server runs as a separate goroutine attached to the same binary, communicating over stdin/stdout. It does not expose network endpoints by default (to avoid attack surface). The MCP client spawns the binary with a flag (e.g., `--mcp`) and then talks JSON‑RPC.
---
Security Considerations
- MCP access control: Since MCP uses stdio, only processes that can start the binary can manage DNS. We can add an additional layer: require a secret token passed via environment variable; MCP tools reject calls without correct token. Or rely on OS‑level restrictions (binary owned by `root`, setuid, group `dnsmgr`).
- Zone data sensitivity: Record values may include internal IPs or service discovery info. The MCP interface should only be used by trusted automation. Logging of MCP requests should avoid printing full values in clear text if logs are accessible.
- SQLite file permissions: `/var/db/dns-server.sqlite` should be owned by `dnsmgr` user/group, mode `0660`.
- DNS amplification: As an authoritative server, we must ensure we don’t allow recursion or open resolver behavior. Default is recursive=no, which is safe.
---
Deployment Model
- Run as a systemd service under a dedicated user `dnsmgr`.
- Binary path: `/usr/local/bin/dns-server`.
- Config: SQLite database at `/var/db/dns-server.sqlite`.
- Ports: UDP/TCP 53 (requires binding to low port → needs root or `CAP_NET_BIND_SERVICE`). We can run as root but drop privileges after binding, or use a systemd socket activation.
- Logging to stdout/stderr, captured by systemd journal.
---
Example Workflow
1. Start server: `systemctl start dns-server`.
2. MCP client adds a zone:
json
{"jsonrpc":"2.0","method":"dns.add_zone","params":{"name":"example.com.","ttl":3600},"id":1}
3. MCP client adds an A record:
json
{"jsonrpc":"2.0","method":"dns.add_record","params":{"zone_name":"example.com.","name":"www","rtype":"A","value":"1.2.3.4","ttl":300},"id":2}
4. Server automatically reloads zone; queries to `www.example.com` now return `1.2.3.4`.
5. Change record: `dns.update_record(record_id=1, new_value="5.6.7.8")`.
6. List zones: `dns.list_zones()`.
All changes are persisted to SQLite and take effect immediately.
---
Advantages of This Approach
- Single binary: No separate DB server, no external dependencies beyond Go stdlib.
- Fast queries: In‑memory lookup, sub‑microsecond response.
- Easy automation: MCP tools allow any MCP‑compatible agent (ChatGPT, Claude, custom scripts) to manage DNS.
- Portable: Static binary can be cross‑compiled; runs on any Linux.
- Extensible: Adding new MCP tools or DNS features is straightforward in Go.
---
Open Questions & Future Work
- DNSSEC: Not needed for MVP but could be added later using `miekg/dns` support.
- Zone transfers (AXFR/IXFR): Useful for secondary DNS; can be implemented if needed.
- TSIG: For signed updates; could protect MCP tools as alternative to Unix user isolation.
- Dynamic DNS: Already covered via MCP; could also implement RFC 2136 dynamic update protocol if required.
- Metrics export: Could add Prometheus endpoint in addition to MCP stats.
---
Conclusion: The custom Go DNS server with SQLite and MCP meets all requirements precisely. It provides a clean, automation‑first design that fits the VPS environment and can be built within a few weeks. The next report will break down the implementation tasks and effort estimate.
---
Word count: ~1,050