Skip to content
Chinonso.Ani
All articles

Dec 02, 2025 ·

Give Django GraphQL to an MCP Agent

A Django GraphQL API already describes what clients can ask for. MCP can turn that schema into an agent-facing tool surface, but the bridge still needs default-deny execution rules and protocol tests.

Give Django GraphQL to an MCP Agent

I had an existing Django GraphQL API and wanted to make it usable by an agent without building a second application API. The backend already had a schema, operations, validation, and authorization behavior. The missing piece was an agent-facing bridge that could expose discovery and controlled execution through the Model Context Protocol.

The implementation stayed intentionally small. It registered two MCP tools: one to introspect the GraphQL schema and one to execute GraphQL operations. Query execution was available by default. Mutations were blocked unless an environment flag explicitly enabled them.

That shape demonstrated a useful pattern in a controlled setting: GraphQL can remain the product contract, while MCP provides a tool surface an agent can call.

_Hero image: MCP client connected to a GraphQL schema through a guarded execution gate._

Project context

I already had a Django application with a GraphQL endpoint. Human clients could use the schema, and the backend owned the domain model and access-control behavior. I did not want to duplicate every resolver as a custom agent tool or create a separate REST layer just for automation.

The goal was narrower: let an MCP client discover the schema and run controlled GraphQL requests through a local bridge. That would make the existing API reachable from agent workflows while keeping the source of truth in GraphQL.

This mattered because agent integrations often grow a parallel tool catalog. That can be useful for a few stable business actions, but it creates another contract to version, test, and secure. If a GraphQL API already describes the available operations, the bridge can use that contract instead of recreating it.

Constraint

The main constraint was capability control. A tool that can send arbitrary GraphQL is powerful. It can read data, and, if allowed, it can write data. The MCP server needed to be useful for discovery and read-heavy workflows without quietly becoming a broad mutation gateway.

There was also a protocol constraint. MCP servers that use stdio communicate with clients over JSON-RPC messages on standard input and standard output. A stray startup log on stdout can corrupt the message stream. The bridge had to respect the transport, return shaped tool results, preserve request identifiers, and expose errors in a form MCP clients could understand.

I treated those concerns as part of the bridge design rather than incidental plumbing.

Engineering approach

The bridge used GraphQL introspection as the discovery layer. The GraphQL specification defines introspection as a built-in way to query the schema itself, and the public GraphQL learning docs describe how clients can ask about types, fields, and descriptions.

That made introspection the natural first tool. Instead of hardcoding a static API manual into the MCP server, the bridge asked the backend schema what existed. It returned the raw schema and a compact summary of type names, query fields, mutation fields, and subscription fields. Internal introspection types were filtered out of the summary so the result focused on application-facing schema elements.

The second tool executed GraphQL requests. It accepted a GraphQL operation string and optional variables, then sent the request to the configured endpoint.

The server configuration stayed simple. It selected the GraphQL endpoint, required an explicit decision before mutations could run, and gave the MCP server a stable identity.

The agent-facing tool list remained deliberately short: one capability for schema discovery and one for GraphQL execution. That narrow surface made the access boundary easier to explain and test without publishing an internal configuration contract.

That was enough for an agent to discover the schema, inspect available operations, and run read-oriented queries without a second domain-specific API layer.

How it worked

The introspection tool called the GraphQL endpoint and returned two levels of detail. The raw schema preserved the full contract for clients that needed it. The compact summary gave an agent a quick index of what it could inspect next.

The execution tool handled the more sensitive path. Before forwarding a request, it checked whether mutation execution was allowed. If mutations were disabled and the operation looked like a mutation, the tool returned an error telling the caller that mutation execution was disabled and required explicit configuration.

The mutation check was intentionally lightweight. It looked for straightforward mutation forms in the submitted operation text. That is useful as a default-deny guard for a local bridge, but it is not a complete GraphQL parser and should not be described as production security.

The default was still the safer posture for this use case. Discovery and reads are the first useful agent workflows. Writes need a higher standard: authentication, authorization, operation allowlists, audit logging, idempotency, validation, and often human review for destructive actions.

The stdio transport shaped the runtime behavior. Startup and diagnostic logs went to stderr, not stdout, so they would not interfere with JSON-RPC messages. Tool results were returned as MCP text content containing formatted JSON. Tool errors were returned through the MCP error shape. Signal handling allowed the server to shut down cleanly.

The SDK handled most of the protocol mechanics. The application code focused on registering tools and routing calls to the GraphQL handlers.

Tradeoffs and safeguards

The main tradeoff was keeping the MCP surface generic. A single execution tool is flexible because it can run any allowed GraphQL query. It also places more responsibility on the underlying GraphQL authorization model. If the backend authorization layer is incomplete, a generic execution tool exposes that weakness.

A safer production version could add named-operation allowlists, per-client credentials, request logging, rate limits, and user-scoped authorization. For write operations, it could require idempotency keys or a separate write-capable server profile. The thin bridge pattern works best when the backend schema and permissions are already mature.

The second tradeoff was using a simple mutation detector. That was acceptable for a development-oriented bridge where mutations are disabled by default. It would not be enough for an internet-exposed service or a workflow with sensitive writes. A hardened version should parse the GraphQL document and evaluate operation type structurally.

The third safeguard was protocol testing. The test suite spawned the server over stdio, requested the tool list, and asserted that the JSON-RPC response preserved the protocol version, preserved the request ID, and exposed exactly the expected tools with the expected schema shape. Tests also exercised malformed request behavior and checked that any response used the JSON-RPC error structure.

Those tests matter because MCP integration can fail even when TypeScript compiles and the GraphQL handler works. Tool registration, request IDs, transport discipline, and error shape are part of the contract.

What changed

The architecture enabled a small, understandable agent bridge over an existing Django GraphQL API. I did not need to invent another API surface for agent use. The schema stayed authoritative. MCP supplied the tool envelope.

The bridge also made the write boundary explicit. Mutations were not accidentally available just because the execution tool existed. Enabling writes required an intentional configuration change, and the remaining hardening work was visible.

The result was not a complete production security story. It had no MCP-side authentication layer, did not bind requests to a Django user, did not allowlist operations, and used a lightweight mutation check. Those limits were acceptable for a controlled bridge and important to name before reusing the pattern in a broader environment.

Where this pattern applies

This pattern fits teams that already have a GraphQL API and want an agent to inspect and call it without duplicating the domain model as custom tools. It is a good fit for local developer automation, internal read-only assistants, schema exploration, and controlled back-office workflows where the GraphQL backend remains the authority.

It is a poor fit for exposing sensitive write access without stronger controls. If the bridge will run remotely, serve multiple users, or perform mutations, treat authentication, operation allowlists, audit logs, and backend authorization as first-order design requirements.

If you maintain a Django GraphQL API, build the introspection tool first. Add read-only execution next. Keep mutations default-deny until you can name the exact operations, identities, logs, and review gates that make writes acceptable.

Sources

Share
Email copied to clipboard