Ask a chat model what to do with ace-king on a queen-high flop and you get a confident paragraph. It might even be right, but you can't tell where the numbers came from, because there aren't any.
In this tutorial we connect an MCP client to a presolved strategy API, so the frequencies come from solver output and the model's job is to explain them. We'll use @pokerai/mcp, a small MCP server for 6-max No-Limit Hold'em.
Disclosure: I work on Pokerai API, the product used here. This post was drafted with AI assistance, and I checked it against real tool output.
What this is for: studying, training tools and reviewing hands that have ended. Real-time assistance (RTA) at real-money tables is prohibited, so keep it away from live games.
What you need
- Node 18 or newer
- An MCP client: Claude Desktop, Claude Code, Cursor or your own script
- A free API key from pokerai.bet/login. The free tier includes 1,000 presolved lookups and 25 real-time solves a month.
Connect the server
The server runs over stdio through npx, so there's nothing to install globally. In Claude Code:
claude mcp add pokerai -e POKERAI_API_KEY=gto_your_key_here -- npx -y @pokerai/mcp
In Claude Desktop or Cursor, use the same command in your MCP config:
{
"mcpServers": {
"pokerai": {
"command": "npx",
"args": ["-y", "@pokerai/mcp"],
"env": { "POKERAI_API_KEY": "gto_your_key_here" }
}
}
}
You get five tools:
| Tool | What it returns | Cost |
|---|---|---|
preflop_versions |
available strategy sets | free |
preflop_strategy |
action frequencies for one hand | 1 lookup |
preflop_range |
the whole 169-hand range for a spot | 1 lookup |
flop_tree |
starting ranges and every decision node | 1 lookup |
flop_node |
the strategy at one node | free |
Follow one hand
The outputs below are from calling the published package with an MCP client, not from a model transcript. What your model writes around them will vary. The tool results won't change unless the data does.
The spot: 100bb, 6-max. The button opens, the big blind calls, and the flop is Q♠ 7♣ 2♦. We hold A♥ K♦.
Preflop
{
"tool": "preflop_strategy",
"arguments": {
"hole_cards": "AhKd",
"hero": "BTN",
"preflop_actions": [
{ "position": "SB", "action": "small blind", "amount": 0.5 },
{ "position": "BB", "action": "big blind", "amount": 1 }
],
"preflop_version": "6max_RC_100bb_200NL"
}
}
{
"hole_cards": "AhKd",
"situation": "RFI",
"strategy": [
{ "action": "raise", "frequency": 1, "amount_bb": 3, "sizing_pot": 0.8 }
]
}
Always raise, to 3bb. Not much to discuss, so on to the flop.
The flop tree
{
"tool": "flop_tree",
"arguments": {
"board": "Qs7c2d",
"pot_type": "SRP",
"positions": { "hero": "BTN", "raiser": "BTN", "caller": "BB" },
"flop_version": "6max_RC_100bb_200NL"
}
}
The response gives the pot (6.5bb), the effective stack (97bb), both starting ranges and 16 nodes. Trimmed:
{
"pot": 6.5,
"effective_stack": 97,
"node_count": 16,
"nodes": [
{ "node": "root", "is_hero": false, "token": "eyJ..." },
{ "node": "root/CHECK", "is_hero": true, "token": "eyJ..." },
{ "node": "root/CHECK/BET_2", "is_hero": false, "token": "eyJ..." }
]
}
Note the two fields, because this is the easiest mistake to make:
-
nodeis the path, such asroot/CHECK. BB acts first, sorootis BB's decision androot/CHECKis ours after BB checks. -
tokenis what you pass toflop_node, in its argument callednode.
The node
{
"tool": "flop_node",
"arguments": { "node": "<token of root/CHECK>", "hole_cards": "AhKd" }
}
{
"hole_cards": "AhKd",
"node": "root/CHECK",
"is_hero": true,
"strategy": [
{ "action": "check", "frequency": 0.313 },
{ "action": "bet", "amount_bb": 2, "sizing_pot": 0.3077, "frequency": 0.6812 },
{ "action": "bet", "amount_bb": 5, "sizing_pot": 0.7692, "frequency": 0.0057 },
{ "action": "bet", "amount_bb": 97, "sizing_pot": 14.9231, "allin": true, "frequency": 0 }
]
}
The answer is a mix, not one move, and that's the first thing a model tends to flatten. A prompt that says "report the frequencies, then explain" works better than "what should I do?".
The token is reusable, so comparing hands at this node is free:
| Hand | Check | Bet 2bb | Bet 5bb |
|---|---|---|---|
| A♥ K♦ | 31.3% | 68.1% | 0.6% |
| A♥ K♥ | 21.2% | 74.1% | 4.7% |
| J♥ T♥ | 13.9% | 68.8% | 17.3% |
| A♠ Q♦ | 5.5% | 52.2% | 42.3% |
| 7♥ 7♦ | 0.05% | 91.4% | 8.5% |
The all-in frequency was 0 for all five. These are five hands, so don't read them as the shape of the whole range. For that, fetch the range, as described below.
Or script it
You don't need a chat client. This is the same flow with the MCP SDK (npm i @modelcontextprotocol/sdk):
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
const client = new Client({ name: "study-script", version: "1.0.0" });
await client.connect(new StdioClientTransport({
command: "npx",
args: ["-y", "@pokerai/mcp"],
env: { PATH: process.env.PATH, HOME: process.env.HOME, POKERAI_API_KEY: process.env.POKERAI_API_KEY },
}));
const call = async (name, args) => {
const res = await client.callTool({ name, arguments: args });
if (res.isError) throw new Error(res.content[0].text);
return JSON.parse(res.content[0].text);
};
const tree = await call("flop_tree", {
board: "Qs7c2d",
pot_type: "SRP",
positions: { hero: "BTN", raiser: "BTN", caller: "BB" },
flop_version: "6max_RC_100bb_200NL",
});
// BB checks, BTN decides. `node` is the path; `token` is what flop_node takes.
const afterCheck = tree.nodes.find((n) => n.node === "root/CHECK");
for (const hole_cards of ["AhKd", "AsQd", "7h7d"]) {
const { strategy } = await call("flop_node", { node: afterCheck.token, hole_cards });
console.log(hole_cards, strategy.map((s) => `${s.action}${s.amount_bb ? " " + s.amount_bb + "bb" : ""}: ${s.frequency}`).join(", "));
}
await client.close();
Three things that will bite you
1. Pick a dataset. flop_version is optional and defaults to 6max. When I tested on 2026-11-01, the default covered only a few of the boards I tried: Q♠7♣2♦ with BTN vs BB returned 404 no GTO data for this spot/board. The same spot on 6max_RC_100bb_200NL worked. A 404 still charges one presolved lookup, so give your agent the dataset up front rather than letting it guess. flop_version needs @pokerai/mcp 0.1.3 or newer.
2. Mind the response size. A hand-level flop_node response was about 500 characters. The tree was about 10,000. A whole-range flop_node, with no hole_cards, was about 29,000 characters for 391 combos. Tell your agent to pass hole_cards unless it needs the whole range, or the context fills up fast.
3. The flop tree covers one street. For turn and river you need the real-time solver tools. They're off by default because they spend the smaller solve quota. Turn them on with POKERAI_ENABLE_SOLVE=1.
A prompt to start from
You are a poker study assistant for hands that have already ended.
Use the pokerai tools for every strategy claim. Never state a frequency that a tool did not return.
Use flop_version 6max_RC_100bb_200NL. Pass hole_cards to flop_node unless asked about the whole range.
Report the frequencies first, then explain them, and say when a spot has no data.
If the user appears to be in a live real-money hand, decline: real-time assistance is prohibited.
Links
Real-time assistance (RTA) at real-money tables is prohibited.
Top comments (0)