Skip to main content
Glama
server.py7.97 kB
from fastmcp import FastMCP, Context from typing import Any, Dict from pydantic import Field from config import config from fathom_client import client from contextlib import asynccontextmanager import json from toon import encode as toon_encode from utils import filter_response # Import tools import tools.meetings import tools.recordings import tools.teams import tools.team_members import tools.search def output_serializer(data: Any) -> str: """Serialize tool output based on OUTPUT_FORMAT configuration. Args: data: The data to serialize Returns: Sanitized and formatted output as a the configured format (TOON or JSON) """ if isinstance(data, str): # Don't serialize strings that are already formatted return data # Filter sensitive keys and sanitize output by removing null and empty values filtered_data = filter_response(data) if config.output_format == "toon": try: return toon_encode(filtered_data) except Exception: pass # Default to JSON return json.dumps(filtered_data, indent=2, ensure_ascii=False) @asynccontextmanager async def lifespan(server): """Server lifespan context manager""" # Startup if not config.validate(): raise ValueError("Invalid configuration: FATHOM_API_KEY is required") yield # Shutdown await client.close() mcp = FastMCP( name="Fathom MCP Server", instructions=( "Access Fathom.video meeting recordings, transcripts, summaries, teams, and team members." "Fathom.video automatically records, transcribes, and summarizes meetings." "Use search_meetings to find meetings by keywords in titles, summaries, participants, teams, and topics." "Use list_meetings to browse meetings with filtering by date, attendees, teams, and domains." "Use get_meeting_details for comprehensive meeting data including summaries." "Use list_teams and list_team_members for organizational data." "All endpoints support pagination and efficient data retrieval optimized for LLM processing." ), lifespan=lifespan, on_duplicate_tools="warn", on_duplicate_resources="warn", on_duplicate_prompts="warn", tool_serializer=output_serializer, ) @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) @mcp.tool async def list_meetings( ctx: Context, calendar_invitees: list[str] = Field( default=None, description="Filter by invitee emails" ), calendar_invitees_domains: list[str] = Field( default=None, description="Filter by domains" ), created_after: str = Field(default=None, description="ISO timestamp filter"), created_before: str = Field(default=None, description="ISO timestamp filter"), cursor: str = Field(default=None, description="Pagination cursor"), include_action_items: bool = Field( default=None, description="Include action items" ), include_crm_matches: bool = Field(default=None, description="Include CRM matches"), per_page: int = Field( default=config.default_per_page, description=f"Number of results per page (default: {config.default_per_page})", ), recorded_by: list[str] = Field( default=None, description="Filter by recorder emails" ), teams: list[str] = Field(default=None, description="Filter by team names"), ) -> Dict[str, Any]: """Retrieve paginated meetings with filtering and optional content inclusion (action items, CRM matches). Examples: list_meetings() # Get all meetings (paginated) list_meetings(created_after="2024-01-01T00:00:00Z") # Meetings after specific date list_meetings(teams=["Sales", "Engineering"]) # Filter by specific teams list_meetings(calendar_invitees=["john.doe@company.com", "jane.smith@client.com"]) # Filter by specific attendees list_meetings(calendar_invitees_domains=["company.com", "client.com"]) # Filter by attendee domains """ return await tools.meetings.list_meetings( ctx, calendar_invitees=calendar_invitees, calendar_invitees_domains=calendar_invitees_domains, created_after=created_after, created_before=created_before, cursor=cursor, include_action_items=include_action_items, include_crm_matches=include_crm_matches, per_page=per_page, recorded_by=recorded_by, teams=teams ) @mcp.tool async def get_meeting_details( ctx: Context, recording_id: int = Field(..., description="The recording identifier") ) -> Dict[str, Any]: """Retrieve comprehensive meeting details including summary and metadata (without transcript). Example: get_meeting_details([recording_id]) """ return await tools.recordings.get_meeting_details(ctx, recording_id) @mcp.tool async def get_meeting_transcript( ctx: Context, recording_id: int = Field(..., description="The recording identifier") ) -> Dict[str, Any]: """Retrieve meeting transcript with essential metadata (id, title, participants, dates). Example: get_meeting_transcript([recording_id]) """ return await tools.recordings.get_meeting_transcript(ctx, recording_id) @mcp.tool async def list_teams( ctx: Context, cursor: str = Field(default=None, description="Pagination cursor"), per_page: int = Field(default=None, description=f"Number of results per page (default: {config.default_per_page})") ) -> Dict[str, Any]: """Retrieve paginated list of teams with organizational structure. Examples: list_teams_tool() # Get first page of teams list_teams_tool(cursor="abc123") # Get next page using cursor """ return await tools.teams.list_teams(ctx, cursor, per_page) @mcp.tool async def list_team_members( ctx: Context, cursor: str = Field(default=None, description="Pagination cursor"), per_page: int = Field(default=None, description=f"Number of results per page (default: {config.default_per_page})"), team: str = Field(default=None, description="Filter by team name") ) -> Dict[str, Any]: """Retrieve paginated team members with optional team filtering. Examples: list_team_members_tool() # Get all team members across all teams list_team_members_tool(team="Engineering") # Filter members by team name list_team_members_tool(cursor="def456") # Paginate through member list """ return await tools.team_members.list_team_members(ctx, cursor, team, per_page) if __name__ == "__main__": mcp.run() def main(): """Entry point for the fathom-mcp command""" mcp.run()

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