Skip to main content
Glama

mcp-server-kubernetes

by Flux159
import { expect, test, describe, beforeEach, afterEach } from "vitest"; import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; import { z } from "zod"; // Define the response type for easier use in tests type KubectlResponse = { content: Array<{ type: "text"; text: string; }>; }; async function sleep(ms: number): Promise<void> { return new Promise((resolve) => setTimeout(resolve, ms)); } // Helper function to retry operations that might be flaky async function retry<T>( operation: () => Promise<T>, maxRetries: number = 2, delayMs: number = 1000 ): Promise<T> { let lastError: Error | unknown; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await operation(); } catch (error) { lastError = error; console.warn( `Attempt ${attempt}/${maxRetries} failed. Retrying in ${delayMs}ms...` ); await sleep(delayMs); } } throw lastError; } describe("kubectl_patch command", () => { let transport: StdioClientTransport; let client: Client; const configMapName = "patch-test-cm-" + Math.random().toString(36).substring(2, 7); beforeEach(async () => { transport = new StdioClientTransport({ command: "bun", args: ["src/index.ts"], stderr: "pipe", }); client = new Client( { name: "test-client", version: "1.0.0", }, { capabilities: {}, } ); await client.connect(transport); await sleep(1000); // Create a configmap that we'll patch in the tests await retry(async () => { await client.request( { method: "tools/call", params: { name: "kubectl_create", arguments: { resourceType: "configmap", name: configMapName, namespace: "default", manifest: JSON.stringify({ apiVersion: "v1", kind: "ConfigMap", metadata: { name: configMapName, namespace: "default" }, data: { key1: "value1", key2: "value2" } }) }, }, }, z.any() ); }); }); afterEach(async () => { try { // Delete the test configmap try { await client.request( { method: "tools/call", params: { name: "kubectl_delete", arguments: { resourceType: "configmap", name: configMapName, namespace: "default" }, }, }, z.any() ); } catch (e) { // Ignore error if configmap doesn't exist } await transport.close(); await sleep(2000); } catch (e) { console.error("Error during cleanup:", e); } }); test("kubectl_patch can modify a configmap with strategic patch", async () => { // Patch the configmap with strategic merge patch const patchResult = await client.request( { method: "tools/call", params: { name: "kubectl_patch", arguments: { resourceType: "configmap", name: configMapName, namespace: "default", patchType: "strategic", patchData: { data: { key1: "updated-value1", key3: "value3" } } }, }, }, z.any() ) as KubectlResponse; expect(patchResult.content[0].type).toBe("text"); expect(patchResult.content[0].text).toContain(`configmap/${configMapName} patched`); // Verify the configmap was updated const getResult = await client.request( { method: "tools/call", params: { name: "kubectl_get", arguments: { resourceType: "configmap", name: configMapName, namespace: "default", output: "json" }, }, }, z.any() ) as KubectlResponse; const configMap = JSON.parse(getResult.content[0].text); expect(configMap.data.key1).toBe("updated-value1"); // Updated value expect(configMap.data.key2).toBe("value2"); // Unchanged value expect(configMap.data.key3).toBe("value3"); // New value }); test("kubectl_patch can modify a configmap with merge patch", async () => { // Patch the configmap with JSON merge patch const patchResult = await client.request( { method: "tools/call", params: { name: "kubectl_patch", arguments: { resourceType: "configmap", name: configMapName, namespace: "default", patchType: "merge", patchData: { data: { key2: null, // Remove key2 key4: "value4" // Add key4 } } }, }, }, z.any() ) as KubectlResponse; expect(patchResult.content[0].type).toBe("text"); expect(patchResult.content[0].text).toContain(`configmap/${configMapName} patched`); // Verify the configmap was updated const getResult = await client.request( { method: "tools/call", params: { name: "kubectl_get", arguments: { resourceType: "configmap", name: configMapName, namespace: "default", output: "json" }, }, }, z.any() ) as KubectlResponse; const configMap = JSON.parse(getResult.content[0].text); expect(configMap.data.key1).toBe("value1"); // Unchanged expect(configMap.data.key2).toBeUndefined(); // Removed expect(configMap.data.key4).toBe("value4"); // Added }); test("kubectl_patch works with dry-run option", async () => { // Patch the configmap with dry-run option const patchResult = await client.request( { method: "tools/call", params: { name: "kubectl_patch", arguments: { resourceType: "configmap", name: configMapName, namespace: "default", patchType: "strategic", patchData: { data: { key1: "dry-run-value" } }, dryRun: true }, }, }, z.any() ) as KubectlResponse; expect(patchResult.content[0].type).toBe("text"); expect(patchResult.content[0].text).toContain(`configmap/${configMapName} patched`); // Verify the configmap was NOT updated const getResult = await client.request( { method: "tools/call", params: { name: "kubectl_get", arguments: { resourceType: "configmap", name: configMapName, namespace: "default", output: "json" }, }, }, z.any() ) as KubectlResponse; const configMap = JSON.parse(getResult.content[0].text); expect(configMap.data.key1).toBe("value1"); // Still has original value }); test("kubectl_patch handles errors gracefully", async () => { const nonExistentResource = "non-existent-resource-" + Date.now(); try { await client.request( { method: "tools/call", params: { name: "kubectl_patch", arguments: { resourceType: "configmap", name: nonExistentResource, namespace: "default", patchData: { data: { key1: "value1" } } }, }, }, z.any() ); // If we get here, the test has failed expect(true).toBe(false); // This should not execute } catch (error: any) { // Expect an error response expect(error.message).toContain("Failed to patch resource"); } }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Flux159/mcp-server-kubernetes'

If you have feedback or need assistance with the MCP directory API, please join our Discord server