All posts
MCP education··6 min read

GraphQL to MCP: introspection, nullability landmines, and lean selection sets

GraphQL is a near-perfect source for MCP tools — introspectable schema, typed arguments, named capabilities. The hard part is the selection set: greedy generators select deep nested objects and die on GraphQL's nullability rules the moment real data contains a null relation. This guide covers the mapping, the lean-selection fix we shipped after hitting it in production, and how to forward each customer's own credentials.

⌘
Command+K team
Platform engineering

The mapping: field → tool

GraphQL is a near-perfect source for MCP tools: the schema is introspectable, arguments are typed, and every query/mutation is already a named capability. Each top-level field becomes one tool; its arguments become the input schema; the generator's real job is the part nobody talks about — the selection set.

The nullability landmine

GraphQL's cruelest ergonomics: if a query selects a non-nullable field and the server returns null anywhere inside it, the whole response errors. A greedy generator that selects nested objects ("give me the post with its project, members, labels, plan…") will work in the demo and explode on the first real record with a null relation. We hit this exact failure with a production changelog API — the posts tool died with "Cannot return null for non-nullable field Post.project".

The fix — lean selection sets:

  • Select scalar/leaf fields by default (ids, names, statuses, timestamps).
  • Cap nesting depth at one; skip nested object types that commonly carry nullability risk.
  • Let agents fetch detail via the single-item tool instead of over-selecting on lists.

The tradeoff is honest: leaner lists sometimes mean a follow-up call for a nested field. Agents handle that fine — they don't handle poisoned responses.

Identity: forward the caller, not a service account

The naive integration runs every tool call with one service token — which means every customer reads every customer's data. The production pattern: each customer's own credential (API key or OAuth token) is stored encrypted and attached to theircalls, so your GraphQL layer's existing authorization does its job unchanged.

Descriptions decide verb accuracy

Field names lie: a query named updateCheck reads, a mutation named postView writes. Derive the verb in tool descriptions from the operation kind — queries read, mutations write — never from the name, or agents will announce "I've updated your data" after a read.

Field-tested, not theoretical

Everything above comes from converting a real production GraphQL API — 84 tools, verified against Claude, Cursor and ChatGPT. Read the full story in the AnnounceKit case study.

Point the wizard at your GraphQL endpoint →

FAQ

How do I turn a GraphQL API into MCP tools?
Introspect the schema, map each query and mutation to one MCP tool (arguments become the tool's JSON-schema input), and generate a selection set for the response. The generated document is executed against your endpoint with the caller's credentials on every tool call.
Why do generated GraphQL queries fail with 'Cannot return null for non-nullable field'?
Because a greedy generator selects deep nested objects, and one null in a non-nullable nested field poisons the entire response. The fix is lean selection sets: select scalar/leaf fields by default, cap nesting depth, and skip nested objects that commonly violate nullability. We hit exactly this in production with a real API's posts query.
Are GraphQL mutations safe to expose as MCP tools?
Expose them deliberately: mark them as writes, describe side effects, and keep destructive mutations out unless customers genuinely need them. Queries read, mutations write — never let a tool description blur that line, because agents pick verbs from descriptions.

Keep reading

Go deeper