feat: init MCP server

This commit is contained in:
gantnocap 2025-03-11 02:08:58 +08:00
parent 576090439b
commit e08a6b313a
4 changed files with 506 additions and 0 deletions

152
openmanus_server/README.md Normal file
View File

@ -0,0 +1,152 @@
# OpenManus-server 🤖
This project provides a server based on [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) that exposes **OpenManus** tool functionalities as standardized APIs.
## ✨ Features
This MCP server provides access to the following OpenManus tools:
1. **Browser Automation** 🌐 - Navigate webpages, click elements, input text, and more
2. **Google Search** 🔍 - Execute searches and retrieve result links
3. **Python Code Execution** 🐍 - Run Python code in a secure environment
4. **File Saving** 💾 - Save content to local files
5. **Termination Control** 🛑 - Control program execution flow
## 🚀 Installation
### Prerequisites
- Python 3.10+
- OpenManus project dependencies
### Installation Steps
1. First, install the OpenManus project:
```bash
git clone https://github.com/mannaandpoem/OpenManus.git
cd OpenManus
```
2. Install dependencies:
```bash
# Using uv (recommended)
curl -LsSf https://astral.sh/uv/install.sh | sh
uv venv
source .venv/bin/activate # Unix/macOS
# or .venv\Scripts\activate # Windows
uv sync
```
3. Install MCP dependencies:
```bash
pip install mcp-python
```
## 📖 Usage
### Starting the MCP Server
The server supports two communication modes: stdio and HTTP.
#### stdio mode (default)
```bash
python mcp_server.py
```
#### HTTP mode
```bash
python mcp_server.py --transport http --host 127.0.0.1 --port 8000
```
### Command Line Arguments
- `--transport`: Communication method, choose "stdio" or "http" (default: stdio)
- `--host`: HTTP server host address (default: 127.0.0.1)
- `--port`: HTTP server port (default: 8000)
## 💻 Client Example
Check out `mcp_client_example.py` to learn how to connect to the server and call tools using the MCP client.
### Running the Client Example
1. First, start the server in HTTP mode:
```bash
python mcp_server.py --transport http
```
2. In another terminal, run the client example:
```bash
python mcp_client_example.py
```
## 🤖 LLM Integration
The MCP server can be integrated with LLMs that support tool calling, such as Claude 3 Opus/Sonnet/Haiku.
### Example with Claude
```python
import anthropic
from mcp.client import MCPClient
# Initialize Claude client
client = anthropic.Anthropic(api_key="your_api_key")
# Connect to MCP server
mcp_client = await MCPClient.create_http("http://localhost:8000")
# Get tool definitions
tools = await mcp_client.list_tools()
tool_definitions = [tool.to_dict() for tool in tools]
# Create Claude message
message = client.messages.create(
model="claude-3-opus-20240229",
max_tokens=1000,
temperature=0,
system="You are a helpful assistant that can use tools to help users.",
messages=[{"role": "user", "content": "Search for Model Context Protocol and summarize the top 3 results"}],
tools=tool_definitions
)
# Handle tool calls
for tool_call in message.content:
if hasattr(tool_call, "tool_use"):
tool_name = tool_call.tool_use.name
tool_params = tool_call.tool_use.input
# Call MCP tool
result = await mcp_client.invoke_tool(tool_name, tool_params)
# Send results back to Claude
message = client.messages.create(
model="claude-3-opus-20240229",
max_tokens=1000,
temperature=0,
system="You are a helpful assistant that can use tools to help users.",
messages=[
{"role": "user", "content": "Search for Model Context Protocol and summarize the top 3 results"},
{"role": "assistant", "content": [tool_call]},
{"role": "user", "content": [{"type": "tool_result", "tool_use_id": tool_call.tool_use.id, "content": result}]}
],
tools=tool_definitions
)
```
## 🔒 Security Considerations
- By default, the HTTP server only listens on localhost (127.0.0.1) and is not exposed externally
- When using in production, ensure proper authentication and authorization mechanisms are in place
- The Python execution tool has timeout limits to prevent long-running code
## 📄 License
Same license as the OpenManus project

View File

@ -0,0 +1,3 @@
# Core dependencies
mcp
httpx>=0.27.0

View File

