Skip to content

Commit b4c58a1

Browse files
authored
fix(docker): make LocalStack container lookup explicitly configurable (#14)
1 parent 8be6aaf commit b4c58a1

3 files changed

Lines changed: 64 additions & 10 deletions

File tree

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,13 @@ Here's how to add your LocalStack Auth Token to the environment variables:
9494
}
9595
```
9696

97+
## LocalStack Configuration
98+
99+
| Variable Name | Description | Default Value |
100+
| ------------- | ----------- | ------------- |
101+
| `LOCALSTACK_AUTH_TOKEN` | The LocalStack Auth Token to use for the MCP server | None |
102+
| `MAIN_CONTAINER_NAME` | The name of the LocalStack container to use for the MCP server | `localstack-main` |
103+
97104
## Contributing
98105

99106
Pull requests are welcomed on GitHub! To get started:

src/lib/docker/docker.client.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ describe("DockerApiClient", () => {
5757
mocks.getContainer.mockImplementation(() => ({ exec: mocks.exec }));
5858
mocks.exec.mockImplementation(async () => ({ start: mocks.start, inspect: mocks.execInspect }));
5959
mocks.__state.demuxTarget = "stdout";
60+
delete process.env.MAIN_CONTAINER_NAME;
61+
delete process.env.LOCALSTACK_MAIN_CONTAINER_NAME;
6062
});
6163

6264
test("findLocalStackContainer throws when none found", async () => {
@@ -77,6 +79,29 @@ describe("DockerApiClient", () => {
7779
await expect(client.findLocalStackContainer()).resolves.toBe("abc123");
7880
});
7981

82+
test("findLocalStackContainer matches MAIN_CONTAINER_NAME when configured", async () => {
83+
process.env.MAIN_CONTAINER_NAME = "my-custom-localstack";
84+
85+
const mocks = getDockerMocks();
86+
mocks.listContainers.mockResolvedValueOnce([
87+
{ Id: "not-this", Names: ["/localstack-main"] },
88+
{ Id: "xyz999", Names: ["/my-custom-localstack"] },
89+
]);
90+
91+
const client = new DockerApiClient();
92+
await expect(client.findLocalStackContainer()).resolves.toBe("xyz999");
93+
});
94+
95+
test("findLocalStackContainer throws when only compose-prefixed name exists without config", async () => {
96+
const mocks = getDockerMocks();
97+
mocks.listContainers.mockResolvedValueOnce([{ Id: "compose123", Names: ["/project-localstack-1"] }]);
98+
99+
const client = new DockerApiClient();
100+
await expect(client.findLocalStackContainer()).rejects.toThrow(
101+
/Could not find a running LocalStack container named "localstack-main"/i
102+
);
103+
});
104+
80105
test("executeInContainer returns stdout on success", async () => {
81106
const mocks = getDockerMocks();
82107
mocks.listContainers.mockResolvedValueOnce([{ Id: "abc123", Names: ["/localstack-main"] }]);

src/lib/docker/docker.client.ts

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,43 @@ export class DockerApiClient {
1414
this.docker = new DockerCtor();
1515
}
1616

17+
private normalizeContainerName(name?: string): string {
18+
if (!name) return "";
19+
return name.startsWith("/") ? name.slice(1) : name;
20+
}
21+
22+
private matchesConfiguredContainerName(
23+
container: { Names?: string[] },
24+
configuredName: string
25+
): boolean {
26+
return (container.Names || []).some(
27+
(n) => this.normalizeContainerName(n) === configuredName
28+
);
29+
}
30+
1731
async findLocalStackContainer(): Promise<string> {
1832
const running = (await (this.docker.listContainers as any)({
1933
filters: { status: ["running"] },
20-
})) as Array<{ Id: string; Names?: string[] }>;
21-
22-
const match = (running || []).find((c) =>
23-
(c.Names || []).some((n) => {
24-
const name = (n || "").startsWith("/") ? n.slice(1) : n;
25-
return name === "localstack-main";
26-
})
34+
})) as Array<{
35+
Id: string;
36+
Names?: string[];
37+
}>;
38+
39+
const configuredName = (
40+
process.env.MAIN_CONTAINER_NAME ||
41+
process.env.LOCALSTACK_MAIN_CONTAINER_NAME ||
42+
"localstack-main"
43+
).trim();
44+
45+
const byConfiguredName = (running || []).find((c) =>
46+
this.matchesConfiguredContainerName(c, configuredName)
2747
);
48+
if (byConfiguredName) return byConfiguredName.Id as string;
2849

29-
if (match) return match.Id as string;
30-
31-
throw new Error("Could not find a running LocalStack container named 'localstack-main'.");
50+
throw new Error(
51+
`Could not find a running LocalStack container named "${configuredName}". ` +
52+
`Set MAIN_CONTAINER_NAME to your container name if it is custom.`
53+
);
3254
}
3355

3456
async executeInContainer(

0 commit comments

Comments
 (0)