ticket_search
Search for project tickets by keywords in title or description with filters for project, status, priority, and tags to find relevant tickets across projects.
Instructions
PROJECT MANAGEMENT (TPM): Search tickets by keyword.
USE THIS TOOL WHEN:
User asks "find tickets about X" or "search for Y"
Looking for tickets by keywords in title or description
Need to discover relevant tickets across projects
Searches title and description. Supports prefix matching (e.g., "org" matches "organization"). Case-insensitive. All filters are combinable.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Search query (keywords to search in title/description) | |
| project_id | No | Filter by project ID (optional, case-insensitive) | |
| status | No | Filter by status (optional) | |
| priority | No | Filter by priority (optional) | |
| tags | No | Filter by tags - ticket must have all specified tags (optional) | |
| limit | No | Maximum results to return (default: 20, max: 100) |
Implementation Reference
- src/tpm_mcp/server.py:210-255 (registration)Tool registration for 'ticket_search' including name, description, and input schema definition.Tool( name="ticket_search", description="""PROJECT MANAGEMENT (TPM): Search tickets by keyword. USE THIS TOOL WHEN: - User asks "find tickets about X" or "search for Y" - Looking for tickets by keywords in title or description - Need to discover relevant tickets across projects Searches title and description. Supports prefix matching (e.g., "org" matches "organization"). Case-insensitive. All filters are combinable.""", inputSchema={ "type": "object", "properties": { "query": { "type": "string", "description": "Search query (keywords to search in title/description)", }, "project_id": { "type": "string", "description": "Filter by project ID (optional, case-insensitive)", }, "status": { "type": "string", "enum": ["backlog", "planned", "in-progress", "done", "blocked"], "description": "Filter by status (optional)", }, "priority": { "type": "string", "enum": ["critical", "high", "medium", "low"], "description": "Filter by priority (optional)", }, "tags": { "type": "array", "items": {"type": "string"}, "description": "Filter by tags - ticket must have all specified tags (optional)", }, "limit": { "type": "integer", "description": "Maximum results to return (default: 20, max: 100)", "default": 20, }, }, "required": ["query"], }, ),
- src/tpm_mcp/server.py:560-579 (handler)Handler implementation for the 'ticket_search' tool: processes input arguments, invokes database search, and returns JSON-formatted results.if name == "ticket_search": limit = min(args.get("limit", 20), 100) status = TicketStatus(args["status"]) if args.get("status") else None priority = Priority(args["priority"]) if args.get("priority") else None results = db.search_tickets( query=args["query"], project_id=args.get("project_id"), status=status, priority=priority, tags=args.get("tags"), limit=limit, ) return _json({ "results": results, "total": len(results), "query": args["query"], })
- src/tpm_mcp/db.py:438-524 (helper)Database helper method implementing the core ticket search logic using SQLite FTS5 full-text search, with support for filters on project, status, priority, tags, and prefix matching.def search_tickets( self, query: str, project_id: str | None = None, status: TicketStatus | None = None, priority: Priority | None = None, tags: list[str] | None = None, limit: int = 20, ) -> list[dict]: """Search tickets using full-text search with optional filters. Args: query: Search query (supports prefix matching) project_id: Filter by project ID (case-insensitive) status: Filter by ticket status priority: Filter by priority level tags: Filter by tags (ticket must have all specified tags) limit: Maximum results to return (default 20) Returns: List of dicts with: id, title, project_id, status, priority, tags, snippet """ if not query or not query.strip(): return [] # Build FTS5 query with prefix matching for each term terms = query.strip().split() fts_query = " ".join(f"{term}*" for term in terms) # Build the SQL query with joins and filters sql = """ SELECT t.id, t.title, t.project_id, t.status, t.priority, t.tags, snippet(tickets_fts, 1, '<b>', '</b>', '...', 32) as snippet FROM tickets_fts JOIN tickets t ON tickets_fts.ticket_id = t.id WHERE tickets_fts MATCH ? """ params: list = [fts_query] # Add filters if project_id: project_id = self._normalize_id(project_id) sql += " AND LOWER(t.project_id) = ?" params.append(project_id) if status: sql += " AND t.status = ?" params.append(status.value) if priority: sql += " AND t.priority = ?" params.append(priority.value) if tags: # Check that ticket has all specified tags using json_each for tag in tags: sql += " AND EXISTS (SELECT 1 FROM json_each(t.tags) WHERE value = ?)" params.append(tag) # Order by relevance (FTS5 rank) and limit sql += " ORDER BY rank LIMIT ?" params.append(limit) try: rows = self.conn.execute(sql, params).fetchall() return [ { "id": r["id"], "title": r["title"], "project_id": r["project_id"], "status": r["status"], "priority": r["priority"], "tags": _from_json(r["tags"]), "snippet": r["snippet"], } for r in rows ] except Exception: # Handle FTS5 syntax errors gracefully return []