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
2 changes: 1 addition & 1 deletion .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,8 @@ jobs:
cache-to: type=gha,mode=max

- name: Sync README to Docker Hub overview
# Only when README lands on main; on tags the README is unchanged.
if: github.ref == 'refs/heads/main'
continue-on-error: true
uses: peter-evans/dockerhub-description@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ terraform.tfstate*
.terraform.lock.hcl
.mcp-test-results/
.DS_Store
cdk.out/
48 changes: 37 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,28 +54,54 @@ Prompts are user-selected workflow templates exposed by MCP clients as slash com

## Installation

| Editor | Installation |
| :--------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Cursor** | [![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=localstack-mcp-server&config=eyJjb21tYW5kIjoibnB4IC15IEBsb2NhbHN0YWNrL2xvY2Fsc3RhY2stbWNwLXNlcnZlciJ9) |
### Quick Setup (Wizard)

For other MCP Clients, refer to the [configuration guide](#configuration).
The fastest way to install the MCP server is the interactive setup wizard:

### Prerequisites
```bash
npx -y @localstack/localstack-mcp-server init
```

The wizard:

- lets you choose how to run the server (`npx` on your machine, or the self-contained Docker image),
- checks the prerequisites (Node.js, LocalStack CLI, Docker) and tells you how to fix anything missing,
- picks up your `LOCALSTACK_AUTH_TOKEN` from the environment, or asks for it,
- lets you pass extra LocalStack config (e.g. `DEBUG=1,PERSISTENCE=1`),
- detects your installed MCP clients (Cursor, Claude Code, Claude Desktop, VS Code, Codex, OpenCode, Amazon Q CLI) and writes the right configuration for each one you select.

It can also run fully non-interactively, e.g. in dotfiles or scripts:

```bash
npx -y @localstack/localstack-mcp-server init --method npx --client cursor,claude-code --yes
```

To remove the server from your clients again:

```bash
npx -y @localstack/localstack-mcp-server remove
```

Run `npx -y @localstack/localstack-mcp-server init --help` for all options.

### Setup using npx

#### Prerequisites

- [LocalStack CLI](https://docs.localstack.cloud/getting-started/installation/#localstack-cli) and Docker installed in your system path
- [`cdklocal`](https://github.com/localstack/aws-cdk-local), [`tflocal`](https://github.com/localstack/terraform-local), or [`samlocal`](https://github.com/localstack/aws-sam-cli-local) installed in your system path if you want to deploy CDK, Terraform, or SAM projects
- Snowflake CLI (`snow`) installed in your system path if you want to use the Snowflake tool
- A [valid LocalStack Auth Token](https://docs.localstack.cloud/aws/getting-started/auth-token/) configured as `LOCALSTACK_AUTH_TOKEN` (**required for all MCP tools**)
- [Node.js v22.x](https://nodejs.org/en/download/) or higher installed in your system path
- [Node.js v20](https://nodejs.org/en/download/) or higher installed in your system path

### Configuration
#### Configuration

Add the following to your MCP client's configuration file (e.g., `~/.cursor/mcp.json`). This configuration uses `npx` to run the server, which will automatically download and install the package if needed. LocalStack and any deployment CLIs used by tools run from your host PATH.

```json
{
"mcpServers": {
"localstack-mcp-server": {
"localstack": {
"command": "npx",
"args": ["-y", "@localstack/localstack-mcp-server"],
"env": {
Expand All @@ -93,7 +119,7 @@ If you installed from source, change `command` and `args` to point to your local
```json
{
"mcpServers": {
"localstack-mcp-server": {
"localstack": {
"command": "node",
"args": ["/path/to/your/localstack-mcp-server/dist/stdio.js"],
"env": {
Expand All @@ -104,7 +130,7 @@ If you installed from source, change `command` and `args` to point to your local
}
```

### Run with Docker
### Setup using Docker

The `localstack/localstack-mcp-server` Docker image bundles the LocalStack CLI, `awslocal`, Terraform/`tflocal`, CDK/`cdklocal`, SAM/`samlocal`, Snowflake CLI, and Docker CLI. The only required host dependency is Docker. The container uses the mounted Docker socket to run LocalStack as a sibling container on the host.

Expand All @@ -113,7 +139,7 @@ If you use the deployer tool with local Terraform, CDK, or SAM projects, bind-mo
```json
{
"mcpServers": {
"localstack-mcp-server": {
"localstack": {
"command": "docker",
"args": [
"run", "-i", "--rm",
Expand Down
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
"node": ">=20.0.0"
},
"scripts": {
"build": "xmcp build",
"build": "xmcp build && yarn build:cli",
"build:cli": "esbuild src/cli/index.ts --bundle --platform=node --format=cjs --target=node20 --tsconfig=tsconfig.cli.json --alias:jsonc-parser=jsonc-parser/lib/esm/main.js --outfile=dist/cli.js --external:./stdio.js \"--banner:js=#!/usr/bin/env node\" --log-level=warning",
"dev": "xmcp dev",
"start": "node dist/stdio.js",
"prepack": "yarn build",
"format": "prettier --write .",
"test": "jest",
"test:mcp:direct": "yarn build && playwright test -c playwright.config.mjs tests/mcp/direct.spec.mjs",
Expand All @@ -23,12 +25,15 @@
"zod": "4.3.6"
},
"devDependencies": {
"@clack/prompts": "^1.5.1",
"@gleanwork/mcp-server-tester": "1.0.0-beta.6",
"@playwright/test": "^1.58.2",
"@types/dockerode": "^3.3.43",
"@types/jest": "^30.0.0",
"esbuild": "^0.28.0",
"eslint-config-prettier": "^10.1.8",
"jest": "^30.1.3",
"jsonc-parser": "^3.3.1",
"prettier": "^3.6.2",
"swc-loader": "^0.2.6",
"ts-jest": "^29.4.1",
Expand All @@ -39,7 +44,7 @@
"dist"
],
"bin": {
"localstack-mcp-server": "./dist/stdio.js"
"localstack-mcp-server": "./dist/cli.js"
},
"author": "@localstack",
"license": "Apache-2.0",
Expand Down
41 changes: 41 additions & 0 deletions src/cli/help.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { ALL_CLIENT_IDS } from "../lib/wizard/clients/registry";

export const HELP_TEXT = `LocalStack MCP Server

Usage:
npx -y @localstack/localstack-mcp-server Start the MCP server (stdio)
npx -y @localstack/localstack-mcp-server init Set up the server in your MCP clients
npx -y @localstack/localstack-mcp-server remove Remove the server from your MCP clients

init options:
--method <npx|docker> How the MCP server should run (default: npx)
--client <ids> MCP clients to configure, comma-separated or repeated.
Valid: ${ALL_CLIENT_IDS.join(", ")}
--token <token> LocalStack Auth Token (default: $LOCALSTACK_AUTH_TOKEN)
--config <pairs> Extra LocalStack config vars, e.g. "DEBUG=1,PERSISTENCE=1"
--cache-dir <path> [docker] State/cache dir mounted into the container
(default: ~/.localstack-mcp)
--workspace <path> [docker] Workspace dir to mount for IaC deployments
(default: current directory; pass "" to skip)
--image-tag <tag> [docker] Image tag for localstack/localstack-mcp-server
(default: latest)
--force Overwrite an existing "localstack" entry without asking
-y, --yes Accept defaults for everything not provided via flags;
existing entries are kept unless --force is also given
-h, --help Show this help

remove options:
--client <ids> Clients to remove "localstack" from (default: all with an entry)
--force, -y, --yes Don't ask for confirmation

Examples:
npx -y @localstack/localstack-mcp-server init
npx -y @localstack/localstack-mcp-server init --method npx --client cursor,claude-code
npx -y @localstack/localstack-mcp-server init --method docker --client cursor --yes
npx -y @localstack/localstack-mcp-server remove --client cursor

The auth token is read from $LOCALSTACK_AUTH_TOKEN when --token is not given.
Get yours at https://app.localstack.cloud/workspace/auth-tokens

The wizard writes and removes only the MCP server entry named "localstack".
`;
65 changes: 65 additions & 0 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* Launcher for the published bin. With no recognized subcommand it starts the
* MCP server exactly as before (dist/stdio.js), so every existing client
* config keeps working. `init`/`remove` run the setup wizard.
*/
import * as fs from "fs";
import * as path from "path";

function getVersion(): string {
try {
const packageJson = JSON.parse(
fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8")
);
return packageJson.version ?? "unknown";
} catch {
return "unknown";
}
}

async function main(): Promise<void> {
const command = process.argv[2];

switch (command) {
case "init": {
const { runInit } = await import("./init");
process.exit(await runInit(process.argv.slice(3)));
break;
}
case "remove": {
const { runRemove } = await import("./remove");
process.exit(await runRemove(process.argv.slice(3)));
break;
}
case "help":
case "--help":
case "-h": {
const { HELP_TEXT } = await import("./help");
console.log(HELP_TEXT);
process.exit(0);
break;
}
case "version":
case "--version":
case "-v": {
console.log(getVersion());
process.exit(0);
break;
}
default:
// Anything else (including no args) starts the MCP server, matching the
// pre-wizard behavior for MCP clients that launch this bin. A stderr
// hint covers typos like "innit" — MCP hosts ignore stderr.
if (command !== undefined) {
console.error(
`Unknown command "${command}" — starting the MCP server. Did you mean "init"? See --help for setup commands.`
);
}
require("./stdio.js");
}
}

main().catch((error) => {
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
});
Loading
Loading