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.

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

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
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden and does well by disclosing key behavioral traits: it explains fuzzy matching (handling partial matches, plurals, case-insensitivity), performance optimization (default transcript exclusion), pagination (fetches all meetings with pagination), and result filtering (returns matching meetings). It doesn't cover error cases or rate limits, but provides substantial operational context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is appropriately sized and front-loaded with the core purpose, followed by details on behavior and examples. Every sentence adds value, though the examples section could be slightly more concise, as they reiterate points already made in the description.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's moderate complexity, no annotations, and the presence of an output schema (which handles return values), the description is complete enough: it covers purpose, usage, key behaviors, and parameters, leaving no critical gaps for an AI agent to understand and invoke the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema description coverage is 100%, so the baseline is 3. The description adds some value by clarifying that 'query' searches across specific metadata fields (titles, attendees, teams, topics, summaries) and optionally transcripts, and it provides examples of usage, but it doesn't significantly expand beyond what the schema already documents.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the specific action ('search meetings by keyword') and resources ('metadata fields and optionally transcripts'), with explicit differentiation from siblings like 'list_meetings' (which lacks search) and 'get_meeting_details'/'get_meeting_transcript' (which fetch specific meetings rather than searching).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit guidance on when to use this tool vs. alternatives: it specifies that transcripts are excluded by default for performance, recommends 'include_transcript=True' for transcript searches, and distinguishes it from siblings by emphasizing its search capability across metadata and optional transcripts.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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-Simple-MCP'

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