Remove HTTP server mode, keep MCP + CLI only
Drop express dependency and HTTP server in favor of MCP-only architecture. CLI still works for quick screenshots and queries. - Delete src/server.ts and src/server.test.ts - Remove express, @types/express, supertest from dependencies - Update CLI to show help when no URL provided - Remove server exports from index.ts -33 packages removed, 757 lines deleted. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
+4
-35
@@ -5,7 +5,6 @@ import { fileURLToPath } from 'node:url';
|
||||
import { parseArgs } from 'node:util';
|
||||
import { ClaudeBrowser } from './browser.js';
|
||||
import * as image from './image.js';
|
||||
import { startServer } from './server.js';
|
||||
import type { ElementInfo } from './types.js';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
@@ -75,25 +74,8 @@ Image processing examples:
|
||||
browse https://example.com --resize 800x600
|
||||
browse https://example.com --compress 60
|
||||
|
||||
Server mode (default):
|
||||
browse # Start server on port 13373
|
||||
browse --headed # Start with visible browser
|
||||
|
||||
# Send commands via curl:
|
||||
curl -X POST http://localhost:13373 -d '{"cmd":"goto","url":"https://example.com"}'
|
||||
curl -X POST http://localhost:13373 -d '{"cmd":"click","selector":"button"}'
|
||||
curl -X POST http://localhost:13373 -d '{"cmd":"type","selector":"input","text":"hello"}'
|
||||
curl -X POST http://localhost:13373 -d '{"cmd":"query","selector":"a[href]"}'
|
||||
curl -X POST http://localhost:13373 -d '{"cmd":"screenshot","path":"shot.png"}'
|
||||
curl -X POST http://localhost:13373 -d '{"cmd":"url"}'
|
||||
curl -X POST http://localhost:13373 -d '{"cmd":"html"}'
|
||||
curl -X POST http://localhost:13373 -d '{"cmd":"close"}'
|
||||
|
||||
# Image processing via server:
|
||||
curl localhost:13373 -d '{"cmd":"favicon","input":"screenshot.png","outputDir":"./favicons"}'
|
||||
curl localhost:13373 -d '{"cmd":"convert","input":"img.png","output":"img.webp","format":"webp"}'
|
||||
curl localhost:13373 -d '{"cmd":"resize","input":"img.png","output":"small.png","width":400}'
|
||||
curl localhost:13373 -d '{"cmd":"compress","input":"img.png","output":"compressed.png","quality":60}'
|
||||
MCP Server (for Claude Code integration):
|
||||
browse-mcp # Run as MCP server (stdio transport)
|
||||
`;
|
||||
|
||||
function getViewportConfig() {
|
||||
@@ -104,19 +86,6 @@ function getViewportConfig() {
|
||||
};
|
||||
}
|
||||
|
||||
async function runServerMode(): Promise<void> {
|
||||
const port = 13373;
|
||||
const server = await startServer({ port, ...getViewportConfig() });
|
||||
|
||||
process.on('SIGINT', async () => {
|
||||
console.log('\nShutting down...');
|
||||
await server.stop();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
await new Promise(() => {});
|
||||
}
|
||||
|
||||
async function processTypeActions(browser: ClaudeBrowser): Promise<void> {
|
||||
const typeActions = values.type as string[] | undefined;
|
||||
if (!typeActions?.length) return;
|
||||
@@ -265,8 +234,8 @@ async function main(): Promise<void> {
|
||||
}
|
||||
|
||||
if (positionals.length === 0) {
|
||||
await runServerMode();
|
||||
return;
|
||||
console.log(HELP);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
await runBrowserMode();
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export { ClaudeBrowser } from './browser.js';
|
||||
export { BrowserServer, startServer } from './server.js';
|
||||
export {
|
||||
createFavicon,
|
||||
convert,
|
||||
@@ -41,4 +40,3 @@ export type {
|
||||
CompressCommand,
|
||||
ThumbnailCommand,
|
||||
} from './types.js';
|
||||
export type { ServerOptions } from './server.js';
|
||||
|
||||
@@ -1,191 +0,0 @@
|
||||
import request from 'supertest';
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
|
||||
import { BrowserServer } from './server.js';
|
||||
|
||||
// Mock the browser module
|
||||
vi.mock('./browser.js', () => ({
|
||||
ClaudeBrowser: class MockClaudeBrowser {
|
||||
async launch() {}
|
||||
async close() {}
|
||||
async executeCommand(cmd: { cmd: string; url?: string; path?: string }) {
|
||||
switch (cmd.cmd) {
|
||||
case 'goto':
|
||||
return { ok: true, url: cmd.url, title: 'Test Page' };
|
||||
case 'click':
|
||||
return { ok: true, url: '/clicked' };
|
||||
case 'type':
|
||||
return { ok: true };
|
||||
case 'query':
|
||||
return { ok: true, count: 2, elements: [] };
|
||||
case 'url':
|
||||
return { ok: true, url: 'https://example.com', title: 'Example' };
|
||||
case 'html':
|
||||
return { ok: true, html: '<html></html>' };
|
||||
case 'back':
|
||||
return { ok: true, url: '/previous' };
|
||||
case 'forward':
|
||||
return { ok: true, url: '/next' };
|
||||
case 'reload':
|
||||
return { ok: true, url: '/current' };
|
||||
case 'wait':
|
||||
return { ok: true };
|
||||
case 'screenshot':
|
||||
return { ok: true, path: cmd.path || 'screenshot.png' };
|
||||
case 'eval':
|
||||
return { ok: true, result: 2 };
|
||||
case 'newpage':
|
||||
return { ok: true };
|
||||
case 'error':
|
||||
throw new Error('Test error');
|
||||
default:
|
||||
return { ok: true };
|
||||
}
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
describe('BrowserServer', () => {
|
||||
let server: BrowserServer;
|
||||
|
||||
beforeAll(async () => {
|
||||
server = new BrowserServer({ port: 0 }); // Use port 0 to get random available port
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('creates server with default port', () => {
|
||||
const s = new BrowserServer();
|
||||
expect(s.getPort()).toBe(13373);
|
||||
});
|
||||
|
||||
it('creates server with custom port', () => {
|
||||
const s = new BrowserServer({ port: 8080 });
|
||||
expect(s.getPort()).toBe(8080);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /', () => {
|
||||
it('handles goto command', async () => {
|
||||
const app = server.getApp();
|
||||
const res = await request(app)
|
||||
.post('/')
|
||||
.send({ cmd: 'goto', url: 'https://example.com' })
|
||||
.expect(200);
|
||||
|
||||
expect(res.body.ok).toBe(true);
|
||||
expect(res.body.url).toBe('https://example.com');
|
||||
expect(res.body.title).toBe('Test Page');
|
||||
});
|
||||
|
||||
it('handles click command', async () => {
|
||||
const app = server.getApp();
|
||||
const res = await request(app).post('/').send({ cmd: 'click', selector: '#btn' }).expect(200);
|
||||
|
||||
expect(res.body.ok).toBe(true);
|
||||
expect(res.body.url).toBe('/clicked');
|
||||
});
|
||||
|
||||
it('handles type command', async () => {
|
||||
const app = server.getApp();
|
||||
const res = await request(app)
|
||||
.post('/')
|
||||
.send({ cmd: 'type', selector: '#input', text: 'hello' })
|
||||
.expect(200);
|
||||
|
||||
expect(res.body.ok).toBe(true);
|
||||
});
|
||||
|
||||
it('handles query command', async () => {
|
||||
const app = server.getApp();
|
||||
const res = await request(app)
|
||||
.post('/')
|
||||
.send({ cmd: 'query', selector: '.items' })
|
||||
.expect(200);
|
||||
|
||||
expect(res.body.ok).toBe(true);
|
||||
expect(res.body.count).toBe(2);
|
||||
});
|
||||
|
||||
it('handles url command', async () => {
|
||||
const app = server.getApp();
|
||||
const res = await request(app).post('/').send({ cmd: 'url' }).expect(200);
|
||||
|
||||
expect(res.body.ok).toBe(true);
|
||||
expect(res.body.url).toBe('https://example.com');
|
||||
});
|
||||
|
||||
it('handles html command', async () => {
|
||||
const app = server.getApp();
|
||||
const res = await request(app).post('/').send({ cmd: 'html' }).expect(200);
|
||||
|
||||
expect(res.body.ok).toBe(true);
|
||||
expect(res.body.html).toBe('<html></html>');
|
||||
});
|
||||
|
||||
it('handles JSON string body', async () => {
|
||||
const app = server.getApp();
|
||||
const res = await request(app)
|
||||
.post('/')
|
||||
.set('Content-Type', 'text/plain')
|
||||
.send(JSON.stringify({ cmd: 'url' }))
|
||||
.expect(200);
|
||||
|
||||
expect(res.body.ok).toBe(true);
|
||||
});
|
||||
|
||||
it('handles back command', async () => {
|
||||
const app = server.getApp();
|
||||
const res = await request(app).post('/').send({ cmd: 'back' }).expect(200);
|
||||
expect(res.body.ok).toBe(true);
|
||||
});
|
||||
|
||||
it('handles forward command', async () => {
|
||||
const app = server.getApp();
|
||||
const res = await request(app).post('/').send({ cmd: 'forward' }).expect(200);
|
||||
expect(res.body.ok).toBe(true);
|
||||
});
|
||||
|
||||
it('handles reload command', async () => {
|
||||
const app = server.getApp();
|
||||
const res = await request(app).post('/').send({ cmd: 'reload' }).expect(200);
|
||||
expect(res.body.ok).toBe(true);
|
||||
});
|
||||
|
||||
it('handles wait command', async () => {
|
||||
const app = server.getApp();
|
||||
const res = await request(app).post('/').send({ cmd: 'wait', ms: 100 }).expect(200);
|
||||
expect(res.body.ok).toBe(true);
|
||||
});
|
||||
|
||||
it('handles screenshot command', async () => {
|
||||
const app = server.getApp();
|
||||
const res = await request(app)
|
||||
.post('/')
|
||||
.send({ cmd: 'screenshot', path: 'test.png' })
|
||||
.expect(200);
|
||||
expect(res.body.ok).toBe(true);
|
||||
});
|
||||
|
||||
it('handles eval command', async () => {
|
||||
const app = server.getApp();
|
||||
const res = await request(app).post('/').send({ cmd: 'eval', script: '1+1' }).expect(200);
|
||||
expect(res.body.ok).toBe(true);
|
||||
});
|
||||
|
||||
it('handles newpage command', async () => {
|
||||
const app = server.getApp();
|
||||
const res = await request(app).post('/').send({ cmd: 'newpage' }).expect(200);
|
||||
expect(res.body.ok).toBe(true);
|
||||
});
|
||||
|
||||
it('handles errors and returns 500', async () => {
|
||||
const app = server.getApp();
|
||||
const res = await request(app).post('/').send({ cmd: 'error' }).expect(500);
|
||||
expect(res.body.ok).toBe(false);
|
||||
expect(res.body.error).toBe('Test error');
|
||||
});
|
||||
});
|
||||
});
|
||||
-111
@@ -1,111 +0,0 @@
|
||||
import chalk from 'chalk';
|
||||
import express, { type Request, type Response } from 'express';
|
||||
import logSymbols from 'log-symbols';
|
||||
import { ClaudeBrowser } from './browser.js';
|
||||
import { logger, ts } from './logger.js';
|
||||
import type { BrowserCommand, BrowserOptions } from './types.js';
|
||||
|
||||
export interface ServerOptions extends BrowserOptions {
|
||||
port?: number;
|
||||
}
|
||||
|
||||
function printBanner(port: number): void {
|
||||
console.log();
|
||||
console.log(chalk.cyan.bold(' 🌐 Claude Browse Server'));
|
||||
console.log(chalk.dim(' ─────────────────────────'));
|
||||
console.log(` ${logSymbols.success} Listening on ${chalk.bold(`http://localhost:${port}`)}`);
|
||||
console.log();
|
||||
console.log(chalk.dim(' Commands:'));
|
||||
console.log(
|
||||
` ${chalk.cyan('goto')} ${chalk.yellow('click')} ${chalk.magenta('type')} ${chalk.blue('query')} ${chalk.green('screenshot')}`
|
||||
);
|
||||
console.log(
|
||||
` ${chalk.cyan('url')} ${chalk.blue('html')} ${chalk.yellow('back')} ${chalk.yellow('forward')} ${chalk.yellow('reload')} ${chalk.gray('wait')} ${chalk.red('close')}`
|
||||
);
|
||||
console.log();
|
||||
console.log(chalk.dim(' Example:'));
|
||||
console.log(
|
||||
chalk.gray(` curl -X POST localhost:${port} -d '{"cmd":"goto","url":"https://example.com"}'`)
|
||||
);
|
||||
console.log();
|
||||
}
|
||||
|
||||
export class BrowserServer {
|
||||
private browser: ClaudeBrowser;
|
||||
private app = express();
|
||||
private server: ReturnType<typeof this.app.listen> | null = null;
|
||||
private port: number;
|
||||
|
||||
constructor(options: ServerOptions = {}) {
|
||||
this.browser = new ClaudeBrowser(options);
|
||||
this.port = options.port ?? 13373;
|
||||
this.setupMiddleware();
|
||||
this.setupRoutes();
|
||||
}
|
||||
|
||||
private setupMiddleware(): void {
|
||||
this.app.use(express.json());
|
||||
this.app.use(express.text({ type: '*/*' }));
|
||||
}
|
||||
|
||||
private setupRoutes(): void {
|
||||
this.app.post('/', (req, res) => this.handleCommand(req, res));
|
||||
}
|
||||
|
||||
private async handleCommand(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const cmd: BrowserCommand = typeof req.body === 'string' ? JSON.parse(req.body) : req.body;
|
||||
logger.command(cmd);
|
||||
|
||||
if (cmd.cmd === 'close') {
|
||||
logger.result(cmd, { ok: true });
|
||||
res.json({ ok: true });
|
||||
await this.stop();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const result = await this.browser.executeCommand(cmd);
|
||||
logger.result(cmd, result);
|
||||
res.json(result);
|
||||
} catch (err) {
|
||||
const error = (err as Error).message;
|
||||
console.log(`${ts()} ${logSymbols.error} ${chalk.red(error)}`);
|
||||
res.status(500).json({ ok: false, error });
|
||||
}
|
||||
}
|
||||
|
||||
async start(): Promise<void> {
|
||||
await this.browser.launch();
|
||||
|
||||
return new Promise((resolve) => {
|
||||
this.server = this.app.listen(this.port, () => {
|
||||
printBanner(this.port);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async stop(): Promise<void> {
|
||||
console.log(chalk.dim('\n Shutting down...'));
|
||||
if (this.server) {
|
||||
this.server.close();
|
||||
this.server = null;
|
||||
}
|
||||
await this.browser.close();
|
||||
console.log(` ${logSymbols.success} Browser closed\n`);
|
||||
}
|
||||
|
||||
getPort(): number {
|
||||
return this.port;
|
||||
}
|
||||
|
||||
getApp() {
|
||||
return this.app;
|
||||
}
|
||||
}
|
||||
|
||||
export async function startServer(options: ServerOptions = {}): Promise<BrowserServer> {
|
||||
const server = new BrowserServer(options);
|
||||
await server.start();
|
||||
return server;
|
||||
}
|
||||
Reference in New Issue
Block a user