Skip to content

Commit 3cd0524

Browse files
committed
refactor(Dockerfile, executor.ts): enhance Dockerfile and browser initialization logic
* Updated Dockerfile to use Node.js lts instead of lts-alpine, improving compatibility. * Added installation of Playwright browser dependencies and browsers to the Docker image. * Modified browser initialization in executor.ts to run headless in Docker environments, enhancing automation capabilities.
1 parent dfc7a4d commit 3cd0524

File tree

10 files changed

+197
-31
lines changed

10 files changed

+197
-31
lines changed

Dockerfile

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,51 @@
11
# Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
2-
# Build stage
3-
FROM node:lts-alpine AS build
2+
# Builder stage
3+
FROM node:lts AS builder
44
WORKDIR /app
55

6-
# Copy dependency manifests and TypeScript config
7-
COPY package.json package-lock.json tsconfig.json ./
8-
9-
# Copy TypeScript source files and public assets
6+
# Copy project files
7+
COPY package.json tsconfig.json ./
108
COPY src ./src
11-
COPY public ./public
129

13-
# Install dependencies and build
14-
RUN npm install
15-
RUN npm run build
10+
# Install dependencies and build TypeScript
11+
RUN npm install && \
12+
npm run build
1613

17-
# Runtime stage
18-
FROM node:lts-alpine AS runtime
14+
# Final image
15+
FROM node:lts
1916
WORKDIR /app
2017

