# Directory Structure
```
├── .gitignore
├── assets
│ ├── claude-installed.png
│ ├── claude-open.gif
│ ├── claude-use.png
│ └── cursor.png
├── Dockerfile
├── LICENSE
├── package.json
├── pnpm-lock.yaml
├── README.MD
├── smithery.yaml
├── src
│ ├── constants
│ │ └── api.ts
│ ├── handlers
│ │ ├── application.ts
│ │ ├── automation.ts
│ │ ├── browser.ts
│ │ └── group.ts
│ ├── index.ts
│ ├── types
│ │ ├── application.ts
│ │ ├── browser.ts
│ │ ├── group.ts
│ │ ├── handlerWrapper.ts
│ │ └── schemas.ts
│ └── utils
│ ├── browserBase.ts
│ ├── handlerWrapper.ts
│ ├── requestBuilder.ts
│ └── toolRegister.ts
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
.pnpm-store/
# Build output
dist/
build/
out/
.output/
# IDE and editor files
.idea/
.vscode/
*.swp
*.swo
.DS_Store
*.sublime-workspace
*.sublime-project
# Environment variables
.env
.env.local
.env.*.local
.env.development
.env.test
.env.production
# Cache and temporary files
.cache/
.temp/
.tmp/
coverage/
*.log
.eslintcache
# System files
Thumbs.db
.DS_Store
*.pem
# Debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# Local files
*.local
# Testing
coverage/
.nyc_output/
# Typescript
*.tsbuildinfo
```
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
```markdown
# AdsPower LocalAPI MCP Server
A Model Context Protocol server that AdsPower browser LocalAPI. This server enables LLMs to interact with start browser, create browser, update browser fingerprint config ...
## Usage with Claude Desktop
Talk to LLMs to create browser: `Create an Android UA browser using Chrome 134`

Talk to LLMs to create browser: `Help me with random UA, random fingerprint, random cookie generation, create 3 browsers, use 134 cores, and open them`

## How to use?
### Requirements
- [AdsPower](https://www.adspower.com/?source=github)
- Node version 18 or greater
### Installation
To use with Claude Desktop, add the server config:
On macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` On Windows: `%APPDATA%/Claude/claude_desktop_config.json`
#### MacOS / Linux
```bash
{
"mcpServers": {
"adspower-local-api": {
"command": "npx",
"args": ["-y", "local-api-mcp-typescript"]
}
}
}
```
#### Windows
```bash
{
"mcpServers": {
"adspower-local-api": {
"command": "cmd",
"args": ["/c", "npx", "-y", "local-api-mcp-typescript"]
}
}
}
```

or use in Cursor

