mcp-server-kubernetes 1.0.1 → 1.2.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 CHANGED
@@ -66,16 +66,10 @@ npx mcp-chat --config "%APPDATA%\Claude\claude_desktop_config.json"
66
66
  ## Features
67
67
 
68
68
  - [x] Connect to a Kubernetes cluster
69
- - [x] List all pods
70
- - [x] List all services
71
- - [x] List all deployments
72
- - [x] List all nodes
73
- - [x] Create a pod
74
- - [x] Delete a pod
75
- - [x] Describe a pod
76
- - [x] List all namespaces
77
- - [x] Create a namespace
78
- - [x] Create custom pod & deployment configs
69
+ - [x] List all pods, services, deployments, nodes
70
+ - [x] Create, describe, delete a pod
71
+ - [x] List all namespaces, create a namespace
72
+ - [x] Create custom pod & deployment configs, update deployment replicas
79
73
  - [x] Get logs from a pod for debugging (supports pods, deployments, jobs, and label selectors)
80
74
  - [x] Support Helm v3 for installing charts
81
75
  - Install charts with custom values
@@ -87,6 +81,7 @@ npx mcp-chat --config "%APPDATA%\Claude\claude_desktop_config.json"
87
81
  - [x] kubectl explain and kubectl api-resources support
88
82
  - [x] Get Kubernetes events from the cluster
89
83
  - [x] Port forward to a pod or service
84
+ - [x] Create, list, and decribe cronjobs
90
85
 
91
86
  ## Local Development
92
87
 
package/dist/index.js CHANGED
@@ -5,10 +5,16 @@ import { listPods, listPodsSchema } from "./tools/list_pods.js";
5
5
  import { listNodes, listNodesSchema } from "./tools/list_nodes.js";
6
6
  import { listServices, listServicesSchema } from "./tools/list_services.js";
7
7
  import { listDeployments, listDeploymentsSchema, } from "./tools/list_deployments.js";
8
+ import { listCronJobs, listCronJobsSchema } from "./tools/list_cronjobs.js";
9
+ import { describeCronJob, describeCronJobSchema } from "./tools/describe_cronjob.js";
10
+ import { listJobs, listJobsSchema } from "./tools/list_jobs.js";
11
+ import { getJobLogs, getJobLogsSchema } from "./tools/get_job_logs.js";
8
12
  import { installHelmChart, installHelmChartSchema, upgradeHelmChart, upgradeHelmChartSchema, uninstallHelmChart, uninstallHelmChartSchema, } from "./tools/helm-operations.js";
9
13
  import { explainResource, explainResourceSchema, listApiResources, listApiResourcesSchema, } from "./tools/kubectl-operations.js";
10
14
  import { createNamespace, createNamespaceSchema, } from "./tools/create_namespace.js";
11
15
  import { createPod, createPodSchema } from "./tools/create_pod.js";
16
+ import { createCronJob, createCronJobSchema } from "./tools/create_cronjob.js";
17
+ import { DeleteCronJob, DeleteCronJobSchema } from "./tools/delete_cronjob.js";
12
18
  import { deletePod, deletePodSchema } from "./tools/delete_pod.js";
13
19
  import { describePod, describePodSchema } from "./tools/describe_pod.js";
14
20
  import { getLogs, getLogsSchema } from "./tools/get_logs.js";
@@ -19,11 +25,13 @@ import { KubernetesManager } from "./types.js";
19
25
  import { serverConfig } from "./config/server-config.js";
20
26
  import { createDeploymentSchema } from "./config/deployment-config.js";
21
27
  import { listNamespacesSchema } from "./config/namespace-config.js";
28
+ import { deleteNamespace, deleteNamespaceSchema } from "./tools/delete_namespace.js";
22
29
  import { cleanupSchema } from "./config/cleanup-config.js";
23
30
  import { startSSEServer } from "./utils/sse.js";
24
31
  import { startPortForward, PortForwardSchema, stopPortForward, StopPortForwardSchema, } from "./tools/port_forward.js";
25
- import { deleteDeployment } from "./tools/delete_deployment.js";
32
+ import { deleteDeployment, deleteDeploymentSchema } from "./tools/delete_deployment.js";
26
33
  import { createDeployment } from "./tools/create_deployment.js";
34
+ import { scaleDeployment, scaleDeploymentSchema } from "./tools/scale_deployment.js";
27
35
  import { describeDeployment, describeDeploymentSchema, } from "./tools/describe_deployment.js";
28
36
  const k8sManager = new KubernetesManager();
29
37
  const server = new Server({
@@ -38,15 +46,22 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
38
46
  createDeploymentSchema,
39
47
  createNamespaceSchema,
40
48
  createPodSchema,
49
+ createCronJobSchema,
41
50
  deletePodSchema,
51
+ deleteDeploymentSchema,
52
+ deleteNamespaceSchema,
53
+ describeCronJobSchema,
42
54
  describePodSchema,
43
55
  describeDeploymentSchema,
44
56
  explainResourceSchema,
45
57
  getEventsSchema,
58
+ getJobLogsSchema,
46
59
  getLogsSchema,
47
60
  installHelmChartSchema,
48
61
  listApiResourcesSchema,
62
+ listCronJobsSchema,
49
63
  listDeploymentsSchema,
64
+ listJobsSchema,
50
65
  listNamespacesSchema,
51
66
  listNodesSchema,
52
67
  listPodsSchema,
@@ -55,6 +70,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
55
70
  upgradeHelmChartSchema,
56
71
  PortForwardSchema,
57
72
  StopPortForwardSchema,
73
+ scaleDeploymentSchema,
74
+ DeleteCronJobSchema,
58
75
  ],
59
76
  };
60
77
  });
@@ -81,6 +98,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
81
98
  case "create_pod": {
82
99
  return await createPod(k8sManager, input);
83
100
  }
