Skip to content

Commit 3072d19

Browse files
blublinskymatzew
andauthored
add support for nodes query (containers#384)
* feat(http): add custom CA certificate support for OIDC providers add support for nodes logs Signed-off-by: blublinsky <[email protected]> * removed some tools Signed-off-by: blublinsky <[email protected]> --------- Signed-off-by: blublinsky <[email protected]> Co-authored-by: Matthias Wessendorf <[email protected]>
1 parent aab9441 commit 3072d19

File tree

9 files changed

+376
-0
lines changed

9 files changed

+376
-0
lines changed

pkg/kubernetes/nodes.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package kubernetes
2+
3+
import (
4+
"context"
5+
"fmt"
6+
)
7+
8+
func (k *Kubernetes) NodeLog(ctx context.Context, name string, logPath string, tail int64) (string, error) {
9+
// Use the node proxy API to access logs from the kubelet
10+
// Common log paths:
11+
// - /var/log/kubelet.log - kubelet logs
12+
// - /var/log/kube-proxy.log - kube-proxy logs
13+
// - /var/log/containers/ - container logs
14+
15+
if logPath == "" {
16+
logPath = "kubelet.log"
17+
}
18+
19+
// Build the URL for the node proxy logs endpoint
20+
url := []string{"api", "v1", "nodes", name, "proxy", "logs", logPath}
21+
22+
// Query parameters for tail
23+
params := make(map[string]string)
24+
if tail > 0 {
25+
params["tailLines"] = fmt.Sprintf("%d", tail)
26+
}
27+
28+
req := k.manager.discoveryClient.RESTClient().
29+
Get().
30+
AbsPath(url...)
31+
32+
// Add tail parameter if specified
33+
for key, value := range params {
34+
req.Param(key, value)
35+
}
36+
37+
result := req.Do(ctx)
38+
if result.Error() != nil {
39+
return "", fmt.Errorf("failed to get node logs: %w", result.Error())
40+
}
41+
42+
rawData, err := result.Raw()
43+
if err != nil {
44+
return "", fmt.Errorf("failed to read node log response: %w", err)
45+
}
46+
47+
return string(rawData), nil
48+
}

pkg/mcp/nodes_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package mcp
2+
3+
import (
4+
"strings"
5+
"testing"
6+
7+
"github.com/mark3labs/mcp-go/mcp"
8+
corev1 "k8s.io/api/core/v1"
9+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
)
11+
12+
func TestNodeLog(t *testing.T) {
13+
testCase(t, func(c *mcpContext) {
14+
c.withEnvTest()
15+
16+
// Create test node
17+
kubernetesAdmin := c.newKubernetesClient()
18+
node := &corev1.Node{
19+
ObjectMeta: metav1.ObjectMeta{
20+
Name: "test-node-log",
21+
},
22+
Status: corev1.NodeStatus{
23+
Addresses: []corev1.NodeAddress{
24+
{Type: corev1.NodeInternalIP, Address: "192.168.1.10"},
25+
},
26+
},
27+
}
28+
29+
_, _ = kubernetesAdmin.CoreV1().Nodes().Create(c.ctx, node, metav1.CreateOptions{})
30+
31+
// Test node_log tool
32+
toolResult, err := c.callTool("node_log", map[string]interface{}{
33+
"name": "test-node-log",
34+
})
35+
36+
t.Run("node_log returns successfully or with expected error", func(t *testing.T) {
37+
if err != nil {
38+
t.Fatalf("call tool failed: %v", err)
39+
}
40+
// Node logs might not be available in test environment
41+
// We just check that the tool call completes
42+
if toolResult.IsError {
43+
content := toolResult.Content[0].(mcp.TextContent).Text
44+
// Expected error messages in test environment
45+
if !strings.Contains(content, "failed to get node logs") &&
46+
!strings.Contains(content, "not logged any message yet") {
47+
t.Logf("tool returned error (expected in test environment): %v", content)
48+
}
49+
}
50+
})
51+
})
52+
}
53+
54+
func TestNodeLogMissingArguments(t *testing.T) {
55+
testCase(t, func(c *mcpContext) {
56+
c.withEnvTest()
57+
58+
t.Run("node_log requires name", func(t *testing.T) {
59+
toolResult, err := c.callTool("node_log", map[string]interface{}{})
60+
61+
if err == nil && !toolResult.IsError {
62+
t.Fatal("expected error when name is missing")
63+
}
64+
})
65+
})
66+
}

pkg/mcp/testdata/toolsets-core-tools.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,40 @@
3333
},
3434
"name": "namespaces_list"
3535
},
36+
{
37+
"annotations": {
38+
"title": "Node: Log",
39+
"readOnlyHint": true,
40+
"destructiveHint": false,
41+
"idempotentHint": false,
42+
"openWorldHint": true
43+
},
44+
"description": "Get logs from a Kubernetes node (kubelet, kube-proxy, or other system logs). This accesses node logs through the Kubernetes API proxy to the kubelet",
45+
"inputSchema": {
46+
"type": "object",
47+
"properties": {
48+
"log_path": {
49+
"default": "kubelet.log",
50+
"description": "Path to the log file on the node (e.g. 'kubelet.log', 'kube-proxy.log'). Default is 'kubelet.log'",
51+
"type": "string"
52+
},
53+
"name": {
54+
"description": "Name of the node to get logs from",
55+
"type": "string"
56+
},
57+
"tail": {
58+
"default": 100,
59+
"description": "Number of lines to retrieve from the end of the logs (Optional, 0 means all logs)",
60+
"minimum": 0,
61+
"type": "integer"
62+
}
63+
},
64+
"required": [
65+
"name"
66+
]
67+
},
68+
"name": "node_log"
69+
},
3670
{
3771
"annotations": {
3872
"title": "Pods: Delete",

pkg/mcp/testdata/toolsets-full-tools-multicluster-enum.json

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,48 @@
195195
},
196196
"name": "namespaces_list"
197197
},
198+
{
199+
"annotations": {
200+
"title": "Node: Log",
201+
"readOnlyHint": true,
202+
"destructiveHint": false,
203+
"idempotentHint": false,
204+
"openWorldHint": true
205+
},
206+
"description": "Get logs from a Kubernetes node (kubelet, kube-proxy, or other system logs). This accesses node logs through the Kubernetes API proxy to the kubelet",
207+
"inputSchema": {
208+
"type": "object",
209+
"properties": {
210+
"context": {
211+
"description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
212+
"enum": [
213+
"extra-cluster",
214+
"fake-context"
215+
],
216+
"type": "string"
217+
},
218+
"log_path": {
219+
"default": "kubelet.log",
220+
"description": "Path to the log file on the node (e.g. 'kubelet.log', 'kube-proxy.log'). Default is 'kubelet.log'",
221+
"type": "string"
222+
},
223+
"name": {
224+
"description": "Name of the node to get logs from",
225+
"type": "string"
226+
},
227+
"tail": {
228+
"default": 100,
229+
"description": "Number of lines to retrieve from the end of the logs (Optional, 0 means all logs)",
230+
"minimum": 0,
231+
"type": "integer"
232+
}
233+
},
234+
"required": [
235+
"name"
236+
]
237+
},
238+
"name": "node_log"
239+
},
198240
{
199241
"annotations": {
200242
"title": "Pods: Delete",

pkg/mcp/testdata/toolsets-full-tools-multicluster.json

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,44 @@
175175
},
176176
"name": "namespaces_list"
177177
},
178+
{
179+
"annotations": {
180+
"title": "Node: Log",
181+
"readOnlyHint": true,
182+
"destructiveHint": false,
183+
"idempotentHint": false,
184+
"openWorldHint": true
185+
},
186+
"description": "Get logs from a Kubernetes node (kubelet, kube-proxy, or other system logs). This accesses node logs through the Kubernetes API proxy to the kubelet",
187+
"inputSchema": {
188+
"type": "object",
189+
"properties": {
190+
"context": {
191+
"description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
192+
"type": "string"
193+
},
194+
"log_path": {
195+
"default": "kubelet.log",
196+
"description": "Path to the log file on the node (e.g. 'kubelet.log', 'kube-proxy.log'). Default is 'kubelet.log'",
197+
"type": "string"
198+
},
199+
"name": {
200+
"description": "Name of the node to get logs from",
201+
"type": "string"
202+
},
203+
"tail": {
204+
"default": 100,
205+
"description": "Number of lines to retrieve from the end of the logs (Optional, 0 means all logs)",
206+
"minimum": 0,
207+
"type": "integer"
208+
}
209+
},
210+
"required": [
211+
"name"
212+
]
213+
},
214+
"name": "node_log"
215+
},
178216
{
179217
"annotations": {
180218
"title": "Pods: Delete",

pkg/mcp/testdata/toolsets-full-tools-openshift.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,40 @@
139139
},
140140
"name": "namespaces_list"
141141
},
142+
{
143+
"annotations": {
144+
"title": "Node: Log",
145+
"readOnlyHint": true,
146+
"destructiveHint": false,
147+
"idempotentHint": false,
148+
"openWorldHint": true
149+
},
150+
"description": "Get logs from a Kubernetes node (kubelet, kube-proxy, or other system logs). This accesses node logs through the Kubernetes API proxy to the kubelet",
151+
"inputSchema": {
152+
"type": "object",
153+
"properties": {
154+
"log_path": {
155+
"default": "kubelet.log",
156+
"description": "Path to the log file on the node (e.g. 'kubelet.log', 'kube-proxy.log'). Default is 'kubelet.log'",
157+
"type": "string"
158+
},
159+
"name": {
160+
"description": "Name of the node to get logs from",
161+
"type": "string"
162+
},
163+
"tail": {
164+
"default": 100,
165+
"description": "Number of lines to retrieve from the end of the logs (Optional, 0 means all logs)",
166+
"minimum": 0,
167+
"type": "integer"
168+
}
169+
},
170+
"required": [
171+
"name"
172+
]
173+
},
174+
"name": "node_log"
175+
},
142176
{
143177
"annotations": {
144178
"title": "Pods: Delete",

pkg/mcp/testdata/toolsets-full-tools.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,40 @@
139139
},
140140
"name": "namespaces_list"
141141
},
142+
{
143+
"annotations": {
144+
"title": "Node: Log",
145+
"readOnlyHint": true,
146+
"destructiveHint": false,
147+
"idempotentHint": false,
148+
"openWorldHint": true
149+
},
150+
"description": "Get logs from a Kubernetes node (kubelet, kube-proxy, or other system logs). This accesses node logs through the Kubernetes API proxy to the kubelet",
151+
"inputSchema": {
152+
"type": "object",
153+
"properties": {
154+
"log_path": {
155+
"default": "kubelet.log",
156+
"description": "Path to the log file on the node (e.g. 'kubelet.log', 'kube-proxy.log'). Default is 'kubelet.log'",
157+
"type": "string"
158+
},
159+
"name": {
160+
"description": "Name of the node to get logs from",
161+
"type": "string"
162+
},
163+
"tail": {
164+
"default": 100,
165+
"description": "Number of lines to retrieve from the end of the logs (Optional, 0 means all logs)",
166+
"minimum": 0,
167+
"type": "integer"
168+
}
169+
},
170+
"required": [
171+
"name"
172+
]
173+
},
174+
"name": "node_log"
175+
},
142176
{
143177
"annotations": {
144178
"title": "Pods: Delete",

0 commit comments

Comments
 (0)