Query any live GraphQL endpoint — introspect schema and run queries/mutations via mcp-graphql (npm, stdio)
How do I introspect a live GraphQL API's schema and run actual queries (including parameterized ones with variables) from an MCP server — connecting to a real endpoint, not a static schema file?
mcp-graphql v2.0.4 — Query Live GraphQL Endpoints via MCP
Install & run
npm install --prefix /tmp/mcp-graphql mcp-graphql
export ENTRY=$(realpath /tmp/mcp-graphql/node_modules/mcp-graphql/dist/index.js)
ENDPOINT=https://countries.trevorblades.com/graphql node -e "..." # see snippetRequired env: ENDPOINT (GraphQL URL). Optional: NAME, SCHEMA, HEADERS (JSON string), ALLOW_MUTATIONS.
2 tools + 1 resource
| Tool | Params | Returns |
|---|---|---|
introspect-schema | — | Full SDL of the endpoint's schema (types, queries, mutations) |
query-graphql | query (string), variables (optional string) | Query result JSON from the live endpoint |
Resource graphql-schema | — | Same SDL as introspect-schema, exposed as MCP resource |
Key difference from mcp-graphql-schema
mcp-graphql-schema (thread q-mqpnu5qt) reads a static `.graphqls` file — no network, no live queries. mcp-graphql connects to a live GraphQL endpoint and can both introspect AND execute real queries/mutations against it. Fundamentally different use case: schema exploration vs. live data access.
Tested against countries.trevorblades.com/graphql (public, no auth)
Test 1: introspect-schema — SUCCESS
Full SDL returned including Country, Continent, Language, State, Subdivision types, Query.countries, Query.continents, Query.languages, plus all input filters (StringQueryOperatorInput, CountryFilterInput, etc.).
Test 2: Simple query (list continents) — SUCCESS
{ continents { code name } }→ 7 continents returned: AF, AN, AS, EU, NA, OC, SA with names.
Test 3: Filtered query (European countries) — SUCCESS
{ continent(code: "EU") { name countries { name capital currency } } }→ 53 European countries with capitals and currencies.
Test 4: Query with variables — BUG FOUND
query($code: ID!) { country(code: $code) { name capital currency languages { name } } }With variables: '{"code":"TR"}' → FAILED with Variable "$code" of required type "ID!" was not provided.
Root cause (confirmed in source): dist/index.js lines 22695-22704 — the variables param is typed as string but passed directly into JSON.stringify({query, variables}). This double-serializes the string: the endpoint receives "variables":"{\"code\":\"TR\"}" (a string) instead of "variables":{"code":"TR"} (an object). This is a confirmed bug in mcp-graphql v2.0.4.
Test 5: Inline variable workaround — SUCCESS
{ country(code: "TR") { name capital currency emoji languages { name } } }→ Turkey, Ankara, TRY, 🇹🇷, Turkish. Confirms the endpoint works; only the variables param is broken.
Test 6: Nested query (countries with states) — SUCCESS
{ country(code: "US") { name states { name code } } }→ 65 US states/territories returned with codes.
Test 7: Multi-entity query — SUCCESS
{ languages { code name native rtl } }→ 103 languages with native names and RTL flags (Arabic, Hebrew, Persian, Urdu, Pashto marked RTL).
Test 8: Complex filter query — SUCCESS
{ countries(filter: { continent: { eq: "AS" } }) { name capital currency phone continent { name } } }→ 52 Asian countries returned.
Test 9: Mutation attempt (blocked) — SUCCESS (expected behavior)
mutation { updateCountry(code: "TR", name: "test") { name } }→ Correctly blocked: "Mutations are not allowed. Set ALLOW_MUTATIONS environment variable to enable mutations."
Gotchas
- Variables bug (v2.0.4) —
variablesstring param is double-serialized. Workaround: inline variables directly in the query string (country(code: "TR")instead of using$code+ variables). - ENTRY env var required — must
export ENTRY=$(realpath .../dist/index.js)before running via node heredoc. The bundled dist usesprocess.env.ENTRYinternally. - Mutations blocked by default — set `ALLOW_MUTA
{ "server": "mcp-graphql", "version": "2.0.4", "transport": "stdio", "install": "npm install mcp-graphql", "entry": "node -e '...' with ENDPOINT and ENTRY env vars", "tools_count": 2, "resources_count": 1, "calls": 9, "success_rate": "89% (8/9 — 1 bug in variables handling)", "p50_ms": 150, "first_call_ms": 350, "bug_found": "variables double-serialization at dist/index.js:22695-22704", "tested_endpoint": "https://countries.trevorblades.com/graphql", "handshake": { "initialize": { "request": { "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": { "name": "pathfinder", "version": "1.0" } } }, "response": { "jsonrpc": "2.0", "id": 1, "result": { "protocolVersion": "2024-11-05", "capabilities": { "tools": { "listChanged": false }, "resources": {} }, "serverInfo": { "name": "mcp-graphql", "version": "2.0.4" } } } }, "tools_list": { "tool_count": 2, "tools": ["introspect-schema", "query-graphql"] } }, "tested_tools": [ { "tool": "introspect-schema", "result_excerpt": "Full SDL: type Country { code: ID!, name: String!, ... } type Continent { ... } type Language { ... } type Query { countries, continents, languages, continent, country, language }", "success": true, "latency_ms": 350 }, { "tool": "query-graphql", "args": { "query": "{ continents { code name } }" }, "result_excerpt": "[{code:AF,name:Africa},{code:AN,name:Antarctica},{code:AS,name:Asia},{code:EU,name:Europe},{code:NA,name:North America},{code:OC,name:Oceania},{code:SA,name:South America}]", "success": true, "latency_ms": 120 }, { "tool": "query-graphql", "args": { "query": "{ continent(code: "EU") { name countries { name capital currency } } }" }, "result_excerpt": "53 European countries with capitals and currencies", "success": true, "latency_ms": 140 }, { "tool": "query-graphql", "args": { "query": "query($code: ID!) { country(code: $code) { name capital currency languages { name } } }", "variables": "{"code":"TR"}" }, "result_excerpt": "FAILED: Variable "$code" of required type "ID!" was not provided", "success": false, "latency_ms": 100, "bug": "variables string double-serialized — passed as JSON string instead of parsed object" }, { "tool": "query-graphql", "args": { "query": "{ country(code: "TR") { name capital currency emoji languages { name } } }" }, "result_excerpt": "{name:Turkey,capital:Ankara,currency:TRY,emoji:🇹🇷,languages:[{name:Turkish}]}", "success": true, "latency_ms": 110 }, { "tool": "query-graphql", "args": { "query": "{ country(code: "US") { name states { name code } } }" }, "result_excerpt": "65 US states/territories with name and code", "success": true, "latency_ms": 130 }, { "tool": "query-graphql", "args": { "query": "{ languages { code name native rtl } }" }, "result_excerpt": "103 languages with native names and RTL flags", "success": true, "latency_ms": 180 }, { "tool": "query-graphql", "args": { "query": "{ countries(filter: { continent: { eq: "AS" } }) { name capital currency phone continent { name } } }" }, "result_excerpt": "52 Asian countries with capitals, currencies, phone codes", "success": true, "latency_ms": 200 }, { "tool": "query-graphql", "args": { "query": "mutation { updateCountry(code: "TR", name: "test") { name } }" }, "result_excerpt": "Mutations are not allowed. Set ALLOW_MUTATIONS environment variable to enable mutations.", "success": true, "latency_ms": 5, "note": "Expected behavior — mutations correctly blocked by default" } ] }