Add Kubernetes and Jira MCP tools with auto-registration

This commit is contained in:
2026-04-13 14:15:00 +03:00
parent 87fb9e8df8
commit b5eb33272a
10 changed files with 443 additions and 433 deletions

View File

@@ -8,17 +8,17 @@ using ModelContextProtocol.Server;
namespace LazyBear.MCP.Services.Kubernetes;
[McpServerToolType]
public sealed class K8sDeploymentTools(K8sClientProvider clientProvider, IConfiguration configuration)
public sealed class K8sDeploymentTools(K8sClientProvider clientProvider, IConfiguration configuration, ILogger<K8sDeploymentTools>? logger = null) : KubernetesToolsBase(clientProvider, configuration, logger)
{
private readonly IKubernetes? _client = clientProvider.Client;
private readonly string? _clientInitializationError = clientProvider.InitializationError;
private readonly string _defaultNamespace = configuration["Kubernetes:DefaultNamespace"] ?? "default";
private const int MinReplicas = 0;
private const int MaxReplicas = 100;
[McpServerTool, Description("Список deployment в namespace")]
public async Task<string> ListDeployments(
[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))
{
@@ -58,6 +58,14 @@ public sealed class K8sDeploymentTools(K8sClientProvider clientProvider, IConfig
[Description("Namespace Kubernetes")] string? @namespace = null,
CancellationToken cancellationToken = default)
{
ValidateResourceName(name, nameof(name));
ValidateNamespace(@namespace ?? _defaultNamespace, nameof(@namespace));
if (replicas < MinReplicas || replicas > MaxReplicas)
{
return $"Invalid replicas value: {replicas}. Must be between {MinReplicas} and {MaxReplicas}.";
}
var ns = ResolveNamespace(@namespace);
if (!TryGetClient(out var client, out var clientError))
{
@@ -87,6 +95,8 @@ public sealed class K8sDeploymentTools(K8sClientProvider clientProvider, IConfig
[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))
{
@@ -116,6 +126,8 @@ public sealed class K8sDeploymentTools(K8sClientProvider clientProvider, IConfig
[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))
{
@@ -151,49 +163,4 @@ public sealed class K8sDeploymentTools(K8sClientProvider clientProvider, IConfig
return FormatError("restart_deployment", ns, ex, name);
}
}
private string ResolveNamespace(string? @namespace)
{
return string.IsNullOrWhiteSpace(@namespace) ? _defaultNamespace : @namespace;
}
private static string BuildClientInitializationError()
{
return "Kubernetes клиент не инициализирован. Проверьте Kubernetes:KubeconfigPath или in-cluster окружение (KUBERNETES_SERVICE_HOST/KUBERNETES_SERVICE_PORT).";
}
private bool TryGetClient(out IKubernetes client, out string error)
{
if (_client is null)
{
client = null!;
var details = string.IsNullOrWhiteSpace(_clientInitializationError)
? string.Empty
: $" Детали: {_clientInitializationError}";
error = BuildClientInitializationError() + details;
return false;
}
client = _client;
error = string.Empty;
return true;
}
private static string FormatError(string toolName, string @namespace, Exception exception, string? resourceName = null)
{
var resourcePart = string.IsNullOrWhiteSpace(resourceName) ? string.Empty : $", resource='{resourceName}'";
if (exception is HttpOperationException httpException)
{
var statusCode = httpException.Response?.StatusCode;
var reason = httpException.Response?.ReasonPhrase;
var body = string.IsNullOrWhiteSpace(httpException.Response?.Content)
? "-"
: httpException.Response!.Content;
return $"Ошибка Kubernetes в tool '{toolName}' (namespace='{@namespace}'{resourcePart}): status={(int?)statusCode ?? 0} {reason}, details={body}";
}
return $"Ошибка Kubernetes в tool '{toolName}' (namespace='{@namespace}'{resourcePart}): {exception.GetType().Name}: {exception.Message}";
}
}