chess-mcp-server 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chess_mcp_server-0.1.0/.gitignore +47 -0
- chess_mcp_server-0.1.0/LICENSE +21 -0
- chess_mcp_server-0.1.0/PKG-INFO +111 -0
- chess_mcp_server-0.1.0/README.md +91 -0
- chess_mcp_server-0.1.0/docs/analysis/feasibility_report.md +160 -0
- chess_mcp_server-0.1.0/docs/chess_logic.md +161 -0
- chess_mcp_server-0.1.0/docs/chess_render.md +143 -0
- chess_mcp_server-0.1.0/docs/mcp_reference_en.md +575 -0
- chess_mcp_server-0.1.0/docs/spec/tools.md +124 -0
- chess_mcp_server-0.1.0/docs/tool_implementation_guide.md +60 -0
- chess_mcp_server-0.1.0/inspector.sh +19 -0
- chess_mcp_server-0.1.0/pyproject.toml +36 -0
- chess_mcp_server-0.1.0/requirements.txt +5 -0
- chess_mcp_server-0.1.0/src/__init__.py +0 -0
- chess_mcp_server-0.1.0/src/chess_engine.py +132 -0
- chess_mcp_server-0.1.0/src/game_state.py +134 -0
- chess_mcp_server-0.1.0/src/mcp_server.py +215 -0
- chess_mcp_server-0.1.0/src/rendering.py +233 -0
- chess_mcp_server-0.1.0/src/web_dashboard.py +66 -0
- chess_mcp_server-0.1.0/tests/test_chess_server.py +73 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
develop-eggs/
|
|
9
|
+
dist/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
.eggs/
|
|
13
|
+
lib/
|
|
14
|
+
lib64/
|
|
15
|
+
parts/
|
|
16
|
+
sdist/
|
|
17
|
+
var/
|
|
18
|
+
wheels/
|
|
19
|
+
*.egg-info/
|
|
20
|
+
.installed.cfg
|
|
21
|
+
*.egg
|
|
22
|
+
MANIFEST
|
|
23
|
+
|
|
24
|
+
# Virtual Environments
|
|
25
|
+
.env
|
|
26
|
+
.venv
|
|
27
|
+
env/
|
|
28
|
+
venv/
|
|
29
|
+
ENV/
|
|
30
|
+
env.bak/
|
|
31
|
+
venv.bak/
|
|
32
|
+
|
|
33
|
+
# Environment Variables
|
|
34
|
+
.env
|
|
35
|
+
.env.*
|
|
36
|
+
|
|
37
|
+
# IDEs
|
|
38
|
+
.vscode/
|
|
39
|
+
.idea/
|
|
40
|
+
*.swp
|
|
41
|
+
|
|
42
|
+
# OS
|
|
43
|
+
.DS_Store
|
|
44
|
+
Thumbs.db
|
|
45
|
+
|
|
46
|
+
# MCP
|
|
47
|
+
.gemini/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fritzprix
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: chess-mcp-server
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Model Context Protocol (MCP) server for playing Chess with AI Agents.
|
|
5
|
+
Project-URL: Homepage, https://github.com/fritzprix/chess-mcp-server
|
|
6
|
+
Project-URL: Issues, https://github.com/fritzprix/chess-mcp-server/issues
|
|
7
|
+
Author-email: fritzprix <your-email@example.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Requires-Dist: fastapi
|
|
15
|
+
Requires-Dist: jinja2
|
|
16
|
+
Requires-Dist: mcp[cli]
|
|
17
|
+
Requires-Dist: python-chess
|
|
18
|
+
Requires-Dist: uvicorn
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# ♟️ Chess MCP Server
|
|
22
|
+
|
|
23
|
+
**Give your AI Agent eyes to see the board and hands to make the move.**
|
|
24
|
+
|
|
25
|
+
This is not just a chess API. It's a **Model Context Protocol (MCP)** server designed to let Large Language Models (LLMs) like Claude play chess *agentically*.
|
|
26
|
+
|
|
27
|
+
Capable of visualizing the board in real-time HTML, understanding spatial relationships via Markdown, and challenging you with a hybrid difficulty engine (Levels 1-10)—or simply facilitating a game between you and your Agent.
|
|
28
|
+
|
|
29
|
+
## 🚀 Features
|
|
30
|
+
|
|
31
|
+
- **MCP-UI Support**: Interactive HTML board embedded directly in the chat (where supported).
|
|
32
|
+
- **Hybrid AI Engine**: Adjustable difficulty from "Random Blunderer" (Level 1) to "Minimax Master" (Level 10).
|
|
33
|
+
- **Agent vs. Agent**: Let two AI personalities battle it out.
|
|
34
|
+
- **Web Dashboard**: Automatically launches a local sidecar dashboard (`http://localhost:8080`) to monitor all active games.
|
|
35
|
+
|
|
36
|
+
## 📦 Installation
|
|
37
|
+
|
|
38
|
+
### Prerequisites
|
|
39
|
+
- Python 3.10+
|
|
40
|
+
- An MCP Client (e.g., [Claude Desktop](https://claude.ai/download), [Cursor](https://cursor.sh/))
|
|
41
|
+
|
|
42
|
+
### 1. Installation
|
|
43
|
+
You can install directly from PyPI:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install chess-mcp-server
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 2. Configure MCP Client
|
|
50
|
+
|
|
51
|
+
Add the following to your MCP Client configuration file (e.g., `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"mcpServers": {
|
|
56
|
+
"chess": {
|
|
57
|
+
"command": "uvx",
|
|
58
|
+
"args": ["chess-mcp-server"]
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
*Alternatively, using pip installation:*
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"mcpServers": {
|
|
68
|
+
"chess": {
|
|
69
|
+
"command": "python",
|
|
70
|
+
"args": ["-m", "src.mcp_server"]
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## 🛠️ Development
|
|
77
|
+
|
|
78
|
+
If you want to modify the code:
|
|
79
|
+
|
|
80
|
+
1. **Clone & Setup**
|
|
81
|
+
```bash
|
|
82
|
+
git clone https://github.com/fritzprix/chess-mcp-server.git
|
|
83
|
+
cd chess-mcp-server
|
|
84
|
+
|
|
85
|
+
python -m venv .venv
|
|
86
|
+
source .venv/bin/activate
|
|
87
|
+
pip install -r requirements.txt
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## 🎮 How to Play
|
|
91
|
+
|
|
92
|
+
Once the server is connected, you can ask your Agent to start a game.
|
|
93
|
+
|
|
94
|
+
### Start a Game
|
|
95
|
+
Ask: *"Start a new chess game against the computer at level 5."*
|
|
96
|
+
- The Agent calls `createGame`.
|
|
97
|
+
- **Pro Tip**: You can also ask *"I want to play against YOU. Create a game where you are White."*
|
|
98
|
+
|
|
99
|
+
### The Game Loop
|
|
100
|
+
1. **Your Move**:
|
|
101
|
+
- Interact with the **HTML Board** if shown. Drag your piece and click **Confirm**.
|
|
102
|
+
- *Or* tell the Agent: *"Move pawn to e4."*
|
|
103
|
+
2. **Agent's Turn**:
|
|
104
|
+
- The Agent calls `waitForNextTurn`.
|
|
105
|
+
- It sees the board (Markdown or HTML) and thinks about the move.
|
|
106
|
+
- It calls `finishTurn` to submit its move.
|
|
107
|
+
3. **Checkmate**:
|
|
108
|
+
- If you deliver the final blow, you can check the **"Claim Checkmate"** box on the UI or tell the Agent *"Checkmate!"*.
|
|
109
|
+
|
|
110
|
+
### Dashboard
|
|
111
|
+
When the server starts, it will try to open **http://localhost:8080**. You can view the list of all active games and spectator views there.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# ♟️ Chess MCP Server
|
|
2
|
+
|
|
3
|
+
**Give your AI Agent eyes to see the board and hands to make the move.**
|
|
4
|
+
|
|
5
|
+
This is not just a chess API. It's a **Model Context Protocol (MCP)** server designed to let Large Language Models (LLMs) like Claude play chess *agentically*.
|
|
6
|
+
|
|
7
|
+
Capable of visualizing the board in real-time HTML, understanding spatial relationships via Markdown, and challenging you with a hybrid difficulty engine (Levels 1-10)—or simply facilitating a game between you and your Agent.
|
|
8
|
+
|
|
9
|
+
## 🚀 Features
|
|
10
|
+
|
|
11
|
+
- **MCP-UI Support**: Interactive HTML board embedded directly in the chat (where supported).
|
|
12
|
+
- **Hybrid AI Engine**: Adjustable difficulty from "Random Blunderer" (Level 1) to "Minimax Master" (Level 10).
|
|
13
|
+
- **Agent vs. Agent**: Let two AI personalities battle it out.
|
|
14
|
+
- **Web Dashboard**: Automatically launches a local sidecar dashboard (`http://localhost:8080`) to monitor all active games.
|
|
15
|
+
|
|
16
|
+
## 📦 Installation
|
|
17
|
+
|
|
18
|
+
### Prerequisites
|
|
19
|
+
- Python 3.10+
|
|
20
|
+
- An MCP Client (e.g., [Claude Desktop](https://claude.ai/download), [Cursor](https://cursor.sh/))
|
|
21
|
+
|
|
22
|
+
### 1. Installation
|
|
23
|
+
You can install directly from PyPI:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install chess-mcp-server
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 2. Configure MCP Client
|
|
30
|
+
|
|
31
|
+
Add the following to your MCP Client configuration file (e.g., `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"mcpServers": {
|
|
36
|
+
"chess": {
|
|
37
|
+
"command": "uvx",
|
|
38
|
+
"args": ["chess-mcp-server"]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
*Alternatively, using pip installation:*
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"mcpServers": {
|
|
48
|
+
"chess": {
|
|
49
|
+
"command": "python",
|
|
50
|
+
"args": ["-m", "src.mcp_server"]
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## 🛠️ Development
|
|
57
|
+
|
|
58
|
+
If you want to modify the code:
|
|
59
|
+
|
|
60
|
+
1. **Clone & Setup**
|
|
61
|
+
```bash
|
|
62
|
+
git clone https://github.com/fritzprix/chess-mcp-server.git
|
|
63
|
+
cd chess-mcp-server
|
|
64
|
+
|
|
65
|
+
python -m venv .venv
|
|
66
|
+
source .venv/bin/activate
|
|
67
|
+
pip install -r requirements.txt
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## 🎮 How to Play
|
|
71
|
+
|
|
72
|
+
Once the server is connected, you can ask your Agent to start a game.
|
|
73
|
+
|
|
74
|
+
### Start a Game
|
|
75
|
+
Ask: *"Start a new chess game against the computer at level 5."*
|
|
76
|
+
- The Agent calls `createGame`.
|
|
77
|
+
- **Pro Tip**: You can also ask *"I want to play against YOU. Create a game where you are White."*
|
|
78
|
+
|
|
79
|
+
### The Game Loop
|
|
80
|
+
1. **Your Move**:
|
|
81
|
+
- Interact with the **HTML Board** if shown. Drag your piece and click **Confirm**.
|
|
82
|
+
- *Or* tell the Agent: *"Move pawn to e4."*
|
|
83
|
+
2. **Agent's Turn**:
|
|
84
|
+
- The Agent calls `waitForNextTurn`.
|
|
85
|
+
- It sees the board (Markdown or HTML) and thinks about the move.
|
|
86
|
+
- It calls `finishTurn` to submit its move.
|
|
87
|
+
3. **Checkmate**:
|
|
88
|
+
- If you deliver the final blow, you can check the **"Claim Checkmate"** box on the UI or tell the Agent *"Checkmate!"*.
|
|
89
|
+
|
|
90
|
+
### Dashboard
|
|
91
|
+
When the server starts, it will try to open **http://localhost:8080**. You can view the list of all active games and spectator views there.
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# MCP Tools Specification Feasibility Analysis
|
|
2
|
+
|
|
3
|
+
## 1. Overview
|
|
4
|
+
This document analyzes the feasibility of the tools defined in `docs/spec/tools.md` based on the `docs/mcp_reference_en.md` standard.
|
|
5
|
+
|
|
6
|
+
**Conclusion:** The proposed specification is **FEASIBLE**.
|
|
7
|
+
**Updates:**
|
|
8
|
+
- Generic `finishTurn` is recommended.
|
|
9
|
+
- `waitForNextTurn` with 30s long-polling is feasible.
|
|
10
|
+
- **Checkmate Claiming**: Explicit checkmate acknowledgment by the user is feasible and supported.
|
|
11
|
+
- **Web Dashboard**: Sidecar web server with auto-launch is feasible.
|
|
12
|
+
|
|
13
|
+
## 2. Detailed Verification
|
|
14
|
+
|
|
15
|
+
| Feature spec | MCP Reference Component | Feasibility | Notes |
|
|
16
|
+
| :--- | :--- | :--- | :--- |
|
|
17
|
+
| **Tool: `createGame`** | Section 2.4 (Tools) | ✅ Supported | Standard tool definition. Returns text. |
|
|
18
|
+
| **Tool: `waitForNextTurn`** | Section 2.4 (Tools) | ✅ Supported | Standard tool definition. Returns text + resource. |
|
|
19
|
+
| **Tool: `finishTurn`** | Section 2.4 (Tools) | ✅ Supported | Generic submission of moves. Replaces `finishUserTurn`. |
|
|
20
|
+
| **Text Rendering** | N/A (Client Rendering) | ✅ Supported | Markdown tables are standard in LLM responses. |
|
|
21
|
+
| **UI Resource** | Section 3.1 & 3.2 | ✅ Supported | `text/html` is a valid `UIResource` MIME type (Strategy 1). |
|
|
22
|
+
| **UI Interaction (Move)** | Section 3.3 (UI Actions) | ✅ Supported* | Requires Host to handle `postMessage` and strict `onUIAction` loop. |
|
|
23
|
+
|
|
24
|
+
## 3. Implementation Details & Considerations
|
|
25
|
+
|
|
26
|
+
### 3.1 UI Resource Implementation
|
|
27
|
+
The specification calls for returning an HTML board when `showUi` is true.
|
|
28
|
+
According to Reference Section 3.2 (Strategy 1):
|
|
29
|
+
- Server returns: simple HTML string.
|
|
30
|
+
- Host behavior: Injects into sandboxed iframe.
|
|
31
|
+
- **Feasible**: Yes, the Python `show_dashboard` example (Reference Section 4.2) directly demonstrates this pattern using `text/html`.
|
|
32
|
+
|
|
33
|
+
### 3.2 Bi-directional Interaction (The "User Move")
|
|
34
|
+
The spec mentions: "User... selects Confirm... user's move is sent to server... usage choice is `finishUserTurn`... via MCP UI ui action".
|
|
35
|
+
|
|
36
|
+
**Mechanism:**
|
|
37
|
+
1. **HTML Logic**: The embedded HTML board must contain JavaScript to capture clicks and send a message.
|
|
38
|
+
```javascript
|
|
39
|
+
// Inside the HTML returned by waitForNextTurn
|
|
40
|
+
window.parent.postMessage({
|
|
41
|
+
type: 'action',
|
|
42
|
+
action: 'finishTurn', // GENERIC NAME RECOMMENDED
|
|
43
|
+
payload: { from: 'e2', to: 'e4' }
|
|
44
|
+
}, '*');
|
|
45
|
+
```
|
|
46
|
+
2. **Host Handling**: The MCP Host (e.g., Claude Desktop, IDE) receives this event.
|
|
47
|
+
3. **Loopback**: The Host must route this action to a tool call.
|
|
48
|
+
- *Note*: The MCP reference Section 3.3 says "The host then cycles this back by calling MCP tools".
|
|
49
|
+
- You will likely need to expose `finishUserTurn` as an MCP Tool so the host (or the Agent reacting to the UI event) can call it.
|
|
50
|
+
|
|
51
|
+
### 3.3 The `waitForNextTurn` Blocking Issue
|
|
52
|
+
One logical ambiguity in the spec is the behavior of `waitForNextTurn` when it is the **Human's** turn.
|
|
53
|
+
- If the LLM calls `waitForNextTurn`, and it is the Human's turn, the tool should likely:
|
|
54
|
+
1. Return the board state immediately with a status "Waiting for User".
|
|
55
|
+
2. The LLM should then perhaps yield or wait?
|
|
56
|
+
3. **Recommended Pattern**: The `waitForNextTurn` tool serves as a "Refresh/Poll" mechanism. The LLM sees the state. If it's human turn, LLM does nothing (or just outputs "Waiting for human move..."). When Human moves (via UI -> `finishUserTurn`), the state updates. The LLM might be re-triggered or manually check again.
|
|
57
|
+
|
|
58
|
+
## 4. Analysis: `finishTurn` vs `finishUserTurn`
|
|
59
|
+
The user questioned if a generic `finishTurn` is sufficient for both scenarios (Agent move vs User move).
|
|
60
|
+
|
|
61
|
+
**Recommendation: Use `finishTurn` for ALL moves.**
|
|
62
|
+
|
|
63
|
+
### Reasons:
|
|
64
|
+
1. **Protocol Simplicity**: The server logic is "Execute Move X on Game Y". The mechanics of *who* originated the move (Agent thinking process vs User UI click) are transport details.
|
|
65
|
+
2. **State-Driven Validation**: The Server already knows whose turn it is.
|
|
66
|
+
* If requests `finishTurn` and it is **Agent's Turn**: Accepted.
|
|
67
|
+
* If requests `finishTurn` and it is **User's Turn**: Accepted (assuming the UI triggered it).
|
|
68
|
+
* *Security Note*: Strictly speaking, if the Agent (LLM) hallucinates and calls `finishTurn` during the User's turn, the server MUST return an error ("Not your turn"). This state check is required regardless of the tool name.
|
|
69
|
+
3. **Unified Schema**:
|
|
70
|
+
```typescript
|
|
71
|
+
finishTurn(game_id: string, move: string)
|
|
72
|
+
```
|
|
73
|
+
This single signature covers both cases.
|
|
74
|
+
|
|
75
|
+
### Revised Flow with `finishTurn`:
|
|
76
|
+
1. **Agent Turn**: LLM thinks -> Calls `finishTurn(game_id, move)`. Server updates state.
|
|
77
|
+
2. **User Turn**:
|
|
78
|
+
* LLM Calls `waitForNextTurn`.
|
|
79
|
+
* Server returns UI.
|
|
80
|
+
* User interacts with UI -> `postMessage({ action: 'finishTurn', ... })`.
|
|
81
|
+
* Host receives message -> Calls `finishTurn(game_id, move)`.
|
|
82
|
+
* Server updates state.
|
|
83
|
+
|
|
84
|
+
## 5. Analysis: `waitForNextTurn` Blocking Strategy
|
|
85
|
+
The proposed strategy is:
|
|
86
|
+
- **Blocking**: 30 seconds wait for opponent move.
|
|
87
|
+
- **Return Condition 1 (Activity)**: Opponent moves -> Return State Immediately.
|
|
88
|
+
- **Return Condition 2 (Timeout)**: 30s elapsed -> Return "Timeout, try again" message.
|
|
89
|
+
|
|
90
|
+
**Feasibility: ✅ Confirmed**
|
|
91
|
+
|
|
92
|
+
### Considerations:
|
|
93
|
+
1. **Host Timeout**: Most MCP Clients (Host applications) set a timeout for tool calls.
|
|
94
|
+
- *Typical Default*: Often 60s or more.
|
|
95
|
+
- *Risk*: If the Host has a very short timeout (e.g., 10s), this call will fail with a Protocol Error before the server returns the custom "Timeout" message.
|
|
96
|
+
- *Mitigation*: 30 seconds is generally a safe lower bound. It allows for a "Long Poll" pattern without triggering aggressive read timeouts.
|
|
97
|
+
2. **Server Implementation**:
|
|
98
|
+
- Must use asynchronous sleep (`await asyncio.sleep(0.1)` or `threading.Event`).
|
|
99
|
+
- Do NOT use `time.sleep(30)` in a single-threaded blocking server, as it will freeze the entire server for all users/requests.
|
|
100
|
+
- *Recommendation*: Use `async def` and event loops (FastMCP supports this natively).
|
|
101
|
+
3. **LLM Loop**:
|
|
102
|
+
- The LLM must be instructed (via the System Prompt or Tool Description) that receiving a "Timeout" is **normal** and it should simply call the tool again. Without this, the LLM might interpret the timeout as a failure and stop or hallucinate complications.
|
|
103
|
+
|
|
104
|
+
**Revised Tool Description Suggestion:**
|
|
105
|
+
> "Waits for the opponent to move. Blocks for up to 30 seconds. If the opponent moves, returns the new board state. If no move occurs within 30 seconds, returns a timeout message. You should immediately call this tool again if you receive a timeout."
|
|
106
|
+
|
|
107
|
+
## 6. Analysis: Checkmate Acknowledgement
|
|
108
|
+
The user requested that the user must "explicitly decide" whether a move is checkmate.
|
|
109
|
+
|
|
110
|
+
**Feasibility: ✅ Confirmed**
|
|
111
|
+
|
|
112
|
+
To support this:
|
|
113
|
+
1. **Protocol**: Update `finishTurn` to accept `claim: string` (values: `null`, `'checkmate'`, `'draw'`).
|
|
114
|
+
2. **Validation**:
|
|
115
|
+
- If `claim='checkmate'` is sent, the server MUST verify `board.is_checkmate()` AFTER the move.
|
|
116
|
+
- If valid -> Win acknowledged.
|
|
117
|
+
- If invalid -> Return error "Accusation failed: Not checkmate".
|
|
118
|
+
3. **UI**: The HTML Board requires an interactive element (e.g., specific "Move & Claim Checkmate" button or a toggle).
|
|
119
|
+
|
|
120
|
+
**Modified Schema**:
|
|
121
|
+
```typescript
|
|
122
|
+
finishTurn(
|
|
123
|
+
game_id: string,
|
|
124
|
+
move: string,
|
|
125
|
+
claim_win?: boolean // if true, asserts this move ends the game
|
|
126
|
+
)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Recommendation for Spec**: Include `claim_win` as an optional parameter to support this "Explicit Decision" mechanic.
|
|
130
|
+
|
|
131
|
+
## 7. Analysis: Web Dashboard & Sidecar Server
|
|
132
|
+
**Use Case**: When the MCP server starts, it should satisfy "start web server + open browser" to show a game list.
|
|
133
|
+
|
|
134
|
+
**Feasibility: ✅ Confirmed (with considerations)**
|
|
135
|
+
|
|
136
|
+
### Architecture
|
|
137
|
+
- **Dual Interface**:
|
|
138
|
+
1. **MCP Interface**: Stdio/SSE handling JSON-RPC.
|
|
139
|
+
2. **HTTP Interface**: Sidecar web server (e.g., Flask/FastAPI/Starlette) running on localhost (random or fixed port).
|
|
140
|
+
- **Execution**: The MCP server entry point must launch the HTTP server background thread/task on startup (`lifespan` event or `__main__`).
|
|
141
|
+
|
|
142
|
+
### Mechanism
|
|
143
|
+
1. **Startup**:
|
|
144
|
+
- Initialize `FastMCP` (or equivalent).
|
|
145
|
+
- Start HTTP Server (e.g. `uvicorn.run` in a thread).
|
|
146
|
+
- Get Port (e.g., `8080`).
|
|
147
|
+
2. **Browser Launch**:
|
|
148
|
+
- Use Python `webbrowser` module: `webbrowser.open("http://localhost:8080")`.
|
|
149
|
+
- *Note*: This works well on local machines (macOS/Windows/Linux). It may fail silently in headless environments (SSH/Cloud), which is acceptable.
|
|
150
|
+
|
|
151
|
+
### UX Consideration
|
|
152
|
+
- **Auto-Open**: If the server crashes/restarts frequently, the browser popping up repeatedly can be annoying.
|
|
153
|
+
- **Mitigation**: Add a logic to only open if not already "running" or check a flag/env var `MCP_DISABLE_BROWSER=1`.
|
|
154
|
+
|
|
155
|
+
### Integration with Game State
|
|
156
|
+
- The Game State manager must be a singleton shared between the MCP Tools context and the HTTP Server endpoints.
|
|
157
|
+
- **Endpoints**:
|
|
158
|
+
- `GET /`: HTML Dashboard showing list of active games.
|
|
159
|
+
- `GET /api/games`: JSON list.
|
|
160
|
+
- `GET /game/{id}`: View specific game board (spectator mode).
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
Here is a proposal to quantify the difficulty into 10 distinct levels using a hybrid approach of **Search Depth** (foresight) and **Error Rate** (probability of making a random move instead of the best move).
|
|
2
|
+
|
|
3
|
+
### Logic: The Hybrid Model
|
|
4
|
+
|
|
5
|
+
* **Depth (1-4):** Controls how far the AI calculates. Python is relatively slow, so Depth 4 is a realistic maximum for a responsive game without C++ extensions.
|
|
6
|
+
* **Error Rate (0.0 - 0.6):** The probability that the AI ignores the "best move" and picks a random legal move (simulating a blunder).
|
|
7
|
+
|
|
8
|
+
### Configuration Dictionary
|
|
9
|
+
|
|
10
|
+
| Level | Depth | Error Rate | Description |
|
|
11
|
+
| --- | --- | --- | --- |
|
|
12
|
+
| **1** | 1 | 60% | Calculates 1 move ahead, frequently plays randomly. |
|
|
13
|
+
| **2** | 1 | 40% | Still shortsighted, but slightly more coherent. |
|
|
14
|
+
| **3** | 1 | 20% | Rarely misses immediate captures, but has no plan. |
|
|
15
|
+
| **4** | 2 | 20% | Starts seeing 2 moves ahead, but makes mistakes. |
|
|
16
|
+
| **5** | 2 | 10% | Standard amateur level. Solid 2-move calculation. |
|
|
17
|
+
| **6** | 3 | 10% | Stronger. Sees 3 moves ahead, occasional errors. |
|
|
18
|
+
| **7** | 3 | 5% | Competent. Very few unforced errors. |
|
|
19
|
+
| **8** | 3 | 0% | Best possible play at Depth 3. |
|
|
20
|
+
| **9** | 4 | 5% | Deep calculation (slower), slight human-like noise. |
|
|
21
|
+
| **10** | 4 | 0% | Maximum strength. Pure Minimax at Depth 4. |
|
|
22
|
+
|
|
23
|
+
### Python Implementation
|
|
24
|
+
|
|
25
|
+
This code wraps your previous Minimax logic into a class structure that accepts a level (1-10).
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
import chess
|
|
29
|
+
import math
|
|
30
|
+
import random
|
|
31
|
+
|
|
32
|
+
class ChessAI:
|
|
33
|
+
def __init__(self):
|
|
34
|
+
# Configuration for 10 levels
|
|
35
|
+
self.levels = {
|
|
36
|
+
1: {"depth": 1, "error_rate": 0.60},
|
|
37
|
+
2: {"depth": 1, "error_rate": 0.40},
|
|
38
|
+
3: {"depth": 1, "error_rate": 0.20},
|
|
39
|
+
4: {"depth": 2, "error_rate": 0.20},
|
|
40
|
+
5: {"depth": 2, "error_rate": 0.10},
|
|
41
|
+
6: {"depth": 3, "error_rate": 0.10},
|
|
42
|
+
7: {"depth": 3, "error_rate": 0.05},
|
|
43
|
+
8: {"depth": 3, "error_rate": 0.00},
|
|
44
|
+
9: {"depth": 4, "error_rate": 0.05},
|
|
45
|
+
10: {"depth": 4, "error_rate": 0.00},
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# Piece values for evaluation
|
|
49
|
+
self.piece_values = {
|
|
50
|
+
chess.PAWN: 10, chess.KNIGHT: 30, chess.BISHOP: 30,
|
|
51
|
+
chess.ROOK: 50, chess.QUEEN: 90, chess.KING: 900
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
def get_move(self, board, level):
|
|
55
|
+
"""
|
|
56
|
+
Returns the best move based on the difficulty level (1-10).
|
|
57
|
+
"""
|
|
58
|
+
if level not in self.levels:
|
|
59
|
+
level = 5 # Default to medium
|
|
60
|
+
|
|
61
|
+
settings = self.levels[level]
|
|
62
|
+
depth = settings["depth"]
|
|
63
|
+
error_rate = settings["error_rate"]
|
|
64
|
+
|
|
65
|
+
# 1. Error Chance (Simulate Blunder)
|
|
66
|
+
# If the random roll is within the error rate, pick a random legal move.
|
|
67
|
+
if random.random() < error_rate:
|
|
68
|
+
return random.choice(list(board.legal_moves))
|
|
69
|
+
|
|
70
|
+
# 2. Strategic Calculation (Minimax)
|
|
71
|
+
# Otherwise, calculate the best move using Minimax.
|
|
72
|
+
return self._get_best_move_minimax(board, depth)
|
|
73
|
+
|
|
74
|
+
def _evaluate_board(self, board):
|
|
75
|
+
if board.is_checkmate():
|
|
76
|
+
return -9999 if board.turn else 9999
|
|
77
|
+
if board.is_stalemate() or board.is_insufficient_material():
|
|
78
|
+
return 0
|
|
79
|
+
|
|
80
|
+
score = 0
|
|
81
|
+
for square in chess.SQUARES:
|
|
82
|
+
piece = board.piece_at(square)
|
|
83
|
+
if piece:
|
|
84
|
+
value = self.piece_values[piece.piece_type]
|
|
85
|
+
if piece.color == chess.WHITE:
|
|
86
|
+
score += value
|
|
87
|
+
else:
|
|
88
|
+
score -= value
|
|
89
|
+
return score
|
|
90
|
+
|
|
91
|
+
def _minimax(self, board, depth, alpha, beta, maximizing):
|
|
92
|
+
if depth == 0 or board.is_game_over():
|
|
93
|
+
return self._evaluate_board(board)
|
|
94
|
+
|
|
95
|
+
legal_moves = list(board.legal_moves)
|
|
96
|
+
|
|
97
|
+
if maximizing:
|
|
98
|
+
max_eval = -math.inf
|
|
99
|
+
for move in legal_moves:
|
|
100
|
+
board.push(move)
|
|
101
|
+
eval = self._minimax(board, depth - 1, alpha, beta, False)
|
|
102
|
+
board.pop()
|
|
103
|
+
max_eval = max(max_eval, eval)
|
|
104
|
+
alpha = max(alpha, eval)
|
|
105
|
+
if beta <= alpha:
|
|
106
|
+
break
|
|
107
|
+
return max_eval
|
|
108
|
+
else:
|
|
109
|
+
min_eval = math.inf
|
|
110
|
+
for move in legal_moves:
|
|
111
|
+
board.push(move)
|
|
112
|
+
eval = self._minimax(board, depth - 1, alpha, beta, True)
|
|
113
|
+
board.pop()
|
|
114
|
+
min_eval = min(min_eval, eval)
|
|
115
|
+
beta = min(beta, eval)
|
|
116
|
+
if beta <= alpha:
|
|
117
|
+
break
|
|
118
|
+
return min_eval
|
|
119
|
+
|
|
120
|
+
def _get_best_move_minimax(self, board, depth):
|
|
121
|
+
best_move = None
|
|
122
|
+
best_value = -math.inf if board.turn == chess.WHITE else math.inf
|
|
123
|
+
alpha = -math.inf
|
|
124
|
+
beta = math.inf
|
|
125
|
+
|
|
126
|
+
legal_moves = list(board.legal_moves)
|
|
127
|
+
|
|
128
|
+
# Simple optimization: shuffle moves to prevent deterministic behavior on equal scores
|
|
129
|
+
random.shuffle(legal_moves)
|
|
130
|
+
|
|
131
|
+
for move in legal_moves:
|
|
132
|
+
board.push(move)
|
|
133
|
+
value = self._minimax(board, depth - 1, alpha, beta, not board.turn)
|
|
134
|
+
board.pop()
|
|
135
|
+
|
|
136
|
+
if board.turn == chess.WHITE:
|
|
137
|
+
if value > best_value:
|
|
138
|
+
best_value = value
|
|
139
|
+
best_move = move
|
|
140
|
+
alpha = max(alpha, best_value)
|
|
141
|
+
else:
|
|
142
|
+
if value < best_value:
|
|
143
|
+
best_value = value
|
|
144
|
+
best_move = move
|
|
145
|
+
beta = min(beta, best_value)
|
|
146
|
+
|
|
147
|
+
return best_move
|
|
148
|
+
|
|
149
|
+
# --- Usage Example ---
|
|
150
|
+
if __name__ == "__main__":
|
|
151
|
+
board = chess.Board()
|
|
152
|
+
ai = ChessAI()
|
|
153
|
+
|
|
154
|
+
# User selects Level 7
|
|
155
|
+
user_level = 7
|
|
156
|
+
print(f"AI Level: {user_level} (Depth: {ai.levels[user_level]['depth']}, Error: {ai.levels[user_level]['error_rate']*100}%)")
|
|
157
|
+
|
|
158
|
+
best_move = ai.get_move(board, level=user_level)
|
|
159
|
+
print(f"AI plays: {best_move}")
|
|
160
|
+
|
|
161
|
+
```
|