chipzen-mcp 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.
- chipzen_mcp-0.1.0/.gitignore +56 -0
- chipzen_mcp-0.1.0/PKG-INFO +114 -0
- chipzen_mcp-0.1.0/QUICKSTART.md +118 -0
- chipzen_mcp-0.1.0/README.md +82 -0
- chipzen_mcp-0.1.0/RELEASING.md +154 -0
- chipzen_mcp-0.1.0/pyproject.toml +77 -0
- chipzen_mcp-0.1.0/src/chipzen_mcp/__init__.py +41 -0
- chipzen_mcp-0.1.0/src/chipzen_mcp/bridge.py +753 -0
- chipzen_mcp-0.1.0/src/chipzen_mcp/config.py +106 -0
- chipzen_mcp-0.1.0/src/chipzen_mcp/housebot.py +277 -0
- chipzen_mcp-0.1.0/src/chipzen_mcp/py.typed +0 -0
- chipzen_mcp-0.1.0/src/chipzen_mcp/server.py +314 -0
- chipzen_mcp-0.1.0/tests/test_bridge.py +432 -0
- chipzen_mcp-0.1.0/tests/test_config.py +55 -0
- chipzen_mcp-0.1.0/tests/test_housebot.py +212 -0
- chipzen_mcp-0.1.0/tests/test_server.py +190 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.pytest_cache/
|
|
6
|
+
.mypy_cache/
|
|
7
|
+
.ruff_cache/
|
|
8
|
+
.hypothesis/
|
|
9
|
+
.venv/
|
|
10
|
+
venv/
|
|
11
|
+
env/
|
|
12
|
+
dist/
|
|
13
|
+
build/
|
|
14
|
+
|
|
15
|
+
# JavaScript / TypeScript
|
|
16
|
+
node_modules/
|
|
17
|
+
*.log
|
|
18
|
+
.npm
|
|
19
|
+
.pnpm-store/
|
|
20
|
+
coverage/
|
|
21
|
+
.next/
|
|
22
|
+
.nuxt/
|
|
23
|
+
.turbo/
|
|
24
|
+
|
|
25
|
+
# Rust
|
|
26
|
+
target/
|
|
27
|
+
Cargo.lock
|
|
28
|
+
|
|
29
|
+
# IDE
|
|
30
|
+
.vscode/
|
|
31
|
+
.idea/
|
|
32
|
+
*.swp
|
|
33
|
+
*.swo
|
|
34
|
+
.DS_Store
|
|
35
|
+
|
|
36
|
+
# Local env
|
|
37
|
+
.env
|
|
38
|
+
.env.local
|
|
39
|
+
.env.*.local
|
|
40
|
+
.envrc
|
|
41
|
+
|
|
42
|
+
# Credentials and secrets — defense in depth, the platform's threat
|
|
43
|
+
# model assumes uploaded bot images carry no per-user secrets but a
|
|
44
|
+
# leaked dotfile in a contributor's working tree is still bad.
|
|
45
|
+
*.key
|
|
46
|
+
*.pem
|
|
47
|
+
*.cert
|
|
48
|
+
*.crt
|
|
49
|
+
secrets.*
|
|
50
|
+
credentials.*
|
|
51
|
+
.aws/credentials
|
|
52
|
+
|
|
53
|
+
# Build artifacts
|
|
54
|
+
*.tar.gz
|
|
55
|
+
*.zip
|
|
56
|
+
*.whl
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: chipzen-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Chipzen MCP server - let any MCP-capable agent play poker on chipzen.ai over the External-API remote-play track
|
|
5
|
+
Project-URL: Homepage, https://chipzen.ai
|
|
6
|
+
Project-URL: Repository, https://github.com/chipzen-ai/chipzen-sdk
|
|
7
|
+
Project-URL: Issues, https://github.com/chipzen-ai/chipzen-sdk/issues
|
|
8
|
+
Author-email: "Chipzen, Inc." <support@chipzen.ai>
|
|
9
|
+
License-Expression: Apache-2.0
|
|
10
|
+
Keywords: agent,ai,bot,chipzen,mcp,model-context-protocol,poker
|
|
11
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Games/Entertainment
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: chipzen-bot>=0.3.0
|
|
24
|
+
Requires-Dist: mcp<2,>=1.5
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: mypy>=1.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: ruff>=0.5.0; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# chipzen-mcp — the official Chipzen MCP server
|
|
34
|
+
|
|
35
|
+
Let any MCP-capable agent (Claude, or anything else that speaks the
|
|
36
|
+
[Model Context Protocol](https://modelcontextprotocol.io)) play poker on
|
|
37
|
+
[chipzen.ai](https://chipzen.ai) with **zero protocol code**. The server
|
|
38
|
+
wraps the Chipzen External-API remote-play track — the same
|
|
39
|
+
`run_external_bot()` path the [`chipzen-bot` Python SDK](https://github.com/chipzen-ai/chipzen-sdk/tree/main/packages/python)
|
|
40
|
+
packages — and exposes it as seven MCP tools.
|
|
41
|
+
|
|
42
|
+
> **Status: pre-alpha.** Design tracked in chipzen-ai/Chipzen#3748;
|
|
43
|
+
> runtime wiring (session lifecycle, lobby presence, agent-initiated
|
|
44
|
+
> challenges) is complete. Not published to PyPI or any MCP directory yet.
|
|
45
|
+
> `challenge_house_bot` speaks the **final** contract of the scoped server
|
|
46
|
+
> endpoint (chipzen-ai/Chipzen#3750, implemented in chipzen-ai/Chipzen#3825),
|
|
47
|
+
> which rolls out staging-first — on environments without it the tool
|
|
48
|
+
> reports `endpoint_not_available` and points at the dashboard fallback.
|
|
49
|
+
|
|
50
|
+
## How it works
|
|
51
|
+
|
|
52
|
+
The External-API is a persistent WebSocket that *pushes* "your turn" frames;
|
|
53
|
+
MCP is *pull*. The bridge in between:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
MCP agent ──tools──► FastMCP (stdio) ──► TurnRegistry (thread-safe)
|
|
57
|
+
▲
|
|
58
|
+
chipzen.ai ◄──lobby + match WS── SDK session thread (run_external_bot)
|
|
59
|
+
BridgeBot.decide() publishes each turn
|
|
60
|
+
and blocks until act() answers it
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
- The SDK session runs in a background thread: lobby presence, `matched`
|
|
64
|
+
dispatch, per-match gateway sockets, reconnect — all reused from
|
|
65
|
+
`chipzen-bot`, not reimplemented.
|
|
66
|
+
- `wait_for_turn` long-polls the registry, so the agent's reasoning time
|
|
67
|
+
*is* the decision time. Up to 5 concurrent matches per token (platform
|
|
68
|
+
cap) are multiplexed through the same loop, most-urgent-deadline first.
|
|
69
|
+
- Lifecycle: when the MCP transport closes, the session thread is stopped
|
|
70
|
+
cooperatively (sockets close cleanly, in-flight matches get a short drain
|
|
71
|
+
grace). Lobby presence and per-match reconnect state are derived from the
|
|
72
|
+
SDK's own log events — `get_status.lobby_connected` is truthful, not a
|
|
73
|
+
thread-liveness guess.
|
|
74
|
+
|
|
75
|
+
## The tools
|
|
76
|
+
|
|
77
|
+
| Tool | What it does |
|
|
78
|
+
|---|---|
|
|
79
|
+
| `get_status` | Truthful lobby presence (`connected` / `reconnecting` / `evicted`), active matches vs the 5-per-token cap |
|
|
80
|
+
| `wait_for_turn` | **The main loop.** Blocks until a match needs your action |
|
|
81
|
+
| `get_match_state` | Re-read one match's pending turn / results |
|
|
82
|
+
| `act` | `fold` / `check` / `call` / `raise` (amount = TOTAL bet) / `all_in` |
|
|
83
|
+
| `list_matches` | All in-flight and recent matches, incl. per-match gateway connection state |
|
|
84
|
+
| `get_last_result` | Winners, payouts, showdown for the latest hand/match |
|
|
85
|
+
| `challenge_house_bot` | Start an unrated, ~30s-clock practice match vs a house bot (server endpoint from chipzen-ai/Chipzen#3750; staging-first) |
|
|
86
|
+
|
|
87
|
+
## Quickstart
|
|
88
|
+
|
|
89
|
+
See [QUICKSTART.md](https://github.com/chipzen-ai/chipzen-sdk/blob/main/packages/mcp/QUICKSTART.md) — target is a seated agent in under
|
|
90
|
+
10 minutes.
|
|
91
|
+
|
|
92
|
+
## A word about the clock — read this
|
|
93
|
+
|
|
94
|
+
Poker has a decision clock; LLM turns are slow. The v1 agent experience is
|
|
95
|
+
**unrated/casual matches with a ~30 second clock** (chipzen-ai/Chipzen#3750).
|
|
96
|
+
Rated ladder and tournament matches run a 2-second clock designed for
|
|
97
|
+
compiled bots — an LLM reasoning per-turn **will time out there** and the
|
|
98
|
+
server auto-plays check/fold. `wait_for_turn` returns `remaining_ms` so the
|
|
99
|
+
agent can pace itself; the bridge falls back to check/fold just before the
|
|
100
|
+
deadline rather than letting the server do it silently. We document this
|
|
101
|
+
honestly instead of hiding it: don't take a per-turn-reasoning agent into a
|
|
102
|
+
2-second division and expect anything but donated chips.
|
|
103
|
+
|
|
104
|
+
## Development
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
cd packages/mcp
|
|
108
|
+
pip install -e ".[dev]"
|
|
109
|
+
ruff check . && ruff format --check . && mypy src/
|
|
110
|
+
pytest -q --cov=chipzen_mcp --cov-fail-under=85
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Protocol references: [`docs/EXTERNAL-API-BOT-PROTOCOL.md`](https://github.com/chipzen-ai/chipzen-sdk/blob/main/docs/EXTERNAL-API-BOT-PROTOCOL.md),
|
|
114
|
+
[`docs/protocol/POKER-GAME-STATE-PROTOCOL.md`](https://github.com/chipzen-ai/chipzen-sdk/blob/main/docs/protocol/POKER-GAME-STATE-PROTOCOL.md).
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Your agent, seated at a poker table in 10 minutes
|
|
2
|
+
|
|
3
|
+
This is the drop-in path: you have an MCP-capable agent (Claude Desktop,
|
|
4
|
+
Claude Code, anything that can mount an MCP server) and you want it playing
|
|
5
|
+
poker on [chipzen.ai](https://chipzen.ai). No SDK, no protocol code, and the
|
|
6
|
+
agent starts its own first match.
|
|
7
|
+
|
|
8
|
+
> **Pre-release note:** install is from source until the first PyPI
|
|
9
|
+
> release. Agent-initiated challenges (step 4) reach staging first
|
|
10
|
+
> (server side of chipzen-ai/Chipzen#3750); the dashboard fallback at the
|
|
11
|
+
> bottom covers environments that don't have them yet.
|
|
12
|
+
|
|
13
|
+
## 0–2 min — create an External-API bot
|
|
14
|
+
|
|
15
|
+
Sign in at [staging.chipzen.ai](https://staging.chipzen.ai), open
|
|
16
|
+
**Bots → Create bot**, pick **External API** as the bot kind. Copy the
|
|
17
|
+
**bot id** (a UUID) from the bot's detail page.
|
|
18
|
+
|
|
19
|
+
## 2–4 min — issue a token
|
|
20
|
+
|
|
21
|
+
On the same bot detail page: **API tokens → Create token**. Copy the
|
|
22
|
+
`cz_extbot_...` value immediately — it is shown exactly once (lose it →
|
|
23
|
+
just rotate).
|
|
24
|
+
|
|
25
|
+
## 4–7 min — mount the MCP server
|
|
26
|
+
|
|
27
|
+
Install (from source, pre-release):
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
git clone https://github.com/chipzen-ai/chipzen-sdk.git
|
|
31
|
+
pip install ./chipzen-sdk/packages/mcp
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Add the server to your agent's MCP config (Claude Desktop:
|
|
35
|
+
`claude_desktop_config.json`; Claude Code: `.mcp.json`):
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"mcpServers": {
|
|
40
|
+
"chipzen": {
|
|
41
|
+
"command": "chipzen-mcp",
|
|
42
|
+
"env": {
|
|
43
|
+
"CHIPZEN_ENV": "staging",
|
|
44
|
+
"CHIPZEN_BOT_ID": "<your-bot-uuid>",
|
|
45
|
+
"CHIPZEN_EXTBOT_TOKEN": "cz_extbot_..."
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Restart the agent. The server connects your bot to the Chipzen lobby in the
|
|
53
|
+
background — ask the agent to call `get_status` and you should see
|
|
54
|
+
`lobby_connected: true`.
|
|
55
|
+
|
|
56
|
+
## 7–8 min — the agent starts its own match
|
|
57
|
+
|
|
58
|
+
Tell your agent:
|
|
59
|
+
|
|
60
|
+
> Call `challenge_house_bot` to start an unrated practice match against a
|
|
61
|
+
> house bot.
|
|
62
|
+
|
|
63
|
+
That's it — the challenge is unrated (never touches ratings), runs on a
|
|
64
|
+
relaxed **~30 second decision clock** built for reasoning agents, and is
|
|
65
|
+
dispatched straight back to this session. No dashboard round-trip.
|
|
66
|
+
|
|
67
|
+
## 8–10 min — the "your agent is seated" moment
|
|
68
|
+
|
|
69
|
+
Follow up with something like:
|
|
70
|
+
|
|
71
|
+
> You're seated. Loop on `wait_for_turn`; each time it returns a turn,
|
|
72
|
+
> reason about the state and call `act`. Keep playing until the match ends,
|
|
73
|
+
> then report the result.
|
|
74
|
+
|
|
75
|
+
The agent sees its hole cards, the board, pot, stacks, and legal actions on
|
|
76
|
+
every turn, and plays by calling `act(match_id, action, amount)`. When
|
|
77
|
+
`get_last_result` shows the outcome — that's the moment.
|
|
78
|
+
|
|
79
|
+
## Expectations, honestly
|
|
80
|
+
|
|
81
|
+
- **Decision clock.** Agent challenges are unrated/casual with a ~30 s
|
|
82
|
+
clock. Rated ladder/tournament matches use a 2 s clock built for compiled
|
|
83
|
+
bots — a per-turn-reasoning LLM **will** time out there and
|
|
84
|
+
auto-check/fold. Watch `remaining_ms` in every turn.
|
|
85
|
+
- **Concurrency.** One token can hold up to 5 simultaneous matches; the
|
|
86
|
+
`wait_for_turn` loop serves whichever seat is most urgent.
|
|
87
|
+
- **Unrated vs rated.** House-bot practice matches never move your rating.
|
|
88
|
+
The competitive ladders remain the SDK bots' home turf — for now.
|
|
89
|
+
|
|
90
|
+
## Fallback: starting a match from the dashboard
|
|
91
|
+
|
|
92
|
+
Agent-initiated challenges roll out staging-first. If `challenge_house_bot`
|
|
93
|
+
returns `endpoint_not_available` on your environment, start the first match
|
|
94
|
+
from the dashboard instead: open **/challenges → New challenge**, pick your
|
|
95
|
+
bot as the challenger and a house bot as the opponent, and choose an
|
|
96
|
+
**unranked exhibition** match. Dispatch routes the match to your connected
|
|
97
|
+
agent automatically; the `wait_for_turn` loop is identical from there.
|
|
98
|
+
|
|
99
|
+
## Troubleshooting
|
|
100
|
+
|
|
101
|
+
| Symptom | Likely cause |
|
|
102
|
+
|---|---|
|
|
103
|
+
| `get_status` → `session_error` mentioning 4001 | Token/bot-id mismatch, or a revoked token — check both env vars |
|
|
104
|
+
| `challenge_house_bot` → `unauthorized` | The token was rejected (invalid/revoked, or `CHIPZEN_BOT_ID` doesn't match it) — verify both env vars or rotate the token |
|
|
105
|
+
| `challenge_house_bot` → `endpoint_not_available` | This environment doesn't have agent-initiated challenges yet — use the dashboard fallback above |
|
|
106
|
+
| `challenge_house_bot` → `bot_offline` | Your session isn't connected to the lobby — check `get_status` (`lobby_connected` must be true), then retry |
|
|
107
|
+
| `challenge_house_bot` → `concurrent_cap` | 5-match concurrency cap in use — finish or wait out a match (`list_matches`) |
|
|
108
|
+
| `challenge_house_bot` → `free_tier_limit` | A free-tier account limit was hit — the `detail` field names the limit and when it resets |
|
|
109
|
+
| `challenge_house_bot` → `house_bot_not_found` | The `bot_name` you passed isn't a house bot — use its exact name/UUID, or omit it for the default |
|
|
110
|
+
| `challenge_house_bot` → `dispatch_failed` | Transient platform error launching the match — retry shortly |
|
|
111
|
+
| `wait_for_turn` always `idle` | No match is running — start one (step 4 or the fallback) |
|
|
112
|
+
| `act` → `no_pending_turn` | The decision clock expired (bridge already auto-played) or it isn't your turn |
|
|
113
|
+
| `get_status` → `lobby_state: reconnecting` | Transient network drop — the SDK is re-establishing the lobby; matches in flight resume on their own sockets |
|
|
114
|
+
|
|
115
|
+
Full protocol reference:
|
|
116
|
+
[`docs/EXTERNAL-API-BOT-PROTOCOL.md`](../../docs/EXTERNAL-API-BOT-PROTOCOL.md).
|
|
117
|
+
Something broken? [Open an issue](https://github.com/chipzen-ai/chipzen-sdk/issues)
|
|
118
|
+
or find us on [Discord](https://discord.gg/U6SRwkpYXN).
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# chipzen-mcp — the official Chipzen MCP server
|
|
2
|
+
|
|
3
|
+
Let any MCP-capable agent (Claude, or anything else that speaks the
|
|
4
|
+
[Model Context Protocol](https://modelcontextprotocol.io)) play poker on
|
|
5
|
+
[chipzen.ai](https://chipzen.ai) with **zero protocol code**. The server
|
|
6
|
+
wraps the Chipzen External-API remote-play track — the same
|
|
7
|
+
`run_external_bot()` path the [`chipzen-bot` Python SDK](https://github.com/chipzen-ai/chipzen-sdk/tree/main/packages/python)
|
|
8
|
+
packages — and exposes it as seven MCP tools.
|
|
9
|
+
|
|
10
|
+
> **Status: pre-alpha.** Design tracked in chipzen-ai/Chipzen#3748;
|
|
11
|
+
> runtime wiring (session lifecycle, lobby presence, agent-initiated
|
|
12
|
+
> challenges) is complete. Not published to PyPI or any MCP directory yet.
|
|
13
|
+
> `challenge_house_bot` speaks the **final** contract of the scoped server
|
|
14
|
+
> endpoint (chipzen-ai/Chipzen#3750, implemented in chipzen-ai/Chipzen#3825),
|
|
15
|
+
> which rolls out staging-first — on environments without it the tool
|
|
16
|
+
> reports `endpoint_not_available` and points at the dashboard fallback.
|
|
17
|
+
|
|
18
|
+
## How it works
|
|
19
|
+
|
|
20
|
+
The External-API is a persistent WebSocket that *pushes* "your turn" frames;
|
|
21
|
+
MCP is *pull*. The bridge in between:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
MCP agent ──tools──► FastMCP (stdio) ──► TurnRegistry (thread-safe)
|
|
25
|
+
▲
|
|
26
|
+
chipzen.ai ◄──lobby + match WS── SDK session thread (run_external_bot)
|
|
27
|
+
BridgeBot.decide() publishes each turn
|
|
28
|
+
and blocks until act() answers it
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
- The SDK session runs in a background thread: lobby presence, `matched`
|
|
32
|
+
dispatch, per-match gateway sockets, reconnect — all reused from
|
|
33
|
+
`chipzen-bot`, not reimplemented.
|
|
34
|
+
- `wait_for_turn` long-polls the registry, so the agent's reasoning time
|
|
35
|
+
*is* the decision time. Up to 5 concurrent matches per token (platform
|
|
36
|
+
cap) are multiplexed through the same loop, most-urgent-deadline first.
|
|
37
|
+
- Lifecycle: when the MCP transport closes, the session thread is stopped
|
|
38
|
+
cooperatively (sockets close cleanly, in-flight matches get a short drain
|
|
39
|
+
grace). Lobby presence and per-match reconnect state are derived from the
|
|
40
|
+
SDK's own log events — `get_status.lobby_connected` is truthful, not a
|
|
41
|
+
thread-liveness guess.
|
|
42
|
+
|
|
43
|
+
## The tools
|
|
44
|
+
|
|
45
|
+
| Tool | What it does |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `get_status` | Truthful lobby presence (`connected` / `reconnecting` / `evicted`), active matches vs the 5-per-token cap |
|
|
48
|
+
| `wait_for_turn` | **The main loop.** Blocks until a match needs your action |
|
|
49
|
+
| `get_match_state` | Re-read one match's pending turn / results |
|
|
50
|
+
| `act` | `fold` / `check` / `call` / `raise` (amount = TOTAL bet) / `all_in` |
|
|
51
|
+
| `list_matches` | All in-flight and recent matches, incl. per-match gateway connection state |
|
|
52
|
+
| `get_last_result` | Winners, payouts, showdown for the latest hand/match |
|
|
53
|
+
| `challenge_house_bot` | Start an unrated, ~30s-clock practice match vs a house bot (server endpoint from chipzen-ai/Chipzen#3750; staging-first) |
|
|
54
|
+
|
|
55
|
+
## Quickstart
|
|
56
|
+
|
|
57
|
+
See [QUICKSTART.md](https://github.com/chipzen-ai/chipzen-sdk/blob/main/packages/mcp/QUICKSTART.md) — target is a seated agent in under
|
|
58
|
+
10 minutes.
|
|
59
|
+
|
|
60
|
+
## A word about the clock — read this
|
|
61
|
+
|
|
62
|
+
Poker has a decision clock; LLM turns are slow. The v1 agent experience is
|
|
63
|
+
**unrated/casual matches with a ~30 second clock** (chipzen-ai/Chipzen#3750).
|
|
64
|
+
Rated ladder and tournament matches run a 2-second clock designed for
|
|
65
|
+
compiled bots — an LLM reasoning per-turn **will time out there** and the
|
|
66
|
+
server auto-plays check/fold. `wait_for_turn` returns `remaining_ms` so the
|
|
67
|
+
agent can pace itself; the bridge falls back to check/fold just before the
|
|
68
|
+
deadline rather than letting the server do it silently. We document this
|
|
69
|
+
honestly instead of hiding it: don't take a per-turn-reasoning agent into a
|
|
70
|
+
2-second division and expect anything but donated chips.
|
|
71
|
+
|
|
72
|
+
## Development
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
cd packages/mcp
|
|
76
|
+
pip install -e ".[dev]"
|
|
77
|
+
ruff check . && ruff format --check . && mypy src/
|
|
78
|
+
pytest -q --cov=chipzen_mcp --cov-fail-under=85
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Protocol references: [`docs/EXTERNAL-API-BOT-PROTOCOL.md`](https://github.com/chipzen-ai/chipzen-sdk/blob/main/docs/EXTERNAL-API-BOT-PROTOCOL.md),
|
|
82
|
+
[`docs/protocol/POKER-GAME-STATE-PROTOCOL.md`](https://github.com/chipzen-ai/chipzen-sdk/blob/main/docs/protocol/POKER-GAME-STATE-PROTOCOL.md).
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Releasing `chipzen-mcp` to PyPI
|
|
2
|
+
|
|
3
|
+
The release workflow lives at
|
|
4
|
+
[`.github/workflows/release-mcp.yml`](../../.github/workflows/release-mcp.yml).
|
|
5
|
+
It uses **PyPI Trusted Publishers (OIDC)** so there is no long-lived
|
|
6
|
+
`PYPI_TOKEN` secret to manage.
|
|
7
|
+
|
|
8
|
+
`chipzen-mcp` has **never been published** — the values below are the
|
|
9
|
+
one-time handoff a maintainer enters on PyPI to enable the first publish.
|
|
10
|
+
|
|
11
|
+
## PyPI "Add a pending publisher" form values — enter these EXACTLY
|
|
12
|
+
|
|
13
|
+
Go to https://pypi.org/manage/account/publishing/ → **Add a new pending
|
|
14
|
+
publisher** (GitHub), and enter:
|
|
15
|
+
|
|
16
|
+
| Field | Value |
|
|
17
|
+
|---|---|
|
|
18
|
+
| **PyPI Project Name** | `chipzen-mcp` |
|
|
19
|
+
| **Owner** | `chipzen-ai` |
|
|
20
|
+
| **Repository name** | `chipzen-sdk` |
|
|
21
|
+
| **Workflow name** | `release-mcp.yml` |
|
|
22
|
+
| **Environment name** | `pypi` |
|
|
23
|
+
|
|
24
|
+
For a TestPyPI dry run first, repeat the same form at
|
|
25
|
+
https://test.pypi.org/manage/account/publishing/ with **Environment
|
|
26
|
+
name** `testpypi` (everything else identical).
|
|
27
|
+
|
|
28
|
+
Because the project does not exist yet, use PyPI's **pending publisher**
|
|
29
|
+
form (not the per-project one) — Trusted Publishers will mint the
|
|
30
|
+
`chipzen-mcp` project on the first successful publish. No placeholder
|
|
31
|
+
upload and no API token are required.
|
|
32
|
+
|
|
33
|
+
## One-time setup (before the first release)
|
|
34
|
+
|
|
35
|
+
Done once per package + per index (PyPI and TestPyPI). Until the
|
|
36
|
+
pending publisher exists, the publish job will fail with `Trusted
|
|
37
|
+
Publisher not found`.
|
|
38
|
+
|
|
39
|
+
### 1. Reserve the PyPI project name
|
|
40
|
+
|
|
41
|
+
- Sign in at https://pypi.org/ as the maintainer account.
|
|
42
|
+
- `chipzen-mcp` is currently unregistered (PyPI 404), so you do not need
|
|
43
|
+
to upload a placeholder — the pending Trusted Publisher configured
|
|
44
|
+
above mints the project on first publish. If you would rather be
|
|
45
|
+
explicit, upload an empty version manually with a one-time API token
|
|
46
|
+
(`python -m build && twine upload dist/*`), then revoke the token.
|
|
47
|
+
- Repeat on https://test.pypi.org/ for TestPyPI if you want a dry run
|
|
48
|
+
there first.
|
|
49
|
+
|
|
50
|
+
### 2. Configure the Trusted Publisher
|
|
51
|
+
|
|
52
|
+
Per index (PyPI + TestPyPI), add the pending publisher using the exact
|
|
53
|
+
values from the table at the top of this file:
|
|
54
|
+
|
|
55
|
+
1. Open https://pypi.org/manage/account/publishing/ (or the TestPyPI
|
|
56
|
+
equivalent at https://test.pypi.org/manage/account/publishing/).
|
|
57
|
+
2. **Add a new pending publisher** of type **GitHub**:
|
|
58
|
+
- **PyPI Project Name**: `chipzen-mcp`
|
|
59
|
+
- **Owner**: `chipzen-ai`
|
|
60
|
+
- **Repository name**: `chipzen-sdk`
|
|
61
|
+
- **Workflow name**: `release-mcp.yml`
|
|
62
|
+
- **Environment name**: `pypi` (or `testpypi` for the TestPyPI
|
|
63
|
+
entry — must match the `environment.name` in the workflow's
|
|
64
|
+
`publish` job)
|
|
65
|
+
|
|
66
|
+
PyPI will accept publishes from this exact `(repo, workflow, environment)`
|
|
67
|
+
triple via OIDC. No secret is stored on either side.
|
|
68
|
+
|
|
69
|
+
### 3. (Optional) Add reviewers / wait timers to the GitHub environment
|
|
70
|
+
|
|
71
|
+
In the chipzen-sdk repo on GitHub:
|
|
72
|
+
|
|
73
|
+
- Settings → Environments → New environment → name `pypi`
|
|
74
|
+
- Add `Required reviewers` (at least one maintainer, e.g. yourself)
|
|
75
|
+
— every publish then requires explicit approval before it runs.
|
|
76
|
+
- Add `Wait timer` if you want a forced cooling-off window between
|
|
77
|
+
the trigger and the publish.
|
|
78
|
+
|
|
79
|
+
Repeat for the `testpypi` environment if you want TestPyPI publishes
|
|
80
|
+
to be gated similarly. (The `pypi` / `testpypi` environments are shared
|
|
81
|
+
with the chipzen-bot `release-python.yml` workflow — if you already
|
|
82
|
+
configured them for that package, no new environment is needed here.)
|
|
83
|
+
|
|
84
|
+
## Cutting a release
|
|
85
|
+
|
|
86
|
+
1. **Bump the version** in
|
|
87
|
+
[`packages/mcp/pyproject.toml`](pyproject.toml). Open a normal PR and
|
|
88
|
+
merge.
|
|
89
|
+
2. **Verify locally**:
|
|
90
|
+
```bash
|
|
91
|
+
cd packages/mcp
|
|
92
|
+
pip install --upgrade build
|
|
93
|
+
python -m build
|
|
94
|
+
ls dist/ # expect chipzen_mcp-X.Y.Z.tar.gz + .whl
|
|
95
|
+
```
|
|
96
|
+
3. **Tag the release** (after merging the version-bump PR to `main`):
|
|
97
|
+
```bash
|
|
98
|
+
git checkout main
|
|
99
|
+
git pull
|
|
100
|
+
git tag mcp-v0.1.0 # match the pyproject version exactly
|
|
101
|
+
git push origin mcp-v0.1.0
|
|
102
|
+
```
|
|
103
|
+
Pushing the tag triggers the workflow, which builds + publishes. The
|
|
104
|
+
`mcp-` prefix keeps this distinct from the chipzen-bot `python-v*`
|
|
105
|
+
tags in the same repo.
|
|
106
|
+
4. **Approve** the publish (if reviewers were added in setup step 3).
|
|
107
|
+
5. **Verify**: `pip install chipzen-mcp==0.1.0` in a clean venv, run
|
|
108
|
+
`chipzen-mcp --help` to confirm the console script resolves.
|
|
109
|
+
|
|
110
|
+
## Cutting a TestPyPI release first
|
|
111
|
+
|
|
112
|
+
Useful before a major bump to verify the wheel installs cleanly
|
|
113
|
+
without polluting the real PyPI namespace.
|
|
114
|
+
|
|
115
|
+
1. Actions → "Release MCP" → Run workflow.
|
|
116
|
+
2. Set `target_index` to `testpypi`, leave `dry_run` unchecked.
|
|
117
|
+
3. Approve the publish.
|
|
118
|
+
4. Install from TestPyPI (the `--extra-index-url` lets pip resolve the
|
|
119
|
+
`chipzen-bot` runtime dependency from real PyPI):
|
|
120
|
+
```bash
|
|
121
|
+
pip install --index-url https://test.pypi.org/simple/ \
|
|
122
|
+
--extra-index-url https://pypi.org/simple/ \
|
|
123
|
+
chipzen-mcp==0.1.0
|
|
124
|
+
```
|
|
125
|
+
5. Once happy, push the real tag (`mcp-v0.1.0`).
|
|
126
|
+
|
|
127
|
+
## Dry-run a build without publishing
|
|
128
|
+
|
|
129
|
+
For inspecting what the wheel + sdist actually contain before the
|
|
130
|
+
first real release.
|
|
131
|
+
|
|
132
|
+
1. Actions → "Release MCP" → Run workflow.
|
|
133
|
+
2. Check `dry_run` (publish job will be skipped).
|
|
134
|
+
3. Download the `chipzen-mcp-dist` artifact from the workflow run.
|
|
135
|
+
4. Inspect:
|
|
136
|
+
```bash
|
|
137
|
+
tar tzvf chipzen_mcp-0.1.0.tar.gz | head -30
|
|
138
|
+
unzip -l chipzen_mcp-0.1.0-py3-none-any.whl | head -30
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Yanking a bad release
|
|
142
|
+
|
|
143
|
+
If you publish a release with a critical bug:
|
|
144
|
+
|
|
145
|
+
1. Cut a fixed version (`0.1.1`) and publish it via the same flow.
|
|
146
|
+
2. Yank the bad version on PyPI:
|
|
147
|
+
- https://pypi.org/manage/project/chipzen-mcp/releases/
|
|
148
|
+
- Open the bad release → Yank → confirm.
|
|
149
|
+
3. Yanked releases stay installable for users who explicitly request
|
|
150
|
+
the version (so reproducibility holds), but pip will skip them by
|
|
151
|
+
default and emit a warning.
|
|
152
|
+
|
|
153
|
+
Do **not** delete a published release unless you absolutely must —
|
|
154
|
+
deletion blocks anyone reproducing an old build.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "chipzen-mcp"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Official Chipzen MCP server - let any MCP-capable agent play poker on chipzen.ai over the External-API remote-play track"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "Apache-2.0"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Chipzen, Inc.", email = "support@chipzen.ai" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["poker", "bot", "ai", "agent", "mcp", "model-context-protocol", "chipzen"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 2 - Pre-Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: Apache Software License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Topic :: Games/Entertainment",
|
|
26
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
27
|
+
"Typing :: Typed",
|
|
28
|
+
]
|
|
29
|
+
dependencies = [
|
|
30
|
+
# The published Chipzen SDK carries the whole External-API remote-play
|
|
31
|
+
# path (lobby -> matched -> gateway -> game loop -> reconnect); this
|
|
32
|
+
# package only bridges it onto MCP.
|
|
33
|
+
"chipzen-bot>=0.3.0",
|
|
34
|
+
# Official MCP Python SDK (FastMCP server).
|
|
35
|
+
"mcp>=1.5,<2",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[project.optional-dependencies]
|
|
39
|
+
dev = [
|
|
40
|
+
"pytest>=7.0",
|
|
41
|
+
"pytest-asyncio>=0.24.0",
|
|
42
|
+
"pytest-cov>=4.0",
|
|
43
|
+
"ruff>=0.5.0",
|
|
44
|
+
"mypy>=1.0",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[project.urls]
|
|
48
|
+
Homepage = "https://chipzen.ai"
|
|
49
|
+
Repository = "https://github.com/chipzen-ai/chipzen-sdk"
|
|
50
|
+
Issues = "https://github.com/chipzen-ai/chipzen-sdk/issues"
|
|
51
|
+
|
|
52
|
+
[project.scripts]
|
|
53
|
+
chipzen-mcp = "chipzen_mcp.server:main"
|
|
54
|
+
|
|
55
|
+
[tool.hatch.build.targets.wheel]
|
|
56
|
+
packages = ["src/chipzen_mcp"]
|
|
57
|
+
|
|
58
|
+
[tool.ruff]
|
|
59
|
+
target-version = "py310"
|
|
60
|
+
line-length = 100
|
|
61
|
+
|
|
62
|
+
[tool.ruff.lint]
|
|
63
|
+
select = ["E", "F", "I", "N", "W"]
|
|
64
|
+
ignore = ["E741", "N806"]
|
|
65
|
+
|
|
66
|
+
[tool.mypy]
|
|
67
|
+
# The published chipzen-bot 0.3.0 wheel predates the py.typed marker this
|
|
68
|
+
# repo now ships (the python-wheel-smoke CI job guards it going forward),
|
|
69
|
+
# so mypy can't see the SDK's inline types when installed from PyPI.
|
|
70
|
+
# Drop this override once the first post-0.3.0 SDK release is out.
|
|
71
|
+
[[tool.mypy.overrides]]
|
|
72
|
+
module = "chipzen.*"
|
|
73
|
+
ignore_missing_imports = true
|
|
74
|
+
|
|
75
|
+
[tool.pytest.ini_options]
|
|
76
|
+
testpaths = ["tests"]
|
|
77
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Chipzen MCP server -- play poker on chipzen.ai from any MCP-capable agent.
|
|
2
|
+
|
|
3
|
+
This package bridges the Chipzen External-API remote-play track (a persistent
|
|
4
|
+
push-style WebSocket protocol, packaged by the ``chipzen-bot`` SDK) onto the
|
|
5
|
+
Model Context Protocol's pull-style request/response tool model, so an agent
|
|
6
|
+
can be seated at a table with zero protocol code.
|
|
7
|
+
|
|
8
|
+
Status: **pre-alpha** (chipzen-ai/Chipzen#3748, runtime wiring complete).
|
|
9
|
+
The push->pull bridge, session lifecycle (cooperative stop, lobby-presence
|
|
10
|
+
surfacing), and the ``challenge_house_bot`` client are implemented; end-to-end
|
|
11
|
+
staging verification against the server side of chipzen-ai/Chipzen#3750 is
|
|
12
|
+
the remaining gate. Not published to any registry.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
__version__ = "0.1.0.dev0"
|
|
16
|
+
|
|
17
|
+
from chipzen_mcp.bridge import BridgeBot, ExternalSession, TurnRegistry, TurnSnapshot
|
|
18
|
+
from chipzen_mcp.config import (
|
|
19
|
+
ENV_BOT_ID,
|
|
20
|
+
ENV_ENV,
|
|
21
|
+
ENV_TOKEN,
|
|
22
|
+
McpConfig,
|
|
23
|
+
McpConfigError,
|
|
24
|
+
load_config,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"__version__",
|
|
29
|
+
# Bridge (push->pull) primitives
|
|
30
|
+
"TurnRegistry",
|
|
31
|
+
"TurnSnapshot",
|
|
32
|
+
"BridgeBot",
|
|
33
|
+
"ExternalSession",
|
|
34
|
+
# Config
|
|
35
|
+
"McpConfig",
|
|
36
|
+
"McpConfigError",
|
|
37
|
+
"load_config",
|
|
38
|
+
"ENV_TOKEN",
|
|
39
|
+
"ENV_BOT_ID",
|
|
40
|
+
"ENV_ENV",
|
|
41
|
+
]
|