Skip to content

Commit adf003e

Browse files
authored
feat(azure): add the localstack-azure-client tool and Azure lifecycle support (#86)
1 parent 39ace66 commit adf003e

47 files changed

Lines changed: 3337 additions & 20 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/azure-smoke.yml‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: Azure smoke
2+
3+
# The Azure tool against the LocalStack Azure emulator, weekly and on demand.
4+
on:
5+
workflow_dispatch:
6+
schedule:
7+
- cron: "0 6 * * 1"
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
azure-smoke:
14+
runs-on: ubuntu-latest
15+
timeout-minutes: 30
16+
env:
17+
# The token's licence must include LocalStack for Azure, or the emulator refuses to start.
18+
LOCALSTACK_AUTH_TOKEN: ${{ secrets.LOCALSTACK_AUTH_TOKEN }}
19+
steps:
20+
- name: Checkout code
21+
uses: actions/checkout@v4
22+
23+
- name: Use Node.js 22
24+
uses: actions/setup-node@v4
25+
with:
26+
node-version: 22.x
27+
28+
- name: Install dependencies
29+
run: yarn
30+
31+
# ubuntu-latest ships the Azure CLI.
32+
- name: Show the Azure CLI version
33+
run: az version
34+
35+
- name: Pull the LocalStack Azure emulator image
36+
run: docker pull localstack/localstack-azure:latest
37+
38+
- name: Run az commands through the Azure tool
39+
run: yarn build && npx playwright test -c playwright.config.mjs tests/mcp/azure-smoke.spec.mjs
40+
41+
- name: Keep the test results of a failed run
42+
if: failure()
43+
uses: actions/upload-artifact@v4
44+
with:
45+
name: azure-smoke-results
46+
path: .mcp-test-results
47+
if-no-files-found: ignore

‎README.md‎

Lines changed: 14 additions & 2 deletions
Large diffs are not rendered by default.

‎data/sample-azure/hello.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
hello from the LocalStack MCP server Azure stage

‎docs/AZURE.md‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Using the Azure tool
2+
3+
`localstack-azure-client` runs the Azure CLI (`az`) installed on your machine against the LocalStack for Azure emulator; start the emulator with `localstack-management` (`service: azure`). Its settings are listed in the [README](../README.md#localstack-configuration).
4+
5+
- The tool keeps its own Azure CLI profile (`LOCALSTACK_AZ_CONFIG_DIR`), logged in to the emulator with a dummy account, so your own Azure login is never used or changed.
6+
- It uses your Azure CLI extensions: install one with `az extension add --name <extension>`.
7+
- Bicep templates need the `bicep` binary on your `PATH` (for example `brew install bicep` or `winget install Microsoft.Bicep`).
8+
- Relative file paths in commands resolve against `LOCALSTACK_AZ_WORKDIR`, by default the directory the MCP client starts the server in.
9+
- `rest` calls the emulator: give it a relative URL. An absolute URL must name a local host; a `management.azure.com` URL is turned into a relative one.
10+
- Only `rest` URLs are checked: an endpoint given in a command, such as `--blob-endpoint` or a connection string, is used as it is.
11+
- The Docker image does not include the Azure CLI yet: run the server with npx or from source to use the Azure tool.

‎docs/DOCKER.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ alias covers bootstrap asset uploads.
125125
on the host even if your editor restarts the MCP container — reconnecting finds
126126
your stack still up. State persists in the `localstack-mcp` named volume (or
127127
`LOCALSTACK_VOLUME_DIR` if you set one).
128+
- **No Azure CLI.** The image lists all 15 tools, but it does not include the Azure
129+
CLI, so `localstack-azure-client` answers that `az` is not available. To use the
130+
Azure tool, run the server with npx or from source. `localstack-management` can
131+
still start and stop the Azure emulator (`service: azure`) from the image.
128132

129133
## Troubleshooting
130134

‎manifest.json‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@
6767
"name": "localstack-aws-client",
6868
"description": "Runs AWS CLI commands inside the running LocalStack container"
6969
},
70+
{
71+
"name": "localstack-azure-client",
72+
"description": "Runs Azure CLI (az) commands against the LocalStack for Azure emulator"
73+
},
7074
{
7175
"name": "localstack-aws-replicator",
7276
"description": "Replicates external AWS resources into a running LocalStack instance using the AWS Replicator HTTP API"
@@ -156,6 +160,12 @@
156160
"arguments": ["description"],
157161
"text": "Please query the LocalStack container for ${arguments.description}."
158162
},
163+
{
164+
"name": "localstack-azure-client",
165+
"description": "Runs Azure CLI commands against the LocalStack Azure emulator",
166+
"arguments": ["description"],
167+
"text": "Please query the LocalStack Azure emulator for ${arguments.description}."
168+
},
159169
{
160170
"name": "aws-replicator-start",
161171
"description": "Start an AWS Replicator job",

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"prepack": "yarn build",
1515
"format": "prettier --write .",
1616
"test": "jest",
17-
"test:mcp:direct": "yarn build && playwright test -c playwright.config.mjs tests/mcp/direct.spec.mjs",
17+
"test:mcp:direct": "yarn build && playwright test -c playwright.config.mjs tests/mcp/direct.spec.mjs tests/mcp/azure-offline.spec.mjs",
1818
"test:mcp:evals": "yarn build && playwright test -c playwright.config.mjs tests/mcp/evals-gemini.spec.mjs",
1919
"test:mcp": "yarn test:mcp:direct && yarn test:mcp:evals"
2020
},

‎playwright.config.mjs‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
import { defineConfig } from "@playwright/test";
2+
import net from "node:net";
3+
import { tmpdir } from "node:os";
4+
import { join } from "node:path";
25

36
const mcpCommand = process.env.MCP_TEST_COMMAND || "node";
47
const mcpArgs = process.env.MCP_TEST_ARGS
58
? process.env.MCP_TEST_ARGS.split(" ").filter(Boolean)
69
: ["dist/cli.js"];
710

11+
// A port where nothing listens, for the Azure tool's tests without an emulator.
12+
const probe = net.createServer();
13+
await new Promise((resolve) => probe.listen(0, "127.0.0.1", resolve));
14+
const freePort = probe.address().port;
15+
await new Promise((resolve) => probe.close(resolve));
16+
817
export default defineConfig({
918
testDir: "./tests/mcp",
1019
timeout: 120000,
@@ -23,6 +32,7 @@ export default defineConfig({
2332
projects: [
2433
{
2534
name: "localstack-mcp-server",
35+
testIgnore: /azure-offline\.spec\.mjs$/,
2636
use: {
2737
mcpConfig: {
2838
transport: "stdio",
@@ -40,5 +50,30 @@ export default defineConfig({
4050
},
4151
},
4252
},
53+
{
54+
// The Azure tool with no emulator: a dummy token, a port where nothing listens and a
55+
// config dir of its own.
56+
name: "azure-offline",
57+
testMatch: /azure-offline\.spec\.mjs$/,
58+
use: {
59+
mcpConfig: {
60+
transport: "stdio",
61+
command: mcpCommand,
62+
args: mcpArgs,
63+
cwd: process.cwd(),
64+
quiet: true,
65+
connectTimeoutMs: 30000,
66+
requestTimeoutMs: 300000,
67+
callTimeoutMs: 300000,
68+
env: {
69+
...process.env,
70+
LOCALSTACK_AUTH_TOKEN: "ls-dummy-token-for-offline-tests",
71+
LOCALSTACK_PORT: String(freePort),
72+
LOCALSTACK_AZ_CONFIG_DIR: join(tmpdir(), "lsmcp-azure-offline"),
73+
MCP_ANALYTICS_DISABLED: "1",
74+
},
75+
},
76+
},
77+
},
4378
],
4479
});

‎server.json‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,55 @@
157157
"format": "string",
158158
"isSecret": false,
159159
"name": "EXTERNAL_SERVICE_PORTS_END"
160+
},
161+
{
162+
"description": "Host name of the LocalStack gateway the tools call. Defaults to localhost; when the server runs in Docker, host.docker.internal.",
163+
"isRequired": false,
164+
"format": "string",
165+
"isSecret": false,
166+
"name": "LOCALSTACK_HOSTNAME"
167+
},
168+
{
169+
"description": "Port of the LocalStack gateway the tools call. Defaults to 4566.",
170+
"isRequired": false,
171+
"format": "string",
172+
"isSecret": false,
173+
"name": "LOCALSTACK_PORT"
174+
},
175+
{
176+
"description": "Docker image the management start action launches for service: azure. Defaults to localstack/localstack-azure:latest.",
177+
"isRequired": false,
178+
"format": "string",
179+
"isSecret": false,
180+
"name": "LOCALSTACK_AZURE_IMAGE_NAME"
181+
},
182+
{
183+
"description": "The az launcher, or the Azure CLI's Python, for localstack-azure-client. Defaults to the first az on PATH.",
184+
"isRequired": false,
185+
"format": "string",
186+
"isSecret": false,
187+
"name": "LOCALSTACK_AZ_PATH"
188+
},
189+
{
190+
"description": "The Azure tool's own Azure CLI profile directory; it must not overlap ~/.azure. Defaults to ~/.localstack/azure/mcp-config-<port>.",
191+
"isRequired": false,
192+
"format": "string",
193+
"isSecret": false,
194+
"name": "LOCALSTACK_AZ_CONFIG_DIR"
195+
},
196+
{
197+
"description": "The directory localstack-azure-client runs az in; relative file paths resolve against it. Defaults to the server's working directory.",
198+
"isRequired": false,
199+
"format": "string",
200+
"isSecret": false,
201+
"name": "LOCALSTACK_AZ_WORKDIR"
202+
},
203+
{
204+
"description": "Time limit in seconds for one Azure CLI command (5-3600). Defaults to 300.",
205+
"isRequired": false,
206+
"format": "string",
207+
"isSecret": false,
208+
"name": "LOCALSTACK_AZ_TIMEOUT_SECONDS"
160209
}
161210
]
162211
}

‎src/core/analytics.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const SHUTDOWN_TIMEOUT_MS = 1000;
1919

2020
export const TOOL_ARG_ALLOWLIST: Record<string, string[]> = {
2121
"localstack-aws-client": ["command"],
22+
"localstack-azure-client": ["command"],
2223
"localstack-aws-replicator": [
2324
"action",
2425
"replication_type",

0 commit comments

Comments
 (0)