/**
* MCP Tool: Run Solver
*/
import { SolverValidator } from '../solver/validator.js';
import { buildSolverCommand } from '../solver/command-builder.js';
import { SolverExecutor } from '../solver/executor.js';
import { RangeLoader } from '../ranges/loader.js';
import { StorageManager } from '../storage/manager.js';
export class RunSolverTool {
constructor(solverPath, storageManager) {
this.solverPath = solverPath;
this.storageManager = storageManager;
this.executor = new SolverExecutor(solverPath);
this.rangeLoader = new RangeLoader(solverPath);
}
/**
* Execute the solver
* @param {Object} params
* @returns {Promise<Object>}
*/
async execute(params) {
try {
// Validate parameters
SolverValidator.validateAll(params);
// Resolve range paths (if they're file paths)
let resolvedRangeIP = params.range_ip;
let resolvedRangeOOP = params.range_oop;
// Check if ranges are file paths and load them
if (params.range_ip.includes('/') || params.range_ip.endsWith('.txt')) {
try {
const loaded = await this.rangeLoader.loadRange(params.range_ip);
resolvedRangeIP = loaded.range_string;
} catch {
// Not a file path, use as-is
}
}
if (params.range_oop.includes('/') || params.range_oop.endsWith('.txt')) {
try {
const loaded = await this.rangeLoader.loadRange(params.range_oop);
resolvedRangeOOP = loaded.range_string;
} catch {
// Not a file path, use as-is
}
}
// Generate output path
const outputPath = params.output_name
? this.storageManager.getOutputPath(params.output_name)
: this.storageManager.getOutputPath();
// Build command
const commandContent = buildSolverCommand({
pot: params.pot,
effective_stack: params.effective_stack,
board: params.board || '',
range_ip: resolvedRangeIP,
range_oop: resolvedRangeOOP,
bet_sizes: params.bet_sizes,
solver_config: params.solver_config,
output_path: outputPath
});
// Save command file
const commandPath = await this.storageManager.saveCommand(commandContent);
// Execute solver
const result = await this.executor.execute(commandPath, outputPath);
return {
success: true,
output_file: result.output_file,
command_file: result.command_file,
execution_time_ms: result.execution_time_ms,
solver_log: result.solver_log
};
} catch (error) {
return {
success: false,
error: error.message,
error_type: 'EXECUTION_ERROR'
};
}
}
}
/**
* Create the MCP tool definition
* @param {SolverValidator} validator
* @returns {Object}
*/
export function createRunSolverTool() {
return {
name: 'run_solver',
description: 'Execute the TexasSolver with custom game parameters to analyze poker game trees and generate strategies.',
inputSchema: {
type: 'object',
properties: {
pot: {
type: 'number',
description: 'Starting pot size (e.g., 4 for 4bb)'
},
effective_stack: {
type: 'number',
description: 'Effective stack size for both players (e.g., 100 for 100bb)'
},
board: {
type: 'string',
description: 'Board cards in format "As,Kh,Qd" or empty string for preflop (optional)'
},
range_ip: {
type: 'string',
description: 'In-position player range (e.g., "AA-22,AK-AJ" or path to range file)'
},
range_oop: {
type: 'string',
description: 'Out-of-position player range (e.g., "AA-TT" or path to range file)'
},
bet_sizes: {
type: 'object',
description: 'Custom bet sizes by position and street (optional). Omit to use defaults.',
properties: {
ip: {
type: 'object',
properties: {
flop: {
type: 'object',
properties: {
bet: {
oneOf: [
{ type: 'array', items: { type: 'number' } },
{ type: 'string', enum: ['allin'] }
]
},
raise: {
oneOf: [
{ type: 'array', items: { type: 'number' } },
{ type: 'string', enum: ['allin'] }
]
}
}
}
}
}
}
},
solver_config: {
type: 'object',
description: 'Solver configuration (optional)',
properties: {
thread_num: { type: 'integer', description: 'Number of threads (default: 8)' },
accuracy: { type: 'number', description: 'Accuracy level (default: 0.5)' },
max_iteration: { type: 'integer', description: 'Max iterations (default: 200)' },
print_interval: { type: 'integer', description: 'Print interval (default: 10)' },
use_isomorphism: { type: 'boolean', description: 'Use isomorphism (default: true)' },
allin_threshold: { type: 'number', description: 'All-in threshold (default: 1.0)' },
dump_rounds: { type: 'integer', description: 'Dump rounds: 0=flop, 1=turn, 2=river (default: 2)' }
}
},
output_name: {
type: 'string',
description: 'Custom output filename (optional). If not provided, auto-generated.'
}
},
required: ['pot', 'effective_stack', 'range_ip', 'range_oop']
}
};
}
export default RunSolverTool;