# =============================
# src/server.py
# =============================
"""
Main MCP server implementation for Pokemon battle simulator.
Provides MCP resources and tools for Pokemon data and battle simulation.
"""
from __future__ import annotations
import json
from typing import Optional, List
from mcp.server.fastmcp import FastMCP
from .datastore import PokemonDataStore
from .pokemon_types import Move, PokemonRecord
from .battle import run_battle
# Initialize FastMCP server instance
mcp = FastMCP("pokemon-mcp")
# Create global datastore instance for Pokemon data operations
store = PokemonDataStore()
# Resources
@mcp.resource("pokemon://list")
async def pokemon_list() -> str:
"""
MCP Resource: Get comprehensive Pokemon data including stats, abilities, moves, and evolution.
Returns:
str: JSON string containing detailed Pokemon data for the first 20 Pokemon.
Includes id, name, types, base stats, abilities, moves, and evolution info.
"""
# Get basic list of first 20 Pokemon
data = await store.list_pokemon(0, 20)
# Fetch detailed information for each Pokemon
detailed_pokemon = []
for pokemon in data:
# Get full Pokemon record with all details
mon = await store.get_pokemon(pokemon["name"])
# Structure the data for MCP response
detailed_pokemon.append({
"id": mon.id,
"name": mon.name,
"displayName": mon.display_name,
"types": mon.types,
"baseStats": mon.base_stats.as_dict(),
"abilities": [{"name": a.name, "effect": a.effect} for a in mon.abilities],
"moves": [{"name": m.name, "type": m.type, "power": m.power, "effect": m.effect} for m in mon.moves[:10]], # First 10 moves
"evolution": {
"evolvesFrom": mon.evolution_from,
"evolvesTo": mon.evolution_to or []
}
})
# Return formatted JSON response
return json.dumps({
"data": detailed_pokemon,
"count": len(detailed_pokemon)
}, indent=2)
# Tool
@mcp.tool()
async def simulate_battle(
pokemonA_name: str,
pokemonB_name: str,
pokemonA_level: int = 50,
pokemonB_level: int = 50,
pokemonA_moves: Optional[List[str]] = None,
pokemonB_moves: Optional[List[str]] = None,
seed: Optional[int | str] = None,
max_turns: int = 200,
) -> str:
"""
MCP Tool: Simulate a Pokémon battle between two Pokémon.
This function fetches Pokemon data, resolves custom movesets, and runs
a complete battle simulation with turn-by-turn logging.
Args:
pokemonA_name: Name or ID for side A Pokemon
pokemonB_name: Name or ID for side B Pokemon
pokemonA_level: Level for side A (1-100, default 50)
pokemonB_level: Level for side B (1-100, default 50)
pokemonA_moves: Optional list of up to 4 move names for side A
pokemonB_moves: Optional list of up to 4 move names for side B
seed: Optional seed for deterministic RNG (for reproducible battles)
max_turns: Maximum number of turns before declaring a draw (default 200)
Returns:
str: JSON string containing battle results including winner, turn count,
detailed battle log, and final status summary for both Pokemon.
"""
# Fetch Pokemon data from datastore
A = await store.get_pokemon(pokemonA_name)
B = await store.get_pokemon(pokemonB_name)
def resolve_moves(mon: PokemonRecord, sel: Optional[List[str]]) -> Optional[List[Move]]:
"""
Helper function to resolve custom move selection for a Pokemon.
Args:
mon: Pokemon record containing available moves
sel: List of move names to select from Pokemon's moveset
Returns:
Optional[List[Move]]: List of resolved Move objects, or None if no selection
"""
if not sel:
return None
out: List[Move] = []
# Convert selection to lowercase for case-insensitive matching
names = [s.lower() for s in sel[:4]] # Limit to 4 moves max
# Find matching moves in Pokemon's available moveset
for mv in mon.moves:
if mv.name.lower() in names:
out.append(mv)
return out or None
# Run the battle simulation
res = run_battle(
(A, max(1, min(100, pokemonA_level)), resolve_moves(A, pokemonA_moves)),
(B, max(1, min(100, pokemonB_level)), resolve_moves(B, pokemonB_moves)),
seed=seed,
max_turns=max_turns,
)
# Return battle results as JSON
return json.dumps({
"winner": res.winner,
"turns": res.turns,
"log": res.log,
"summary": res.summary,
}, indent=2)
if __name__ == "__main__":
"""
Main entry point for the MCP server.
Runs the server with STDIO transport for MCP communication.
Note: Avoid print() statements as they interfere with STDIO transport.
"""
mcp.run(transport="stdio")