MCP Rand
by turlockmike
- src
- handlers
import { CallToolRequestSchema, CallToolResultSchema, McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
type CallToolRequest = typeof CallToolRequestSchema._output;
type CallToolResult = typeof CallToolResultSchema._output;
interface Card {
suit: 'hearts' | 'diamonds' | 'clubs' | 'spades';
value: string; // '2' through '10', 'J', 'Q', 'K', 'A'
}
export const toolSpec = {
name: 'draw_cards',
description: 'Draw cards from a standard deck of playing cards',
inputSchema: {
type: 'object' as const,
properties: {
count: {
type: 'number',
description: 'Number of cards to draw',
},
deckState: {
type: 'string',
description: 'Optional base64 encoded string representing the current deck state',
}
},
required: ['count']
}
};
const SUITS = ['hearts', 'diamonds', 'clubs', 'spades'] as const;
const VALUES = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A'];
function createDeck(): Card[] {
const deck: Card[] = [];
for (const suit of SUITS) {
for (const value of VALUES) {
deck.push({ suit, value });
}
}
return deck;
}
function shuffleArray<T>(array: T[]): T[] {
const shuffled = [...array];
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
return shuffled;
}
function getCardIndex(card: Card): number {
const suitIndex = SUITS.indexOf(card.suit);
const valueIndex = VALUES.indexOf(card.value);
return suitIndex * VALUES.length + valueIndex;
}
function getDeckStateFromCards(availableCards: Card[]): string {
// Create a binary array where 1 represents available cards
const binaryArray = new Uint8Array(7); // 52 bits rounded up to bytes
for (const card of availableCards) {
const index = getCardIndex(card);
const byteIndex = Math.floor(index / 8);
const bitIndex = index % 8;
binaryArray[byteIndex] |= 1 << bitIndex;
}
return Buffer.from(binaryArray).toString('base64');
}
function getAvailableCardsFromState(deckState: string): Card[] {
try {
const binaryArray = Buffer.from(deckState, 'base64');
if (binaryArray.length !== 7) {
throw new McpError(
ErrorCode.InvalidParams,
'Invalid deck state: incorrect length'
);
}
const availableCards: Card[] = [];
for (let i = 0; i < 52; i++) {
const byteIndex = Math.floor(i / 8);
const bitIndex = i % 8;
if ((binaryArray[byteIndex] & (1 << bitIndex)) !== 0) {
const suitIndex = Math.floor(i / VALUES.length);
const valueIndex = i % VALUES.length;
availableCards.push({
suit: SUITS[suitIndex],
value: VALUES[valueIndex]
});
}
}
return availableCards;
} catch (error) {
throw new McpError(
ErrorCode.InvalidParams,
'Invalid deck state: must be base64 encoded'
);
}
}
export const drawCardsHandler = async (
request: CallToolRequest
): Promise<CallToolResult> => {
const args = request.params.arguments as { count: number; deckState?: string };
if (args.count <= 0) {
throw new McpError(
ErrorCode.InvalidParams,
'Must draw at least one card'
);
}
// Validate and process deck state if provided
let availableCards: Card[];
if (args.deckState) {
// Validate base64 format
if (!/^[A-Za-z0-9+/]+=*$/.test(args.deckState)) {
throw new McpError(
ErrorCode.InvalidParams,
'Invalid deck state: must be base64 encoded'
);
}
availableCards = getAvailableCardsFromState(args.deckState);
} else {
availableCards = createDeck();
}
if (args.count > availableCards.length) {
throw new McpError(
ErrorCode.InvalidParams,
`Cannot draw ${args.count} cards from deck with ${availableCards.length} cards remaining`
);
}
// Shuffle the available cards
const shuffledCards = shuffleArray(availableCards);
// Draw the requested number of cards
const drawnCards = shuffledCards.slice(0, args.count);
// Calculate remaining cards and get their state
const remainingCards = shuffledCards.slice(args.count);
const newDeckState = getDeckStateFromCards(remainingCards);
const result = {
drawnCards,
remainingCount: remainingCards.length,
deckState: newDeckState
};
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2)
}
]
};
};