@ -0,0 +1,174 @@
import asyncio
from typing import Optional
from contextlib import AsyncExitStack
import os
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
class OpenManusClient:
def __init__(self):
# Initialize session and client objects
self.session: Optional[ClientSession] = None
self.exit_stack = AsyncExitStack()
self.stdio = None
self.write = None
async def connect_to_server(self, server_script_path: str):
"""Connect to an MCP server via stdio
Args:
server_script_path: Path to the server script
"""
if not server_script_path.endswith('.py'):
raise ValueError("Server script must be a .py file")
# Get the current directory to add to PYTHONPATH
current_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.dirname(current_dir) # Get parent directory
# Prepare environment variables
env = os.environ.copy() # Copy current environment
# Add current directory and project root to PYTHONPATH
path_separator = ';' if os.name == 'nt' else ':' # Use ; for Windows, : for Unix
if 'PYTHONPATH' in env:
env['PYTHONPATH'] = f"{current_dir}{path_separator}{project_root}{path_separator}{env['PYTHONPATH']}"
else:
env['PYTHONPATH'] = f"{current_dir}{path_separator}{project_root}"
server_params = StdioServerParameters(
command="python",
args=[server_script_path],
env=env
)
stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
self.stdio, self.write = stdio_transport
self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))
await self.session.initialize()
# List available tools
response = await self.session.list_tools()
tools = response.tools
print("\nConnected to server with tools:", [tool.name for tool in tools])
return tools
async def run_examples(self):
"""Run example tool calls to demonstrate functionality"""
try:
print("\nExample 1: Google Search")
search_result = await self.session.call_tool(
"google_search",
{"query": "Model Context Protocol", "num_results": 5}
)
print(f"Search results: {search_result.content}")
print("\nExample 2: Python Code Execution")
code = """
import math
result = 0
for i in range(1, 10):
result += math.sqrt(i)
print(f"Calculation result: {result}")
"""
python_result = await self.session.call_tool(
"python_execute",
{"code": code, "timeout": 3}
)
print(f"Python execution result: {python_result.content}")
print("\nExample 3: File Saving")
file_result = await self.session.call_tool(
"file_saver",
{
"content": "This is a test file content saved through MCP",
"file_path": "mcp_test_file.txt"
}
)
print(f"File save result: {file_result.content}")
print("\nExample 4: Browser Usage")
# Navigate to webpage
browser_result = await self.session.call_tool(
"browser_use",
{"action": "navigate", "url": "https://www.example.com"}
)
print(f"Browser navigation result: {browser_result.content}")
# Get browser state
state_result = await self.session.call_tool(
"get_browser_state",
{}
)
print(f"Browser state: {state_result.content}")
except Exception as e:
print(f"\nError during example execution: {str(e)}")
async def chat_loop(self):
"""Run an interactive chat loop for testing tools"""
print("\nOpenManus MCP Client Started!")
print("Type your commands or 'quit' to exit.")
print("Available commands: google_search, python_execute, file_saver, browser_use, get_browser_state")
while True:
try:
command = input("\nCommand: ").strip()
if command.lower() == 'quit':
break
# Parse command and parameters
parts = command.split(maxsplit=1)
if len(parts) == 0:
continue
tool_name = parts[0]
tool_args = {}
if len(parts) > 1:
try:
tool_args = eval(parts[1]) # Convert string to dict
except:
print("Invalid arguments format. Please provide a valid Python dictionary.")
continue
result = await self.session.call_tool(tool_name, tool_args)
print("\nResult:", result.content)
except Exception as e:
print(f"\nError: {str(e)}")
async def cleanup(self):
"""Clean up resources"""
if self.session:
await self.session.close()
await self.exit_stack.aclose()
print("\nClosed MCP client connection")
async def main():
"""Main entry point"""
import sys
if len(sys.argv) < 2:
print("Usage: python openmanus_client_example.py <path_to_server_script>")
print("Example: python openmanus_client_example.py ../mcp_server.py")
sys.exit(1)
client = OpenManusClient()
try:
await client.connect_to_server(server_script_path=sys.argv[1])
# Run examples first
await client.run_examples()
# Then start interactive chat loop
await client.chat_loop()
except Exception as e:
print(f"Error: {str(e)}")
finally:
await client.cleanup()
if __name__ == "__main__":
asyncio.run(main())

View File

