Skip to content

Commit 2e03d93

Browse files
committed
added: list k8s nodes
1 parent df28745 commit 2e03d93

File tree

6 files changed

+195
-11
lines changed

6 files changed

+195
-11
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
- 🗂️ resource: K8S contexts as read from kubeconfig configurations
3131
- 🤖 tool: list-k8s-contexts
3232
- 🤖 tool: list-k8s-namespaces in a given context
33+
- 🤖 tool: list-k8s-nodes in a given context
3334
- 🤖 tool: list-k8s-pods in a given context and namespace
3435
- 🤖 tool: list-k8s-events in a given context and namespace
3536
- 🤖 tool: list-k8s-services in a given context and namespace

internal/tools/nodes.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package tools
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"sort"
7+
"time"
8+
9+
"github.com/strowk/mcp-k8s-go/internal/k8s"
10+
"github.com/strowk/mcp-k8s-go/internal/utils"
11+
12+
"github.com/strowk/foxy-contexts/pkg/fxctx"
13+
"github.com/strowk/foxy-contexts/pkg/mcp"
14+
"github.com/strowk/foxy-contexts/pkg/toolinput"
15+
16+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
17+
)
18+
19+
func NewListNodesTool(pool k8s.ClientPool) fxctx.Tool {
20+
contextProperty := "context"
21+
schema := toolinput.NewToolInputSchema(
22+
toolinput.WithString(contextProperty, "Name of the Kubernetes context to use, defaults to current context"),
23+
)
24+
return fxctx.NewTool(
25+
&mcp.Tool{
26+
Name: "list-k8s-nodes",
27+
Description: utils.Ptr("List Kubernetes nodes using specific context"),
28+
InputSchema: schema.GetMcpToolInputSchema(),
29+
},
30+
func(args map[string]interface{}) *mcp.CallToolResult {
31+
input, err := schema.Validate(args)
32+
if err != nil {
33+
return errResponse(err)
34+
}
35+
k8sCtx := input.StringOr(contextProperty, "")
36+
37+
clientset, err := pool.GetClientset(k8sCtx)
38+
if err != nil {
39+
return errResponse(err)
40+
}
41+
42+
nodes, err := clientset.
43+
CoreV1().
44+
Nodes().
45+
List(context.Background(), metav1.ListOptions{})
46+
if err != nil {
47+
return errResponse(err)
48+
}
49+
50+
sort.Slice(nodes.Items, func(i, j int) bool {
51+
return nodes.Items[i].Name < nodes.Items[j].Name
52+
})
53+
54+
var contents []interface{} = make([]interface{}, len(nodes.Items))
55+
for i, ns := range nodes.Items {
56+
// Calculate age
57+
age := time.Since(ns.CreationTimestamp.Time)
58+
59+
// Determine status
60+
status := "NotReady"
61+
for _, condition := range ns.Status.Conditions {
62+
if condition.Type == "Ready" {
63+
if condition.Status == "True" {
64+
status = "Ready"
65+
} else {
66+
status = "NotReady"
67+
}
68+
break
69+
}
70+
}
71+
72+
content, err := NewJsonContent(NodeInList{
73+
Name: ns.Name,
74+
Status: status,
75+
Age: formatAge(age),
76+
CreatedAt: ns.CreationTimestamp.Time,
77+
})
78+
if err != nil {
79+
return errResponse(err)
80+
}
81+
contents[i] = content
82+
}
83+
84+
return &mcp.CallToolResult{
85+
Meta: map[string]interface{}{},
86+
Content: contents,
87+
IsError: utils.Ptr(false),
88+
}
89+
},
90+
)
91+
}
92+
93+
// NodeInList provides a structured representation of node information
94+
type NodeInList struct {
95+
Name string `json:"name"`
96+
Status string `json:"status"`
97+
Age string `json:"age"`
98+
CreatedAt time.Time `json:"created_at"`
99+
}
100+
101+
// formatAge converts a duration to a human-readable age string
102+
func formatAge(duration time.Duration) string {
103+
if duration.Hours() < 1 {
104+
return duration.Round(time.Minute).String()
105+
}
106+
if duration.Hours() < 24 {
107+
return duration.Round(time.Hour).String()
108+
}
109+
days := int(duration.Hours() / 24)
110+
return formatDays(days)
111+
}
112+
113+
// formatDays provides a concise representation of days
114+
func formatDays(days int) string {
115+
if days < 7 {
116+
return fmt.Sprintf("%dd", days)
117+
}
118+
if days < 30 {
119+
weeks := days / 7
120+
return fmt.Sprintf("%dw", weeks)
121+
}
122+
months := days / 30
123+
return fmt.Sprintf("%dmo", months)
124+
}

