Windows DNS Policy Manager
A browser-based GUI for creating and managing Windows Server DNS Policies. Built with Next.js, TypeScript, and shadcn/ui. Connects to a live DNS server via the included PowerShell bridge for real-time policy management. Includes an MCP server for AI agent integration (Claude Code, Cursor, VS Code).
Features
- 12-tab interface: Server, Objects, Zones, Policies, Create, Blocklists, Wizards, DNSSEC, DNS over HTTPS, Resolvers, Backup, PowerShell
- Multi-server management with Kerberos, DPAPI-saved, or session-based credentials and header server switcher for quick switching from any tab
- Server configuration dashboard with inline editing for 13 config panels (settings, forwarders, recursion, cache, blocklist, diagnostics, statistics, RRL, scavenging, root hints, EDNS, AD settings, global name zone)
- DNS Best Practices Analyzer — run Windows BPA from the UI with severity-coded findings
- Zone browser with two-panel layout, settings editor, full record CRUD, CSV import/export, pagination, and filters for Forward/Reverse, zone type (Primary/Secondary/Stub/Forwarder), and AD-integrated
- 8 scenario wizards with typed execution (geo-location, split-brain, time-of-day, load balancing, query filters, blocklist, geo+LB combo, primary-secondary)
- Blocklists tab — Quick Block (single domain), Bulk Import (.txt), Active Block Policies table, and Global Query Block List management
- Resolvers tab — IP stack DNS per adapter (IPv4/IPv6), forwarder configuration, Mermaid topology diagram with color-coded edges
- DNSSEC management — zone signing, signing key CRUD, trust anchors, trust points
- DNS over HTTPS (DoH) — dedicated tab to enable/disable inbound DoH and manage up to 3 URI templates live on Windows Server 2025+ (KB5094125), plus an offline generator for the full prerequisite setup script (certificate import,
netshSSL binding, firewall rule,Set-DnsServerEncryptionProtocol, and DNS service restart) - Policy CRUD with enable/disable toggle, processing order editor, and cross-server copy
- DNS object management for client subnets, zone scopes, and recursion scopes
- Zone lifecycle — create, delete, convert, suspend, resume, export zones
- PowerShell command generation with copy-to-clipboard — works offline as a command generator
- Backup & export — policy JSON backup/restore, server configuration export (Get-DnsServer as JSON), DNS zone export (single or all primary zones via Export-DnsServerZone), AD-integrated backup info
- DNS Lookup utility — slide-over panel with live nslookup and dig queries from any tab. Tool-specific options (+trace, +dnssec, +short, +all, +multiline, debug), custom or registered nameserver targeting, and console-style session output with copy/clear
- Context-sensitive help with slide-over panel and full-page popout
- 21 Playwright E2E tests with mock bridge, integrated into CI
- MCP server — 31 read-only tools for AI agents via Model Context Protocol (zones, records, policies, server config, DNSSEC, RRL, scavenging, and more), plus offline PowerShell command generation
- Docker-ready with multi-stage Alpine image (221 MB)
- Keyboard-accessible tab navigation and ARIA attributes
Running Locally
Requirements
- Node.js 20.19+ or 22.12+ (for the frontend and test tooling; CI builds on Node 22)
- Windows Server with the DNS Server role, or a machine with the
DnsServerPowerShell module - The bridge binds only to
127.0.0.1by default — never exposed to the network
One-command launch
powershell -ExecutionPolicy Bypass -File Start-DNSPolicyManager.ps1
This starts both the PowerShell bridge (port 8650) and the Next.js dev server (port 10010), then opens your browser. Frontend dependencies are installed automatically on first run.
Manual startup
# Terminal 1: Start the bridge
powershell -ExecutionPolicy Bypass -File server/bridge.ps1
# Terminal 2: Start the frontend
cd dns-manager
npm install # first time only
npm run dev
Then open http://localhost:10010.
Offline mode
If the bridge isn't running, the app falls back to command generation only — build policies visually and copy the generated PowerShell commands.
MCP Server (AI Agent Integration)
The MCP server lets AI agents query your DNS servers through 31 read-only tools via the Model Context Protocol. It connects to the same PowerShell bridge used by the web UI.
Setup
cd mcp-server
npm install
npm run build
Or use the launcher:
powershell -ExecutionPolicy Bypass -File Start-DNSPolicyManager.ps1 -MCP
Register with Claude Code
claude mcp add dns-policy-manager -- node /path/to/mcp-server/dist/index.js
With environment variables for a specific server:
claude mcp add dns-policy-manager \
-e BRIDGE_URL=http://127.0.0.1:8650 \
-e DNS_DEFAULT_SERVER=dc01.contoso.com \
-e DNS_CREDENTIAL_MODE=currentUser \
-- node /path/to/mcp-server/dist/index.js
Configuration
All configuration is via environment variables:
| Variable | Default | Description |
|---|---|---|
BRIDGE_URL |
http://127.0.0.1:8650 |
PowerShell bridge URL |
DNS_DEFAULT_SERVER |
(none) | Default DNS server hostname |
DNS_SERVER_ID |
(none) | Server ID for credential lookup |
DNS_CREDENTIAL_MODE |
currentUser |
Auth mode: currentUser or savedCredential |
Available Tools (31)
| Category | Tools |
|---|---|
| Zones | dns_list_zones, dns_get_zone_details, dns_get_zone_records, dns_get_zone_aging, dns_get_zone_delegations |
| Policies | dns_list_policies, dns_list_transfer_policies |
| Server Config | dns_get_server_settings, dns_get_forwarders, dns_get_cache_settings, dns_get_recursion_settings, dns_get_blocklist, dns_get_diagnostics, dns_get_statistics, dns_get_rrl, dns_get_rrl_exceptions, dns_get_scavenging, dns_get_root_hints, dns_get_edns, dns_get_encryption, dns_get_global_name_zone, dns_export_server_config |
| DNS Objects | dns_list_subnets, dns_list_zone_scopes, dns_list_recursion_scopes |
| DNSSEC | dns_get_dnssec_settings, dns_get_signing_keys, dns_get_trust_anchors, dns_get_trust_points |
| Offline | dns_generate_policy_commands (works without bridge) |
| Health | dns_check_health |
Authentication
The MCP server supports two credential modes:
currentUser(default) — uses Kerberos/NTLM from the logged-in user. No setup needed.savedCredential— uses DPAPI-encrypted credentials stored via the web UI. SetDNS_SERVER_IDto the server ID configured in the GUI.
Session credentials (username/password) are intentionally not supported in the MCP server — AI agents should not handle raw passwords.
Security
- All tools are read-only — no write operations on DNS servers
- The bridge's
/api/executeendpoint (arbitrary PowerShell) is not exposed through MCP - Generated PowerShell commands (offline command generator, both the web UI and the MCP
dns_generate_policy_commandstool) escape user input against injection ($,`,") before interpolation - The bridge must be running for live queries; the offline command generator works independently
Running with Docker
The frontend runs in a standard Linux container. The bridge requires Windows PowerShell with the DnsServer module, so it runs on the host machine (or a Windows container).
Frontend in Docker, bridge on host
This is the recommended Docker setup. The frontend container proxies API calls to the bridge running on your Windows machine.
Step 1 — Start the bridge on the host:
powershell -ExecutionPolicy Bypass -File server/bridge.ps1
Step 2 — Start the frontend container:
docker compose up -d
Or build and run manually:
docker build -t dns-manager ./dns-manager
docker run -d -p 10010:10010 dns-manager
Open http://localhost:10010.
Custom bridge address
By default, the Docker image connects to the bridge at http://host.docker.internal:8650 (Docker Desktop's host gateway). To point at a different bridge:
# At build time (baked into the image)
docker build --build-arg BRIDGE_URL=http://192.168.1.50:8650 -t dns-manager ./dns-manager
# Or override in docker-compose.yml
services:
frontend:
build:
args:
BRIDGE_URL: http://192.168.1.50:8650
Bridge on a remote host or separate network
If the bridge runs on a different machine than Docker, use -BindAddress to make it listen on all interfaces:
powershell -ExecutionPolicy Bypass -File server/bridge.ps1 -BindAddress 0.0.0.0
Security note:
-BindAddress 0.0.0.0exposes the bridge to the network. Only use this on trusted networks or behind a firewall. The default127.0.0.1restricts access to localhost.
Docker Compose reference
docker compose up -d # Start frontend (bridge on host)
docker compose down # Stop
docker compose up -d --build # Rebuild after code changes
docker compose logs -f frontend # View logs
Architecture
Browser (:10010) PowerShell Bridge (:8650)
───────────────── ──────────────────────────
Next.js frontend ──HTTP──► [System.Net.HttpListener]
│
AI Agent (stdio) DNS cmdlets (splatted)
──────────────── Get/Add/Remove/Set-DnsServer*
MCP Server ──HTTP──► │
31 read-only tools Returns JSON
Frontend (dns-manager/):
- Next.js App Router with TypeScript and Tailwind CSS v4
- shadcn/ui components (Radix primitives)
- Zustand store with localStorage persistence
- API client with direct bridge calls (bypasses Next.js proxy)
- Standalone output for production Docker images
Bridge (server/bridge.ps1):
- PowerShell
HttpListenerwith regex-based routing and runspace pool for concurrent request handling - Splatted parameters for all DNS cmdlet calls (prevents injection)
- Three credential modes: Kerberos, DPAPI-encrypted, session — with CIM sessions for credential-based remote server access
- Background jobs for long-running operations (BPA, Resolvers)
- Path traversal protection on serverId and fileName parameters
- Graceful degradation — frontend works offline when bridge is unavailable
API Endpoints (Bridge)
| Method | Path | Purpose |
|---|---|---|
| GET | /api/health |
Ping + DNS module check |
| POST | /api/connect |
Test DNS server, list zones |
| GET | /api/zones |
List DNS zones |
| GET | /api/zones/{name} |
Zone details |
| GET | /api/zones/{name}/records |
List zone records |
| POST | /api/zones/{name}/records |
Add record |
| PUT | /api/zones/{name}/records |
Update record |
| DELETE | /api/zones/{name}/records |
Delete record |
| PUT | /api/zones/{name}/settings |
Edit zone settings |
| GET | /api/policies |
List policies |
| POST | /api/policies |
Create policy |
| DELETE | /api/policies/{name} |
Remove policy |
| PUT | /api/policies/{name}/state |
Enable/disable policy |
| POST | /api/policies/multi |
Create on multiple servers |
| POST | /api/policies/copy |
Copy policies between servers |
| GET | /api/transferpolicies |
List zone transfer policies |
| POST | /api/transferpolicies |
Create zone transfer policy |
| DELETE | /api/transferpolicies/{name} |
Remove zone transfer policy |
| GET | /api/subnets |
List client subnets |
| POST | /api/subnets |
Create client subnet |
| DELETE | /api/subnets/{name} |
Delete client subnet |
| GET | /api/zonescopes |
List zone scopes |
| POST | /api/zonescopes |
Create zone scope |
| DELETE | /api/zonescopes/{name} |
Delete zone scope |
| POST | /api/zonescopes/records |
Add record to zone scope |
| GET | /api/recursionscopes |
List recursion scopes |
| POST | /api/recursionscopes |
Create recursion scope |
| PUT | /api/recursionscopes/{name} |
Update recursion scope |
| DELETE | /api/recursionscopes/{name} |
Delete recursion scope |
| POST | /api/credentials/store |
Store DPAPI credential |
| POST | /api/credentials/session |
Store session credential |
| GET | /api/credentials/check |
Check credential exists |
| DELETE | /api/credentials/{id} |
Delete credential |
| POST | /api/backup |
Export policies as JSON |
| POST | /api/server/resolvers |
Start DNS client resolver discovery job |
| GET | /api/server/resolvers |
Poll resolver discovery job result |
| GET | /api/export/serverconfig |
Export full server config as JSON |
| POST | /api/export/allzones |
Export all primary zones |
| POST | /api/execute |
Run allowlisted DNS cmdlet |
Project Structure
Start-DNSPolicyManager.ps1 Launcher: starts bridge + frontend (+ MCP with -MCP flag)
docker-compose.yml Docker Compose for containerized deployment
server/
bridge.ps1 PowerShell HTTP bridge (localhost:8650)
start.bat Double-click launcher for bridge only
mcp-server/ MCP server for AI agents
src/
index.ts Entry point, stdio transport
bridge-client.ts HTTP client for bridge REST API
tools/
shared.ts Shared Zod schemas (ServerParamsSchema)
zones.ts Zone query tools (5)
policies.ts Policy query tools (2)
server-config.ts Server config query tools (15)
objects.ts DNS object query tools (3)
security.ts DNSSEC query tools (4)
command-gen.ts Offline PowerShell command generation (1)
dns-manager/ Next.js frontend
Dockerfile Multi-stage Alpine build (standalone output)
src/
app/ App Router pages (12 tabs + help)
server/page.tsx Server management + configuration dashboard
objects/page.tsx DNS Objects (subnets, scopes)
zones/page.tsx Zone browser + record CRUD + lifecycle + filters
policies/page.tsx Policy list
create/page.tsx Create Policy form
blocklists/page.tsx Blocklists (quick block, bulk import, GQBL)
wizards/page.tsx Scenario wizards (8 scenarios)
dnssec/page.tsx DNSSEC management
doh/page.tsx DNS over HTTPS (inbound DoH config + setup-script generator)
resolvers/page.tsx Resolvers (adapter DNS, forwarders, topology)
backup/page.tsx Backup & Import + zone/config export
powershell/page.tsx PowerShell output
help/[slug]/page.tsx Help documentation viewer
components/
app-shell.tsx Layout shell, header, bridge status
tab-nav.tsx Tab navigation
help-panel.tsx Slide-over help panel
ui/ shadcn/ui components
lib/
api.ts Typed API client (~80 methods)
store.ts Zustand store
types.ts TypeScript interfaces
help-mapping.ts Route-to-help-doc mapping
wizards/
scenarios.ts Scenario definitions
command-generator.ts PowerShell command generation
public/help/ Help documentation (Markdown)
docs/help/ Source help documentation
DnsServer Module Coverage
The app wraps ~98 of ~120 cmdlets in the DnsServer PowerShell module with dedicated UI. The remaining cmdlets are accessible through the PowerShell tab via the execute endpoint, which allows any DnsServer cmdlet.
Cmdlets without dedicated UI (reachable via execute):
| Group | Cmdlets | Reason |
|---|---|---|
| Type-specific record creation | Add-DnsServerResourceRecordA, ...AAAA, ...CName, ...MX, ...Ptr, ...DnsKey, ...DS |
Already handled by the generic Add-DnsServerResourceRecord |
| Record aging | Set-DnsServerResourceRecordAging |
Bulk operation, run via execute |
| Key rollover | Invoke-DnsServerSigningKeyRollover, Step-DnsServerSigningKeyRollover |
One-off DNSSEC operations |
| Zone restore | Restore-DnsServerPrimaryZone, Restore-DnsServerSecondaryZone |
Rarely used, forces reload from AD/file |
| Zone sync | Sync-DnsServerZone |
Flushes memory to storage |
| Imports | Import-DnsServerRootHint, Import-DnsServerResourceRecordDS, Import-DnsServerTrustAnchor |
File-based import operations |
| Key storage | Show-DnsServerKeyStorageProvider |
Read-only diagnostic |
| Directory partitions | Get/Add/Remove/Register/Unregister-DnsServerDirectoryPartition |
AD infrastructure, rarely changed |
| Virtualization | Get/Add/Set/Remove-DnsServerVirtualizationInstance |
Server 2016+ niche feature |
Browser Support
Chrome, Firefox, and Edge (current versions).
Port Reference
| Service | Default Port | Override |
|---|---|---|
| Frontend (dev) | 10010 | Edit package.json dev script |
| Frontend (Docker) | 10010 | Change in docker-compose.yml ports |
| Bridge | 8650 | -Port parameter on bridge.ps1 |
Windows Hyper-V/Docker dynamically reserves port ranges (check netsh int ipv4 show excludedportrange) that can conflict with port 8650. If the bridge fails to bind, use -Port to specify an open one.
No comments yet
Be the first to share your take.