Repository navigation
Expand file tree
/
Copy pathlocalstack-azure-client.ts
More file actions
72 lines (67 loc) · 3.02 KB
/
Copy pathlocalstack-azure-client.ts
File metadata and controls
72 lines (67 loc) · 3.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import { z } from "zod";
import { type InferSchema, type ToolExtraArguments, type ToolMetadata } from "xmcp";
import { requireAuthToken, runPreflights } from "../core/preflight";
import { ResponseBuilder } from "../core/response-builder";
import { withToolAnalytics } from "../core/analytics";
import { formatAzResult } from "../lib/azure/output";
import { evaluateAzCommand } from "../lib/azure/policy";
import {
requireAzureCli,
requireAzureCliConfigured,
requireAzureConfig,
requireAzureEmulatorRunning,
} from "../lib/azure/preflight";
import { azureConfig, runAzCommand } from "../lib/azure/services";
export const schema = {
command: z
.string()
.trim()
.min(1, { message: "The command string cannot be empty." })
.describe(
"One Azure CLI command, with or without the leading `az`, for example `group create --name demo --location westeurope`. No shell syntax: no pipes, `&&`, redirects or `$(...)`."
),
};
export const metadata: ToolMetadata = {
name: "localstack-azure-client",
description:
"Run Azure CLI (`az`) commands against the LocalStack Azure emulator. The tool keeps its own Azure CLI profile, logged in to the emulator with a dummy account, so your own Azure login is never used or changed. Relative file paths resolve against the server's working directory (LOCALSTACK_AZ_WORKDIR), and a leading `~` in any argument is your home directory. For an operation without a command, use `rest` with a relative URL; `rest` reaches the emulator and other local hosts only. Start the emulator with `localstack-management` (service: azure).",
annotations: {
title: "LocalStack Azure Client",
readOnlyHint: false,
destructiveHint: true,
idempotentHint: false,
openWorldHint: false,
},
};
export default async function localstackAzureClient(
{ command }: InferSchema<typeof schema>,
extra?: ToolExtraArguments
) {
return withToolAnalytics("localstack-azure-client", { command }, async () => {
try {
// The token, the settings and the policy first: a refusal needs no emulator.
const early = requireAuthToken() ?? requireAzureConfig();
if (early) return early;
const policy = evaluateAzCommand(command);
if (!policy.ok) return ResponseBuilder.error(policy.title, policy.message);
// Both run at once; a missing az is reported first, as the more basic prerequisite.
const signal = extra?.signal;
const preflightError =
(await runPreflights([requireAzureCli(signal), requireAzureEmulatorRunning()])) ??
(await requireAzureCliConfigured(signal));
if (preflightError) return preflightError;
const config = azureConfig();
const result = await runAzCommand(policy.argv, { signal });
return formatAzResult(result, {
notes: policy.notes,
timeoutSeconds: config.timeoutMs / 1000,
healthBaseUrl: config.healthBaseUrl,
});
} catch (error) {
return ResponseBuilder.error(
"Azure Tool Error",
error instanceof Error ? error.message : String(error)
);
}
});
}