Spaces:
Sleeping
Sleeping
File size: 10,226 Bytes
27cde0c 54ec9cb 27cde0c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 | """Tool registry and testing endpoints."""
import logging
from typing import Any
from fastapi import APIRouter, HTTPException, status
from pydantic import BaseModel, Field
router = APIRouter(prefix="/tools")
logger = logging.getLogger(__name__)
class ToolParameter(BaseModel):
"""Parameter definition for a tool."""
name: str
type: str
description: str
required: bool = True
default: Any | None = None
class ToolDefinition(BaseModel):
"""Definition of a tool in the registry."""
name: str
description: str
category: str
parameters: list[ToolParameter]
returns: str
examples: list[dict[str, Any]] = Field(default_factory=list)
requires_browser: bool = False
cost_estimate: float = 0.0
class ToolRegistryResponse(BaseModel):
"""Response containing the tool registry."""
tools: list[ToolDefinition]
categories: list[str]
total_count: int
class ToolTestRequest(BaseModel):
"""Request to test a tool."""
tool_name: str
parameters: dict[str, Any] = Field(default_factory=dict)
dry_run: bool = True
class ToolTestResponse(BaseModel):
"""Response from tool testing."""
tool_name: str
success: bool
result: Any | None = None
error: str | None = None
execution_time_ms: float = 0.0
dry_run: bool
# Tool definitions (would be dynamically registered in production)
TOOL_DEFINITIONS: list[ToolDefinition] = [
ToolDefinition(
name="navigate_to",
description="Navigate the browser to a specified URL",
category="browser",
parameters=[
ToolParameter(name="url", type="string", description="URL to navigate to"),
ToolParameter(name="wait_for", type="string", description="CSS selector to wait for", required=False),
],
returns="NavigationResult with page info",
requires_browser=True,
cost_estimate=0.01,
),
ToolDefinition(
name="click_element",
description="Click on an element identified by selector",
category="browser",
parameters=[
ToolParameter(name="selector", type="string", description="CSS selector of element to click"),
],
returns="ClickResult with success status",
requires_browser=True,
cost_estimate=0.005,
),
ToolDefinition(
name="extract_text",
description="Extract text content from elements",
category="extraction",
parameters=[
ToolParameter(name="selector", type="string", description="CSS selector to extract from"),
ToolParameter(name="multiple", type="boolean", description="Extract from all matches", default=False),
],
returns="Extracted text or list of texts",
requires_browser=True,
cost_estimate=0.002,
),
ToolDefinition(
name="extract_attribute",
description="Extract attribute value from element",
category="extraction",
parameters=[
ToolParameter(name="selector", type="string", description="CSS selector"),
ToolParameter(name="attribute", type="string", description="Attribute name to extract"),
],
returns="Attribute value",
requires_browser=True,
cost_estimate=0.002,
),
ToolDefinition(
name="search_engine",
description="Perform a search using a search engine",
category="search",
parameters=[
ToolParameter(name="query", type="string", description="Search query"),
ToolParameter(name="engine", type="string", description="Search engine", default="google"),
ToolParameter(name="num_results", type="integer", description="Number of results", default=10),
],
returns="List of search results",
cost_estimate=0.05,
),
ToolDefinition(
name="fill_form",
description="Fill a form field with a value",
category="browser",
parameters=[
ToolParameter(name="selector", type="string", description="CSS selector of form field"),
ToolParameter(name="value", type="string", description="Value to fill"),
],
returns="FillResult with success status",
requires_browser=True,
cost_estimate=0.005,
),
ToolDefinition(
name="screenshot",
description="Take a screenshot of the current page",
category="browser",
parameters=[
ToolParameter(name="full_page", type="boolean", description="Capture full page", default=False),
],
returns="Base64 encoded screenshot",
requires_browser=True,
cost_estimate=0.01,
),
ToolDefinition(
name="get_page_html",
description="Get the full HTML content of the current page",
category="extraction",
parameters=[],
returns="HTML string",
requires_browser=True,
cost_estimate=0.001,
),
ToolDefinition(
name="wait_for_selector",
description="Wait for an element to appear on the page",
category="browser",
parameters=[
ToolParameter(name="selector", type="string", description="CSS selector to wait for"),
ToolParameter(name="timeout_ms", type="integer", description="Timeout in milliseconds", default=30000),
],
returns="Boolean indicating if element appeared",
requires_browser=True,
cost_estimate=0.001,
),
ToolDefinition(
name="scroll_to",
description="Scroll to a position or element",
category="browser",
parameters=[
ToolParameter(name="selector", type="string", description="CSS selector", required=False),
ToolParameter(name="position", type="string", description="Position: top, bottom, or pixel value", required=False),
],
returns="ScrollResult",
requires_browser=True,
cost_estimate=0.001,
),
]
@router.get(
"/registry",
response_model=ToolRegistryResponse,
status_code=status.HTTP_200_OK,
summary="Get tool registry",
description="Get all available tools in the registry",
)
async def get_tool_registry(category: str | None = None) -> ToolRegistryResponse:
"""
Get the tool registry with all available tools.
Args:
category: Optional filter by category.
Returns:
ToolRegistryResponse: List of available tools.
"""
tools = TOOL_DEFINITIONS
if category:
tools = [t for t in tools if t.category == category]
categories = list(set(t.category for t in TOOL_DEFINITIONS))
return ToolRegistryResponse(
tools=tools,
categories=categories,
total_count=len(tools),
)
@router.get(
"/registry/{tool_name}",
response_model=ToolDefinition,
status_code=status.HTTP_200_OK,
summary="Get tool details",
description="Get details of a specific tool",
)
async def get_tool_details(tool_name: str) -> ToolDefinition:
"""
Get details of a specific tool.
Args:
tool_name: Name of the tool.
Returns:
ToolDefinition: Tool details.
"""
for tool in TOOL_DEFINITIONS:
if tool.name == tool_name:
return tool
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Tool '{tool_name}' not found",
)
@router.post(
"/test",
response_model=ToolTestResponse,
status_code=status.HTTP_200_OK,
summary="Test a tool",
description="Test a tool with provided parameters",
)
async def test_tool(request: ToolTestRequest) -> ToolTestResponse:
"""
Test a tool execution.
Args:
request: Tool test request.
Returns:
ToolTestResponse: Result of tool test.
"""
import time
start_time = time.time()
logger.info(f"Testing tool '{request.tool_name}' with dry_run={request.dry_run}")
# Find the tool
tool = None
for t in TOOL_DEFINITIONS:
if t.name == request.tool_name:
tool = t
break
if not tool:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Tool '{request.tool_name}' not found",
)
try:
# Validate required parameters
for param in tool.parameters:
if param.required and param.name not in request.parameters:
raise ValueError(f"Missing required parameter: {param.name}")
if request.dry_run:
# Return mock result for dry run
result = {
"status": "dry_run",
"tool": request.tool_name,
"parameters": request.parameters,
"would_require_browser": tool.requires_browser,
}
else:
# Actually execute the tool (placeholder)
from app.tools.registry import MCPToolRegistry
registry = MCPToolRegistry()
result = await registry.execute_tool(request.tool_name, request.parameters)
execution_time = (time.time() - start_time) * 1000
return ToolTestResponse(
tool_name=request.tool_name,
success=True,
result=result,
execution_time_ms=execution_time,
dry_run=request.dry_run,
)
except Exception as e:
execution_time = (time.time() - start_time) * 1000
logger.error(f"Tool test failed: {e}")
return ToolTestResponse(
tool_name=request.tool_name,
success=False,
error=str(e),
execution_time_ms=execution_time,
dry_run=request.dry_run,
)
@router.get(
"/categories",
status_code=status.HTTP_200_OK,
summary="Get tool categories",
description="Get all tool categories",
)
async def get_categories() -> dict[str, Any]:
"""
Get all tool categories.
Returns:
Dict with category information.
"""
categories = {}
for tool in TOOL_DEFINITIONS:
if tool.category not in categories:
categories[tool.category] = []
categories[tool.category].append(tool.name)
return {"categories": categories}
|