5
5
"context"
6
6
"fmt"
7
7
"strings"
8
+ "time"
8
9
9
10
"github.com/strowk/mcp-k8s-go/internal/k8s"
10
11
"github.com/strowk/mcp-k8s-go/internal/utils"
@@ -20,16 +21,20 @@ import (
20
21
"k8s.io/client-go/tools/remotecommand"
21
22
)
22
23
24
+ const timeout = 5 * time .Second
25
+
23
26
func NewPodExecCommandTool (pool k8s.ClientPool ) fxctx.Tool {
24
27
k8sNamespace := "namespace"
25
- k8sPodName := "podName "
28
+ k8sPodName := "pod "
26
29
execCommand := "command"
27
30
k8sContext := "context"
31
+ stdin := "stdin"
28
32
schema := toolinput .NewToolInputSchema (
29
- toolinput .WithRequiredString (k8sNamespace , "The name of the namespace where the pod to execute the command is located." ),
30
- toolinput .WithRequiredString (k8sPodName , "The name of the pod in which the command needs to be executed." ),
31
- toolinput .WithRequiredString (execCommand , "The command to be executed inside the pod." ),
32
- toolinput .WithString (k8sContext , "Kubernetes context name." ),
33
+ toolinput .WithString (k8sContext , "Kubernetes context name, defaults to current context" ),
34
+ toolinput .WithRequiredString (k8sNamespace , "Namespace where pod is located" ),
35
+ toolinput .WithRequiredString (k8sPodName , "Name of the pod to execute command in" ),
36
+ toolinput .WithRequiredString (execCommand , "Command to be executed" ),
37
+ toolinput .WithString (stdin , "Standard input to the command, defaults to empty string" ),
33
38
)
34
39
return fxctx .NewTool (
35
40
& mcp.Tool {
@@ -54,14 +59,15 @@ func NewPodExecCommandTool(pool k8s.ClientPool) fxctx.Tool {
54
59
if err != nil {
55
60
return errResponse (fmt .Errorf ("invalid input command: %w" , err ))
56
61
}
57
- k8sContext := input .StringOr (k8sContext , "default" )
62
+ k8sContext := input .StringOr (k8sContext , "" )
63
+ stdin := input .StringOr (stdin , "" )
58
64
59
65
kubeconfig := k8s .GetKubeConfigForContext (k8sContext )
60
66
config , err := kubeconfig .ClientConfig ()
61
67
if err != nil {
62
68
return errResponse (fmt .Errorf ("invalid config: %w" , err ))
63
69
}
64
- execResult , err := cmdExecuter (pool , config , k8sPodName , k8sNamespace , execCommand , k8sContext )
70
+ execResult , err := cmdExecuter (pool , config , k8sPodName , k8sNamespace , execCommand , k8sContext , stdin )
65
71
if err != nil {
66
72
return errResponse (fmt .Errorf ("command execute failed: %w" , err ))
67
73
}
@@ -88,7 +94,15 @@ type ExecResult struct {
88
94
Stderr interface {} `json:"stderr"`
89
95
}
90
96
91
- func cmdExecuter (pool k8s.ClientPool , config * rest.Config , podName , namespace , cmd , k8sContext string ) (ExecResult , error ) {
97
+ func cmdExecuter (
98
+ pool k8s.ClientPool ,
99
+ config * rest.Config ,
100
+ podName ,
101
+ namespace ,
102
+ cmd ,
103
+ k8sContext ,
104
+ stdin string ,
105
+ ) (ExecResult , error ) {
92
106
execResult := ExecResult {}
93
107
clientset , err := pool .GetClientset (k8sContext )
94
108
if err != nil {
@@ -122,12 +136,20 @@ func cmdExecuter(pool k8s.ClientPool, config *rest.Config, podName, namespace, c
122
136
if err != nil {
123
137
return execResult , err
124
138
}
139
+
140
+ ctx := context .Background ()
141
+ withTimeout , cancel := context .WithTimeout (ctx , timeout )
142
+ defer cancel () // release resources if operation finishes before timeout
143
+
125
144
var stdout , stderr bytes.Buffer
126
- if err = executor .Stream ( remotecommand.StreamOptions {
127
- Stdin : strings .NewReader ("" ),
145
+ if err = executor .StreamWithContext ( withTimeout , remotecommand.StreamOptions {
146
+ Stdin : strings .NewReader (stdin ),
128
147
Stdout : & stdout ,
129
148
Stderr : & stderr ,
130
149
}); err != nil {
150
+ if err == context .DeadlineExceeded {
151
+ return execResult , fmt .Errorf ("command timed out after %s" , timeout )
152
+ }
131
153
return execResult , err
132
154
}
133
155
execResult .Stdout = stdout .String ()
0 commit comments