Skip to main content
Glama

search_meetings

Search meeting recordings by keyword across metadata fields like titles, attendees, and topics, with optional transcript search for comprehensive results.

Instructions

Search meetings by keyword across metadata fields and optionally transcripts.

This tool searches meeting metadata (titles, attendees, teams, topics, summaries) and optionally full transcript content. Uses fuzzy matching to handle partial matches, plurals, and case-insensitive search.

By default, transcripts are NOT searched or included to optimize performance. Set include_transcript=True to search within and return transcript data.

Fetches all meetings (with pagination) and returns those matching the search query.

Examples: search_meetings("McDonalds") # Search metadata only search_meetings("budget discussion", include_transcript=True) # Search including transcripts search_meetings("engineering") # Find meetings related to engineering

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryYesSearch query to match against meeting metadata (titles, participants, teams, topics, summaries, and optionally transcripts)
include_transcriptNoIf True, search within transcripts and include them in results.

Implementation Reference

  • The core handler function implementing the search_meetings tool logic. It normalizes the query, fetches meetings with pagination, optionally retrieves transcripts, filters matches using helper functions across metadata and transcripts, formats results, and returns structured output.
    async def search_meetings(
        ctx: Context,
        query: str,
        include_transcript: bool = False
    ) -> dict:
        """Search meetings by keyword across titles, participants, teams, topics, and optionally transcripts.
        
        This tool searches meeting metadata and optionally full transcript content, returning
        matching meetings with their recording_id, summary, and optionally transcripts. Uses fuzzy matching
        to handle partial matches, plurals, and case-insensitive search.
        
        Fetches up to 10 pages (500 meetings max) to provide comprehensive search results.
        
        Args:
            ctx: MCP context for logging
            query: Search query string to match against meeting metadata
            include_transcript: If True, search within transcripts and include them in results (default: False)
        
        Returns:
            dict: {
                "items": [Meeting objects matching the search query with summary and optionally transcripts],
                "query": str (the search query used),
                "total_matches": int (number of matches found),
                "searched_transcripts": bool (whether transcripts were searched)
            }
        """
        try:
            await ctx.info(f"Searching meetings with query: {query}")
            
            if not query or not query.strip():
                await ctx.error("Search query cannot be empty")
                return {
                    "items": [],
                    "query": query,
                    "total_matches": 0
                }
            
            # Normalize the search query
            search_normalized = _normalize_search(query)
            
            # Fetch all meetings (with pagination, max 10 pages = 500 meetings)
            all_meetings = []
            cursor: Optional[str] = None
            page = 1
            max_pages = 10
            
            while page <= max_pages:
                await ctx.info(f"Fetching meetings page {page}/{max_pages}")
                
                params = {
                    "include_summary": True  # Always include summaries in search results
                }
                if cursor:
                    params["cursor"] = cursor
                
                response = await client.get_meetings(params=params)
                items = response.get("items", [])
                
                if not items:
                    break
                
                all_meetings.extend(items)
                
                # Check for next page
                cursor = response.get("cursor")
                if not cursor:
                    break
                
                page += 1
            
            await ctx.info(f"Total meetings fetched: {len(all_meetings)}")
            
            # If including transcripts, fetch them for meetings that don't have them
            if include_transcript:
                await ctx.info("Fetching transcripts for meetings...")
                for meeting in all_meetings:
                    if not meeting.get("transcript"):
                        recording_id = meeting.get("recording_id")
                        if recording_id:
                            try:
                                transcript_data = await client.get_transcript(recording_id)
                                meeting["transcript"] = transcript_data.get("transcript")
                            except Exception as e:
                                await ctx.info(f"Could not fetch transcript for {recording_id}: {str(e)}")
            
            # Filter meetings by search query and track where matches are found
            matched_meetings = []
            
            if include_transcript:
                for m in all_meetings:
                    matches, found_in_transcript = _meeting_matches_search_with_transcript(m, search_normalized)
                    if matches:
                        matched_meetings.append((m, found_in_transcript))
            else:
                for m in all_meetings:
                    matches, _ = _meeting_matches_search(m, search_normalized)
                    if matches:
                        matched_meetings.append((m, False))
            
            # Apply field filtering
            filtered_meetings = [
                _filter_meeting_fields(m, found_in_transcript=found_in_transcript)
                for m, found_in_transcript in matched_meetings
            ]
            
            await ctx.info(
                f"Search completed: found {len(matched_meetings)} matches out of {len(all_meetings)} meetings"
            )
            
            return {
                "items": filtered_meetings,
                "query": query,
                "total_matches": len(matched_meetings),
                "searched_transcripts": include_transcript
            }
            
        except FathomAPIError as e:
            await ctx.error(f"Fathom API error during search: {e.message}")
            raise e
        except Exception as e:
            await ctx.error(f"Unexpected error during search: {str(e)}")
            raise e
  • server.py:76-104 (registration)
    The MCP tool registration using @mcp.tool decorator. Defines the input schema with Pydantic Field descriptions and docstring, and delegates execution to the implementation in tools/search.py.
    @mcp.tool
    async def search_meetings(
        ctx: Context,
        query: str = Field(
            ...,
            description="Search query to match against meeting metadata (titles, participants, teams, topics, summaries, and optionally transcripts)",
        ),
        include_transcript: bool = Field(
            default=False,
            description="If True, search within transcripts and include them in results.",
        ),
    ) -> Dict[str, Any]:
        """Search meetings by keyword across metadata fields and optionally transcripts.
    
        This tool searches meeting metadata (titles, attendees, teams, topics, summaries) and optionally
        full transcript content. Uses fuzzy matching to handle partial matches, plurals, and case-insensitive search.
    
        By default, transcripts are NOT searched or included to optimize performance. Set include_transcript=True
        to search within and return transcript data.
    
        Fetches all meetings (with pagination) and returns those matching the search query.
    
        Examples:
            search_meetings(\"McDonalds\")  # Search metadata only
            search_meetings(\"budget discussion\", include_transcript=True)  # Search including transcripts
            search_meetings(\"engineering\")  # Find meetings related to engineering
        """
        return await tools.search.search_meetings(ctx, query, include_transcript)
  • Helper function to check if a meeting matches the search query in metadata fields like title, participants, teams, topics, and summary.
    def _meeting_matches_search(meeting: dict, search_normalized: str) -> tuple:
        """Check if a meeting matches the search term in title, attendees, or teams.
        
        Returns:
            tuple: (matches, found_in_transcript) - matches=True if found, found_in_transcript=False for metadata searches
        """
        
        # Check title
        title = _normalize_search(meeting.get("title") or "")
        if search_normalized in title:
            return True, False
    
        # Check meeting_title
        meeting_title = _normalize_search(meeting.get("meeting_title") or "")
        if search_normalized in meeting_title:
            return True, False
    
        # Check attendee names and emails
        for invitee in meeting.get("calendar_invitees") or []:
            name = _normalize_search(invitee.get("name") or "")
            email = (invitee.get("email") or "").lower()
            if search_normalized in name or search_normalized in email:
                return True, False
    
        # Check team names
        for team in meeting.get("teams") or []:
            team_name = _normalize_search(team.get("name") or "") if isinstance(team, dict) else _normalize_search(str(team))
            if search_normalized in team_name:
                return True, False
    
        # Check topics
        for topic in meeting.get("topics") or []:
            topic_text = _normalize_search(topic.get("name") or "") if isinstance(topic, dict) else _normalize_search(str(topic))
            if search_normalized in topic_text:
                return True, False
    
        # Check default_summary.markdown_formatted (summary)
        summary = meeting.get("default_summary")
        summary_text = None
    
        if isinstance(summary, dict):
            summary_text = summary.get("markdown_formatted")
    
        if summary_text:
            summary_text_norm = _normalize_search(str(summary_text))
            if search_normalized in summary_text_norm:
                return True, False
    
        return False, False
  • Helper function to filter and structure the meeting data returned in search results, extracting relevant fields including summary.
    def _filter_meeting_fields(meeting: dict, found_in_transcript: bool = False) -> dict:
        """Filter and structure meeting fields for search results.
        
        Args:
            meeting: The meeting object from the API
            found_in_transcript: Whether the search match was found in the transcript
        
        Returns:
            dict: Filtered meeting fields with summary and optional found_in_transcript flag
        """
        summary = meeting.get("default_summary")
        summary_text = None
        if isinstance(summary, dict):
            summary_text = summary.get("markdown_formatted")
        elif isinstance(summary, str):
            summary_text = summary
        
        result = {
            "title": meeting.get("title"),
            "recording_id": meeting.get("recording_id"),
            "url": meeting.get("url"),
            "share_url": meeting.get("share_url"),
            "created_at": meeting.get("created_at"),
            "scheduled_start_time": meeting.get("scheduled_start_time"),
            "scheduled_end_time": meeting.get("scheduled_end_time"),
            "recording_start_time": meeting.get("recording_start_time"),
            "recording_end_time": meeting.get("recording_end_time"),
            "transcript_language": meeting.get("transcript_language"),
            "calendar_invitees": meeting.get("calendar_invitees"),
            "recorded_by": meeting.get("recorded_by"),
            "teams": meeting.get("teams"),
            "topics": meeting.get("topics"),
            "summary": summary_text
        }
        
        # Add flag indicating if match was found in transcript
        if found_in_transcript:
            result["found_in_transcript"] = True
        
        return result
  • Helper function for normalizing search text: lowercases, removes spaces/hyphens/underscores, strips trailing 's' for plural handling.
    def _normalize_search(text: str) -> str:
        """Normalize text for fuzzy matching: lowercase, remove spaces/hyphens, strip trailing 's'."""
        normalized = text.lower().replace(" ", "").replace("-", "").replace("_", "")
        # Strip trailing 's' to handle simple plurals (labs -> lab, meetings -> meeting)
        if normalized.endswith("s") and len(normalized) > 2:
            normalized = normalized[:-1]
        return normalized

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/druellan/fathom-get-mcp'

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