Skip to content

Commit 5b8948a

Browse files
committed
feat: add kubernetes tools
1 parent 239eb6d commit 5b8948a

File tree

6 files changed

+194
-8
lines changed

6 files changed

+194
-8
lines changed

go.mod

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
module github.com/Azure/aks-mcp
22

3-
go 1.23.0
3+
go 1.24
44

5-
toolchain go1.24.2
5+
toolchain go1.24.5
66

77
require (
88
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1
@@ -17,6 +17,7 @@ require (
1717

1818
require (
1919
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
20+
github.com/Azure/mcp-kubernetes v0.0.3 // indirect
2021
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
2122
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
2223
github.com/google/uuid v1.6.0 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v2 v2.2
1818
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v2 v2.2.1/go.mod h1:Bzf34hhAE9NSxailk8xVeLEZbUjOXcC+GnU1mMKdhLw=
1919
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 h1:ECsQtyERDVz3NP3kvDOTLvbQhqWp/x9EsGKtb4ogUr8=
2020
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0/go.mod h1:s1tW/At+xHqjNFvWU4G0c0Qv33KOhvbGNj0RCTQDV8s=
21+
github.com/Azure/mcp-kubernetes v0.0.3 h1:5YaaDCL+r5CUg8X72jNaYD3hLK1+8Zu86Ka++KS5MO8=
22+
github.com/Azure/mcp-kubernetes v0.0.3/go.mod h1:nXHG50UKzFqwz8pPidHRXWM9+aSQRJy8m9dvm/FNnJ8=
2123
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
2224
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
2325
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=

internal/config/config.go

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package config
22

33
import (
4+
"strings"
45
"time"
56

67
"github.com/Azure/aks-mcp/internal/security"
@@ -21,17 +22,28 @@ type ConfigData struct {
2122
Host string
2223
Port int
2324
AccessLevel string
25+
26+
// Kubernetes-specific options
27+
// Map of additional tools enabled (helm, cilium)
28+
AdditionalTools map[string]bool
29+
// Comma-separated list of allowed Kubernetes namespaces
30+
AllowNamespaces string
31+
// Read-only mode for Kubernetes operations
32+
ReadOnly bool
2433
}
2534

2635
// NewConfig creates and returns a new configuration instance
2736
func NewConfig() *ConfigData {
2837
return &ConfigData{
29-
Timeout: 60,
30-
CacheTimeout: 1 * time.Minute,
31-
SecurityConfig: security.NewSecurityConfig(),
32-
Transport: "stdio",
33-
Port: 8000,
34-
AccessLevel: "readonly",
38+
Timeout: 60,
39+
CacheTimeout: 1 * time.Minute,
40+
SecurityConfig: security.NewSecurityConfig(),
41+
Transport: "stdio",
42+
Port: 8000,
43+
AccessLevel: "readonly",
44+
AdditionalTools: make(map[string]bool),
45+
AllowNamespaces: "",
46+
ReadOnly: false,
3547
}
3648
}
3749

@@ -45,8 +57,25 @@ func (cfg *ConfigData) ParseFlags() {
4557
// Security settings
4658
flag.StringVar(&cfg.AccessLevel, "access-level", "readonly", "Access level (readonly, readwrite, admin)")
4759

60+
// Kubernetes-specific settings
61+
additionalTools := flag.String("additional-tools", "",
62+
"Comma-separated list of additional Kubernetes tools to support (kubectl is always enabled). Available: helm,cilium")
63+
flag.StringVar(&cfg.AllowNamespaces, "allow-namespaces", "",
64+
"Comma-separated list of allowed Kubernetes namespaces (empty means all namespaces)")
65+
flag.BoolVar(&cfg.ReadOnly, "k8s-readonly", false,
66+
"Enable read-only mode for Kubernetes operations")
67+
4868
flag.Parse()
4969

5070
// Update security config
5171
cfg.SecurityConfig.AccessLevel = cfg.AccessLevel
72+
cfg.SecurityConfig.AllowedNamespaces = cfg.AllowNamespaces
73+
74+
// Parse additional tools
75+
if *additionalTools != "" {
76+
tools := strings.Split(*additionalTools, ",")
77+
for _, tool := range tools {
78+
cfg.AdditionalTools[strings.TrimSpace(tool)] = true
79+
}
80+
}
5281
}

internal/k8s/adapter.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package k8s
2+
3+
import (
4+
"github.com/Azure/aks-mcp/internal/config"
5+
"github.com/Azure/aks-mcp/internal/tools"
6+
k8sconfig "github.com/Azure/mcp-kubernetes/pkg/config"
7+
k8ssecurity "github.com/Azure/mcp-kubernetes/pkg/security"
8+
k8stools "github.com/Azure/mcp-kubernetes/pkg/tools"
9+
)
10+
11+
// ConfigAdapter converts aks-mcp config to mcp-kubernetes config
12+
func ConvertConfig(cfg *config.ConfigData) *k8sconfig.ConfigData {
13+
// Create K8s security config
14+
k8sSecurityConfig := k8ssecurity.NewSecurityConfig()
15+
16+
// Map access levels
17+
switch cfg.AccessLevel {
18+
case "readonly":
19+
k8sSecurityConfig.ReadOnly = true
20+
case "readwrite":
21+
k8sSecurityConfig.ReadOnly = false
22+
case "admin":
23+
k8sSecurityConfig.ReadOnly = false
24+
}
25+
26+
// Map allowed namespaces
27+
k8sSecurityConfig.SetAllowedNamespaces(cfg.AllowNamespaces)
28+
29+
// Create K8s config
30+
k8sCfg := &k8sconfig.ConfigData{
31+
AdditionalTools: cfg.AdditionalTools,
32+
Timeout: cfg.Timeout,
33+
SecurityConfig: k8sSecurityConfig,
34+
Transport: cfg.Transport,
35+
Host: cfg.Host,
36+
Port: cfg.Port,
37+
ReadOnly: cfg.ReadOnly,
38+
AllowNamespaces: cfg.AllowNamespaces,
39+
}
40+
41+
return k8sCfg
42+
}
43+
44+
// WrapK8sExecutor wraps a mcp-kubernetes executor to work with aks-mcp config
45+
func WrapK8sExecutor(k8sExecutor k8stools.CommandExecutor) tools.CommandExecutor {
46+
return &executorAdapter{k8sExecutor: k8sExecutor}
47+
}
48+
49+
// WrapK8sExecutorFunc wraps a mcp-kubernetes executor function to work with aks-mcp config
50+
func WrapK8sExecutorFunc(k8sFunc k8stools.CommandExecutorFunc) tools.CommandExecutor {
51+
return &executorAdapter{k8sExecutor: k8sFunc}
52+
}
53+
54+
// executorAdapter adapts between aks-mcp and mcp-kubernetes configs
55+
type executorAdapter struct {
56+
k8sExecutor k8stools.CommandExecutor
57+
}
58+
59+
func (a *executorAdapter) Execute(params map[string]interface{}, cfg *config.ConfigData) (string, error) {
60+
// Convert aks-mcp config to k8s config
61+
k8sCfg := ConvertConfig(cfg)
62+
63+
// Execute using the k8s executor
64+
return a.k8sExecutor.Execute(params, k8sCfg)
65+
}

internal/security/security.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,37 @@
11
package security
22

3+
import "strings"
4+
35
// SecurityConfig holds security-related configuration
46
type SecurityConfig struct {
57
// AccessLevel controls the level of operations allowed (readonly, readwrite, admin)
68
AccessLevel string
9+
// AllowedNamespaces is a comma-separated list of allowed Kubernetes namespaces
10+
AllowedNamespaces string
711
}
812

913
// NewSecurityConfig creates a new SecurityConfig instance
1014
func NewSecurityConfig() *SecurityConfig {
1115
return &SecurityConfig{
1216
AccessLevel: "readwrite",
17+
AllowedNamespaces: "",
18+
}
19+
}
20+
21+
// IsNamespaceAllowed checks if a namespace is allowed to be accessed
22+
func (s *SecurityConfig) IsNamespaceAllowed(namespace string) bool {
23+
// If no restrictions are defined, allow all namespaces
24+
if s.AllowedNamespaces == "" {
25+
return true
1326
}
27+
28+
// Check if the namespace is in the allowed list
29+
namespaces := strings.Split(s.AllowedNamespaces, ",")
30+
for _, ns := range namespaces {
31+
if strings.TrimSpace(ns) == namespace {
32+
return true
33+
}
34+
}
35+
36+
return false
1437
}

internal/server/server.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ import (
1414
"github.com/Azure/aks-mcp/internal/components/monitor"
1515
"github.com/Azure/aks-mcp/internal/components/monitor/diagnostics"
1616
"github.com/Azure/aks-mcp/internal/components/network"
17+
"github.com/Azure/mcp-kubernetes/pkg/cilium"
18+
"github.com/Azure/mcp-kubernetes/pkg/helm"
19+
"github.com/Azure/mcp-kubernetes/pkg/kubectl"
1720
"github.com/Azure/aks-mcp/internal/config"
21+
"github.com/Azure/aks-mcp/internal/k8s"
1822
"github.com/Azure/aks-mcp/internal/tools"
1923
"github.com/Azure/aks-mcp/internal/version"
2024
"github.com/mark3labs/mcp-go/server"
@@ -62,6 +66,9 @@ func (s *Service) Initialize() error {
6266
// Register AKS Control Plane tools
6367
s.registerControlPlaneTools()
6468

69+
// Register Kubernetes tools
70+
s.registerKubernetesTools()
71+
6572
return nil
6673
}
6774

@@ -315,3 +322,62 @@ func (s *Service) registerAdvisorTools() {
315322
advisorTool := advisor.RegisterAdvisorRecommendationTool()
316323
s.mcpServer.AddTool(advisorTool, tools.CreateResourceHandler(advisor.GetAdvisorRecommendationHandler(s.cfg), s.cfg))
317324
}
325+
326+
// registerKubernetesTools registers Kubernetes-related tools (kubectl, helm, cilium)
327+
func (s *Service) registerKubernetesTools() {
328+
log.Println("Registering Kubernetes tools...")
329+
330+
// Register kubectl commands based on access level
331+
s.registerKubectlCommands()
332+
333+
// Register helm if enabled
334+
if s.cfg.AdditionalTools["helm"] {
335+
log.Println("Registering Kubernetes tool: helm")
336+
helmTool := helm.RegisterHelm()
337+
helmExecutor := k8s.WrapK8sExecutor(helm.NewExecutor())
338+
s.mcpServer.AddTool(helmTool, tools.CreateToolHandler(helmExecutor, s.cfg))
339+
}
340+
341+
// Register cilium if enabled
342+
if s.cfg.AdditionalTools["cilium"] {
343+
log.Println("Registering Kubernetes tool: cilium")
344+
ciliumTool := cilium.RegisterCilium()
345+
ciliumExecutor := k8s.WrapK8sExecutor(cilium.NewExecutor())
346+
s.mcpServer.AddTool(ciliumTool, tools.CreateToolHandler(ciliumExecutor, s.cfg))
347+
}
348+
}
349+
350+
// registerKubectlCommands registers kubectl commands based on access level
351+
func (s *Service) registerKubectlCommands() {
352+
// Register read-only kubectl commands (available at all access levels)
353+
for _, cmd := range kubectl.GetReadOnlyKubectlCommands() {
354+
log.Printf("Registering kubectl command: %s", cmd.Name)
355+
kubectlTool := kubectl.RegisterKubectlCommand(cmd)
356+
k8sExecutor := kubectl.CreateCommandExecutorFunc(cmd.Name)
357+
wrappedExecutor := k8s.WrapK8sExecutorFunc(k8sExecutor)
358+
s.mcpServer.AddTool(kubectlTool, tools.CreateToolHandler(wrappedExecutor, s.cfg))
359+
}
360+
361+
// Register read-write commands if access level is readwrite or admin
362+
if s.cfg.AccessLevel == "readwrite" || s.cfg.AccessLevel == "admin" {
363+
for _, cmd := range kubectl.GetReadWriteKubectlCommands() {
364+
log.Printf("Registering kubectl command: %s", cmd.Name)
365+
kubectlTool := kubectl.RegisterKubectlCommand(cmd)
366+
k8sExecutor := kubectl.CreateCommandExecutorFunc(cmd.Name)
367+
wrappedExecutor := k8s.WrapK8sExecutorFunc(k8sExecutor)
368+
s.mcpServer.AddTool(kubectlTool, tools.CreateToolHandler(wrappedExecutor, s.cfg))
369+
}
370+
}
371+
372+
// Register admin commands only if access level is admin
373+
if s.cfg.AccessLevel == "admin" {
374+
for _, cmd := range kubectl.GetAdminKubectlCommands() {
375+
log.Printf("Registering kubectl command: %s", cmd.Name)
376+
kubectlTool := kubectl.RegisterKubectlCommand(cmd)
377+
k8sExecutor := kubectl.CreateCommandExecutorFunc(cmd.Name)
378+
wrappedExecutor := k8s.WrapK8sExecutorFunc(k8sExecutor)
379+
s.mcpServer.AddTool(kubectlTool, tools.CreateToolHandler(wrappedExecutor, s.cfg))
380+
}
381+
}
382+
}
383+

0 commit comments

Comments
 (0)