mcp-server-kubernetes 1.6.2 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -22
- package/dist/config/container-templates.d.ts +2 -2
- package/dist/index.d.ts +314 -736
- package/dist/index.js +93 -200
- package/dist/models/resource-models.d.ts +6 -6
- package/dist/tools/kubectl-apply.d.ts +46 -0
- package/dist/tools/kubectl-apply.js +110 -0
- package/dist/tools/kubectl-context.d.ts +49 -0
- package/dist/tools/kubectl-context.js +233 -0
- package/dist/tools/kubectl-create.d.ts +150 -0
- package/dist/tools/kubectl-create.js +321 -0
- package/dist/tools/kubectl-delete.d.ts +77 -0
- package/dist/tools/kubectl-delete.js +177 -0
- package/dist/tools/kubectl-describe.d.ts +47 -0
- package/dist/tools/kubectl-describe.js +96 -0
- package/dist/tools/kubectl-generic.d.ts +71 -0
- package/dist/tools/kubectl-generic.js +121 -0
- package/dist/tools/kubectl-get.d.ts +72 -0
- package/dist/tools/kubectl-get.js +251 -0
- package/dist/tools/kubectl-list.d.ts +61 -0
- package/dist/tools/kubectl-list.js +189 -0
- package/dist/tools/{get_logs.d.ts → kubectl-logs.d.ts} +35 -19
- package/dist/tools/kubectl-logs.js +312 -0
- package/dist/tools/kubectl-patch.d.ts +57 -0
- package/dist/tools/kubectl-patch.js +128 -0
- package/dist/tools/kubectl-rollout.d.ts +67 -0
- package/dist/tools/kubectl-rollout.js +115 -0
- package/dist/tools/{scale_deployment.d.ts → kubectl-scale.d.ts} +13 -3
- package/dist/tools/kubectl-scale.js +73 -0
- package/dist/utils/kubernetes-manager.d.ts +4 -0
- package/dist/utils/kubernetes-manager.js +21 -3
- package/package.json +1 -1
- package/dist/tools/create_configmap.d.ts +0 -33
- package/dist/tools/create_configmap.js +0 -66
- package/dist/tools/create_cronjob.d.ts +0 -47
- package/dist/tools/create_cronjob.js +0 -93
- package/dist/tools/create_deployment.d.ts +0 -135
- package/dist/tools/create_deployment.js +0 -162
- package/dist/tools/create_namespace.d.ts +0 -22
- package/dist/tools/create_namespace.js +0 -48
- package/dist/tools/create_pod.d.ts +0 -130
- package/dist/tools/create_pod.js +0 -153
- package/dist/tools/create_service.d.ts +0 -74
- package/dist/tools/create_service.js +0 -102
- package/dist/tools/delete_configmap.d.ts +0 -26
- package/dist/tools/delete_configmap.js +0 -49
- package/dist/tools/delete_cronjob.d.ts +0 -26
- package/dist/tools/delete_cronjob.js +0 -48
- package/dist/tools/delete_deployment.d.ts +0 -31
- package/dist/tools/delete_deployment.js +0 -47
- package/dist/tools/delete_namespace.d.ts +0 -27
- package/dist/tools/delete_namespace.js +0 -44
- package/dist/tools/delete_pod.d.ts +0 -31
- package/dist/tools/delete_pod.js +0 -45
- package/dist/tools/delete_service.d.ts +0 -32
- package/dist/tools/delete_service.js +0 -46
- package/dist/tools/describe_cronjob.d.ts +0 -27
- package/dist/tools/describe_cronjob.js +0 -83
- package/dist/tools/describe_deployment.d.ts +0 -26
- package/dist/tools/describe_deployment.js +0 -40
- package/dist/tools/describe_node.d.ts +0 -22
- package/dist/tools/describe_node.js +0 -84
- package/dist/tools/describe_pod.d.ts +0 -33
- package/dist/tools/describe_pod.js +0 -81
- package/dist/tools/describe_service.d.ts +0 -34
- package/dist/tools/describe_service.js +0 -85
- package/dist/tools/get_configmap.d.ts +0 -27
- package/dist/tools/get_configmap.js +0 -48
- package/dist/tools/get_current_context.d.ts +0 -23
- package/dist/tools/get_current_context.js +0 -55
- package/dist/tools/get_events.d.ts +0 -28
- package/dist/tools/get_events.js +0 -66
- package/dist/tools/get_job_logs.d.ts +0 -40
- package/dist/tools/get_job_logs.js +0 -104
- package/dist/tools/get_logs.js +0 -150
- package/dist/tools/list_contexts.d.ts +0 -23
- package/dist/tools/list_contexts.js +0 -39
- package/dist/tools/list_cronjobs.d.ts +0 -23
- package/dist/tools/list_cronjobs.js +0 -35
- package/dist/tools/list_deployments.d.ts +0 -23
- package/dist/tools/list_deployments.js +0 -30
- package/dist/tools/list_jobs.d.ts +0 -29
- package/dist/tools/list_jobs.js +0 -77
- package/dist/tools/list_nodes.d.ts +0 -15
- package/dist/tools/list_nodes.js +0 -21
- package/dist/tools/list_pods.d.ts +0 -23
- package/dist/tools/list_pods.js +0 -29
- package/dist/tools/list_services.d.ts +0 -23
- package/dist/tools/list_services.js +0 -31
- package/dist/tools/scale_deployment.js +0 -50
- package/dist/tools/set_current_context.d.ts +0 -23
- package/dist/tools/set_current_context.js +0 -35
- package/dist/tools/update_configmap.d.ts +0 -33
- package/dist/tools/update_configmap.js +0 -71
- package/dist/tools/update_deployment.d.ts +0 -113
- package/dist/tools/update_deployment.js +0 -155
- package/dist/tools/update_service.d.ts +0 -72
- package/dist/tools/update_service.js +0 -125
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { kubectlGet } from "./kubectl-get.js";
|
|
2
|
+
import { execSync } from "child_process";
|
|
3
|
+
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
export const kubectlListSchema = {
|
|
5
|
+
name: "kubectl_list",
|
|
6
|
+
description: "List Kubernetes resources by resource type and optionally namespace",
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: "object",
|
|
9
|
+
properties: {
|
|
10
|
+
resourceType: {
|
|
11
|
+
type: "string",
|
|
12
|
+
description: "Type of resource to list (e.g., pods, deployments, services, configmaps, etc.)"
|
|
13
|
+
},
|
|
14
|
+
namespace: {
|
|
15
|
+
type: "string",
|
|
16
|
+
description: "Namespace of the resources (optional - defaults to 'default' for namespaced resources)",
|
|
17
|
+
default: "default"
|
|
18
|
+
},
|
|
19
|
+
output: {
|
|
20
|
+
type: "string",
|
|
21
|
+
enum: ["json", "yaml", "wide", "name", "custom", "formatted"],
|
|
22
|
+
description: "Output format - 'formatted' uses a resource-specific format with key information",
|
|
23
|
+
default: "formatted"
|
|
24
|
+
},
|
|
25
|
+
allNamespaces: {
|
|
26
|
+
type: "boolean",
|
|
27
|
+
description: "If true, list resources across all namespaces",
|
|
28
|
+
default: false
|
|
29
|
+
},
|
|
30
|
+
labelSelector: {
|
|
31
|
+
type: "string",
|
|
32
|
+
description: "Filter resources by label selector (e.g. 'app=nginx')",
|
|
33
|
+
optional: true
|
|
34
|
+
},
|
|
35
|
+
fieldSelector: {
|
|
36
|
+
type: "string",
|
|
37
|
+
description: "Filter resources by field selector (e.g. 'metadata.name=my-pod')",
|
|
38
|
+
optional: true
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
required: ["resourceType"],
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
export async function kubectlList(k8sManager, input) {
|
|
45
|
+
try {
|
|
46
|
+
const resourceType = input.resourceType.toLowerCase();
|
|
47
|
+
const namespace = input.namespace || "default";
|
|
48
|
+
const output = input.output || "formatted";
|
|
49
|
+
const allNamespaces = input.allNamespaces || false;
|
|
50
|
+
const labelSelector = input.labelSelector || "";
|
|
51
|
+
const fieldSelector = input.fieldSelector || "";
|
|
52
|
+
// If not using formatted output, delegate to kubectl_get
|
|
53
|
+
if (output !== "formatted") {
|
|
54
|
+
return await kubectlGet(k8sManager, {
|
|
55
|
+
resourceType: input.resourceType,
|
|
56
|
+
namespace: input.namespace,
|
|
57
|
+
output: input.output,
|
|
58
|
+
allNamespaces: input.allNamespaces,
|
|
59
|
+
labelSelector: input.labelSelector,
|
|
60
|
+
fieldSelector: input.fieldSelector
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
// For formatted output, we'll use resource-specific custom columns
|
|
64
|
+
let customColumns = "";
|
|
65
|
+
switch (resourceType) {
|
|
66
|
+
case "pods":
|
|
67
|
+
case "pod":
|
|
68
|
+
case "po":
|
|
69
|
+
customColumns = "NAME:.metadata.name,NAMESPACE:.metadata.namespace,STATUS:.status.phase,NODE:.spec.nodeName,IP:.status.podIP,AGE:.metadata.creationTimestamp";
|
|
70
|
+
break;
|
|
71
|
+
case "deployments":
|
|
72
|
+
case "deployment":
|
|
73
|
+
case "deploy":
|
|
74
|
+
customColumns = "NAME:.metadata.name,NAMESPACE:.metadata.namespace,READY:.status.readyReplicas/.status.replicas,UP-TO-DATE:.status.updatedReplicas,AVAILABLE:.status.availableReplicas,AGE:.metadata.creationTimestamp";
|
|
75
|
+
break;
|
|
76
|
+
case "services":
|
|
77
|
+
case "service":
|
|
78
|
+
case "svc":
|
|
79
|
+
customColumns = "NAME:.metadata.name,NAMESPACE:.metadata.namespace,TYPE:.spec.type,CLUSTER-IP:.spec.clusterIP,EXTERNAL-IP:.status.loadBalancer.ingress[0].ip,PORTS:.spec.ports[*].port,AGE:.metadata.creationTimestamp";
|
|
80
|
+
break;
|
|
81
|
+
case "nodes":
|
|
82
|
+
case "node":
|
|
83
|
+
case "no":
|
|
84
|
+
customColumns = "NAME:.metadata.name,STATUS:.status.conditions[?(@.type==\"Ready\")].status,ROLES:.metadata.labels.kubernetes\\.io/role,VERSION:.status.nodeInfo.kubeletVersion,INTERNAL-IP:.status.addresses[?(@.type==\"InternalIP\")].address,OS-IMAGE:.status.nodeInfo.osImage,KERNEL-VERSION:.status.nodeInfo.kernelVersion,CONTAINER-RUNTIME:.status.nodeInfo.containerRuntimeVersion";
|
|
85
|
+
break;
|
|
86
|
+
case "namespaces":
|
|
87
|
+
case "namespace":
|
|
88
|
+
case "ns":
|
|
89
|
+
customColumns = "NAME:.metadata.name,STATUS:.status.phase,AGE:.metadata.creationTimestamp";
|
|
90
|
+
break;
|
|
91
|
+
case "persistentvolumes":
|
|
92
|
+
case "pv":
|
|
93
|
+
customColumns = "NAME:.metadata.name,CAPACITY:.spec.capacity.storage,ACCESS_MODES:.spec.accessModes,RECLAIM_POLICY:.spec.persistentVolumeReclaimPolicy,STATUS:.status.phase,CLAIM:.spec.claimRef.name,STORAGECLASS:.spec.storageClassName,AGE:.metadata.creationTimestamp";
|
|
94
|
+
break;
|
|
95
|
+
case "persistentvolumeclaims":
|
|
96
|
+
case "pvc":
|
|
97
|
+
customColumns = "NAME:.metadata.name,NAMESPACE:.metadata.namespace,STATUS:.status.phase,VOLUME:.spec.volumeName,CAPACITY:.status.capacity.storage,ACCESS_MODES:.spec.accessModes,STORAGECLASS:.spec.storageClassName,AGE:.metadata.creationTimestamp";
|
|
98
|
+
break;
|
|
99
|
+
case "configmaps":
|
|
100
|
+
case "configmap":
|
|
101
|
+
case "cm":
|
|
102
|
+
customColumns = "NAME:.metadata.name,NAMESPACE:.metadata.namespace,DATA:.data,AGE:.metadata.creationTimestamp";
|
|
103
|
+
break;
|
|
104
|
+
case "secrets":
|
|
105
|
+
case "secret":
|
|
106
|
+
customColumns = "NAME:.metadata.name,NAMESPACE:.metadata.namespace,TYPE:.type,DATA:.data,AGE:.metadata.creationTimestamp";
|
|
107
|
+
break;
|
|
108
|
+
case "jobs":
|
|
109
|
+
case "job":
|
|
110
|
+
customColumns = "NAME:.metadata.name,NAMESPACE:.metadata.namespace,COMPLETIONS:.status.succeeded/.spec.completions,DURATION:.status.completionTime-(.status.startTime),AGE:.metadata.creationTimestamp";
|
|
111
|
+
break;
|
|
112
|
+
case "cronjobs":
|
|
113
|
+
case "cronjob":
|
|
114
|
+
case "cj":
|
|
115
|
+
customColumns = "NAME:.metadata.name,NAMESPACE:.metadata.namespace,SCHEDULE:.spec.schedule,SUSPEND:.spec.suspend,ACTIVE:.status.active,LAST_SCHEDULE:.status.lastScheduleTime,AGE:.metadata.creationTimestamp";
|
|
116
|
+
break;
|
|
117
|
+
default:
|
|
118
|
+
// For unknown resource types, fall back to a generic format
|
|
119
|
+
customColumns = "NAME:.metadata.name,NAMESPACE:.metadata.namespace,KIND:.kind,AGE:.metadata.creationTimestamp";
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
// Build the kubectl command
|
|
123
|
+
let command = "kubectl get ";
|
|
124
|
+
// Add resource type
|
|
125
|
+
command += resourceType;
|
|
126
|
+
// Add namespace flag unless all namespaces is specified
|
|
127
|
+
if (allNamespaces) {
|
|
128
|
+
command += " --all-namespaces";
|
|
129
|
+
}
|
|
130
|
+
else if (namespace && !isNonNamespacedResource(resourceType)) {
|
|
131
|
+
command += ` -n ${namespace}`;
|
|
132
|
+
}
|
|
133
|
+
// Add label selector if provided
|
|
134
|
+
if (labelSelector) {
|
|
135
|
+
command += ` -l ${labelSelector}`;
|
|
136
|
+
}
|
|
137
|
+
// Add field selector if provided
|
|
138
|
+
if (fieldSelector) {
|
|
139
|
+
command += ` --field-selector=${fieldSelector}`;
|
|
140
|
+
}
|
|
141
|
+
// Add custom columns format
|
|
142
|
+
command += ` -o custom-columns="${customColumns}"`;
|
|
143
|
+
// Execute the command
|
|
144
|
+
try {
|
|
145
|
+
const result = execSync(command, { encoding: "utf8" });
|
|
146
|
+
return {
|
|
147
|
+
content: [
|
|
148
|
+
{
|
|
149
|
+
type: "text",
|
|
150
|
+
text: result,
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
if (error.status === 404 || error.message.includes("not found")) {
|
|
157
|
+
return {
|
|
158
|
+
content: [
|
|
159
|
+
{
|
|
160
|
+
type: "text",
|
|
161
|
+
text: JSON.stringify({
|
|
162
|
+
error: `Resource type ${resourceType} not found or no resources exist`,
|
|
163
|
+
status: "not_found",
|
|
164
|
+
}, null, 2),
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
isError: true,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
throw new McpError(ErrorCode.InternalError, `Failed to list resources: ${error.message}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
throw new McpError(ErrorCode.InternalError, `Failed to execute kubectl list command: ${error.message}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Helper function to determine if a resource is non-namespaced
|
|
178
|
+
function isNonNamespacedResource(resourceType) {
|
|
179
|
+
const nonNamespacedResources = [
|
|
180
|
+
"nodes", "node", "no",
|
|
181
|
+
"namespaces", "namespace", "ns",
|
|
182
|
+
"persistentvolumes", "pv",
|
|
183
|
+
"storageclasses", "sc",
|
|
184
|
+
"clusterroles",
|
|
185
|
+
"clusterrolebindings",
|
|
186
|
+
"customresourcedefinitions", "crd", "crds"
|
|
187
|
+
];
|
|
188
|
+
return nonNamespacedResources.includes(resourceType.toLowerCase());
|
|
189
|
+
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { KubernetesManager } from "../types.js";
|
|
2
|
-
export declare const
|
|
3
|
-
readonly name: "
|
|
4
|
-
readonly description: "Get logs from pods, deployments,
|
|
2
|
+
export declare const kubectlLogsSchema: {
|
|
3
|
+
readonly name: "kubectl_logs";
|
|
4
|
+
readonly description: "Get logs from Kubernetes resources like pods, deployments, or jobs";
|
|
5
5
|
readonly inputSchema: {
|
|
6
6
|
readonly type: "object";
|
|
7
7
|
readonly properties: {
|
|
8
8
|
readonly resourceType: {
|
|
9
9
|
readonly type: "string";
|
|
10
|
-
readonly enum: readonly ["pod", "deployment", "job"];
|
|
10
|
+
readonly enum: readonly ["pod", "deployment", "job", "cronjob"];
|
|
11
11
|
readonly description: "Type of resource to get logs from";
|
|
12
12
|
};
|
|
13
13
|
readonly name: {
|
|
@@ -19,11 +19,6 @@ export declare const getLogsSchema: {
|
|
|
19
19
|
readonly description: "Namespace of the resource";
|
|
20
20
|
readonly default: "default";
|
|
21
21
|
};
|
|
22
|
-
readonly labelSelector: {
|
|
23
|
-
readonly type: "string";
|
|
24
|
-
readonly description: "Label selector to filter resources";
|
|
25
|
-
readonly optional: true;
|
|
26
|
-
};
|
|
27
22
|
readonly container: {
|
|
28
23
|
readonly type: "string";
|
|
29
24
|
readonly description: "Container name (required when pod has multiple containers)";
|
|
@@ -35,8 +30,13 @@ export declare const getLogsSchema: {
|
|
|
35
30
|
readonly optional: true;
|
|
36
31
|
};
|
|
37
32
|
readonly since: {
|
|
38
|
-
readonly type: "
|
|
39
|
-
readonly description: "
|
|
33
|
+
readonly type: "string";
|
|
34
|
+
readonly description: "Show logs since relative time (e.g. '5s', '2m', '3h')";
|
|
35
|
+
readonly optional: true;
|
|
36
|
+
};
|
|
37
|
+
readonly sinceTime: {
|
|
38
|
+
readonly type: "string";
|
|
39
|
+
readonly description: "Show logs since absolute time (RFC3339)";
|
|
40
40
|
readonly optional: true;
|
|
41
41
|
};
|
|
42
42
|
readonly timestamps: {
|
|
@@ -44,21 +44,37 @@ export declare const getLogsSchema: {
|
|
|
44
44
|
readonly description: "Include timestamps in logs";
|
|
45
45
|
readonly default: false;
|
|
46
46
|
};
|
|
47
|
+
readonly previous: {
|
|
48
|
+
readonly type: "boolean";
|
|
49
|
+
readonly description: "Include logs from previously terminated containers";
|
|
50
|
+
readonly default: false;
|
|
51
|
+
};
|
|
52
|
+
readonly follow: {
|
|
53
|
+
readonly type: "boolean";
|
|
54
|
+
readonly description: "Follow logs output (not recommended, may cause timeouts)";
|
|
55
|
+
readonly default: false;
|
|
56
|
+
};
|
|
57
|
+
readonly labelSelector: {
|
|
58
|
+
readonly type: "string";
|
|
59
|
+
readonly description: "Filter resources by label selector";
|
|
60
|
+
readonly optional: true;
|
|
61
|
+
};
|
|
47
62
|
};
|
|
48
|
-
readonly required: readonly ["resourceType"];
|
|
63
|
+
readonly required: readonly ["resourceType", "name", "namespace"];
|
|
49
64
|
};
|
|
50
65
|
};
|
|
51
|
-
export declare function
|
|
66
|
+
export declare function kubectlLogs(k8sManager: KubernetesManager, input: {
|
|
52
67
|
resourceType: string;
|
|
53
|
-
name
|
|
54
|
-
namespace
|
|
55
|
-
labelSelector?: string;
|
|
68
|
+
name: string;
|
|
69
|
+
namespace: string;
|
|
56
70
|
container?: string;
|
|
57
71
|
tail?: number;
|
|
58
|
-
|
|
72
|
+
since?: string;
|
|
73
|
+
sinceTime?: string;
|
|
59
74
|
timestamps?: boolean;
|
|
60
|
-
|
|
61
|
-
follow?:
|
|
75
|
+
previous?: boolean;
|
|
76
|
+
follow?: boolean;
|
|
77
|
+
labelSelector?: string;
|
|
62
78
|
}): Promise<{
|
|
63
79
|
content: {
|
|
64
80
|
type: string;
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
export const kubectlLogsSchema = {
|
|
4
|
+
name: "kubectl_logs",
|
|
5
|
+
description: "Get logs from Kubernetes resources like pods, deployments, or jobs",
|
|
6
|
+
inputSchema: {
|
|
7
|
+
type: "object",
|
|
8
|
+
properties: {
|
|
9
|
+
resourceType: {
|
|
10
|
+
type: "string",
|
|
11
|
+
enum: ["pod", "deployment", "job", "cronjob"],
|
|
12
|
+
description: "Type of resource to get logs from",
|
|
13
|
+
},
|
|
14
|
+
name: {
|
|
15
|
+
type: "string",
|
|
16
|
+
description: "Name of the resource",
|
|
17
|
+
},
|
|
18
|
+
namespace: {
|
|
19
|
+
type: "string",
|
|
20
|
+
description: "Namespace of the resource",
|
|
21
|
+
default: "default",
|
|
22
|
+
},
|
|
23
|
+
container: {
|
|
24
|
+
type: "string",
|
|
25
|
+
description: "Container name (required when pod has multiple containers)",
|
|
26
|
+
optional: true,
|
|
27
|
+
},
|
|
28
|
+
tail: {
|
|
29
|
+
type: "number",
|
|
30
|
+
description: "Number of lines to show from end of logs",
|
|
31
|
+
optional: true,
|
|
32
|
+
},
|
|
33
|
+
since: {
|
|
34
|
+
type: "string",
|
|
35
|
+
description: "Show logs since relative time (e.g. '5s', '2m', '3h')",
|
|
36
|
+
optional: true,
|
|
37
|
+
},
|
|
38
|
+
sinceTime: {
|
|
39
|
+
type: "string",
|
|
40
|
+
description: "Show logs since absolute time (RFC3339)",
|
|
41
|
+
optional: true,
|
|
42
|
+
},
|
|
43
|
+
timestamps: {
|
|
44
|
+
type: "boolean",
|
|
45
|
+
description: "Include timestamps in logs",
|
|
46
|
+
default: false,
|
|
47
|
+
},
|
|
48
|
+
previous: {
|
|
49
|
+
type: "boolean",
|
|
50
|
+
description: "Include logs from previously terminated containers",
|
|
51
|
+
default: false,
|
|
52
|
+
},
|
|
53
|
+
follow: {
|
|
54
|
+
type: "boolean",
|
|
55
|
+
description: "Follow logs output (not recommended, may cause timeouts)",
|
|
56
|
+
default: false,
|
|
57
|
+
},
|
|
58
|
+
labelSelector: {
|
|
59
|
+
type: "string",
|
|
60
|
+
description: "Filter resources by label selector",
|
|
61
|
+
optional: true,
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
required: ["resourceType", "name", "namespace"],
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
export async function kubectlLogs(k8sManager, input) {
|
|
68
|
+
try {
|
|
69
|
+
const resourceType = input.resourceType.toLowerCase();
|
|
70
|
+
const name = input.name;
|
|
71
|
+
const namespace = input.namespace || "default";
|
|
72
|
+
// Build the kubectl command base
|
|
73
|
+
let baseCommand = `kubectl -n ${namespace}`;
|
|
74
|
+
// Handle different resource types
|
|
75
|
+
if (resourceType === "pod") {
|
|
76
|
+
// Direct pod logs
|
|
77
|
+
baseCommand += ` logs ${name}`;
|
|
78
|
+
// If container is specified, add it
|
|
79
|
+
if (input.container) {
|
|
80
|
+
baseCommand += ` -c ${input.container}`;
|
|
81
|
+
}
|
|
82
|
+
// Add options
|
|
83
|
+
baseCommand = addLogOptions(baseCommand, input);
|
|
84
|
+
// Execute the command
|
|
85
|
+
try {
|
|
86
|
+
const result = execSync(baseCommand, { encoding: "utf8" });
|
|
87
|
+
return formatLogOutput(name, result);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
return handleCommandError(error, `pod ${name}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else if (resourceType === "deployment" || resourceType === "job" || resourceType === "cronjob") {
|
|
94
|
+
// For deployments, jobs and cronjobs we need to find the pods first
|
|
95
|
+
let selectorCommand;
|
|
96
|
+
if (resourceType === "deployment") {
|
|
97
|
+
selectorCommand = `kubectl -n ${namespace} get deployment ${name} -o jsonpath='{.spec.selector.matchLabels}'`;
|
|
98
|
+
}
|
|
99
|
+
else if (resourceType === "job") {
|
|
100
|
+
// For jobs, we use the job-name label
|
|
101
|
+
return getLabelSelectorLogs(`job-name=${name}`, namespace, input);
|
|
102
|
+
}
|
|
103
|
+
else if (resourceType === "cronjob") {
|
|
104
|
+
// For cronjobs, it's more complex - need to find the job first
|
|
105
|
+
const jobsCommand = `kubectl -n ${namespace} get jobs --selector=job-name=${name} -o jsonpath='{.items[*].metadata.name}'`;
|
|
106
|
+
try {
|
|
107
|
+
const jobs = execSync(jobsCommand, { encoding: "utf8" }).trim().split(' ');
|
|
108
|
+
if (jobs.length === 0 || (jobs.length === 1 && jobs[0] === '')) {
|
|
109
|
+
return {
|
|
110
|
+
content: [
|
|
111
|
+
{
|
|
112
|
+
type: "text",
|
|
113
|
+
text: JSON.stringify({
|
|
114
|
+
message: `No jobs found for cronjob ${name} in namespace ${namespace}`,
|
|
115
|
+
}, null, 2),
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
// Get logs for all jobs
|
|
121
|
+
const allJobLogs = {};
|
|
122
|
+
for (const job of jobs) {
|
|
123
|
+
// Get logs for pods from this job
|
|
124
|
+
const result = await getLabelSelectorLogs(`job-name=${job}`, namespace, input);
|
|
125
|
+
const jobLog = JSON.parse(result.content[0].text);
|
|
126
|
+
allJobLogs[job] = jobLog.logs;
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
content: [
|
|
130
|
+
{
|
|
131
|
+
type: "text",
|
|
132
|
+
text: JSON.stringify({
|
|
133
|
+
cronjob: name,
|
|
134
|
+
namespace: namespace,
|
|
135
|
+
jobs: allJobLogs,
|
|
136
|
+
}, null, 2),
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
return handleCommandError(error, `cronjob ${name}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
if (resourceType === "deployment") {
|
|
147
|
+
// Get the deployment's selector
|
|
148
|
+
if (!selectorCommand) {
|
|
149
|
+
throw new Error("Selector command is undefined");
|
|
150
|
+
}
|
|
151
|
+
const selectorJson = execSync(selectorCommand, { encoding: "utf8" }).trim();
|
|
152
|
+
const selector = JSON.parse(selectorJson.replace(/'/g, '"'));
|
|
153
|
+
// Convert to label selector format
|
|
154
|
+
const labelSelector = Object.entries(selector)
|
|
155
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
156
|
+
.join(',');
|
|
157
|
+
return getLabelSelectorLogs(labelSelector, namespace, input);
|
|
158
|
+
}
|
|
159
|
+
// For jobs and cronjobs, the logic is handled above
|
|
160
|
+
return {
|
|
161
|
+
content: [
|
|
162
|
+
{
|
|
163
|
+
type: "text",
|
|
164
|
+
text: JSON.stringify({
|
|
165
|
+
error: `Unexpected resource type: ${resourceType}`,
|
|
166
|
+
}, null, 2),
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
isError: true,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
return handleCommandError(error, `${resourceType} ${name}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
else if (input.labelSelector) {
|
|
177
|
+
// Handle logs by label selector
|
|
178
|
+
return getLabelSelectorLogs(input.labelSelector, namespace, input);
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
throw new McpError(ErrorCode.InvalidRequest, `Unsupported resource type: ${resourceType}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
if (error instanceof McpError)
|
|
186
|
+
throw error;
|
|
187
|
+
throw new McpError(ErrorCode.InternalError, `Failed to get logs: ${error.message}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// Helper function to add log options to the kubectl command
|
|
191
|
+
function addLogOptions(baseCommand, input) {
|
|
192
|
+
let command = baseCommand;
|
|
193
|
+
// Add options based on inputs
|
|
194
|
+
if (input.tail !== undefined) {
|
|
195
|
+
command += ` --tail=${input.tail}`;
|
|
196
|
+
}
|
|
197
|
+
if (input.since) {
|
|
198
|
+
command += ` --since=${input.since}`;
|
|
199
|
+
}
|
|
200
|
+
if (input.sinceTime) {
|
|
201
|
+
command += ` --since-time=${input.sinceTime}`;
|
|
202
|
+
}
|
|
203
|
+
if (input.timestamps) {
|
|
204
|
+
command += ` --timestamps`;
|
|
205
|
+
}
|
|
206
|
+
if (input.previous) {
|
|
207
|
+
command += ` --previous`;
|
|
208
|
+
}
|
|
209
|
+
if (input.follow) {
|
|
210
|
+
command += ` --follow`;
|
|
211
|
+
}
|
|
212
|
+
return command;
|
|
213
|
+
}
|
|
214
|
+
// Helper function to get logs for resources selected by labels
|
|
215
|
+
async function getLabelSelectorLogs(labelSelector, namespace, input) {
|
|
216
|
+
try {
|
|
217
|
+
// First, find all pods matching the label selector
|
|
218
|
+
const podsCommand = `kubectl -n ${namespace} get pods --selector=${labelSelector} -o jsonpath='{.items[*].metadata.name}'`;
|
|
219
|
+
const pods = execSync(podsCommand, { encoding: "utf8" }).trim().split(' ');
|
|
220
|
+
if (pods.length === 0 || (pods.length === 1 && pods[0] === '')) {
|
|
221
|
+
return {
|
|
222
|
+
content: [
|
|
223
|
+
{
|
|
224
|
+
type: "text",
|
|
225
|
+
text: JSON.stringify({
|
|
226
|
+
message: `No pods found with label selector "${labelSelector}" in namespace ${namespace}`,
|
|
227
|
+
}, null, 2),
|
|
228
|
+
},
|
|
229
|
+
],
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
// Get logs for each pod
|
|
233
|
+
const logsMap = {};
|
|
234
|
+
for (const pod of pods) {
|
|
235
|
+
// Skip empty pod names
|
|
236
|
+
if (!pod)
|
|
237
|
+
continue;
|
|
238
|
+
let podCommand = `kubectl -n ${namespace} logs ${pod}`;
|
|
239
|
+
// Add container if specified
|
|
240
|
+
if (input.container) {
|
|
241
|
+
podCommand += ` -c ${input.container}`;
|
|
242
|
+
}
|
|
243
|
+
// Add other options
|
|
244
|
+
podCommand = addLogOptions(podCommand, input);
|
|
245
|
+
try {
|
|
246
|
+
const logs = execSync(podCommand, { encoding: "utf8" });
|
|
247
|
+
logsMap[pod] = logs;
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
logsMap[pod] = `Error: ${error.message}`;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
content: [
|
|
255
|
+
{
|
|
256
|
+
type: "text",
|
|
257
|
+
text: JSON.stringify({
|
|
258
|
+
selector: labelSelector,
|
|
259
|
+
namespace: namespace,
|
|
260
|
+
logs: logsMap,
|
|
261
|
+
}, null, 2),
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
return handleCommandError(error, `pods with selector "${labelSelector}"`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// Helper function to format log output
|
|
271
|
+
function formatLogOutput(resourceName, logOutput) {
|
|
272
|
+
return {
|
|
273
|
+
content: [
|
|
274
|
+
{
|
|
275
|
+
type: "text",
|
|
276
|
+
text: JSON.stringify({
|
|
277
|
+
name: resourceName,
|
|
278
|
+
logs: logOutput,
|
|
279
|
+
}, null, 2),
|
|
280
|
+
},
|
|
281
|
+
],
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
// Helper function to handle command errors
|
|
285
|
+
function handleCommandError(error, resourceDescription) {
|
|
286
|
+
console.error(`Error getting logs for ${resourceDescription}:`, error);
|
|
287
|
+
if (error.status === 404 || error.message.includes("not found")) {
|
|
288
|
+
return {
|
|
289
|
+
content: [
|
|
290
|
+
{
|
|
291
|
+
type: "text",
|
|
292
|
+
text: JSON.stringify({
|
|
293
|
+
error: `Resource ${resourceDescription} not found`,
|
|
294
|
+
status: "not_found",
|
|
295
|
+
}, null, 2),
|
|
296
|
+
},
|
|
297
|
+
],
|
|
298
|
+
isError: true,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
return {
|
|
302
|
+
content: [
|
|
303
|
+
{
|
|
304
|
+
type: "text",
|
|
305
|
+
text: JSON.stringify({
|
|
306
|
+
error: `Failed to get logs for ${resourceDescription}: ${error.message}`,
|
|
307
|
+
}, null, 2),
|
|
308
|
+
},
|
|
309
|
+
],
|
|
310
|
+
isError: true,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { KubernetesManager } from "../types.js";
|
|
2
|
+
export declare const kubectlPatchSchema: {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
inputSchema: {
|
|
6
|
+
type: string;
|
|
7
|
+
properties: {
|
|
8
|
+
resourceType: {
|
|
9
|
+
type: string;
|
|
10
|
+
description: string;
|
|
11
|
+
};
|
|
12
|
+
name: {
|
|
13
|
+
type: string;
|
|
14
|
+
description: string;
|
|
15
|
+
};
|
|
16
|
+
namespace: {
|
|
17
|
+
type: string;
|
|
18
|
+
description: string;
|
|
19
|
+
default: string;
|
|
20
|
+
};
|
|
21
|
+
patchType: {
|
|
22
|
+
type: string;
|
|
23
|
+
description: string;
|
|
24
|
+
enum: string[];
|
|
25
|
+
default: string;
|
|
26
|
+
};
|
|
27
|
+
patchData: {
|
|
28
|
+
type: string;
|
|
29
|
+
description: string;
|
|
30
|
+
};
|
|
31
|
+
patchFile: {
|
|
32
|
+
type: string;
|
|
33
|
+
description: string;
|
|
34
|
+
};
|
|
35
|
+
dryRun: {
|
|
36
|
+
type: string;
|
|
37
|
+
description: string;
|
|
38
|
+
default: boolean;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
required: string[];
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
export declare function kubectlPatch(k8sManager: KubernetesManager, input: {
|
|
45
|
+
resourceType: string;
|
|
46
|
+
name: string;
|
|
47
|
+
namespace?: string;
|
|
48
|
+
patchType?: "strategic" | "merge" | "json";
|
|
49
|
+
patchData?: object;
|
|
50
|
+
patchFile?: string;
|
|
51
|
+
dryRun?: boolean;
|
|
52
|
+
}): Promise<{
|
|
53
|
+
content: {
|
|
54
|
+
type: string;
|
|
55
|
+
text: string;
|
|
56
|
+
}[];
|
|
57
|
+
}>;
|