formatters.ts•5.48 kB
/**
* Response formatters for MCP tools
*/
import type { Pokemon, Type, LocationAreaEncounter } from "./client.js";
export interface MCPTextContent {
[x: string]: unknown;
type: "text";
text: string;
_meta?: { [x: string]: unknown } | undefined;
}
export interface MCPResponse {
[x: string]: unknown;
content: MCPTextContent[];
_meta?: { [x: string]: unknown } | undefined;
structuredContent?: { [x: string]: unknown } | undefined;
isError?: boolean | undefined;
}
/**
* Format a Pokemon object into a readable text response
*/
export function formatPokemon(pokemon: Pokemon): MCPResponse {
const stats = pokemon.stats
.map((s) => `${s.stat.name}: ${s.base_stat}`)
.join(", ");
const types = pokemon.types.map((t) => t.type.name).join(", ");
const abilities = pokemon.abilities
.map((a) =>
a.is_hidden ? `${a.ability.name} (hidden)` : a.ability.name,
)
.join(", ");
const artworkUrl = pokemon.sprites.other?.["official-artwork"]?.front_default;
return {
content: [
{
type: "text",
text: `**${pokemon.name}** (ID: ${pokemon.id})
Height: ${pokemon.height} decimetres (${(pokemon.height / 10).toFixed(1)}m)
Weight: ${pokemon.weight} hectograms (${(pokemon.weight / 10).toFixed(1)}kg)
Base Experience: ${pokemon.base_experience || "N/A"}
Order: ${pokemon.order || "N/A"}
**Types:** ${types}
**Abilities:** ${abilities}
**Base Stats:** ${stats}
**Sprites:**
- Default: ${pokemon.sprites.front_default || "N/A"}
- Shiny: ${pokemon.sprites.front_shiny || "N/A"}
${artworkUrl ? `- Artwork: ${artworkUrl}` : ""}`,
},
],
};
}
/**
* Format type effectiveness information into a readable text response
*/
export function formatTypeEffectiveness(effectiveness: {
type: Type;
strongAgainst: string[];
weakAgainst: string[];
immuneTo: string[];
resistantTo: string[];
vulnerableTo: string[];
}): MCPResponse {
const formatList = (list: string[]) =>
list.length > 0 ? list.join(", ") : "None";
const pokemonList = effectiveness.type.pokemon
.slice(0, 10)
.map((p) => p.pokemon.name)
.join(", ");
const moreCount = effectiveness.type.pokemon.length > 10
? ` and ${effectiveness.type.pokemon.length - 10} more...`
: "";
return {
content: [
{
type: "text",
text: `**${effectiveness.type.name.toUpperCase()} Type Effectiveness**
**Strong Against (2x damage to):** ${formatList(effectiveness.strongAgainst)}
**Weak Against (0.5x damage to):** ${formatList(effectiveness.weakAgainst)}
**No Effect Against (0x damage to):** ${formatList(effectiveness.immuneTo)}
**Resistant To (0.5x damage from):** ${formatList(effectiveness.resistantTo)}
**Vulnerable To (2x damage from):** ${formatList(effectiveness.vulnerableTo)}
**Pokémon with this type:** ${pokemonList}${moreCount}`,
},
],
};
}
/**
* Format Pokemon encounters into a readable text response
*/
export function formatPokemonEncounters(
pokemon: Pokemon,
encounters: LocationAreaEncounter[]
): MCPResponse {
if (encounters.length === 0) {
return {
content: [
{
type: "text",
text: `**${pokemon.name}** has no recorded wild encounters. This Pokémon might be:
- A starter Pokémon
- Obtained through evolution
- A legendary/mythical Pokémon
- Only available through special events`,
},
],
};
}
const locationInfo = encounters
.map((encounter) => {
const locationName = encounter.location_area.name.replace("-", " ");
const versions = encounter.version_details
.map((vd) => {
const encounterMethods = vd.encounter_details
.map(
(ed) =>
`${ed.method.name} (Lv.${ed.min_level}-${ed.max_level}, ${ed.chance}% chance)`,
)
.join(", ");
return `${vd.version.name}: ${encounterMethods}`;
})
.join("\n ");
return `**${locationName}:**\n ${versions}`;
})
.join("\n\n");
return {
content: [
{
type: "text",
text: `**${pokemon.name} Encounter Locations:**\n\n${locationInfo}`,
},
],
};
}
/**
* Format Pokemon search results into a readable text response
*/
export function formatPokemonSearchResults(
query: string,
matches: Array<{ name: string; url: string }>
): MCPResponse {
if (matches.length === 0) {
return {
content: [
{
type: "text",
text: `No Pokémon found matching "${query}". Try a different search term.`,
},
],
};
}
const resultText = matches
.map((p, index) => {
const id = p.url.split("/").filter(Boolean).pop();
return `${index + 1}. ${p.name} (ID: ${id})`;
})
.join("\n");
return {
content: [
{
type: "text",
text: `**Search results for "${query}":**\n\n${resultText}\n\n*Use fetch_pokemon with any of these names to get detailed information.*`,
},
],
};
}
/**
* Format error messages into MCP response format
*/
export function formatError(message: string): MCPResponse {
return {
content: [
{
type: "text",
text: message,
},
],
};
}
/**
* Format generic error from caught exception
*/
export function formatCaughtError(error: unknown, context: string): MCPResponse {
const message = error instanceof Error ? error.message : String(error);
return formatError(`Error ${context}: ${message}`);
}