## Development
```bash
# git clone
git clone https://github.com/AdsPower/local-api-mcp-typescript.git
# install package
cd local-api-mcp-typescript && npx pnpm i
# build
npm run build
```
```bash
# Add the server to your claude_desktop_config.json
"mcpServers": {
"adspower-local-api": {
"command": "node",
"args": [
"<Replace Your Project Path>/local-api-mcp-typescript/build/index.js"
]
}
}
```
## Components
### Tools
- **open_browser**
- Open the browser
- Inputs:
- `serialNumber` (string, optional): The serial number of the browser to open
- `userId` (string, optional): The browser id of the browser to open
- **close_browser**
- Close the browser
- Input:
- `userId` (string): The browser id of the browser to stop
- **create_browser**
- Create a browser
- Inputs:
- `groupId` (string): The group id for the browser
- `domainName` (string, optional): The domain name
- `openUrls` (string[], optional): URLs to open
- `cookie` (string, optional): Browser cookie
- `username` (string, optional): Username
- `password` (string, optional): Password
- `system` (string, optional): System type
- `name` (string, optional): Browser name
- `country` (string, optional): Country
- `sysAppCateId` (string, optional): System application category id
- `storageStrategy` (number, optional): Storage strategy
- `userProxyConfig` (object): Proxy configuration
- `fingerprintConfig` (object, optional): Browser fingerprint configuration
- **update_browser**
- Update the browser
- Inputs: Same as create_browser, plus:
- `userId` (string): The user id of the browser to update
- **delete_browser**
- Delete the browser
- Input:
- `userIds` (string[]): The user ids of the browsers to delete
- **get_browser_list**
- Get the list of browsers
- Inputs:
- `groupId` (string, optional): The group id of the browser
- `size` (number, optional): The size of the page
- `id` (string, optional): The id of the browser
- `serialNumber` (string, optional): The serial number of the browser
- `sort` (enum, optional): Sort field ('serial_number' | 'last_open_time' | 'created_time')
- `order` (enum, optional): Sort order ('asc' | 'desc')
- **get-opened_browser**
- Get the list of opened browsers
- No inputs required
- **move_browser**
- Move browsers to a group
- Inputs:
- `groupId` (string): The target group id
- `userIds` (string[]): The browser ids to move
- **create_group**
- Create a browser group
- Inputs:
- `groupName` (string): The name of the group to create
- `remark` (string, optional): The remark of the group
- **update_group**
- Update the browser group
- Inputs:
- `groupId` (string): The id of the group to update
- `groupName` (string): The new name of the group
- `remark` (string | null, optional): The new remark of the group, set null to clear
- **get_group_list**
- Get the list of groups
- Inputs:
- `name` (string, optional): The name of the group
- `size` (number, optional): The size of the page
- **get-application_list**
- Get the list of applications
- Input:
- `size` (number, optional): The size of the page
### Advanced Configuration Types
#### UserProxyConfig
- `proxy_soft` (enum): The proxy soft type ('brightdata', 'brightauto', 'oxylabsauto', etc.)
- `proxy_type` (enum, optional): Proxy type ('http', 'https', 'socks5', 'no_proxy')
- `proxy_host` (string, optional): Proxy host
- `proxy_port` (string, optional): Proxy port
- `proxy_user` (string, optional): Proxy username
- `proxy_password` (string, optional): Proxy password
- `proxy_url` (string, optional): Proxy URL
- `global_config` (enum, optional): Global config ('0' | '1')
#### FingerprintConfig
- `automatic_timezone` (enum, optional): Automatic timezone ('0' | '1')
- `timezone` (string, optional): Timezone
- `language` (string[], optional): Languages
- `flash` (string, optional): Flash version
- `fonts` (string[], optional): Font list
- `webrtc` (enum, optional): WebRTC setting ('disabled' | 'forward' | 'proxy' | 'local')
- `browser_kernel_config` (object, optional):
- `version` (string, optional): Browser version
- `type` (enum, optional): Browser type ('chrome' | 'firefox')
- `random_ua` (object, optional):
- `ua_version` (string[], optional): User agent versions
- `ua_system_version` (enum[], optional): System versions
- `tls_switch` (enum, optional): TLS switch ('0' | '1')
- `tls` (string, optional): TLS configuration
```
--------------------------------------------------------------------------------
/src/types/application.ts:
--------------------------------------------------------------------------------
```typescript
export interface GetApplicationListParams {
size?: number;
}
```
--------------------------------------------------------------------------------
/src/types/group.ts:
--------------------------------------------------------------------------------
```typescript
export interface CreateGroupParams {
groupName: string;
remark?: string;
}
export interface UpdateGroupParams {
groupId: string;
groupName: string;
remark?: string | null;
}
export interface GetGroupListParams {
groupName?: string;
size?: number;
page?: number;
}
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ESNext",
"module": "CommonJS",
"moduleResolution": "Node",
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
```
--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------
```yaml
# Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
startCommand:
type: stdio
configSchema:
# JSON Schema defining the configuration options for the MCP.
type: object
properties: {}
commandFunction:
# A JS function that produces the CLI command based on the given config to start the MCP on stdio.
|-
(config) => ({
command: 'node',
args: ['build/index.js'],
env: {}
})
exampleConfig: {}
```
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
# Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
FROM node:lts-alpine
WORKDIR /app
# Install dependencies
COPY package.json pnpm-lock.yaml ./
# Use npm install if pnpm is not available
RUN npm install --ignore-scripts
# Copy remaining source files
COPY tsconfig.json ./
COPY src ./src
# Build the project (transpiling TypeScript to JavaScript)
RUN npm run build
# Expose no ports as MCP runs via stdio; if needed, expose port here
# Set entrypoint
ENTRYPOINT ["node", "build/index.js"]
```
--------------------------------------------------------------------------------
/src/types/handlerWrapper.ts:
--------------------------------------------------------------------------------
```typescript
export function wrapHandler(handler: Function) {
return async (params: any) => {
try {
const result = await handler(params);
return {
content: [{
type: 'text',
text: result
}]
};
} catch (error) {
return {
content: [{
type: 'text',
text: error instanceof Error ? error.message : String(error)
}]
};
}
};
}
```
--------------------------------------------------------------------------------
/src/handlers/application.ts:
--------------------------------------------------------------------------------
```typescript
import axios from 'axios';
import { LOCAL_API_BASE, API_ENDPOINTS } from '../constants/api.js';
import type { GetApplicationListParams } from '../types/application.js';
export const applicationHandlers = {
async getApplicationList({ size }: GetApplicationListParams) {
const params = new URLSearchParams();
if (size) {
params.set('page_size', size.toString());
}
const response = await axios.get(`${LOCAL_API_BASE}${API_ENDPOINTS.GET_APPLICATION_LIST}`, { params });
return `Application list: ${JSON.stringify(response.data.data.list, null, 2)}`;
}
};
```
--------------------------------------------------------------------------------
/src/constants/api.ts:
--------------------------------------------------------------------------------
```typescript
export const LOCAL_API_BASE = 'http://127.0.0.1:50325';
export const API_ENDPOINTS = {
START_BROWSER: '/api/v1/browser/start',
CLOSE_BROWSER: '/api/v1/browser/stop',
CREATE_BROWSER: '/api/v1/user/create',
GET_BROWSER_LIST: '/api/v1/user/list',
GET_GROUP_LIST: '/api/v1/group/list',
GET_APPLICATION_LIST: '/api/v1/application/list',
UPDATE_BROWSER: '/api/v1/user/update',
DELETE_BROWSER: '/api/v1/user/delete',
GET_OPENED_BROWSER: '/api/v1/browser/local-active',
CREATE_GROUP: '/api/v1/group/create',
UPDATE_GROUP: '/api/v1/group/update',
MOVE_BROWSER: '/api/v1/user/regroup'
} as const;
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "local-api-mcp-typescript",
"version": "1.0.6",
"main": "index.js",
"type": "commonjs",
"bin": {
"adspower-local-api-mcp": "./build/index.js"
},
"scripts": {
"build": "tsc",
"test": "echo \"Error: no test specified\" && exit 1"
},
"files": [
"build"
],
"keywords": [
"adspower",
"local-api",
"mcp",
"typescript"
],
"author": "AdsPower",
"license": "ISC",
"description": "",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.7.0",
"axios": "^1.8.4",
"playwright": "^1.51.1",
"zod": "^3.24.2"
},
"devDependencies": {
"@types/node": "^22.13.13",
"typescript": "^5.8.2"
},
"engines": {
"node": ">=18.0.0"
},
"repository": {
"type": "git",
"url": "https://github.com/AdsPower/local-api-mcp-typescript.git"
},
"bugs": {
"url": "https://github.com/AdsPower/local-api-mcp-typescript/issues"
},
"homepage": "https://github.com/AdsPower/local-api-mcp-typescript#readme"
}
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
#!/usr/bin/env node
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { registerTools } from './utils/toolRegister.js';
// import { getScreenshot } from './handlers/automation.js';
// Create server instance
const server = new McpServer({
name: 'adspower-local-api',
version: '1.0.6',
capabilities: {
resources: {},
tools: {},
},
});
// Register all tools
registerTools(server);
// Resources
// server.resource('get-screenshot', 'Get the screenshot of the page', async (uri, extra) => {
// const filename = uri.toString().split("://")[1];
// const screenshot = getScreenshot(filename);
// if (!screenshot) {
// return {
// contents: [],
// mimeType: 'text/plain',
// };
// }
// return {
// contents: [{
// uri: filename,
// blob: screenshot,
// mimeType: 'image/png',
// }],
// };
// });
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('AdsPower Local Api MCP Server running on stdio');
}
main().catch((error) => {
console.error('Fatal error in main():', error);
process.exit(1);
});
```
--------------------------------------------------------------------------------
/src/utils/browserBase.ts:
--------------------------------------------------------------------------------
```typescript
import { Browser, Page, chromium } from 'playwright';
class BrowserBase {
private browser: Browser | null;
private page: Page | null;
private screenshots: Map<string, string>;
constructor() {
this.browser = null;
this.page = null;
this.screenshots = new Map();
}
get browserInstance() {
return this.browser;
}
get pageInstance() {
return this.page;
}
set pageInstance(page: Page | null) {
this.page = page;
}
get screenshotsInstance() {
return this.screenshots;
}
checkConnected() {
const error = new Error('Browser not connected, please connect browser first');
if (!this.browser) {
throw error;
}
if (!this.browser.isConnected()) {
throw error;
}
if (!this.page) {
throw error;
}
}
async connectBrowserWithWs(wsUrl: string) {
this.browser = await chromium.connectOverCDP(wsUrl);
const defaultContext = this.browser.contexts()[0];
this.page = defaultContext.pages()[0];
await this.page.bringToFront().catch((error) => {
console.error('Failed to bring page to front', error);
});
}
async resetBrowser() {
this.browser = null;
this.page = null;
}
}
export default new BrowserBase();
```
--------------------------------------------------------------------------------
/src/utils/handlerWrapper.ts:
--------------------------------------------------------------------------------
```typescript
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
import browser from './browserBase.js';
export function wrapHandler(handler: Function) {
return async (params: any): Promise<CallToolResult> => {
try {
const content = await handler(params);
if (typeof content === 'string') {
return {
content: [{
type: 'text' as const,
text: content
}]
};
}
return {
content
};
} catch (error) {
let errorMessage = error instanceof Error ? error.message : String(error);
if (
errorMessage.includes("Target page, context or browser has been closed") ||
errorMessage.includes("Target closed") ||
errorMessage.includes("Browser has been disconnected") ||
errorMessage.includes("Protocol error") ||
errorMessage.includes("Connection closed")
) {
await browser.resetBrowser();
errorMessage = `Browser connection error: ${errorMessage}. Connection has been reset - please retry the operation.`;
}
return {
content: [{
type: 'text' as const,
text: errorMessage
}]
};
}
};
}
```
--------------------------------------------------------------------------------
/src/types/browser.ts:
--------------------------------------------------------------------------------
```typescript
import { z } from 'zod';
import { schemas } from './schemas.js';
// 从已有的 schema 中导出类型
export type CreateBrowserParams = z.infer<typeof schemas.createBrowserSchema>;
export type UpdateBrowserParams = z.infer<typeof schemas.updateBrowserSchema>;
export type OpenBrowserParams = z.infer<typeof schemas.openBrowserSchema>;
export type CloseBrowserParams = z.infer<typeof schemas.closeBrowserSchema>;
export type DeleteBrowserParams = z.infer<typeof schemas.deleteBrowserSchema>;
export type GetBrowserListParams = z.infer<typeof schemas.getBrowserListSchema>;
export type MoveBrowserParams = z.infer<typeof schemas.moveBrowserSchema>;
export type CreateAutomationParams = z.infer<typeof schemas.createAutomationSchema>;
export type NavigateParams = z.infer<typeof schemas.navigateSchema>;
export type ScreenshotParams = z.infer<typeof schemas.screenshotSchema>;
export type ClickElementParams = z.infer<typeof schemas.clickElementSchema>;
export type FillInputParams = z.infer<typeof schemas.fillInputSchema>;
export type SelectOptionParams = z.infer<typeof schemas.selectOptionSchema>;
export type HoverElementParams = z.infer<typeof schemas.hoverElementSchema>;
export type ScrollElementParams = z.infer<typeof schemas.scrollElementSchema>;
export type PressKeyParams = z.infer<typeof schemas.pressKeySchema>;
export type EvaluateScriptParams = z.infer<typeof schemas.evaluateScriptSchema>;
export type DragElementParams = z.infer<typeof schemas.dragElementSchema>;
export type IframeClickElementParams = z.infer<typeof schemas.iframeClickElementSchema>;
```
--------------------------------------------------------------------------------
/src/utils/requestBuilder.ts:
--------------------------------------------------------------------------------
```typescript
import { CreateBrowserParams, UpdateBrowserParams } from '../types/browser.js';
export function buildRequestBody(params: CreateBrowserParams | UpdateBrowserParams): Record<string, any> {
const requestBody: Record<string, any> = {};
const basicFields: Record<string, string> = {
domainName: 'domain_name',
openUrls: 'open_urls',
cookie: 'cookie',
username: 'username',
password: 'password',
groupId: 'group_id',
name: 'name',
country: 'country',
sysAppCateId: 'sys_app_cate_id',
userId: 'user_id'
};
Object.entries(basicFields).forEach(([paramKey, key]) => {
const value = params[paramKey as keyof typeof params];
if (value !== undefined) {
requestBody[key] = value;
}
});
if (params.userProxyConfig) {
const proxyConfig = buildNestedConfig(params.userProxyConfig);
if (Object.keys(proxyConfig).length > 0) {
requestBody.user_proxy_config = proxyConfig;
}
}
if (params.fingerprintConfig) {
const fpConfig = buildNestedConfig(params.fingerprintConfig);
if (Object.keys(fpConfig).length > 0) {
requestBody.fingerprint_config = fpConfig;
}
}
if (params.storageStrategy !== undefined) {
requestBody.storage_strategy = params.storageStrategy;
}
return requestBody;
}
function buildNestedConfig(config: Record<string, any>): Record<string, any> {
const result: Record<string, any> = {};
Object.entries(config).forEach(([key, value]) => {
if (value !== undefined) {
if (typeof value === 'object' && value !== null) {
const nestedConfig = buildNestedConfig(value);
if (Object.keys(nestedConfig).length > 0) {
result[key] = nestedConfig;
}
} else {
result[key] = value;
}
}
});
return result;
}
```
--------------------------------------------------------------------------------
/src/handlers/group.ts:
--------------------------------------------------------------------------------
```typescript
import axios from 'axios';
import { LOCAL_API_BASE, API_ENDPOINTS } from '../constants/api.js';
import type {
CreateGroupParams,
UpdateGroupParams,
GetGroupListParams
} from '../types/group.js';
export const groupHandlers = {
async createGroup({ groupName, remark }: CreateGroupParams) {
const requestBody: Record<string, string> = {
group_name: groupName
};
if (remark !== undefined) {
requestBody.remark = remark;
}
const response = await axios.post(`${LOCAL_API_BASE}${API_ENDPOINTS.CREATE_GROUP}`, requestBody);
if (response.data.code === 0) {
return `Group created successfully with name: ${groupName}${remark ? `, remark: ${remark}` : ''}`;
}
throw new Error(`Failed to create group: ${response.data.msg}`);
},
async updateGroup({ groupId, groupName, remark }: UpdateGroupParams) {
const requestBody: Record<string, any> = {
group_id: groupId,
group_name: groupName
};
if (remark !== undefined) {
requestBody.remark = remark;
}
const response = await axios.post(`${LOCAL_API_BASE}${API_ENDPOINTS.UPDATE_GROUP}`, requestBody);
if (response.data.code === 0) {
return `Group updated successfully with id: ${groupId}, name: ${groupName}${remark !== undefined ? `, remark: ${remark === null ? '(cleared)' : remark}` : ''}`;
}
throw new Error(`Failed to update group: ${response.data.msg}`);
},
async getGroupList({ groupName, size, page }: GetGroupListParams) {
const params = new URLSearchParams();
if (groupName) {
params.set('group_name', groupName);
}
if (size) {
params.set('page_size', size.toString());
}
if (page) {
params.set('page', page.toString());
}
const response = await axios.get(`${LOCAL_API_BASE}${API_ENDPOINTS.GET_GROUP_LIST}`, { params });
return `Group list: ${JSON.stringify(response.data.data.list, null, 2)}`;
}
};
```
--------------------------------------------------------------------------------
/src/utils/toolRegister.ts:
--------------------------------------------------------------------------------
```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { browserHandlers } from '../handlers/browser.js';
import { groupHandlers } from '../handlers/group.js';
import { applicationHandlers } from '../handlers/application.js';
import { schemas } from '../types/schemas.js';
import { wrapHandler } from './handlerWrapper.js';
import { automationHandlers } from '../handlers/automation.js';
export function registerTools(server: McpServer) {
// Browser tools
server.tool('open-browser', 'Open the browser, both environment and profile mean browser', schemas.openBrowserSchema.shape,
wrapHandler(browserHandlers.openBrowser));
server.tool('close-browser', 'Close the browser', schemas.closeBrowserSchema.shape,
wrapHandler(browserHandlers.closeBrowser));
server.tool('create-browser', 'Create a browser', schemas.createBrowserSchema.shape,
wrapHandler(browserHandlers.createBrowser));
server.tool('update-browser', 'Update the browser', schemas.updateBrowserSchema.shape,
wrapHandler(browserHandlers.updateBrowser));
server.tool('delete-browser', 'Delete the browser', schemas.deleteBrowserSchema.shape,
wrapHandler(browserHandlers.deleteBrowser));
server.tool('get-browser-list', 'Get the list of browsers', schemas.getBrowserListSchema.shape,
wrapHandler(browserHandlers.getBrowserList));
server.tool('get-opened-browser', 'Get the list of opened browsers', schemas.emptySchema.shape,
wrapHandler(browserHandlers.getOpenedBrowser));
server.tool('move-browser', 'Move browsers to a group', schemas.moveBrowserSchema.shape,
wrapHandler(browserHandlers.moveBrowser));
// Group tools
server.tool('create-group', 'Create a browser group', schemas.createGroupSchema.shape,
wrapHandler(groupHandlers.createGroup));
server.tool('update-group', 'Update the browser group', schemas.updateGroupSchema.shape,
wrapHandler(groupHandlers.updateGroup));
server.tool('get-group-list', 'Get the list of groups', schemas.getGroupListSchema.shape,
wrapHandler(groupHandlers.getGroupList));
// Application tools
server.tool('get-application-list', 'Get the list of applications', schemas.getApplicationListSchema.shape,
wrapHandler(applicationHandlers.getApplicationList));
// Automation tools
server.tool('connect-browser-with-ws', 'Connect the browser with the ws url', schemas.createAutomationSchema.shape,
wrapHandler(automationHandlers.connectBrowserWithWs));
server.tool('open-new-page', 'Open a new page', schemas.emptySchema.shape,
wrapHandler(automationHandlers.openNewPage));
server.tool('navigate', 'Navigate to the url', schemas.navigateSchema.shape,
wrapHandler(automationHandlers.navigate));
server.tool('screenshot', 'Get the screenshot of the page', schemas.screenshotSchema.shape,
wrapHandler(automationHandlers.screenshot));
server.tool('get-page-visible-text', 'Get the visible text content of the page', schemas.emptySchema.shape,
wrapHandler(automationHandlers.getPageVisibleText));
server.tool('get-page-html', 'Get the html content of the page', schemas.emptySchema.shape,
wrapHandler(automationHandlers.getPageHtml));
server.tool('click-element', 'Click the element', schemas.clickElementSchema.shape,
wrapHandler(automationHandlers.clickElement));
server.tool('fill-input', 'Fill the input', schemas.fillInputSchema.shape,
wrapHandler(automationHandlers.fillInput));
server.tool('select-option', 'Select the option', schemas.selectOptionSchema.shape,
wrapHandler(automationHandlers.selectOption));
server.tool('hover-element', 'Hover the element', schemas.hoverElementSchema.shape,
wrapHandler(automationHandlers.hoverElement));
server.tool('scroll-element', 'Scroll the element', schemas.scrollElementSchema.shape,
wrapHandler(automationHandlers.scrollElement));
server.tool('press-key', 'Press the key', schemas.pressKeySchema.shape,
wrapHandler(automationHandlers.pressKey));
server.tool('evaluate-script', 'Evaluate the script', schemas.evaluateScriptSchema.shape,
wrapHandler(automationHandlers.evaluateScript));
server.tool('drag-element', 'Drag the element', schemas.dragElementSchema.shape,
wrapHandler(automationHandlers.dragElement));
server.tool('iframe-click-element', 'Click the element in the iframe', schemas.iframeClickElementSchema.shape,
wrapHandler(automationHandlers.iframeClickElement));
}
```
--------------------------------------------------------------------------------
/src/handlers/browser.ts:
--------------------------------------------------------------------------------
```typescript
import axios from 'axios';
import { LOCAL_API_BASE, API_ENDPOINTS } from '../constants/api.js';
import { buildRequestBody } from '../utils/requestBuilder.js';
import type {
OpenBrowserParams,
CloseBrowserParams,
CreateBrowserParams,
UpdateBrowserParams,
DeleteBrowserParams,
GetBrowserListParams,
MoveBrowserParams
} from '../types/browser.js';
export const browserHandlers = {
async openBrowser({ serialNumber, userId, ipTab, launchArgs, clearCacheAfterClosing, cdpMask }: OpenBrowserParams) {
const params = new URLSearchParams();
if (serialNumber) {
params.set('serial_number', serialNumber);
}
if (userId) {
params.set('user_id', userId);
}
if (ipTab) {
params.set('open_tabs', ipTab);
}
if (launchArgs) {
params.set('launch_args', launchArgs);
}
if (clearCacheAfterClosing) {
params.set('clear_cache_after_closing', clearCacheAfterClosing);
}
if (cdpMask) {
params.set('cdp_mask', cdpMask);
}
params.set('open_tabs', '0');
const response = await axios.get(`${LOCAL_API_BASE}${API_ENDPOINTS.START_BROWSER}`, { params });
if (response.data.code === 0) {
return `Browser opened successfully with: ${Object.entries(response.data.data).map(([key, value]) => {
if (value && typeof value === 'object') {
return Object.entries(value).map(([key, value]) => `ws.${key}: ${value}`).join('\n');
}
return `${key}: ${value}`;
}).join('\n')}`;
}
throw new Error(`Failed to open browser: ${response.data.msg}`);
},
async closeBrowser({ userId }: CloseBrowserParams) {
const response = await axios.get(`${LOCAL_API_BASE}${API_ENDPOINTS.CLOSE_BROWSER}`, {
params: { user_id: userId }
});
return 'Browser closed successfully';
},
async createBrowser(params: CreateBrowserParams) {
const requestBody = buildRequestBody(params);
const response = await axios.post(`${LOCAL_API_BASE}${API_ENDPOINTS.CREATE_BROWSER}`, requestBody);
if (response.data.code === 0) {
return `Browser created successfully with: ${Object.entries(response.data.data).map(([key, value]) => `${key}: ${value}`).join('\n')}`;
}
throw new Error(`Failed to create browser: ${response.data.msg}`);
},
async updateBrowser(params: UpdateBrowserParams) {
const requestBody = buildRequestBody({
...params
});
requestBody.user_id = params.userId;
const response = await axios.post(`${LOCAL_API_BASE}${API_ENDPOINTS.UPDATE_BROWSER}`, requestBody);
if (response.data.code === 0) {
return `Browser updated successfully with: ${Object.entries(response.data.data).map(([key, value]) => `${key}: ${value}`).join('\n')}`;
}
throw new Error(`Failed to update browser: ${response.data.msg}`);
},
async deleteBrowser({ userIds }: DeleteBrowserParams) {
const response = await axios.post(`${LOCAL_API_BASE}${API_ENDPOINTS.DELETE_BROWSER}`, {
user_ids: userIds
});
if (response.data.code === 0) {
return `Browsers deleted successfully: ${userIds.join(', ')}`;
}
throw new Error(`Failed to delete browsers: ${response.data.msg}`);
},
async getBrowserList(params: GetBrowserListParams) {
const { groupId, size, id, serialNumber, sort, order, page } = params;
const urlParams = new URLSearchParams();
if (size) {
urlParams.set('page_size', size.toString());
}
if (page) {
urlParams.set('page', page.toString());
}
if (id) {
urlParams.set('user_id', id);
}
if (groupId) {
urlParams.set('group_id', groupId);
}
if (serialNumber) {
urlParams.set('serial_number', serialNumber);
}
if (sort) {
urlParams.set('user_sort', JSON.stringify({
[sort]: order || 'asc',
}));
}
const response = await axios.get(`${LOCAL_API_BASE}${API_ENDPOINTS.GET_BROWSER_LIST}`, { params: urlParams });
return `Browser list: ${JSON.stringify(response.data.data.list, null, 2)}`;
},
async getOpenedBrowser() {
const response = await axios.get(`${LOCAL_API_BASE}${API_ENDPOINTS.GET_OPENED_BROWSER}`);
if (response.data.code === 0) {
return `Opened browser list: ${JSON.stringify(response.data.data.list, null, 2)}`;
}
throw new Error(`Failed to get opened browsers: ${response.data.msg}`);
},
async moveBrowser({ groupId, userIds }: MoveBrowserParams) {
const response = await axios.post(`${LOCAL_API_BASE}${API_ENDPOINTS.MOVE_BROWSER}`, {
group_id: groupId,
user_ids: userIds
});
if (response.data.code === 0) {
return `Browsers moved successfully to group ${groupId}: ${userIds.join(', ')}`;
}
throw new Error(`Failed to move browsers: ${response.data.msg}`);
}
};
```
--------------------------------------------------------------------------------
/src/handlers/automation.ts:
--------------------------------------------------------------------------------
```typescript
import path from 'path';
import os from 'os';
import type { CreateAutomationParams, NavigateParams, ScreenshotParams, ClickElementParams, FillInputParams, SelectOptionParams, HoverElementParams, ScrollElementParams, PressKeyParams, EvaluateScriptParams, DragElementParams, IframeClickElementParams } from '../types/browser.js';
import browser from '../utils/browserBase.js';
const defaultDownloadsPath = path.join(os.homedir(), 'Downloads');
export const automationHandlers = {
async connectBrowserWithWs({ wsUrl }: CreateAutomationParams) {
try {
await browser.connectBrowserWithWs(wsUrl);
return `Browser connected successfully with: ${wsUrl}`;
} catch (error) {
return `Failed to connect browser with: ${error?.toString()}`;
}
},
async openNewPage() {
browser.checkConnected();
const newPage = await browser.pageInstance!.context().newPage();
browser.pageInstance = newPage;
return `New page opened successfully`;
},
async navigate({ url }: NavigateParams) {
browser.checkConnected();
await browser.pageInstance!.goto(url);
return `Navigated to ${url} successfully`;
},
async screenshot({ savePath, isFullPage }: ScreenshotParams) {
browser.checkConnected();
const filename = `screenshot-${Date.now()}-${Math.random().toString(36).substring(2, 15)}.png`;
const outputPath = path.join(savePath || defaultDownloadsPath, filename);
const screenshot = await browser.pageInstance!.screenshot({ path: outputPath, fullPage: isFullPage });
const screenshotBase64 = screenshot.toString('base64');
browser.screenshotsInstance.set(filename, screenshotBase64);
return [{
type: 'image' as const,
data: screenshotBase64,
mimeType: 'image/png'
}];
},
async getPageVisibleText() {
browser.checkConnected();
try {
const visibleText = await browser.pageInstance!.evaluate(() => {
const walker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_TEXT,
{
acceptNode: (node) => {
const style = window.getComputedStyle(
node.parentElement!
);
return style.display !== 'none' &&
style.visibility !== 'hidden'
? NodeFilter.FILTER_ACCEPT
: NodeFilter.FILTER_REJECT;
},
}
);
let text = '';
let node;
while ((node = walker.nextNode())) {
const trimmedText = node.textContent?.trim();
if (trimmedText) {
text += trimmedText + '\n';
}
}
return text.trim();
});
return `Visible text content:\n${visibleText}`;
} catch (error) {
return `Failed to get visible text content: ${(error as Error).message}`;
}
},
async getPageHtml() {
browser.checkConnected();
const html = await browser.pageInstance!.content();
return html;
},
async clickElement({ selector }: ClickElementParams) {
browser.checkConnected();
await browser.pageInstance!.click(selector);
return `Clicked element with selector: ${selector} successfully`;
},
async iframeClickElement({ selector, iframeSelector }: IframeClickElementParams) {
const frame = browser.pageInstance!.frameLocator(iframeSelector);
if (!frame) {
return `Iframe not found: ${iframeSelector}`;
}
await frame.locator(selector).click();
return `Clicked element ${selector} inside iframe ${iframeSelector} successfully`;
},
async fillInput({ selector, text }: FillInputParams) {
browser.checkConnected();
await browser.pageInstance!.waitForSelector(selector);
await browser.pageInstance!.fill(selector, text);
return `Filled input with selector: ${selector} with text: ${text} successfully`;
},
async selectOption({ selector, value }: SelectOptionParams) {
browser.checkConnected();
await browser.pageInstance!.waitForSelector(selector);
await browser.pageInstance!.selectOption(selector, value);
return `Selected option with selector: ${selector} with value: ${value} successfully`;
},
async hoverElement({ selector }: HoverElementParams) {
browser.checkConnected();
await browser.pageInstance!.waitForSelector(selector);
await browser.pageInstance!.hover(selector);
return `Hovered element with selector: ${selector} successfully`;
},
async scrollElement({ selector }: ScrollElementParams) {
browser.checkConnected();
await browser.pageInstance!.waitForSelector(selector);
await browser.pageInstance!.evaluate((selector) => {
const element = document.querySelector(selector);
if (element) {
element.scrollIntoView({ behavior: 'smooth' });
}
}, selector);
return `Scrolled element with selector: ${selector} successfully`;
},
async pressKey({ key, selector }: PressKeyParams) {
browser.checkConnected();
if (selector) {
await browser.pageInstance!.waitForSelector(selector);
await browser.pageInstance!.focus(selector);
}
await browser.pageInstance!.keyboard.press(key);
return `Pressed key: ${key} successfully`;
},
async evaluateScript({ script }: EvaluateScriptParams) {
browser.checkConnected();
const result = await browser.pageInstance!.evaluate(script);
return result;
},
async dragElement({ selector, targetSelector }: DragElementParams) {
browser.checkConnected();
const sourceElement = await browser.pageInstance!.waitForSelector(selector);
const targetElement = await browser.pageInstance!.waitForSelector(targetSelector);
const sourceBound = await sourceElement.boundingBox();
const targetBound = await targetElement.boundingBox();
if (!sourceBound || !targetBound) {
return `Could not get element positions for drag operation`;
}
await browser.pageInstance!.mouse.move(
sourceBound.x + sourceBound.width / 2,
sourceBound.y + sourceBound.height / 2
);
await browser.pageInstance!.mouse.down();
await browser.pageInstance!.mouse.move(
targetBound.x + targetBound.width / 2,
targetBound.y + targetBound.height / 2
);
await browser.pageInstance!.mouse.up();
return `Dragged element with selector: ${selector} to ${targetSelector} successfully`;
}
}
export const getScreenshot = (filename: string) => {
return browser.screenshotsInstance.get(filename);
}
```
--------------------------------------------------------------------------------
/src/types/schemas.ts:
--------------------------------------------------------------------------------
```typescript
import { z } from 'zod';
// Proxy Config Schema
const userProxyConfigSchema = z.object({
proxy_soft: z.enum([
'brightdata', 'brightauto', 'oxylabsauto', '922S5auto',
'ipideeauto', 'ipfoxyauto', '922S5auth', 'kookauto',
'ssh', 'other', 'no_proxy'
]).describe('The proxy soft of the browser'),
proxy_type: z.enum(['http', 'https', 'socks5', 'no_proxy']).optional(),
proxy_host: z.string().optional().describe('The proxy host of the browser, eg: 127.0.0.1'),
proxy_port: z.string().optional().describe('The proxy port of the browser, eg: 8080'),
proxy_user: z.string().optional().describe('The proxy user of the browser, eg: user'),
proxy_password: z.string().optional().describe('The proxy password of the browser, eg: password'),
proxy_url: z.string().optional().describe('The proxy url of the browser, eg: http://127.0.0.1:8080'),
global_config: z.enum(['0', '1']).optional().describe('The global config of the browser, default is 0')
}).describe('The user proxy config of the browser');
// Browser Kernel Config Schema
const browserKernelConfigSchema = z.object({
version: z.union([
z.literal("92"), z.literal("99"), z.literal("102"),
z.literal("105"), z.literal("108"), z.literal("111"),
z.literal("114"), z.literal("115"), z.literal("116"),
z.literal("117"), z.literal("118"), z.literal("119"),
z.literal("120"), z.literal("121"), z.literal("122"),
z.literal("123"), z.literal("124"), z.literal("125"),
z.literal("126"), z.literal("127"), z.literal("128"),
z.literal("129"), z.literal("130"), z.literal("131"),
z.literal("132"), z.literal("133"), z.literal("134"),
z.literal("ua_auto")
]).optional().describe('The version of the browser, default is ua_auto'),
type: z.enum(['chrome', 'firefox']).optional().describe('The type of the browser, default is chrome')
}).optional().describe('The browser kernel config of the browser, default is version: ua_auto, type: chrome');
// Random UA Config Schema
const randomUaConfigSchema = z.object({
ua_version: z.array(z.string()).optional(),
ua_system_version: z.array(
z.enum([
'Android 9', 'Android 10', 'Android 11', 'Android 12', 'Android 13',
'iOS 14', 'iOS 15',
'Windows 7', 'Windows 8', 'Windows 10', 'Windows 11',
'Mac OS X 10', 'Mac OS X 11', 'Mac OS X 12', 'Mac OS X 13',
'Linux'
])
).optional().describe('The ua system version of the browser, eg: ["Android 9", "iOS 14"]')
}).optional().describe('The random ua config of the browser, default is ua_version: [], ua_system_version: []');
// Fingerprint Config Schema
const fingerprintConfigSchema = z.object({
automatic_timezone: z.enum(['0', '1']).optional().describe('The automatic timezone of the browser, default is 0'),
timezone: z.string().optional().describe('The timezone of the browser, eg: Asia/Shanghai'),
language: z.array(z.string()).optional().describe('The language of the browser, eg: ["en-US", "zh-CN"]'),
flash: z.enum(['block', 'allow']).optional().describe('The flash of the browser, default is disabled'),
fonts: z.array(z.string()).optional().describe('The fonts of the browser, eg: ["Arial", "Times New Roman"]'),
webrtc: z.enum(['disabled', 'forward', 'proxy', 'local']).optional().describe('The webrtc of the browser, default is disabled'),
browser_kernel_config: browserKernelConfigSchema,
random_ua: randomUaConfigSchema,
tls_switch: z.enum(['0', '1']).optional().describe('The tls switch of the browser, default is 0'),
tls: z.string().optional().describe('The tls of the browser, if tls_switch is 1, you can set the tls of the browser, eg: "0xC02C,0xC030"')
}).optional().describe('The fingerprint config of the browser, default is automatic_timezone: 0, timezone: "", language: [], flash: "", fonts: [], webrtc: disabled, browser_kernel_config: ua_auto, random_ua: ua_version: [], ua_system_version: [], tls_switch: 0, tls: ""');
export const schemas = {
// Browser Related Schema
createBrowserSchema: z.object({
domainName: z.string().optional().describe('The domain name of the browser, eg: facebook.com'),
openUrls: z.array(z.string()).optional().describe('The open urls of the browser, eg: ["https://www.google.com"]'),
cookie: z.string().optional().describe('The cookie of the browser, eg: "[{\"domain\":\".baidu.com\",\"expirationDate\":\"\",\"name\":\"\",\"path\":\"/\",\"sameSite\":\"unspecified\",\"secure\":true,\"value\":\"\",\"id\":1}]"'),
username: z.string().optional().describe('The username of the browser, eg: "user"'),
password: z.string().optional().describe('The password of the browser, eg: "password"'),
groupId: z.string()
.regex(/^\d+$/, "Group ID must be a numeric string")
.describe('The group id of the browser, must be a numeric string (e.g., "123"). You can use the get-group-list tool to get the group list or create a new group, or default is 0'),
name: z.string().optional().describe('The name of the browser, eg: "My Browser"'),
country: z.string().optional().describe('The country of the browser, eg: "CN"'),
sysAppCateId: z.string().optional().describe('The sys app cate id of the browser, you can use the get-application-list tool to get the application list'),
userProxyConfig: userProxyConfigSchema,
fingerprintConfig: fingerprintConfigSchema,
storageStrategy: z.number().optional().describe('The storage strategy of the browser, default is 0')
}),
updateBrowserSchema: z.object({
domainName: z.string().optional().describe('The domain name of the browser, eg: facebook.com'),
openUrls: z.array(z.string()).optional().describe('The open urls of the browser, eg: ["https://www.google.com"]'),
cookie: z.string().optional().describe('The cookie of the browser, eg: "[{\"domain\":\".baidu.com\",\"expirationDate\":\"\",\"name\":\"\",\"path\":\"/\",\"sameSite\":\"unspecified\",\"secure\":true,\"value\":\"\",\"id\":1}]"'),
username: z.string().optional().describe('The username of the browser, eg: "user"'),
password: z.string().optional().describe('The password of the browser, eg: "password"'),
groupId: z.string().optional().describe('The group id of the browser, must be a numeric string (e.g., "123"). You can use the get-group-list tool to get the group list or create a new group'),
name: z.string().optional().describe('The name of the browser, eg: "My Browser"'),
country: z.string().optional().describe('The country of the browser, eg: "CN"'),
sysAppCateId: z.string().optional().describe('The sys app cate id of the browser, you can use the get-application-list tool to get the application list'),
userProxyConfig: userProxyConfigSchema.optional(),
fingerprintConfig: fingerprintConfigSchema.optional(),
storageStrategy: z.number().optional().describe('The storage strategy of the browser, default is 0'),
userId: z.string().describe('The user id of the browser to update, it is required when you want to update the browser')
}),
openBrowserSchema: z.object({
serialNumber: z.string().optional().describe('The serial number of the browser to open'),
userId: z.string().optional().describe('The browser id of the browser to open'),
ipTab: z.enum(['0', '1']).optional().describe('The ip tab of the browser, 0 is not use ip tab, 1 is use ip tab, default is 0'),
launchArgs: z.string().optional().describe(`The launch args of the browser, use chrome launch args, eg: ${JSON.stringify(["--blink-settings=imagesEnabled=false", "--disable-notifications"])}, or vista url, eg: ${JSON.stringify(["https://www.adspower.net"])}`),
clearCacheAfterClosing: z.enum(['0', '1']).optional().describe('The clear cache after closing of the browser, 0 is not clear cache after closing, 1 is clear cache after closing, default is 0'),
cdpMask: z.enum(['0', '1']).optional().describe('The cdp mask of the browser, 0 is not use cdp mask, 1 is use cdp mask, default is 0'),
}).strict(),
closeBrowserSchema: z.object({
userId: z.string().describe('The browser id of the browser to stop, it is required when you want to stop the browser')
}).strict(),
deleteBrowserSchema: z.object({
userIds: z.array(z.string()).describe('The user ids of the browsers to delete, it is required when you want to delete the browser')
}).strict(),
getBrowserListSchema: z.object({
groupId: z.string()
.regex(/^\d+$/, "Group ID must be a numeric string")
.optional()
.describe('The group id of the browser, must be a numeric string (e.g., "123"). You can use the get-group-list tool to get the group list'),
size: z.number().optional().describe('The size of the page, max is 100, if get more than 100, you need to use the page to get the next page, default is 10'),
page: z.number().optional().describe('The page of the browser, default is 1'),
id: z.string().optional().describe('The id of the browser'),
serialNumber: z.string().optional().describe('The serial number of the browser'),
sort: z.enum(['serial_number', 'last_open_time', 'created_time']).optional()
.describe('The sort of the browser'),
order: z.enum(['asc', 'desc']).optional()
.describe('The order of the browser')
}).strict(),
moveBrowserSchema: z.object({
groupId: z.string()
.regex(/^\d+$/, "Group ID must be a numeric string")
.describe('The target group id, must be a numeric string (e.g., "123"). You can use the get-group-list tool to get the group list'),
userIds: z.array(z.string()).describe('The browser ids to move')
}).strict(),
// Group Related Schema
createGroupSchema: z.object({
groupName: z.string().describe('The name of the group to create'),
remark: z.string().optional().describe('The remark of the group')
}).strict(),
updateGroupSchema: z.object({
groupId: z.string()
.regex(/^\d+$/, "Group ID must be a numeric string")
.describe('The id of the group to update, must be a numeric string (e.g., "123"). You can use the get-group-list tool to get the group list'),
groupName: z.string().describe('The new name of the group'),
remark: z.string().nullable().optional().describe('The new remark of the group')
}).strict(),
getGroupListSchema: z.object({
groupName: z.string().optional().describe('The name of the group to search, use like to search, often used group name to find the group id, so eg: "test" will search "test" and "test1"'),
size: z.number().optional().describe('The size of the page, max is 100, if get more than 100, you need to use the page to get the next page, default is 10'),
page: z.number().optional().describe('The page of the group, default is 1')
}).strict(),
// Application Related Schema
getApplicationListSchema: z.object({
size: z.number().optional().describe('The size of the page')
}).strict(),
// Empty Schema
emptySchema: z.object({}).strict(),
// Automation Related Schema
createAutomationSchema: z.object({
userId: z.string().optional().describe('The browser id of the browser to connect'),
serialNumber: z.string().optional().describe('The serial number of the browser to connect'),
wsUrl: z.string().describe('The ws url of the browser, get from the open-browser tool content `ws.puppeteer`')
}).strict(),
navigateSchema: z.object({
url: z.string().describe('The url to navigate to')
}).strict(),
screenshotSchema: z.object({
savePath: z.string().optional().describe('The path to save the screenshot'),
isFullPage: z.boolean().optional().describe('The is full page of the screenshot')
}).strict(),
clickElementSchema: z.object({
selector: z.string().describe('The selector of the element to click, find from the page source code')
}).strict(),
fillInputSchema: z.object({
selector: z.string().describe('The selector of the input to fill, find from the page source code'),
text: z.string().describe('The text to fill in the input')
}).strict(),
selectOptionSchema: z.object({
selector: z.string().describe('The selector of the option to select, find from the page source code'),
value: z.string().describe('The value of the option to select')
}).strict(),
hoverElementSchema: z.object({
selector: z.string().describe('The selector of the element to hover, find from the page source code')
}).strict(),
scrollElementSchema: z.object({
selector: z.string().describe('The selector of the element to scroll, find from the page source code, Simulates a user navigating page by scrolling, usually finding element in the bottom of the page')
}).strict(),
pressKeySchema: z.object({
key: z.string().describe('The key to press, eg: "Enter"'),
selector: z.string().optional().describe('The selector of the element to press the key, find from the page source code')
}).strict(),
evaluateScriptSchema: z.object({
script: z.string().describe('The script to evaluate, eg: "document.querySelector(\'#username\').value = \'test\'"')
}).strict(),
dragElementSchema: z.object({
selector: z.string().describe('The selector of the element to drag, find from the page source code'),
targetSelector: z.string().describe('The selector of the element to drag to, find from the page source code'),
}).strict(),
iframeClickElementSchema: z.object({
selector: z.string().describe('The selector of the element to click, find from the page source code'),
iframeSelector: z.string().describe('The selector of the iframe to click, find from the page source code')
}).strict(),
};
```