101
+ case "create_cronjob": {
102
+ return await createCronJob(k8sManager, input);
103
+ }
104
+ case "delete_cronjob": {
105
+ return await DeleteCronJob(k8sManager, input);
106
+ }
84
107
  case "delete_pod": {
85
108
  return await deletePod(k8sManager, input);
86
109
  }
@@ -130,6 +153,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
130
153
  case "list_services": {
131
154
  return await listServices(k8sManager, input);
132
155
  }
156
+ case "list_cronjobs": {
157
+ return await listCronJobs(k8sManager, input);
158
+ }
159
+ case "describe_cronjob": {
160
+ return await describeCronJob(k8sManager, input);
161
+ }
162
+ case "list_jobs": {
163
+ return await listJobs(k8sManager, input);
164
+ }
165
+ case "get_job_logs": {
166
+ return await getJobLogs(k8sManager, input);
167
+ }
133
168
  case "uninstall_helm_chart": {
134
169
  return await uninstallHelmChart(input);
135
170
  }
@@ -142,6 +177,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
142
177
  case "stop_port_forward": {
143
178
  return await stopPortForward(k8sManager, input);
144
179
  }
180
+ case "delete_namespace": {
181
+ return await deleteNamespace(k8sManager, input);
182
+ }
145
183
  case "delete_deployment": {
146
184
  return await deleteDeployment(k8sManager, input);
147
185
  }
@@ -151,6 +189,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
151
189
  case "describe_deployment": {
152
190
  return await describeDeployment(k8sManager, input);
153
191
  }
192
+ case "scale_deployment": {
193
+ return await scaleDeployment(k8sManager, input);
194
+ }
154
195
  default:
155
196
  throw new McpError(ErrorCode.InvalidRequest, `Unknown tool: ${name}`);
156
197
  }
@@ -21,6 +21,28 @@ export declare const CreateNamespaceResponseSchema: z.ZodObject<{
21
21
  text: string;
22
22
  }[];
23
23
  }>;
