@cyanheads/pokeapi-mcp-server
Look up Pokémon, moves, abilities, items, natures, and type matchups from PokéAPI v2 via MCP. STDIO or Streamable HTTP.
Public Hosted Server: https://pokeapi.caseyjhand.com/mcp
Overview
Pokémon game data from PokéAPI: Pokémon, moves, abilities, items, and natures. Get a complete Pokémon profile in one call (stats, abilities, evolution, sprites), filter by generation, type, pokédex, or egg group, and compute dual-type matchups.
Tools
| Tool | Description |
|---|---|
pokeapi_get_pokemon | Denormalized Pokémon dossier in one call — stats, types, abilities, evolution chain, sprites, and species data |
pokeapi_get_type_matchups | Computed offensive and defensive type effectiveness for a type or Pokémon, with correctly composed dual-type matchups |
pokeapi_get_move | Move details — type, damage class, power, accuracy, PP, priority, stat changes, and effect text |
pokeapi_get_ability | Ability details — effect text and the Pokémon that have it, with hidden-ability flag and slot |
pokeapi_get_item | Item details — effect text, category, versioned prices, fling power, attributes, and common holders |
pokeapi_get_nature | Nature details — stat boost/penalty and berry flavor preferences; lists all 25 when called without an identifier |
pokeapi_find_pokemon | Filter Pokémon by generation, type, pokédex, or egg group, with name-token matching and pagination |
Resources
| Resource | Description |
|---|---|
pokeapi://pokemon/{identifier} | Pokémon dossier by name or PokéAPI Pokémon-record ID — same payload as pokeapi_get_pokemon without moves |
pokeapi://type/{typeName} | Type damage relations — raw multiplier table, offensive and defensive |
All resource data is also reachable via tools.
Capability reference
pokeapi_get_pokemon tool
- Accepts a lowercase-hyphenated name or numeric PokéAPI Pokémon-record ID as
identifier; an unknown entry returnsnot_found. A species name resolves to its default variety (deoxysreturnsdeoxys-normal, withresolvedFromSpecies: "deoxys"). - Returns stats, types, ability effects, sprites, evolution chain (every
evolutionDetailsalternative), varieties, capture and growth rates, gender ratio, and legendary/mythical flags in one dossier. include_moves(defaultfalse) adds the move summary;moveCountis always returned.game_versionselects flavor text and falls back to the most recent English entry.
pokeapi_get_type_matchups tool
- Requires exactly one of
type(type name) orpokemon(name or PokéAPI Pokémon-record ID); unknown entries returnnot_found. - Species names resolve to their default variety (
deoxysusesdeoxys-normal);resolvedPokemonandresolvedFromSpeciesreport the match and are null for direct type queries. - Returns
offensiveRelations(null for dual-type Pokémon) anddefensiveMatchups, with dual-type defenses composed and immunity taking precedence. composedMultiplierscarries 0, 0.25, 0.5, 1, 2, or 4 per attacking type; absent types also deal 1×.
pokeapi_get_move tool
- Accepts a lowercase-hyphenated move name or numeric ID; an unknown entry returns
not_found. - Returns type, damage class, power, accuracy, PP, priority, target, stat changes, and secondary-effect chance, plus full and short English effect text
include_learners(defaultfalse) adds the list of Pokémon that can learn the move.learnersIncludeddistinguishes an unrequested list from a requested list with no known learners.
pokeapi_get_ability tool
- Accepts a lowercase-hyphenated ability name or numeric ID
- Returns full and short English effect text, the generation introduced, and every Pokémon that has the ability, with its hidden-ability flag and slot
not_foundwhen the identifier resolves to no ability
pokeapi_get_item tool
- Accepts a lowercase-hyphenated item name or numeric ID
- Returns category, fling power, attributes (holdable, consumable, etc.), sprite URL, effect text, and Pokémon that commonly hold it
priceslists every version/currency row (versionGroup,currency,purchasePrice,sellPrice); null means not purchasable/sellable there, and an empty list means no price records.costis the legacy Pokédollar cost, or null.not_foundwhen the identifier resolves to no item
pokeapi_get_nature tool
identifier(name or ID 1–25) is optional — omit it to return all 25 natures at once (isListAll: true)- Each entry carries the boosted stat, reduced stat, and liked/disliked berry flavor — all null for the 5 neutral natures
not_foundwhen a provided identifier resolves to no nature
pokeapi_find_pokemon tool
- Requires at least one of
generation,type,pokedex, andegg_group, combined with AND logic;query(at most 100 characters) adds per-token name matching within them. Unrecognized category names returninvalid_filter. - Returns
idandnameentries for follow-uppokeapi_get_pokemoncalls, withtotalCountbefore paging. Type-only searches include forms; generation, pokédex, and egg-group filters return species. IDs are PokéAPI IDs, not dex numbers. appliedFiltersechoes the filters, query tokens,limit, andoffsetthat were applied. A call with no category returns no entries and a notice saying one is required.limit(default 50) andoffset(default 0) paginate the filtered set. A page past the last match keepstotalCountand advisesoffset: 0; zero matches advise relaxing the filters.
pokeapi://pokemon/{identifier} resource
- Same payload as
pokeapi_get_pokemonwithinclude_movesfixed tofalse identifieris a name or PokéAPI Pokémon-record ID, including form IDs; a species name resolves to its default varietynot_foundwhen the identifier matches no Pokémon record and no species
pokeapi://type/{typeName} resource
- Returns the raw offensive and defensive damage-relation multiplier table for one type
typeNameis one of the 18 canonical Pokémon typesnot_foundwhen the type name doesn't exist in PokéAPI
Features
Built on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
PokéAPI-specific:
- Keyless and read-only — no API key, no auth, no configuration required to run
- Graph-walk consolidation —
pokeapi_get_pokemonfans out across/pokemon,/pokemon-species,/evolution-chain, and N/abilityendpoints in two parallel tiers, returning one object - Aggressive caching — PokéAPI data is static game data; responses are cached with a configurable TTL (default 6 h) to respect PokéAPI's fair-use policy
- Input normalization — accepts lowercase-hyphenated names or numeric IDs; trims, lowercases, and hyphenates whitespace. A blank identifier or one over 100 characters returns
not_found(invalid_filterfor a search filter) without an upstream request - English-first —
effect_entriesandflavor_text_entriesare always filtered tolanguage.name === 'en'; absent entries surface asnullrather than a foreign-language string
Agent-friendly output:
- Dual-type composition —
pokeapi_get_type_matchupscomputes the effective matchup matrix from raw damage relations, so agents get a direct answer rather than raw tables to multiply - Variant surface —
pokeapi_get_pokemonlists all form variants so agents can identify and re-call with specific forms (Alolan, Galarian, Mega, Gigantamax) - Nullable details — missing descriptions, sprites, holders, and learners are stated explicitly in text and structured results rather than omitted
Getting started
Public Hosted Instance
A public instance is available at https://pokeapi.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"pokeapi-mcp-server": {
"type": "streamable-http",
"url": "https://pokeapi.caseyjhand.com/mcp"
}
}
}
Self-Hosted / Local
No API key required. Add the following to your MCP client configuration file:
{
"mcpServers": {
"pokeapi-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/pokeapi-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"pokeapi-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/pokeapi-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"pokeapi-mcp-server": {
"type": "stdio",
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "MCP_TRANSPORT_TYPE=stdio",
"ghcr.io/cyanheads/pokeapi-mcp-server:latest"
]
}
}
}
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp
Prerequisites
- Bun v1.4.0 or higher (or Node.js v24+).
- No API key required — PokéAPI is fully public.
Installation
- Clone the repository:
git clone https://github.com/cyanheads/pokeapi-mcp-server.git
- Navigate into the directory:
cd pokeapi-mcp-server
- Install dependencies:
bun install
- Configure environment (optional):
cp .env.example .env
# All vars are optional — the server works with defaults
Configuration
| Variable | Description | Default |
|---|---|---|
POKEAPI_BASE_URL | PokéAPI base URL — override for local mirrors or proxies. | https://pokeapi.co/api/v2 |
POKEAPI_CACHE_TTL_SECONDS | How long to cache PokéAPI responses (seconds). | 21600 (6 h) |
POKEAPI_REQUEST_TIMEOUT_MS | Per-request timeout in milliseconds. | 10000 |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_SESSION_MODE | HTTP session mode: auto, stateful, or stateless. | stateless |
MCP_HTTP_PORT | Port for HTTP server. | 3010 |
MCP_AUTH_MODE | Auth mode: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (RFC 5424). | info |
LOGS_DIR | Directory for log files (Node.js only). | <project-root>/logs |
LOG_TOOL_FAILURE_PAYLOADS | Log failed-call input and result, redacted by key name and capped at LOG_TOOL_FAILURE_PAYLOAD_MAX_BYTES (default 16384). Secrets inside free-form values are not redacted. | false |
OTEL_ENABLED | Enable OpenTelemetry instrumentation. | false |
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT | OTLP log export endpoint. | Unset |
See .env.example for the full list of optional overrides.
Self-hosting for high-volume use
PokéAPI's Fair Use Policy asks consumers to cache aggressively and points high-volume deployments toward running a local instance. This server already caches responses for 6 hours by default (POKEAPI_CACHE_TTL_SECONDS), which covers most workloads. For hosted or batch-heavy deployments, run the official PokéAPI Docker image locally and point POKEAPI_BASE_URL at it — the server switches transparently.
Running the server
Local development
-
Build and run:
bun run rebuild bun run start:stdio # or bun run start:http -
Run checks and tests:
bun run devcheck # Lint, format, typecheck, security bun run test # Offline Vitest suite with deterministic fetch fixtures bun run lint:mcp # Validate MCP definitions against spec
Docker
docker build -t pokeapi-mcp-server .
docker run --rm -p 3010:3010 pokeapi-mcp-server
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/pokeapi-mcp-server. Build with --build-arg OTEL_ENABLED=false to omit OpenTelemetry peer dependencies.
Project structure
| Path | Purpose |
|---|---|
src/index.ts | createApp() entry point — registers tools, resources, and inits services. |
src/config/ | Server-specific env var parsing with Zod (server-config.ts). |
src/mcp-server/tools/ | Tool definitions (*.tool.ts). |
src/mcp-server/resources/ | Resource definitions (*.resource.ts). |
src/services/pokeapi/ | PokeApiService — typed fetch methods, caching, retry, timeout. |
tests/ | Vitest test suite mirroring src/. |
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
- Handlers throw, framework catches — catch typed upstream errors only to map a declared
errors[]contract withctx.fail(...) - Use
ctx.logfor request-scoped logging,ctx.statefor tenant-scoped storage (and caching) - Register new tools and resources in the
createApp()arrays insrc/index.ts - Wrap external API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields
Contributing
Issues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
License
Apache-2.0 — see LICENSE for details.