Skip to content

Commit be90867

Browse files
committed
Gate all LocalStack MCP tools behind LOCALSTACK_AUTH_TOKEN
1 parent b175a96 commit be90867

15 files changed

Lines changed: 89 additions & 68 deletions

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ jobs:
2727
run: yarn build
2828

2929
mcp-direct-tests:
30+
if: ${{ secrets.LOCALSTACK_AUTH_TOKEN != '' }}
3031
runs-on: ubuntu-latest
32+
env:
33+
LOCALSTACK_AUTH_TOKEN: ${{ secrets.LOCALSTACK_AUTH_TOKEN }}
3134

3235
steps:
3336
- name: Checkout code

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ terraform.tfstate*
1111
.terraform/
1212
.terraform.lock.hcl
1313
.mcp-test-results/
14+
.DS_Store

README.md

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ This server eliminates custom scripts and manual LocalStack management with dire
2020

2121
This server provides your AI with dedicated tools for managing your LocalStack environment:
2222

23+
> [!NOTE]
24+
> All tools in this MCP server require `LOCALSTACK_AUTH_TOKEN`.
25+
2326
| Tool Name | Description | Key Features |
2427
| :-------------------------------------------------------------------------------- | :------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
2528
| [`localstack-management`](./src/tools/localstack-management.ts) | Manages LocalStack runtime operations for AWS and Snowflake stacks | - Execute start, stop, restart, and status checks<br/>- Integrate LocalStack authentication tokens<br/>- Inject custom environment variables<br/>- Verify real-time status and perform health monitoring |
@@ -43,7 +46,7 @@ For other MCP Clients, refer to the [configuration guide](#configuration).
4346

4447
- [LocalStack CLI](https://docs.localstack.cloud/getting-started/installation/#localstack-cli) and Docker installed in your system path
4548
- [`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 for running infrastructure deployment tooling
46-
- A [valid LocalStack Auth Token](https://docs.localstack.cloud/aws/getting-started/auth-token/) to enable Pro services, IAM Policy Analyzer, Cloud Pods, Chaos Injector, and Extensions tools (**optional**)
49+
- A [valid LocalStack Auth Token](https://docs.localstack.cloud/aws/getting-started/auth-token/) configured as `LOCALSTACK_AUTH_TOKEN` (**required for all MCP tools**)
4750
- [Node.js v22.x](https://nodejs.org/en/download/) or higher installed in your system path
4851

4952
### Configuration
@@ -55,40 +58,28 @@ Add the following to your MCP client's configuration file (e.g., `~/.cursor/mcp.
5558
"mcpServers": {
5659
"localstack-mcp-server": {
5760
"command": "npx",
58-
"args": ["-y", "@localstack/localstack-mcp-server"]
61+
"args": ["-y", "@localstack/localstack-mcp-server"],
62+
"env": {
63+
"LOCALSTACK_AUTH_TOKEN": "<YOUR_TOKEN>"
64+
}
5965
}
6066
}
6167
}
6268
```
6369

70+
All LocalStack MCP tools require `LOCALSTACK_AUTH_TOKEN` to be set. You can get your LocalStack Auth Token by following the official [documentation](https://docs.localstack.cloud/aws/getting-started/auth-token/).
71+
6472
If you installed from source, change `command` and `args` to point to your local build:
6573

6674
```json
6775
{
6876
"mcpServers": {
6977
"localstack-mcp-server": {
7078
"command": "node",
71-
"args": ["/path/to/your/localstack-mcp-server/dist/stdio.js"]
72-
}
73-
}
74-
}
75-
```
76-
77-
#### Enabling Licensed Features
78-
79-
To activate LocalStack licensed features, you need to add your LocalStack Auth Token to the environment variables. You can get your LocalStack Auth Token by following the official [documentation](https://docs.localstack.cloud/aws/getting-started/auth-token/).
80-
81-
Here's how to add your LocalStack Auth Token to the environment variables:
82-
83-
```json
84-
{
85-
"mcpServers": {
86-
"localstack-mcp-server": {
87-
"command": "npx",
88-
"args": ["-y", "@localstack/localstack-mcp-server"],
79+
"args": ["/path/to/your/localstack-mcp-server/dist/stdio.js"],
8980
"env": {
9081
"LOCALSTACK_AUTH_TOKEN": "<YOUR_TOKEN>"
91-
}
82+
}
9283
}
9384
}
9485
}
@@ -98,7 +89,7 @@ Here's how to add your LocalStack Auth Token to the environment variables:
9889

9990
| Variable Name | Description | Default Value |
10091
| ------------- | ----------- | ------------- |
101-
| `LOCALSTACK_AUTH_TOKEN` | The LocalStack Auth Token to use for the MCP server | None |
92+
| `LOCALSTACK_AUTH_TOKEN` (**required**) | The LocalStack Auth Token to use for the MCP server | None |
10293
| `MAIN_CONTAINER_NAME` | The name of the LocalStack container to use for the MCP server | `localstack-main` |
10394
| `MCP_ANALYTICS_DISABLED` | Disable MCP analytics when set to `1` | `0` |
10495

@@ -139,7 +130,7 @@ This repository includes [MCP Server Tester](https://github.com/gleanwork/mcp-se
139130
Notes:
140131

141132
- MCP tests target the local STDIO server command `node dist/stdio.js` by default.
142-
- `LOCALSTACK_AUTH_TOKEN` is required for the comprehensive Gemini eval suite.
133+
- `LOCALSTACK_AUTH_TOKEN` is required for all MCP tool usage and test suites.
143134
- You can override the target command with:
144135
- `MCP_TEST_COMMAND`
145136
- `MCP_TEST_ARGS` (space-separated arguments)

server.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
},
2020
"environment_variables": [
2121
{
22-
"description": "LocalStack Auth Token (optional for Pro features)",
23-
"is_required": false,
22+
"description": "LocalStack Auth Token (required for all LocalStack MCP tools)",
23+
"is_required": true,
2424
"format": "string",
2525
"is_secret": true,
2626
"name": "LOCALSTACK_AUTH_TOKEN"

src/core/preflight.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const requireProFeature = async (feature: ProFeature): Promise<ToolRespon
1717
};
1818

1919
export const requireAuthToken = (): ToolResponse | null => {
20-
if (!process.env.LOCALSTACK_AUTH_TOKEN) {
20+
if (!process.env.LOCALSTACK_AUTH_TOKEN?.trim()) {
2121
return ResponseBuilder.error(
2222
"Auth Token Required",
2323
"LOCALSTACK_AUTH_TOKEN is required for this operation."
@@ -27,9 +27,9 @@ export const requireAuthToken = (): ToolResponse | null => {
2727
};
2828

2929
export const runPreflights = async (
30-
checks: Array<Promise<ToolResponse | null>>
30+
checks: Array<ToolResponse | null | Promise<ToolResponse | null>>
3131
): Promise<ToolResponse | null> => {
32-
const results = await Promise.all(checks);
32+
const results = await Promise.all(checks.map((check) => Promise.resolve(check)));
3333
return results.find((r) => r !== null) || null;
3434
};
3535

src/tools/localstack-aws-client.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { z } from "zod";
22
import { type ToolMetadata, type InferSchema } from "xmcp";
3-
import { runPreflights, requireLocalStackRunning } from "../core/preflight";
3+
import { runPreflights, requireLocalStackRunning, requireAuthToken } from "../core/preflight";
44
import { ResponseBuilder } from "../core/response-builder";
55
import { withToolAnalytics } from "../core/analytics";
66
import { DockerApiClient } from "../lib/docker/docker.client";
@@ -29,7 +29,7 @@ export const metadata: ToolMetadata = {
2929

3030
export default async function localstackAwsClient({ command }: InferSchema<typeof schema>) {
3131
return withToolAnalytics("localstack-aws-client", { command }, async () => {
32-
const preflightError = await runPreflights([requireLocalStackRunning()]);
32+
const preflightError = await runPreflights([requireAuthToken(), requireLocalStackRunning()]);
3333
if (preflightError) return preflightError;
3434

3535
try {

src/tools/localstack-chaos-injector.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ import { type ToolMetadata, type InferSchema } from "xmcp";
33
import { ProFeature } from "../lib/localstack/license-checker";
44
import { ChaosApiClient } from "../lib/localstack/localstack.client";
55
import { ResponseBuilder } from "../core/response-builder";
6-
import { runPreflights, requireProFeature } from "../core/preflight";
6+
import {
7+
runPreflights,
8+
requireAuthToken,
9+
requireLocalStackRunning,
10+
requireProFeature,
11+
} from "../core/preflight";
712
import { withToolAnalytics } from "../core/analytics";
813

914
// Define the fault rule schema
@@ -130,7 +135,11 @@ export default async function localstackChaosInjector({
130135
"localstack-chaos-injector",
131136
{ action, rules_count: rules?.length, latency_ms },
132137
async () => {
133-
const preflightError = await runPreflights([requireProFeature(ProFeature.CHAOS_ENGINEERING)]);
138+
const preflightError = await runPreflights([
139+
requireAuthToken(),
140+
requireLocalStackRunning(),
141+
requireProFeature(ProFeature.CHAOS_ENGINEERING),
142+
]);
134143
if (preflightError) return preflightError;
135144

136145
const client = new ChaosApiClient();

src/tools/localstack-cloud-pods.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@ import { type ToolMetadata, type InferSchema } from "xmcp";
33
import { ProFeature } from "../lib/localstack/license-checker";
44
import { CloudPodsApiClient } from "../lib/localstack/localstack.client";
55
import { ResponseBuilder } from "../core/response-builder";
6-
import { runPreflights, requireLocalStackCli, requireProFeature } from "../core/preflight";
6+
import {
7+
runPreflights,
8+
requireAuthToken,
9+
requireLocalStackRunning,
10+
requireLocalStackCli,
11+
requireProFeature,
12+
} from "../core/preflight";
713
import { withToolAnalytics } from "../core/analytics";
814

915
// Define the schema for tool parameters
@@ -43,6 +49,8 @@ export default async function localstackCloudPods({
4349
}: InferSchema<typeof schema>) {
4450
return withToolAnalytics("localstack-cloud-pods", { action, pod_name }, async () => {
4551
const preflightError = await runPreflights([
52+
requireAuthToken(),
53+
requireLocalStackRunning(),
4654
requireLocalStackCli(),
4755
requireProFeature(ProFeature.CLOUD_PODS),
4856
]);

src/tools/localstack-deployer.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ import { type ToolMetadata, type InferSchema } from "xmcp";
33
import { runCommand, stripAnsiCodes } from "../core/command-runner";
44
import path from "path";
55
import fs from "fs";
6-
import { ensureLocalStackCli } from "../lib/localstack/localstack.utils";
7-
import { runPreflights, requireLocalStackRunning } from "../core/preflight";
6+
import {
7+
runPreflights,
8+
requireAuthToken,
9+
requireLocalStackRunning,
10+
} from "../core/preflight";
811
import { DockerApiClient } from "../lib/docker/docker.client";
912
import {
1013
checkDependencies,
@@ -100,15 +103,8 @@ export default async function localstackDeployer({
100103
"localstack-deployer",
101104
{ action, projectType, directory, stackName, templatePath, variables, s3Bucket, resolveS3, saveParams },
102105
async () => {
103-
if (action === "deploy" || action === "destroy") {
104-
const preflightError = await runPreflights([requireLocalStackRunning()]);
105-
if (preflightError) return preflightError;
106-
const cliError = await ensureLocalStackCli();
107-
if (cliError) return cliError;
108-
} else {
109-
const preflightError = await runPreflights([requireLocalStackRunning()]);
110-
if (preflightError) return preflightError;
111-
}
106+
const preflightError = await runPreflights([requireAuthToken(), requireLocalStackRunning()]);
107+
if (preflightError) return preflightError;
112108

113109
if (action === "create-stack") {
114110
if (!stackName) {

src/tools/localstack-docs.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { z } from "zod";
22
import { type ToolMetadata, type InferSchema } from "xmcp";
33
import { httpClient } from "../core/http-client";
4+
import { runPreflights, requireAuthToken } from "../core/preflight";
45
import { ResponseBuilder } from "../core/response-builder";
56
import { withToolAnalytics } from "../core/analytics";
67

@@ -38,6 +39,9 @@ export const metadata: ToolMetadata = {
3839
export default async function localstackDocs({ query, limit }: InferSchema<typeof schema>) {
3940
return withToolAnalytics("localstack-docs", { query, limit }, async () => {
4041
try {
42+
const preflightError = await runPreflights([requireAuthToken()]);
43+
if (preflightError) return preflightError;
44+
4145
const endpoint = `${CRAWLCHAT_DOCS_ENDPOINT}?query=${encodeURIComponent(query)}`;
4246
const response = await httpClient.request<CrawlChatDocsResult[]>(endpoint, {
4347
method: "GET",

0 commit comments

Comments
 (0)