Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ This server eliminates custom scripts and manual LocalStack management with dire
- Manage LocalStack state snapshots via [Cloud Pods](https://docs.localstack.cloud/aws/capabilities/state-management/cloud-pods/) for development workflows. (requires active license)
- Install, remove, list, and discover [LocalStack Extensions](https://docs.localstack.cloud/aws/capabilities/extensions/) from the marketplace. (requires active license)
- 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.
- Connect AI assistants and dev tools for automated cloud testing workflows.

## Tools Reference
Expand All @@ -35,6 +36,7 @@ This server provides your AI with dedicated tools for managing your LocalStack e
| [`localstack-extensions`](./src/tools/localstack-extensions.ts) | Installs, uninstalls, lists, and discovers LocalStack Extensions | - Manage installed extensions via CLI actions (`list`, `install`, `uninstall`)<br/>- Browse the LocalStack Extensions marketplace (`available`)<br/>- Requires a valid LocalStack Auth Token support |
| [`localstack-ephemeral-instances`](./src/tools/localstack-ephemeral-instances.ts) | Manages cloud-hosted LocalStack Ephemeral Instances | - Create temporary cloud-hosted LocalStack instances and get an endpoint URL<br/>- List available ephemeral instances, fetch logs, and delete instances<br/>- Supports lifetime, extension preload, Cloud Pod preload, and custom env vars on create<br/>- Requires a valid LocalStack Auth Token and LocalStack CLI |
| [`localstack-aws-client`](./src/tools/localstack-aws-client.ts) | Runs AWS CLI commands inside the LocalStack for AWS container | - Executes commands via `awslocal` inside the running container<br/>- Sanitizes commands to block shell chaining<br/>- Auto-detects LocalStack coverage errors and links to docs |
| [`localstack-aws-replicator`](./src/tools/localstack-aws-replicator.ts) | Replicates external AWS resources into a running LocalStack instance | - Start single-resource replication jobs with a resource type and identifier or ARN<br/>- Start batch replication jobs, such as SSM parameters under a path prefix<br/>- Poll job status by job ID and list existing jobs<br/>- List resource types supported by the running Replicator extension<br/>- Reads source AWS credentials from the MCP server environment and supports optional target account or region overrides |
| [`localstack-docs`](./src/tools/localstack-docs.ts) | Searches LocalStack documentation through CrawlChat | - Queries LocalStack docs through a public CrawlChat collection<br/>- Returns focused snippets with source links only<br/>- Helps answer coverage, configuration, and setup questions without requiring LocalStack runtime |

## Installation
Expand Down Expand Up @@ -94,6 +96,11 @@ If you installed from source, change `command` and `args` to point to your local
| `LOCALSTACK_AUTH_TOKEN` (**required**) | The LocalStack Auth Token to use for the MCP server | None |
| `MAIN_CONTAINER_NAME` | The name of the LocalStack container to use for the MCP server | `localstack-main` |
| `MCP_ANALYTICS_DISABLED` | Disable MCP analytics when set to `1` | `0` |
| `AWS_ACCESS_KEY_ID` (**required for AWS Replicator tool**) | Source AWS access key used by AWS Replicator to read external AWS resources | None |
Comment thread
HarshCasper marked this conversation as resolved.
| `AWS_SECRET_ACCESS_KEY` (**required for AWS Replicator tool**) | Source AWS secret access key used by AWS Replicator to read external AWS resources | None |
| `AWS_DEFAULT_REGION` (**required for AWS Replicator tool**) | Source AWS region used by AWS Replicator | None |

For AWS Replicator-specific source credentials, you can use the `AWS_REPLICATOR_SOURCE_` prefixed variants instead of the unprefixed variants. Do not mix the prefixed and unprefixed source credential groups; when any `AWS_REPLICATOR_SOURCE_` variable is set, the Replicator tool reads the source configuration only from that group.

## Contributing

Expand All @@ -119,7 +126,7 @@ This repository includes [MCP Server Tester](https://github.com/gleanwork/mcp-se
export GOOGLE_GENERATIVE_AI_API_KEY="<your-gemini-key>"
export LOCALSTACK_AUTH_TOKEN="<your-localstack-auth-token>"
yarn test:mcp:evals
```
```
- Open the latest MCP Server Tester HTML report:
```bash
npx mcp-server-tester open
Expand Down
26 changes: 26 additions & 0 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@
"name": "localstack-ephemeral-instances",
"description": "Manage cloud-hosted LocalStack Ephemeral Instances by creating, listing, viewing logs, and deleting instances"
},
{
"name": "localstack-aws-client",
"description": "Runs AWS CLI commands inside the running LocalStack container"
},
{
"name": "localstack-aws-replicator",
"description": "Replicates external AWS resources into a running LocalStack instance using the AWS Replicator HTTP API"
},
{
"name": "localstack-docs",
"description": "Search the LocalStack documentation to find guides, API references, and configuration details"
Expand Down Expand Up @@ -127,6 +135,24 @@
"arguments": ["description"],
"text": "Please query the LocalStack container for ${arguments.description}."
},
{
"name": "aws-replicator-start",
"description": "Start an AWS Replicator job",
"arguments": ["resource_type", "resource_identifier"],
"text": "Start an AWS Replicator job for ${arguments.resource_type} ${arguments.resource_identifier} into LocalStack using the AWS credentials configured in the MCP server environment."
},
Comment thread
HarshCasper marked this conversation as resolved.
{
"name": "aws-replicator-start-by-arn",
"description": "Start an AWS Replicator job from a resource ARN",
"arguments": ["resource_arn"],
"text": "Start an AWS Replicator job for the resource ARN ${arguments.resource_arn} into LocalStack using the AWS credentials configured in the MCP server environment."
},
{
"name": "aws-replicator-status",
"description": "Check AWS Replicator job status",
"arguments": ["job_id"],
"text": "Check the AWS Replicator job status for ${arguments.job_id}."
},
{
"name": "cloud-pods",
"description": "Manage LocalStack state snapshots (Cloud Pods) for saving, loading, deleting, and resetting",
Expand Down
63 changes: 63 additions & 0 deletions server.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,69 @@
"format": "string",
"is_secret": true,
"name": "LOCALSTACK_AUTH_TOKEN"
},
{
"description": "Source AWS access key used by AWS Replicator to read external AWS resources",
"is_required": false,
"format": "string",
"is_secret": true,
"name": "AWS_ACCESS_KEY_ID"
},
{
"description": "Source AWS secret access key used by AWS Replicator to read external AWS resources",
"is_required": false,
"format": "string",
"is_secret": true,
"name": "AWS_SECRET_ACCESS_KEY"
},
{
"description": "Optional source AWS session token used by AWS Replicator for temporary credentials",
"is_required": false,
"format": "string",
"is_secret": true,
"name": "AWS_SESSION_TOKEN"
},
{
"description": "Source AWS region used by AWS Replicator",
"is_required": false,
"format": "string",
"is_secret": false,
"name": "AWS_DEFAULT_REGION"
},
{
"description": "Optional Replicator-specific source AWS access key. Use this instead of AWS_ACCESS_KEY_ID when the Replicator source account should be isolated from generic AWS config.",
"is_required": false,
"format": "string",
"is_secret": true,
"name": "AWS_REPLICATOR_SOURCE_AWS_ACCESS_KEY_ID"
},
{
"description": "Optional Replicator-specific source AWS secret access key. Use with AWS_REPLICATOR_SOURCE_AWS_ACCESS_KEY_ID.",
"is_required": false,
"format": "string",
"is_secret": true,
"name": "AWS_REPLICATOR_SOURCE_AWS_SECRET_ACCESS_KEY"
},
{
"description": "Optional Replicator-specific source AWS session token for temporary credentials.",
"is_required": false,
"format": "string",
"is_secret": true,
"name": "AWS_REPLICATOR_SOURCE_AWS_SESSION_TOKEN"
},
{
"description": "Optional Replicator-specific source AWS region. Use with the AWS_REPLICATOR_SOURCE_* credential group.",
"is_required": false,
"format": "string",
"is_secret": false,
"name": "AWS_REPLICATOR_SOURCE_REGION_NAME"
},
{
"description": "Optional Replicator-specific source AWS endpoint URL for advanced source-account scenarios.",
"is_required": false,
"format": "string",
"is_secret": false,
"name": "AWS_REPLICATOR_SOURCE_ENDPOINT_URL"
}
]
}
Expand Down
9 changes: 9 additions & 0 deletions src/core/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ const SHUTDOWN_TIMEOUT_MS = 1000;

export const TOOL_ARG_ALLOWLIST: Record<string, string[]> = {
"localstack-aws-client": ["command"],
"localstack-aws-replicator": [
"action",
"replication_type",
"resource_target_kind",
"resource_type",
"resource_arn_service",
"has_resource_identifier",
"has_resource_arn",
],
"localstack-chaos-injector": ["action", "rules_count", "latency_ms"],
"localstack-cloud-pods": ["action", "pod_name"],
"localstack-deployer": [
Expand Down
178 changes: 178 additions & 0 deletions src/lib/localstack/aws-replicator.logic.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import {
buildStartReplicationJobRequest,
formatReplicationJob,
formatReplicationJobs,
formatSupportedResources,
} from "../../tools/localstack-aws-replicator";

describe("localstack-aws-replicator", () => {
const originalEnv = process.env;

beforeEach(() => {
process.env = {
...originalEnv,
AWS_ACCESS_KEY_ID: "AKIA...",
AWS_SECRET_ACCESS_KEY: "secret",
AWS_DEFAULT_REGION: "us-east-1",
};
});

afterEach(() => {
process.env = originalEnv;
});

describe("buildStartReplicationJobRequest", () => {
it("builds a single-resource request from type and identifier using env credentials", () => {
const request = buildStartReplicationJobRequest({
action: "start",
replication_type: "SINGLE_RESOURCE",
resource_type: "AWS::EC2::VPC",
resource_identifier: "vpc-123",
target_account_id: "111111111111",
target_region_name: "eu-central-1",
} as any);

expect(request).toEqual({
replication_type: "SINGLE_RESOURCE",
replication_job_config: {
resource_type: "AWS::EC2::VPC",
resource_identifier: "vpc-123",
},
source_aws_config: {
aws_access_key_id: "AKIA...",
aws_secret_access_key: "secret",
region_name: "us-east-1",
},
target_aws_config: {
aws_access_key_id: "111111111111",
aws_secret_access_key: "test",
region_name: "eu-central-1",
},
});
});

it("builds a resource ARN request without requiring resource type using env credentials", () => {
process.env.AWS_SESSION_TOKEN = "token";
process.env.AWS_ENDPOINT_URL = "https://example.com";

const request = buildStartReplicationJobRequest({
action: "start",
replication_type: "SINGLE_RESOURCE",
resource_arn: "arn:aws:secretsmanager:us-east-1:123456789012:secret:my-secret",
} as any);

expect(request).toEqual({
replication_type: "SINGLE_RESOURCE",
replication_job_config: {
resource_arn: "arn:aws:secretsmanager:us-east-1:123456789012:secret:my-secret",
},
source_aws_config: {
aws_access_key_id: "AKIA...",
aws_secret_access_key: "secret",
aws_session_token: "token",
region_name: "us-east-1",
endpoint_url: "https://example.com",
},
});
});

it("does not mix prefixed source credentials with generic AWS session values", () => {
process.env.AWS_REPLICATOR_SOURCE_AWS_ACCESS_KEY_ID = "replicator-key";
process.env.AWS_REPLICATOR_SOURCE_AWS_SECRET_ACCESS_KEY = "replicator-secret";
process.env.AWS_REPLICATOR_SOURCE_REGION_NAME = "eu-west-1";
process.env.AWS_SESSION_TOKEN = "generic-token";

const request = buildStartReplicationJobRequest({
action: "start",
replication_type: "SINGLE_RESOURCE",
resource_type: "AWS::SSM::Parameter",
resource_identifier: "my-param",
} as any);

expect(request.source_aws_config).toEqual({
aws_access_key_id: "replicator-key",
aws_secret_access_key: "replicator-secret",
region_name: "eu-west-1",
});
});

it("does not allow a target endpoint URL override", () => {
process.env.AWS_REPLICATOR_TARGET_ENDPOINT_URL = "https://not-localstack.example.com";

const request = buildStartReplicationJobRequest({
action: "start",
replication_type: "SINGLE_RESOURCE",
resource_type: "AWS::EC2::VPC",
resource_identifier: "vpc-123",
target_region_name: "eu-central-1",
} as any);

expect(request.target_aws_config).toEqual({
aws_access_key_id: "test",
aws_secret_access_key: "test",
region_name: "eu-central-1",
});
});
});

describe("formatReplicationJob", () => {
it("includes batch result details", () => {
const formatted = formatReplicationJob("AWS Replicator Job Status", {
job_id: "job-123",
state: "SUCCEEDED",
type: "BATCH",
replication_config: {
resource_type: "AWS::SSM::Parameter",
identifier: "/dev/",
},
result: {
resources_succeeded: 2,
resources_failed: 0,
},
});

expect(formatted).toContain("AWS Replicator Job Status");
expect(formatted).toContain("`job-123`");
expect(formatted).toContain("resources_succeeded");
expect(formatted).toContain("AWS::SSM::Parameter");
});
});

describe("formatReplicationJobs", () => {
it("summarizes listed jobs and includes the raw response", () => {
const formatted = formatReplicationJobs([
{
job_id: "job-123",
state: "SUCCEEDED",
type: "SINGLE_RESOURCE",
replication_config: {
resource_type: "AWS::EC2::VPC",
resource_identifier: "vpc-123",
},
},
]);

expect(formatted).toContain("AWS Replicator Jobs");
expect(formatted).toContain("job-123");
expect(formatted).toContain("SUCCEEDED");
expect(formatted).toContain("Raw Response");
});
});

describe("formatSupportedResources", () => {
it("summarizes supported resource types and identifiers", () => {
const formatted = formatSupportedResources([
{
resource_type: "AWS::SSM::Parameter",
service: "ssm",
identifier: "Name",
},
]);

expect(formatted).toContain("AWS Replicator Supported Resources");
expect(formatted).toContain("AWS::SSM::Parameter");
expect(formatted).toContain("identifier: `Name`");
expect(formatted).toContain("Raw Response");
});
});
});
1 change: 1 addition & 0 deletions src/lib/localstack/license-checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export enum ProFeature {
CLOUD_PODS = "localstack.platform.plugin/pods",
CHAOS_ENGINEERING = "localstack.platform.plugin/chaos",
EXTENSIONS = "localstack.platform.plugin/extensions",
REPLICATOR = "localstack.platform.plugin/replicator",
SNOWFLAKE = "localstack.aws.provider/snowflake:pro",
}

Expand Down
Loading
Loading