Jofthomas commited on
Commit
e743ebd
·
verified ·
1 Parent(s): 3ccc294

Update geoguessr/geo_server.py

Browse files
Files changed (1) hide show
  1. geoguessr/geo_server.py +235 -5
geoguessr/geo_server.py CHANGED
@@ -1,7 +1,237 @@
1
- from mcp.server.fastmcp import FastMCP
 
 
 
 
2
 
3
- mcp = FastMCP(name="EchoServer", stateless_http=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
- @mcp.tool(description="A simple echo tool")
6
- def echo(message: str) -> str:
7
- return f"Echo: {message}"
 
1
+ import os
2
+ import requests
3
+ import base64
4
+ from dotenv import load_dotenv
5
+ from mcp.server.fastmcp import FastMCP, Image
6
 
7
+ mcp = FastMCP(name="GeoServer", stateless_http=True)
8
+
9
+
10
+ # --- Game State Management ---
11
+ # Store the current game ID and basic state
12
+ active_game = {}
13
+
14
+ # --- Flask API Helper Functions ---
15
+ def call_flask_api(endpoint, method='GET', json_data=None):
16
+ """Helper function to call Flask API endpoints"""
17
+ url = f"{FLASK_API_URL}{endpoint}"
18
+ try:
19
+ if method == 'POST':
20
+ response = requests.post(url, json=json_data, headers={'Content-Type': 'application/json'}, timeout=30)
21
+ else:
22
+ response = requests.get(url, timeout=30)
23
+
24
+ if response.status_code in [200, 201]:
25
+ return response.json()
26
+ else:
27
+ error_msg = f"API call failed: {response.status_code} - {response.text}"
28
+ print(f"Flask API Error: {error_msg}")
29
+ raise Exception(error_msg)
30
+ except requests.exceptions.ConnectionError as e:
31
+ error_msg = f"Could not connect to Flask API at {FLASK_API_URL}. Make sure the Flask server is running. Error: {str(e)}"
32
+ print(f"Connection Error: {error_msg}")
33
+ raise Exception(error_msg)
34
+ except requests.exceptions.Timeout as e:
35
+ error_msg = f"Timeout calling Flask API at {FLASK_API_URL}. Error: {str(e)}"
36
+ print(f"Timeout Error: {error_msg}")
37
+ raise Exception(error_msg)
38
+ except Exception as e:
39
+ error_msg = f"API call error: {str(e)}"
40
+ print(f"General Error: {error_msg}")
41
+ raise Exception(error_msg)
42
+
43
+ def base64_to_image_bytes(base64_string):
44
+ """Convert base64 string to image bytes"""
45
+ try:
46
+ if not base64_string:
47
+ raise ValueError("Empty base64 string provided")
48
+
49
+ # Remove any data URL prefix if present
50
+ if base64_string.startswith('data:'):
51
+ base64_string = base64_string.split(',', 1)[1]
52
+
53
+ # Decode the base64 string
54
+ image_bytes = base64.b64decode(base64_string)
55
+
56
+ if len(image_bytes) == 0:
57
+ raise ValueError("Decoded image is empty")
58
+
59
+ print(f"Successfully decoded image: {len(image_bytes)} bytes")
60
+ return image_bytes
61
+
62
+ except Exception as e:
63
+ print(f"Error decoding base64 image: {e}")
64
+ raise ValueError(f"Failed to decode base64 image: {str(e)}")
65
+
66
+ # --- MCP Tools ---
67
+
68
+ @mcp.tool()
69
+ def start_game(difficulty: str = "easy", player_name: str = "MCP Agent") -> Image:
70
+ """
71
+ Starts a new GeoGuessr game by calling the Flask API.
72
+ Args:
73
+ difficulty (str): The difficulty of the game ('easy', 'medium', 'hard').
74
+ player_name (str): The name of the player/agent.
75
+ Returns:
76
+ Image: The first Street View image with compass overlay.
77
+ """
78
+ global active_game
79
+
80
+ # Call Flask API to start game
81
+ game_data = call_flask_api('/start_game', 'POST', {
82
+ 'difficulty': difficulty,
83
+ 'player_name': player_name
84
+ })
85
+
86
+ # Store game state
87
+ active_game = {
88
+ 'game_id': game_data['game_id'],
89
+ 'player_name': game_data['player_name'],
90
+ 'game_over': False
91
+ }
92
+
93
+ # Convert base64 image to bytes and return as Image
94
+ if game_data.get('streetview_image'):
95
+ try:
96
+ image_bytes = base64_to_image_bytes(game_data['streetview_image'])
97
+ print(f"Successfully started game {active_game['game_id']} for player {active_game['player_name']}")
98
+ return Image(data=image_bytes, format="jpeg")
99
+ except Exception as e:
100
+ print(f"Error processing Street View image: {e}")
101
+ raise Exception(f"Failed to process Street View image: {str(e)}")
102
+ else:
103
+ raise Exception("No Street View image received from the game")
104
+
105
+
106
+
107
+ @mcp.tool()
108
+ def move(direction: str = None, degree: float = None, distance: float = 0.1) -> Image:
109
+ """
110
+ Moves the player in a specified direction by calling the Flask API.
111
+ Args:
112
+ direction (str, optional): Direction to move (N, NE, E, SE, S, SW, W, NW).
113
+ degree (float, optional): Precise direction in degrees (0-360).
114
+ distance (float): Distance to move in kilometers (default: 0.1km = 100m).
115
+ Returns:
116
+ Image: The new Street View image with compass overlay.
117
+ """
118
+ global active_game
119
+ if not active_game or not active_game.get('game_id'):
120
+ raise ValueError("Game not started. Call start_game() first.")
121
+ if active_game.get('game_over'):
122
+ raise ValueError("Game is over.")
123
+
124
+
125
+ # Prepare move data
126
+ move_data = {'distance': distance}
127
+ if direction:
128
+ move_data['direction'] = direction
129
+ elif degree is not None:
130
+ move_data['degree'] = degree
131
+ else:
132
+ raise ValueError("Must provide either direction or degree parameter.")
133
+
134
+ # Call Flask API to move
135
+ game_id = active_game['game_id']
136
+ move_result = call_flask_api(f'/game/{game_id}/move', 'POST', move_data)
137
+
138
+ # Convert base64 image to bytes and return as Image
139
+ if move_result.get('streetview_image'):
140
+ try:
141
+ image_bytes = base64_to_image_bytes(move_result['streetview_image'])
142
+ direction_info = move_result.get('moved_direction', 'unknown direction')
143
+ distance_info = move_result.get('distance_moved_km', 0) * 1000
144
+ print(f"Successfully moved {direction_info} for {distance_info:.0f}m in game {game_id}")
145
+ return Image(data=image_bytes, format="jpeg")
146
+ except Exception as e:
147
+ print(f"Error processing move Street View image: {e}")
148
+ raise Exception(f"Failed to process move Street View image: {str(e)}")
149
+ else:
150
+ raise Exception("No Street View image received from the move")
151
+
152
+ @mcp.tool()
153
+ def make_placeholder_guess(lat: float, lng: float) -> dict:
154
+ """
155
+ Records a temporary guess for the location (stored locally until final guess).
156
+ Args:
157
+ lat (float): The latitude of the guess.
158
+ lng (float): The longitude of the guess.
159
+ Returns:
160
+ dict: A status message.
161
+ """
162
+ global active_game
163
+ if not active_game or not active_game.get('game_id'):
164
+ raise ValueError("Game not started.")
165
+ if active_game.get('game_over'):
166
+ raise ValueError("Game is over.")
167
+
168
+ active_game['placeholder_guess'] = {'lat': lat, 'lng': lng}
169
+ return {"status": "success", "message": f"Placeholder guess recorded: {lat:.6f}, {lng:.6f}"}
170
+
171
+
172
+ @mcp.tool()
173
+ def make_final_guess() -> dict:
174
+ """
175
+ Makes the final guess for the active game by calling the Flask API.
176
+ Uses the stored placeholder guess coordinates.
177
+ Returns:
178
+ dict: The results of the guess including distance, score, and actual location.
179
+ """
180
+ global active_game
181
+ if not active_game or not active_game.get('game_id'):
182
+ raise ValueError("Game not started.")
183
+ if active_game.get('game_over'):
184
+ raise ValueError("Game is already over.")
185
+ if 'placeholder_guess' not in active_game:
186
+ raise ValueError("No placeholder guess was made. Call make_placeholder_guess() first.")
187
+
188
+ # Get the placeholder guess
189
+ guess_location = active_game['placeholder_guess']
190
+
191
+ # Call Flask API to make the final guess
192
+ game_id = active_game['game_id']
193
+ guess_result = call_flask_api(f'/game/{game_id}/guess', 'POST', {
194
+ 'lat': guess_location['lat'],
195
+ 'lng': guess_location['lng']
196
+ })
197
+
198
+ # Mark game as over
199
+ active_game['game_over'] = True
200
+
201
+ return {
202
+ "distance_km": guess_result['distance_km'],
203
+ "score": guess_result['score'],
204
+ "actual_location": guess_result['actual_location'],
205
+ "guess_location": guess_result['guess_location']
206
+ }
207
+
208
+ @mcp.tool()
209
+ def get_game_state() -> dict:
210
+ """
211
+ Gets the current game state from the Flask API.
212
+ Returns:
213
+ dict: Current game state including moves, actions, and game status.
214
+ """
215
+ global active_game
216
+ if not active_game or not active_game.get('game_id'):
217
+ raise ValueError("Game not started.")
218
+
219
+ game_id = active_game['game_id']
220
+ game_state = call_flask_api(f'/game/{game_id}/state')
221
+
222
+ # Don't expose the actual coordinates - keep the guessing challenge
223
+ state_info = {
224
+ "game_id": game_state.get('game_id', game_id),
225
+ "player_name": game_state.get('player_name', active_game.get('player_name')),
226
+ "moves": game_state.get('moves', 0),
227
+ "game_over": game_state.get('game_over', False),
228
+ "total_actions": len(game_state.get('actions', [])),
229
+ "guesses_made": len(game_state.get('guesses', [])),
230
+ "placeholder_guess": active_game.get('placeholder_guess')
231
+ }
232
+
233
+ # Update local game state
234
+ active_game['game_over'] = game_state.get('game_over', False)
235
+
236
+ return state_info
237