24
+ export declare const DeleteNamespaceResponseSchema: z.ZodObject<{
25
+ content: z.ZodArray<z.ZodObject<{
26
+ type: z.ZodLiteral<"text">;
27
+ text: z.ZodString;
28
+ }, "strip", z.ZodTypeAny, {
29
+ type: "text";
30
+ text: string;
31
+ }, {
32
+ type: "text";
33
+ text: string;
34
+ }>, "many">;
35
+ }, "strip", z.ZodTypeAny, {
36
+ content: {
37
+ type: "text";
38
+ text: string;
39
+ }[];
40
+ }, {
41
+ content: {
42
+ type: "text";
43
+ text: string;
44
+ }[];
45
+ }>;
24
46
  export declare const CreatePodResponseSchema: z.ZodObject<{
25
47
  content: z.ZodArray<z.ZodObject<{
26
48
  type: z.ZodLiteral<"text">;
@@ -285,6 +307,116 @@ export declare const GetEventsResponseSchema: z.ZodObject<{
285
307
  text: string;
286
308
  }[];
287
309
  }>;
310
+ export declare const ListCronJobsResponseSchema: z.ZodObject<{
311
+ content: z.ZodArray<z.ZodObject<{
312
+ type: z.ZodLiteral<"text">;
313
+ text: z.ZodString;
314
+ }, "strip", z.ZodTypeAny, {
315
+ type: "text";
316
+ text: string;
317
+ }, {
318
+ type: "text";
319
+ text: string;
320
+ }>, "many">;
321
+ }, "strip", z.ZodTypeAny, {
322
+ content: {
323
+ type: "text";
324
+ text: string;
325
+ }[];
326
+ }, {
327
+ content: {
328
+ type: "text";
329
+ text: string;
330
+ }[];
331
+ }>;
332
+ export declare const CreateCronJobResponseSchema: z.ZodObject<{
333
+ content: z.ZodArray<z.ZodObject<{
334
+ type: z.ZodLiteral<"text">;
335
+ text: z.ZodString;
336
+ }, "strip", z.ZodTypeAny, {
337
+ type: "text";
338
+ text: string;
339
+ }, {
340
+ type: "text";
341
+ text: string;
342
+ }>, "many">;
343
+ }, "strip", z.ZodTypeAny, {
344
+ content: {
345
+ type: "text";
346
+ text: string;
347
+ }[];
348
+ }, {
349
+ content: {
350
+ type: "text";
351
+ text: string;
352
+ }[];
353
+ }>;
354
+ export declare const DescribeCronJobResponseSchema: z.ZodObject<{
355
+ content: z.ZodArray<z.ZodObject<{
356
+ type: z.ZodLiteral<"text">;
357
+ text: z.ZodString;
358
+ }, "strip", z.ZodTypeAny, {
359
+ type: "text";
360
+ text: string;
361
+ }, {
362
+ type: "text";
363
+ text: string;
364
+ }>, "many">;
365
+ }, "strip", z.ZodTypeAny, {
366
+ content: {
367
+ type: "text";
368
+ text: string;
369
+ }[];
370
+ }, {
371
+ content: {
372
+ type: "text";
373
+ text: string;
374
+ }[];
375
+ }>;
376
+ export declare const ListJobsResponseSchema: z.ZodObject<{
377
+ content: z.ZodArray<z.ZodObject<{
378
+ type: z.ZodLiteral<"text">;
379
+ text: z.ZodString;
380
+ }, "strip", z.ZodTypeAny, {
381
+ type: "text";
382
+ text: string;
383
+ }, {
384
+ type: "text";
385
+ text: string;
386
+ }>, "many">;
387
+ }, "strip", z.ZodTypeAny, {
388
+ content: {
389
+ type: "text";
390
+ text: string;
391
+ }[];
392
+ }, {
393
+ content: {
394
+ type: "text";
395
+ text: string;
396
+ }[];
397
+ }>;
398
+ export declare const GetJobLogsResponseSchema: z.ZodObject<{
399
+ content: z.ZodArray<z.ZodObject<{
400
+ type: z.ZodLiteral<"text">;
401
+ text: z.ZodString;
402
+ }, "strip", z.ZodTypeAny, {
403
+ type: "text";
404
+ text: string;
405
+ }, {
406
+ type: "text";
407
+ text: string;
408
+ }>, "many">;
409
+ }, "strip", z.ZodTypeAny, {
410
+ content: {
411
+ type: "text";
412
+ text: string;
413
+ }[];
414
+ }, {
415
+ content: {
416
+ type: "text";
417
+ text: string;
418
+ }[];
419
+ }>;
288
420
  export declare const PortForwardResponseSchema: z.ZodObject<{
289
421
  content: z.ZodArray<z.ZodObject<{
290
422
  success: z.ZodBoolean;
@@ -307,3 +439,47 @@ export declare const PortForwardResponseSchema: z.ZodObject<{
307
439
  success: boolean;
308
440
  }[];
309
441
  }>;
442
+ export declare const ScaleDeploymentResponseSchema: z.ZodObject<{
443
+ content: z.ZodArray<z.ZodObject<{
444
+ success: z.ZodBoolean;
445
+ message: z.ZodString;
446
+ }, "strip", z.ZodTypeAny, {
447
+ message: string;
448
+ success: boolean;
449
+ }, {
450
+ message: string;
451
+ success: boolean;
452
+ }>, "many">;
453
+ }, "strip", z.ZodTypeAny, {
454
+ content: {
455
+ message: string;
456
+ success: boolean;
457
+ }[];
458
+ }, {
459
+ content: {
460
+ message: string;
461
+ success: boolean;
462
+ }[];
463
+ }>;
464
+ export declare const DeleteCronJobResponseSchema: z.ZodObject<{
465
+ content: z.ZodArray<z.ZodObject<{
466
+ success: z.ZodBoolean;
467
+ message: z.ZodString;
468
+ }, "strip", z.ZodTypeAny, {
469
+ message: string;
470
+ success: boolean;
471
+ }, {
472
+ message: string;
473
+ success: boolean;
474
+ }>, "many">;
475
+ }, "strip", z.ZodTypeAny, {
476
+ content: {
477
+ message: string;
478
+ success: boolean;
479
+ }[];
480
+ }, {
481
+ content: {
482
+ message: string;
483
+ success: boolean;
484
+ }[];
485
+ }>;
@@ -7,6 +7,9 @@ const ToolResponseContent = z.object({
7
7
  export const CreateNamespaceResponseSchema = z.object({
8
8
  content: z.array(ToolResponseContent),
9
9
  });
10
+ export const DeleteNamespaceResponseSchema = z.object({
11
+ content: z.array(ToolResponseContent),
12
+ });
10
13
  export const CreatePodResponseSchema = z.object({
11
14
  content: z.array(ToolResponseContent),
12
15
  });
@@ -43,9 +46,36 @@ export const GetLogsResponseSchema = z.object({
43
46
  export const GetEventsResponseSchema = z.object({
44
47
  content: z.array(ToolResponseContent),
45
48
  });
49
+ export const ListCronJobsResponseSchema = z.object({
50
+ content: z.array(ToolResponseContent),
51
+ });
52
+ export const CreateCronJobResponseSchema = z.object({
53
+ content: z.array(ToolResponseContent),
54
+ });
55
+ export const DescribeCronJobResponseSchema = z.object({
56
+ content: z.array(ToolResponseContent),
57
+ });
58
+ export const ListJobsResponseSchema = z.object({
59
+ content: z.array(ToolResponseContent),
60
+ });
61
+ export const GetJobLogsResponseSchema = z.object({
62
+ content: z.array(ToolResponseContent),
63
+ });
46
64
  export const PortForwardResponseSchema = z.object({
47
65
  content: z.array(z.object({
48
66
  success: z.boolean(),
49
67
  message: z.string(),
50
68
  })),
51
69
  });
70
+ export const ScaleDeploymentResponseSchema = z.object({
71
+ content: z.array(z.object({
72
+ success: z.boolean(),
73
+ message: z.string(),
74
+ })),
75
+ });
76
+ export const DeleteCronJobResponseSchema = z.object({
77
+ content: z.array(z.object({
78
+ success: z.boolean(),
79
+ message: z.string(),
80
+ })),
81
+ });
@@ -0,0 +1,47 @@
1
+ import { KubernetesManager } from "../types.js";
2
+ export declare const createCronJobSchema: {
3
+ readonly name: "create_cronjob";
4
+ readonly description: "Create a new Kubernetes CronJob";
5
+ readonly inputSchema: {
6
+ readonly type: "object";
7
+ readonly properties: {
8
+ readonly name: {
9
+ readonly type: "string";
10
+ };
11
+ readonly namespace: {
12
+ readonly type: "string";
13
+ };
14
+ readonly schedule: {
15
+ readonly type: "string";
16
+ };
17
+ readonly image: {
18
+ readonly type: "string";
19
+ };
20
+ readonly command: {
21
+ readonly type: "array";
22
+ readonly items: {
23
+ readonly type: "string";
24
+ };
25
+ readonly optional: true;
26
+ };
27
+ readonly suspend: {
28
+ readonly type: "boolean";
29
+ readonly optional: true;
30
+ };
31
+ };
32
+ readonly required: readonly ["name", "namespace", "schedule", "image"];
33
+ };
34
+ };
35
+ export declare function createCronJob(k8sManager: KubernetesManager, input: {
36
+ name: string;
37
+ namespace: string;
38
+ schedule: string;
39
+ image: string;
40
+ command?: string[];
41
+ suspend?: boolean;
42
+ }): Promise<{
43
+ content: {
44
+ type: string;
45
+ text: string;
46
+ }[];
47
+ }>;
@@ -0,0 +1,93 @@
1
+ export const createCronJobSchema = {
2
+ name: "create_cronjob",
3
+ description: "Create a new Kubernetes CronJob",
4
+ inputSchema: {
5
+ type: "object",
6
+ properties: {
7
+ name: { type: "string" },
8
+ namespace: { type: "string" },
9
+ schedule: { type: "string" },
10
+ image: { type: "string" },
11
+ command: {
12
+ type: "array",
13
+ items: { type: "string" },
14
+ optional: true,
15
+ },
16
+ suspend: {
17
+ type: "boolean",
18
+ optional: true,
19
+ },
20
+ },
21
+ required: ["name", "namespace", "schedule", "image"],
22
+ },
23
+ };
24
+ export async function createCronJob(k8sManager, input) {
25
+ try {
26
+ const cronJob = {
27
+ apiVersion: "batch/v1",
28
+ kind: "CronJob",
29
+ metadata: {
30
+ name: input.name,
31
+ namespace: input.namespace,
32
+ labels: {
33
+ "mcp-managed": "true",
34
+ app: input.name,
35
+ },
36
+ },
37
+ spec: {
38
+ schedule: input.schedule,
39
+ suspend: input.suspend || false,
40
+ jobTemplate: {
41
+ spec: {
42
+ template: {
43
+ spec: {
44
+ containers: [
45
+ {
46
+ name: input.name,
47
+ image: input.image,
48
+ ...(input.command && {
49
+ command: input.command,
50
+ }),
51
+ },
52
+ ],
53
+ restartPolicy: "OnFailure",
54
+ },
55
+ },
56
+ },
57
+ },
58
+ },
59
+ };
60
+ const response = await k8sManager
61
+ .getBatchApi()
62
+ .createNamespacedCronJob(input.namespace, cronJob)
63
+ .catch((error) => {
64
+ console.error("CronJob creation error:", {
65
+ status: error.response?.statusCode,
66
+ message: error.response?.body?.message || error.message,
67
+ details: error.response?.body,
68
+ });
69
+ throw error;
70
+ });
71
+ k8sManager.trackResource("CronJob", input.name, input.namespace);
72
+ return {
73
+ content: [
74
+ {
75
+ type: "text",
76
+ text: JSON.stringify({
77
+ cronJobName: response.body.metadata.name,
78
+ schedule: response.body.spec.schedule,
79
+ status: "created",
80
+ }, null, 2),
81
+ },
82
+ ],
83
+ };
84
+ }
85
+ catch (error) {
86
+ console.error("CronJob creation error:", {
87
+ status: error.response?.statusCode,
88
+ message: error.response?.body?.message || error.message,
89
+ details: error.response?.body,
90
+ });
91
+ throw error;
92
+ }
93
+ }
@@ -0,0 +1,26 @@
1
+ import { KubernetesManager } from "../types.js";
2
+ export declare const DeleteCronJobSchema: {
3
+ readonly name: "delete_cronjob";
4
+ readonly description: "Delete a Kubernetes CronJob";
5
+ readonly inputSchema: {
6
+ readonly type: "object";
7
+ readonly properties: {
8
+ readonly name: {
9
+ readonly type: "string";
10
+ };
11
+ readonly namespace: {
12
+ readonly type: "string";
13
+ };
14
+ };
15
+ readonly required: readonly ["name", "namespace"];
16
+ };
17
+ };
18
+ export declare function DeleteCronJob(k8sManager: KubernetesManager, input: {
19
+ name: string;
20
+ namespace: string;
21
+ }): Promise<{
22
+ content: {
23
+ success: boolean;
24
+ message: string;
25
+ }[];
26
+ }>;
@@ -0,0 +1,48 @@
1
+ export const DeleteCronJobSchema = {
2
+ name: "delete_cronjob",
3
+ description: "Delete a Kubernetes CronJob",
4
+ inputSchema: {
5
+ type: "object",
6
+ properties: {
7
+ name: { type: "string" },
8
+ namespace: { type: "string" }
9
+ },
10
+ required: ["name", "namespace"]
11
+ },
12
+ };
13
+ export async function DeleteCronJob(k8sManager, input) {
14
+ try {
15
+ const response = await k8sManager.getBatchApi().deleteNamespacedCronJob(input.name, input.namespace);
16
+ if (response.response?.statusCode !== undefined && (response.response.statusCode === 200 || response.response.statusCode === 202)) {
17
+ return {
18
+ content: [
19
+ {
20
+ success: true,
21
+ message: `Deleted cronjob ${input.name} in namespace ${input.namespace}.` +
22
+ (response.body?.details ? ` Details: ${response.body.details}` : "")
23
+ }
24
+ ]
25
+ };
26
+ }
27
+ else {
28
+ return {
29
+ content: [
30
+ {
31
+ success: false,
32
+ message: `Failed to delete cronjob ${input.name} in namespace ${input.namespace}.` + (response.body?.details ? ` Details: ${response.body.details}` : "")
33
+ }
34
+ ]
35
+ };
36
+ }
37
+ }
38
+ catch (error) {
39
+ return {
40
+ content: [
41
+ {
42
+ success: false,
43
+ message: `Failed to delete cronjob: ${error.message}`
44
+ }
45
+ ]
46
+ };
47
+ }
48
+ }
@@ -0,0 +1,27 @@
1
+ import { KubernetesManager } from "../types.js";
2
+ export declare const deleteNamespaceSchema: {
3
+ readonly name: "delete_namespace";
4
+ readonly description: "Delete a Kubernetes namespace";
5
+ readonly inputSchema: {
6
+ readonly type: "object";
7
+ readonly properties: {
8
+ readonly name: {
9
+ readonly type: "string";
10
+ };
11
+ readonly ignoreNotFound: {
12
+ readonly type: "boolean";
13
+ readonly default: false;
14
+ };
15
+ };
16
+ readonly required: readonly ["name"];
17
+ };
18
+ };
19
+ export declare function deleteNamespace(k8sManager: KubernetesManager, input: {
20
+ name: string;
21
+ ignoreNotFound?: boolean;
22
+ }): Promise<{
23
+ content: {
24
+ type: string;
25
+ text: string;
26
+ }[];
27
+ }>;
@@ -0,0 +1,44 @@
1
+ export const deleteNamespaceSchema = {
2
+ name: "delete_namespace",
3
+ description: "Delete a Kubernetes namespace",
4
+ inputSchema: {
5
+ type: "object",
6
+ properties: {
7
+ name: { type: "string" },
8
+ ignoreNotFound: { type: "boolean", default: false },
9
+ },
10
+ required: ["name"],
11
+ },
12
+ };
13
+ export async function deleteNamespace(k8sManager, input) {
14
+ try {
15
+ await k8sManager.getCoreApi().deleteNamespace(input.name);
16
+ return {
17
+ content: [
18
+ {
19
+ type: "text",
20
+ text: JSON.stringify({
21
+ success: true,
22
+ status: "deleted",
23
+ }, null, 2),
24
+ },
25
+ ],
26
+ };
27
+ }
28
+ catch (error) {
29
+ if (input.ignoreNotFound && error.response?.statusCode === 404) {
30
+ return {
31
+ content: [
32
+ {
33
+ type: "text",
34
+ text: JSON.stringify({
35
+ success: true,
36
+ status: "not_found",
37
+ }, null, 2),
38
+ },
39
+ ],
40
+ };
41
+ }
42
+ throw error;
43
+ }
44
+ }
@@ -0,0 +1,27 @@
1
+ import { KubernetesManager } from "../types.js";
2
+ export declare const describeCronJobSchema: {
3
+ readonly name: "describe_cronjob";
4
+ readonly description: "Get detailed information about a Kubernetes CronJob including recent job history";
5
+ readonly inputSchema: {
6
+ readonly type: "object";
7
+ readonly properties: {
8
+ readonly name: {
9
+ readonly type: "string";
10
+ };
11
+ readonly namespace: {
12
+ readonly type: "string";
13
+ readonly default: "default";
14
+ };
15
+ };
16
+ readonly required: readonly ["name", "namespace"];
17
+ };
18
+ };
19
+ export declare function describeCronJob(k8sManager: KubernetesManager, input: {
20
+ name: string;
21
+ namespace: string;
22
+ }): Promise<{
23
+ content: {
24
+ type: string;
25
+ text: string;
26
+ }[];
27
+ }>;
@@ -0,0 +1,83 @@
1
+ export const describeCronJobSchema = {
2
+ name: "describe_cronjob",
3
+ description: "Get detailed information about a Kubernetes CronJob including recent job history",
4
+ inputSchema: {
5
+ type: "object",
6
+ properties: {
7
+ name: { type: "string" },
8
+ namespace: { type: "string", default: "default" },
9
+ },
10
+ required: ["name", "namespace"],
11
+ },
12
+ };
13
+ export async function describeCronJob(k8sManager, input) {
14
+ try {
15
+ // Get the CronJob details
16
+ const batchV1Api = k8sManager.getBatchApi();
17
+ const cronJobResponse = await batchV1Api.readNamespacedCronJob(input.name, input.namespace);
18
+ const cronJob = cronJobResponse.body;
19
+ // Get recent Jobs associated with this CronJob
20
+ const labelSelector = `app=${input.name},cronjob-name=${input.name}`;
21
+ const jobsResponse = await batchV1Api.listNamespacedJob(input.namespace, undefined, // pretty
22
+ undefined, // allowWatchBookmarks
23
+ undefined, // _continue
24
+ undefined, // fieldSelector
25
+ labelSelector);
26
+ // Sort jobs by creation time (newest first)
27
+ const jobs = jobsResponse.body.items.sort((a, b) => {
28
+ const aTime = a.metadata?.creationTimestamp
29
+ ? new Date(a.metadata.creationTimestamp)
30
+ : new Date(0);
31
+ const bTime = b.metadata?.creationTimestamp
32
+ ? new Date(b.metadata.creationTimestamp)
33
+ : new Date(0);
34
+ return bTime.getTime() - aTime.getTime();
35
+ });
36
+ // Limit to 5 most recent jobs
37
+ const recentJobs = jobs.slice(0, 5).map((job) => ({
38
+ name: job.metadata?.name || "",
39
+ creationTime: job.metadata?.creationTimestamp || "",
40
+ status: {
41
+ active: job.status?.active || 0,
42
+ succeeded: job.status?.succeeded || 0,
43
+ failed: job.status?.failed || 0,
44
+ completionTime: job.status?.completionTime || null,
45
+ },
46
+ }));
47
+ // Format the response with CronJob details and recent jobs
48
+ const cronJobDetails = {
49
+ name: cronJob.metadata?.name || "",
50
+ namespace: cronJob.metadata?.namespace || "",
51
+ schedule: cronJob.spec?.schedule || "",
52
+ suspend: cronJob.spec?.suspend || false,
53
+ concurrencyPolicy: cronJob.spec?.concurrencyPolicy || "Allow",
54
+ lastScheduleTime: cronJob.status?.lastScheduleTime || null,
55
+ lastSuccessfulTime: cronJob.status?.lastSuccessfulTime || null,
56
+ creationTimestamp: cronJob.metadata?.creationTimestamp || "",
57
+ recentJobs: recentJobs,
58
+ jobTemplate: {
59
+ image: cronJob.spec?.jobTemplate?.spec?.template?.spec?.containers?.[0]
60
+ ?.image || "",
61
+ command: cronJob.spec?.jobTemplate?.spec?.template?.spec?.containers?.[0]
62
+ ?.command || [],
63
+ restartPolicy: cronJob.spec?.jobTemplate?.spec?.template?.spec?.restartPolicy || "",
64
+ },
65
+ };
66
+ return {
67
+ content: [
68
+ {
69
+ type: "text",
70
+ text: JSON.stringify(cronJobDetails, null, 2),
71
+ },
72
+ ],
73
+ };
74
+ }
75
+ catch (error) {
76
+ console.error("Error describing CronJob:", {
77
+ status: error.response?.statusCode,
78
+ message: error.response?.body?.message || error.message,
79
+ details: error.response?.body,
80
+ });
81
+ throw error;
82
+ }
83
+ }
@@ -0,0 +1,40 @@
1
+ import { KubernetesManager } from "../types.js";
2
+ export declare const getJobLogsSchema: {
3
+ readonly name: "get_job_logs";
4
+ readonly description: "Get logs from Pods created by a specific Job";
5
+ readonly inputSchema: {
6
+ readonly type: "object";
7
+ readonly properties: {
8
+ readonly name: {
9
+ readonly type: "string";
10
+ readonly description: "Name of the Job to get logs from";
11
+ };
12
+ readonly namespace: {
13
+ readonly type: "string";
14
+ readonly default: "default";
15
+ };
16
+ readonly tail: {
17
+ readonly type: "number";
18
+ readonly description: "Number of lines to return from the end of the logs";
19
+ readonly optional: true;
20
+ };
21
+ readonly timestamps: {
22
+ readonly type: "boolean";
23
+ readonly description: "Include timestamps in the logs";
24
+ readonly optional: true;
25
+ };
26
+ };
27
+ readonly required: readonly ["name", "namespace"];
28
+ };
29
+ };
30
+ export declare function getJobLogs(k8sManager: KubernetesManager, input: {
31
+ name: string;
32
+ namespace: string;
33
+ tail?: number;
34
+ timestamps?: boolean;
35
+ }): Promise<{
36
+ content: {
37
+ type: string;
38
+ text: string;
39
+ }[];
40
+ }>;
@@ -0,0 +1,104 @@
1
+ export const getJobLogsSchema = {
2
+ name: "get_job_logs",
3
+ description: "Get logs from Pods created by a specific Job",
4
+ inputSchema: {
5
+ type: "object",
6
+ properties: {
7
+ name: {
8
+ type: "string",
9
+ description: "Name of the Job to get logs from",
10
+ },
11
+ namespace: {
12
+ type: "string",
13
+ default: "default",
14
+ },
15
+ tail: {
16
+ type: "number",
17
+ description: "Number of lines to return from the end of the logs",
18
+ optional: true,
19
+ },
20
+ timestamps: {
21
+ type: "boolean",
22
+ description: "Include timestamps in the logs",
23
+ optional: true,
24
+ },
25
+ },
26
+ required: ["name", "namespace"],
27
+ },
28
+ };
29
+ export async function getJobLogs(k8sManager, input) {
30
+ try {
31
+ const coreApi = k8sManager.getCoreApi();
32
+ // First, get the job to check if it exists
33
+ const batchApi = k8sManager.getBatchApi();
34
+ await batchApi.readNamespacedJob(input.name, input.namespace);
35
+ // Find pods associated with this job
36
+ const labelSelector = `job-name=${input.name}`;
37
+ const { body: podList } = await coreApi.listNamespacedPod(input.namespace, undefined, // pretty
38
+ undefined, // allowWatchBookmarks
39
+ undefined, // _continue
40
+ undefined, // fieldSelector
41
+ labelSelector // labelSelector
42
+ );
43
+ if (podList.items.length === 0) {
44
+ return {
45
+ content: [
46
+ {
47
+ type: "text",
48
+ text: JSON.stringify({
49
+ message: `No pods found for job ${input.name}`,
50
+ }, null, 2),
51
+ },
52
+ ],
53
+ };
54
+ }
55
+ // Get logs from all pods belonging to this job
56
+ const podLogs = await Promise.all(podList.items.map(async (pod) => {
57
+ const podName = pod.metadata?.name || "";
58
+ try {
59
+ const logResponse = await coreApi.readNamespacedPodLog(podName, input.namespace, undefined, // container
60
+ undefined, // follow
61
+ input.timestamps || false, // timestamps
62
+ undefined, // sinceSeconds
63
+ undefined, // sinceTime
64
+ (input.tail != undefined ? true : true) || undefined, // tailLines
65
+ undefined // pretty
66
+ );
67
+ return {
68
+ podName,
69
+ logs: logResponse.body,
70
+ status: pod.status?.phase || "Unknown",
71
+ startTime: pod.status?.startTime || null,
72
+ };
73
+ }
74
+ catch (error) {
75
+ return {
76
+ podName,
77
+ logs: `Error retrieving logs: ${error.message || "Unknown error"}`,
78
+ status: pod.status?.phase || "Unknown",
79
+ startTime: pod.status?.startTime || null,
80
+ };
81
+ }
82
+ }));
83
+ return {
84
+ content: [
85
+ {
86
+ type: "text",
87
+ text: JSON.stringify({
88
+ job: input.name,
89
+ namespace: input.namespace,
90
+ pods: podLogs,
91
+ }, null, 2),
92
+ },
93
+ ],
94
+ };
95
+ }
96
+ catch (error) {
97
+ console.error("Error getting Job logs:", {
98
+ status: error.response?.statusCode,
99
+ message: error.response?.body?.message || error.message,
100
+ details: error.response?.body,
101
+ });
102
+ throw error;
103
+ }
104
+ }
@@ -0,0 +1,23 @@
1
+ import { KubernetesManager } from "../types.js";
2
+ export declare const listCronJobsSchema: {
3
+ readonly name: "list_cronjobs";
4
+ readonly description: "List CronJobs in a namespace";
5
+ readonly inputSchema: {
6
+ readonly type: "object";
7
+ readonly properties: {
8
+ readonly namespace: {
9
+ readonly type: "string";
10
+ readonly default: "default";
11
+ };
12
+ };
13
+ readonly required: readonly ["namespace"];
14
+ };
15
+ };
16
+ export declare function listCronJobs(k8sManager: KubernetesManager, input: {
17
+ namespace?: string;
18
+ }): Promise<{
19
+ content: {
20
+ type: string;
21
+ text: string;
22
+ }[];
23
+ }>;
@@ -0,0 +1,35 @@
1
+ export const listCronJobsSchema = {
2
+ name: "list_cronjobs",
3
+ description: "List CronJobs in a namespace",
4
+ inputSchema: {
5
+ type: "object",
6
+ properties: {
7
+ namespace: { type: "string", default: "default" },
8
+ },
9
+ required: ["namespace"],
10
+ },
11
+ };
12
+ export async function listCronJobs(k8sManager, input) {
13
+ const namespace = input.namespace || "default";
14
+ // Get BatchV1Api from KubernetesManager
15
+ const batchV1Api = k8sManager.getBatchApi();
16
+ // List cronjobs in the specified namespace
17
+ const { body } = await batchV1Api.listNamespacedCronJob(namespace);
18
+ // Transform cronjob data to a more readable format
19
+ const cronjobs = body.items.map((cronjob) => ({
20
+ name: cronjob.metadata?.name || "",
21
+ namespace: cronjob.metadata?.namespace || "",
22
+ schedule: cronjob.spec?.schedule || "",
23
+ suspend: cronjob.spec?.suspend || false,
24
+ lastScheduleTime: cronjob.status?.lastScheduleTime || null,
25
+ createdAt: cronjob.metadata?.creationTimestamp,
26
+ }));
27
+ return {
28
+ content: [
29
+ {
30
+ type: "text",
31
+ text: JSON.stringify({ cronjobs }, null, 2),
32
+ },
33
+ ],
34
+ };
35
+ }
@@ -0,0 +1,29 @@
1
+ import { KubernetesManager } from "../types.js";
2
+ export declare const listJobsSchema: {
3
+ readonly name: "list_jobs";
4
+ readonly description: "List Jobs in a namespace, optionally filtered by a CronJob parent";
5
+ readonly inputSchema: {
6
+ readonly type: "object";
7
+ readonly properties: {
8
+ readonly namespace: {
9
+ readonly type: "string";
10
+ readonly default: "default";
11
+ };
12
+ readonly cronJobName: {
13
+ readonly type: "string";
14
+ readonly description: "Optional: Filter jobs created by a specific CronJob";
15
+ readonly optional: true;
16
+ };
17
+ };
18
+ readonly required: readonly ["namespace"];
19
+ };
20
+ };
21
+ export declare function listJobs(k8sManager: KubernetesManager, input: {
22
+ namespace: string;
23
+ cronJobName?: string;
24
+ }): Promise<{
25
+ content: {
26
+ type: string;
27
+ text: string;
28
+ }[];
29
+ }>;
@@ -0,0 +1,77 @@
1
+ export const listJobsSchema = {
2
+ name: "list_jobs",
3
+ description: "List Jobs in a namespace, optionally filtered by a CronJob parent",
4
+ inputSchema: {
5
+ type: "object",
6
+ properties: {
7
+ namespace: { type: "string", default: "default" },
8
+ cronJobName: {
9
+ type: "string",
10
+ description: "Optional: Filter jobs created by a specific CronJob",
11
+ optional: true,
12
+ },
13
+ },
14
+ required: ["namespace"],
15
+ },
16
+ };
17
+ export async function listJobs(k8sManager, input) {
18
+ try {
19
+ const namespace = input.namespace;
20
+ const batchV1Api = k8sManager.getBatchApi();
21
+ // Set up label selector if cronJobName is provided
22
+ let labelSelector;
23
+ if (input.cronJobName) {
24
+ labelSelector = `cronjob-name=${input.cronJobName}`;
25
+ }
26
+ // Get jobs with optional filtering
27
+ const { body } = await batchV1Api.listNamespacedJob(namespace, undefined, // pretty
28
+ undefined, // allowWatchBookmarks
29
+ undefined, // _continue
30
+ undefined, // fieldSelector
31
+ labelSelector // labelSelector
32
+ );
33
+ // Sort jobs by creation time (newest first)
34
+ const jobs = body.items.sort((a, b) => {
35
+ const aTime = a.metadata?.creationTimestamp
36
+ ? new Date(a.metadata.creationTimestamp)
37
+ : new Date(0);
38
+ const bTime = b.metadata?.creationTimestamp
39
+ ? new Date(b.metadata.creationTimestamp)
40
+ : new Date(0);
41
+ return bTime.getTime() - aTime.getTime();
42
+ });
43
+ // Transform job data to a more readable format
44
+ const formattedJobs = jobs.map((job) => ({
45
+ name: job.metadata?.name || "",
46
+ namespace: job.metadata?.namespace || "",
47
+ creationTime: job.metadata?.creationTimestamp || "",
48
+ labels: job.metadata?.labels || {},
49
+ completions: job.spec?.completions || 1,
50
+ parallelism: job.spec?.parallelism || 1,
51
+ status: {
52
+ active: job.status?.active || 0,
53
+ succeeded: job.status?.succeeded || 0,
54
+ failed: job.status?.failed || 0,
55
+ completionTime: job.status?.completionTime || null,
56
+ startTime: job.status?.startTime || null,
57
+ conditions: job.status?.conditions || [],
58
+ },
59
+ }));
60
+ return {
61
+ content: [
62
+ {
63
+ type: "text",
64
+ text: JSON.stringify({ jobs: formattedJobs }, null, 2),
65
+ },
66
+ ],
67
+ };
68
+ }
69
+ catch (error) {
70
+ console.error("Error listing Jobs:", {
71
+ status: error.response?.statusCode,
72
+ message: error.response?.body?.message || error.message,
73
+ details: error.response?.body,
74
+ });
75
+ throw error;
76
+ }
77
+ }
@@ -0,0 +1,30 @@
1
+ import { KubernetesManager } from "../types.js";
2
+ export declare const scaleDeploymentSchema: {
3
+ name: string;
4
+ description: string;
5
+ inputSchema: {
6
+ type: string;
7
+ properties: {
8
+ name: {
9
+ type: string;
10
+ };
11
+ namespace: {
12
+ type: string;
13
+ };
14
+ replicas: {
15
+ type: string;
16
+ };
17
+ };
18
+ required: string[];
19
+ };
20
+ };
21
+ export declare function scaleDeployment(k8sManager: KubernetesManager, input: {
22
+ name: string;
23
+ namespace: string;
24
+ replicas: number;
25
+ }): Promise<{
26
+ content: {
27
+ success: boolean;
28
+ message: string;
29
+ }[];
30
+ }>;
@@ -0,0 +1,50 @@
1
+ export const scaleDeploymentSchema = {
2
+ name: "scale_deployment",
3
+ description: "Scale a Kubernetes deployment",
4
+ inputSchema: {
5
+ type: "object",
6
+ properties: {
7
+ name: { type: "string" },
8
+ namespace: { type: "string" },
9
+ replicas: { type: "number" }
10
+ },
11
+ required: ["name", "namespace", "replicas"]
12
+ }
13
+ };
14
+ export async function scaleDeployment(k8sManager, input) {
15
+ try {
16
+ const scale = k8sManager.getAppsApi().readNamespacedDeploymentScale(input.name, input.namespace);
17
+ (await scale).body.spec.replicas = input.replicas;
18
+ const result = await k8sManager.getAppsApi().replaceNamespacedDeploymentScale(input.name, input.namespace, (await scale).body);
19
+ if (result.response?.statusCode !== undefined && result.response.statusCode >= 200 && result.response.statusCode < 300) {
20
+ return {
21
+ content: [
22
+ {
23
+ success: true,
24
+ message: `Scaled deployment ${input.name} to ${input.replicas} replicas`
25
+ }
26
+ ]
27
+ };
28
+ }
29
+ else {
30
+ return {
31
+ content: [
32
+ {
33
+ success: false,
34
+ message: `Failed to scale deployment ${input.name} to ${input.replicas} replicas`
35
+ }
36
+ ]
37
+ };
38
+ }
39
+ }
40
+ catch (error) {
41
+ return {
42
+ content: [
43
+ {
44
+ success: false,
45
+ message: `Failed to scale deployment ${error.message}`
46
+ }
47
+ ]
48
+ };
49
+ }
50
+ }
@@ -7,6 +7,7 @@ export declare class KubernetesManager {
7
7
  private kc;
8
8
  private k8sApi;
9
9
  private k8sAppsApi;
10
+ private k8sBatchApi;
10
11
  constructor();
11
12
  cleanup(): Promise<void>;
12
13
  trackResource(kind: string, name: string, namespace: string): void;
@@ -18,4 +19,5 @@ export declare class KubernetesManager {
18
19
  getKubeConfig(): k8s.KubeConfig;
19
20
  getCoreApi(): k8s.CoreV1Api;
20
21
  getAppsApi(): k8s.AppsV1Api;
22
+ getBatchApi(): k8s.BatchV1Api;
21
23
  }
@@ -6,11 +6,13 @@ export class KubernetesManager {
6
6
  kc;
7
7
  k8sApi;
8
8
  k8sAppsApi;
9
+ k8sBatchApi;
9
10
  constructor() {
10
11
  this.kc = new k8s.KubeConfig();
11
12
  this.kc.loadFromDefault();
12
13
  this.k8sApi = this.kc.makeApiClient(k8s.CoreV1Api);
13
14
  this.k8sAppsApi = this.kc.makeApiClient(k8s.AppsV1Api);
15
+ this.k8sBatchApi = this.kc.makeApiClient(k8s.BatchV1Api);
14
16
  }
15
17
  async cleanup() {
16
18
  // Stop watches
@@ -41,6 +43,9 @@ export class KubernetesManager {
41
43
  case "service":
42
44
  await this.k8sApi.deleteNamespacedService(name, namespace);
43
45
  break;
46
+ case "cronjob":
47
+ await this.k8sBatchApi.deleteNamespacedCronJob(name, namespace);
48
+ break;
44
49
  }
45
50
  this.resources = this.resources.filter((r) => !(r.kind === kind && r.name === name && r.namespace === namespace));
46
51
  }
@@ -65,4 +70,7 @@ export class KubernetesManager {
65
70
  getAppsApi() {
66
71
  return this.k8sAppsApi;
67
72
  }
73
+ getBatchApi() {
74
+ return this.k8sBatchApi;
75
+ }
68
76
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-server-kubernetes",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
4
4
  "description": "MCP server for interacting with Kubernetes clusters via kubectl",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -37,10 +37,10 @@
37
37
  "dependencies": {
38
38
  "@kubernetes/client-node": "0.20.0",
39
39
  "@modelcontextprotocol/sdk": "1.7.0",
40
+ "express": "4.21.2",
40
41
  "js-yaml": "4.1.0",
41
42
  "yaml": "2.7.0",
42
- "zod": "3.23.8",
43
- "express": "4.21.2"
43
+ "zod": "3.23.8"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/express": "5.0.1",