ravithejads commited on
Commit
5d905b0
·
verified ·
1 Parent(s): 6d608d0

Upload 4 files

Browse files
Files changed (4) hide show
  1. Dockerfile +25 -0
  2. app.py +320 -0
  3. mcp_server.py +381 -0
  4. requirements.txt +5 -0
Dockerfile ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ # Set working directory
4
+ WORKDIR /app
5
+
6
+ # Copy requirements first for better caching
7
+ COPY requirements.txt .
8
+
9
+ # Install dependencies
10
+ RUN pip install --no-cache-dir -r requirements.txt
11
+
12
+ # Copy all application files
13
+ COPY . .
14
+
15
+ # Expose port 7860 for Hugging Face Spaces
16
+ EXPOSE 7860
17
+
18
+ # Create startup script that runs MCP server only (simpler approach)
19
+ RUN echo '#!/bin/bash\n\
20
+ echo "Starting Tic-Tac-Toe MCP Server on port 7860..."\n\
21
+ echo "This is the MCP server for LeChat integration"\n\
22
+ echo "MCP Tools: create_room, make_move, send_chat, get_room_state, list_rooms"\n\
23
+ python mcp_server.py' > start.sh && chmod +x start.sh
24
+
25
+ CMD ["./start.sh"]
app.py ADDED
@@ -0,0 +1,320 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import uuid
4
+ import time
5
+ from flask import Flask, request, jsonify, render_template, send_from_directory
6
+ from flask_cors import CORS
7
+ import logging
8
+ from mistralai import Mistral
9
+
10
+ # Flask app setup
11
+ app = Flask(__name__)
12
+ CORS(app)
13
+
14
+ # Logging setup
15
+ logging.basicConfig(level=logging.INFO)
16
+ logger = logging.getLogger(__name__)
17
+
18
+ # Initialize Mistral client
19
+ MISTRAL_API_KEY = os.getenv('MISTRAL_API_KEY')
20
+ if not MISTRAL_API_KEY:
21
+ logger.error("MISTRAL_API_KEY not configured")
22
+ exit(1)
23
+
24
+ client = Mistral(api_key=MISTRAL_API_KEY)
25
+
26
+ class Room:
27
+ def __init__(self, room_id=None):
28
+ self.id = room_id or str(uuid.uuid4())[:8]
29
+ self.board = [''] * 9 # 9 positions (0-8)
30
+ self.current_player = 'X' # X = human, O = AI
31
+ self.game_status = 'active' # 'active', 'won', 'draw'
32
+ self.winner = None
33
+ self.chat_history = []
34
+ self.created = time.time()
35
+ self.last_activity = time.time()
36
+ self.moves_count = 0
37
+
38
+ # Add welcome message
39
+ self.chat_history.append({
40
+ 'sender': 'ai',
41
+ 'message': "Hey there! Ready for a game of Tic-Tac-Toe? I'm pretty good at this... 😏 You're X, I'm O. Good luck!",
42
+ 'timestamp': time.time()
43
+ })
44
+
45
+ def make_move(self, position, player):
46
+ """Make a move on the board"""
47
+ if self.game_status != 'active' or self.board[position] != '':
48
+ return False
49
+
50
+ self.board[position] = player
51
+ self.moves_count += 1
52
+ self.last_activity = time.time()
53
+
54
+ # Check for winner
55
+ if self.check_winner():
56
+ self.game_status = 'won'
57
+ self.winner = player
58
+ elif self.moves_count == 9:
59
+ self.game_status = 'draw'
60
+ else:
61
+ self.current_player = 'O' if player == 'X' else 'X'
62
+
63
+ return True
64
+
65
+ def check_winner(self):
66
+ """Check if there's a winner"""
67
+ win_patterns = [
68
+ [0, 1, 2], [3, 4, 5], [6, 7, 8], # rows
69
+ [0, 3, 6], [1, 4, 7], [2, 5, 8], # columns
70
+ [0, 4, 8], [2, 4, 6] # diagonals
71
+ ]
72
+
73
+ for pattern in win_patterns:
74
+ a, b, c = pattern
75
+ if self.board[a] and self.board[a] == self.board[b] == self.board[c]:
76
+ return True
77
+ return False
78
+
79
+ def add_chat_message(self, message, sender):
80
+ """Add a chat message to history"""
81
+ self.chat_history.append({
82
+ 'sender': sender,
83
+ 'message': message,
84
+ 'timestamp': time.time()
85
+ })
86
+ self.last_activity = time.time()
87
+
88
+ def to_markdown(self):
89
+ """Convert game state to markdown for display"""
90
+ markdown = f"# Game Room: {self.id}\n"
91
+ markdown += f"## Status: "
92
+
93
+ if self.game_status == 'won':
94
+ winner_name = "You" if self.winner == 'X' else "Mistral AI"
95
+ markdown += f"Game Over - {winner_name} wins! 🎉\n"
96
+ elif self.game_status == 'draw':
97
+ markdown += "Game Over - It's a draw! 🤝\n"
98
+ else:
99
+ turn_name = "Your turn" if self.current_player == 'X' else "Mistral's turn"
100
+ markdown += f"{turn_name} ({self.current_player} to play)\n"
101
+
102
+ markdown += f"Moves: {self.moves_count}/9\n\n"
103
+
104
+ # Board representation
105
+ markdown += "```\n"
106
+ for i in range(0, 9, 3):
107
+ row = [self.board[i] or '·', self.board[i+1] or '·', self.board[i+2] or '·']
108
+ markdown += f"{row[0]} | {row[1]} | {row[2]}\n"
109
+ if i < 6:
110
+ markdown += "-----------\n"
111
+ markdown += "```\n\n"
112
+
113
+ # Recent chat history
114
+ if self.chat_history:
115
+ markdown += "## Recent Chat\n"
116
+ recent_messages = self.chat_history[-3:] # Last 3 messages
117
+ for msg in recent_messages:
118
+ sender_name = "**You:**" if msg['sender'] == 'user' else "**Mistral AI:**"
119
+ markdown += f"{sender_name} {msg['message']}\n"
120
+
121
+ return markdown
122
+
123
+ def to_dict(self):
124
+ """Convert room to dictionary"""
125
+ return {
126
+ 'id': self.id,
127
+ 'board': self.board,
128
+ 'current_player': self.current_player,
129
+ 'game_status': self.game_status,
130
+ 'winner': self.winner,
131
+ 'chat_history': self.chat_history,
132
+ 'moves_count': self.moves_count,
133
+ 'created': self.created,
134
+ 'last_activity': self.last_activity
135
+ }
136
+
137
+ # In-memory room storage
138
+ rooms = {}
139
+
140
+ def get_ai_move_for_room(room):
141
+ """Get AI move from Mistral"""
142
+ board_string = ""
143
+ for i in range(0, 9, 3):
144
+ row = [room.board[i] or ' ', room.board[i+1] or ' ', room.board[i+2] or ' ']
145
+ board_string += f"{row[0]} | {row[1]} | {row[2]}\n"
146
+ if i < 6:
147
+ board_string += "---------\n"
148
+
149
+ messages = [
150
+ {
151
+ "role": "system",
152
+ "content": """You are a competitive Tic-Tac-Toe AI with personality. You play as 'O' and the human plays as 'X'.
153
+
154
+ Rules:
155
+ 1. Analyze the board and choose your best move (0-8, left to right, top to bottom)
156
+ 2. Add a short, witty comment about your move or the game state
157
+ 3. Be competitive but fun - trash talk, celebrate good moves, react to the situation
158
+ 4. Keep messages under 50 words
159
+ 5. Use emojis occasionally
160
+
161
+ ALWAYS respond with valid JSON in this exact format:
162
+ {"move": [0-8], "message": "your witty comment"}
163
+
164
+ Board positions:
165
+ 0 | 1 | 2
166
+ ---------
167
+ 3 | 4 | 5
168
+ ---------
169
+ 6 | 7 | 8"""
170
+ },
171
+ {
172
+ "role": "user",
173
+ "content": f"Current board:\n{board_string}\n\nBoard array: {room.board}"
174
+ }
175
+ ]
176
+
177
+ response = client.chat.complete(
178
+ model="mistral-large-latest",
179
+ messages=messages,
180
+ temperature=0.1,
181
+ response_format={"type": "json_object"}
182
+ )
183
+
184
+ return json.loads(response.choices[0].message.content)
185
+
186
+ def get_ai_chat_for_room(room, user_message):
187
+ """Get AI chat response"""
188
+ board_string = ""
189
+ for i in range(0, 9, 3):
190
+ row = [room.board[i] or ' ', room.board[i+1] or ' ', room.board[i+2] or ' ']
191
+ board_string += f"{row[0]} | {row[1]} | {row[2]}\n"
192
+ if i < 6:
193
+ board_string += "---------\n"
194
+
195
+ messages = [
196
+ {
197
+ "role": "system",
198
+ "content": f"""You are a competitive, witty Tic-Tac-Toe AI with personality. You're currently playing a game.
199
+
200
+ Current board state:
201
+ {board_string}
202
+
203
+ Respond to the human's message with personality - be competitive, funny, encouraging, or trash-talking as appropriate.
204
+ Keep responses under 50 words. Use emojis occasionally. Don't make game moves in chat - that happens separately."""
205
+ },
206
+ {
207
+ "role": "user",
208
+ "content": user_message
209
+ }
210
+ ]
211
+
212
+ response = client.chat.complete(
213
+ model="mistral-large-latest",
214
+ messages=messages
215
+ )
216
+
217
+ return response.choices[0].message.content
218
+
219
+ # Room management endpoints
220
+ @app.route('/rooms', methods=['POST'])
221
+ def create_room():
222
+ """Create a new game room"""
223
+ room = Room()
224
+ rooms[room.id] = room
225
+ logger.info(f"Created room: {room.id}")
226
+ return jsonify({
227
+ 'room_id': room.id,
228
+ 'status': 'created',
229
+ 'room_data': room.to_dict()
230
+ })
231
+
232
+ @app.route('/rooms/<room_id>', methods=['GET'])
233
+ def get_room(room_id):
234
+ """Get room state"""
235
+ if room_id not in rooms:
236
+ return jsonify({'error': 'Room not found'}), 404
237
+
238
+ room = rooms[room_id]
239
+ return jsonify({
240
+ 'room_id': room_id,
241
+ 'room_data': room.to_dict(),
242
+ 'markdown': room.to_markdown()
243
+ })
244
+
245
+ @app.route('/rooms/<room_id>/move', methods=['POST'])
246
+ def make_room_move(room_id):
247
+ """Make a move in the game"""
248
+ if room_id not in rooms:
249
+ return jsonify({'error': 'Room not found'}), 404
250
+
251
+ room = rooms[room_id]
252
+ data = request.json
253
+ position = data.get('position')
254
+
255
+ if position is None or position < 0 or position > 8:
256
+ return jsonify({'error': 'Invalid position'}), 400
257
+
258
+ # Make human move
259
+ if not room.make_move(position, 'X'):
260
+ return jsonify({'error': 'Invalid move'}), 400
261
+
262
+ # Check if game ended
263
+ if room.game_status != 'active':
264
+ return jsonify({
265
+ 'room_data': room.to_dict(),
266
+ 'markdown': room.to_markdown(),
267
+ 'ai_move': None
268
+ })
269
+
270
+ # Get AI move
271
+ try:
272
+ ai_response = get_ai_move_for_room(room)
273
+ if ai_response and 'move' in ai_response:
274
+ ai_move = ai_response['move']
275
+ if 0 <= ai_move <= 8 and room.board[ai_move] == '':
276
+ room.make_move(ai_move, 'O')
277
+ if 'message' in ai_response:
278
+ room.add_chat_message(ai_response['message'], 'ai')
279
+
280
+ return jsonify({
281
+ 'room_data': room.to_dict(),
282
+ 'markdown': room.to_markdown(),
283
+ 'ai_move': ai_response
284
+ })
285
+ except Exception as e:
286
+ logger.error(f"AI move failed: {e}")
287
+ return jsonify({'error': 'AI move failed'}), 500
288
+
289
+ @app.route('/rooms/<room_id>/chat', methods=['POST'])
290
+ def room_chat(room_id):
291
+ """Send a chat message"""
292
+ if room_id not in rooms:
293
+ return jsonify({'error': 'Room not found'}), 404
294
+
295
+ room = rooms[room_id]
296
+ data = request.json
297
+ user_message = data.get('message', '')
298
+
299
+ if not user_message.strip():
300
+ return jsonify({'error': 'Empty message'}), 400
301
+
302
+ # Add user message
303
+ room.add_chat_message(user_message, 'user')
304
+
305
+ # Get AI response
306
+ try:
307
+ ai_response = get_ai_chat_for_room(room, user_message)
308
+ room.add_chat_message(ai_response, 'ai')
309
+
310
+ return jsonify({
311
+ 'room_data': room.to_dict(),
312
+ 'markdown': room.to_markdown(),
313
+ 'ai_response': ai_response
314
+ })
315
+ except Exception as e:
316
+ logger.error(f"AI chat failed: {e}")
317
+ return jsonify({'error': 'AI chat failed'}), 500
318
+
319
+ if __name__ == '__main__':
320
+ app.run(host='0.0.0.0', port=500, debug=True)
mcp_server.py ADDED
@@ -0,0 +1,381 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import asyncio
3
+ from dotenv import load_dotenv
4
+ from mcp.server.fastmcp import FastMCP
5
+ from app import Room, rooms, get_ai_move_for_room, get_ai_chat_for_room
6
+
7
+ # Load environment variables
8
+ load_dotenv()
9
+
10
+ # Initialize MCP server
11
+ mcp = FastMCP(
12
+ name="TicTacToeRooms",
13
+ host="0.0.0.0",
14
+ port=7860, # Standard port for Hugging Face Spaces
15
+ )
16
+
17
+ # Global session state for the current user
18
+ current_session = {
19
+ 'active_room_id': None,
20
+ 'username': 'MCPPlayer'
21
+ }
22
+
23
+ @mcp.tool()
24
+ def create_room() -> dict:
25
+ """
26
+ Create a new tic-tac-toe game room.
27
+ Returns:
28
+ dict: Room information including room ID and initial markdown state
29
+ """
30
+ global current_session
31
+ try:
32
+ room = Room()
33
+ rooms[room.id] = room
34
+
35
+ # Set as active room for this session
36
+ current_session['active_room_id'] = room.id
37
+
38
+ return {
39
+ "status": "success",
40
+ "room_id": room.id,
41
+ "message": f"Created new tic-tac-toe room: {room.id}",
42
+ "markdown_state": room.to_markdown(),
43
+ "instructions": "Use make_move() to play or send_chat() to talk with Mistral AI",
44
+ "game_info": {
45
+ "your_symbol": "X",
46
+ "ai_symbol": "O",
47
+ "board_positions": "0-8 (left to right, top to bottom)"
48
+ }
49
+ }
50
+ except Exception as e:
51
+ return {
52
+ "status": "error",
53
+ "message": f"Failed to create room: {str(e)}"
54
+ }
55
+
56
+ @mcp.tool()
57
+ def get_room_state(room_id: str = None) -> dict:
58
+ """
59
+ Get the current state of a tic-tac-toe room in markdown format.
60
+ Args:
61
+ room_id (str, optional): Room ID to check (uses current active room if not provided)
62
+ Returns:
63
+ dict: Current room state with markdown representation
64
+ """
65
+ global current_session
66
+ try:
67
+ # Use provided room_id or current active room
68
+ target_room_id = room_id or current_session.get('active_room_id')
69
+
70
+ if not target_room_id:
71
+ return {
72
+ "status": "error",
73
+ "message": "No active room. Create a room first using create_room()."
74
+ }
75
+
76
+ if target_room_id not in rooms:
77
+ return {
78
+ "status": "error",
79
+ "message": f"Room {target_room_id} not found. It may have been cleaned up."
80
+ }
81
+
82
+ room = rooms[target_room_id]
83
+
84
+ return {
85
+ "status": "success",
86
+ "room_id": target_room_id,
87
+ "markdown_state": room.to_markdown(),
88
+ "game_status": room.game_status,
89
+ "current_player": room.current_player,
90
+ "moves_made": room.moves_count,
91
+ "your_turn": room.current_player == 'X' and room.game_status == 'active'
92
+ }
93
+ except Exception as e:
94
+ return {
95
+ "status": "error",
96
+ "message": f"Failed to get room state: {str(e)}"
97
+ }
98
+
99
+ @mcp.tool()
100
+ async def make_move(position: int, room_id: str = None) -> dict:
101
+ """
102
+ Make a move in a tic-tac-toe game. This will also trigger the AI's response move.
103
+ Args:
104
+ position (int): Board position (0-8, left to right, top to bottom)
105
+ room_id (str, optional): Room ID (uses current active room if not provided)
106
+ Returns:
107
+ dict: Result of your move and the AI's response with updated game state
108
+ """
109
+ global current_session
110
+ try:
111
+ # Use provided room_id or current active room
112
+ target_room_id = room_id or current_session.get('active_room_id')
113
+
114
+ if not target_room_id:
115
+ return {
116
+ "status": "error",
117
+ "message": "No active room. Create a room first using create_room()."
118
+ }
119
+
120
+ if target_room_id not in rooms:
121
+ return {
122
+ "status": "error",
123
+ "message": f"Room {target_room_id} not found."
124
+ }
125
+
126
+ room = rooms[target_room_id]
127
+
128
+ # Validate move
129
+ if position < 0 or position > 8:
130
+ return {
131
+ "status": "error",
132
+ "message": "Invalid position. Use 0-8 (left to right, top to bottom)."
133
+ }
134
+
135
+ if room.game_status != 'active':
136
+ return {
137
+ "status": "error",
138
+ "message": f"Game is over. Status: {room.game_status}",
139
+ "markdown_state": room.to_markdown()
140
+ }
141
+
142
+ if room.current_player != 'X':
143
+ return {
144
+ "status": "error",
145
+ "message": "It's not your turn! Wait for AI to move.",
146
+ "markdown_state": room.to_markdown()
147
+ }
148
+
149
+ # Make human move
150
+ if not room.make_move(position, 'X'):
151
+ return {
152
+ "status": "error",
153
+ "message": f"Invalid move! Position {position} may already be occupied.",
154
+ "markdown_state": room.to_markdown()
155
+ }
156
+
157
+ result_message = f"✅ You played X at position {position}\n\n"
158
+
159
+ # Check if game ended after human move
160
+ if room.game_status != 'active':
161
+ if room.winner == 'X':
162
+ result_message += "🎉 Congratulations! You won!\n\n"
163
+ else:
164
+ result_message += "🤝 It's a draw!\n\n"
165
+
166
+ result_message += room.to_markdown()
167
+ return {
168
+ "status": "success",
169
+ "message": result_message,
170
+ "game_over": True,
171
+ "winner": room.winner
172
+ }
173
+
174
+ # Get AI move
175
+ try:
176
+ ai_response = get_ai_move_for_room(room)
177
+ if ai_response and 'move' in ai_response:
178
+ ai_move = ai_response['move']
179
+ if 0 <= ai_move <= 8 and room.board[ai_move] == '':
180
+ room.make_move(ai_move, 'O')
181
+ if 'message' in ai_response:
182
+ room.add_chat_message(ai_response['message'], 'ai')
183
+
184
+ result_message += f"🤖 Mistral AI played O at position {ai_response['move']}\n"
185
+ if 'message' in ai_response:
186
+ result_message += f"💬 Mistral says: \"{ai_response['message']}\"\n\n"
187
+ else:
188
+ result_message += "\n"
189
+
190
+ # Check if AI won
191
+ if room.game_status == 'won' and room.winner == 'O':
192
+ result_message += "💀 Mistral AI wins this round!\n\n"
193
+ elif room.game_status == 'draw':
194
+ result_message += "🤝 It's a draw!\n\n"
195
+ else:
196
+ result_message += "⚠️ AI move failed, but you can continue\n\n"
197
+
198
+ except Exception as e:
199
+ result_message += f"⚠️ AI move error: {str(e)}\n\n"
200
+
201
+ result_message += room.to_markdown()
202
+
203
+ return {
204
+ "status": "success",
205
+ "message": result_message,
206
+ "game_over": room.game_status != 'active',
207
+ "winner": room.winner if room.game_status == 'won' else None,
208
+ "your_turn": room.current_player == 'X' and room.game_status == 'active'
209
+ }
210
+
211
+ except Exception as e:
212
+ return {
213
+ "status": "error",
214
+ "message": f"Failed to make move: {str(e)}"
215
+ }
216
+ @mcp.tool()
217
+ async def send_chat(message: str, room_id: str = None) -> dict:
218
+ """
219
+ Send a chat message to Mistral AI in the current game room.
220
+ Args:
221
+ message (str): Your message to send to the AI
222
+ room_id (str, optional): Room ID (uses current active room if not provided)
223
+ Returns:
224
+ dict: Your message and the AI's response with updated room state
225
+ """
226
+ global current_session
227
+ try:
228
+ target_room_id = room_id or current_session.get('active_room_id')
229
+
230
+ if not target_room_id:
231
+ return {
232
+ "status": "error",
233
+ "message": "No active room. Create a room first using create_room()."
234
+ }
235
+
236
+ if target_room_id not in rooms:
237
+ return {
238
+ "status": "error",
239
+ "message": f"Room {target_room_id} not found."
240
+ }
241
+
242
+ room = rooms[target_room_id]
243
+
244
+ # Add user message
245
+ room.add_chat_message(message, 'user')
246
+
247
+ # Get AI response
248
+ ai_response = get_ai_chat_for_room(room, message)
249
+ room.add_chat_message(ai_response, 'ai')
250
+
251
+ result_message = f"💬 **You:** {message}\n💬 **Mistral AI:** {ai_response}\n\n"
252
+ result_message += room.to_markdown()
253
+
254
+ return {
255
+ "status": "success",
256
+ "message": result_message,
257
+ "your_message": message,
258
+ "ai_response": ai_response
259
+ }
260
+
261
+ except Exception as e:
262
+ return {
263
+ "status": "error",
264
+ "message": f"Failed to send chat: {str(e)}"
265
+ }
266
+
267
+ @mcp.tool()
268
+ def list_rooms() -> dict:
269
+ """
270
+ List all active tic-tac-toe game rooms.
271
+ Returns:
272
+ dict: List of active rooms with their status
273
+ """
274
+ try:
275
+ if not rooms:
276
+ return {
277
+ "status": "success",
278
+ "message": "No active rooms. Use create_room() to start a new game!",
279
+ "active_rooms": [],
280
+ "count": 0
281
+ }
282
+
283
+ room_list = []
284
+ for room_id, room in rooms.items():
285
+ room_info = {
286
+ "room_id": room_id,
287
+ "game_status": room.game_status,
288
+ "current_player": room.current_player,
289
+ "moves_count": room.moves_count,
290
+ "winner": room.winner,
291
+ "is_your_turn": room.current_player == 'X' and room.game_status == 'active',
292
+ "is_active": current_session.get('active_room_id') == room_id
293
+ }
294
+ room_list.append(room_info)
295
+
296
+ active_room_id = current_session.get('active_room_id')
297
+ message = f"Found {len(room_list)} active rooms."
298
+ if active_room_id:
299
+ message += f" Current active room: {active_room_id}"
300
+
301
+ return {
302
+ "status": "success",
303
+ "message": message,
304
+ "active_rooms": room_list,
305
+ "count": len(room_list),
306
+ "current_active_room": active_room_id
307
+ }
308
+ except Exception as e:
309
+ return {
310
+ "status": "error",
311
+ "message": f"Failed to list rooms: {str(e)}"
312
+ }
313
+
314
+ @mcp.tool()
315
+ def get_help() -> dict:
316
+ """
317
+ Get help information about playing tic-tac-toe.
318
+ Returns:
319
+ dict: Instructions and tips for playing the game
320
+ """
321
+ return {
322
+ "status": "success",
323
+ "message": "Tic-Tac-Toe Game Help",
324
+ "instructions": {
325
+ "how_to_play": [
326
+ "1. Create a new game room with create_room()",
327
+ "2. Make moves using make_move(position) where position is 0-8",
328
+ "3. Chat with Mistral AI using send_chat('your message')",
329
+ "4. Check game state anytime with get_room_state()"
330
+ ],
331
+ "board_layout": {
332
+ "description": "Board positions (0-8):",
333
+ "layout": [
334
+ "0 | 1 | 2",
335
+ "---------",
336
+ "3 | 4 | 5",
337
+ "---------",
338
+ "6 | 7 | 8"
339
+ ]
340
+ },
341
+ "symbols": {
342
+ "you": "X (you go first)",
343
+ "ai": "O (Mistral AI)"
344
+ },
345
+ "tips": [
346
+ "The AI has personality and will trash talk!",
347
+ "You can have multiple rooms active at once",
348
+ "Use list_rooms() to see all your games"
349
+ ]
350
+ },
351
+ "available_commands": [
352
+ "create_room() - Start a new game",
353
+ "make_move(position) - Make your move (0-8)",
354
+ "send_chat('message') - Chat with AI",
355
+ "get_room_state() - Check current game",
356
+ "list_rooms() - See all active games",
357
+ "get_help() - Show this help"
358
+ ]
359
+ }
360
+
361
+ # Server execution
362
+ if __name__ == "__main__":
363
+ print(f"Tic-Tac-Toe Rooms MCP Server starting on port 7860...")
364
+ print("Available game features:")
365
+ print("- Create multiple game rooms")
366
+ print("- Play against Mistral AI with personality")
367
+ print("- Real-time chat with the AI")
368
+ print("- Markdown state representation")
369
+ print("- Room management")
370
+ print()
371
+ print("MCP Tools available:")
372
+ print("- create_room()")
373
+ print("- make_move(position)")
374
+ print("- send_chat(message)")
375
+ print("- get_room_state()")
376
+ print("- list_rooms()")
377
+ print("- get_help()")
378
+ print()
379
+ print("This MCP server is ready for LeChat integration!")
380
+ print("Running Tic-Tac-Toe MCP server with SSE transport")
381
+ mcp.run(transport="sse")
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ fastmcp
2
+ flask
3
+ flask-cors
4
+ mistralai
5
+ python-dotenv