add register_tool for mcp_server

This commit is contained in:
liangxinbing 2025-03-19 13:26:07 +08:00
parent 0952caf526
commit 8f3a60f52b

View File

@ -1,21 +1,22 @@
import argparse
import asyncio
import atexit
import json
import logging
import os
import sys
from inspect import Parameter, Signature
from typing import Any, Optional
from mcp.server.fastmcp import FastMCP
# Add current directory to Python path
# Add directories to Python path
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(current_dir)
root_dir = os.path.dirname(parent_dir)
sys.path.insert(0, parent_dir)
sys.path.insert(0, current_dir)
# Add root directory to Python path
root_dir = os.path.dirname(parent_dir)
sys.path.insert(0, root_dir)
# Configure logging
@ -24,10 +25,12 @@ logging.basicConfig(
)
logger = logging.getLogger("mcp-server")
from app.tool.base import BaseTool
from app.tool.bash import Bash
# Import OpenManus tools
from app.tool.browser_use_tool import BrowserUseTool
from app.tool.file_saver import FileSaver
from app.tool.python_execute import PythonExecute
from app.tool.str_replace_editor import StrReplaceEditor
from app.tool.terminate import Terminate
@ -35,109 +38,119 @@ from app.tool.terminate import Terminate
openmanus = FastMCP("openmanus")
# Initialize tool instances
bash_tool = Bash()
browser_tool = BrowserUseTool()
python_execute_tool = PythonExecute()
file_saver_tool = FileSaver()
str_replace_editor_tool = StrReplaceEditor()
terminate_tool = Terminate()
# Browser tool
@openmanus.tool()
async def browser_use(
action: str,
url: str = None,
index: int = None,
text: str = None,
script: str = None,
scroll_amount: int = None,
tab_id: int = None,
) -> str:
"""Execute various browser operations.
def register_tool(tool: BaseTool, method_name: Optional[str] = None) -> None:
"""Register a tool with the OpenManus server.
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
tool: The tool instance to register
method_name: Optional custom name for the tool method
"""
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())
tool_name = method_name or tool.name
# Get tool information using its own methods
tool_param = tool.to_param()
tool_function = tool_param["function"]
# Define the async function to be registered
async def tool_method(**kwargs):
logger.info(f"Executing {tool_name}: {kwargs}")
result = await tool.execute(**kwargs)
# Handle different types of results
if hasattr(result, "model_dump"):
return json.dumps(result.model_dump())
elif isinstance(result, dict):
return json.dumps(result)
return result
# Set the function name
tool_method.__name__ = tool_name
# Set the function docstring
description = tool_function.get("description", "")
param_props = tool_function.get("parameters", {}).get("properties", {})
required_params = tool_function.get("parameters", {}).get("required", [])
# Build a proper docstring with parameter descriptions
docstring = description
# Create parameter list separately for the signature
parameters = []
# Add parameters to both docstring and signature
if param_props:
docstring += "\n\nParameters:\n"
for param_name, param_details in param_props.items():
required_str = (
"(required)" if param_name in required_params else "(optional)"
)
param_type = param_details.get("type", "any")
param_desc = param_details.get("description", "")
# Add to docstring
docstring += (
f" {param_name} ({param_type}) {required_str}: {param_desc}\n"
)
# Create parameter for signature
default = Parameter.empty if param_name in required_params else None
annotation = Any
# Try to get a better type annotation based on the parameter type
if param_type == "string":
annotation = str
elif param_type == "integer":
annotation = int
elif param_type == "number":
annotation = float
elif param_type == "boolean":
annotation = bool
elif param_type == "object":
annotation = dict
elif param_type == "array":
annotation = list
# Create parameter
param = Parameter(
name=param_name,
kind=Parameter.KEYWORD_ONLY,
default=default,
annotation=annotation,
)
parameters.append(param)
# Store the full docstring
tool_method.__doc__ = docstring
# Create and set the signature
tool_method.__signature__ = Signature(parameters=parameters)
# Store the complete parameter schema for tools that need to access it programmatically
tool_method._parameter_schema = {
param_name: {
"description": param_details.get("description", ""),
"type": param_details.get("type", "any"),
"required": param_name in required_params,
}
for param_name, param_details in param_props.items()
}
# Register the tool with FastMCP
openmanus.tool()(tool_method)
logger.info(f"Registered tool: {tool_name}")
@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())
# 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
# Register all tools
register_tool(bash_tool)
register_tool(browser_tool)
register_tool(str_replace_editor_tool)
register_tool(terminate_tool)
# Clean up resources
@ -148,9 +161,6 @@ async def cleanup():
# Register cleanup function
import atexit
atexit.register(lambda: asyncio.run(cleanup()))
@ -168,6 +178,5 @@ def parse_args():
if __name__ == "__main__":
args = parse_args()
logger.info("Starting OpenManus server (stdio mode)")
openmanus.run(transport="stdio")