main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ func main() {
7777
WithTool(tools.NewListEventsTool).
7878
WithTool(tools.NewListPodsTool).
7979
WithTool(tools.NewListServicesTool).
80+
WithTool(tools.NewListNodesTool).
8081
WithPrompt(prompts.NewListPodsPrompt).
8182
WithPrompt(prompts.NewListNamespacesPrompt).
8283
WithResourceProvider(resources.NewContextsResourceProvider).

packages/npm-mcp-k8s/README.md

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@
33
This is a distribution of MCP server connecting to Kubernetes written in Golang and published to npm.
44

55
Currently available:
6-
- resource: K8S contexts as read from kubeconfig configurations
7-
- tool: list-k8s-contexts
8-
- tool: list-k8s-namespaces in a given context
9-
- tool: list-k8s-pods in a given context and namespace
10-
- tool: list-k8s-events in a given context and namespace
11-
- tool: list-k8s-services in a given context and namespace
12-
- tool: get-k8s-pod-logs in a given context and namespace
13-
- prompt: list-k8s-namespaces in a given context
14-
- prompt: list-k8s-pods in current context and with given namespace
6+
7+
- 🗂️ resource: K8S contexts as read from kubeconfig configurations
8+
- 🤖 tool: list-k8s-contexts
9+
- 🤖 tool: list-k8s-namespaces in a given context
10+
- 🤖 tool: list-k8s-nodes in a given context
11+
- 🤖 tool: list-k8s-pods in a given context and namespace
12+
- 🤖 tool: list-k8s-events in a given context and namespace
13+
- 🤖 tool: list-k8s-services in a given context and namespace
14+
- 🤖 tool: get-k8s-pod-logs in a given context and namespace
15+
- 💬 prompt: list-k8s-namespaces in a given context
16+
- 💬 prompt: list-k8s-pods in current context and with given namespace
1517

1618
## Example usage with Claude Desktop
1719

testdata/list_tools_test.yaml

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,22 @@ out:
9797
},
9898
},
9999
},
100+
{
101+
"name": "list-k8s-nodes",
102+
"description": "List Kubernetes nodes using specific context",
103+
"inputSchema":
104+
{
105+
"type": "object",
106+
"properties":
107+
{
108+
"context":
109+
{
110+
"type": "string",
111+
"description": "Name of the Kubernetes context to use, defaults to current context",
112+
},
113+
},
114+
},
115+
},
100116
{
101117
"name": "list-k8s-pods",
102118
"description": "List Kubernetes pods using specific context in a specified namespace",
@@ -105,8 +121,16 @@ out:
105121
"type": "object",
106122
"properties":
107123
{
108-
"context": { "type": "string", "description": "Name of the Kubernetes context to use, defaults to current context" },
109-
"namespace": { "type": "string", "description": "Namespace to list pods from, defaults to all namespaces" },
124+
"context":
125+
{
126+
"type": "string",
127+
"description": "Name of the Kubernetes context to use, defaults to current context",
128+
},
129+
"namespace":
130+
{
131+
"type": "string",
132+
"description": "Namespace to list pods from, defaults to all namespaces",
133+
},
110134
},
111135
},
112136
},
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
case: List nodes using tool
2+
3+
in:
4+
{
5+
"jsonrpc": "2.0",
6+
"method": "tools/call",
7+
"id": 2,
8+
"params":
9+
{
10+
"name": "list-k8s-nodes",
11+
"arguments":
12+
{ "context": "k3d-mcp-k8s-integration-test" },
13+
},
14+
}
15+
out:
16+
{
17+
"jsonrpc": "2.0",
18+
"id": 2,
19+
"result":
20+
{
21+
"content":
22+
[
23+
{
24+
"type": "text",
25+
"text": !!ere '{"name":"k3d-mcp-k8s-integration-test-server-0","status":"Ready","age":"/[0-9sm]{2,4}/","created_at":"/.+/"}',
26+
# ^ this is a pattern, this ^ too
27+
# this just to match a duration // and this is for timestamp
28+
}
29+
],
30+
"isError": false,
31+
},
32+
}

0 commit comments

Comments
 (0)