import * as plugins from '../plugins.js'; import type { OpsServer } from './classes.opsserver.js'; import type * as interfaces from '../../ts_interfaces/index.js'; type TRouteTarget = NonNullable[number]; type TRouteTargetValue = TRouteTarget['host'] | TRouteTarget['port']; interface IDnsRecordSummary { id: string; domainId: string; domainName?: string; name: string; type: interfaces.data.TDnsRecordType; value: string; ttl: number; proxied?: boolean; source: interfaces.data.TDnsRecordSource; createdAt: number; updatedAt: number; } export class McpManager { private handler: plugins.smartmcp.ISmartMcpHttpHandler; constructor(private opsServerRef: OpsServer) { this.handler = plugins.smartmcp.createSmartMcpHttpHandler({ name: 'dcrouter-mcp', version: '1.0.0', validateRequest: ({ request }) => this.validateRequest(request), authorize: async ({ request }) => this.authorizeRequest(request), tools: [ { name: 'dcrouter_get_status', description: 'Get dcrouter runtime inventory counts without logs, tokens, certificates, or credentials.', handler: async () => ({ status: await this.getStatusSummary(), }), }, { name: 'dcrouter_list_routes', description: 'List dcrouter route summaries without raw route secrets, logs, or certificates.', handler: async () => ({ routes: this.getMergedRoutes().map((routeArg) => this.summarizeRoute(routeArg)), }), }, { name: 'dcrouter_list_source_profiles', description: 'List source profile summaries without embedded credentials.', handler: async () => ({ sourceProfiles: (this.opsServerRef.dcRouterRef.referenceResolver?.listProfiles() || []).map((profileArg) => this.summarizeSourceProfile(profileArg)), }), }, { name: 'dcrouter_list_target_profiles', description: 'List target profile summaries for VPN and private access routing.', handler: async () => ({ targetProfiles: (this.opsServerRef.dcRouterRef.targetProfileManager?.listProfiles() || []).map((profileArg) => this.summarizeTargetProfile(profileArg)), }), }, { name: 'dcrouter_list_network_targets', description: 'List reusable network target summaries.', handler: async () => ({ networkTargets: (this.opsServerRef.dcRouterRef.referenceResolver?.listTargets() || []).map((targetArg) => this.summarizeNetworkTarget(targetArg)), }), }, { name: 'dcrouter_list_domains', description: 'List managed DNS domains without provider zone IDs or provider credentials.', handler: async () => ({ domains: (await this.opsServerRef.dcRouterRef.dnsManager?.listDomains() || []).map((domainArg) => this.summarizeDomain(domainArg)), }), }, { name: 'dcrouter_list_dns_records', description: 'List managed DNS record summaries without provider record IDs or provider credentials.', handler: async () => ({ dnsRecords: await this.getDnsRecords(), }), }, { name: 'dcrouter_list_email_domains', description: 'List email domain summaries without DKIM private keys, mailboxes, or message contents.', handler: async () => ({ emailSettings: this.opsServerRef.dcRouterRef.emailSettingsManager?.getPublicSettings(), emailDomains: (await this.opsServerRef.dcRouterRef.emailDomainManager?.getAll() || []).map((domainArg) => this.summarizeEmailDomain(domainArg)), }), }, { name: 'dcrouter_list_remote_ingress_edges', description: 'List RemoteIngress edge summaries without edge secrets.', handler: async () => ({ hubSettings: this.summarizeRemoteIngressHubSettings(this.opsServerRef.dcRouterRef.remoteIngressManager?.getHubSettings()), edges: (this.opsServerRef.dcRouterRef.remoteIngressManager?.getAllEdges() || []).map((edgeArg) => this.summarizeRemoteIngressEdge(edgeArg)), }), }, { name: 'dcrouter_list_vpn_clients', description: 'List VPN client summaries without private keys or exported configs.', handler: async () => ({ running: this.opsServerRef.dcRouterRef.vpnManager?.running ?? false, clients: (this.opsServerRef.dcRouterRef.vpnManager?.listClients() || []).map((clientArg) => this.summarizeVpnClient(clientArg)), }), }, ], }); } public async handleRequest(requestArg: Request): Promise { return this.handler.handleRequest(requestArg); } public getHandler(): plugins.smartmcp.ISmartMcpHttpHandler { return this.handler; } private validateRequest(requestArg: Request): Response | undefined { const origin = requestArg.headers.get('origin'); if (origin && origin !== new URL(requestArg.url).origin) { return new Response('Forbidden', { status: 403 }); } } private async authorizeRequest(requestArg: Request): Promise { const jwt = this.extractBearerToken(requestArg); if (!jwt) { return new Response('Unauthorized', { status: 401 }); } try { const identity = await this.opsServerRef.adminHandler.getVerifiedAdminIdentityFromJwt(jwt); return { token: jwt, clientId: identity.userId, scopes: ['admin', 'mcp', 'dcrouter:read'], }; } catch { return new Response('Unauthorized', { status: 401 }); } } private extractBearerToken(requestArg: Request): string | undefined { const authHeader = requestArg.headers.get('authorization'); if (!authHeader) { return undefined; } const [scheme, token] = authHeader.split(' '); if (scheme?.toLowerCase() !== 'bearer' || !token) { return undefined; } return token.trim() || undefined; } private async getStatusSummary() { const routes = this.getMergedRoutes(); const domains = await this.opsServerRef.dcRouterRef.dnsManager?.listDomains().catch(() => []) || []; const emailDomains = await this.opsServerRef.dcRouterRef.emailDomainManager?.getAll().catch(() => []) || []; const edges = this.opsServerRef.dcRouterRef.remoteIngressManager?.getAllEdges() || []; const vpnClients = this.opsServerRef.dcRouterRef.vpnManager?.listClients() || []; return { smartProxyRunning: Boolean(this.opsServerRef.dcRouterRef.smartProxy), emailEnabled: this.opsServerRef.dcRouterRef.emailSettingsManager?.getPublicSettings?.()?.enabled ?? Boolean(this.opsServerRef.dcRouterRef.emailServer), dnsEnabled: Boolean(this.opsServerRef.dcRouterRef.dnsServer), radiusEnabled: Boolean(this.opsServerRef.dcRouterRef.radiusServer), remoteIngressEnabled: this.opsServerRef.dcRouterRef.remoteIngressManager?.getHubSettings?.()?.enabled ?? false, vpnRunning: this.opsServerRef.dcRouterRef.vpnManager?.running ?? false, routeCount: routes.length, enabledRouteCount: routes.filter((routeArg) => routeArg.enabled !== false).length, domainCount: domains.length, emailDomainCount: emailDomains.length, remoteIngressEdgeCount: edges.length, vpnClientCount: vpnClients.length, }; } private getMergedRoutes(): interfaces.data.IMergedRoute[] { return this.opsServerRef.dcRouterRef.routeConfigManager?.getMergedRoutes()?.routes || []; } private async getDnsRecords(): Promise { const dnsManager = this.opsServerRef.dcRouterRef.dnsManager; if (!dnsManager?.listDomains || !dnsManager?.listRecordsForDomain) { return []; } const domains = await dnsManager.listDomains(); const records: IDnsRecordSummary[] = []; for (const domain of domains) { const domainRecords = await dnsManager.listRecordsForDomain(domain.id); records.push(...domainRecords.map((recordArg) => this.summarizeDnsRecord(recordArg, domain.name))); } return records; } private summarizeRoute(routeArg: interfaces.data.IMergedRoute) { const route = routeArg.route || {}; const targets = route.action?.targets || []; return { id: routeArg.id, name: route.name, enabled: routeArg.enabled, origin: routeArg.origin, systemKey: routeArg.systemKey, match: { ports: route.match?.ports, domains: route.match?.domains, protocol: route.match?.protocol, transport: route.match?.transport, path: route.match?.path, }, action: { targetCount: targets.length, targets: targets.map((targetArg) => this.summarizeRouteTarget(targetArg)), }, remoteIngress: route.remoteIngress ? { enabled: route.remoteIngress.enabled, edgeFilter: route.remoteIngress.edgeFilter, } : undefined, vpnOnly: route.vpnOnly, metadata: routeArg.metadata ? { ownerType: routeArg.metadata.ownerType, gatewayClientType: routeArg.metadata.gatewayClientType, networkTargetName: routeArg.metadata.networkTargetName, sourceBindingCount: routeArg.metadata.sourceBindings?.length || 0, pathPolicyCount: routeArg.metadata.sourceBindings?.reduce((countArg, bindingArg) => countArg + (bindingArg.pathPolicies?.length || 0), 0) || 0, } : undefined, createdAt: routeArg.createdAt, updatedAt: routeArg.updatedAt, }; } private summarizeRouteTarget(targetArg: TRouteTarget) { return { host: this.summarizeRouteTargetValue(targetArg.host), port: this.summarizeRouteTargetValue(targetArg.port), }; } private summarizeRouteTargetValue(valueArg: TRouteTargetValue) { return typeof valueArg === 'function' ? '[dynamic]' : valueArg; } private summarizeSourceProfile(profileArg: interfaces.data.ISourceProfile) { return { id: profileArg.id, name: profileArg.name, description: profileArg.description, extendsProfiles: profileArg.extendsProfiles, security: this.summarizeSecurity(profileArg.security), createdAt: profileArg.createdAt, updatedAt: profileArg.updatedAt, }; } private summarizeSecurity(securityArg: interfaces.data.IRouteSecurity) { if (!securityArg) return undefined; return { ipAllowListCount: securityArg.ipAllowList?.length || 0, ipBlockListCount: securityArg.ipBlockList?.length || 0, rateLimitEnabled: Boolean(securityArg.rateLimit), challengeEnabled: Boolean(securityArg.challenge), maxConnections: securityArg.maxConnections, requireVpnClient: securityArg.vpn?.required, }; } private summarizeTargetProfile(profileArg: interfaces.data.ITargetProfile) { return { id: profileArg.id, name: profileArg.name, description: profileArg.description, domainCount: profileArg.domains?.length || 0, domains: profileArg.domains, targetCount: profileArg.targets?.length || 0, routeRefCount: profileArg.routeRefs?.length || 0, allowRoutesByClientSourceIp: profileArg.allowRoutesByClientSourceIp, createdAt: profileArg.createdAt, updatedAt: profileArg.updatedAt, }; } private summarizeNetworkTarget(targetArg: interfaces.data.INetworkTarget) { return { id: targetArg.id, name: targetArg.name, description: targetArg.description, host: targetArg.host, port: targetArg.port, createdAt: targetArg.createdAt, updatedAt: targetArg.updatedAt, }; } private summarizeDomain(domainArg: interfaces.data.IDomain) { return { id: domainArg.id, name: domainArg.name, source: domainArg.source, providerId: domainArg.providerId, authoritative: domainArg.authoritative, nameservers: domainArg.nameservers, lastSyncedAt: domainArg.lastSyncedAt, description: domainArg.description, createdAt: domainArg.createdAt, updatedAt: domainArg.updatedAt, }; } private summarizeDnsRecord(recordArg: interfaces.data.IDnsRecord, domainNameArg?: string): IDnsRecordSummary { return { id: recordArg.id, domainId: recordArg.domainId, domainName: domainNameArg, name: recordArg.name, type: recordArg.type, value: recordArg.value, ttl: recordArg.ttl, proxied: recordArg.proxied, source: recordArg.source, createdAt: recordArg.createdAt, updatedAt: recordArg.updatedAt, }; } private summarizeEmailDomain(domainArg: interfaces.data.IEmailDomain) { return { id: domainArg.id, domain: domainArg.domain, linkedDomainId: domainArg.linkedDomainId, subdomain: domainArg.subdomain, dkim: domainArg.dkim ? { selector: domainArg.dkim.selector, keySize: domainArg.dkim.keySize, rotateKeys: domainArg.dkim.rotateKeys, rotationIntervalDays: domainArg.dkim.rotationIntervalDays, lastRotatedAt: domainArg.dkim.lastRotatedAt, } : undefined, dnsStatus: domainArg.dnsStatus, rateLimits: domainArg.rateLimits, createdAt: domainArg.createdAt, updatedAt: domainArg.updatedAt, }; } private summarizeRemoteIngressHubSettings(settingsArg: interfaces.data.IRemoteIngressHubSettings | undefined) { if (!settingsArg) return undefined; return { enabled: settingsArg.enabled, tunnelPort: settingsArg.tunnelPort, hubDomain: settingsArg.hubDomain, performance: settingsArg.performance, updatedAt: settingsArg.updatedAt, }; } private summarizeRemoteIngressEdge(edgeArg: interfaces.data.IRemoteIngress) { const manager = this.opsServerRef.dcRouterRef.remoteIngressManager; return { id: edgeArg.id, name: edgeArg.name, enabled: edgeArg.enabled, listenPorts: edgeArg.listenPorts, listenPortsUdp: edgeArg.listenPortsUdp, effectiveListenPorts: manager?.getEffectiveListenPorts(edgeArg), effectiveListenPortsUdp: manager?.getEffectiveListenPortsUdp(edgeArg), autoDerivePorts: edgeArg.autoDerivePorts, tags: edgeArg.tags, performance: edgeArg.performance, createdAt: edgeArg.createdAt, updatedAt: edgeArg.updatedAt, }; } private summarizeVpnClient(clientArg: interfaces.data.IVpnClient) { return { clientId: clientArg.clientId, enabled: clientArg.enabled, assignedIp: clientArg.assignedIp, description: clientArg.description, targetProfileIds: clientArg.targetProfileIds, expiresAt: clientArg.expiresAt, useHostIp: clientArg.useHostIp, useDhcp: clientArg.useDhcp, staticIp: clientArg.staticIp, forceVlan: clientArg.forceVlan, vlanId: clientArg.vlanId, sourceIp: this.opsServerRef.dcRouterRef.vpnManager?.getClientSourceIp?.(clientArg.clientId), createdAt: clientArg.createdAt, updatedAt: clientArg.updatedAt, }; } }