Multi-Tool MCP Server
by brguru90
README.md
# Multi-Tool MCP Server
A powerful Docker-based FastMCP server that enables dynamic Python code execution with access to multiple MCP (Model Context Protocol) clients, featuring automatic validation and runtime installation of MCP tools.
## Table of Contents
- [Features](#features)
- [Architecture](#architecture)
- [Quick Start](#quick-start)
- [MCP Tools Validation](#mcp-tools-validation)
- [Configuration](#configuration)
- [Usage](#usage)
- [Examples](#examples)
- [Pre-configured MCP Servers](#pre-configured-mcp-servers)
- [Development](#development)
- [Security](#security)
- [Monitoring](#monitoring)
- [Troubleshooting](#troubleshooting)
- [API Reference](#api-reference)
- [Performance](#performance)
- [Implementation Details](#implementation-details)
- [Issues Resolved](#issues-resolved)
- [Browser Automation](#browser-automation)
- [Manual Install Support](#manual-install-support)
## Features
### Core Features
- π **Dynamic Code Execution**: Execute Python code at runtime with full type safety
- π **Multiple MCP Clients**: Connect to and orchestrate multiple MCP servers simultaneously (9 clients)
- π³ **Docker-Based**: Lightweight, isolated, and easy to deploy
- π **Secure Sandboxing**: Isolated execution with timeout enforcement and resource limits
- π **HTTP Transport**: RESTful API access via FastMCP's HTTP transport
- β‘ **High Performance**: Async/await architecture with connection pooling
- π‘οΈ **Production Ready**: Health checks, monitoring, and graceful shutdown
- π **Browser Automation**: Playwright & Chrome DevTools integration with isolated instances
### Validation & Installation Features
- β
**Build-Time Package Installation**: Pre-installs MCP packages during Docker build from `.env` configuration
- β
**Automatic Validation**: Validates all MCP servers on container startup
- β
**Runtime Installation**: Auto-installs npm and Python packages on-demand (with caching)
- β
**Fast Startup**: Pre-installed packages load in <1 second (70% faster than runtime installation)
- β
**HTTP Server Checks**: Verifies remote server reachability
- β
**STDIO Server Support**: Validates local command availability
- β
**Flexible Modes**: Default, strict, and no-install validation modes
- β
**Manual Install Support**: Custom installation commands for URLs, Git repos, build scripts
- β
**Comprehensive Logging**: Detailed progress with status indicators
- β
**27 Pre-configured Servers**: Ready-to-use MCP server configurations
### Browser Automation Features
- β
**Real Browser Control**: Headless Chromium with full page rendering
- β
**Isolated Instances**: Parallel requests with separate browser profiles
- β
**Dynamic Version Detection**: Automatic browser version handling
- β
**Screenshot Capture**: Full-page and element screenshots
- β
**DOM Extraction**: Retrieve visible text and HTML content
- β
**JavaScript Execution**: Run scripts in page context
- β
**Network Monitoring**: Track requests and responses
## Architecture
```
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Docker Container β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Validation & Installation Layer β β
β β - Validates MCP servers on startup β β
β β - Installs npm packages (npx servers) β β
β β - Pre-caches Python packages (uvx servers) β β
β β - Verifies HTTP server reachability β β
β β - Executes manual_install_command (custom installs) β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β FastMCP HTTP Server (Port 8000) β β
β β β β
β β Tools: β β
β β - list_tools() : Lists available MCP clients β β
β β - multi_tool_executer(): Executes dynamic Python codeβ β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β MCP Clients Manager (9 clients) β β
β β - context7_mcp_client β β
β β - fetch_mcp_client β β
β β - playwright_mcp_client (32 tools) β β
β β - chrome-devtools_mcp_client (26 tools) β β
β β - sqlite_mcp_client (6 tools) β β
β β - git_mcp_client β β
β β - ... (9 total) β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Runtime Script Execution Engine β β
β β - Generate unique UID per execution β β
β β - Isolated sandbox environment β β
β β - Timeout enforcement (60s default) β β
β β - Automatic cleanup β β
β β - Async/await support for MCP clients β β
β β - extract_content helper for result parsing β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
External MCP Servers (HTTP/STDIO)
```
## Quick Start
See [QUICKSTART.md](QUICKSTART.md) for a 5-minute setup guide.
### Prerequisites
- Docker and Docker Compose
- Internet connection (for HTTP MCP servers and package installation)
### 1. Clone and Setup
```bash
git clone <repository-url>
cd wasm_mcp
# Copy example environment file
cp .env.example .env
```
### 2. Test Validation System
```bash
# Verify configuration
make test-validation
```
### 3. Build and Run
```bash
make build
make run
```
### 4. Verify Installation
```bash
# Check health
make health
# Validate MCP tools
make validate
```
# Example:
> - prompt: list complete documentations of vg-ui lib, using multi_mcp_executer
> - tool_call_name: mcp__multi-mcp-executer__multi_tool_executer
- input:
```
{
"python_code": "async def execute_tools(initial_input, **kwargs):
"""
List all VG-UI components using the vg_ui_lib_mcp_server.
This will:
1. Get startup instructions
2. List all available components
3. Get component categories
"""
vg_ui_client = kwargs.get("vg_ui_lib_mcp_server_mcp_client")
extract_content = kwargs.get("extract_content")
if not vg_ui_client:
return {
"result": None,
"result_explanation": "VG-UI MCP client is not available in the configuration."
}
try:
results = {}
# Step 1: Get startup instructions
async with vg_ui_client:
startup_result = await vg_ui_client.call_tool(
"StartupInstructions",
{}
)
# Extract startup instructions
if hasattr(startup_result, 'content'):
content = startup_result.content
if isinstance(content, list) and len(content) > 0:
startup_text = content[0].text if hasattr(content[0], 'text') else str(content[0])
else:
startup_text = str(content)
else:
startup_text = str(startup_result)
results['startup_instructions'] = startup_text
# Step 2: List all components
async with vg_ui_client:
components_result = await vg_ui_client.call_tool(
"list_components",
{}
)
# Extract components list
if hasattr(components_result, 'content'):
content = components_result.content
if isinstance(content, list) and len(content) > 0:
components_text = content[0].text if hasattr(content[0], 'text') else str(content[0])
else:
components_text = str(content)
else:
components_text = str(components_result)
results['components'] = components_text
# Step 3: List categories
async with vg_ui_client:
categories_result = await vg_ui_client.call_tool(
"list_categories",
{}
)
# Extract categories
if hasattr(categories_result, 'content'):
content = categories_result.content
if isinstance(content, list) and len(content) > 0:
categories_text = content[0].text if hasattr(content[0], 'text') else str(content[0])
else:
categories_text = str(content)
else:
categories_text = str(categories_result)
results['categories'] = categories_text
# Step 4: List CSS categories
async with vg_ui_client:
css_categories_result = await vg_ui_client.call_tool(
"list_css_categories",
{}
)
# Extract CSS categories
if hasattr(css_categories_result, 'content'):
content = css_categories_result.content
if isinstance(content, list) and len(content) > 0:
css_categories_text = content[0].text if hasattr(content[0], 'text') else str(content[0])
else:
css_categories_text = str(content)
else:
css_categories_text = str(css_categories_result)
results['css_categories'] = css_categories_text
# Step 5: List schemas
async with vg_ui_client:
schemas_result = await vg_ui_client.call_tool(
"list_schemas",
{}
)
# Extract schemas
if hasattr(schemas_result, 'content'):
content = schemas_result.content
if isinstance(content, list) and len(content) > 0:
schemas_text = content[0].text if hasattr(content[0], 'text') else str(content[0])
else:
schemas_text = str(content)
else:
schemas_text = str(schemas_result)
results['schemas'] = schemas_text
return {
"result": results,
"result_explanation": "Successfully retrieved VG-UI library documentation including startup instructions, component list, categories, CSS categories, and schemas."
}
except Exception as e:
import traceback
error_details = traceback.format_exc()
return {
"result": None,
"result_explanation": f"Error while fetching VG-UI documentation: {str(e)}\
\
Traceback:\
{error_details}"
}
",
"input": {}
}
```
- output:
```
{
"success": true,
"result": {
"startup_instructions": "{"role":"assistant","content":{"type":"text","text":"\nThis MCP server provides access to VG UI Library web components documentation from the Storybook component registry.\nYou can use the tools to fetch, categorize, and retrieve information about VG web components, their schemas, CSS styles, and examples.\n\n# VG UI Library Web Components Documentation Server\n\n## Overview\nThis server provides access to comprehensive VG UI Library web component documentation including:\n- Component definitions with props, events, and slots\n- TypeScript type schemas and definitions\n- CSS variables and styling information (with categorization support)\n- Component examples and usage patterns with framework-specific filtering\n- Category-based component organization\n\n## Framework Filtering\nWhen fetching component examples, you can use the `--use-framework` command-line argument to filter sources for a specific framework:\n- Supported frameworks: `html`, `react`, `react19`, `vue`, `angular`, `lit`\n- Command-line usage: `--use-framework vue` will return only Vue.js code samples\n- Example: `python main.py --use-framework vue` or `fastmcp run main.py --use-framework react`\n- If no argument is provided or framework not found, all framework sources are returned\n\n## Instructions\n- List all VG UI Library components initially before starting implementation to understand available options\n- For any slot in web components, if documentation shows a `default` value, it can be used directly in the template\n- Use VG UI Library web components extensively and minimize custom HTML elements\n- Minimize custom CSS/styling and custom classes; prefer using VG component CSS properties and variables\n- In CSS: avoid absolute pixels, avoid custom colors (use VG CSS variables instead), prefer flexbox over grid\n- All VG UI Library components are prefixed with `vg-` (e.g., `vg-button`, `vg-card`, `vg-flex`)\n\n## Usage Guidelines\n1. Start by listing available components to understand what's available\n2. Use schema tools to understand TypeScript types and interfaces\n3. Get detailed component documentation for specific components\n4. Access CSS definitions for styling information\n5. Browse examples to see component usage patterns\n\n## Recommended Tool Usage Flow\n\n### For Finding and Using Components:\n1. **list_components** - Get overview of all available components (recommended first step)\n2. **search_components** - Search for components by name or category\n3. **get_component_by_tag** - Get specific component documentation with props, events, slots\n4. **get_component_example** - Get usage examples for specific component and example ID\n5. **list_categories** - Browse components by category\n\n### For TypeScript Types and Schemas:\n1. **list_schemas** - See available TypeScript types\n2. **get_schema_definition** - Get specific type definitions for interfaces, enums, and type aliases\n\n### For CSS Styles and Variables:\n1. **categorize_css** - Categorize CSS styles using LLM (run once per session, avoid unless necessary)\n2. **list_css_categories** - List all CSS categories\n3. **get_css_for_category** - Fetch CSS variables and styles for a specific category\n\n## Indexing and Categorizing\n- **categorize_css**: Avoid using this tool unless you want to re-categorize the CSS styles, as it will take time to process and categorize using LLM sampling.\n- Only call the categorization tool once per server session or if the quality of categorization is not good.\n- If CSS categories are not available, run `categorize_css` first, then use `list_css_categories` and `get_css_for_category`.\n\n## Best Practices\n- Always start with `list_components` or `search_components` to discover available components\n- Use `get_component_by_tag` to understand component APIs (props, events, slots) before implementation\n- Reference `get_component_example` for real usage patterns and framework-specific code samples\n- Use VG CSS variables for consistent theming and styling instead of hardcoded values\n- Leverage TypeScript schemas for type-safe component integration\n","annotations":null,"_meta":null}}",
"components": "["vg-input","vg-button","vg-dropdown","vg-theme-provider","vg-card"]",
"categories": "[{"category":"Input","components_count":1,"components":["vg-input"]},{"category":"Button","components_count":1,"components":["vg-button"]},{"category":"Dropdown","components_count":1,"components":["vg-dropdown"]},{"category":"ThemeProvider","components_count":1,"components":["vg-theme-provider"]},{"category":"Card","components_count":1,"components":["vg-card"]}]",
"css_categories": "No CSS categories found. Please run `categorize_css` to categorize the CSS styles.",
"schemas": "["ButtonVariant","ButtonSize","ButtonNativeType","ButtonClickDetail","CardVariant","CardActionDetail","DropdownOption","DropdownChangeDetail","InputType","InputChangeDetail","ThemeMode","ThemeChangeDetail"]"
},
"result_explanation": "Successfully retrieved VG-UI library documentation including startup instructions, component list, categories, CSS categories, and schemas."
}
```
> - prompt: list all the tools from multi_mcp_executer
> - tool_call_name: mcp__multi-mcp-executer__list_tools
```json
{
"success": true,
"client_count": 9,
"tools": {
"context7": [
"resolve-library-id(libraryName: string) -> dict",
"get-library-docs(context7CompatibleLibraryID: string, topic: string, tokens: number) -> dict"
],
"pulsemcp": [],
"mcp_compass": [],
"git": [],
"fetch": [
"fetch(url: string, max_length: integer, start_index: integer, raw: boolean) -> dict"
],
"playwright": [
"start_codegen_session(options: object) -> dict",
"end_codegen_session(sessionId: string) -> dict",
"get_codegen_session(sessionId: string) -> dict",
"clear_codegen_session(sessionId: string) -> dict",
"playwright_navigate(url: string, browserType: string, width: number, height: number, timeout: number, waitUntil: string, headless: boolean) -> dict",
"playwright_screenshot(name: string, selector: string, width: number, height: number, storeBase64: boolean, fullPage: boolean, savePng: boolean, downloadsDir: string) -> dict",
"playwright_click(selector: string) -> dict",
"playwright_iframe_click(iframeSelector: string, selector: string) -> dict",
"playwright_iframe_fill(iframeSelector: string, selector: string, value: string) -> dict",
"playwright_fill(selector: string, value: string) -> dict",
"playwright_select(selector: string, value: string) -> dict",
"playwright_hover(selector: string) -> dict",
"playwright_upload_file(selector: string, filePath: string) -> dict",
"playwright_evaluate(script: string) -> dict",
"playwright_console_logs(type: string, search: string, limit: number, clear: boolean) -> dict",
"playwright_close() -> dict",
"playwright_get(url: string) -> dict",
"playwright_post(url: string, value: string, token: string, headers: object) -> dict",
"playwright_put(url: string, value: string) -> dict",
"playwright_patch(url: string, value: string) -> dict",
"playwright_delete(url: string) -> dict",
"playwright_expect_response(id: string, url: string) -> dict",
"playwright_assert_response(id: string, value: string) -> dict",
"playwright_custom_user_agent(userAgent: string) -> dict",
"playwright_get_visible_text() -> dict",
"playwright_get_visible_html(selector: string, removeScripts: boolean, removeComments: boolean, removeStyles: boolean, removeMeta: boolean, cleanHtml: boolean, minify: boolean, maxLength: number) -> dict",
"playwright_go_back() -> dict",
"playwright_go_forward() -> dict",
"playwright_drag(sourceSelector: string, targetSelector: string) -> dict",
"playwright_press_key(key: string, selector: string) -> dict",
"playwright_save_as_pdf(outputPath: string, filename: string, format: string, printBackground: boolean, margin: object) -> dict",
"playwright_click_and_switch_tab(selector: string) -> dict"
],
"sqlite": [
"read_query(query: string) -> dict",
"write_query(query: string) -> dict",
"create_table(query: string) -> dict",
"list_tables() -> dict",
"describe_table(table_name: string) -> dict",
"append_insight(insight: string) -> dict"
],
"chrome-devtools": [
"list_console_messages() -> dict",
"emulate_cpu(throttlingRate: number) -> dict",
"emulate_network(throttlingOption: string) -> dict",
"click(uid: string, dblClick: boolean) -> dict",
"drag(from_uid: string, to_uid: string) -> dict",
"fill(uid: string, value: string) -> dict",
"fill_form(elements: array) -> dict",
"hover(uid: string) -> dict",
"upload_file(uid: string, filePath: string) -> dict",
"get_network_request(url: string) -> dict",
"list_network_requests(pageSize: integer, pageIdx: integer, resourceTypes: array) -> dict",
"close_page(pageIdx: number) -> dict",
"handle_dialog(action: string, promptText: string) -> dict",
"list_pages() -> dict",
"navigate_page(url: string, timeout: integer) -> dict",
"navigate_page_history(navigate: string, timeout: integer) -> dict",
"new_page(url: string, timeout: integer) -> dict",
"resize_page(width: number, height: number) -> dict",
"select_page(pageIdx: number) -> dict",
"performance_analyze_insight(insightName: string) -> dict",
"performance_start_trace(reload: boolean, autoStop: boolean) -> dict",
"performance_stop_trace() -> dict",
"take_screenshot(format: string, quality: number, uid: string, fullPage: boolean, filePath: string) -> dict",
"evaluate_script(function: string, args: array) -> dict",
"take_snapshot() -> dict",
"wait_for(text: string, timeout: integer) -> dict"
],
"vg_ui_lib_mcp_server": [
"ClearCache() -> dict",
"StartupInstructions() -> dict",
"InitialProjectSetup() -> dict",
"list_components() -> dict",
"get_component_by_tag(component_tag: string) -> dict",
"search_components(search_term: string) -> dict",
"list_schemas() -> dict",
"get_schema_definition(schema_name: string) -> dict",
"list_categories() -> dict",
"get_component_example(component_tag: string, example_id: string) -> dict",
"categorize_css() -> dict",
"list_css_categories() -> dict",
"get_css_for_category(category_name: string) -> dict"
]
}
}
```
## MCP Tools Validation
The server includes a comprehensive validation system that automatically checks and installs MCP tools on startup.
### Validation Features
- **HTTP Server Validation**: Tests reachability of remote MCP servers
- **STDIO Command Verification**: Checks availability of local commands (npx, uvx)
- **Automatic Installation**: Installs missing npm and Python packages
- **Manual Installation**: Execute custom installation commands (URLs, Git, build scripts)
- **Comprehensive Reporting**: Detailed logs with success/failure indicators
- **Flexible Modes**: Default (warns), strict (fails), no-install (validates only)
### Validation Output Example
```
==========================================
Multi-Tool MCP Server - Starting
==========================================
[2025-01-11 10:30:15] Configuration loaded with 27 MCP servers
[2025-01-11 10:30:15] Validating and installing MCP tools...
==========================================
Starting MCP Tools Validation
==========================================
[1/27] Validating: context7
β
Status: ok - HTTP 200
[2/27] Validating: filesystem
β
Status: ok - Installed successfully
[3/27] Validating: git
β
Status: ok - Pre-cached successfully
[4/27] Validating: github
βοΈ Status: skipped - Server disabled
==========================================
Validation Summary
==========================================
Total servers: 27
β
Available: 13
βοΈ Skipped: 12
β Failed: 2
==========================================
```
### Validation Commands
```bash
# Test validation system
make test-validation
# Validate in running container
make validate
# Validate with strict mode (fails on errors)
make validate-strict
# Validate locally (no Docker)
make validate-local
```
### Validation Modes
**Default Mode** (Recommended)
```bash
make run
# Warns on failures, continues startup
```
**Strict Mode** (Production)
```bash
docker run -e STRICT_VALIDATION=true ...
# Exits on any validation failure
```
**No-Install Mode** (Testing)
```bash
python3 scripts/validate_mcp_tools.py --no-install
# Only validates, doesn't install
```
## Configuration
### Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `MCP_SERVERS_CONFIGS` | `{}` | JSON string of MCP server configurations (required) |
| `SERVER_PORT` | `8000` | HTTP server port |
| `SERVER_HOST` | `0.0.0.0` | HTTP server host |
| `LOG_LEVEL` | `INFO` | Logging level (DEBUG, INFO, WARNING, ERROR) |
| `FASTMCP_DEV_MODE` | `false` | Enable development mode with MCP Inspector (requires `fastmcp[dev]`) |
| `EXECUTION_TIMEOUT` | `60` | Max execution time in seconds |
| `MAX_SCRIPT_SIZE` | `1048576` | Max Python code size in bytes (1MB) |
| `SCRIPTS_DIR` | `/tmp/scripts` | Directory for runtime scripts |
| `STRICT_VALIDATION` | `false` | Enable strict validation mode |
### MCP Servers Configuration
The `.env` file includes 27 pre-configured MCP servers. Configuration format:
#### HTTP Server
```json
{
"context7": {
"type": "http",
"url": "https://mcp.context7.com/mcp",
"description": "Library documentation"
}
}
```
#### STDIO Server (NPM)
```json
{
"puppeteer": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-puppeteer"],
"description": "Browser automation"
}
}
```
#### STDIO Server (Python)
```json
{
"git": {
"type": "stdio",
"command": "uvx",
"args": ["mcp-server-git", "--repository", "."],
"description": "Git operations"
}
}
```
#### With Manual Installation
```json
{
"custom_server": {
"type": "stdio",
"command": "uvx",
"args": ["custom-server", "--option", "value"],
"manual_install_command": "uv tool install https://example.com/package.whl",
"description": "Custom package from URL"
}
}
```
#### Browser with Isolated Mode
```json
{
"chrome-devtools": {
"type": "stdio",
"command": "npx",
"args": ["chrome-devtools-mcp@latest", "--headless=true", "--isolated"],
"description": "Chrome DevTools with isolated instances"
}
}
```
#### Disabled Server
```json
{
"github": {
"type": "stdio",
"enabled": false,
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}"
}
}
}
```
## Usage
### Connecting to the Server
#### VSCode / Claude Desktop Configuration
```json
{
"mcpServers": {
"multi_mcp_tool_executer": {
"type": "http",
"url": "http://localhost:8000/mcp",
"transport": "http"
}
}
}
```
### Available Tools
#### 1. `list_tools`
Lists all available tools from configured MCP clients with their signatures.
**Example Response:**
```json
{
"success": true,
"client_count": 9,
"tools": {
"context7_mcp_client": [
"resolve_library_id(library_name: str) -> dict",
"get_library_docs(library_id: str, topic: str, tokens: int) -> str"
],
"playwright_mcp_client": [
"playwright_navigate(url: str, browserType: str, waitUntil: str) -> dict",
"playwright_screenshot(name: str, savePng: bool) -> dict",
"... (32 tools total)"
],
"chrome_devtools_mcp_client": [
"chrome_navigate(url: str) -> dict",
"chrome_click(selector: str) -> dict",
"... (26 tools total)"
]
}
}
```
#### 2. `multi_tool_executer`
Executes custom Python code that can orchestrate multiple MCP tools.
**Parameters:**
- `python_code` (str): Python code containing `execute_tools` function
- `input` (Any): Input data to pass to the function
**Required Function Signature:**
```python
async def execute_tools(initial_input: Any, **kwargs: Dict[str, Any]) -> Dict[str, Any]:
"""
Args:
initial_input: The input data provided
**kwargs: MCP clients and helper functions
- {name}_mcp_client: MCP client objects
- extract_content: Helper to parse CallToolResult objects
Returns:
Dict with 'result' and 'result_explanation' keys
"""
pass
```
**CRITICAL: MCP Client Usage Requirements**
All MCP client methods are async and MUST be used with the 'async with' context manager:
β
**CORRECT - Use async with context manager:**
```python
async def execute_tools(initial_input, **kwargs):
fetch_client = kwargs.get("fetch_mcp_client")
extract_content = kwargs.get("extract_content")
# MUST use 'async with' before calling any client methods
async with fetch_client:
result = await fetch_client.call_tool(
"fetch",
{"url": "https://api.ipify.org?format=json"}
)
# Use extract_content to parse the CallToolResult
parsed_data = extract_content(result)
return {
"result": parsed_data,
"result_explanation": f"Fetched IP: {parsed_data.get('ip', 'N/A')}"
}
```
β **WRONG - Will raise "Client is not connected" error:**
```python
async def execute_tools(initial_input, **kwargs):
fetch_client = kwargs.get("fetch_mcp_client")
# Missing 'async with' - THIS WILL FAIL
result = await fetch_client.call_tool("fetch", {"url": "..."})
return {"result": result, "result_explanation": "..."}
```
**Multiple clients example:**
```python
async def execute_tools(initial_input, **kwargs):
context7_client = kwargs.get("context7_mcp_client")
fetch_client = kwargs.get("fetch_mcp_client")
extract_content = kwargs.get("extract_content")
# Use each client within its own async with block
async with context7_client:
docs = await context7_client.call_tool(
"get-library-docs",
{"context7CompatibleLibraryID": "/python/requests"}
)
async with fetch_client:
data = await fetch_client.call_tool(
"fetch",
{"url": "https://api.example.com"}
)
return {
"result": {
"docs": extract_content(docs),
"data": extract_content(data)
},
"result_explanation": "Retrieved docs and fetched data"
}
```
## Examples
### Example 1: Simple Library Documentation Lookup
```python
python_code = """
async def execute_tools(initial_input: Any, **kwargs: Dict[str, Any]) -> Dict[str, Any]:
context7 = kwargs.get("context7_mcp_client")
extract_content = kwargs.get("extract_content")
library_name = initial_input["library"]
async with context7:
result = await context7.call_tool(
"resolve-library-id",
{"libraryName": library_name}
)
library_data = extract_content(result)
return {
"result": library_data,
"result_explanation": f"Found documentation for {library_name}"
}
"""
input_data = {"library": "fastmcp"}
```
### Example 2: Browser Automation - Extract IP Address
```python
python_code = """
async def execute_tools(initial_input: dict, **kwargs) -> dict:
playwright_client = kwargs.get("playwright_mcp_client")
extract_content = kwargs.get("extract_content")
# 1. Launch browser and navigate
async with playwright_client:
await playwright_client.call_tool("playwright_navigate", {
"url": "https://whatismyipaddress.com/",
"browserType": "chromium",
"waitUntil": "load",
"headless": True
})
# 2. Extract page content
async with playwright_client:
text_result = await playwright_client.call_tool(
"playwright_get_visible_text", {}
)
text = extract_content(text_result)
# 3. Capture screenshot
async with playwright_client:
await playwright_client.call_tool("playwright_screenshot", {
"name": "ip_page",
"savePng": True
})
# 4. Parse IP from page text
import re
ip_match = re.search(r'IPv4:.*?(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', text)
ip_address = ip_match.group(1) if ip_match else "Not found"
# 5. Cleanup
async with playwright_client:
await playwright_client.call_tool("playwright_close", {})
return {
"result": {"ip_address": ip_address, "page_text": text[:200]},
"result_explanation": f"Successfully extracted IP: {ip_address}"
}
"""
input_data = {}
```
### Example 3: Multi-Server File Processing
```python
python_code = """
async def execute_tools(initial_input: Any, **kwargs: Dict[str, Any]) -> Dict[str, Any]:
filesystem = kwargs.get("filesystem_mcp_client")
git = kwargs.get("git_mcp_client")
extract_content = kwargs.get("extract_content")
filepath = initial_input["file"]
# Read file
async with filesystem:
content_result = await filesystem.call_tool("read_file", {"path": filepath})
content = extract_content(content_result)
# Check git status
async with git:
status_result = await git.call_tool("git_status", {"repository": "."})
git_status = extract_content(status_result)
return {
"result": {
"file": filepath,
"content_length": len(content),
"git_tracked": "tracked" in str(git_status).lower()
},
"result_explanation": f"Processed {filepath} with git status check"
}
"""
input_data = {"file": "/path/to/file.txt"}
```
### Example 4: Data Processing Pipeline
```python
python_code = """
async def execute_tools(initial_input: Any, **kwargs: Dict[str, Any]) -> Dict[str, Any]:
items = initial_input["items"]
processed_items = []
for item in items:
processed_items.append({
"original": item,
"processed": item.upper(),
"length": len(item)
})
return {
"result": {
"total_processed": len(processed_items),
"items": processed_items
},
"result_explanation": f"Successfully processed {len(items)} items"
}
"""
input_data = {"items": ["hello", "world", "fastmcp"]}
```
### Example 5: VG-UI Library Documentation Retrieval
This example demonstrates fetching complete documentation from a custom MCP server (VG-UI Library).
```python
python_code = """
async def execute_tools(initial_input, **kwargs):
'''
List all VG-UI components using the vg_ui_lib_mcp_server.
This will:
1. Get startup instructions
2. List all available components
3. Get component categories
4. List CSS categories
5. List TypeScript schemas
'''
vg_ui_client = kwargs.get("vg_ui_lib_mcp_server_mcp_client")
extract_content = kwargs.get("extract_content")
if not vg_ui_client:
return {
"result": None,
"result_explanation": "VG-UI MCP client is not available in the configuration."
}
try:
results = {}
# Step 1: Get startup instructions
async with vg_ui_client:
startup_result = await vg_ui_client.call_tool(
"StartupInstructions",
{}
)
# Extract startup instructions
if hasattr(startup_result, 'content'):
content = startup_result.content
if isinstance(content, list) and len(content) > 0:
startup_text = content[0].text if hasattr(content[0], 'text') else str(content[0])
else:
startup_text = str(content)
else:
startup_text = str(startup_result)
results['startup_instructions'] = startup_text
# Step 2: List all components
async with vg_ui_client:
components_result = await vg_ui_client.call_tool(
"list_components",
{}
)
# Extract components list
if hasattr(components_result, 'content'):
content = components_result.content
if isinstance(content, list) and len(content) > 0:
components_text = content[0].text if hasattr(content[0], 'text') else str(content[0])
else:
components_text = str(content)
else:
components_text = str(components_result)
results['components'] = components_text
# Step 3: List categories
async with vg_ui_client:
categories_result = await vg_ui_client.call_tool(
"list_categories",
{}
)
# Extract categories
if hasattr(categories_result, 'content'):
content = categories_result.content
if isinstance(content, list) and len(content) > 0:
categories_text = content[0].text if hasattr(content[0], 'text') else str(content[0])
else:
categories_text = str(content)
else:
categories_text = str(categories_result)
results['categories'] = categories_text
# Step 4: List CSS categories
async with vg_ui_client:
css_categories_result = await vg_ui_client.call_tool(
"list_css_categories",
{}
)
# Extract CSS categories
if hasattr(css_categories_result, 'content'):
content = css_categories_result.content
if isinstance(content, list) and len(content) > 0:
css_categories_text = content[0].text if hasattr(content[0], 'text') else str(content[0])
else:
css_categories_text = str(content)
else:
css_categories_text = str(css_categories_result)
results['css_categories'] = css_categories_text
# Step 5: List schemas
async with vg_ui_client:
schemas_result = await vg_ui_client.call_tool(
"list_schemas",
{}
)
# Extract schemas
if hasattr(schemas_result, 'content'):
content = schemas_result.content
if isinstance(content, list) and len(content) > 0:
schemas_text = content[0].text if hasattr(content[0], 'text') else str(content[0])
else:
schemas_text = str(content)
else:
schemas_text = str(schemas_result)
results['schemas'] = schemas_text
return {
"result": results,
"result_explanation": "Successfully retrieved VG-UI library documentation including startup instructions, component list, categories, CSS categories, and schemas."
}
except Exception as e:
import traceback
error_details = traceback.format_exc()
return {
"result": None,
"result_explanation": f"Error while fetching VG-UI documentation: {str(e)}\\n\\nTraceback:\\n{error_details}"
}
"""
input_data = {}
```
**Expected Output:**
```json
{
"success": true,
"result": {
"startup_instructions": "{\"role\":\"assistant\",\"content\":{...}}",
"components": "[\"vg-input\",\"vg-button\",\"vg-dropdown\",\"vg-theme-provider\",\"vg-card\"]",
"categories": "[{\"category\":\"Input\",\"components_count\":1,...}]",
"css_categories": "No CSS categories found. Please run `categorize_css` to categorize the CSS styles.",
"schemas": "[\"ButtonVariant\",\"ButtonSize\",\"ButtonNativeType\",...]"
},
"result_explanation": "Successfully retrieved VG-UI library documentation including startup instructions, component list, categories, CSS categories, and schemas."
}
```
**Key Features Demonstrated:**
- Multiple sequential MCP tool calls to gather comprehensive documentation
- Handling different response formats (content lists vs direct strings)
- Graceful error handling with detailed traceback
- Structured result aggregation from multiple tool calls
- Working with custom MCP servers beyond standard HTTP/STDIO
## Pre-configured MCP Servers
### HTTP Remote Servers (No API Keys Required)
| Server | Description | Auto-Install |
|--------|-------------|--------------|
| **context7** | Library documentation and code examples | N/A (HTTP) |
| **pulsemcp** | MCP community hub and newsletter | N/A (HTTP) |
| **mcp_compass** | Intelligent server recommendations | N/A (HTTP) |
### STDIO Servers (No API Keys Required)
| Server | Description | Package Manager | Auto-Install | Tools Count |
|--------|-------------|-----------------|--------------|-------------|
| **filesystem** | Secure file operations | npm | β
Yes | Multiple |
| **git** | Git repository operations | Python (uv) | β
Yes | Multiple |
| **fetch** | Web content fetching | Python (uv) | β
Yes | 1 |
| **playwright** | Cross-browser testing & automation | npm | β
Yes | 32 |
| **chrome-devtools** | Chrome DevTools integration | npm | β
Yes | 26 |
| **sqlite** | SQLite database operations | Python (uv) | β
Yes | 6 |
| **memory** | Knowledge graph memory | npm | β
Yes | Multiple |
| **docker** | Container management | npm | β
Yes | Multiple |
| **time** | Timezone operations | npm | β
Yes | Multiple |
| **sequential_thinking** | Problem-solving | npm | β
Yes | Multiple |
### STDIO Servers (API Keys Required - Disabled by Default)
Enable by adding API keys to `.env` and setting `"enabled": true`
| Server | Description | Requires |
|--------|-------------|----------|
| **github** | GitHub integration | `GITHUB_TOKEN` |
| **postgres** | PostgreSQL operations | Connection string |
| **brave_search** | Web search | `BRAVE_API_KEY` |
| **kubernetes** | K8s cluster operations | Kube config |
| **aws** | AWS services | AWS credentials |
| **google_drive** | Google Drive operations | OAuth tokens |
| **google_maps** | Google Maps API | `GOOGLE_MAPS_API_KEY` |
| **sentry** | Error tracking | `SENTRY_AUTH_TOKEN` |
| **slack** | Slack integration | `SLACK_BOT_TOKEN` |
| **everart** | AI image generation | `EVERART_API_KEY` |
| **axiom** | Log analytics | `AXIOM_API_TOKEN` |
| **linear** | Issue tracking | `LINEAR_API_KEY` |
| **jira** | Atlassian integration | Jira credentials |
| **raygun** | Crash reporting | `RAYGUN_API_KEY` |
## Development
### Development Mode with MCP Inspector
FastMCP supports a development mode that runs the server alongside the **MCP Inspector** for interactive debugging and testing.
#### Enable Development Mode
Set the `FASTMCP_DEV_MODE` environment variable to `true`:
```bash
# In .env file
FASTMCP_DEV_MODE=true
# Or via Docker command
docker run -e FASTMCP_DEV_MODE=true ...
```
#### Requirements
Development mode requires the FastMCP dev dependencies:
```bash
pip install 'fastmcp[dev]'
```
#### Features
- π **MCP Inspector UI**: Interactive web interface for testing tools
- π **Real-time Debugging**: See tool calls and responses in real-time
- π§ͺ **Interactive Testing**: Test tools without writing client code
- π **Request/Response Inspection**: Detailed view of all MCP protocol messages
- β‘ **Hot Reload**: Automatically restarts on code changes
#### Using Development Mode
**With Docker:**
```bash
# Update .env
FASTMCP_DEV_MODE=true
# Rebuild and run
make rebuild
# Or run directly
docker run -d \
-e FASTMCP_DEV_MODE=true \
-p 8000:8000 \
--env-file .env \
multi-mcp-tool-executer:latest
```
**Locally (without Docker):**
```bash
# Set environment variable
export FASTMCP_DEV_MODE=true
# Run server
python -m src.server
# Or use FastMCP CLI directly
fastmcp dev src/server.py
```
### Local Development
```bash
# Install dependencies
make dev-install
# Run locally (without Docker)
make dev-run
# Run in dev mode with Inspector
export FASTMCP_DEV_MODE=true
make dev-run
# Format code
make dev-format
# Run linters
make dev-lint
```
### Running Tests
```bash
# Test validation system
make test-validation
# In container
make test
# Locally
make test-local
```
### Project Structure
```
wasm_mcp/
βββ Dockerfile # Multi-stage Docker build
βββ docker-compose.yml # Docker Compose configuration
βββ Makefile # Build automation
βββ requirements.txt # Python dependencies
βββ .env # Environment configuration (27 MCP servers)
βββ README.md # Comprehensive documentation (this file)
βββ QUICKSTART.md # Quick start guide
βββ CLAUDE.md # Project instructions
β
βββ src/
β βββ __init__.py
β βββ server.py # Main FastMCP server
β βββ client_manager.py # MCP clients manager
β βββ executor.py # Runtime code executor
β βββ config.py # Configuration management
β βββ utils.py # Utility functions
β
βββ scripts/
βββ validate_mcp_tools.py # MCP tools validation script
βββ install_mcp_packages.py # Build-time package installer
βββ docker_entrypoint.sh # Container startup script
βββ test_validation.sh # Validation system tests
```
## Security
### Execution Sandbox
- **Timeout Enforcement**: 60s default maximum execution time
- **Resource Limits**: CPU and memory constraints via Docker
- **Isolated Execution**: Each execution in separate UID directory
- **No Persistence**: Scripts cleaned after execution
- **Input Validation**: Size limits and type checking
- **Clean Separation**: No access to host filesystem
### Network Security
- **HTTPS Support**: Use reverse proxy (nginx, traefik) for TLS
- **Authentication**: Bearer token support for MCP clients
- **CORS**: Configurable via Starlette middleware
- **Rate Limiting**: Implement via reverse proxy or custom middleware
### Validation Security
- Only installs packages from trusted sources (npm, PyPI)
- Validates commands before execution
- Logs all installation activities
- Supports strict mode for production environments
### Browser Security
- **Isolated Profiles**: Each browser instance uses separate profile
- **Headless Mode**: No UI exposure
- **Resource Cleanup**: Automatic browser session termination
- **Sandboxed Execution**: Chromium sandbox enabled
### Best Practices
1. **Always use HTTPS** in production
2. **Validate all inputs** before execution
3. **Monitor resource usage** via Docker stats
4. **Review execution logs** regularly
5. **Keep dependencies updated**
6. **Use secrets management** for sensitive configurations
7. **Enable strict validation** in production: `STRICT_VALIDATION=true`
8. **Disable unused servers** to reduce attack surface
9. **Use isolated mode** for browser automation: `--isolated` flag
## Monitoring
### Health Checks
```bash
# Check server health
curl http://localhost:8000/health
make health
```
Response:
```json
{
"status": "healthy",
"server": "Multi-Tool MCP Executer",
"clients_configured": 9,
"clients_initialized": 9
}
```
### Logs
```bash
# Follow logs
make logs
# Last 100 lines
make logs-tail
# Docker Compose logs
make compose-logs
# View validation logs
make logs | grep "Validation"
```
### Resource Monitoring
```bash
# Container stats
make stats
# Inspect container
make inspect
# Check running processes
make ps
```
## Troubleshooting
### Server Won't Start
1. Check validation logs:
```bash
docker logs multi-mcp-tool-executer
```
2. Test validation system:
```bash
make test-validation
```
3. Verify configuration:
```bash
echo $MCP_SERVERS_CONFIGS | jq .
```
4. Check port availability:
```bash
lsof -i :8000
```
### Validation Failing
1. Run test suite:
```bash
make test-validation
```
2. Validate locally:
```bash
make validate-local
```
3. Check specific server:
```bash
docker exec multi-mcp-tool-executer curl https://mcp.context7.com/mcp
```
4. Manual package installation:
```bash
docker exec multi-mcp-tool-executer npm install -g package-name
```
### Client Connection Failures
1. Verify downstream MCP servers are accessible
2. Check network connectivity from container
3. Review client initialization logs
4. Test with curl from inside container
### Browser Automation Issues
1. **Profile Lock Error**: Add `--isolated` flag to chrome-devtools configuration
2. **Browser Version Mismatch**: Rebuild container to update dynamic browser detection
3. **Playwright Timeout**: Increase `waitUntil` timeout in navigation calls
4. **Screenshot Failures**: Check `/tmp/` directory permissions
### Execution Timeouts
1. Increase `EXECUTION_TIMEOUT` if legitimate operations take longer
2. Optimize Python code for better performance
3. Check for infinite loops or blocking operations
4. Monitor execution logs for bottlenecks
### Memory Issues
1. Monitor with `docker stats`
2. Reduce `MAX_SCRIPT_SIZE` if needed
3. Limit concurrent executions
4. Implement result streaming for large outputs
## API Reference
### Health Endpoint
**GET** `/health`
Returns server health status and client information.
**Response:**
```json
{
"status": "healthy",
"server": "Multi-Tool MCP Executer",
"clients_configured": 9,
"clients_initialized": 9
}
```
### MCP Endpoint
**POST** `/mcp`
Standard MCP protocol endpoint. Use FastMCP client to interact.
## Performance
### Benchmarks
- Docker build time: ~4 minutes (one-time, includes package pre-installation)
- Cold start: ~2-3 seconds (with pre-installed packages)
- Request latency: <100ms for list_tools
- Execution overhead: ~50-100ms
- Concurrent executions: 10 (configurable)
- Memory per execution: ~50-100MB
- Validation time: 5-15 seconds (with pre-installed packages), 10-30 seconds (without)
- **Package load time**: <1 second (pre-installed), 120+ seconds (runtime install)
- **Browser launch**: 2-3 seconds per instance
- **Screenshot capture**: <1 second
### Performance Improvements
**Build-Time Package Installation**:
- Pre-installs packages from `.env` during Docker build
- Reduces runtime startup by 70% for common packages
- Example: `mcp-server-git` installs in 0.08s (was timing out before)
- Example: `@modelcontextprotocol/server-filesystem` installs in 3s (was 120s+)
**Package Installation Performance**:
| Package | Runtime Install | Pre-installed | Improvement |
|---------|----------------|---------------|-------------|
| filesystem | ~120s | ~3s | 97% faster |
| mcp-server-git | Timeout | 0.08s | β
Now works |
| mcp-server-fetch | Timeout | 0.03s | β
Now works |
| playwright | ~120s | ~11s | 90% faster |
**Docker Layer Caching**:
1. **Base dependencies** - System packages, Node.js (cached)
2. **Scripts + .env** - Installation scripts (cached separately)
3. **Playwright install** - pip + npm packages (~18s, cached)
4. **Browser download** - Chromium 175MB (~141s, cached separately)
5. **MCP packages** - Pre-install from .env (only on .env changes)
6. **Application code** - Last layer (frequent changes)
**Result**: ~5-10s rebuilds after initial build
### Optimization Tips
1. **Build-Time Installation**: Packages are automatically pre-installed from `.env` during build
2. **Connection Pooling**: Reuse MCP client connections
3. **Package Caching**: Pre-installed packages are cached in Docker layers
4. **Async Operations**: All I/O is async
5. **Resource Limits**: Configure Docker memory/CPU limits
6. **Code Optimization**: Minimize execution time in user code
7. **Disable Unused Servers**: Reduce startup time and resource usage
8. **Browser Pooling**: Reuse browser instances with isolated profiles
## Make Commands Reference
| Command | Description |
|---------|-------------|
| `make help` | Show all available commands |
| `make test-validation` | Test validation system |
| `make build` | Build Docker image |
| `make run` | Run container |
| `make stop` | Stop and remove container |
| `make restart` | Restart container |
| `make logs` | Follow container logs |
| `make logs-tail` | View last 100 lines |
| `make health` | Check server health |
| `make validate` | Validate MCP tools |
| `make validate-strict` | Validate with strict mode |
| `make validate-local` | Validate locally |
| `make shell` | Open shell in container |
| `make clean` | Stop and remove everything |
| `make rebuild` | Clean, build, and run |
| `make compose-up` | Start with docker-compose |
| `make compose-down` | Stop docker-compose services |
| `make stats` | Show resource usage |
## Implementation Details
### Docker Build Strategy
The project uses a **multi-stage Docker build** for optimal image size and build caching:
#### Stage 1: Base Image
- Python 3.11-slim with system dependencies (gcc, g++, curl)
- Installs build tools required for Python package compilation
#### Stage 2: Dependencies
- Installs Python packages from requirements.txt
- Separate stage enables efficient caching of dependencies
- Only rebuilds when requirements.txt changes
#### Stage 3: Runtime
- Minimal runtime image with only necessary components
- Copies Python packages from dependencies stage
- Installs Node.js, npm, and uv for MCP server support
- Includes validation scripts and entrypoint
- Sets up directory structure and permissions
- **Pre-installs MCP packages from `.env` during build** for faster startup
- **Installs Playwright browsers** with dynamic version detection
**Build-Time Package Installation**:
- Dynamically reads `.env` file to extract MCP server configurations
- Executes `manual_install_command` for custom installations (URLs, Git, build scripts)
- Installs npm packages globally: `npm install -g <package>`
- Pre-caches Python packages: `uv tool install <package>`
- Validates package names to skip invalid paths/URLs
- Gracefully handles timeouts (packages retry at runtime)
- Result: 70% faster startup time for pre-installed packages
**Build Optimization Benefits**:
- Reduces final image size by ~40%
- Faster rebuilds through layer caching
- Separates build-time and runtime dependencies
- Improves security by minimizing installed packages
- **Pre-installed packages load in <1 second vs 120+ seconds at runtime**
### Execution Flow
```
1. Container Startup
βββ docker_entrypoint.sh executes
βββ Dynamic browser version detection and symlinking
βββ Calls validate_mcp_tools.py
βββ Parses MCP_SERVERS_CONFIGS
βββ HTTP Servers: Tests reachability
βββ STDIO Servers: Installs packages
βββ Manual Install: Executes custom commands
βββ Reports validation results
2. FastMCP Server Initialization
βββ Loads configuration from environment
βββ Initializes MCP client connections (async with support)
βββ Exposes HTTP endpoint on port 8000
3. Client Request Handling
βββ Receives tool call via HTTP
βββ Routes to list_tools or multi_tool_executer
βββ Returns structured response
4. Code Execution (multi_tool_executer)
βββ Generates unique UID
βββ Creates isolated directory: /tmp/scripts/{UID}/
βββ Writes Python code to script.py
βββ Dynamic import and execution (async support)
βββ Provides extract_content helper for result parsing
βββ Captures result and logs
βββ Cleanup temporary files
βββ Returns result + explanation
```
### Core Components
#### 1. Validation System (`scripts/validate_mcp_tools.py`)
**Class Structure**:
```python
class MCPToolValidator:
def __init__(self, config_str: str)
# Parses MCP_SERVERS_CONFIGS JSON
def check_http_server(self, url: str) -> Tuple[bool, str]
# Tests HTTP server reachability with timeout
def check_command_available(self, command: str) -> bool
# Verifies command exists in PATH
def install_npx_package(self, package: str) -> Tuple[bool, str]
# Runs: npm install -g {package}
def install_uvx_package(self, package: str) -> Tuple[bool, str]
# Runs: uv tool install {package}
def validate_and_install_server(self, name: str, config: Dict) -> Dict
# Orchestrates validation and installation
def run_validation(self, strict: bool, no_install: bool) -> Dict
# Main validation loop with reporting
```
**Validation Logic**:
- HTTP servers: GET request with 5-second timeout
- NPX servers: Checks npm command, extracts package name, installs globally
- UVX servers: Checks uvx command, pre-caches package
- Manual install: Executes custom bash commands (URLs, Git, build scripts)
- Skips disabled servers (`"enabled": false`)
#### 2. Package Installer (`scripts/install_mcp_packages.py`)
**Manual Install Support**:
```python
def run_manual_install(command: str, server_name: str) -> bool:
"""Execute manual installation command"""
result = subprocess.run(
command,
shell=True,
capture_output=True,
text=True,
timeout=600 # 10 minutes for manual installations
)
```
**Installation Priority**:
1. manual_install_command (if present)
2. npm packages (npx command)
3. uv packages (uvx command)
#### 3. Server Manager (`src/server.py`)
**Key Components**:
- FastMCP HTTP server initialization
- Tool registration and routing
- MCP client lifecycle management
- Request/response handling
- Error handling and logging
- Health check endpoint
**Tool Implementation**:
```python
@mcp.tool(name="list_tools")
def list_tools() -> dict:
# Queries all MCP clients
# Returns unified tool list with signatures
@mcp.tool(name="multi_tool_executer")
def multi_tool_executer(python_code: str, input: Any) -> dict:
# Generates UID
# Writes code to isolated directory
# Dynamically imports and executes (async support)
# Provides extract_content helper
# Returns structured result
```
#### 4. Client Manager (`src/client_manager.py`)
**Responsibilities**:
- Parse MCP_SERVERS_CONFIGS environment variable
- Initialize FastMCP clients (HTTP and STDIO transports)
- Maintain connection pools and lifecycle
- Handle connection failures and retries
- Provide unified API for tool discovery
**Configuration Parsing**:
- Validates JSON structure
- Extracts transport type (http/stdio)
- Handles authentication headers
- Manages environment variable interpolation
- Supports MCPConfig dict format for STDIO
#### 5. Runtime Executor (`src/executor.py`)
**Security Features**:
- UUID-based execution isolation
- Timeout enforcement (default 60s)
- Resource limits via Docker constraints
- Input size validation (max 1MB)
- Automatic cleanup after execution
- No persistent storage
**Helper Functions**:
- `extract_content()`: Automatically parses CallToolResult objects
- Extracts JSON data using regex
- Falls back to text content extraction
- Returns raw string if parsing fails
**Execution Process**:
```python
1. Generate UUID4 for execution instance
2. Create directory: /tmp/scripts/{uuid}/
3. Write user code to {uuid}/script.py
4. Add helper functions (extract_content)
5. Import: importlib.import_module(f"scripts.{uuid}.script")
6. Execute: await module.execute_tools(input, **mcp_clients) if async
7. Capture result dictionary
8. Clean up temporary directory
9. Return result with explanation
```
## Issues Resolved
### Import Errors β
**Problem**: `ImportError: attempted relative import with no known parent package`
**Solution**: Added sys.path manipulation for absolute imports in all source files
- Files: [src/server.py](src/server.py#L9-L14), [src/executor.py](src/executor.py#L10-L15), [src/client_manager.py](src/client_manager.py#L12-L17)
### FastMCP Lifespan Error β
**Problem**: `RuntimeError: Task group is not initialized`
**Solution**: Combined FastMCP's original lifespan with custom startup/shutdown logic
- File: [src/server.py](src/server.py#L299-L310)
### HTTP Client Initialization β
**Problem**: `Client.__init__() got an unexpected keyword argument 'headers'`
**Solution**: Removed headers parameter from HTTP client initialization
- File: [src/client_manager.py](src/client_manager.py#L80-L88)
### STDIO Client Initialization β
**Problem**: `Could not infer a valid transport from: ['uvx', ...]`
**Solution**: Used MCPConfig dict format for STDIO transports
- File: [src/client_manager.py](src/client_manager.py#L59-L78)
### Tool Listing Format β
**Problem**: `'list' object has no attribute 'tools'`
**Solution**: Handle both list and object response formats dynamically
- File: [src/client_manager.py](src/client_manager.py#L126-L163)
### Async Execution Support β
**Problem**: Coroutines not awaited, helper functions undefined
**Solution**: Added async/await support with automatic coroutine detection
- Files: [src/executor.py](src/executor.py#L147-L209), [src/server.py](src/server.py#L97-L158)
### Chrome DevTools Profile Lock β
**Problem**: Browser profile locked, blocking parallel requests
**Solution**: Added `--isolated` flag to create temporary profiles per request
- File: [.env](. env#L59)
### Playwright Browser Version Mismatch β
**Problem**: Expecting chromium-1179, but chromium-1187 installed
**Solution**: Dynamic browser detection in entrypoint script
- File: [scripts/docker_entrypoint.sh](scripts/docker_entrypoint.sh#L21-L38)
## Browser Automation
### Successful Test: IP Extraction
**Test Objective**: Extract IP address from https://whatismyipaddress.com/ using browser tools only
**Result**: β
SUCCESS
- **IP Address**: 152.57.51.5
- **Location**: Udupi, Karnataka, India
- **ISP**: Reliance Jio Infocomm Limited
- **Method**: Playwright MCP with real Chromium browser
### Browser Capabilities
**Playwright MCP (32 tools)**:
- Navigate to URLs with various wait strategies
- Extract visible text and HTML content
- Capture screenshots and PDFs
- Execute JavaScript in page context
- Handle forms, clicks, and interactions
- Monitor network requests and console logs
- Close and cleanup browser sessions
**Chrome DevTools MCP (26 tools)**:
- Advanced browser control and debugging
- Performance monitoring
- Network traffic analysis
- Console log capture
- Element inspection
### Browser Automation Steps
```python
# 1. Launch browser and navigate
async with playwright_client:
await playwright_client.call_tool("playwright_navigate", {
"url": "https://example.com",
"browserType": "chromium",
"waitUntil": "load",
"headless": True
})
# 2. Extract page content
async with playwright_client:
text_result = await playwright_client.call_tool(
"playwright_get_visible_text", {}
)
# 3. Capture screenshot
async with playwright_client:
await playwright_client.call_tool("playwright_screenshot", {
"name": "page_screenshot",
"savePng": True
})
# 4. Cleanup
async with playwright_client:
await playwright_client.call_tool("playwright_close", {})
```
### Isolated Browser Instances
Both Playwright and Chrome DevTools support isolated instances for parallel requests:
**Playwright**: Automatically creates separate browser contexts per request
**Chrome DevTools**: Use `--isolated` flag in configuration:
```json
{
"chrome-devtools": {
"args": ["chrome-devtools-mcp@latest", "--headless=true", "--isolated"]
}
}
```
## Manual Install Support
### Overview
Added support for `manual_install_command` field in MCP server configuration to handle custom installation scenarios.
### Installation Priority
```
1. manual_install_command (if present)
2. npm packages (npx command)
3. uv packages (uvx command)
```
### Usage Examples
#### Install from URL (wheel file)
```json
{
"custom_server": {
"type": "stdio",
"command": "uvx",
"args": ["custom-server", "--option", "value"],
"manual_install_command": "uv tool install https://example.com/package.whl",
"description": "Custom package from URL"
}
}
```
#### Install from Git Repository
```json
{
"git_mcp": {
"type": "stdio",
"command": "uvx",
"args": ["git-mcp-server"],
"manual_install_command": "uv tool install git+https://github.com/user/mcp.git",
"description": "Custom MCP from Git"
}
}
```
#### Install with pip
```json
{
"special_mcp": {
"type": "stdio",
"command": "python",
"args": ["-m", "special_mcp"],
"manual_install_command": "pip install special-mcp-package==1.2.3",
"description": "MCP requiring specific pip install"
}
}
```
#### Custom Build Script
```json
{
"build_mcp": {
"type": "stdio",
"command": "node",
"args": ["dist/server.js"],
"manual_install_command": "cd /tmp && git clone https://github.com/user/mcp.git && cd mcp && npm install && npm run build",
"description": "MCP requiring build from source"
}
}
```
### Behavior
When manual_install_command is present:
1. β
Executes the manual install command
2. β
Logs output (first 200 chars)
3. β
Continues on failure (will retry at runtime)
4. β
Skips standard npm/uv package extraction for that server
5. β
Uses shell=True for complex commands
6. β
10-minute timeout for complex installations
### Benefits
β
**Flexibility**: Install packages from any source (URL, Git, local path)
β
**Custom Commands**: Run complex installation sequences
β
**System Dependencies**: Install system packages before Python/Node packages
β
**Build Scripts**: Compile from source if needed
β
**Version Control**: Pin to specific commits, tags, or releases
### Security Considerations
β οΈ **Shell Injection Risk**: Commands are executed with `shell=True`
- Only use manual_install_command from trusted sources
- Validate commands before adding to .env
- Docker build-time execution provides some isolation
## License
MIT License - See LICENSE file for details
## Contributing
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests
5. Submit a pull request
## Support
- **Quick Start**: [QUICKSTART.md](QUICKSTART.md)
- **Project Instructions**: [CLAUDE.md](CLAUDE.md)
- **Configuration**: [.env](.env)
- **Commands**: Run `make help`
- **GitHub Issues**: <repository-url>/issues
## Roadmap
### Current Features (Phase 1)
- β
Dynamic Python code execution
- β
Multiple MCP client orchestration (9 clients)
- β
Docker-based deployment
- β
HTTP transport support
- β
Automatic MCP tools validation
- β
Runtime package installation
- β
Manual installation support
- β
Browser automation (Playwright + Chrome DevTools)
- β
Isolated browser instances
- β
Dynamic browser version detection
- β
27 pre-configured servers
- β
Comprehensive logging
- β
Health checks
### Phase 2 Features
- [ ] WebSocket support for real-time communication
- [ ] Streaming execution logs
- [ ] Persistent execution sessions
- [ ] Tool versioning
- [ ] OAuth2 authentication
- [ ] Metrics dashboard
- [ ] Redis caching layer
- [ ] Load balancing support
### Advanced Features
- [ ] Code analysis before execution
- [ ] Quota management
- [ ] Audit logging
- [ ] Signature verification
- [ ] Worker pools
- [ ] Result streaming
- [ ] Pre-warming optimization
- [ ] Automatic package version management
- [ ] Custom validation rules
- [ ] Plugin system for custom servers
- [ ] WebAssembly runtime integration (original goal)
- [ ] RustPython support with FastMCP
- [ ] Virtual file system for dynamic scripts
---
**Built with β€οΈ using FastMCP, Docker, Playwright, and Python**
**Current Status**: β
Production Ready | All Systems Operational | 9/9 Clients Initialized
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues