mcp-server-kubernetes 1.0.1 → 1.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 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,15 @@ 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";
12
17
  import { deletePod, deletePodSchema } from "./tools/delete_pod.js";
13
18
  import { describePod, describePodSchema } from "./tools/describe_pod.js";
14
19
  import { getLogs, getLogsSchema } from "./tools/get_logs.js";
@@ -24,6 +29,7 @@ import { startSSEServer } from "./utils/sse.js";
24
29
  import { startPortForward, PortForwardSchema, stopPortForward, StopPortForwardSchema, } from "./tools/port_forward.js";
25
30
  import { deleteDeployment } from "./tools/delete_deployment.js";
26
31
  import { createDeployment } from "./tools/create_deployment.js";
32
+ import { scaleDeployment, scaleDeploymentSchema } from "./tools/scale_deployment.js";
27
33
  import { describeDeployment, describeDeploymentSchema, } from "./tools/describe_deployment.js";
28
34
  const k8sManager = new KubernetesManager();
29
35
  const server = new Server({
@@ -38,15 +44,20 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
38
44
  createDeploymentSchema,
39
45
  createNamespaceSchema,
40
46
  createPodSchema,
47
+ createCronJobSchema,
41
48
  deletePodSchema,
49
+ describeCronJobSchema,
42
50
  describePodSchema,
43
51
  describeDeploymentSchema,
44
52
  explainResourceSchema,
45
53
  getEventsSchema,
54
+ getJobLogsSchema,
46
55
  getLogsSchema,
47
56
  installHelmChartSchema,
48
57
  listApiResourcesSchema,
58
+ listCronJobsSchema,
49
59
  listDeploymentsSchema,
60
+ listJobsSchema,
50
61
  listNamespacesSchema,
51
62
  listNodesSchema,
52
63
  listPodsSchema,
@@ -55,6 +66,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
55
66
  upgradeHelmChartSchema,
56
67
  PortForwardSchema,
57
68
  StopPortForwardSchema,
69
+ scaleDeploymentSchema,
58
70
  ],
59
71
  };
60
72
  });
@@ -81,6 +93,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
81
93
  case "create_pod": {
82
94
  return await createPod(k8sManager, input);
83
95
  }
96
+ case "create_cronjob": {
97
+ return await createCronJob(k8sManager, input);
98
+ }
84
99
  case "delete_pod": {
85
100
  return await deletePod(k8sManager, input);
86
101
  }
@@ -130,6 +145,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
130
145
  case "list_services": {
131
146
  return await listServices(k8sManager, input);
132
147
  }
148
+ case "list_cronjobs": {
149
+ return await listCronJobs(k8sManager, input);
150
+ }
151
+ case "describe_cronjob": {
152
+ return await describeCronJob(k8sManager, input);
153
+ }
154
+ case "list_jobs": {
155
+ return await listJobs(k8sManager, input);
156
+ }
157
+ case "get_job_logs": {
158
+ return await getJobLogs(k8sManager, input);
159
+ }
133
160
  case "uninstall_helm_chart": {
134
161
  return await uninstallHelmChart(input);
135
162
  }
@@ -151,6 +178,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
151
178
  case "describe_deployment": {
152
179
  return await describeDeployment(k8sManager, input);
153
180
  }
181
+ case "scale_deployment": {
182
+ return await scaleDeployment(k8sManager, input);
183
+ }
154
184
  default:
155
185
  throw new McpError(ErrorCode.InvalidRequest, `Unknown tool: ${name}`);
156
186
  }
@@ -285,6 +285,116 @@ export declare const GetEventsResponseSchema: z.ZodObject<{
285
285
  text: string;
286
286
  }[];
287
287
  }>;
288
+ export declare const ListCronJobsResponseSchema: z.ZodObject<{
289
+ content: z.ZodArray<z.ZodObject<{
290
+ type: z.ZodLiteral<"text">;
291
+ text: z.ZodString;
292
+ }, "strip", z.ZodTypeAny, {
293
+ type: "text";
294
+ text: string;
295
+ }, {
296
+ type: "text";
297
+ text: string;
298
+ }>, "many">;
299
+ }, "strip", z.ZodTypeAny, {
300
+ content: {
301
+ type: "text";
302
+ text: string;
303
+ }[];
304
+ }, {
305
+ content: {
306
+ type: "text";
307
+ text: string;
308
+ }[];
309
+ }>;
310
+ export declare const CreateCronJobResponseSchema: 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 DescribeCronJobResponseSchema: 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 ListJobsResponseSchema: 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 GetJobLogsResponseSchema: 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
+ }>;
288
398
  export declare const PortForwardResponseSchema: z.ZodObject<{
289
399
  content: z.ZodArray<z.ZodObject<{
290
400
  success: z.ZodBoolean;
@@ -307,3 +417,25 @@ export declare const PortForwardResponseSchema: z.ZodObject<{
307
417
  success: boolean;
308
418
  }[];
309
419
  }>;
420
+ export declare const ScaleDeploymentResponseSchema: z.ZodObject<{
421
+ content: z.ZodArray<z.ZodObject<{
422
+ success: z.ZodBoolean;
423
+ message: z.ZodString;
424
+ }, "strip", z.ZodTypeAny, {
425
+ message: string;
426
+ success: boolean;
427
+ }, {
428
+ message: string;
429
+ success: boolean;
430
+ }>, "many">;
431
+ }, "strip", z.ZodTypeAny, {
432
+ content: {
433
+ message: string;
434
+ success: boolean;
435
+ }[];
436
+ }, {
437
+ content: {
438
+ message: string;
439
+ success: boolean;
440
+ }[];
441
+ }>;
@@ -43,9 +43,30 @@ export const GetLogsResponseSchema = z.object({
43
43
  export const GetEventsResponseSchema = z.object({
44
44
  content: z.array(ToolResponseContent),
45
45
  });
46
+ export const ListCronJobsResponseSchema = z.object({
47
+ content: z.array(ToolResponseContent),
48
+ });
49
+ export const CreateCronJobResponseSchema = z.object({
50
+ content: z.array(ToolResponseContent),
51
+ });
52
+ export const DescribeCronJobResponseSchema = z.object({
53
+ content: z.array(ToolResponseContent),
54
+ });
55
+ export const ListJobsResponseSchema = z.object({
56
+ content: z.array(ToolResponseContent),
57
+ });
58
+ export const GetJobLogsResponseSchema = z.object({
59
+ content: z.array(ToolResponseContent),
60
+ });
46
61
  export const PortForwardResponseSchema = z.object({
47
62
  content: z.array(z.object({
48
63
  success: z.boolean(),
49
64
  message: z.string(),
50
65
  })),
51
66
  });
67
+ export const ScaleDeploymentResponseSchema = z.object({
68
+ content: z.array(z.object({
69
+ success: z.boolean(),
70
+ message: z.string(),
71
+ })),
72
+ });
@@ -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,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.1.0",
4
4
  "description": "MCP server for interacting with Kubernetes clusters via kubectl",
5
5
  "license": "MIT",
6
6
  "type": "module",