@ -0,0 +1,177 @@
from typing import Optional
import asyncio
import json
import argparse
from mcp.server.fastmcp import FastMCP
import logging
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger("mcp-server")
# Import OpenManus tools
from app.tool.browser_use_tool import BrowserUseTool
from app.tool.google_search import GoogleSearch
from app.tool.python_execute import PythonExecute
from app.tool.file_saver import FileSaver
from app.tool.terminate import Terminate
# Initialize FastMCP server
openmanus = FastMCP("openmanus")
# Initialize tool instances
browser_tool = BrowserUseTool()
google_search_tool = GoogleSearch()
python_execute_tool = PythonExecute()
file_saver_tool = FileSaver()
terminate_tool = Terminate()
# Browser tool
@openmanus.tool()
async def browser_use(
action: str,
url: Optional[str] = None,
index: Optional[int] = None,
text: Optional[str] = None,
script: Optional[str] = None,
scroll_amount: Optional[int] = None,
tab_id: Optional[int] = None,
) -> str:
"""Execute various browser operations.
Args:
action: The browser operation to execute, possible values include:
- navigate: Navigate to specified URL
- click: Click on an element on the page
- input_text: Input text into a text field
- screenshot: Take a screenshot of the current page
- get_html: Get HTML of the current page
- get_text: Get text content of the current page
- execute_js: Execute JavaScript code
- scroll: Scroll the page
- switch_tab: Switch to specified tab
- new_tab: Open new tab
- close_tab: Close current tab
- refresh: Refresh current page
url: URL for 'navigate' or 'new_tab' operations
index: Element index for 'click' or 'input_text' operations
text: Text for 'input_text' operation
script: JavaScript code for 'execute_js' operation
scroll_amount: Scroll pixels for 'scroll' operation (positive for down, negative for up)
tab_id: Tab ID for 'switch_tab' operation
"""
logger.info(f"Executing browser operation: {action}")
result = await browser_tool.execute(
action=action,
url=url,
index=index,
text=text,
script=script,
scroll_amount=scroll_amount,
tab_id=tab_id
)
return json.dumps(result.model_dump())
@openmanus.tool()
async def get_browser_state() -> str:
"""Get current browser state, including URL, title, tabs and interactive elements."""
logger.info("Getting browser state")
result = await browser_tool.get_current_state()
return json.dumps(result.model_dump())
# Google search tool
@openmanus.tool()
async def google_search(query: str, num_results: int = 10) -> str:
"""Execute Google search and return list of relevant links.
Args:
query: Search query
num_results: Number of results to return (default is 10)
"""
logger.info(f"Executing Google search: {query}")
results = await google_search_tool.execute(query=query, num_results=num_results)
return json.dumps(results)
# Python execution tool
@openmanus.tool()
async def python_execute(code: str, timeout: int = 5) -> str:
"""Execute Python code and return results.
Args:
code: Python code to execute
timeout: Execution timeout in seconds
"""
logger.info("Executing Python code")
result = await python_execute_tool.execute(code=code, timeout=timeout)
return json.dumps(result)
# File saver tool
@openmanus.tool()
async def file_saver(content: str, file_path: str, mode: str = "w") -> str:
"""Save content to local file.
Args:
content: Content to save
file_path: File path
mode: File open mode (default is 'w')
"""
logger.info(f"Saving file: {file_path}")
result = await file_saver_tool.execute(content=content, file_path=file_path, mode=mode)
return result
# Terminate tool
@openmanus.tool()
async def terminate(status: str) -> str:
"""Terminate program execution.
Args:
status: Termination status, can be 'success' or 'failure'
"""
logger.info(f"Terminating execution: {status}")
result = await terminate_tool.execute(status=status)
return result
# Clean up resources
async def cleanup():
"""Clean up all tool resources"""
logger.info("Cleaning up resources")
await browser_tool.cleanup()
# Register cleanup function
import atexit
atexit.register(lambda: asyncio.run(cleanup()))
def parse_args():
"""Parse command line arguments"""
parser = argparse.ArgumentParser(description="OpenManus MCP Server")
parser.add_argument(
"--transport",
choices=["stdio", "http"],
default="stdio",
help="Communication method: stdio or http (default: stdio)"
)
parser.add_argument(
"--host",
default="127.0.0.1",
help="HTTP server host (default: 127.0.0.1)"
)
parser.add_argument(
"--port",
type=int,
default=8000,
help="HTTP server port (default: 8000)"
)
return parser.parse_args()
if __name__ == "__main__":
args = parse_args()
if args.transport == "stdio":
logger.info("Starting OpenManus server (stdio mode)")
openmanus.run(transport="stdio")
else:
logger.info(f"Starting OpenManus server (HTTP mode) at {args.host}:{args.port}")
openmanus.run(transport="http", host=args.host, port=args.port)