Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ serverscom-mcp/
├── networks.go # server networks (IPv4/IPv6, gateway/route)
├── l2_segments.go # L2 segment management
├── drives.go # drive slot listing
└── rbs.go # Remote Block Storage volumes
├── rbs.go # Remote Block Storage volumes
└── kubernetes_clusters.go # Kubernetes cluster and node inspection
```

## How to Add a New Tool
Expand Down Expand Up @@ -129,8 +130,9 @@ API tag groups map to tool files as follows:
| Locations, Order options | `locations.go` |
| L2 Segment | `l2_segments.go` |
| Remote Block Storage (RBS) | `rbs.go` |
| Kubernetes Cluster | `kubernetes_clusters.go` |

Not yet implemented: Cloud Computing, Cloud Block Storage, Load Balancers, Network Pool, Kubernetes Cluster, Racks, Billing/Account, Metrics.
Not yet implemented: Cloud Computing, Cloud Block Storage, Load Balancers, Network Pool, Racks, Billing/Account, Metrics.
Comment on lines 132 to +135
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that Kubernetes Cluster tools are implemented and mapped here, the earlier “Top-level services” section should also document h.client.KubernetesClusters alongside the other client services to keep contributor guidance accurate.

Copilot uses AI. Check for mistakes.

## Async Operations

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ SC_TOKEN=your-api-token npx @servers.com/mcp

## Available Tools

73 tools across 6 categories — see **[TOOLS.md](TOOLS.md)** for the full reference.
78 tools across 7 categories — see **[TOOLS.md](TOOLS.md)** for the full reference.

Comment on lines 37 to 40
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tool count/category summary is updated, but the later “Project Structure” tree in this README still lists internal/tools/ ending at rbs.go and doesn’t include the new kubernetes_clusters.go, which makes the documentation inconsistent. Please update the tree section as well.

Copilot uses AI. Check for mistakes.
## Async Operations

Expand Down
16 changes: 16 additions & 0 deletions TOOLS.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,19 @@ L2 segments unite dedicated servers within a location group into a single broadc
**Member modes:** `native` (no OS config needed, 1 per interface per type) · `trunk` (requires VLAN sub-interface on server OS, up to 16 per type)

**Limits per server:** 1 native public + 1 native private + 16 public trunk + 16 private trunk = 34 max

## Kubernetes Clusters

Managed Kubernetes clusters on Servers.com infrastructure. Clusters are provisioned through the Servers.com portal; these tools provide read and label-management access.

| Tool | Description |
|---|---|
| `list_kubernetes_clusters` | List all Kubernetes clusters in the account |
| `get_kubernetes_cluster` | Get cluster details: status, location, labels |
| `update_kubernetes_cluster` | Update cluster labels (replaces all existing labels) |
| `list_kubernetes_cluster_nodes` | List all nodes in a cluster: role, type, status, IP addresses |
| `get_kubernetes_cluster_node` | Get full details of a specific cluster node |

**Node roles:** `master` · `node`

**Node types:** `cloud` · `baremetal`
102 changes: 102 additions & 0 deletions internal/tools/kubernetes_clusters.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package tools

import (
"context"

"github.com/modelcontextprotocol/go-sdk/mcp"
serverscom "github.com/serverscom/serverscom-go-client/pkg"
)

func registerKubernetesClusterTools(server *mcp.Server, h *handler) {
registerListKubernetesClusters(server, h)
registerGetKubernetesCluster(server, h)
registerUpdateKubernetesCluster(server, h)
registerListKubernetesClusterNodes(server, h)
registerGetKubernetesClusterNode(server, h)
}

type kubernetesClusterIDArgs struct {
ClusterID string `json:"cluster_id" jsonschema:"cluster ID,required"`
}

type updateKubernetesClusterArgs struct {
ClusterID string `json:"cluster_id" jsonschema:"cluster ID,required"`
Labels map[string]string `json:"labels,omitempty" jsonschema:"key-value labels (replaces all existing labels)"`
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update_kubernetes_cluster currently allows calls with only cluster_id because labels is optional. That makes the tool behavior ambiguous (no-op vs clearing labels depending on API semantics). Consider making labels required in the args schema and/or returning a clear error when args.Labels == nil so callers can’t accidentally send an empty update.

Suggested change
Labels map[string]string `json:"labels,omitempty" jsonschema:"key-value labels (replaces all existing labels)"`
Labels map[string]string `json:"labels" jsonschema:"key-value labels (replaces all existing labels),required"`

Copilot uses AI. Check for mistakes.
}

type listKubernetesClusterNodesArgs struct {
ClusterID string `json:"cluster_id" jsonschema:"cluster ID,required"`
}
Comment on lines +27 to +29
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

listKubernetesClusterNodesArgs duplicates kubernetesClusterIDArgs (both only contain cluster_id). Reusing the existing args type would reduce duplication and keep the API surface consistent if the ID field’s schema/description changes later.

Suggested change
type listKubernetesClusterNodesArgs struct {
ClusterID string `json:"cluster_id" jsonschema:"cluster ID,required"`
}
type listKubernetesClusterNodesArgs = kubernetesClusterIDArgs

Copilot uses AI. Check for mistakes.

type kubernetesClusterNodeArgs struct {
ClusterID string `json:"cluster_id" jsonschema:"cluster ID,required"`
NodeID string `json:"node_id" jsonschema:"node ID,required"`
}

func registerListKubernetesClusters(server *mcp.Server, h *handler) {
mcp.AddTool(server, &mcp.Tool{
Name: "list_kubernetes_clusters",
Description: "List all Kubernetes clusters in the account. Returns cluster ID, name, status, location_id, labels, and timestamps for each cluster.",
}, func(ctx context.Context, req *mcp.CallToolRequest, _ struct{}) (*mcp.CallToolResult, any, error) {
clusters, err := h.client.KubernetesClusters.Collection().Collect(ctx)
if err != nil {
return errorResult(err), nil, nil
}
return jsonResult(clusters)
})
}

func registerGetKubernetesCluster(server *mcp.Server, h *handler) {
mcp.AddTool(server, &mcp.Tool{
Name: "get_kubernetes_cluster",
Description: "Get details of a Kubernetes cluster by ID. Returns name, status, location_id, labels, and timestamps.",
}, func(ctx context.Context, req *mcp.CallToolRequest, args kubernetesClusterIDArgs) (*mcp.CallToolResult, any, error) {
cluster, err := h.client.KubernetesClusters.Get(ctx, args.ClusterID)
if err != nil {
return errorResult(err), nil, nil
}
return jsonResult(cluster)
})
}

func registerUpdateKubernetesCluster(server *mcp.Server, h *handler) {
mcp.AddTool(server, &mcp.Tool{
Name: "update_kubernetes_cluster",
Description: "Update labels on a Kubernetes cluster. The provided labels replace all existing labels on the cluster.",
}, func(ctx context.Context, req *mcp.CallToolRequest, args updateKubernetesClusterArgs) (*mcp.CallToolResult, any, error) {
input := serverscom.KubernetesClusterUpdateInput{
Labels: args.Labels,
}
cluster, err := h.client.KubernetesClusters.Update(ctx, args.ClusterID, input)
if err != nil {
return errorResult(err), nil, nil
}
return jsonResult(cluster)
})
}

func registerListKubernetesClusterNodes(server *mcp.Server, h *handler) {
mcp.AddTool(server, &mcp.Tool{
Name: "list_kubernetes_cluster_nodes",
Description: "List all nodes in a Kubernetes cluster. Each node includes: role (node/master), type (cloud/baremetal), status, private and public IPv4 addresses, hostname, configuration, and ref_id.",
}, func(ctx context.Context, req *mcp.CallToolRequest, args listKubernetesClusterNodesArgs) (*mcp.CallToolResult, any, error) {
nodes, err := h.client.KubernetesClusters.Nodes(args.ClusterID).Collect(ctx)
if err != nil {
return errorResult(err), nil, nil
}
return jsonResult(nodes)
})
}

func registerGetKubernetesClusterNode(server *mcp.Server, h *handler) {
mcp.AddTool(server, &mcp.Tool{
Name: "get_kubernetes_cluster_node",
Description: "Get details of a specific node in a Kubernetes cluster. Returns role, type, status, IP addresses, hostname, configuration, ref_id, labels, and timestamps. Use list_kubernetes_cluster_nodes to find node IDs.",
}, func(ctx context.Context, req *mcp.CallToolRequest, args kubernetesClusterNodeArgs) (*mcp.CallToolResult, any, error) {
node, err := h.client.KubernetesClusters.GetNode(ctx, args.ClusterID, args.NodeID)
if err != nil {
return errorResult(err), nil, nil
}
return jsonResult(node)
})
}
1 change: 1 addition & 0 deletions internal/tools/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ func Register(server *mcp.Server, token, endpoint, version string) {
registerNetworkTools(server, h)
registerL2SegmentTools(server, h)
registerRBSTools(server, h)
registerKubernetesClusterTools(server, h)
}

// jsonResult serialises v as indented JSON and wraps it in a TextContent result.
Expand Down