using System.ComponentModel; using k8s; using k8s.Autorest; using ModelContextProtocol.Server; namespace LazyBear.MCP.Services.Kubernetes; [McpServerToolType] public sealed class K8sNetworkTools(K8sClientProvider clientProvider, IConfiguration configuration, ILogger? logger = null) : KubernetesToolsBase(clientProvider, configuration, logger) { [McpServerTool, Description("Список service в namespace")] public async Task ListServices( [Description("Namespace Kubernetes")] string? @namespace = null, CancellationToken cancellationToken = default) { ValidateNamespace(@namespace ?? _defaultNamespace, nameof(@namespace)); var ns = ResolveNamespace(@namespace); if (!TryGetClient(out var client, out var clientError)) { return clientError; } try { var services = await client.CoreV1.ListNamespacedServiceAsync(ns, cancellationToken: cancellationToken); if (services.Items.Count == 0) { return $"В namespace '{ns}' service не найдены."; } var lines = services.Items.Select(svc => { var name = svc.Metadata?.Name ?? "unknown"; var type = svc.Spec?.Type ?? "ClusterIP"; var clusterIp = svc.Spec?.ClusterIP ?? "-"; var ports = svc.Spec?.Ports is null ? "-" : string.Join(",", svc.Spec.Ports.Select(p => $"{p.Port}/{p.Protocol}")); return $"{name}: type={type}, clusterIp={clusterIp}, ports={ports}"; }); return $"Services namespace '{ns}':\n{string.Join('\n', lines)}"; } catch (Exception ex) { return FormatError("list_services", ns, ex); } } [McpServerTool, Description("Детали service")] public async Task GetServiceDetails( [Description("Имя service")] string name, [Description("Namespace Kubernetes")] string? @namespace = null, CancellationToken cancellationToken = default) { ValidateResourceName(name, nameof(name)); ValidateNamespace(@namespace ?? _defaultNamespace, nameof(@namespace)); var ns = ResolveNamespace(@namespace); if (!TryGetClient(out var client, out var clientError)) { return clientError; } try { var service = await client.CoreV1.ReadNamespacedServiceAsync(name, ns, cancellationToken: cancellationToken); var type = service.Spec?.Type ?? "ClusterIP"; var clusterIp = service.Spec?.ClusterIP ?? "-"; var externalIps = service.Spec?.ExternalIPs is null ? "-" : string.Join(",", service.Spec.ExternalIPs); var selector = service.Spec?.Selector is null ? "-" : string.Join(",", service.Spec.Selector.Select(x => $"{x.Key}={x.Value}")); var ports = service.Spec?.Ports is null ? "-" : string.Join(",", service.Spec.Ports.Select(p => $"{p.Name ?? "port"}:{p.Port}->{p.TargetPort}")); return $"Service '{name}' namespace '{ns}': type={type}, clusterIp={clusterIp}, externalIps={externalIps}, selector={selector}, ports={ports}"; } catch (Exception ex) { return FormatError("get_service_details", ns, ex, name); } } [McpServerTool, Description("Список ingress в namespace")] public async Task ListIngresses( [Description("Namespace Kubernetes")] string? @namespace = null, CancellationToken cancellationToken = default) { ValidateNamespace(@namespace ?? _defaultNamespace, nameof(@namespace)); var ns = ResolveNamespace(@namespace); if (!TryGetClient(out var client, out var clientError)) { return clientError; } try { var ingresses = await client.NetworkingV1.ListNamespacedIngressAsync(ns, cancellationToken: cancellationToken); if (ingresses.Items.Count == 0) { return $"В namespace '{ns}' ingress не найдены."; } var lines = ingresses.Items.Select(ing => { var name = ing.Metadata?.Name ?? "unknown"; var rules = ing.Spec?.Rules is null ? "-" : string.Join(";", ing.Spec.Rules.Select(r => { var host = string.IsNullOrWhiteSpace(r.Host) ? "*" : r.Host; var paths = r.Http?.Paths is null ? "-" : string.Join(",", r.Http.Paths.Select(p => { var serviceName = p.Backend?.Service?.Name ?? "-"; var servicePort = p.Backend?.Service?.Port?.Number?.ToString() ?? p.Backend?.Service?.Port?.Name ?? "-"; return $"{p.Path ?? "/"}->{serviceName}:{servicePort}"; })); return $"{host}:{paths}"; })); return $"{name}: rules={rules}"; }); return $"Ingress namespace '{ns}':\n{string.Join('\n', lines)}"; } catch (Exception ex) { return FormatError("list_ingresses", ns, ex); } } }