init project

This commit is contained in:
root
2026-07-31 13:12:54 -04:00
parent 0da92d5e02
commit f3863f760c
7215 changed files with 1860260 additions and 1 deletions
+92
View File
@@ -0,0 +1,92 @@
import fs from 'node:fs';
import path from 'node:path';
import { z } from 'zod';
import { config } from '../../config.js';
import { resolveWorkspacePath } from '../../lib/sandbox.js';
import { errorResult, jsonResult, toErrorMessage } from '../shared.js';
import { dockerManager } from './dockerManager.js';
export function registerDockerBuildImageTool(server) {
server.registerTool('docker_build_image', {
title: 'Docker Build Image',
description: 'Build a Docker image from a Dockerfile in the workspace. Streams the build output back and returns the resulting image tag.',
inputSchema: {
tag: z.string().min(1).describe('Image tag, e.g. "my-app:latest".'),
dockerfile: z
.string()
.optional()
.default('Dockerfile')
.describe('Path to the Dockerfile, relative to the workspace root.'),
buildContext: z
.string()
.optional()
.default('.')
.describe('Build context directory, relative to the workspace root.'),
},
}, async ({ tag, dockerfile, buildContext }) => {
try {
await dockerManager.ensureReachable();
const dockerfilePath = resolveWorkspacePath(config.workspaceRoot, dockerfile);
const contextPath = resolveWorkspacePath(config.workspaceRoot, buildContext);
if (!fs.existsSync(dockerfilePath)) {
return errorResult(`Dockerfile not found: ${dockerfilePath}`);
}
const stream = await dockerManager.getDocker().buildImage({
context: contextPath,
src: [path.basename(dockerfilePath), ...listContextFiles(contextPath)],
}, { t: tag, dockerfile: path.basename(dockerfilePath) });
const output = [];
await new Promise((resolve, reject) => {
dockerManager
.getDocker()
.modem.followProgress(stream, (err, res) => {
if (err) {
reject(err);
return;
}
const last = res?.[res.length - 1];
if (last && 'error' in last && last.error) {
reject(new Error(String(last.error)));
return;
}
resolve();
}, (event) => {
const line = typeof event === 'string' ? event : JSON.stringify(event);
output.push(line);
if (output.length > config.maxBufferedLogLines) {
output.splice(0, output.length - config.maxBufferedLogLines);
}
});
});
return jsonResult({
tag,
dockerfile: dockerfilePath,
context: contextPath,
output: output.join('\n').slice(-config.maxToolOutputChars),
});
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
function listContextFiles(root) {
const files = [];
const walk = (dir) => {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const full = path.join(dir, entry.name);
const rel = path.relative(root, full);
if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === 'dist')
continue;
if (entry.isDirectory()) {
walk(full);
}
else {
files.push(rel);
}
}
};
walk(root);
return files;
}
//# sourceMappingURL=buildImage.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"buildImage.js","sourceRoot":"","sources":["../../../src/tools/docker/buildImage.ts"],"names":[],"mappings":"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"}
+78
View File
@@ -0,0 +1,78 @@
import { z } from 'zod';
import { config } from '../../config.js';
import { runCommand } from '../../lib/runCommand.js';
import { errorResult, jsonResult, toErrorMessage } from '../shared.js';
/**
* Docker Compose is invoked via the local `docker compose` or `docker-compose`
* CLI. The server does not parse compose files itself; it delegates to the
* official tool and streams the output back, just like workflow tools.
*/
export function registerDockerComposeTools(server) {
server.registerTool('docker_compose_up', {
title: 'Docker Compose Up',
description: 'Bring up a docker-compose.yml stack in the workspace root. Detached by default so the server does not block.',
inputSchema: {
file: z
.string()
.optional()
.default('docker-compose.yml')
.describe('Compose file path, relative to the workspace root.'),
services: z
.array(z.string())
.optional()
.describe('Specific services to start. Omit to start all.'),
build: z
.boolean()
.optional()
.default(false)
.describe('Build images before starting.'),
},
}, async ({ file, services, build }) => {
try {
const args = ['compose', '-f', file, 'up', '-d'];
if (build)
args.push('--build');
if (services && services.length > 0)
args.push(...services);
const result = await runCommand('docker', args, {
cwd: config.workspaceRoot,
timeoutMs: config.scaffoldCommandTimeoutMs,
});
return jsonResult({ file, ...result });
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
server.registerTool('docker_compose_down', {
title: 'Docker Compose Down',
description: 'Shut down a docker-compose.yml stack in the workspace root.',
inputSchema: {
file: z
.string()
.optional()
.default('docker-compose.yml')
.describe('Compose file path, relative to the workspace root.'),
volumes: z
.boolean()
.optional()
.default(false)
.describe('Also remove named volumes declared in the volumes section.'),
},
}, async ({ file, volumes }) => {
try {
const args = ['compose', '-f', file, 'down'];
if (volumes)
args.push('-v');
const result = await runCommand('docker', args, {
cwd: config.workspaceRoot,
timeoutMs: config.defaultCommandTimeoutMs,
});
return jsonResult({ file, ...result });
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
//# sourceMappingURL=compose.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"compose.js","sourceRoot":"","sources":["../../../src/tools/docker/compose.ts"],"names":[],"mappings":"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"}
+52
View File
@@ -0,0 +1,52 @@
import { z } from 'zod';
import { config } from '../../config.js';
import { errorResult, jsonResult, toErrorMessage } from '../shared.js';
import { dockerManager } from './dockerManager.js';
export function registerDockerContainerLogsTool(server) {
server.registerTool('docker_get_container_logs', {
title: 'Docker Get Container Logs',
description: 'Fetch logs from a running or stopped Docker container started by this server.',
inputSchema: {
containerId: z.string().min(1).describe('Container ID (short or long) returned by docker_run_container.'),
tail: z
.number()
.int()
.positive()
.optional()
.default(200)
.describe('Only return the last N lines.'),
follow: z
.boolean()
.optional()
.default(false)
.describe('Stream logs (not implemented; kept for API compatibility).'),
},
}, async ({ containerId, tail, follow }) => {
try {
await dockerManager.ensureReachable();
if (follow) {
return jsonResult({
containerId,
note: 'Live log streaming is not supported via this tool; use docker_get_container_logs with follow:false to poll.',
logs: '',
});
}
const docker = dockerManager.getDocker();
const buffer = await docker.getContainer(containerId).logs({
stdout: true,
stderr: true,
tail,
});
const raw = Buffer.isBuffer(buffer) ? buffer.toString('utf8') : String(buffer);
return jsonResult({
containerId,
logs: raw.slice(-config.maxToolOutputChars),
bytes: raw.length,
});
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
//# sourceMappingURL=containerLogs.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"containerLogs.js","sourceRoot":"","sources":["../../../src/tools/docker/containerLogs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,MAAM,UAAU,+BAA+B,CAAC,MAAiB;IAC/D,MAAM,CAAC,YAAY,CACjB,2BAA2B,EAC3B;QACE,KAAK,EAAE,2BAA2B;QAClC,WAAW,EACT,+EAA+E;QACjF,WAAW,EAAE;YACX,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,gEAAgE,CAAC;YACzG,IAAI,EAAE,CAAC;iBACJ,MAAM,EAAE;iBACR,GAAG,EAAE;iBACL,QAAQ,EAAE;iBACV,QAAQ,EAAE;iBACV,OAAO,CAAC,GAAG,CAAC;iBACZ,QAAQ,CAAC,+BAA+B,CAAC;YAC5C,MAAM,EAAE,CAAC;iBACN,OAAO,EAAE;iBACT,QAAQ,EAAE;iBACV,OAAO,CAAC,KAAK,CAAC;iBACd,QAAQ,CAAC,4DAA4D,CAAC;SAC1E;KACF,EACD,KAAK,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE;QACtC,IAAI,CAAC;YACH,MAAM,aAAa,CAAC,eAAe,EAAE,CAAC;YACtC,IAAI,MAAM,EAAE,CAAC;gBACX,OAAO,UAAU,CAAC;oBAChB,WAAW;oBACX,IAAI,EAAE,6GAA6G;oBACnH,IAAI,EAAE,EAAE;iBACT,CAAC,CAAC;YACL,CAAC;YAED,MAAM,MAAM,GAAG,aAAa,CAAC,SAAS,EAAE,CAAC;YACzC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC;gBACzD,MAAM,EAAE,IAAI;gBACZ,MAAM,EAAE,IAAI;gBACZ,IAAI;aACL,CAAC,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAC/E,OAAO,UAAU,CAAC;gBAChB,WAAW;gBACX,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,kBAAkB,CAAC;gBAC3C,KAAK,EAAE,GAAG,CAAC,MAAM;aAClB,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,WAAW,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
+102
View File
@@ -0,0 +1,102 @@
import Docker from 'dockerode';
import { config } from '../../config.js';
import { resolveWorkspacePath } from '../../lib/sandbox.js';
import { logger } from '../../lib/logger.js';
class DockerManager {
docker;
containers = new Map();
daemonReachable = null;
constructor() {
this.docker = new Docker();
}
async ensureReachable() {
if (this.daemonReachable === true)
return;
try {
await this.docker.ping();
this.daemonReachable = true;
}
catch (error) {
this.daemonReachable = false;
throw new Error('Docker daemon is not reachable. Make sure Docker is running and the current user can access it. ' +
`Original error: ${error instanceof Error ? error.message : String(error)}`);
}
}
getDocker() {
return this.docker;
}
track(containerId, image, name, ports) {
if (this.containers.size >= config.maxConcurrentContainers) {
throw new Error(`Refusing to start another container: already at the max of ${config.maxConcurrentContainers} concurrent containers.`);
}
const shortId = containerId.substring(0, 12);
const managed = {
id: shortId,
image,
name,
startedAt: new Date().toISOString(),
ports,
};
this.containers.set(shortId, managed);
return managed;
}
getTracked(id) {
return this.containers.get(id) ?? this.containers.get(id.substring(0, 12));
}
listTracked() {
return Array.from(this.containers.values());
}
untrack(id) {
this.containers.delete(id.substring(0, 12));
}
async cleanupAll() {
for (const [id, managed] of this.containers.entries()) {
try {
const container = this.docker.getContainer(id);
await container.stop({ t: 5 }).catch(() => null);
await container.remove({ force: true }).catch(() => null);
}
catch (error) {
logger.error(`failed to cleanup container ${managed.name} (${id})`, {
error: error instanceof Error ? error.message : error,
});
}
finally {
this.containers.delete(id);
}
}
}
}
export const dockerManager = new DockerManager();
/**
* Validates that a volume mount specification keeps the source path inside the
* workspace root. Rejects Docker socket mounts, host-relative escapes, etc.
*/
export function validateVolumeMount(spec) {
const parts = spec.split(':');
if (parts.length < 2) {
throw new Error(`Invalid volume mount "${spec}": expected format "source:target[:mode]".`);
}
const source = parts[0] ?? '';
const target = parts[1] ?? '';
const mode = parts[2] ?? 'rw';
if (!source || !target) {
throw new Error(`Invalid volume mount "${spec}": source and target are required.`);
}
const absoluteSource = resolveWorkspacePath(config.workspaceRoot, source);
if (absoluteSource.toLowerCase().startsWith('\\\\.\\pipe\\docker_engine') || source.includes('/var/run/docker.sock')) {
throw new Error(`Refusing to mount the Docker socket: "${source}".`);
}
return { source: absoluteSource, target, mode };
}
export function isAllowListedImage(image) {
const base = (image.split(':')[0] ?? '').split('/').pop()?.toLowerCase() ?? '';
return config.dockerAllowedBaseImages.some((allowed) => base === allowed || base.startsWith(allowed));
}
export function isLocallyBuiltImage(image) {
// Heuristic: an image built by this server via docker_build_image won't have
// a registry prefix. We treat "my-app" or "web-dev-mcp" as local; anything
// with a registry host (contains '.' or '/') is not.
return !/[/.]/.test(image);
}
//# sourceMappingURL=dockerManager.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"dockerManager.js","sourceRoot":"","sources":["../../../src/tools/docker/dockerManager.ts"],"names":[],"mappings":"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"}
+47
View File
@@ -0,0 +1,47 @@
import { z } from 'zod';
import { config } from '../../config.js';
import { errorResult, jsonResult, toErrorMessage } from '../shared.js';
import { dockerManager } from './dockerManager.js';
export function registerDockerExecTool(server) {
server.registerTool('docker_exec', {
title: 'Docker Exec',
description: 'Run a one-off command inside a running Docker container started by this server and return stdout/stderr.',
inputSchema: {
containerId: z.string().min(1).describe('Container ID (short or long) returned by docker_run_container.'),
command: z.array(z.string()).min(1).describe('Command and arguments to execute inside the container.'),
workingDir: z.string().optional().describe('Working directory inside the container.'),
env: z.record(z.string()).optional().describe('Additional environment variables for the exec.'),
},
}, async ({ containerId, command, workingDir, env }) => {
try {
await dockerManager.ensureReachable();
const docker = dockerManager.getDocker();
const exec = await docker.getContainer(containerId).exec({
Cmd: command,
WorkingDir: workingDir,
Env: env ? Object.entries(env).map(([k, v]) => `${k}=${v}`) : undefined,
AttachStdout: true,
AttachStderr: true,
});
const stream = await exec.start({ Detach: false, Tty: false });
const chunks = [];
await new Promise((resolve, reject) => {
stream.on('data', (chunk) => chunks.push(chunk));
stream.on('end', resolve);
stream.on('error', reject);
});
const raw = Buffer.concat(chunks).toString('utf8');
const result = await exec.inspect();
return jsonResult({
containerId,
command,
exitCode: result.ExitCode ?? null,
output: raw.slice(-config.maxToolOutputChars),
});
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
//# sourceMappingURL=exec.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"exec.js","sourceRoot":"","sources":["../../../src/tools/docker/exec.ts"],"names":[],"mappings":"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"}
+232
View File
@@ -0,0 +1,232 @@
import fs from 'node:fs';
import { z } from 'zod';
import { config } from '../../config.js';
import { detectProject, resolvePackage } from '../../lib/frameworks/detect.js';
import { resolveWorkspacePath } from '../../lib/sandbox.js';
import { runCommand } from '../../lib/runCommand.js';
import { requireLaravel } from '../laravel/requireLaravel.js';
import { errorResult, jsonResult, packageSchema, toErrorMessage } from '../shared.js';
function nodeDockerfile() {
return `# Generated by web-dev-mcp
FROM node:24-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:24-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev && npm cache clean --force
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/index.js"]
`;
}
function phpDockerfile(kind) {
const publicDir = kind === 'laravel' ? 'public' : 'public';
return `# Generated by web-dev-mcp for ${kind}
FROM php:8.3-cli-alpine AS vendor
WORKDIR /app
COPY composer.json composer.lock* ./
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \\
&& composer install --no-dev --optimize-autoloader --no-interaction --prefer-dist || composer install --no-interaction --prefer-dist
FROM php:8.3-apache
WORKDIR /var/www/html
RUN docker-php-ext-install pdo pdo_mysql
COPY --from=vendor /app/vendor ./vendor
COPY . .
ENV APACHE_DOCUMENT_ROOT=/var/www/html/${publicDir}
RUN sed -ri -e 's!/var/www/html!\\\${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf \\
&& a2enmod rewrite
EXPOSE 80
`;
}
function nginxNodeCompose() {
return `# Generated by web-dev-mcp
services:
app:
build: .
ports:
- "3000:3000"
environment:
NODE_ENV: production
depends_on:
- redis
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: app
POSTGRES_DB: app
ports:
- "5432:5432"
volumes:
- db_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
db_data:
`;
}
function phpCompose() {
return `# Generated by web-dev-mcp
services:
app:
build: .
ports:
- "8080:80"
environment:
APP_ENV: local
DB_HOST: db
DB_DATABASE: app
DB_USERNAME: app
DB_PASSWORD: app
REDIS_HOST: redis
depends_on:
- db
- redis
db:
image: mysql:8.0
environment:
MYSQL_DATABASE: app
MYSQL_USER: app
MYSQL_PASSWORD: app
MYSQL_ROOT_PASSWORD: root
ports:
- "3306:3306"
volumes:
- db_data:/var/lib/mysql
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
db_data:
`;
}
export function registerGenerateDockerfileTool(server) {
server.registerTool('generate_dockerfile', {
title: 'Generate Dockerfile',
description: 'Write a stack-aware Dockerfile at the workspace (or package) root based on detected framework (Node multi-stage or PHP/Apache for Laravel/CodeIgniter).',
inputSchema: {
package: packageSchema,
overwrite: z.boolean().optional().default(false),
},
}, async ({ package: packageName, overwrite }) => {
try {
const root = packageName
? resolvePackage(config.workspaceRoot, packageName).pkg.absolutePath
: config.workspaceRoot;
const detected = detectProject(root);
const file = resolveWorkspacePath(root, 'Dockerfile');
if (!overwrite && fs.existsSync(file)) {
return errorResult(`"${file}" already exists. Pass overwrite: true to replace it.`);
}
let contents;
let stack;
if (detected.backend?.kind === 'laravel' ||
detected.backend?.kind === 'codeigniter' ||
detected.backend?.kind === 'symfony') {
stack = detected.backend.kind;
contents = phpDockerfile(detected.backend.kind === 'codeigniter' ? 'codeigniter' : 'laravel');
}
else {
const front = detected.frontend?.kind;
stack =
front === 'react' || front === 'vue' || front === 'nuxt'
? `${front}/node`
: detected.backend?.kind === 'django' || detected.backend?.kind === 'rails'
? detected.backend.kind
: 'node';
contents = nodeDockerfile();
}
fs.writeFileSync(file, contents);
return jsonResult({ file, stack, package: packageName ?? null });
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
export function registerGenerateComposeTool(server) {
server.registerTool('generate_compose', {
title: 'Generate docker-compose.yml',
description: 'Write a docker-compose.yml with app + db + redis presets inferred from the detected stack (Postgres for Node, MySQL for PHP).',
inputSchema: {
package: packageSchema,
overwrite: z.boolean().optional().default(false),
},
}, async ({ package: packageName, overwrite }) => {
try {
const root = packageName
? resolvePackage(config.workspaceRoot, packageName).pkg.absolutePath
: config.workspaceRoot;
const detected = detectProject(root);
const file = resolveWorkspacePath(root, 'docker-compose.yml');
if (!overwrite && fs.existsSync(file)) {
return errorResult(`"${file}" already exists. Pass overwrite: true to replace it.`);
}
const isPhp = detected.backend?.kind === 'laravel' ||
detected.backend?.kind === 'codeigniter' ||
detected.backend?.kind === 'symfony';
const contents = isPhp ? phpCompose() : nginxNodeCompose();
fs.writeFileSync(file, contents);
return jsonResult({
file,
stack: isPhp
? detected.backend?.kind
: (detected.frontend?.kind ?? detected.backend?.kind ?? 'node'),
services: ['app', 'db', 'redis'],
package: packageName ?? null,
});
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
export function registerGenerateSailTool(server) {
server.registerTool('generate_sail', {
title: 'Install Laravel Sail',
description: 'Run `php artisan sail:install` in a detected Laravel project. Requires confirm: true because it modifies docker-compose and related files.',
inputSchema: {
with: z
.array(z.string())
.optional()
.describe('Optional Sail services, e.g. ["mysql", "redis", "mailpit"].'),
confirm: z.boolean().optional().describe('Required as true to run sail:install.'),
},
}, async ({ with: services, confirm }) => {
try {
if (confirm !== true) {
return errorResult('generate_sail requires confirm: true.');
}
const laravel = requireLaravel();
const args = ['artisan', 'sail:install', '--no-interaction'];
if (services && services.length > 0) {
args.push(`--with=${services.join(',')}`);
}
const result = await runCommand('php', args, {
cwd: laravel.root,
timeoutMs: config.scaffoldCommandTimeoutMs,
});
return jsonResult({
services: services ?? null,
exitCode: result.exitCode,
failed: result.failed,
output: result.output,
cwd: result.cwd,
});
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
//# sourceMappingURL=generate.js.map
File diff suppressed because one or more lines are too long
+24
View File
@@ -0,0 +1,24 @@
import { registerDockerBuildImageTool } from './buildImage.js';
import { registerDockerRunContainerTool } from './runContainer.js';
import { registerDockerListContainersTool } from './listContainers.js';
import { registerDockerContainerLogsTool } from './containerLogs.js';
import { registerDockerExecTool } from './exec.js';
import { registerDockerStopRemoveTools } from './stopRemove.js';
import { registerDockerComposeTools } from './compose.js';
import { registerGenerateComposeTool, registerGenerateDockerfileTool, registerGenerateSailTool, } from './generate.js';
import { registerDockerPushImageTool } from './pushImage.js';
export function registerDockerTools(server) {
registerDockerBuildImageTool(server);
registerDockerRunContainerTool(server);
registerDockerListContainersTool(server);
registerDockerContainerLogsTool(server);
registerDockerExecTool(server);
registerDockerStopRemoveTools(server);
registerDockerComposeTools(server);
registerGenerateDockerfileTool(server);
registerGenerateComposeTool(server);
registerGenerateSailTool(server);
registerDockerPushImageTool(server);
}
export { dockerManager } from './dockerManager.js';
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/tools/docker/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,4BAA4B,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,8BAA8B,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,gCAAgC,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,EAAE,+BAA+B,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,6BAA6B,EAAE,MAAM,iBAAiB,CAAC;AAChE,OAAO,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,EACL,2BAA2B,EAC3B,8BAA8B,EAC9B,wBAAwB,GACzB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,2BAA2B,EAAE,MAAM,gBAAgB,CAAC;AAE7D,MAAM,UAAU,mBAAmB,CAAC,MAAiB;IACnD,4BAA4B,CAAC,MAAM,CAAC,CAAC;IACrC,8BAA8B,CAAC,MAAM,CAAC,CAAC;IACvC,gCAAgC,CAAC,MAAM,CAAC,CAAC;IACzC,+BAA+B,CAAC,MAAM,CAAC,CAAC;IACxC,sBAAsB,CAAC,MAAM,CAAC,CAAC;IAC/B,6BAA6B,CAAC,MAAM,CAAC,CAAC;IACtC,0BAA0B,CAAC,MAAM,CAAC,CAAC;IACnC,8BAA8B,CAAC,MAAM,CAAC,CAAC;IACvC,2BAA2B,CAAC,MAAM,CAAC,CAAC;IACpC,wBAAwB,CAAC,MAAM,CAAC,CAAC;IACjC,2BAA2B,CAAC,MAAM,CAAC,CAAC;AACtC,CAAC;AAED,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC"}
+44
View File
@@ -0,0 +1,44 @@
import { z } from 'zod';
import { errorResult, jsonResult, toErrorMessage } from '../shared.js';
import { dockerManager } from './dockerManager.js';
export function registerDockerListContainersTool(server) {
server.registerTool('docker_list_containers', {
title: 'Docker List Containers',
description: 'List Docker containers started by this server with their current status and port mappings.',
inputSchema: {
includeDockerStatus: z
.boolean()
.optional()
.default(false)
.describe('Also query the Docker daemon for live status of each tracked container.'),
},
}, async ({ includeDockerStatus }) => {
try {
await dockerManager.ensureReachable();
const tracked = dockerManager.listTracked();
if (!includeDockerStatus) {
return jsonResult({ containers: tracked });
}
const docker = dockerManager.getDocker();
const enriched = await Promise.all(tracked.map(async (c) => {
try {
const info = await docker.getContainer(c.id).inspect();
return {
...c,
state: info.State?.Status ?? 'unknown',
running: info.State?.Running ?? false,
exitCode: info.State?.ExitCode ?? null,
};
}
catch {
return { ...c, state: 'unknown', running: false, exitCode: null };
}
}));
return jsonResult({ containers: enriched });
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
//# sourceMappingURL=listContainers.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"listContainers.js","sourceRoot":"","sources":["../../../src/tools/docker/listContainers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,MAAM,UAAU,gCAAgC,CAAC,MAAiB;IAChE,MAAM,CAAC,YAAY,CACjB,wBAAwB,EACxB;QACE,KAAK,EAAE,wBAAwB;QAC/B,WAAW,EAAE,4FAA4F;QACzG,WAAW,EAAE;YACX,mBAAmB,EAAE,CAAC;iBACnB,OAAO,EAAE;iBACT,QAAQ,EAAE;iBACV,OAAO,CAAC,KAAK,CAAC;iBACd,QAAQ,CAAC,yEAAyE,CAAC;SACvF;KACF,EACD,KAAK,EAAE,EAAE,mBAAmB,EAAE,EAAE,EAAE;QAChC,IAAI,CAAC;YACH,MAAM,aAAa,CAAC,eAAe,EAAE,CAAC;YACtC,MAAM,OAAO,GAAG,aAAa,CAAC,WAAW,EAAE,CAAC;YAC5C,IAAI,CAAC,mBAAmB,EAAE,CAAC;gBACzB,OAAO,UAAU,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC;YAC7C,CAAC;YAED,MAAM,MAAM,GAAG,aAAa,CAAC,SAAS,EAAE,CAAC;YACzC,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAChC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE;gBACtB,IAAI,CAAC;oBACH,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;oBACvD,OAAO;wBACL,GAAG,CAAC;wBACJ,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,IAAI,SAAS;wBACtC,OAAO,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,IAAI,KAAK;wBACrC,QAAQ,EAAE,IAAI,CAAC,KAAK,EAAE,QAAQ,IAAI,IAAI;qBACvC,CAAC;gBACJ,CAAC;gBAAC,MAAM,CAAC;oBACP,OAAO,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;gBACpE,CAAC;YACH,CAAC,CAAC,CACH,CAAC;YACF,OAAO,UAAU,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC9C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,WAAW,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
+45
View File
@@ -0,0 +1,45 @@
import { z } from 'zod';
import { config } from '../../config.js';
import { runCommand } from '../../lib/runCommand.js';
import { errorResult, jsonResult, toErrorMessage } from '../shared.js';
import { dockerManager } from './dockerManager.js';
export function registerDockerPushImageTool(server) {
server.registerTool('docker_push_image', {
title: 'Docker Push Image',
description: 'Push a locally built or allow-listed Docker image to a registry (`docker push`). Requires confirm: true. ' +
'Credentials must already be available via the Docker credential store / environment — never pass secrets as tool args.',
inputSchema: {
image: z
.string()
.min(1)
.describe('Image reference to push, e.g. "ghcr.io/org/app:latest" or "my-app:1.0".'),
confirm: z.boolean().optional().describe('Required as true to push an image.'),
},
}, async ({ image, confirm }) => {
try {
if (confirm !== true) {
return errorResult('docker_push_image requires confirm: true.');
}
if (image.includes('--force') || /\s/.test(image)) {
return errorResult('Invalid image reference.');
}
await dockerManager.ensureReachable();
const result = await runCommand('docker', ['push', image], {
cwd: config.workspaceRoot,
timeoutMs: config.scaffoldCommandTimeoutMs,
});
return jsonResult({
image,
exitCode: result.exitCode,
failed: result.failed,
timedOut: result.timedOut,
output: result.output,
note: 'Auth must come from `docker login` / credential helpers / env — not from this tool.',
});
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
//# sourceMappingURL=pushImage.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"pushImage.js","sourceRoot":"","sources":["../../../src/tools/docker/pushImage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,MAAM,UAAU,2BAA2B,CAAC,MAAiB;IAC3D,MAAM,CAAC,YAAY,CACjB,mBAAmB,EACnB;QACE,KAAK,EAAE,mBAAmB;QAC1B,WAAW,EACT,2GAA2G;YAC3G,wHAAwH;QAC1H,WAAW,EAAE;YACX,KAAK,EAAE,CAAC;iBACL,MAAM,EAAE;iBACR,GAAG,CAAC,CAAC,CAAC;iBACN,QAAQ,CAAC,yEAAyE,CAAC;YACtF,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oCAAoC,CAAC;SAC/E;KACF,EACD,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE;QAC3B,IAAI,CAAC;YACH,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;gBACrB,OAAO,WAAW,CAAC,2CAA2C,CAAC,CAAC;YAClE,CAAC;YACD,IAAI,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;gBAClD,OAAO,WAAW,CAAC,0BAA0B,CAAC,CAAC;YACjD,CAAC;YAED,MAAM,aAAa,CAAC,eAAe,EAAE,CAAC;YAEtC,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE;gBACzD,GAAG,EAAE,MAAM,CAAC,aAAa;gBACzB,SAAS,EAAE,MAAM,CAAC,wBAAwB;aAC3C,CAAC,CAAC;YAEH,OAAO,UAAU,CAAC;gBAChB,KAAK;gBACL,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,IAAI,EAAE,qFAAqF;aAC5F,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,WAAW,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
+112
View File
@@ -0,0 +1,112 @@
import { z } from 'zod';
import { config } from '../../config.js';
import { errorResult, jsonResult, toErrorMessage } from '../shared.js';
import { dockerManager, isAllowListedImage, isLocallyBuiltImage, validateVolumeMount } from './dockerManager.js';
export function registerDockerRunContainerTool(server) {
server.registerTool('docker_run_container', {
title: 'Docker Run Container',
description: 'Run a Docker container from an image with port mappings, env vars, and workspace-confined volume mounts. ' +
'Locally built images are allowed; non-local images must be on the allow-list unless confirm: true is passed.',
inputSchema: {
image: z.string().min(1).describe('Docker image to run, e.g. "my-app:latest" or "nginx".'),
name: z.string().optional().describe('Optional container name.'),
ports: z
.record(z.number().int().positive())
.optional()
.describe('Port mapping { hostPort: containerPort }.'),
env: z.record(z.string()).optional().describe('Environment variables.'),
volumes: z
.array(z.string())
.optional()
.describe('Volume mounts in "source:target[:mode]" format. Source paths are confined to WORKSPACE_ROOT.'),
command: z.array(z.string()).optional().describe('Override the container command.'),
memory: z.string().optional().describe('Memory limit, e.g. "512m". Defaults to 512m.'),
cpus: z.string().optional().describe('CPU limit, e.g. "1.0". Defaults to 1.0.'),
confirm: z
.boolean()
.optional()
.describe('Required as true to run an image not on the allow-list.'),
},
}, async ({ image, name, ports, env, volumes, command, memory, cpus, confirm }) => {
try {
await dockerManager.ensureReachable();
if (!isLocallyBuiltImage(image) && !isAllowListedImage(image) && confirm !== true) {
return errorResult(`Image "${image}" is not locally built and not on the allow-list (${config.dockerAllowedBaseImages.join(', ')}). ` +
'Pass confirm: true to run it anyway.');
}
const portBindings = {};
const exposedPorts = {};
const portMap = {};
if (ports) {
for (const [hostPort, containerPort] of Object.entries(ports)) {
const key = `${containerPort}/tcp`;
portBindings[key] = [{ HostPort: hostPort }];
exposedPorts[key] = {};
portMap[Number(hostPort)] = containerPort;
}
}
const binds = [];
if (volumes) {
for (const spec of volumes) {
const mount = validateVolumeMount(spec);
binds.push(`${mount.source}:${mount.target}:${mount.mode}`);
}
}
const createOptions = {
Image: image,
name,
Cmd: command,
Env: env ? Object.entries(env).map(([k, v]) => `${k}=${v}`) : undefined,
ExposedPorts: Object.keys(exposedPorts).length > 0 ? exposedPorts : undefined,
HostConfig: {
PortBindings: Object.keys(portBindings).length > 0 ? portBindings : undefined,
Binds: binds.length > 0 ? binds : undefined,
Memory: parseMemory(memory ?? config.defaultContainerMemory),
NanoCpus: parseCpus(cpus ?? config.defaultContainerCpus),
// Security guardrails:
Privileged: false,
NetworkMode: 'bridge',
PidMode: undefined,
IpcMode: undefined,
},
};
const docker = dockerManager.getDocker();
const container = await docker.createContainer(createOptions);
await container.start();
const info = await container.inspect();
const managed = dockerManager.track(container.id, image, info.Name?.replace(/^\//, '') ?? container.id, portMap);
return jsonResult({ ...managed, shortId: container.id.substring(0, 12) });
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
function parseMemory(value) {
const match = value.match(/^([0-9.]+)([kmgt]?b?)$/i);
if (!match || !match[1])
return 512 * 1024 * 1024;
const num = Number.parseFloat(match[1]);
const unit = match[2]?.toLowerCase() ?? '';
switch (unit) {
case 'k':
case 'kb':
return num * 1024;
case 'm':
case 'mb':
return num * 1024 * 1024;
case 'g':
case 'gb':
return num * 1024 * 1024 * 1024;
case 't':
case 'tb':
return num * 1024 * 1024 * 1024 * 1024;
default:
return num;
}
}
function parseCpus(value) {
const num = Number.parseFloat(value);
return Number.isNaN(num) ? 1_000_000_000 : Math.round(num * 1_000_000_000);
}
//# sourceMappingURL=runContainer.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"runContainer.js","sourceRoot":"","sources":["../../../src/tools/docker/runContainer.ts"],"names":[],"mappings":"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"}
+41
View File
@@ -0,0 +1,41 @@
import { z } from 'zod';
import { errorResult, jsonResult, toErrorMessage } from '../shared.js';
import { dockerManager } from './dockerManager.js';
export function registerDockerStopRemoveTools(server) {
server.registerTool('docker_stop_container', {
title: 'Docker Stop Container',
description: 'Stop a Docker container started by this server.',
inputSchema: {
containerId: z.string().min(1).describe('Container ID returned by docker_run_container.'),
timeout: z.number().int().positive().optional().default(10).describe('Seconds to wait before killing.'),
},
}, async ({ containerId, timeout }) => {
try {
await dockerManager.ensureReachable();
await dockerManager.getDocker().getContainer(containerId).stop({ t: timeout });
return jsonResult({ stopped: true, containerId });
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
server.registerTool('docker_remove_container', {
title: 'Docker Remove Container',
description: 'Remove a Docker container started by this server.',
inputSchema: {
containerId: z.string().min(1).describe('Container ID returned by docker_run_container.'),
force: z.boolean().optional().default(false).describe('Force removal even if running.'),
},
}, async ({ containerId, force }) => {
try {
await dockerManager.ensureReachable();
await dockerManager.getDocker().getContainer(containerId).remove({ force });
dockerManager.untrack(containerId);
return jsonResult({ removed: true, containerId });
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
//# sourceMappingURL=stopRemove.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"stopRemove.js","sourceRoot":"","sources":["../../../src/tools/docker/stopRemove.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,MAAM,UAAU,6BAA6B,CAAC,MAAiB;IAC7D,MAAM,CAAC,YAAY,CACjB,uBAAuB,EACvB;QACE,KAAK,EAAE,uBAAuB;QAC9B,WAAW,EAAE,iDAAiD;QAC9D,WAAW,EAAE;YACX,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,gDAAgD,CAAC;YACzF,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,iCAAiC,CAAC;SACxG;KACF,EACD,KAAK,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,EAAE,EAAE;QACjC,IAAI,CAAC;YACH,MAAM,aAAa,CAAC,eAAe,EAAE,CAAC;YACtC,MAAM,aAAa,CAAC,SAAS,EAAE,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;YAC/E,OAAO,UAAU,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;QACpD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,WAAW,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,yBAAyB,EACzB;QACE,KAAK,EAAE,yBAAyB;QAChC,WAAW,EAAE,mDAAmD;QAChE,WAAW,EAAE;YACX,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,gDAAgD,CAAC;YACzF,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,gCAAgC,CAAC;SACxF;KACF,EACD,KAAK,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,EAAE;QAC/B,IAAI,CAAC;YACH,MAAM,aAAa,CAAC,eAAe,EAAE,CAAC;YACtC,MAAM,aAAa,CAAC,SAAS,EAAE,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;YAC5E,aAAa,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;YACnC,OAAO,UAAU,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;QACpD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,WAAW,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}