Skip to main content
Glama

Polarion MCP Server

by Sdunga1
client.py10.8 kB
import json import os import time import webbrowser from typing import Dict, List, Optional from loguru import logger import requests # Configuration - support environment variable override DEFAULT_POLARION_BASE_URL = "http://dev.polarion.atoms.tech/polarion" POLARION_BASE_URL = os.getenv("POLARION_BASE_URL", DEFAULT_POLARION_BASE_URL) LOGIN_URL = POLARION_BASE_URL TOKEN_PAGE_URL = f"{POLARION_BASE_URL}/#/user_tokens?id=admin" TOKEN_FILE = "polarion_token.json" REQUEST_TIMEOUT_SECONDS = 8 WORK_ITEM_MIN_FIELDS = "id,title,type,description" # Log the configured URL on module load logger.info(f"Polarion MCP Server configured for: {POLARION_BASE_URL}") if POLARION_BASE_URL != DEFAULT_POLARION_BASE_URL: logger.info(f"Using custom URL from POLARION_BASE_URL environment variable") class PolarionClient: def __init__(self): self.session = requests.Session() self.token = None def _ensure_token(self): if not self.token: self.token = self.load_token() if not self.token: raise Exception("No token available. Please set or generate a token first.") def _handle_api_response(self, response, operation_name: str): """Handle API response and provide meaningful error messages with workflow guidance.""" if response.status_code == 200: return True if response.status_code == 401: raise Exception(f""" Authentication failed: Token may be expired or invalid. NEXT STEPS: 1. Use check_polarion_status() to verify token status 2. Use open_polarion_login() to get new token 3. Use set_polarion_token() to update token 4. Then retry {operation_name} """) elif response.status_code == 403: raise Exception(f""" Access denied: You don't have permission to {operation_name}. TROUBLESHOOTING: 1. Verify you have access to this project/resource 2. Check if project_id is correct using get_polarion_projects() 3. Contact administrator for permissions """) elif response.status_code == 404: raise Exception(f""" Resource not found: {operation_name} failed. TROUBLESHOOTING: 1. Use get_polarion_projects() to verify project exists 2. Use get_polarion_work_items() to discover available work items 3. Check spelling of IDs and names - they are case-sensitive 4. For documents: Space names must be provided by user or found in work item references """) elif response.status_code == 500: raise Exception(f""" Polarion server error: {operation_name} failed. NEXT STEPS: 1. Wait a moment and retry 2. Check if Polarion instance is accessible 3. Try with smaller page sizes or simpler queries """) else: raise Exception(f"API error {response.status_code}: {response.text}") def _headers(self) -> Dict[str, str]: return { 'Authorization': f'Bearer {self.token}', 'Accept': 'application/json', 'Content-Type': 'application/json' } def open_login_page(self) -> str: """Open Polarion login page in user's browser for manual authentication""" try: webbrowser.open(LOGIN_URL) return json.dumps({ "status": "success", "message": f"Polarion login page opened in your browser: {LOGIN_URL}", "instructions": [ "1. Complete the login form in your browser", "2. After successful login, navigate to: " + TOKEN_PAGE_URL, "3. Generate a new token manually", "4. Copy the token and use it with the 'set_polarion_token' command" ], "login_url": LOGIN_URL, "token_page_url": TOKEN_PAGE_URL, "note": "If you get an 'Internal server error', try refreshing the page or check if the Polarion instance is accessible" }, indent=2) except Exception as e: logger.error(f"Failed to open login page: {e}") return json.dumps({ "status": "error", "message": f"Failed to open login page: {e}", "manual_url": LOGIN_URL }, indent=2) def set_token_manually(self, token: str) -> str: """Set token manually (after user generates it in browser)""" try: self.token = token self.save_token(token) return json.dumps({ "status": "success", "message": "Token set successfully. Please test it by fetching work items or projects.", "token_preview": f"{token[:10]}...{token[-10:]}" }, indent=2) except Exception as e: logger.error(f"Failed to set token: {e}") return json.dumps({ "status": "error", "message": f"Failed to set token: {e}" }, indent=2) def save_token(self, token: str): """Save token to file""" try: token_data = {"token": token, "generated_at": time.time()} with open(TOKEN_FILE, 'w') as f: json.dump(token_data, f) except Exception as e: logger.error(f"Failed to save token: {e}") def load_token(self) -> Optional[str]: """Load token from file""" try: if os.path.exists(TOKEN_FILE): with open(TOKEN_FILE, 'r') as f: token_data = json.load(f) return token_data.get("token") except Exception as e: logger.error(f"Failed to load token: {e}") return None def get_projects(self, limit: int = 10) -> List[Dict]: """Fetch projects from Polarion REST API (lightweight fields).""" try: self._ensure_token() api_url = f"{POLARION_BASE_URL}/rest/v1/projects" params = { 'fields[projects]': '@basic', 'page[size]': limit } response = self.session.get(api_url, params=params, headers=self._headers(), timeout=REQUEST_TIMEOUT_SECONDS) self._handle_api_response(response, "fetch projects") data = response.json() projects = (data.get('data') or [])[:limit] logger.info(f"Fetched {len(projects)} projects") return projects except Exception as e: logger.error(f"Failed to fetch projects: {e}") return [] def get_project(self, project_id: str, fields: str = "@basic") -> Optional[Dict]: """Fetch specific project details from Polarion REST API.""" try: self._ensure_token() api_url = f"{POLARION_BASE_URL}/rest/v1/projects/{project_id}" params = {'fields[projects]': fields} response = self.session.get(api_url, params=params, headers=self._headers(), timeout=REQUEST_TIMEOUT_SECONDS) if response.status_code == 404: logger.warning(f"Project not found: {project_id}") return None self._handle_api_response(response, f"fetch project {project_id}") project_data = response.json() logger.info(f"Fetched project: {project_id}") return project_data except Exception as e: logger.error(f"Failed to fetch project {project_id}: {e}") return None def get_work_items(self, project_id: str, limit: int = 10, query: str = "") -> List[Dict]: """Fetch work items (minimal fields). Parameters: project_id, limit, optional query.""" try: self._ensure_token() api_url = f"{POLARION_BASE_URL}/rest/v1/projects/{project_id}/workitems" params = { 'fields[workitems]': WORK_ITEM_MIN_FIELDS, 'page[size]': limit } if query: params['query'] = query response = self.session.get(api_url, params=params, headers=self._headers(), timeout=REQUEST_TIMEOUT_SECONDS) self._handle_api_response(response, f"fetch work items from project {project_id}") data = response.json() work_items = (data.get('data') or [])[:limit] logger.info(f"Fetched {len(work_items)} work items from {project_id}") return work_items except Exception as e: logger.error(f"Failed to fetch work items from {project_id}: {e}") return [] def get_work_item(self, project_id: str, work_item_id: str, fields: str = "@basic") -> Optional[Dict]: """Fetch a specific work item by ID from Polarion REST API.""" try: self._ensure_token() api_url = f"{POLARION_BASE_URL}/rest/v1/projects/{project_id}/workitems/{work_item_id}" params = {'fields[workitems]': fields} response = self.session.get(api_url, params=params, headers=self._headers(), timeout=REQUEST_TIMEOUT_SECONDS) if response.status_code == 404: logger.warning(f"Work item not found: {work_item_id} in project: {project_id}") return None self._handle_api_response(response, f"fetch work item {work_item_id} from project {project_id}") work_item_data = response.json() logger.info(f"Fetched work item: {work_item_id} from project: {project_id}") return work_item_data except Exception as e: logger.error(f"Failed to fetch work item {work_item_id} from project {project_id}: {e}") return None def get_document(self, project_id: str, space_id: str, document_name: str, fields: str = "@basic") -> Optional[Dict]: """Fetch a specific document from Polarion REST API.""" try: self._ensure_token() api_url = f"{POLARION_BASE_URL}/rest/v1/projects/{project_id}/spaces/{space_id}/documents/{document_name}" params = {'fields[documents]': fields} response = self.session.get(api_url, params=params, headers=self._headers(), timeout=REQUEST_TIMEOUT_SECONDS) if response.status_code == 404: logger.warning(f"Document not found: {document_name} in space: {space_id} of project: {project_id}") return None self._handle_api_response(response, f"fetch document {document_name} from space {space_id} in project {project_id}") document_data = response.json() logger.info(f"Fetched document: {document_name} from space: {space_id} in project: {project_id}") return document_data except Exception as e: logger.error(f"Failed to fetch document {document_name} from space {space_id} in project {project_id}: {e}") return None

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/Sdunga1/MCP-Polarion'

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