Skip to main content
Glama
OxSci-AI
by OxSci-AI
tool_template.py10.7 kB
""" Tool Template - Comprehensive MCP Tool Example This is a complete template file for creating new MCP tools using the @oma_tool decorator. Copy this file and modify it to create your own tools. Key Concepts: - Request/Response models with proper validation using Pydantic - Context injection for accessing shared data across tool chains - Proper parameter types (required, optional, default values) - Error handling and validation - Tool chaining through context.set_shared_data / get_shared_data When to use enable=False: - During development (tool not ready for production) - For internal/debug tools that shouldn't be exposed to agents - Tools that are disabled but kept for reference Effect of enable=False: - Tool will NOT appear in /tools/discover (agent tool discovery) - Tool WILL appear in /tools/list (complete tool inventory) - Tool can still be executed directly via POST /tools/{tool_name} """ from typing import Optional, List from fastapi import Depends, HTTPException from pydantic import BaseModel, Field, field_validator from oxsci_oma_mcp import ( oma_tool, require_context, IMCPToolContext, IDataServiceClient, require_data_service, ) # ==================== Request/Response Models ==================== class ToolTemplateRequest(BaseModel): """ Request model for tool_template Demonstrates different parameter types and validation patterns. """ # Required parameter - must be provided input_text: str = Field( ..., # ... means required description="Input text to process", min_length=1, max_length=10000, ) # Optional parameter with default value uppercase: bool = Field( default=False, description="Convert to uppercase if True", ) # Optional parameter that can be None prefix: Optional[str] = Field( default=None, description="Optional prefix to add to result", max_length=100, ) # Parameter with constraints repeat_count: int = Field( default=1, description="Number of times to repeat the text", ge=1, # greater than or equal to 1 le=10, # less than or equal to 10 ) # List parameter with default empty list tags: List[str] = Field( default_factory=list, description="Optional tags to attach (max 20 items)", ) # Custom validator example (Pydantic V2 style) @field_validator("input_text") @classmethod def validate_input_text(cls, v: str) -> str: """Custom validation for input_text""" if not v or v.isspace(): raise ValueError("Input text cannot be empty or whitespace only") return v.strip() class ToolTemplateResponse(BaseModel): """ Response model for tool_template Always define clear response structure for better API documentation. """ result: str = Field( ..., description="Processed result text", ) metadata: dict = Field( default_factory=dict, description="Additional metadata about the processing", ) length: int = Field( ..., description="Length of the result text", ) processing_info: dict = Field( default_factory=dict, description="Information about how the text was processed", ) # ==================== Tool Definition ==================== @oma_tool( description="Template tool demonstrating comprehensive MCP tool patterns and best practices", version="1.0.0", enable=False, # Set to False during development or to disable from agent discovery ) async def tool_template( request: ToolTemplateRequest, context: IMCPToolContext = Depends(require_context), data_service: IDataServiceClient = Depends(require_data_service), ) -> ToolTemplateResponse: """ Comprehensive tool template implementation. This tool demonstrates: - Different parameter types (required, optional, with defaults) - Context usage for tool chaining - DataServiceClient usage for simplified data service calls - Proper error handling - Metadata tracking - Business logic implementation Context Usage: - Use context.get_shared_data(key, default) to read data from previous tools - Use context.set_shared_data(key, value) to pass data to next tools - Context is shared across all tools in a single execution chain DataServiceClient Usage: - Injected via Depends(require_data_service) - one instance per request - Pre-configured with DATA_SERVICE_URL and authentication - Simply call: await data_service.call(method="GET", endpoint="/path") - No need to manually create ServiceClient or pass user_request Args: request: Tool request parameters (automatically validated by Pydantic) context: MCP context for accessing shared data across tool chains data_service: Data service client for calling data service APIs Returns: ToolTemplateResponse with processed result and metadata Raises: HTTPException: If processing fails (e.g., validation errors, business logic errors) Example Usage: POST /tools/tool_template { "arguments": { "input_text": "Hello World", "uppercase": true, "prefix": ">> ", "repeat_count": 2, "tags": ["example", "demo"] }, "context": { "user_id": "user123", "session_id": "session456" } } """ # ==================== 1. Access Context Data ==================== # Get data from previous tools in the chain (if any) # Always provide a default value to handle the case when the key doesn't exist user_id = context.get_shared_data("user_id", "anonymous") previous_result = context.get_shared_data("last_result", None) execution_count = context.get_shared_data("execution_count", 0) # ==================== 2. Business Logic / Processing ==================== # Start with input text (already validated by Pydantic) result = request.input_text # Apply uppercase transformation if requested if request.uppercase: result = result.upper() # Add prefix if provided if request.prefix: result = f"{request.prefix}{result}" # Repeat the text if repeat_count > 1 if request.repeat_count > 1: result = " ".join([result] * request.repeat_count) # Example: If there was a previous result in the chain, append it if previous_result: result = f"{result}\n[Previous: {previous_result}]" # ==================== Example: DataServiceClient Usage ==================== # Uncomment the following code to see how to use DataServiceClient # # # Simple GET request # document_data = await data_service.call( # method="GET", # endpoint="/documents/123" # ) # # # GET with query parameters # sections = await data_service.call( # method="GET", # endpoint="/article_structured_contents/overviews/{overview_id}/sections", # path_params={"overview_id": "some_id"}, # query_params={"user_id": user_id} if user_id else {} # ) # # # POST with JSON body # created = await data_service.call( # method="POST", # endpoint="/documents", # json_data={"title": "New Document", "content": result} # ) # # Benefits of DataServiceClient: # ✅ No need to create ServiceClient manually # ✅ No need to pass target_service_url (auto from config.DATA_SERVICE_URL) # ✅ No need to pass user_request (auto forwarded for authentication) # ✅ One instance per request (reused across multiple calls) # ✅ Clean and simple API # ==================== 3. Build Metadata ==================== metadata = { "processed_by": user_id, "tags": request.tags, "uppercase_applied": request.uppercase, "prefix_applied": request.prefix is not None, "repeat_count": request.repeat_count, "has_previous_result": previous_result is not None, } processing_info = { "original_text": request.input_text, "original_length": len(request.input_text), "result_length": len(result), "transformations": [], } # Track what transformations were applied if request.uppercase: processing_info["transformations"].append("uppercase") if request.prefix: processing_info["transformations"].append(f"prefix: {request.prefix}") if request.repeat_count > 1: processing_info["transformations"].append(f"repeated {request.repeat_count}x") # ==================== 4. Update Context for Next Tools ==================== # Store results that might be useful for subsequent tools in the chain context.set_shared_data("last_result", result) context.set_shared_data("last_length", len(result)) context.set_shared_data("execution_count", execution_count + 1) context.set_shared_data("last_tool", "tool_template") # Store tags for potential filtering by later tools if request.tags: context.set_shared_data("last_tags", request.tags) # ==================== 5. Return Response ==================== return ToolTemplateResponse( result=result, metadata=metadata, length=len(result), processing_info=processing_info, ) # ==================== Additional Examples ==================== """ Example 1: Simple text processing POST /tools/tool_template { "arguments": { "input_text": "hello world" }, "context": {} } Response: {"result": "hello world", "length": 11, ...} Example 2: Complex transformation POST /tools/tool_template { "arguments": { "input_text": "test", "uppercase": true, "prefix": ">> ", "repeat_count": 3, "tags": ["important", "demo"] }, "context": { "user_id": "user123" } } Response: {"result": ">> TEST >> TEST >> TEST", "length": 23, ...} Example 3: Tool chaining (this tool can read data from previous tools) If previous tool stored data in context, this tool can access it: - context.get_shared_data("last_result") - get previous result - context.get_shared_data("user_preferences") - get user settings - etc. Tool Discovery: - With enable=True: Tool appears in /tools/discover (agents can find it) - With enable=False: Tool does NOT appear in /tools/discover - Always appears in /tools/list regardless of enable flag - Can still be executed via POST /tools/tool_template regardless of enable flag """

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/OxSci-AI/oxsci-mcp-scaffold'

If you have feedback or need assistance with the MCP directory API, please join our Discord server