// ABOUTME: MCP tool handlers wrapping Minesweeper Rails API calls.
// ABOUTME: Validates inputs and delegates to the Rails client.
import { RailsClient } from "../rails/client.js";
import { requireCoordinates, requireString, requireStringOrEnv } from "./validation.js";
export type ToolDefinition = {
name: string;
description: string;
inputSchema: Record<string, unknown>;
};
export type ToolHandler = (input: unknown) => Promise<unknown>;
export function createTools(rails: RailsClient): {
definitions: ToolDefinition[];
handlers: Map<string, ToolHandler>;
} {
const handlers = new Map<string, ToolHandler>();
const addTool = (definition: ToolDefinition, handler: ToolHandler) => {
handlers.set(definition.name, handler);
return definition;
};
const definitions = [
addTool(
{
name: "user_start",
description: "Start a new game for a user slug.",
inputSchema: {
type: "object",
properties: {
user_slug: { type: "string" },
},
},
},
async (input) => {
const userSlug = requireStringOrEnv(
(input as { user_slug?: unknown })?.user_slug,
"user_slug",
process.env.MINESWEEPER_USER_SLUG,
"MINESWEEPER_USER_SLUG"
);
return rails.startGame(userSlug);
}
),
addTool(
{
name: "game_state",
description: "Fetch the public state for a game.",
inputSchema: {
type: "object",
properties: {
public_id: { type: "string" },
},
required: ["public_id"],
},
},
async (input) => {
const publicId = requireString(
(input as { public_id?: unknown })?.public_id,
"public_id"
);
return rails.getState(publicId);
}
),
addTool(
{
name: "game_open",
description: "Open a cell in a game.",
inputSchema: {
type: "object",
properties: {
public_id: { type: "string" },
x: { type: "integer" },
y: { type: "integer" },
},
required: ["public_id", "x", "y"],
},
},
async (input) => {
const publicId = requireString(
(input as { public_id?: unknown })?.public_id,
"public_id"
);
const { x, y } = requireCoordinates(input);
return rails.openCell(publicId, x, y);
}
),
addTool(
{
name: "game_flag",
description: "Toggle a flag on a cell.",
inputSchema: {
type: "object",
properties: {
public_id: { type: "string" },
x: { type: "integer" },
y: { type: "integer" },
},
required: ["public_id", "x", "y"],
},
},
async (input) => {
const publicId = requireString(
(input as { public_id?: unknown })?.public_id,
"public_id"
);
const { x, y } = requireCoordinates(input);
return rails.flagCell(publicId, x, y);
}
),
addTool(
{
name: "game_chord",
description: "Chord a cell in a game.",
inputSchema: {
type: "object",
properties: {
public_id: { type: "string" },
x: { type: "integer" },
y: { type: "integer" },
},
required: ["public_id", "x", "y"],
},
},
async (input) => {
const publicId = requireString(
(input as { public_id?: unknown })?.public_id,
"public_id"
);
const { x, y } = requireCoordinates(input);
return rails.chordCell(publicId, x, y);
}
),
addTool(
{
name: "game_end",
description: "End a game.",
inputSchema: {
type: "object",
properties: {
public_id: { type: "string" },
},
required: ["public_id"],
},
},
async (input) => {
const publicId = requireString(
(input as { public_id?: unknown })?.public_id,
"public_id"
);
return rails.endGame(publicId);
}
),
addTool(
{
name: "user_games",
description: "List public games for a user slug.",
inputSchema: {
type: "object",
properties: {
user_slug: { type: "string" },
},
},
},
async (input) => {
const userSlug = requireStringOrEnv(
(input as { user_slug?: unknown })?.user_slug,
"user_slug",
process.env.MINESWEEPER_USER_SLUG,
"MINESWEEPER_USER_SLUG"
);
return rails.listGames(userSlug);
}
),
];
return { definitions, handlers };
}