DraCor MCP Server

by stijn-meijers
Verified
#!/usr/bin/env python3 from typing import Dict, List, Optional, Any, Union import requests from mcp.server.fastmcp import FastMCP # Base API URL for DraCor v1 DRACOR_API_BASE_URL = "https://dracor.org/api/v1" # Create the FastMCP server instance mcp = FastMCP("DraCor API v1") # Helper function to make API requests def api_request(endpoint: str, params: Optional[Dict] = None) -> Any: """Make a request to the DraCor API v1.""" url = f"{DRACOR_API_BASE_URL}/{endpoint}" response = requests.get(url, params=params) response.raise_for_status() return response.json() # Resource implementations using decorators @mcp.resource("info://") def get_api_info() -> Dict: """Get API information and version details.""" try: info = api_request("info") return info except Exception as e: return {"error": str(e)} @mcp.resource("corpora://") def get_corpora() -> Dict: """List of all available corpora (collections of plays).""" try: # The include parameter needs to be handled differently as it's not in the URI # We'll handle it as a query parameter in the implementation corpora = api_request("corpora") return {"corpora": corpora} except Exception as e: return {"error": str(e)} @mcp.resource("corpus://{corpus_name}") def get_corpus(corpus_name: str) -> Dict: """Information about a specific corpus.""" try: corpus = api_request(f"corpora/{corpus_name}") return corpus except Exception as e: return {"error": str(e)} @mcp.resource("corpus_metadata://{corpus_name}") def get_corpus_metadata(corpus_name: str) -> Dict: """Get metadata for all plays in a corpus.""" try: metadata = api_request(f"corpora/{corpus_name}/metadata") return {"metadata": metadata} except Exception as e: return {"error": str(e)} @mcp.resource("plays://{corpus_name}") def get_plays(corpus_name: str) -> Dict: """List of plays in a specific corpus.""" try: corpus = api_request(f"corpora/{corpus_name}") return {"plays": corpus.get("plays", [])} except Exception as e: return {"error": str(e)} @mcp.resource("play://{corpus_name}/{play_name}") def get_play(corpus_name: str, play_name: str) -> Dict: """Information about a specific play.""" try: play = api_request(f"corpora/{corpus_name}/plays/{play_name}") return play except Exception as e: return {"error": str(e)} @mcp.resource("play_metrics://{corpus_name}/{play_name}") def get_play_metrics(corpus_name: str, play_name: str) -> Dict: """Get network metrics for a specific play.""" try: metrics = api_request(f"corpora/{corpus_name}/plays/{play_name}/metrics") return metrics except Exception as e: return {"error": str(e)} @mcp.resource("characters://{corpus_name}/{play_name}") def get_characters(corpus_name: str, play_name: str) -> Dict: """List of characters in a specific play.""" try: characters = api_request(f"corpora/{corpus_name}/plays/{play_name}/characters") return {"characters": characters} except Exception as e: return {"error": str(e)} @mcp.resource("spoken_text://{corpus_name}/{play_name}") def get_spoken_text(corpus_name: str, play_name: str) -> Dict: """Get the spoken text for a play, with optional filters (gender, relation, role) as query parameters.""" try: # For now, we won't use optional query parameters since they're causing issues # We can implement this differently once we better understand the FastMCP API url = f"{DRACOR_API_BASE_URL}/corpora/{corpus_name}/plays/{play_name}/spoken-text" response = requests.get(url) response.raise_for_status() text = response.text return {"text": text} except Exception as e: return {"error": str(e)} @mcp.resource("spoken_text_by_character://{corpus_name}/{play_name}") def get_spoken_text_by_character(corpus_name: str, play_name: str) -> Dict: """Get spoken text for each character in a play.""" try: text_by_character = api_request(f"corpora/{corpus_name}/plays/{play_name}/spoken-text-by-character") return {"text_by_character": text_by_character} except Exception as e: return {"error": str(e)} @mcp.resource("stage_directions://{corpus_name}/{play_name}") def get_stage_directions(corpus_name: str, play_name: str) -> Dict: """Get all stage directions of a play.""" try: # Note: This endpoint returns plain text, not JSON url = f"{DRACOR_API_BASE_URL}/corpora/{corpus_name}/plays/{play_name}/stage-directions" response = requests.get(url) response.raise_for_status() text = response.text return {"text": text} except Exception as e: return {"error": str(e)} @mcp.resource("network_data://{corpus_name}/{play_name}") def get_network_data(corpus_name: str, play_name: str) -> Dict: """Get network data of a play in CSV format.""" try: # Note: This endpoint returns CSV, not JSON url = f"{DRACOR_API_BASE_URL}/corpora/{corpus_name}/plays/{play_name}/networkdata/csv" response = requests.get(url) response.raise_for_status() csv_data = response.text return {"csv_data": csv_data} except Exception as e: return {"error": str(e)} @mcp.resource("relations://{corpus_name}/{play_name}") def get_relations(corpus_name: str, play_name: str) -> Dict: """Get character relation data for a play.""" try: url = f"{DRACOR_API_BASE_URL}/corpora/{corpus_name}/plays/{play_name}/relations" response = requests.get(url) response.raise_for_status() relations = response.json() return {"relations": relations} except Exception as e: return {"error": str(e)} @mcp.resource("full_text://{corpus_name}/{play_name}") def get_full_text(corpus_name: str, play_name: str) -> Dict: """Get the full text of a play in plain text format.""" try: # The DraCor API doesn't have a direct plain text endpoint # Use the spoken-text endpoint which returns plain text of all dialogue url = f"{DRACOR_API_BASE_URL}/corpora/{corpus_name}/plays/{play_name}/spoken-text" response = requests.get(url) response.raise_for_status() # Get stage directions too stage_url = f"{DRACOR_API_BASE_URL}/corpora/{corpus_name}/plays/{play_name}/stage-directions" stage_response = requests.get(stage_url) stage_response.raise_for_status() # Combine both for a more complete text representation text = f"DIALOGUE:\n\n{response.text}\n\nSTAGE DIRECTIONS:\n\n{stage_response.text}" return {"text": text} except Exception as e: return {"error": str(e)} @mcp.resource("tei_text://{corpus_name}/{play_name}") def get_tei_text(corpus_name: str, play_name: str) -> Dict: """Get the full TEI XML text of a play.""" try: url = f"{DRACOR_API_BASE_URL}/corpora/{corpus_name}/plays/{play_name}/tei" response = requests.get(url) response.raise_for_status() tei_text = response.text return {"tei_text": tei_text} except Exception as e: return {"error": str(e)} @mcp.resource("character_by_wikidata://{wikidata_id}") def get_plays_with_character(wikidata_id: str) -> Dict: """List plays having a character identified by Wikidata ID.""" try: plays = api_request(f"character/{wikidata_id}") return {"plays": plays} except Exception as e: return {"error": str(e)} # Tool implementations using decorators @mcp.tool() def search_plays( query: str = None, corpus_name: str = None, character_name: str = None, country: str = None, language: str = None, author: str = None, year_from: int = None, year_to: int = None, gender_filter: str = None ) -> Dict: """ Advanced search for plays in the DraCor database with multiple filter options. Parameters: - query: General text search across title, subtitle, and author - corpus_name: Specific corpus to search within (e.g., "shake", "ger", "rus", "span", "dutch") - character_name: Name of a character that should appear in the play - country: Country of origin for the play - language: Language of the play - author: Name of the playwright - year_from: Starting year for date range filter - year_to: Ending year for date range filter - gender_filter: Filter by plays with a certain gender ratio ("female_dominated", "male_dominated", "balanced") """ try: # Get corpora to search in corpora_result = get_corpora() if "error" in corpora_result: return {"error": corpora_result["error"]} all_corpora = corpora_result.get("corpora", []) target_corpora = [] # Filter corpora if specified if corpus_name: target_corpora = [corp for corp in all_corpora if corpus_name.lower() in corp.get("name", "").lower()] else: target_corpora = all_corpora # Initialize results results = [] detailed_results = [] # For each corpus, search for plays for corpus in target_corpora: corpus_name = corpus.get("name") # Get all plays from this corpus plays_result = get_plays(corpus_name) if "error" in plays_result: continue # Iterate through plays and apply filters for play in plays_result.get("plays", []): # Initialize as a match until proven otherwise by filters is_match = True # Apply general text search if specified if query and is_match: searchable_text = ( play.get("title", "") + " " + " ".join([a.get("name", "") for a in play.get("authors", [])]) + " " + play.get("subtitle", "") + " " + play.get("originalTitle", "") ).lower() if query.lower() not in searchable_text: is_match = False # Apply country filter if specified if country and is_match: play_country = ( play.get("writtenIn", "") + " " + play.get("printedIn", "") + " " + " ".join([a.get("country", "") for a in play.get("authors", [])]) ).lower() if country.lower() not in play_country: is_match = False # Apply language filter if specified if language and is_match: if language.lower() not in play.get("originalLanguage", "").lower(): is_match = False # Apply author filter if specified if author and is_match: author_names = [a.get("name", "").lower() for a in play.get("authors", [])] if not any(author.lower() in name for name in author_names): is_match = False # Apply year range filter if specified if (year_from or year_to) and is_match: play_year = play.get("yearNormalized") or play.get("yearWritten") or play.get("yearPrinted") or 0 if year_from and play_year < year_from: is_match = False if year_to and play_year > year_to: is_match = False # If character name is specified, need to check character list if character_name and is_match: try: # Get characters for this play play_name = play.get("name") characters_result = get_characters(corpus_name, play_name) if "error" not in characters_result: character_found = False for character in characters_result.get("characters", []): if character_name.lower() in character.get("name", "").lower(): character_found = True break if not character_found: is_match = False else: # If we can't get characters, we assume it's not a match is_match = False except: # If error occurs, we assume it's not a match is_match = False # Apply gender filter if specified if gender_filter and is_match: try: # Get characters for this play play_name = play.get("name") characters_result = get_characters(corpus_name, play_name) if "error" not in characters_result: male_count = sum(1 for c in characters_result.get("characters", []) if c.get("gender") == "MALE") female_count = sum(1 for c in characters_result.get("characters", []) if c.get("gender") == "FEMALE") total = male_count + female_count if total > 0: female_ratio = female_count / total if gender_filter == "female_dominated" and female_ratio <= 0.5: is_match = False elif gender_filter == "male_dominated" and female_ratio >= 0.5: is_match = False elif gender_filter == "balanced" and (female_ratio < 0.4 or female_ratio > 0.6): is_match = False except: # If error occurs, we keep it as a match pass # If all filters passed, add to results if is_match: # Add basic info to results results.append({ "corpus": corpus_name, "play": play }) # Try to add more detailed info for top results if len(detailed_results) < 5: try: play_name = play.get("name") # Get more details play_info = get_play(corpus_name, play_name) if "error" not in play_info: detailed_results.append({ "corpus": corpus_name, "play_name": play_name, "title": play.get("title"), "author": play.get("authors", [{}])[0].get("name") if play.get("authors") else "Unknown", "year": play.get("yearNormalized"), "language": play.get("originalLanguage"), "characters": len(play_info.get("characters", [])), "link": f"https://dracor.org/{corpus_name}/{play_name}" }) except: pass return { "count": len(results), "results": results, "top_results": detailed_results, "filters_applied": { "query": query, "corpus_name": corpus_name, "character_name": character_name, "country": country, "language": language, "author": author, "year_range": f"{year_from}-{year_to}" if year_from or year_to else None, "gender_filter": gender_filter } } except Exception as e: return {"error": str(e)} @mcp.tool() def compare_plays( corpus_name1: str, play_name1: str, corpus_name2: str, play_name2: str ) -> Dict: """Compare two plays in terms of metrics and structure.""" try: play1 = api_request(f"corpora/{corpus_name1}/plays/{play_name1}") play2 = api_request(f"corpora/{corpus_name2}/plays/{play_name2}") metrics1 = api_request(f"corpora/{corpus_name1}/plays/{play_name1}/metrics") metrics2 = api_request(f"corpora/{corpus_name2}/plays/{play_name2}/metrics") # Compile comparison data comparison = { "plays": [ { "title": play1.get("title"), "author": play1.get("authors", [{}])[0].get("name") if play1.get("authors") else None, "year": play1.get("yearNormalized"), "metrics": metrics1 }, { "title": play2.get("title"), "author": play2.get("authors", [{}])[0].get("name") if play2.get("authors") else None, "year": play2.get("yearNormalized"), "metrics": metrics2 } ] } return comparison except Exception as e: return {"error": str(e)} @mcp.tool() def analyze_character_relations(corpus_name: str, play_name: str) -> Dict: """Analyze the character relationships in a play.""" try: # Get play data play = api_request(f"corpora/{corpus_name}/plays/{play_name}") # Get character data characters = api_request(f"corpora/{corpus_name}/plays/{play_name}/characters") # Get network data in CSV format url = f"{DRACOR_API_BASE_URL}/corpora/{corpus_name}/plays/{play_name}/networkdata/csv" response = requests.get(url) response.raise_for_status() csv_data = response.text # Parse CSV data to extract relations relations = [] lines = csv_data.strip().split('\n') if len(lines) > 1: # Skip header headers = lines[0].split(',') for line in lines[1:]: parts = line.split(',') if len(parts) >= 4: source = parts[0].strip('"') target = parts[2].strip('"') weight = int(parts[3]) if parts[3].isdigit() else 0 # Find character names from IDs source_name = None target_name = None for char in characters: if char.get("id") == source: source_name = char.get("name") if char.get("id") == target: target_name = char.get("name") relations.append({ "source": source_name or source, "source_id": source, "target": target_name or target, "target_id": target, "weight": weight }) # Sort by weight to identify strongest relationships relations.sort(key=lambda x: x.get("weight", 0), reverse=True) # Try to get relations data if available try: relations_url = f"{DRACOR_API_BASE_URL}/corpora/{corpus_name}/plays/{play_name}/relations/csv" relations_response = requests.get(relations_url) formal_relations = [] if relations_response.status_code == 200: rel_lines = relations_response.text.strip().split('\n') if len(rel_lines) > 1: # Skip header for line in rel_lines[1:]: parts = line.split(',') if len(parts) >= 4: source = parts[0].strip('"') target = parts[2].strip('"') relation_type = parts[3].strip('"') # Find character names from IDs source_name = None target_name = None for char in characters: if char.get("id") == source: source_name = char.get("name") if char.get("id") == target: target_name = char.get("name") formal_relations.append({ "source": source_name or source, "target": target_name or target, "type": relation_type }) except: formal_relations = [] # Get metrics metrics = api_request(f"corpora/{corpus_name}/plays/{play_name}/metrics") return { "play": { "title": play.get("title"), "author": play.get("authors", [{}])[0].get("name") if play.get("authors") else None, "year": play.get("yearNormalized") }, "totalCharacters": len(characters), "totalRelations": len(relations), "strongestRelations": relations[:10], # Top 10 strongest relations "weakestRelations": relations[-10:] if len(relations) >= 10 else relations, # Bottom 10 "formalRelations": formal_relations, # Explicit relations if available "metrics": metrics } except Exception as e: return {"error": str(e)} @mcp.tool() def analyze_play_structure(corpus_name: str, play_name: str) -> Dict: """Analyze the structure of a play including acts, scenes, and metrics.""" try: play = api_request(f"corpora/{corpus_name}/plays/{play_name}") metrics = api_request(f"corpora/{corpus_name}/plays/{play_name}/metrics") # Extract structural information from segments acts = [] scenes = [] for segment in play.get("segments", []): if segment.get("type") == "act": acts.append({ "number": segment.get("number"), "title": segment.get("title") }) elif segment.get("type") == "scene": scenes.append({ "number": segment.get("number"), "title": segment.get("title"), "speakers": segment.get("speakers", []) }) # Get character data characters = api_request(f"corpora/{corpus_name}/plays/{play_name}/characters") # Count characters by gender gender_counts = {"MALE": 0, "FEMALE": 0, "UNKNOWN": 0} for character in characters: gender = character.get("gender") if gender in gender_counts: gender_counts[gender] += 1 # Get spoken text by character data spoken_text_by_char = api_request(f"corpora/{corpus_name}/plays/{play_name}/spoken-text-by-character") # Calculate total words and distribution total_words = sum(char.get("numOfWords", 0) for char in characters) speaking_distribution = [] if total_words > 0: for char in characters: char_words = char.get("numOfWords", 0) speaking_distribution.append({ "character": char.get("name"), "words": char_words, "percentage": round((char_words / total_words) * 100, 2) }) # Sort by word count speaking_distribution.sort(key=lambda x: x["words"], reverse=True) # Get structural information structure = { "title": play.get("title"), "authors": [author.get("name") for author in play.get("authors", [])], "year": play.get("yearNormalized"), "yearWritten": play.get("yearWritten"), "yearPrinted": play.get("yearPrinted"), "yearPremiered": play.get("yearPremiered"), "acts": acts, "scenes": scenes, "numOfActs": len(acts), "numOfScenes": len(scenes), "segments": metrics.get("segments"), "dialogues": metrics.get("dialogues"), "wordCount": total_words, "characters": { "total": len(characters), "byGender": gender_counts }, "speakingDistribution": speaking_distribution[:10], # Top 10 characters by speaking time } return structure except Exception as e: return {"error": str(e)} @mcp.tool() def find_character_across_plays(character_name: str) -> Dict: """Find a character across multiple plays in the DraCor database.""" try: all_corpora = api_request("corpora") matches = [] for corpus in all_corpora: corpus_name = corpus["name"] corpus_data = api_request(f"corpora/{corpus_name}") for play in corpus_data.get("plays", []): play_name = play.get("name") try: characters = api_request(f"corpora/{corpus_name}/plays/{play_name}/characters") for character in characters: if character_name.lower() in (character.get("name") or "").lower(): matches.append({ "corpus": corpus_name, "play": play.get("title"), "character": character.get("name"), "gender": character.get("gender"), "numOfSpeechActs": character.get("numOfSpeechActs"), "numOfWords": character.get("numOfWords") }) except: continue return {"matches": matches} except Exception as e: return {"error": str(e)} @mcp.tool("analyze_full_text") def analyze_full_text(corpus_name: str, play_name: str) -> Dict: """Analyze the full text of a play, including dialogue and stage directions.""" try: # Get the TEI XML as primary source tei_result = get_tei_text(corpus_name, play_name) if "error" in tei_result: # Fall back to the plain text if TEI fails full_text = get_full_text(corpus_name, play_name) if "error" in full_text: return {"error": full_text["error"]} has_tei = False text_content = full_text["text"] else: has_tei = True tei_text = tei_result["tei_text"] # Simple XML parsing to extract basic structure # In a production environment, use a proper XML parser library import re # Extract title title_match = re.search(r'<title[^>]*>([^<]+)</title>', tei_text) title = title_match.group(1) if title_match else "Unknown" # Extract author(s) author_matches = re.findall(r'<author[^>]*>([^<]+)</author>', tei_text) authors = author_matches if author_matches else ["Unknown"] # Extract acts acts = re.findall(r'<div type="act"[^>]*>(.*?)</div>', tei_text, re.DOTALL) act_count = len(acts) # Extract scenes scenes = re.findall(r'<div type="scene"[^>]*>(.*?)</div>', tei_text, re.DOTALL) scene_count = len(scenes) # Extract speeches speeches = re.findall(r'<sp[^>]*>(.*?)</sp>', tei_text, re.DOTALL) speech_count = len(speeches) # Extract stage directions stage_directions = re.findall(r'<stage[^>]*>(.*?)</stage>', tei_text, re.DOTALL) stage_direction_count = len(stage_directions) # Also get the plain text for easier processing full_text = get_full_text(corpus_name, play_name) text_content = full_text.get("text", "") # Get play metadata play_info = get_play(corpus_name, play_name) if "error" in play_info: return {"error": play_info["error"]} # Get character list characters = get_characters(corpus_name, play_name) if "error" in characters: return {"error": characters["error"]} result = { "play": play_info.get("play", {}), "characters": characters.get("characters", []), "text": text_content, } # Add TEI-specific analysis if available if has_tei: result["tei_analysis"] = { "title": title, "authors": authors, "structure": { "acts": act_count, "scenes": scene_count, "speeches": speech_count, "stage_directions": stage_direction_count }, "text_sample": { "first_speech": speeches[0] if speeches else "", "first_stage_direction": stage_directions[0] if stage_directions else "" } } # Add basic text analysis in either case result["analysis"] = { "text_length": len(text_content), "character_count": len(characters.get("characters", [])), "dialogue_to_direction_ratio": text_content.count("\n\nDIALOGUE:") / (text_content.count("\n\nSTAGE DIRECTIONS:") or 1) } return result except Exception as e: return {"error": str(e)} # Prompt templates using decorators @mcp.prompt() def analyze_play(corpus_name: str, play_name: str) -> str: """Create a prompt for analyzing a specific play.""" return f""" You are a drama analysis expert who can help analyze plays from the DraCor (Drama Corpora Project) database. You have access to the following play: Corpus: {corpus_name} Play: {play_name} Analyze this play in terms of: 1. Basic information (title, author, year) 2. Structure (acts, scenes) 3. Character relationships 4. Key metrics and statistics Please provide a comprehensive analysis including: - Historical context of the play - Structural analysis - Character analysis - Network analysis (how characters relate to each other) - Notable aspects of this play compared to others from the same period """ @mcp.prompt() def character_analysis(corpus_name: str, play_name: str, character_id: str) -> str: """Create a prompt for analyzing a specific character.""" return f""" You are a drama character analysis expert who can help analyze characters from plays in the DraCor database. You have access to the following character: Corpus: {corpus_name} Play: {play_name} Character: {character_id} Analyze this character in terms of: 1. Basic information (name, gender) 2. Importance in the play (based on speech counts, words spoken) 3. Relationships with other characters 4. Character development throughout the play Please provide a comprehensive character analysis that could help researchers or students understand this character better. """ @mcp.prompt() def network_analysis(corpus_name: str, play_name: str) -> str: """Create a prompt for analyzing a character network.""" return f""" You are a network analysis expert who can help analyze character networks from plays in the DraCor database. You have access to the following play network: Corpus: {corpus_name} Play: {play_name} Analyze this play's character network in terms of: 1. Overall network structure and density 2. Central characters (highest degree, betweenness) 3. Character communities or groups 4. Strongest and weakest relationships 5. How the network structure relates to the themes of the play Please provide a comprehensive network analysis that could help researchers understand the social dynamics in this play. """ @mcp.prompt() def comparative_analysis(corpus_name1: str, play_name1: str, corpus_name2: str, play_name2: str) -> str: """Create a prompt for comparing two plays.""" return f""" You are a drama analysis expert who can help compare plays from the DraCor database. You have access to the following two plays: Play 1: Corpus: {corpus_name1} Play: {play_name1} Play 2: Corpus: {corpus_name2} Play: {play_name2} Compare these plays in terms of: 1. Basic information (title, author, year) 2. Structure (acts, scenes, length) 3. Character count and dynamics 4. Network complexity and density 5. Historical context and significance Please provide a comprehensive comparative analysis that highlights similarities and differences between these plays. """ @mcp.prompt() def gender_analysis(corpus_name: str, play_name: str) -> str: """Create a prompt for analyzing gender representation in a play.""" return f""" You are a scholar specializing in gender studies and dramatic literature. You've been asked to analyze gender representation in a drama. Corpus: {corpus_name} Play: {play_name} Please analyze the play in terms of: 1. Gender distribution of characters 2. Speaking time and importance of male vs. female characters 3. Relationships between characters of different genders 4. Historical context of gender representation in this period 5. Notable aspects of gender portrayal in this play Your analysis should consider both quantitative data (number of characters, speaking lines) and qualitative aspects (power dynamics, character development). """ @mcp.prompt() def historical_context(corpus_name: str, play_name: str) -> str: """Create a prompt for analyzing the historical context of a play.""" return f""" You are a theater historian who specializes in putting dramatic works in their historical context. Corpus: {corpus_name} Play: {play_name} Please provide a detailed analysis of the historical context of this play, including: 1. Political and social climate when the play was written 2. Theatrical conventions of the period 3. How contemporary events might have influenced the play 4. Reception of the play when it was first performed 5. The play's significance in the author's body of work 6. How the play reflects or challenges the values of its time Your analysis should help modern readers and scholars understand the play within its original historical framework. """ @mcp.prompt("full_text_analysis") def full_text_analysis_prompt() -> str: """Template for analyzing the full text of a play.""" return """ I'll analyze the full text of {play_title} by {author} from the {corpus_name} corpus. ## Basic Information - Title: {play_title} - Author: {author} - Written: {written_year} - Premiere: {premiere_date} ## Full Text Analysis {analysis} ## Key Themes and Motifs {themes} ## Language and Style {style} ## Historical and Cultural Context {context} """ @mcp.prompt("character_tagging_analysis") def character_tagging_analysis(corpus_name: str = "dutch", play_name: str = None) -> str: """Template for analyzing character ID tagging issues in plays. Parameters: - corpus_name: The corpus to analyze (default: "dutch") - play_name: The specific play to analyze """ prompt_text = f""" Your task is to analyze '{play_name}' from the {corpus_name} corpus in the DraCor database to identify character ID tagging issues. Specifically: 1. Perform a comprehensive analysis of: * Character relations * Full text (especially TEI format) * Play structure 2. Identify all possible inconsistencies in character ID tagging, including: * Spelling variations of character names * Character name confusion or conflation * Historical spelling variants * Discrepancies between character IDs and stage directions 3. Create a detailed report of potential character ID tagging errors in a structured table format with the following columns: * Text ID: {corpus_name}/{play_name} * Current character ID used in the database * Problematic variant(s) found in the text * Type of error (spelling, variation, confusion, etc.) * Explanation of the issue """ # If no specific play is provided, add instructions to select one if not play_name: prompt_text = """ Your task is to analyze a play from the {corpus_name} corpus in the DraCor database to identify character ID tagging issues. First, use the search_plays tool to find available plays in the {corpus_name} corpus, then select one for analysis. Once you've selected a play, perform a comprehensive analysis of: 1. Character relations 2. Full text (especially TEI format) 3. Play structure Identify all possible inconsistencies in character ID tagging, including: * Spelling variations of character names * Character name confusion or conflation * Historical spelling variants * Discrepancies between character IDs and stage directions Create a detailed report of potential character ID tagging errors in a structured table format with the following columns: * Text ID (unique identifier for the play) * Current character ID used in the database * Problematic variant(s) found in the text * Type of error (spelling, variation, confusion, etc.) * Explanation of the issue """ return prompt_text if __name__ == "__main__": mcp.run()