Class 1: What Is MCP, and Why GraphQL Over It?
Duration: 35 minutes | Difficulty: Intermediate | Prerequisites: Working GraphQL knowledge (schemas, queries, mutations) and basic Spring Boot familiarity.
This course teaches MCP, and it assumes you can already read a GraphQL schema and write queries and mutations against it. The Introduction to GraphQL covers those fundamentals if you want them first. Class 2 onwards builds on the Spring GraphQL tutorial; Class 1 is concept-first and is approachable without it. The companion code starts in Class 2.
This course is an introduction to MCP for a GraphQL API, and the way its code is deployed is not a production pattern. In production, the MCP server and the GraphQL service would usually be two separate deployables. The MCP server runs as its own service and calls the GraphQL API over HTTP like any other client.
For simplicity, the two Spring tracks put both into a single Spring Boot application, so there is one project to clone and one application to run while you learn. Class 17 explains when and why to split them. We plan a follow-up course that builds a more capable standalone MCP server.
By the end of this lesson you will know what the Model Context Protocol is, what its three primitives do, and how its two transports differ. You will also know why a GraphQL schema makes such a good MCP surface. The tutorial then teaches three separate ways to expose yours: Apollo MCP Server, the Spring AI MCP starter, and a DIY implementation in Spring.
You can follow one track, two, or all three, and you can leave that choice open as long as you like. Read the track that matches what you are building, browse the others for comparison, and come back to them when a later project calls for one. The track you actually run decides which tools you install. On the Apollo track, the Spring starter setup in Class 6 is there if you want it, and you can skip it if you would rather keep moving.
This lesson introduces a set of terms that may be new. Each one has a short definition here, and a fuller explanation where the lesson puts it to work:
- Model Context Protocol (MCP) is an open specification for connecting language models to external systems such as your API.
- JSON-RPC is a small request-and-response message format encoded as JSON. MCP messages use it.
- Host, client and server are MCP's three roles: the LLM application the user works in, the connector inside it, and the service that offers capabilities.
- Tool is an action an MCP server offers. It has a name, a description and an input schema, and the model decides when to call it.
- Resource is read-only data an MCP server offers to the model, identified by a URI.
- Prompt is a reusable prompt template an MCP server offers, usually picked by the user.
- Transport is how MCP messages travel: over a subprocess's standard input and output (stdio), or over streamable HTTP.
- Server-Sent Events (SSE) is a web standard that lets a server stream messages to a client over one HTTP response.
- JSON Schema is the standard vocabulary for describing the shape of a JSON value. MCP uses it to describe tool inputs.
- Language Server Protocol (LSP) is the protocol between code editors and language tools. MCP's design follows several of its ideas.
- Persisted operations are GraphQL operations written and reviewed in advance, which clients call by name instead of sending arbitrary queries.
- Apollo Federation combines several team-owned GraphQL services into one graph, called a supergraph.
- GraphOS is Apollo's managed platform, which includes a schema registry.
- Dynamic discovery is an MCP design in which the agent searches the live schema and writes its own queries at runtime, through a few meta-tools.
What MCP Is
The Model Context Protocol (MCP) is an open specification for connecting language models to external systems. It is JSON-RPC underneath, with a small handshake on top. A client connects to a server, the two exchange capabilities, and from then on the client sends method calls and reads results. That client is usually an LLM host: Claude Desktop, an agent framework, or a coding tool. The important part is the three primitives the spec defines. An MCP server exposes some combination of resources, tools, and prompts, and the client knows how to surface each of those to its model in the right way.
Anthropic published the first MCP revision in late 2024, and the protocol has since been adopted by OpenAI, Google, Microsoft, and most major agent frameworks. The revision this whole tutorial targets is 2025-11-25. Track B's Spring AI 2.0 starter ships the MCP Java SDK 2.x, which negotiates it. Track C implements the protocol by hand in Class 13 and advertises the revision explicitly. Because Track C owns every layer, it also controls which revision it negotiates. A newer revision, 2026-07-28, is now the current revision, and it is the largest change since launch: a stateless core, removal of the initialize handshake, and first-class extensions (Tasks among them).
The tutorial still targets 2025-11-25, because that is what the shipping tooling negotiates. Framework support for 2026-07-28 should arrive over the following months, so check which revision your Spring AI and Apollo versions support. The revision number matters because client and server agree on it during the handshake. Older blog posts show a handshake with "protocolVersion": "2024-11-05". That is an earlier revision, and several message shapes have changed since. Always check which revision a piece of MCP code targets before copying it.
Resources are read-only data the model can fetch. The canonical example is a file the host wants to make available to the model: documents, log entries, config snapshots, anything answering "what does this thing currently look like?" Resources have URIs, MIME types, and content. The model treats them as context, not as actions, and reading the same resource twice does not cause side effects. They are the closest MCP analog to GET.
Tools are actions the model can invoke. A tool has a name, a description, an input schema (JSON Schema), and a handler on the server side. When the model decides it wants to do something (search a database, send an email, look up a movie), it calls a tool. Tools are how an agent takes action. They can have side effects, they can fail, and they always return a structured result that the model reads. For an MCP server in front of a real backend, most of the value is in its tools.
Prompts are reusable prompt templates the server offers to the client. The client surfaces them to the user (typically as slash commands) and the user picks one. Resources and prompts have real uses. Your movie service could expose a poster image as a resource, or a "summarize this watchlist" prompt template, and both would be reasonable designs. This tutorial focuses on tools because GraphQL queries and mutations map onto them directly. Only an argument-less query resembles a resource, and no GraphQL operation corresponds to a prompt.
Teams new to MCP often miss this: the LLM decides whether, when and in what order tools are called. The host application, such as Claude Desktop or your agent app, lists the tools and passes on the model's choices. It does not pick tools itself. This is why tool descriptions matter so much. The model reads them when it decides. The host may ask the user to approve the call, but the model has already made its choice from your descriptions. A clear description is what leads the model to the correct call.
The Two Transports
MCP defines two standard transports. The first is stdio. The host launches the server as a subprocess, and the two communicate over stdin and stdout using newline-delimited JSON-RPC messages. This is how Claude Desktop and most local tools connect to MCP servers today. It is easy to debug, and it avoids networking concerns, because everything stays on one machine. If your MCP server runs on the same machine as its only client, use stdio.
The second is streamable HTTP. The server runs as an HTTP service, and the client sends POST requests. The server can stream responses back with Server-Sent Events. Hosted MCP servers in production use this transport. It scales horizontally, works behind a normal load balancer, handles TLS like any other web service, and lets many clients reach one server. The 2025-11-25 revision we target uses a single endpoint that handles both single-shot and streaming requests, which simplifies deployment compared to the older two-endpoint design.
This tutorial mainly teaches HTTP, for three reasons:
- Spring is a web framework.
- Your existing Spring for GraphQL service is already an HTTP application.
- A hosted MCP server, which serves many clients, uses HTTP.
Class 19 covers stdio, so that Claude Desktop can connect to your server during local development. The rest of the course assumes an HTTP deployment.
MCP's design borrows a lot from the Language Server Protocol (LSP). Microsoft published LSP in 2016 so that any code editor can work with any language server. The two protocols share JSON-RPC framing, the capabilities-handshake pattern, the request/notification distinction, and the "host launches server as subprocess" stdio transport. The MCP specification says it takes some inspiration from LSP. LSP solved the "many editors, many languages" problem with one shared protocol. MCP applies the same idea to "many LLMs, many tools." This is why MCP feels familiar to anyone who has built a VS Code extension.
The Anatomy of a Tool Call
The MCP handshake and a single tool invocation look like this on the wire:
The initialize call is where the two sides agree on a protocol revision and announce what they can do. The server says "I support tools and resources but not prompts" and the client says "I am an LLM host running version X." Neither side calls anything else until this finishes. (The 2026-07-28 revision replaces this handshake with a stateless core, as the note above explains. The 2025-11-25 revision that this tutorial targets still has it.) After the handshake, tools/list is how the client discovers what is on offer. The response includes each tool's name, a human-readable description, and a JSON Schema for its inputs.
The model learns your tools at runtime, every session, so nothing has to describe them out of band.
The tools/call is where the work happens. The client sends a tool name and an arguments object that conforms to the input schema. The server validates the arguments, runs the tool and returns content. A tool result is a list of content items, each with a type (text, image, audio, resource_link or resource), plus an isError flag that reports failures. Compared to a REST call this looks chatty. The extra messages have a purpose: every tool describes itself, the client works without a separate spec, and both sides negotiate capabilities when they connect. That is why an MCP client can connect to a server it has not seen before and start working.
Why GraphQL Over MCP
A GraphQL schema is very close to a ready-made MCP server, because nearly every MCP concept has a GraphQL counterpart.
| MCP Primitive | GraphQL Operation | Why It Fits |
|---|---|---|
| Resource | Query (no required arguments) | Read-only, idempotent, returns structured data, has a URI-like name |
| Tool (read) | Parameterized query | Read-only but takes inputs; typed arguments, typed return |
| Tool (write) | Mutation | Side-effecting action with typed inputs and a structured result |
| Tool description | Schema description (""" string on field) | Free-form documentation already attached to the operation |
| Tool input schema | GraphQL argument types | Typed, validated, with optional/nullable distinctions |
| Tool output schema | GraphQL return type | Selection-set-aware, structured, agent-readable |
| Prompt | (no direct analog) | An acceptable gap; you almost never want a server-side prompt for a backend API |
This works because both protocols solve the same problem: a typed contract written once and used by many clients. GraphQL was designed so that one schema could serve an iOS app, a web app and a partner integration, without a custom server for each client. MCP was designed so that one server could serve Claude Desktop, an agent framework, a coding tool and future hosts, without a custom integration for each.
For a longer treatment of this argument, see the blog post that motivates this tutorial: Your GraphQL Schema Is Already an MCP Server (It Just Doesn't Know Yet). This tutorial implements that argument.
The time you spent writing field descriptions, marking nullability accurately and tightening input types also prepares your schema for an MCP server. The hardest part of building an MCP server is writing typed tool definitions and good descriptions. GraphQL teams have been doing that work for a decade. A well-documented schema already contains most of what an MCP server needs.
The Persisted-Operations Insight
If GraphQL is such a good fit for MCP, why not just hand the agent a GraphQL endpoint and let it write whatever queries it likes? You can, but there are three reasons you should not.
The first is cost. GraphQL's flexibility, which makes it so useful for frontends, becomes a risk when the client is an LLM. Runtime cost analysis catches expensive queries cheaply. It catches them by rejecting them, though, after the model has already spent tokens deciding to ask. Worse, the model cannot tell what is expensive. It will request users { posts { author { posts { author { ... } } } } } because the schema said it was legal, then loop on the rejection trying to fix it.
The second is security. If your schema includes a password field on User, even one that holds a hash, an agent can ask for it. If it includes a payoutsAccount on Vendor, an agent can ask for that too. Schema-level field authorization helps, and you should still have it. The safer answer is to keep the dynamic schema away from the agent entirely, and show it only operations you have already reviewed.
The third reason is how agents behave: an agent keeps calling and keeps exploring. A human developer queries your API once during integration, sees which fields exist, and writes a working request. An agent queries your API every time it needs the data. It explores every nullable field and retries on every error. The cost starts with wasted tokens and latency. At worst, the agent works its way through expensive resolvers and untested error paths that human clients rarely touch.
A live GraphQL endpoint is a tool for human developers writing client code at design time. It is not a runtime surface for agents. The agent should see a small, curated set of named operations: each one written, reviewed, cost-analyzed, and authorized like any other API endpoint you would put in production. This is the persisted-operations pattern adapted for agents. If your team already uses persisted documents for mobile clients, you know the pattern, and here the agent is one more client. The main line of this tutorial exposes curated operations throughout. The one deliberate exception is Class 4b. It looks at dynamic discovery as an optional comparison, together with the server-side limits that make that mode safe enough where it fits.
This decision shapes the entire tutorial. Every approach we cover, Apollo's, Spring AI's and the DIY one, exposes named operations to the agent. The three approaches differ in how operations are written, registered and served.
Three Modes of Tool Definition
Separate from the choice of track, there is a second design choice: where the operations themselves live. Apollo MCP Server, the Spring AI starter and the DIY route all face this choice, and the same three options exist in each. We describe them here, so that the configuration choices in later chapters make sense.
Operation files are static .graphql documents stored in a directory next to your code. Each file is one query or mutation; the file becomes one MCP tool. This is the simplest mode, the easiest to review, and the one Class 4 walks through end-to-end. It is the right default for small and medium schemas where the operations are stable enough to live in a folder.
Persisted-query manifests turn the same operations into a deployment artifact. Operations are authored in your codebase, registered in a manifest as part of the CI pipeline, and the MCP server loads them from the manifest instead of the filesystem. Apollo GraphOS supports this path natively, and many multi-team federated organizations use it. The pattern matches the persisted-documents workflow you may already use for mobile clients, with the agent as one more registered client.
Dynamic discovery is the third mode, and it gets the most attention in articles from 2025 and 2026. Instead of pre-defining operations, the server exposes a small set of meta-tools and lets the agent compose queries against the live schema at runtime. Grafbase's original design used three (search, introspect, execute); Apollo MCP Server 1.0 ships four, adding a validate tool so the agent can check a query before spending an execution on it. Safety comes from server-side policies (depth limits, complexity caps, field-level deny lists) instead of a curated operations directory. Grafbase described the pattern in Q2 2025, and Apollo MCP Server added its tools during 2025, before the 1.0 release in October 2025. Class 4b covers it in depth as an optional detour after Class 4.
| Mode | Where operations live | Right for | Class to read |
|---|---|---|---|
| Operation files | A directory of .graphql files | Small to medium schemas | Class 4 |
| Persisted manifests | A CI-published manifest (GraphOS) | Federated multi-team graphs | Class 5 (within its Deployment Topology section) |
| Dynamic discovery | Generated at runtime via meta-tools | Very large schemas; exploratory agents | Class 4b |
You do not have to pick now. Class 4 starts with operation files because they are the simplest path, and Class 4b and Class 5 introduce the other two modes as comparative material. By the time you reach Class 6 and start Track B, you will know enough to pick the right mode for your stack.
The Wider Ecosystem
This tutorial focuses on the three tracks readers ask for most often: Apollo MCP Server, the Spring AI MCP starter, and a DIY implementation in Spring. They are not the only options. Knowing the alternatives helps you understand the choices ahead, and shows which vendor blog posts address the same problem.
- Grafbase was, by its own account, the first GraphQL Federation platform to ship MCP support built in, in Q2 2025, and the originator of the
search/introspect/executedesign that Class 4b covers. - IBM API Connect for GraphQL (the productized continuation of StepZen) combines declarative GraphQL building blocks with automatic MCP server generation, aimed at enterprise teams.
- Microsoft Fabric shows a local, development-grade MCP server over Fabric's API for GraphQL, so that AI agents can discover and query Fabric data.
- Hasura PromptQL is the next product from Hasura, the company best known for instant GraphQL APIs. Hasura's announcement calls PromptQL "the spiritual successor to GraphQL for the age of AI", and says Hasura keeps maintaining its GraphQL Engine. The post is worth reading as a different view of the same problem.
- Open-source bridges like blurrah/mcp-graphql, Arize's text-to-GraphQL MCP, and a handful of Python and Rust variants give you minimal generic implementations to study or adapt.
The list of vendors will keep changing. The architecture you build in this tutorial works with most of these options, because it depends on the schema and not on the bridge. Pick the bridge that fits your team. The schema work carries over to any of them.
Choosing Your Path
The rest of the tutorial splits into three tracks, each a complete way to put MCP in front of your GraphQL schema. They are not mutually exclusive (you might use Apollo in production and the DIY approach for a special internal endpoint), but for any given service you will pick one.
Track A: Apollo MCP Server (Classes 4 and 5). Apollo MCP Server is a standalone binary that takes your GraphQL endpoint plus a directory of .graphql operations and serves them as MCP tools. You do not write any Java for the MCP layer. The server runs alongside your Spring app, or in front of a federated supergraph, and translates MCP traffic into GraphQL traffic. It is the right choice for federated-graph teams, for polyglot stacks where the GraphQL backend is not Java, and for teams that want the API and the agent surface deployed separately.
Track B: Spring AI MCP Server starter (Classes 6 through 12). Spring AI ships an MCP server starter that runs in-process inside your Spring Boot application. You annotate methods, the starter registers them as MCP tools, and a single deployable serves both your GraphQL endpoint and your MCP endpoint. This is the deepest treatment in the tutorial, and the easiest track to follow, because there is only one application to build and run. The tools live in the same Spring codebase as the GraphQL API, reuse the security and observability setup the app already has, and call your Spring beans directly. Most readers should follow Track B end-to-end and read A and C as comparison material.
Track C: DIY in Spring (Classes 13 and 14). MCP is JSON-RPC over a defined transport. You can implement it yourself in a Spring @RestController and a few hundred lines of code. There are two reasons to do it. Your transport may be non-standard, such as an internal service mesh, a proprietary protocol, or an embedded use case. Or you may want to understand every layer that the other two tracks handle for you. The layers are thin, so studying them one by one teaches a lot. Track C is not the recommended default. It also depends on Track B, because its controller reuses the operation constants that Class 7 builds.
If you do not have a strong opinion, do Track B and skim the others. If your team runs Apollo Federation already, do Track A first: it stands alone, and Track B picks up cleanly afterwards. If you want to see the protocol itself, without a framework in between, Track C is written for you. Work through Track B first, because Class 13 starts from the code that Class 7 leaves behind.
Summary
MCP is JSON-RPC with a capabilities handshake on top. Its three primitives, resources, tools and prompts, are how a server describes itself to an LLM host at runtime. For a backend API, most of the value is in the tools. The LLM decides when to call them, not the host, which is why descriptions matter as much as code.
A GraphQL schema maps onto this model almost one-to-one. Queries and mutations become tools, argument types become input schemas, and the field descriptions you already wrote become the documentation the model reads at decision time. The one thing to avoid is handing an agent your live endpoint. The agent should see curated, named operations. The three tracks ahead, Apollo MCP Server, the Spring AI starter and DIY Spring, are three ways to serve them.
Further Reading
- Model Context Protocol specification - the canonical reference for protocol semantics, primitives, and revision history.
- MCP Specification: Versioning - revision dates and how negotiation works.
- Spring AI: Connect Your AI to Everything (Christian Tzolov, Spring blog, Sept 2025) - the Spring team's framing of "tools owned by the LLM" and the boot starter philosophy.
- "Your GraphQL Schema Is Already an MCP Server" - the blog post that motivated this tutorial.
- Apollo MCP Server documentation - reference for Track A.
- Anthropic: Introduction to MCP - the protocol's launch announcement and conceptual overview.
What's Next
In Class 2 we extend the existing Movie Database schema with two operations designed for agents, recommendMoviesForMood and summarizeMovieReviews. We also audit an operation the app already ships, the addToWatchlist mutation, which is about to become an agent-facing write. The Movie Database application comes from the Spring GraphQL tutorial, extended with a few extra domain objects, such as the watchlist, that this course uses to show agent scenarios.
Following that tutorial is optional, and it is a good place to practice GraphQL first. Class 3 then works on the schema descriptions, because the agent reads them at runtime to decide whether and how to call a tool. After Class 3 the foundations are in place, and the three tracks separate. Apollo MCP Server runs in Classes 4 and 5, the Spring AI starter in Classes 6 through 12, and the DIY in-Spring implementation in Classes 13 and 14.