21-
# Copy built artifacts and production modules
22-
COPY --from=build /app/build ./build
23-
COPY --from=build /app/node_modules ./node_modules
24-
COPY --from=build /app/public ./public
18+
COPY --from=builder /app/dist ./dist
19+
COPY --from=builder /app/node_modules ./node_modules
20+
COPY package.json ./
21+
22+
# Install Playwright browser dependencies
23+
RUN apt-get update && apt-get install -y --no-install-recommends \
24+
libglib2.0-0 \
25+
libnss3 \
26+
libnspr4 \
27+
libatk1.0-0 \
28+
libatk-bridge2.0-0 \
29+
libcups2 \
30+
libdrm2 \
31+
libdbus-1-3 \
32+
libxcb1 \
33+
libxkbcommon0 \
34+
libx11-6 \
35+
libxcomposite1 \
36+
libxdamage1 \
37+
libxext6 \
38+
libxfixes3 \
39+
libxrandr2 \
40+
libgbm1 \
41+
libpango-1.0-0 \
42+
libcairo2 \
43+
libasound2 \
44+
libatspi2.0-0 \
45+
&& rm -rf /var/lib/apt/lists/*
2546

26-
# Expose port for MCP server if needed
27-
EXPOSE 8888
47+
# Install Playwright browsers
48+
RUN npx playwright install chromium firefox webkit --with-deps
2849

29-
# Default command to start the MCP server
30-
CMD ["node", "build/index.js"]
50+
# Expose stdio (no ports) and run the MCP server
51+
CMD ["node", "dist/index.js"]

SECURITY.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ Only the latest version of the MCP Browser Agent is actively maintained and rece
66

77
| Version | Supported |
88
| ------- | ------------------ |
9-
| 0.7.x | :white_check_mark: |
10-
| < 0.7.0 | :x: |
9+
| 0.8.x | :white_check_mark: |
10+
| < 0.8.0 | :x: |
1111

1212
## Reporting a Vulnerability
1313

docker-compose.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
version: '3'
2+
services:
3+
mcp-browser-agent:
4+
build:
5+
context: .
6+
dockerfile: Dockerfile
7+
volumes:
8+
- /tmp:/tmp # For file sharing if needed
9+
environment:
10+
- MCP_BROWSER_TYPE=chromium # Use chromium by default
11+
- MCP_VIEWPORT_WIDTH=1280
12+
- MCP_VIEWPORT_HEIGHT=800
13+
- MCP_DEVICE_SCALE_FACTOR=1.25

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "mcp-browser-agent",
3-
"version": "0.7.0",
3+
"version": "0.8.0",
44
"author":{
55
"name": "Iván Luna",
66
"email": "[email protected]",

smithery.yaml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,26 @@
11
name: mcp-browser-agent
2-
displayName: Status Observer MCP
2+
displayName: Browser Agent MCP
3+
description: A Model Context Protocol (MCP) integration that provides Claude Desktop with autonomous browser automation capabilities.
34
visibility: public
45
type: mcp
56
author:
67
name: Iván Luna
78
url: https://github.com/imprvhub
89
repository: https://github.com/imprvhub/mcp-browser-agent
910
keywords:
10-
- status
11-
- monitoring
12-
- platforms
13-
- operational
11+
- browser
12+
- agent
13+
- orchestration
14+
- automation
1415
files:
1516
- README.md
1617
- package.json
1718
- tsconfig.json
1819
- Dockerfile
1920
- src/index.ts
21+
- src/tools.ts
22+
- src/handlers.ts
23+
- src/executor.ts
2024
startCommand:
2125
type: stdio
2226
configSchema:

src/executor.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,12 @@ async function initBrowser(): Promise<Page> {
110110
break;
111111
}
112112

113+
// Determine if we're running in a Docker container
114+
const isDocker = fs.existsSync('/.dockerenv') || fs.existsSync('/proc/1/cgroup') && fs.readFileSync('/proc/1/cgroup', 'utf8').includes('docker');
115+
113116
browser = await browserInstance.launch({
114-
headless: false,
115-
channel: config.browserType === 'chrome' ? 'chrome' : undefined
117+
headless: isDocker ? true : false,
118+
channel: config.browserType === 'chrome' && !isDocker ? 'chrome' : undefined
116119
});
117120

118121
const context = await browser.newContext({

src/tests/basic.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { test, expect, describe, jest } from '@jest/globals';
2+
import fs from 'node:fs';
3+
import path from 'node:path';
4+
import { fileURLToPath } from 'node:url';
5+
6+
const __filename = fileURLToPath(import.meta.url);
7+
const __dirname = path.dirname(__filename);
8+
const rootDir = path.resolve(__dirname, '..');
9+
10+
describe('MCP Browser Agent - Basic Tests', () => {
11+
test('Package should be properly configured', async () => {
12+
const packageJsonPath = path.join(rootDir, 'package.json');
13+
const packageJsonContent = await fs.promises.readFile(packageJsonPath, 'utf8');
14+
const packageJson = JSON.parse(packageJsonContent);
15+
16+
expect(packageJson.name).toBe('mcp-browser-agent');
17+
expect(packageJson.type).toBe('module');
18+
expect(typeof packageJson.scripts.test).toBe('string');
19+
});
20+
21+
test('Project should have required files', async () => {
22+
expect(fs.existsSync(path.join(rootDir, 'src/index.ts'))).toBeTruthy();
23+
expect(fs.existsSync(path.join(rootDir, 'src/executor.ts'))).toBeTruthy();
24+
expect(fs.existsSync(path.join(rootDir, 'src/tools.ts'))).toBeTruthy();
25+
expect(fs.existsSync(path.join(rootDir, 'src/handlers.ts'))).toBeTruthy();
26+
});
27+
28+
test('Tools module should exist', async () => {
29+
const toolsPath = path.join(rootDir, 'src/tools.ts');
30+
expect(fs.existsSync(toolsPath)).toBeTruthy();
31+
32+
const toolsContent = await fs.promises.readFile(toolsPath, 'utf8');
33+
expect(toolsContent).toContain('BROWSER_TOOLS');
34+
expect(toolsContent).toContain('browser_navigate');
35+
expect(toolsContent).toContain('browser_screenshot');
36+
expect(toolsContent).toContain('API_TOOLS');
37+
expect(toolsContent).toContain('api_get');
38+
expect(toolsContent).toContain('api_post');
39+
});
40+
});

src/tests/browser-error.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { test, expect, describe, jest, beforeAll } from '@jest/globals';
2+
import fs from 'node:fs';
3+
import path from 'node:path';
4+
import { fileURLToPath } from 'node:url';
5+
6+
const __filename = fileURLToPath(import.meta.url);
7+
const __dirname = path.dirname(__filename);
8+
const rootDir = path.resolve(__dirname, '..');
9+
const processEvents: Record<string, Array<() => void>> = {
10+
'SIGINT': [],
11+
'SIGTERM': []
12+
};
13+
14+
let cleanupFn: boolean | undefined;
15+
16+
beforeAll(() => {
17+
const executorPath = path.join(rootDir, 'src/executor.ts');
18+
const executorContent = fs.readFileSync(executorPath, 'utf8');
19+
if (executorContent.includes('process.on(\'SIGINT\'')) {
20+
processEvents['SIGINT'] = [() => {}];
21+
}
22+
23+
if (executorContent.includes('process.on(\'SIGTERM\'')) {
24+
processEvents['SIGTERM'] = [() => {}];
25+
}
26+
27+
if (executorContent.includes('cleanupBrowser')) {
28+
cleanupFn = true;
29+
}
30+
});
31+
32+
describe('Browser Error Handling Tests', () => {
33+
test('Executor should contain process cleanup handlers', async () => {
34+
const executorPath = path.join(rootDir, 'src/executor.ts');
35+
const executorContent = fs.readFileSync(executorPath, 'utf8');
36+
expect(executorContent.includes('process.on(\'SIGINT\'')).toBeTruthy();
37+
expect(executorContent.includes('process.on(\'SIGTERM\'')).toBeTruthy();
38+
expect(executorContent.includes('cleanupBrowser')).toBeTruthy();
39+
});
40+
41+
test('README should contain browser process cleanup documentation', () => {
42+
const readmePath = path.join(rootDir, 'README.md');
43+
const readmeContent = fs.readFileSync(readmePath, 'utf8');
44+
expect(readmeContent).toContain('Browser process not closing properly');
45+
expect(readmeContent).toContain('Windows');
46+
expect(readmeContent).toContain('macOS');
47+
expect(readmeContent).toContain('Linux');
48+
expect(readmeContent).toContain('Playwright');
49+
expect(readmeContent).toContain('issues');
50+
expect(readmeContent).toContain('github.com/microsoft/playwright/issues');
51+
});
52+
});

src/tests/docker-test.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
const { chromium } = require('playwright');
2+
3+
async function test() {
4+
console.log('Starting browser test in Docker environment...');
5+
6+
const isDocker = require('fs').existsSync('/.dockerenv') ||
7+
(require('fs').existsSync('/proc/1/cgroup') &&
8+
require('fs').readFileSync('/proc/1/cgroup', 'utf8').includes('docker'));
9+
10+
console.log(`Running in Docker environment: ${isDocker}`);
11+
12+
try {
13+
const browser = await chromium.launch({ headless: true });
14+
console.log('Browser launched successfully');
15+
const context = await browser.newContext();
16+
const page = await context.newPage();
17+
console.log('Browser page created successfully');
18+
await page.goto('https://example.com');
19+
console.log('Navigation successful');
20+
await page.screenshot({ path: '/tmp/example.png' });
21+
console.log('Screenshot saved to /tmp/example.png');
22+
const title = await page.title();
23+
console.log(`Page title: ${title}`);
24+
await browser.close();
25+
console.log('Browser closed successfully');
26+
console.log('Test completed successfully!');
27+
} catch (error) {
28+
console.error('Test failed:', error);
29+
}
30+
}
31+
32+
test();

tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@
88
"strict": true,
99
"skipLibCheck": true,
1010
"isolatedModules": true,
11+
"rootDir": "src",
1112
"outDir": "dist",
1213
"declaration": true
1314
},
14-
"include": ["src/**/*", "tests/**/*"],
15+
"include": ["src/**/*", "src/tests/**/*"],
1516
"exclude": ["node_modules", "dist"]
1617
}

0 commit comments

Comments
 (0)