Skip to main content
Glama
localstack
by localstack
localstack-chaos-injector.ts10.4 kB
import { z } from "zod"; import { type ToolMetadata, type InferSchema } from "xmcp"; import { ProFeature } from "../lib/localstack/license-checker"; import { ChaosApiClient } from "../lib/localstack/localstack.client"; import { ResponseBuilder } from "../core/response-builder"; import { runPreflights, requireProFeature } from "../core/preflight"; // Define the fault rule schema const faultRuleSchema = z .object({ service: z .string() .optional() .describe("Name of the AWS service to affect (e.g., 's3', 'lambda')."), region: z.string().optional().describe("Name of the AWS region to affect (e.g., 'us-east-1')."), operation: z .string() .optional() .describe("Name of the specific service operation to affect (e.g., 'CreateBucket')."), probability: z .number() .min(0) .max(1) .optional() .describe("The probability (0.0 to 1.0) of the fault occurring."), error: z .object({ statusCode: z .number() .int() .optional() .describe("The HTTP status code to return (e.g., 503)."), code: z .string() .optional() .describe("The AWS error code to return (e.g., 'ServiceUnavailable')."), }) .optional() .describe("The custom error to return."), }) .describe("A single rule defining a chaos fault."); // Define the schema for tool parameters export const schema = { action: z .enum([ "inject-faults", "add-fault-rule", "remove-fault-rule", "get-faults", "clear-all-faults", "inject-latency", "get-latency", "clear-latency", ]) .describe("The specific chaos engineering action to perform."), rules: z .array(faultRuleSchema) .optional() .describe( "An array of fault rules. Required for 'inject-faults', 'add-fault-rule', and 'remove-fault-rule' actions." ), latency_ms: z .number() .int() .min(0) .optional() .describe("Network latency in milliseconds. Required for the 'inject-latency' action."), }; // Define tool metadata export const metadata: ToolMetadata = { name: "localstack-chaos-injector", description: "Injects, manages, and clears chaos faults and network effects in LocalStack to test system resilience.", annotations: { title: "LocalStack Chaos Injector", readOnlyHint: false, destructiveHint: true, idempotentHint: false, }, }; // Check if two fault rules match exactly function rulesMatch(rule1: any, rule2: any): boolean { const keys1 = Object.keys(rule1).sort(); const keys2 = Object.keys(rule2).sort(); if (keys1.length !== keys2.length) return false; if (keys1.join(",") !== keys2.join(",")) return false; for (const key of keys1) { if (typeof rule1[key] === "object" && typeof rule2[key] === "object") { if (!rulesMatch(rule1[key], rule2[key])) return false; } else if (rule1[key] !== rule2[key]) { return false; } } return true; } // Format fault rules for display function formatFaultRules(rules: any[]): string { if (!rules || rules.length === 0) { return "✅ No chaos faults are currently active."; } return `\`\`\`json\n${JSON.stringify(rules, null, 2)}\n\`\`\``; } // Create workflow guidance for injection actions function addWorkflowGuidance(message: string): string { return `${message} **Next Step:** Now, run your application or tests to observe the system's behavior under these conditions. Once you are done, ask me to "**analyze the logs for errors**" to see the impact of this chaos experiment.`; } export default async function localstackChaosInjector({ action, rules, latency_ms, }: InferSchema<typeof schema>) { const preflightError = await runPreflights([requireProFeature(ProFeature.CHAOS_ENGINEERING)]); if (preflightError) return preflightError; const client = new ChaosApiClient(); switch (action) { case "get-faults": { const result = await client.getFaults(); if (!result.success) { return { content: [{ type: "text", text: result.message }] }; } const formattedRules = formatFaultRules(result.data); return { content: [{ type: "text", text: formattedRules }] }; } case "clear-all-faults": { const result = await client.setFaults([]); if (!result.success) { return ResponseBuilder.error("Chaos API Error", result.message); } return ResponseBuilder.success( "All chaos faults have been cleared. The system is now operating normally." ); } case "inject-faults": { if (!rules || rules.length === 0) { return { content: [ { type: "text", text: "❌ **Error:** The `inject-faults` action requires the `rules` parameter to be specified.", }, ], }; } const setResult = await client.setFaults(rules); if (!setResult.success) { return ResponseBuilder.error("Chaos API Error", setResult.message); } // Get current state to confirm const getCurrentResult = await client.getFaults(); if (!getCurrentResult.success) { return ResponseBuilder.error("Chaos API Error", getCurrentResult.message); } const message = `✅ New chaos faults have been injected (overwriting any previous rules). The current active faults are: ${formatFaultRules(getCurrentResult.data)}`; return ResponseBuilder.markdown(addWorkflowGuidance(message)); } case "add-fault-rule": { if (!rules || rules.length === 0) { return { content: [ { type: "text", text: "❌ **Error:** The `add-fault-rule` action requires the `rules` parameter to be specified.", }, ], }; } const addResult = await client.addFaultRules(rules); if (!addResult.success) { return ResponseBuilder.error("Chaos API Error", addResult.message); } // Get current state to confirm const getCurrentResult = await client.getFaults(); if (!getCurrentResult.success) { return ResponseBuilder.error("Chaos API Error", getCurrentResult.message); } const message = `✅ New fault rule(s) have been added. The current active faults are: ${formatFaultRules(getCurrentResult.data)}`; return ResponseBuilder.markdown(addWorkflowGuidance(message)); } case "remove-fault-rule": { if (!rules || rules.length === 0) { return { content: [ { type: "text", text: "❌ **Error:** The `remove-fault-rule` action requires the `rules` parameter to be specified.", }, ], }; } // First get current rules to check if the rule exists const getCurrentResult = await client.getFaults(); if (!getCurrentResult.success) { return ResponseBuilder.error("Chaos API Error", getCurrentResult.message); } // Check if all rules to remove exist in current configuration const currentRules = getCurrentResult.data || []; const rulesToRemove = rules; for (const ruleToRemove of rulesToRemove) { const ruleExists = currentRules.some((currentRule: any) => rulesMatch(currentRule, ruleToRemove) ); if (!ruleExists) { return ResponseBuilder.markdown(`⚠️ The specified rule was not found in the current configuration. No changes were made. Current configuration: ${formatFaultRules(currentRules)}`); } } // Rule exists, proceed with removal const removeResult = await client.removeFaultRules(rulesToRemove); if (!removeResult.success) { return ResponseBuilder.error("Chaos API Error", removeResult.message); } // Get current state after removal to confirm const getUpdatedResult = await client.getFaults(); if (!getUpdatedResult.success) { return ResponseBuilder.error("Chaos API Error", getUpdatedResult.message); } const message = `✅ The specified fault rule(s) have been removed. The current active faults are: ${formatFaultRules(getUpdatedResult.data)}`; return ResponseBuilder.markdown(message); } case "get-latency": { const result = await client.getEffects(); if (!result.success) { return ResponseBuilder.error("Chaos API Error", result.message); } const latency = (result.data as any)?.latency || 0; return ResponseBuilder.markdown(`The current network latency is ${latency}ms.`); } case "clear-latency": { const result = await client.setEffects({ latency: 0 }); if (!result.success) { return ResponseBuilder.error("Chaos API Error", result.message); } // Get current state to confirm const getCurrentResult = await client.getEffects(); if (!getCurrentResult.success) { return ResponseBuilder.error("Chaos API Error", getCurrentResult.message); } const message = `✅ Network latency has been cleared. The current effects are: \`\`\`json ${JSON.stringify(getCurrentResult.data, null, 2)} \`\`\``; return ResponseBuilder.markdown(message); } case "inject-latency": { if (latency_ms === undefined || latency_ms === null) { return { content: [ { type: "text", text: "❌ **Error:** The `inject-latency` action requires the `latency_ms` parameter to be specified.", }, ], }; } const result = await client.setEffects({ latency: latency_ms }); if (!result.success) { return ResponseBuilder.error("Chaos API Error", result.message); } // Get current state to confirm const getCurrentResult = await client.getEffects(); if (!getCurrentResult.success) { return ResponseBuilder.error("Chaos API Error", getCurrentResult.message); } const message = `✅ Latency of ${latency_ms}ms has been injected. The current network effects are: \`\`\`json ${JSON.stringify(getCurrentResult.data, null, 2)} \`\`\``; return ResponseBuilder.markdown(addWorkflowGuidance(message)); } default: return ResponseBuilder.error("Unknown action", `Unsupported action: ${action}`); } }

Implementation Reference

Latest Blog Posts

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/localstack/localstack-mcp-server'

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