Skip to main content
Glama
open_webui_interface.py14.3 kB
import os import requests import json import sys # For stderr # Consider making these configurable, e.g., via environment variables for OpenWebUI deployment MCP_SERVER_URL = os.environ.get("MCP_JENKINS_SERVER_URL", "http://localhost:5000") MCP_API_KEY = os.environ.get('MCP_API_KEY') class Tools: def __init__(self): """ Tools for interacting with a Jenkins MCP server. """ self.mcp_server_url = MCP_SERVER_URL self.mcp_api_key = MCP_API_KEY self._ensure_api_key_warning() def _ensure_api_key_warning(self): if not self.mcp_api_key: print("Warning: MCP_API_KEY not found. Requests to MCP server might be unauthorized.", file=sys.stderr) def _call_mcp_server(self, endpoint: str, method: str = "GET", data: dict = None) -> dict | str: """ Helper method to call the MCP Jenkins server. :param endpoint: The API endpoint to call (e.g., "/jobs"). :param method: HTTP method ("GET", "POST"). :param data: JSON payload for POST requests. :return: Parsed JSON response from the server or an error message string. """ headers = {} if self.mcp_api_key: headers['X-API-Key'] = self.mcp_api_key url = f"{self.mcp_server_url}{endpoint}" try: if method.upper() == "GET": mcp_response = requests.get(url, headers=headers) elif method.upper() == "POST": headers['Content-Type'] = 'application/json' mcp_response = requests.post(url, headers=headers, json=data) else: return f"Unsupported HTTP method: {method}" mcp_response.raise_for_status() return mcp_response.json() except requests.exceptions.HTTPError as e: error_details = "" try: error_payload = e.response.json() if "error" in error_payload: error_details = f" Server message: {error_payload['error']}" except ValueError: error_details = f" Server response: {e.response.text}" return f"Error calling MCP server ({e.response.status_code} {e.response.reason}) for {url}.{error_details}" except requests.exceptions.RequestException as e: return f"Error calling MCP server: {e}" except json.JSONDecodeError: return f"Error parsing MCP server JSON response. Response: {mcp_response.text if 'mcp_response' in locals() else 'No response object'}" def get_build_status(self, job_name: str, build_number: str | int) -> str: """ Get the status of a specific Jenkins build. :param job_name: The name of the Jenkins job (e.g., "MyProject/main"). :type job_name: str :param build_number: The build number (e.g., 15, "lastBuild", "lastSuccessfulBuild"). :type build_number: str | int :return: A string describing the build status or an error message. :rtype: str """ if not job_name or build_number is None: return "Error: Missing job_name or build_number for get_build_status." result = self._call_mcp_server(f"/job/{job_name}/build/{build_number}") if isinstance(result, dict): is_building = result.get('building') build_result_status = result.get('result') status = "UNKNOWN" if is_building: status = "BUILDING" elif build_result_status: status = build_result_status return f"Status for {job_name} build {result.get('build_number', build_number)}: {status}. URL: {result.get('url')}" return str(result) # Return error string from _call_mcp_server def list_job_builds(self, job_name: str, limit: int = 5) -> str: """ List recent builds for a Jenkins job. :param job_name: The name of the Jenkins job (e.g., "MyProject/main"). :type job_name: str :param limit: The maximum number of recent builds to list. Defaults to 5. :type limit: int :return: A summary of recent builds or an error message. :rtype: str """ if not job_name: return "Error: Missing job_name for list_job_builds." builds_data = self._call_mcp_server(f"/job/{job_name}/builds") if isinstance(builds_data, dict) and "builds" in builds_data: builds = builds_data["builds"] if not builds: return f"No builds found for job {job_name}." summary = f"Recent builds for {job_name} (limit {limit}):\n" for build in builds[:limit]: status = "BUILDING" if build.get('building') else build.get('result', 'UNKNOWN') summary += f" - Build #{build['number']}: {status} ({build['url']})\n" return summary.strip() return str(builds_data) def trigger_build(self, job_name: str, build_parameters: dict = None) -> str: """ Trigger a new build for a Jenkins job, optionally with parameters. :param job_name: The name of the Jenkins job to build (e.g., "MyProject/main"). :type job_name: str :param build_parameters: A dictionary of parameters to pass to the build. Defaults to None. :type build_parameters: dict, optional :return: A message indicating the build trigger status or an error message. :rtype: str """ if not job_name: return "Error: Missing job_name for trigger_build." payload_for_server = {"parameters": build_parameters if build_parameters else {}} result = self._call_mcp_server(f"/job/{job_name}/build", method="POST", data=payload_for_server) if isinstance(result, dict) and "message" in result: return f"{result['message']} for {job_name}. Queue item: {result.get('queue_item_url') or result.get('queue_item', 'N/A')}" return str(result) def list_jobs(self, folder_name: str = None, recursive: bool = False) -> str: """ List Jenkins jobs, optionally filtered by folder and recursion. :param folder_name: The name of the folder to list jobs from. Defaults to None (root). :type folder_name: str, optional :param recursive: Whether to list jobs recursively within folders. Defaults to False. :type recursive: bool :return: A summary of available jobs or an error message. :rtype: str """ endpoint = "/jobs" query_params = [] if folder_name: query_params.append(f"folder_name={requests.utils.quote(folder_name)}") if recursive: query_params.append(f"recursive=true") if query_params: endpoint += "?" + "&".join(query_params) jobs_data = self._call_mcp_server(endpoint) if isinstance(jobs_data, dict) and "jobs" in jobs_data: jobs = jobs_data["jobs"] if not jobs: return "No jobs found with the given criteria." summary = "Available jobs:\n" for job in jobs[:20]: # Limit output for brevity summary += f" - {job['name']} (Class: {job.get('_class', 'N/A')}, URL: {job.get('url', 'N/A')})\n" if len(jobs) > 20: summary += f"... and {len(jobs)-20} more." return summary.strip() return str(jobs_data) def get_build_log(self, job_name: str, build_number: str | int) -> str: """ Get the console output (log) of a specific Jenkins build. Provides a summary and a URL to the full log. :param job_name: The name of the Jenkins job (e.g., "MyProject/main"). :type job_name: str :param build_number: The build number (e.g., 15, "lastBuild"). :type build_number: str | int :return: A summary of the build log and a link to the full log, or an error message. :rtype: str """ if not job_name or build_number is None: return "Error: Missing job_name or build_number for get_build_log." result = self._call_mcp_server(f"/job/{job_name}/build/{build_number}/log") if isinstance(result, dict) and "summary" in result and "log_url" in result: return (f"Log summary for {result.get('job_name', job_name)} build {result.get('build_number', build_number)}:\n" f"{result['summary']}\n" f"Full log available at: {result['log_url']}") elif isinstance(result, dict) and "error" in result: return f"Error from server: {result['error']}" return str(result) def create_job(self, job_name: str, job_type: str, job_description: str = None, month: int = None, year: int = None, city: str = None) -> str: """ Create a new Jenkins job. Currently supports 'calendar' or 'weather' job types. For 'calendar' jobs, 'month' and 'year' are required. For 'weather' jobs, 'city' is required. :param job_name: The desired name for the new job. :type job_name: str :param job_type: The type of job to create ('calendar' or 'weather'). :type job_type: str :param job_description: An optional description for the job. :type job_description: str, optional :param month: The month (1-12) for 'calendar' jobs. :type month: int, optional :param year: The year (e.g., 2024) for 'calendar' jobs. :type year: int, optional :param city: The city name for 'weather' jobs. :type city: str, optional :return: A message indicating success or failure of job creation. :rtype: str """ if not job_name: return "Error: Job name cannot be empty." if job_type not in ["calendar", "weather"]: return "Error: Invalid job_type. Must be 'calendar' or 'weather'." payload_for_server = { "job_name": job_name, "job_type": job_type, "job_description": job_description, "month": month, "year": year, "city": city } # Validate required parameters based on job_type if job_type == "calendar": if month is None or year is None: return "Error: For 'calendar' job_type, 'month' and 'year' parameters are required." elif job_type == "weather": if not city: return "Error: For 'weather' job_type, 'city' parameter is required." # Filter out None values as server Pydantic models handle Optional fields payload_for_server = {k: v for k, v in payload_for_server.items() if v is not None} result = self._call_mcp_server("/job/create", method="POST", data=payload_for_server) if isinstance(result, dict) and "message" in result: return f"{result['message']}. Job: {result.get('job_name')}, URL: {result.get('job_url')}" elif isinstance(result, dict) and "error" in result: return f"Error from server: {result['error']}" return str(result) # Example of a tool from the provided OpenWebUI snippet, can be removed if not relevant def get_current_time(self, __user__: dict = {}) -> str: """ Get the current time in a human-readable format. :return: The current time. :rtype: str """ # Do not include :param for __user__ in the docstring as it should not be shown from datetime import datetime now = datetime.now() current_time = now.strftime("%I:%M:%S %p") current_date = now.strftime("%A, %B %d, %Y") return f"Current Date and Time = {current_date}, {current_time}" # To test a specific tool method locally (example): if __name__ == '__main__': tools_instance = Tools() # Ensure MCP_API_KEY is set in your environment if required by your server # Ensure MCP_JENKINS_SERVER_URL is set or defaults to http://localhost:5000 print("Attempting to list jobs...") # Replace with your actual Jenkins job name for testing other functions # print(tools_instance.list_jobs(recursive=True)) # print(tools_instance.get_build_status(job_name="folder/my-job", build_number="lastBuild")) # print(tools_instance.trigger_build(job_name="test-job")) # print(tools_instance.create_job(job_name="TestWebUIJob", job_type="calendar", month=12, year=2025, job_description="A test job from WebUI")) # print(tools_instance.get_build_log(job_name="TestWebUIJob", build_number=1)) # Example for get_current_time # print(tools_instance.get_current_time()) # Test list_jobs # print("\n--- Testing list_jobs ---") # print(tools_instance.list_jobs()) # Test create_job (calendar) # print("\n--- Testing create_job (calendar) ---") # print(tools_instance.create_job(job_name="WebUICalendarJob", job_type="calendar", month=7, year=2024, job_description="Calendar job via WebUI")) # Test list_job_builds (assuming WebUICalendarJob might not have builds yet) # print("\n--- Testing list_job_builds for WebUICalendarJob ---") # print(tools_instance.list_job_builds(job_name="WebUICalendarJob")) # Test trigger_build (for a job that can be triggered, e.g., a freestyle or pipeline job) # print("\n--- Testing trigger_build for 'freestyle-project-test' ---") # Replace with an actual job name # print(tools_instance.trigger_build(job_name="freestyle-project-test")) # After triggering, you might want to check status or log # print("\n--- Testing get_build_status for 'freestyle-project-test' build 'lastBuild' ---") # print(tools_instance.get_build_status(job_name="freestyle-project-test", build_number="lastBuild")) # print("\n--- Testing get_build_log for 'freestyle-project-test' build 'lastBuild' ---") # print(tools_instance.get_build_log(job_name="freestyle-project-test", build_number="lastBuild")) # Example of listing jobs in a folder # print(tools_instance.list_jobs(folder_name="my_folder", recursive=True)) pass # Add more specific tests as needed

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/andreimatveyeu/mcp_jenkins'

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