search_issues_full
Search Redmine issues by keyword across subjects, descriptions, and all comments. Filter results by project or limit output for targeted issue discovery.
Instructions
Full-text search for issues by keyword. Returns results with description and all comments. Searches across subject, description, and all comments.
Args:
query: Search keyword
project_id: Filter by project ID (all projects if omitted)
limit: Maximum number of results (all results if omitted)Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | ||
| project_id | No | ||
| limit | No |
Implementation Reference
- redmine_mcp_interface.py:293-315 (handler)The MCP tool registration and handler wrapper for search_issues_full.
def search_issues_full( query: str, project_id: Optional[str] = None, limit: Optional[int] = None, ) -> List[Dict[str, Any]]: """Full-text search for issues by keyword. Returns results with description and all comments. Searches across subject, description, and all comments. Args: query: Search keyword project_id: Filter by project ID (all projects if omitted) limit: Maximum number of results (all results if omitted) """ logger.info(f"tool=search_issues_full query={query!r} project_id={project_id}") try: return _client().search_issues_full( query=query, project_id=project_id, limit=limit, ) except RedmineError as e: logger.error(f"search_issues_full error: {e}") raise - redmine_mcp_server.py:238-314 (handler)The implementation of search_issues_full, which performs a full-text search against the Redmine API and then fetches detailed issue data.
def search_issues_full( self, query: str, project_id: Optional[str] = None, limit: Optional[int] = None, ) -> List[Dict[str, Any]]: try: params: Dict[str, Any] = { "q": query, "issues": 1, "titles_only": 0, } if project_id is not None: params["scope"] = "projects" params["project_id"] = project_id else: params["scope"] = "all" issue_ids: List[int] = [] offset = 0 page_size = 25 while True: params["offset"] = offset params["limit"] = page_size resp = requests.get( f"{self._url}/search.json", params=params, headers={"X-Redmine-API-Key": self._api_key}, timeout=30, ) if resp.status_code != 200: raise RedmineError( f"search_issues_full failed: HTTP {resp.status_code} {resp.text}" ) data = resp.json() results = data.get("results", []) for r in results: if r.get("type") == "issue": issue_ids.append(r["id"]) if limit is not None and len(issue_ids) >= limit: issue_ids = issue_ids[:limit] break total = data.get("total_count", 0) offset += page_size if offset >= total or not results: break output = [] for issue_id in issue_ids: full = self._redmine.issue.get( issue_id, include=["journals"] ) journals = _safe(full, "journals", []) output.append({ "id": full.id, "subject": full.subject, "description": _safe(full, "description", ""), "status": _safe(_safe(full, "status"), "name", ""), "tracker": _safe(_safe(full, "tracker"), "name", ""), "priority": _safe(_safe(full, "priority"), "name", ""), "assigned_to": _safe(_safe(full, "assigned_to"), "name", ""), "updated_on": str(_safe(full, "updated_on", "")), "journals": [ { "notes": _safe(j, "notes", ""), "created_on": str(_safe(j, "created_on", "")), "user": _safe(_safe(j, "user"), "name", ""), } for j in journals if _safe(j, "notes") ], }) return output except (AuthError, ForbiddenError) as e: raise RedmineError(f"Authentication failed: {e}") from e except Exception as e: raise RedmineError(f"search_issues_full failed: {e}") from e