Spaces:
Runtime error
Runtime error
| import gradio as gr | |
| import httpx | |
| import json | |
| import re | |
| BASE_URL = "https://nelsonandre.outsystemscloud.com/MCPServer/rest/MCP" | |
| MAX_STRING_LENGTH = 500 | |
| DATE_PATTERN = re.compile(r"^\d{4}-\d{2}-\d{2}$") | |
| def validate_employee_id(employee_id) -> int: | |
| """Validate and convert employee ID to a positive integer.""" | |
| try: | |
| eid = int(employee_id) | |
| if eid <= 0: | |
| raise ValueError("Employee ID must be a positive integer") | |
| return eid | |
| except (TypeError, ValueError) as e: | |
| raise ValueError(f"Invalid employee ID: {e}") | |
| def validate_string(value: str, field_name: str, required: bool = False, max_length: int = MAX_STRING_LENGTH) -> str: | |
| """Validate and sanitize string input.""" | |
| if value is None: | |
| value = "" | |
| value = str(value).strip() | |
| if required and not value: | |
| raise ValueError(f"{field_name} is required") | |
| if len(value) > max_length: | |
| raise ValueError(f"{field_name} exceeds maximum length of {max_length}") | |
| return value | |
| def validate_date(value: str, field_name: str) -> str: | |
| """Validate date format (YYYY-MM-DD).""" | |
| if not value: | |
| return "" | |
| if not DATE_PATTERN.match(value): | |
| raise ValueError(f"{field_name} must be in YYYY-MM-DD format") | |
| return value | |
| def validate_nif(value) -> int: | |
| """Validate NIF as a non-negative integer.""" | |
| try: | |
| nif = int(value) if value else 0 | |
| if nif < 0: | |
| raise ValueError("NIF cannot be negative") | |
| return nif | |
| except (TypeError, ValueError): | |
| raise ValueError("NIF must be a valid integer") | |
| async def get_employees() -> list[dict]: | |
| """ | |
| Return a list of employees by calling an external web service. | |
| Returns: | |
| list[dict]: Response from the web service with columns: | |
| Name (str): Name of the employee (required) | |
| NIF (int): Tax identification number (defaults to 0) | |
| DateOfBirth (str): Date of birth in YYYY-MM-DD format (defaults to empty string) | |
| Address (str): Employee's address (defaults to empty string) | |
| CreatedOn (date time): Date and time when the employee was created (defaults to empty string) | |
| Phone (str): Employee's phone number (defaults to empty string) | |
| """ | |
| async with httpx.AsyncClient() as client: | |
| try: | |
| response = await client.get(f"{BASE_URL}/GetEmployees") | |
| response.raise_for_status() | |
| return response.json() | |
| except httpx.HTTPStatusError as e: | |
| raise Exception(f"Failed to fetch employees (HTTP {e.response.status_code})") | |
| except Exception: | |
| raise Exception("Failed to connect to employee service") | |
| async def get_employee_by_id(employee_id: int) -> dict: | |
| """ | |
| Return a specific employee by ID. | |
| Args: | |
| employee_id (int): The unique identifier of the employee | |
| Returns: | |
| dict: Employee data with fields: | |
| Id (int): Employee ID | |
| Name (str): Name of the employee | |
| NIF (int): Tax identification number | |
| DateOfBirth (str): Date of birth in YYYY-MM-DD format | |
| Address (str): Employee's address | |
| CreatedOn (datetime): Date and time when the employee was created | |
| Phone (str): Employee's phone number | |
| """ | |
| validated_id = validate_employee_id(employee_id) | |
| async with httpx.AsyncClient() as client: | |
| try: | |
| response = await client.get(f"{BASE_URL}/GetEmployeeBy", params={"EmployeeId": validated_id}) | |
| response.raise_for_status() | |
| return response.json() | |
| except httpx.HTTPStatusError as e: | |
| if e.response.status_code == 404: | |
| raise Exception("Employee not found") | |
| raise Exception(f"Failed to fetch employee (HTTP {e.response.status_code})") | |
| except ValueError as e: | |
| raise Exception(str(e)) | |
| except Exception: | |
| raise Exception("Failed to connect to employee service") | |
| async def create_employee(name: str, nif: int = 0, date_of_birth: str = "", address: str = "") -> str: | |
| """ | |
| Create a new employee. | |
| Args: | |
| name (str): Name of the employee (required) | |
| nif (int): Tax identification number (optional, defaults to 0) | |
| date_of_birth (str): Date of birth in YYYY-MM-DD format (optional) | |
| address (str): Employee's address (optional) | |
| Returns: | |
| str: Result message from the server | |
| """ | |
| # Validate all inputs | |
| validated_name = validate_string(name, "Name", required=True) | |
| validated_nif = validate_nif(nif) | |
| validated_dob = validate_date(date_of_birth, "Date of birth") | |
| validated_address = validate_string(address, "Address", required=False) | |
| async with httpx.AsyncClient() as client: | |
| try: | |
| payload = { | |
| "EmployeeName": validated_name, | |
| "NIF": validated_nif, | |
| "DateOfBirth": validated_dob, | |
| "Address": validated_address | |
| } | |
| response = await client.post(f"{BASE_URL}/EmployeeCreate", json=payload) | |
| response.raise_for_status() | |
| return response.text | |
| except httpx.HTTPStatusError as e: | |
| raise Exception(f"Failed to create employee (HTTP {e.response.status_code})") | |
| except ValueError as e: | |
| raise Exception(str(e)) | |
| except Exception: | |
| raise Exception("Failed to connect to employee service") | |
| # MCP Protocol handler | |
| async def handle_mcp_request(request_data: str) -> str: | |
| """Handle MCP protocol requests""" | |
| try: | |
| request = json.loads(request_data) | |
| if request.get("method") == "tools/list": | |
| # Return available tools | |
| return json.dumps({ | |
| "jsonrpc": "2.0", | |
| "id": request.get("id"), | |
| "result": { | |
| "tools": [ | |
| { | |
| "name": "get_employees", | |
| "description": "Get list of all employees", | |
| "inputSchema": { | |
| "type": "object", | |
| "properties": {}, | |
| "required": [] | |
| } | |
| }, | |
| { | |
| "name": "get_employee_by_id", | |
| "description": "Get a specific employee by their ID", | |
| "inputSchema": { | |
| "type": "object", | |
| "properties": { | |
| "employee_id": { | |
| "type": "integer", | |
| "description": "The unique identifier of the employee" | |
| } | |
| }, | |
| "required": ["employee_id"] | |
| } | |
| }, | |
| { | |
| "name": "create_employee", | |
| "description": "Create a new employee record", | |
| "inputSchema": { | |
| "type": "object", | |
| "properties": { | |
| "name": { | |
| "type": "string", | |
| "description": "Name of the employee" | |
| }, | |
| "nif": { | |
| "type": "integer", | |
| "description": "Tax identification number (optional)" | |
| }, | |
| "date_of_birth": { | |
| "type": "string", | |
| "description": "Date of birth in YYYY-MM-DD format (optional)" | |
| }, | |
| "address": { | |
| "type": "string", | |
| "description": "Employee's address (optional)" | |
| } | |
| }, | |
| "required": ["name"] | |
| } | |
| } | |
| ] | |
| } | |
| }) | |
| elif request.get("method") == "tools/call": | |
| tool_name = request.get("params", {}).get("name") | |
| arguments = request.get("params", {}).get("arguments", {}) | |
| if tool_name == "get_employees": | |
| result = await get_employees() | |
| return json.dumps({ | |
| "jsonrpc": "2.0", | |
| "id": request.get("id"), | |
| "result": { | |
| "content": [{ | |
| "type": "text", | |
| "text": json.dumps(result, indent=2) | |
| }] | |
| } | |
| }) | |
| elif tool_name == "get_employee_by_id": | |
| employee_id = arguments.get("employee_id") | |
| if employee_id is None: | |
| return json.dumps({ | |
| "jsonrpc": "2.0", | |
| "id": request.get("id"), | |
| "error": { | |
| "code": -32602, | |
| "message": "Missing required parameter: employee_id" | |
| } | |
| }) | |
| try: | |
| result = await get_employee_by_id(employee_id) | |
| return json.dumps({ | |
| "jsonrpc": "2.0", | |
| "id": request.get("id"), | |
| "result": { | |
| "content": [{ | |
| "type": "text", | |
| "text": json.dumps(result, indent=2) | |
| }] | |
| } | |
| }) | |
| except Exception as e: | |
| return json.dumps({ | |
| "jsonrpc": "2.0", | |
| "id": request.get("id"), | |
| "error": { | |
| "code": -32602, | |
| "message": str(e) | |
| } | |
| }) | |
| elif tool_name == "create_employee": | |
| name = arguments.get("name") | |
| if not name: | |
| return json.dumps({ | |
| "jsonrpc": "2.0", | |
| "id": request.get("id"), | |
| "error": { | |
| "code": -32602, | |
| "message": "Missing required parameter: name" | |
| } | |
| }) | |
| try: | |
| result = await create_employee( | |
| name=name, | |
| nif=arguments.get("nif", 0), | |
| date_of_birth=arguments.get("date_of_birth", ""), | |
| address=arguments.get("address", "") | |
| ) | |
| return json.dumps({ | |
| "jsonrpc": "2.0", | |
| "id": request.get("id"), | |
| "result": { | |
| "content": [{ | |
| "type": "text", | |
| "text": result | |
| }] | |
| } | |
| }) | |
| except Exception as e: | |
| return json.dumps({ | |
| "jsonrpc": "2.0", | |
| "id": request.get("id"), | |
| "error": { | |
| "code": -32602, | |
| "message": str(e) | |
| } | |
| }) | |
| # Default error response | |
| return json.dumps({ | |
| "jsonrpc": "2.0", | |
| "id": request.get("id"), | |
| "error": { | |
| "code": -32601, | |
| "message": "Method not found" | |
| } | |
| }) | |
| except Exception as e: | |
| return json.dumps({ | |
| "jsonrpc": "2.0", | |
| "id": request.get("id", None), | |
| "error": { | |
| "code": -32603, | |
| "message": f"Internal error: {str(e)}" | |
| } | |
| }) | |
| # Create interfaces | |
| employees_interface = gr.Interface( | |
| fn=get_employees, | |
| inputs=None, | |
| outputs="json", | |
| title="Get Employees", | |
| description="Return the list of all employees" | |
| ) | |
| employee_by_id_interface = gr.Interface( | |
| fn=get_employee_by_id, | |
| inputs=gr.Number(label="Employee ID", precision=0), | |
| outputs="json", | |
| title="Get Employee by ID", | |
| description="Return a specific employee by their ID" | |
| ) | |
| create_employee_interface = gr.Interface( | |
| fn=create_employee, | |
| inputs=[ | |
| gr.Textbox(label="Name", placeholder="Employee name (required)"), | |
| gr.Number(label="NIF", value=0, precision=0), | |
| gr.Textbox(label="Date of Birth", placeholder="YYYY-MM-DD"), | |
| gr.Textbox(label="Address", placeholder="Employee address") | |
| ], | |
| outputs="text", | |
| title="Create Employee", | |
| description="Create a new employee record" | |
| ) | |
| mcp_interface = gr.Interface( | |
| fn=handle_mcp_request, | |
| inputs=gr.Textbox(label="MCP Request JSON"), | |
| outputs=gr.Textbox(label="MCP Response JSON"), | |
| title="MCP Server", | |
| description="MCP protocol endpoint" | |
| ) | |
| # Combine interfaces | |
| demo = gr.TabbedInterface( | |
| [employees_interface, employee_by_id_interface, create_employee_interface, mcp_interface], | |
| ["Get All Employees", "Get Employee by ID", "Create Employee", "MCP Protocol"] | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch(mcp_server=True, share=True) |