From cdc21631aebe25d57989d36f1c1c5ec56cbe4cdc Mon Sep 17 00:00:00 2001 From: Harsh Mishra Date: Fri, 1 May 2026 01:13:14 +0530 Subject: [PATCH 1/4] New Prompt: infrastructure-tester --- README.md | 9 ++ manifest.json | 13 +++ src/core/analytics.ts | 70 +++++++++++++ src/prompts/infrastructure-tester.ts | 146 +++++++++++++++++++++++++++ tests/mcp/direct.spec.mjs | 22 ++++ xmcp.config.ts | 1 - 6 files changed, 260 insertions(+), 1 deletion(-) create mode 100644 src/prompts/infrastructure-tester.ts diff --git a/README.md b/README.md index 10a64c5..e2a7e0f 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ This server eliminates custom scripts and manual LocalStack management with dire - Launch and manage [Ephemeral Instances](https://docs.localstack.cloud/aws/capabilities/cloud-sandbox/ephemeral-instances/) for remote LocalStack testing workflows. - Replicate external AWS resources into LocalStack with [AWS Replicator](https://docs.localstack.cloud/aws/tooling/aws-replicator/) so IaC stacks can resolve shared dependencies locally. - Inspect LocalStack application flows with [App Inspector](https://docs.localstack.cloud/aws/capabilities/web-app/app-inspector/) traces, spans, events, payload metadata, and IAM policy evaluations. +- Start repeatable LocalStack workflows from ready-made MCP prompts, including infrastructure validation and integration test generation. - Connect AI assistants and dev tools for automated cloud testing workflows. ## Tools Reference @@ -43,6 +44,14 @@ This server provides your AI with dedicated tools for managing your LocalStack e | [`localstack-app-inspector`](./src/tools/localstack-app-inspector.ts) | Inspects LocalStack application traces, spans, events, and IAM evaluations | - Enable or disable App Inspector for the running LocalStack instance
- List and inspect traces to understand AWS service-to-service flows
- Drill into spans, events, payload metadata, and IAM policy evaluation events
- Filter by service, region, operation, resource, ARN, status, and time range
- Requires a valid LocalStack Auth Token and the App Inspector feature in the connected LocalStack license | | [`localstack-docs`](./src/tools/localstack-docs.ts) | Searches LocalStack documentation through CrawlChat | - Queries LocalStack docs through a public CrawlChat collection
- Returns focused snippets with source links only
- Helps answer coverage, configuration, and setup questions without requiring LocalStack runtime | +## Prompts + +Prompts are user-selected workflow templates exposed by MCP clients as slash commands or quick actions. They frame multi-step LocalStack tasks so the assistant follows the same phases, evidence requirements, and reporting format every time. + +| Prompt Name | Description | Arguments | +| :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :--------------------------------------------------------------------------------------------- | +| `infrastructure-tester` | Deploys an IaC project to LocalStack, validates declared resources with live AWS probes and App Inspector evidence, then writes and runs deterministic integration tests. | `iac_path` (required), `iac_type`, `test_language`, `test_framework`, `mode`, `services_focus` | + ## Installation | Editor | Installation | diff --git a/manifest.json b/manifest.json index 948871b..da48c08 100644 --- a/manifest.json +++ b/manifest.json @@ -81,6 +81,19 @@ } ], "prompts": [ + { + "name": "infrastructure-tester", + "description": "Deploy IaC to LocalStack, validate resources with live evidence, then write and run deterministic integration tests", + "arguments": [ + "iac_path", + "iac_type", + "test_language", + "test_framework", + "mode", + "services_focus" + ], + "text": "Run the Infrastructure Tester workflow for ${arguments.iac_path}. Detect or use ${arguments.iac_type}, validate deployed resources, and if mode is ${arguments.mode}, generate and run ${arguments.test_language}/${arguments.test_framework} integration tests focused on ${arguments.services_focus}." + }, { "name": "localstack-start", "description": "Start LocalStack", diff --git a/src/core/analytics.ts b/src/core/analytics.ts index f8cade4..522b50e 100644 --- a/src/core/analytics.ts +++ b/src/core/analytics.ts @@ -8,6 +8,8 @@ type UnknownRecord = Record; const ANALYTICS_EVENT_TOOL = "mcp_tool_executed"; const ANALYTICS_EVENT_ERROR = "mcp_tool_error"; +const ANALYTICS_EVENT_PROMPT = "mcp_prompt_invoked"; +const ANALYTICS_EVENT_PROMPT_ERROR = "mcp_prompt_error"; const DEFAULT_POSTHOG_API_KEY = "phc_avw42FXoCcfAZUS67wftg93WOBeftfJuAhGHMAubGDB"; const DEFAULT_POSTHOG_HOST = "https://us.i.posthog.com"; const ANALYTICS_ID_DIR = path.join(os.homedir(), ".localstack", "mcp"); @@ -314,3 +316,71 @@ export async function withToolAnalytics( return result as T; } + +function sanitizePromptArgs(args: unknown): UnknownRecord { + const argsKeys = + args && typeof args === "object" + ? Object.keys(args as UnknownRecord) + .filter((key) => Object.prototype.hasOwnProperty.call(args, key)) + .sort() + : []; + + return { keys: argsKeys }; +} + +export async function withPromptAnalytics( + promptName: string, + args: unknown, + handler: () => Promise +): Promise { + const eventId = crypto.randomUUID(); + const startedAt = Date.now(); + const sanitizedArgs = sanitizePromptArgs(args); + let result: T | undefined; + let hasCaughtError = false; + let caughtError: unknown; + let success = false; + let errorName: string | null = null; + let errorMessage: string | null = null; + + try { + result = await handler(); + success = true; + } catch (error) { + hasCaughtError = true; + caughtError = error; + success = false; + const err = error instanceof Error ? error : new Error(String(error)); + errorName = err.name; + errorMessage = truncateValue(err.message || "Unknown error"); + } finally { + const durationMs = Date.now() - startedAt; + + await captureToolEvent(ANALYTICS_EVENT_PROMPT, { + event_id: eventId, + prompt_name: promptName, + duration_ms: durationMs, + success, + error_name: errorName, + error_message: errorMessage, + args: sanitizedArgs, + }); + + if (!success) { + await captureToolEvent(ANALYTICS_EVENT_PROMPT_ERROR, { + event_id: eventId, + prompt_name: promptName, + duration_ms: durationMs, + error_name: errorName, + error_message: errorMessage, + args: sanitizedArgs, + }); + } + } + + if (hasCaughtError) { + throw caughtError; + } + + return result as T; +} diff --git a/src/prompts/infrastructure-tester.ts b/src/prompts/infrastructure-tester.ts new file mode 100644 index 0000000..8c3e5e7 --- /dev/null +++ b/src/prompts/infrastructure-tester.ts @@ -0,0 +1,146 @@ +import { z } from "zod"; +import { type InferSchema, type PromptMetadata } from "xmcp"; +import { withPromptAnalytics } from "../core/analytics"; + +export const schema = { + iac_path: z + .string() + .min(1) + .describe("Path to the IaC root directory, e.g. ./infra, ./cdk, or ./terraform."), + iac_type: z + .string() + .optional() + .describe("IaC framework: cdk, terraform, sam, cloudformation, or auto. Defaults to auto."), + test_language: z + .string() + .optional() + .describe("Language for generated integration tests. Defaults to typescript."), + test_framework: z + .string() + .optional() + .describe("Test framework. Defaults from the test language, e.g. jest or pytest."), + mode: z + .string() + .optional() + .describe("validate-only runs deployment validation only; full also writes and runs tests."), + services_focus: z + .string() + .optional() + .describe("Comma-separated AWS services to focus on. Empty means all discovered services."), +}; + +export const metadata: PromptMetadata = { + name: "infrastructure-tester", + title: "Infrastructure Tester", + description: + "Deploy IaC to LocalStack, validate every resource, then write and run integration tests with trace-backed debugging.", + role: "user", +}; + +type PromptArgs = InferSchema; + +export default async function infrastructureTester(args: PromptArgs): Promise { + return withPromptAnalytics(metadata.name, args, async () => { + const values = { + iac_path: args.iac_path, + iac_type: normalize(args.iac_type, "auto"), + test_language: normalize(args.test_language, "typescript"), + test_framework: normalize(args.test_framework, defaultFrameworkFor(args.test_language)), + mode: normalize(args.mode, "full"), + services_focus: normalize(args.services_focus, "all discovered services"), + }; + + return renderInfrastructureTesterPrompt(values); + }); +} + +function normalize(value: string | undefined, fallback: string): string { + const normalized = value?.trim(); + return normalized && normalized.length > 0 ? normalized : fallback; +} + +function defaultFrameworkFor(language: string | undefined): string { + switch (language?.trim().toLowerCase()) { + case "python": + return "pytest"; + case "java": + return "junit"; + case "go": + return "go-test"; + case "javascript": + case "typescript": + default: + return "jest"; + } +} + +function renderInfrastructureTesterPrompt(values: { + iac_path: string; + iac_type: string; + test_language: string; + test_framework: string; + mode: string; + services_focus: string; +}): string { + return `# Infrastructure Tester (LocalStack) + +You are an Infrastructure Tester operating against one running LocalStack instance. Deploy the IaC, prove the declared resources exist with matching configuration, then write and run integration tests until they pass or you can explain why they cannot. + +## Inputs + +- IaC path: \`${values.iac_path}\` +- IaC framework: \`${values.iac_type}\` +- Test language: \`${values.test_language}\` +- Test framework: \`${values.test_framework}\` +- Mode: \`${values.mode}\` +- Services in focus: \`${values.services_focus}\` + +## Tool Discipline + +Use the LocalStack MCP tools instead of guessing: +- \`localstack-management\` for runtime status and start/restart. +- \`localstack-deployer\` for CDK, Terraform, SAM, or CloudFormation deploy/destroy. +- \`localstack-aws-client\` for live \`awslocal\` resource probes. +- \`localstack-app-inspector\` for traces, spans, events, and IAM evaluation evidence. +- \`localstack-logs-analysis\` for container errors around deploy or test windows. +- \`localstack-docs\` for service coverage and LocalStack-specific limitations. + +## Phase 0: Preflight + +1. Check LocalStack status. Start it if it is not running; do not start a second container. +2. Detect the IaC framework if \`${values.iac_type}\` is \`auto\`: \`cdk.json\` means CDK, \`*.tf\` means Terraform, \`template.yaml\` plus SAM config means SAM, and CloudFormation templates mean CloudFormation. +3. Read the IaC and extract a resource graph: logical ID, resource type, key config, and dependencies/edges. + +Report a short preflight summary before continuing. + +## Phase 1: Deploy and Validate + +1. Deploy \`${values.iac_path}\` with \`localstack-deployer\`. +2. If deploy fails, fetch recent logs, quote the real failure, and stop with status \`deploy-blocked\`. +3. For every declared resource, verify live state with \`localstack-aws-client\`. Compare the deployed configuration to the IaC declaration. +4. Use App Inspector traces for deployment API calls when available. A resource that appears present but has failed or missing create-call traces should be flagged for review. + +Return this table: + +| Resource | Type | Status | Evidence | Remediation | +| --- | --- | --- | --- | --- | +| \`Example\` | \`AWS::S3::Bucket\` | ready / partial / failed / unsupported | tool-backed proof | next action | + +After the table, summarize whether Phase 2 should proceed. If mode is \`validate-only\`, stop after Phase 1. + +## Phase 2: Write and Run Integration Tests + +1. Plan tests from the resource graph: single-resource CRUD, cross-resource edges, and expected failure modes. +2. Generate deterministic tests in \`${values.test_language}\` using \`${values.test_framework}\`. Put them under \`tests/integration/\`. +3. Bake in LocalStack settings: endpoint \`http://localhost.localstack.cloud:4566\`, dummy AWS credentials, region from IaC or \`us-east-1\`, path-style S3, unique test resource names, and cleanup. +4. Run tests. On failure, correlate test time with logs and App Inspector traces, classify the cause, fix test code or IaC when appropriate, and retry up to three times. + +## Final Report + +Return: +- Readiness table from Phase 1. +- Per-test table with status, iterations, last error, and remediation. +- Headline counts: resources ready/partial/failed/unsupported, tests written, passed, failed, skipped. + +Never hide real failures. If IaC is wrong, say so and propose the smallest fix. Ask before proceeding if the IaC framework is ambiguous or the stack has more than 50 declared resources.`; +} diff --git a/tests/mcp/direct.spec.mjs b/tests/mcp/direct.spec.mjs index d4234e4..42d8cc0 100644 --- a/tests/mcp/direct.spec.mjs +++ b/tests/mcp/direct.spec.mjs @@ -17,6 +17,8 @@ const EXPECTED_TOOLS = [ "localstack-app-inspector", ]; +const EXPECTED_PROMPT = "infrastructure-tester"; + function requireEnv(name) { const value = process.env[name]; if (!value || !value.trim()) { @@ -34,6 +36,26 @@ test("exposes all expected LocalStack MCP tools", async ({ mcp }) => { } }); +test("smoke tests the infrastructure tester prompt", async ({ mcp }) => { + const prompts = await mcp.client.listPrompts(); + const prompt = prompts.prompts.find((entry) => entry.name === EXPECTED_PROMPT); + + expect(prompt).toBeDefined(); + + const result = await mcp.client.getPrompt({ + name: EXPECTED_PROMPT, + arguments: { + iac_path: "./infra", + }, + }); + + expect(result.messages).toHaveLength(1); + expect(result.messages[0].role).toBe("user"); + expect(result.messages[0].content.type).toBe("text"); + expect(result.messages[0].content.text).toContain("# Infrastructure Tester (LocalStack)"); + expect(result.messages[0].content.text).toContain("`./infra`"); +}); + test("docs tool returns useful documentation snippets", async ({ mcp }) => { requireEnv("LOCALSTACK_AUTH_TOKEN"); diff --git a/xmcp.config.ts b/xmcp.config.ts index 9b43316..d0a6556 100644 --- a/xmcp.config.ts +++ b/xmcp.config.ts @@ -3,7 +3,6 @@ import { type XmcpConfig } from "xmcp"; const config: XmcpConfig = { stdio: true, paths: { - prompts: false, resources: false, }, typescript: { From a6ce22261c78c8ccfb599a2c0fdc8a30fbc86661 Mon Sep 17 00:00:00 2001 From: Harsh Mishra Date: Fri, 1 May 2026 16:07:22 +0530 Subject: [PATCH 2/4] improve the tester prompt --- src/prompts/infrastructure-tester.ts | 51 ++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/src/prompts/infrastructure-tester.ts b/src/prompts/infrastructure-tester.ts index 8c3e5e7..e61a5df 100644 --- a/src/prompts/infrastructure-tester.ts +++ b/src/prompts/infrastructure-tester.ts @@ -27,6 +27,12 @@ export const schema = { .string() .optional() .describe("Comma-separated AWS services to focus on. Empty means all discovered services."), + user_focus: z + .string() + .optional() + .describe( + "What should this test run focus on? For example: a service, resource path, workflow, or concern to prioritize. Optional." + ), }; export const metadata: PromptMetadata = { @@ -48,6 +54,7 @@ export default async function infrastructureTester(args: PromptArgs): Promise Date: Fri, 1 May 2026 21:33:17 +0530 Subject: [PATCH 3/4] resolve comments --- src/prompts/infrastructure-tester.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/prompts/infrastructure-tester.ts b/src/prompts/infrastructure-tester.ts index e61a5df..972ee5e 100644 --- a/src/prompts/infrastructure-tester.ts +++ b/src/prompts/infrastructure-tester.ts @@ -6,32 +6,32 @@ export const schema = { iac_path: z .string() .min(1) - .describe("Path to the IaC root directory, e.g. ./infra, ./cdk, or ./terraform."), + .describe("Where is the IaC project? Example: ./infra, ./cdk, or ./terraform."), iac_type: z .string() .optional() - .describe("IaC framework: cdk, terraform, sam, cloudformation, or auto. Defaults to auto."), + .describe("What IaC framework is it? Use auto, cdk, terraform, sam, or cloudformation."), test_language: z .string() .optional() - .describe("Language for generated integration tests. Defaults to typescript."), + .describe("What language should the integration tests use? Defaults to typescript."), test_framework: z .string() .optional() - .describe("Test framework. Defaults from the test language, e.g. jest or pytest."), + .describe("What test framework should be used? Defaults from the test language."), mode: z .string() .optional() - .describe("validate-only runs deployment validation only; full also writes and runs tests."), + .describe("Run validation only, or also write and run tests? Use 'validate-only' or 'full'."), services_focus: z .string() .optional() - .describe("Comma-separated AWS services to focus on. Empty means all discovered services."), + .describe("Which AWS services should get extra attention? Example: s3,lambda,dynamodb."), user_focus: z .string() .optional() .describe( - "What should this test run focus on? For example: a service, resource path, workflow, or concern to prioritize. Optional." + "Anything specific to focus on? Example: a resource path, workflow, service, or bug." ), }; From a4a9c2b91a6cd92ff021aa303a924efab28594bd Mon Sep 17 00:00:00 2001 From: Harsh Mishra Date: Fri, 1 May 2026 21:47:43 +0530 Subject: [PATCH 4/4] add optional --- src/prompts/infrastructure-tester.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/prompts/infrastructure-tester.ts b/src/prompts/infrastructure-tester.ts index 972ee5e..a7e4d03 100644 --- a/src/prompts/infrastructure-tester.ts +++ b/src/prompts/infrastructure-tester.ts @@ -10,28 +10,28 @@ export const schema = { iac_type: z .string() .optional() - .describe("What IaC framework is it? Use auto, cdk, terraform, sam, or cloudformation."), + .describe("(Optional) What IaC framework is it? Use auto, cdk, terraform, sam, or cloudformation."), test_language: z .string() .optional() - .describe("What language should the integration tests use? Defaults to typescript."), + .describe("(Optional) What language should the integration tests use? Defaults to typescript."), test_framework: z .string() .optional() - .describe("What test framework should be used? Defaults from the test language."), + .describe("(Optional) What test framework should be used? Defaults from the test language."), mode: z .string() .optional() - .describe("Run validation only, or also write and run tests? Use 'validate-only' or 'full'."), + .describe("(Optional) Run validation only, or also write and run tests? Use 'validate-only' or 'full'."), services_focus: z .string() .optional() - .describe("Which AWS services should get extra attention? Example: s3,lambda,dynamodb."), + .describe("(Optional) Which AWS services should get extra attention? Example: s3,lambda,dynamodb."), user_focus: z .string() .optional() .describe( - "Anything specific to focus on? Example: a resource path, workflow, service, or bug." + "(Optional) Anything specific to focus on? Example: a resource path, workflow, service, or bug." ), };