Skip to main content
Glama
Guild-Wars-MCP

GW2 MCP Server

gw2mcp — GW2 Wiki Answers

An agentic AI assistant that answers Guild Wars 2 questions from authoritative sources: the official wiki and the official GW2 API, with citations back to the wiki. Spec: specs/gw2-agentic-ai-spec.md.

Two components, one repo:

  • MCP tool server (gw2mcp-server) — a FastMCP server exposing wiki search/fetch and GW2 API tools (items, skills, live trading-post prices, daily fractals, event timers, waypoint/POI chat codes) to any MCP client: Claude Desktop/Code, ChatGPT, Gemini, or our own agent.

  • Agent core + adapters (gw2mcp-repl, gw2mcp-discord) — the provider-pluggable tool-use loop (Anthropic, OpenAI, or OpenRouter) that turns "question in" into "grounded answer + citations out". Transport-agnostic: a terminal REPL and a Discord bot share the same core.

REPL / Discord bot              any MCP client (Claude, ChatGPT, Gemini)
        │                                        │
   Agent core (Anthropic/OpenAI/OpenRouter)      │
        │  MCP: in-memory (default) or HTTP      │  MCP: stdio or HTTP
        └────────────────┬───────────────────────┘
                 FastMCP server (gw2mcp)
                 wiki_search · wiki_get_page · get_event_timer · search_items
                 get_item · get_item_prices · search_skills · get_skill
                 get_build_id · get_daily_fractals · search_waypoints
                          │
                 Supabase Postgres (TTL cache · name index · run traces)

Setup

Prereqs: uv, Supabase CLI, Docker (for local Supabase).

supabase start            # local Postgres on 127.0.0.1:54322
supabase migration up     # create cache/name-index/run-trace tables
uv sync --all-groups
cp .env.example .env      # then fill in ANTHROPIC_API_KEY

Every config variable the code reads is documented in .env.example (optional tunables are listed commented-out with their defaults). Secrets live only in .env (gitignored) — never commit them.

Related MCP server: OSRS MCP Server

Run

uv run gw2mcp-repl                      # terminal REPL (agent + in-process server)
uv run gw2mcp-server                    # MCP server over stdio (Claude Desktop/Code)
uv run gw2mcp-server --transport http   # MCP server over streamable HTTP :8720

To point an external MCP client at the server, use stdio config (command: uv, args: [run, gw2mcp-server], cwd this repo) or the HTTP URL http://127.0.0.1:8720/mcp.

To run the agent against a separately running server instead of in-process, set MCP_SERVER_URL in .env.

Authentication (optional)

By default, the HTTP MCP server is unauthenticated — any client can call tools. Setting MCP_AUTH_ENABLED=true requires all HTTP clients to authenticate via OAuth (WorkOS AuthKit) before accessing tools. Local transports are unaffected: stdio (Claude Desktop) and the in-memory transport (REPL, tests) never see the HTTP auth layer. Pointing the REPL at a remote authed server via MCP_SERVER_URL is not supported (it sends no bearer token).

Discord /link flow

Users can link their Discord account to a WorkOS identity (for higher rate limits) and optionally store an encrypted GW2 API key through Discord slash commands:

  1. /link opens a temporary login URL to your AuthKit application.

  2. User completes sign-up or login at the AuthKit domain.

  3. Authorization redirects back to the MCP server with an authorization code (which the server exchanges with WorkOS).

  4. The bot stores the linked identity (person and tier); future /gw2 calls run at the linked tier with higher rate limits.

  5. Optionally, /setkey opens a modal to securely store an encrypted GW2 API key.

  6. /whoami shows the current link status; /unlink revokes the authorization.

/account — GW2 API key for MCP clients

MCP users (Claude Desktop, etc.) manage their GW2 API key in the browser at {PUBLIC_BASE_URL}/account — sign in with the same WorkOS account the MCP OAuth uses, then save, replace, or clear the key. Keys are validated against the GW2 API before storing and encrypted at rest; the key never appears in the agent conversation. The account_info MCP tool returns this URL plus non-secret status (key stored, tier, Discord linked).

The key is stored on the person, not the surface: set it via Discord /setkey or via /account and it is available to both — provided you sign into the same WorkOS account in both places. /link now asks for an explicit confirmation in the browser before attaching a Discord account.

New env var: ACCOUNT_SESSION_TTL_S (optional, default 1800) — browser session lifetime for /account.

Upgrade note: when /link (WorkOS) settings are configured, GW2_KEY_ENCRYPTION_KEY is now REQUIRED at startup — a server with link settings but a missing or invalid Fernet key refuses to boot.

WorkOS dashboard setup

