Skip to content

Commit 3bf7a0f

Browse files
committed
feat(kubernetes): pods_log for explicit or nil namespace
1 parent f591e2b commit 3bf7a0f

File tree

4 files changed

+108
-7
lines changed

4 files changed

+108
-7
lines changed

pkg/kubernetes/kubernetes.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,12 @@ func resolveClientConfig() (*rest.Config, error) {
6363
}
6464
return resolveConfig().ClientConfig()
6565
}
66+
67+
func namespaceOrDefault(namespace string) string {
68+
if namespace == "" {
69+
if ns, _, nsErr := resolveConfig().Namespace(); nsErr == nil {
70+
namespace = ns
71+
}
72+
}
73+
return namespace
74+
}

pkg/kubernetes/pods.go

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package kubernetes
22

33
import (
44
"context"
5+
v1 "k8s.io/api/core/v1"
56
"k8s.io/apimachinery/pkg/runtime/schema"
7+
"k8s.io/client-go/kubernetes"
68
)
79

810
func (k *Kubernetes) PodsListInAllNamespaces(ctx context.Context) (string, error) {
@@ -18,12 +20,27 @@ func (k *Kubernetes) PodsListInNamespace(ctx context.Context, namespace string)
1820
}
1921

2022
func (k *Kubernetes) PodsGet(ctx context.Context, namespace, name string) (string, error) {
21-
if namespace == "" {
22-
if ns, _, nsErr := resolveConfig().Namespace(); nsErr == nil {
23-
namespace = ns
24-
}
25-
}
2623
return k.ResourcesGet(ctx, &schema.GroupVersionKind{
2724
Group: "", Version: "v1", Kind: "Pod",
28-
}, namespace, name)
25+
}, namespaceOrDefault(namespace), name)
26+
}
27+
28+
func (k *Kubernetes) PodsLog(ctx context.Context, namespace, name string) (string, error) {
29+
cs, err := kubernetes.NewForConfig(k.cfg)
30+
if err != nil {
31+
return "", err
32+
}
33+
tailLines := int64(256)
34+
req := cs.CoreV1().Pods(namespaceOrDefault(namespace)).GetLogs(name, &v1.PodLogOptions{
35+
TailLines: &tailLines,
36+
})
37+
res := req.Do(ctx)
38+
if res.Error() != nil {
39+
return "", res.Error()
40+
}
41+
rawData, err := res.Raw()
42+
if err != nil {
43+
return "", err
44+
}
45+
return string(rawData), nil
2946
}

pkg/mcp/pods.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func (s *Sever) initPods() {
2323
), podsListInNamespace)
2424
s.server.AddTool(mcp.NewTool(
2525
"pods_get",
26-
mcp.WithDescription("Get a Kubernetes Pod in the current namespace with the provided name"),
26+
mcp.WithDescription("Get a Kubernetes Pod in the current or provided namespace with the provided name"),
2727
mcp.WithString("namespace",
2828
mcp.Description("Namespace to get the Pod from"),
2929
),
@@ -32,6 +32,17 @@ func (s *Sever) initPods() {
3232
mcp.Required(),
3333
),
3434
), podsGet)
35+
s.server.AddTool(mcp.NewTool(
36+
"pods_log",
37+
mcp.WithDescription("Get the logs of a Kubernetes Pod in the current or provided namespace with the provided name"),
38+
mcp.WithString("namespace",
39+
mcp.Description("Namespace to get the Pod logs from"),
40+
),
41+
mcp.WithString("name",
42+
mcp.Description("Name of the Pod"),
43+
mcp.Required(),
44+
),
45+
), podsLog)
3546
}
3647

3748
func podsListInAllNamespaces(ctx context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
@@ -81,3 +92,23 @@ func podsGet(ctx context.Context, ctr mcp.CallToolRequest) (*mcp.CallToolResult,
8192
}
8293
return NewTextResult(ret, err), nil
8394
}
95+
96+
func podsLog(ctx context.Context, ctr mcp.CallToolRequest) (*mcp.CallToolResult, error) {
97+
k, err := kubernetes.NewKubernetes()
98+
if err != nil {
99+
return NewTextResult("", fmt.Errorf("failed to get pod log: %v", err)), nil
100+
}
101+
ns := ctr.Params.Arguments["namespace"]
102+
if ns == nil {
103+
ns = ""
104+
}
105+
name := ctr.Params.Arguments["name"]
106+
if name == nil {
107+
return NewTextResult("", errors.New("failed to get pod log, missing argument name")), nil
108+
}
109+
ret, err := k.PodsLog(ctx, ns.(string), name.(string))
110+
if err != nil {
111+
return NewTextResult("", fmt.Errorf("failed to get pod %s log in namespace %s: %v", name, ns, err)), nil
112+
}
113+
return NewTextResult(ret, err), nil
114+
}

pkg/mcp/pods_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,3 +198,47 @@ func TestPodsGet(t *testing.T) {
198198
})
199199
})
200200
}
201+
202+
func TestPodsLog(t *testing.T) {
203+
testCase(t, func(c *mcpContext) {
204+
c.withEnvTest()
205+
t.Run("pods_log with nil name returns error", func(t *testing.T) {
206+
toolResult, _ := c.callTool("pods_log", map[string]interface{}{})
207+
if toolResult.IsError != true {
208+
t.Fatalf("call tool should fail")
209+
return
210+
}
211+
if toolResult.Content[0].(map[string]interface{})["text"].(string) != "failed to get pod log, missing argument name" {
212+
t.Fatalf("invalid error message, got %v", toolResult.Content[0].(map[string]interface{})["text"].(string))
213+
return
214+
}
215+
})
216+
podsLogNilNamespace, err := c.callTool("pods_log", map[string]interface{}{
217+
"name": "a-pod-in-default",
218+
})
219+
t.Run("pods_log with name and nil namespace returns pod log", func(t *testing.T) {
220+
if err != nil {
221+
t.Fatalf("call tool failed %v", err)
222+
return
223+
}
224+
if podsLogNilNamespace.IsError {
225+
t.Fatalf("call tool failed")
226+
return
227+
}
228+
})
229+
podsLogInNamespace, err := c.callTool("pods_log", map[string]interface{}{
230+
"namespace": "ns-1",
231+
"name": "a-pod-in-ns-1",
232+
})
233+
t.Run("pods_log with name and namespace returns pod log", func(t *testing.T) {
234+
if err != nil {
235+
t.Fatalf("call tool failed %v", err)
236+
return
237+
}
238+
if podsLogInNamespace.IsError {
239+
t.Fatalf("call tool failed")
240+
return
241+
}
242+
})
243+
})
244+
}

0 commit comments

Comments
 (0)