Abdelkarim Bengrine commited on
Commit ·
d08b8f9
1
Parent(s): f434338
fix: deployment
Browse files- echo_server.py +129 -9
echo_server.py
CHANGED
|
@@ -8,6 +8,7 @@ from typing import List, Tuple, defaultdict, Dict
|
|
| 8 |
from dataclasses import dataclass
|
| 9 |
import io
|
| 10 |
|
|
|
|
| 11 |
@dataclass
|
| 12 |
class CoachNote:
|
| 13 |
move_no: int
|
|
@@ -592,7 +593,9 @@ def _identify_patterns(notes: List[CoachNote]) -> List[MistakePattern]:
|
|
| 592 |
mcp = FastMCP(name="EchoServer", stateless_http=True)
|
| 593 |
|
| 594 |
|
| 595 |
-
@mcp.tool(
|
|
|
|
|
|
|
| 596 |
def get_monthly_analysis(
|
| 597 |
username: str, depth: int = 12, max_games: int = 20
|
| 598 |
) -> List[Dict]:
|
|
@@ -626,9 +629,7 @@ def get_monthly_analysis(
|
|
| 626 |
for i, pgn in enumerate(pgns):
|
| 627 |
print(f"Analyzing game {i+1}/{len(pgns)}...")
|
| 628 |
try:
|
| 629 |
-
notes = analyze_pgn_with_stockfish(
|
| 630 |
-
pgn, depth=depth
|
| 631 |
-
)
|
| 632 |
all_notes.extend(notes)
|
| 633 |
except Exception as e:
|
| 634 |
print(f"Error analyzing game {i+1}: {e}")
|
|
@@ -651,7 +652,9 @@ def get_monthly_analysis(
|
|
| 651 |
]
|
| 652 |
|
| 653 |
|
| 654 |
-
@mcp.tool(
|
|
|
|
|
|
|
| 655 |
def get_human_feedback(pgn_text: str, depth: int = 16) -> List[Dict]:
|
| 656 |
"""
|
| 657 |
Analyze a single game and return detailed human-readable feedback.
|
|
@@ -693,10 +696,10 @@ def get_human_feedback(pgn_text: str, depth: int = 16) -> List[Dict]:
|
|
| 693 |
]
|
| 694 |
|
| 695 |
|
| 696 |
-
@mcp.tool(
|
| 697 |
-
|
| 698 |
-
|
| 699 |
-
) -> List[Dict]:
|
| 700 |
"""
|
| 701 |
Analyze a single game and return classic chess.com style analysis.
|
| 702 |
|
|
@@ -751,6 +754,123 @@ def get_classic_analysis(
|
|
| 751 |
return classic_analysis
|
| 752 |
|
| 753 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 754 |
@mcp.tool(description="Fetch and analyze the most recent finished game for a user")
|
| 755 |
def get_latest_game_analysis(username: str, depth: int = 16) -> Dict:
|
| 756 |
"""
|
|
|
|
| 8 |
from dataclasses import dataclass
|
| 9 |
import io
|
| 10 |
|
| 11 |
+
|
| 12 |
@dataclass
|
| 13 |
class CoachNote:
|
| 14 |
move_no: int
|
|
|
|
| 593 |
mcp = FastMCP(name="EchoServer", stateless_http=True)
|
| 594 |
|
| 595 |
|
| 596 |
+
@mcp.tool(
|
| 597 |
+
description="Analyze all games from the last 30 days and return behavioral patterns"
|
| 598 |
+
)
|
| 599 |
def get_monthly_analysis(
|
| 600 |
username: str, depth: int = 12, max_games: int = 20
|
| 601 |
) -> List[Dict]:
|
|
|
|
| 629 |
for i, pgn in enumerate(pgns):
|
| 630 |
print(f"Analyzing game {i+1}/{len(pgns)}...")
|
| 631 |
try:
|
| 632 |
+
notes = analyze_pgn_with_stockfish(pgn, depth=depth)
|
|
|
|
|
|
|
| 633 |
all_notes.extend(notes)
|
| 634 |
except Exception as e:
|
| 635 |
print(f"Error analyzing game {i+1}: {e}")
|
|
|
|
| 652 |
]
|
| 653 |
|
| 654 |
|
| 655 |
+
@mcp.tool(
|
| 656 |
+
description="Analyze a single game and return detailed human-readable feedback"
|
| 657 |
+
)
|
| 658 |
def get_human_feedback(pgn_text: str, depth: int = 16) -> List[Dict]:
|
| 659 |
"""
|
| 660 |
Analyze a single game and return detailed human-readable feedback.
|
|
|
|
| 696 |
]
|
| 697 |
|
| 698 |
|
| 699 |
+
@mcp.tool(
|
| 700 |
+
description="Analyze a single game and return classic chess.com style analysis"
|
| 701 |
+
)
|
| 702 |
+
def get_classic_analysis(pgn_text: str, depth: int = 16) -> List[Dict]:
|
| 703 |
"""
|
| 704 |
Analyze a single game and return classic chess.com style analysis.
|
| 705 |
|
|
|
|
| 754 |
return classic_analysis
|
| 755 |
|
| 756 |
|
| 757 |
+
import requests
|
| 758 |
+
import re
|
| 759 |
+
import json
|
| 760 |
+
from typing import List, Dict
|
| 761 |
+
from bs4 import BeautifulSoup
|
| 762 |
+
|
| 763 |
+
|
| 764 |
+
def _fetch_tournaments_page() -> str:
|
| 765 |
+
"""Fetch the raw content from the chess tournaments page."""
|
| 766 |
+
url = "https://www.echecsfrance.com/en/tournaments"
|
| 767 |
+
try:
|
| 768 |
+
response = requests.get(url)
|
| 769 |
+
response.raise_for_status()
|
| 770 |
+
return response.text
|
| 771 |
+
except requests.RequestException as e:
|
| 772 |
+
print(f"Error fetching the page: {e}")
|
| 773 |
+
return ""
|
| 774 |
+
|
| 775 |
+
|
| 776 |
+
def _extract_tournament_references(html_content: str) -> List[str]:
|
| 777 |
+
"""Extract all FicheTournoi.aspx?Ref=XXXXX patterns from HTML."""
|
| 778 |
+
# Regex pattern to match FicheTournoi.aspx?Ref= followed by digits
|
| 779 |
+
pattern = r"FicheTournoi\.aspx\?Ref=\d+"
|
| 780 |
+
|
| 781 |
+
# Find all matches
|
| 782 |
+
matches = re.findall(pattern, html_content)
|
| 783 |
+
|
| 784 |
+
# Remove duplicates while preserving order
|
| 785 |
+
unique_matches = list(dict.fromkeys(matches))
|
| 786 |
+
|
| 787 |
+
return unique_matches
|
| 788 |
+
|
| 789 |
+
|
| 790 |
+
def _fetch_tournament_details(tournament_ref: str) -> Dict[str, str]:
|
| 791 |
+
"""Fetch tournament details from a specific tournament page."""
|
| 792 |
+
base_url = "https://www.echecs.asso.fr/"
|
| 793 |
+
full_url = base_url + tournament_ref
|
| 794 |
+
|
| 795 |
+
try:
|
| 796 |
+
response = requests.get(full_url)
|
| 797 |
+
response.raise_for_status()
|
| 798 |
+
|
| 799 |
+
soup = BeautifulSoup(response.text, "html.parser")
|
| 800 |
+
|
| 801 |
+
def extract_field(field_id: str) -> str:
|
| 802 |
+
"""Helper function to extract field value by ID."""
|
| 803 |
+
elem = soup.find("span", {"id": field_id})
|
| 804 |
+
return elem.get_text().strip() if elem else "N/A"
|
| 805 |
+
|
| 806 |
+
# Extract all requested fields
|
| 807 |
+
details = {
|
| 808 |
+
"name": extract_field("ctl00_ContentPlaceHolderMain_LabelNom"),
|
| 809 |
+
"dates": extract_field("ctl00_ContentPlaceHolderMain_LabelDates"),
|
| 810 |
+
"nombre_rondes": extract_field(
|
| 811 |
+
"ctl00_ContentPlaceHolderMain_LabelNbrRondes"
|
| 812 |
+
),
|
| 813 |
+
"cadence": extract_field("ctl00_ContentPlaceHolderMain_LabelCadence"),
|
| 814 |
+
"organisateur": extract_field(
|
| 815 |
+
"ctl00_ContentPlaceHolderMain_LabelOrganisateur"
|
| 816 |
+
),
|
| 817 |
+
"arbitre": extract_field("ctl00_ContentPlaceHolderMain_LabelArbitre"),
|
| 818 |
+
"adresse": extract_field("ctl00_ContentPlaceHolderMain_LabelAdresse"),
|
| 819 |
+
"contact": extract_field("ctl00_ContentPlaceHolderMain_LabelContact"),
|
| 820 |
+
"premier_prix": extract_field("ctl00_ContentPlaceHolderMain_LabelPrix1"),
|
| 821 |
+
"inscription_senior": extract_field(
|
| 822 |
+
"ctl00_ContentPlaceHolderMain_LabelInscriptionSenior"
|
| 823 |
+
),
|
| 824 |
+
"inscription_jeunes": extract_field(
|
| 825 |
+
"ctl00_ContentPlaceHolderMain_LabelInscriptionJeune"
|
| 826 |
+
),
|
| 827 |
+
"annonce": extract_field("ctl00_ContentPlaceHolderMain_LabelAnnonce"),
|
| 828 |
+
"url": full_url,
|
| 829 |
+
}
|
| 830 |
+
|
| 831 |
+
return details
|
| 832 |
+
except requests.RequestException as e:
|
| 833 |
+
print(f"Error fetching tournament {tournament_ref}: {e}")
|
| 834 |
+
return {
|
| 835 |
+
"name": "Error",
|
| 836 |
+
"dates": "Error",
|
| 837 |
+
"nombre_rondes": "Error",
|
| 838 |
+
"cadence": "Error",
|
| 839 |
+
"organisateur": "Error",
|
| 840 |
+
"arbitre": "Error",
|
| 841 |
+
"adresse": "Error",
|
| 842 |
+
"contact": "Error",
|
| 843 |
+
"premier_prix": "Error",
|
| 844 |
+
"inscription_senior": "Error",
|
| 845 |
+
"inscription_jeunes": "Error",
|
| 846 |
+
"annonce": "Error",
|
| 847 |
+
"url": full_url,
|
| 848 |
+
}
|
| 849 |
+
|
| 850 |
+
|
| 851 |
+
@mcp.tool(description="Get the latest tournaments data")
|
| 852 |
+
def get_tournaments_data():
|
| 853 |
+
html_content = _fetch_tournaments_page()
|
| 854 |
+
|
| 855 |
+
if not html_content:
|
| 856 |
+
return {"total_tournaments": 0, "tournaments": []}
|
| 857 |
+
|
| 858 |
+
tournament_refs = _extract_tournament_references(html_content)
|
| 859 |
+
|
| 860 |
+
tournament_details = []
|
| 861 |
+
|
| 862 |
+
for ref in tournament_refs:
|
| 863 |
+
details = _fetch_tournament_details(ref)
|
| 864 |
+
tournament_details.append(details)
|
| 865 |
+
|
| 866 |
+
result = {
|
| 867 |
+
"total_tournaments": len(tournament_details),
|
| 868 |
+
"tournaments": tournament_details,
|
| 869 |
+
}
|
| 870 |
+
|
| 871 |
+
return result
|
| 872 |
+
|
| 873 |
+
|
| 874 |
@mcp.tool(description="Fetch and analyze the most recent finished game for a user")
|
| 875 |
def get_latest_game_analysis(username: str, depth: int = 16) -> Dict:
|
| 876 |
"""
|