To enable the /link flow and OAuth on the HTTP server:

  1. Create an AuthKit application in your WorkOS organization; note the AuthKit domain, Client ID, and API key.

  2. In AuthKit → ConfigurationDynamic Client Registration: enable it (required so Claude/ChatGPT can self-register with the MCP server).

  3. In the Redirects tab, add both redirect URIs:

    • Add <PUBLIC_BASE_URL>/link/callback (e.g., https://your-host.example.com/link/callback).

    • Add <PUBLIC_BASE_URL>/account/callback to the allowed Redirect URIs (exact byte match — 127.0.0.1 vs localhost matters).

  4. Recommended: In OAuthResource Indicators (RFC 8707): enable it and register the MCP resource URL (e.g., https://your-host.example.com/mcp). This ensures tokens carry the correct aud (audience) claim.

Fill in the corresponding .env variables: WORKOS_CLIENT_ID, WORKOS_API_KEY, AUTHKIT_DOMAIN, PUBLIC_BASE_URL. Generate LINK_SIGNING_KEY and GW2_KEY_ENCRYPTION_KEY as random cryptographic values (see comments in .env.example).

Smoke-testing account tools

The REPL has no person identity, so account tools answer with setup guidance there — that itself is the no-identity smoke test. Real-data smoke tests (do NOT commit keys):

  1. Discord path: /setkey with a real API key (all permissions), then /ask what's in my material storage? — expect real counts, ephemeral.

  2. Hosted OAuth path: connect Claude to the hosted server, store a key at /account, then ask the same question.

  3. Ask with a key lacking inventories — expect a message naming the missing permission, not an error.

Discord bot

The Discord adapter (spec §6.5–6.6) runs the same agent core behind two slash commands: /gw2 question:<text> (defers ephemerally, then edits the answer in, splitting across follow-ups past Discord's 2000-char limit) and /about. Per-user (5/10 min) and per-guild (30/hour) rate limits plus a global monthly budget kill-switch ($50 default, summed across all runs including REPL and eval) are enforced as aggregate queries over run_traces — fail-closed: if Postgres is unreachable, the bot declines to answer rather than run unmetered.

Discord application setup (once per environment)

Dev and prod are separate Discord applications so guild-scoped dev commands never collide with global prod ones.

  1. Create an application at the Discord Developer PortalBot tab → Reset Token → put it in .env as DISCORD_TOKEN. No privileged intents are needed (slash commands only — leave presence, server members, and message content off).

  2. For dev: enable Developer Mode in your Discord client, right-click your test server → Copy Server ID → set DISCORD_DEV_GUILD_ID in .env. Commands sync instantly to that guild. For prod: leave it unset — commands register globally (first propagation can take up to an hour).

  3. Invite the bot with (replace <APP_ID> from the portal's General Information page):

    https://discord.com/api/oauth2/authorize?client_id=<APP_ID>&scope=bot%20applications.commands&permissions=2048

    permissions=2048 is just Send Messages; interaction replies work even without it, keeping the permission footprint minimal.

Run

uv run gw2mcp-discord     # requires DISCORD_TOKEN in .env, local Supabase running

Rate-limit and budget knobs (RATE_LIMIT_*, MONTHLY_BUDGET_USD) are documented in .env.example.

Eval

uv run python eval/run_eval.py              # full 25-question set, manual scoring
uv run python eval/run_eval.py --category live --limit 2

The question set (eval/questions.yaml) is versioned; re-run it on every significant prompt/tool change. Results land in eval/results/*.jsonl.

Baseline (2026-07-22, claude-sonnet-5, prompt v1): 21/25 pass (84%), 3 partial, 1 fail · median latency 12.0s (target ≤15s ✓) · avg cost $0.0621/question (target ≤$0.06, marginal). The fail + 2 partials traced to a wikitext-cleaner bug that stripped inline cost templates ({{gold|…}}, {{item icon|…}}); fixed after this run — re-run the eval to record a v2 baseline against the ≥90% target.

Model

LLM_PROVIDER env var picks the backend — anthropic (default), openai, or openrouter — and MODEL_ID names the model in that provider's naming (default claude-sonnet-5, $3/M input, $15/M output — the best price/quality Sonnet-class model per the spec's cost target of ≤ $0.06/question; OpenRouter ids are vendor-prefixed, e.g. anthropic/claude-sonnet-5). The agent loop is provider-agnostic (src/gw2mcp/agent/providers/); pricing for cost accounting lives in src/gw2mcp/agent/costs.py and is keyed by (provider, model) — a pair without a pricing entry refuses to start (fail-closed, so nothing can spend invisibly to the monthly budget cap).

Tests

uv run pytest            # fully offline: respx-mocked HTTP, in-memory MCP, fake cache

Observability

Every run writes one JSON line to logs/runs.jsonl (every tool call, tokens, cost, latency) and a queryable row to the run_traces table in Postgres — the table the Discord adapter's rate limits and budget kill-switch aggregate over (src/gw2mcp/agent/gating.py).

Politeness & attribution

  • Wiki requests carry a descriptive User-Agent and are limited to ≤ 2 req/s.

  • Wiki content is cached (pages 24h, search 1h); GW2 API static data is cached keyed to the game build; prices for 5 minutes.

  • Wiki content is GFDL; this project is not affiliated with ArenaNet.

Install Server
F
license - not found
-
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Guild-Wars-MCP/GW2_MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server