MCP Apps Development
Key concepts when developing MCP Apps
This document explains the core concepts of developing MCP Apps using React, Apollo Client, and Apollo MCP Server.
We recommend that you use the Apollo AI Apps Template to get started developing your app. The template includes much of the code referenced in this document.
Apollo Client initialization
If you have experience developing with Apollo Client, you already know most of what you need to know to develop with MCP Apps, which uses the @apollo/client-ai-apps package to manage GraphQL operations. However, this guide doesn't provide a comprehensive explanation of Apollo Client. To learn about it, go to the Apollo Client documentation.
You initialize ApolloClient by using the ApolloClient class provided by @apollo/client-ai-apps. The ApolloClient constructor needs at least an instance of ApolloCache (typically InMemoryCache) and the manifest file.
ApolloClient from the @apollo/client-ai-apps package, not the core @apollo/client repository. The ApolloClient class from @apollo/client-ai-apps is for integrating with the MCP Apps environment.1import { InMemoryCache } from "@apollo/client";
2import { ApolloClient, ApplicationManifest } from "@apollo/client-ai-apps";
3// Note the manifest is written to the root of the app
4import manifest from "../.application-manifest.json";
5
6// You may also provide other options that the core ApolloClient accepts
7const client = new ApolloClient({
8 cache: new InMemoryCache(),
9 manifest: manifest as ApplicationManifest,
10});ApolloClient class from the core @apollo/client package, the ApolloClient class from @apollo/client-ai-apps doesn't require a configured link. By default, the class uses ToolCallLink, which executes GraphQL queries through MCP tools. You can provide a custom link chain to grant additional capabilities to
ApolloClient; however, your terminating link needs to be a ToolCallLink.Provide your client to ApolloProvider
After you've created your client instance, pass it to ApolloProvider. As with ApolloClient, you need to use the ApolloProvider component exported from the @apollo/client-ai-apps package.
1import { ApolloProvider } from "@apollo/client-ai-apps/react";
2
3const client = new ApolloClient({
4 // ...
5});
6
7createRoot(document.getElementById("root")!).render(
8 <ApolloProvider client={client}>
9 <App />
10 </ApolloProvider>,
11);The ApolloProvider implementation from @apollo/client-ai-apps/react initializes your application with Apollo MCP Server and provides the client instance in React context for use with Apollo Client's data-fetching hooks.
Show a loading fallback during initialization
The ApolloProvider component uses React's Suspense functionality during initialization, which avoids rendering application code that relies on client initialization. The client is initialized when it receives the ui/notifications/tool-result notification from the host.
If you want to display a loading fallback while the app initializes, wrap ApolloProvider with React's Suspense component.
1import { Suspense } from "react";
2
3createRoot(document.getElementById("root")!).render(
4 <Suspense fallback={<LoadingFallback />}>
5 <ApolloProvider client={client}>
6 <App />
7 </ApolloProvider>
8 </Suspense>,
9);Suspense component to display a loading fallback is optional. If you don't provide a Suspense component, the screen remains blank until the host provides the tool result.Register an MCP tool
To register MCP tools, combine your GraphQL queries with the @tool directive.
For example, if you want to define a query that gets the highest-rated products in a marketplace application, define a query called TopProductsQuery in your React component, then use the useQuery hook from @apollo/client/react to read the query data. To register the query as its own tool, use the @tool directive.
1import { gql, TypedDocumentNode } from "@apollo/client";
2import { useQuery } from "@apollo/client/react";
3
4// Note: These types should be generated using a tool like GraphQL Codegen.
5// Avoid writing them by hand.
6const TOP_PRODUCTS_QUERY: TypedDocumentNode<
7 TopProductsQuery,
8 TopProductsQueryVariables
9> = gql`
10 query TopProductsQuery
11 @tool(
12 name: "TopProducts"
13 description: "Shows a list of the highest rated products."
14 ) {
15 topProducts {
16 id
17 title
18 rating
19 price
20 thumbnail
21 }
22 }
23`;
24
25function App() {
26 const { data, loading, error, dataState } = useQuery(TOP_PRODUCTS_QUERY);
27
28 if (loading) {
29 return <div>Loading...</div>;
30 }
31
32 if (error) {
33 return <div>Error! {error.message}</div>;
34 }
35
36 return (
37 <div>
38 {data.topProducts.map((product) => (
39 <Product key={product.id} product={product} />
40 ))}
41 </div>
42 );
43}Now that the query is registered as a tool, you can run the app in ChatGPT or an MCP Apps-compatible host and prompt the LLM to show you the top products. They render using data fetched by MCP Server.
Platform-specific modules
To create platform-specific modules, append the file extension that corresponds with the host you want to target.
*.openai.ts(x)- Code specific to a ChatGPT app*.mcp.ts(x)- Code specific to an MCP app
Those extensions allow you to write code only available to the targeted host.
For example, the @apollo/client-ai-apps/openai entry point provides a useWidgetState hook that is only available to ChatGPT apps. To use that hook, create a component file with the .openai.tsx extension.
1import { useWidgetState } from "@apollo/client-ai-apps/openai";
2
3export function MyComponent() {
4 const [widgetState, setWidgetState] = useWidgetState();
5
6 return (
7 <div>
8 <div>{widgetState.foo.toString()}</div>
9 <button onClick={() => setWidgetState({ foo: true })}>Set foo</button>
10 </div>
11 );
12}How to import platform-specific modules
To import platform-specific modules, use the base name. For example, if you create a platform-specific Home component for OpenAI (Home.openai.tsx) and MCP (Home.mcp.tsx), you import it like this:
1import { Home } from "./Home";Home.openai.tsx file, you also need to create either a Home.mcp.tsx or Home.tsx file.TypeScript configuration
@apollo/client-ai-apps provides extendable TypeScript configurations that make sure you only use code available to your application from platform-specific modules.
@apollo/client-ai-apps/tsconfig/core— for shared modules that run in both environments. This config ensures that included files only use shared utilities available to all environments. Use this when type-checking shared files for all environments..@apollo/client-ai-apps/tsconfig/mcp— For developing MCP-specific modules. This configuration allows you to access utilities exported by@apollo/client-ai-apps/mcp. Use this when type-checking your.mcp.*files.@apollo/client-ai-apps/tsconfig/openai— For developing ChatGPT-specific modules. This configuration allows you to use utilities exported by@apollo/client-ai-apps/openai. Use this when type-checking your.openai.*files.
Recommended TypeScript configuration
Use TypeScript project references to apply a TypeScript configuration to a subset of files. We recommend these configuration files:
1.
2├── tsconfig.app.json
3├── tsconfig.base.json
4├── tsconfig.mcp.json
5├── tsconfig.openai.json
6└── tsconfig.jsontsconfig.base.json
Shared configuration for all TypeScript files. This is be where you should configure most of your compilerOptions.
1{
2 "compilerOptions": {
3 "strict": true,
4 // ... all other preferred TypeScript settings
5 },
6}tsconfig.app.json
Configuration that targets all shared modules. Extend @apollo/client-ai-apps/tsconfig/core and exclude platform-specific modules (.mcp.* and .openai.* files).
1{
2 "extends": ["@apollo/client-ai-apps/tsconfig/core", "./tsconfig.base.json"],
3 "include": ["src"],
4 "exclude": ["src/**/*.mcp.*", "src/**/*.openai.*"],
5}tsconfig.mcp.json
Configuration that targets MCP app specific modules. Extend @apollo/client-ai-apps/tsconfig/mcp and include .mcp.* files.
1{
2 "extends": ["@apollo/client-ai-apps/tsconfig/mcp", "./tsconfig.base.json"],
3 "include": ["src/**/*.mcp.ts", "src/**/*.mcp.tsx"],
4}include setting.tsconfig.openai.json
Configuration that targets ChatGPT App-specific modules. Extend @apollo/client-ai-apps/tsconfig/openai and include .openai.* files. Include the openai/globals types in the types configuration, which provides types for window.openai.
1{
2 "extends": ["@apollo/client-ai-apps/tsconfig/openai", "./tsconfig.base.json"],
3 "compilerOptions": {
4 "types": ["@apollo/client-ai-apps/openai/globals"],
5 },
6 "include": ["src/**/*.openai.ts", "src/**/*.openai.tsx"],
7}include setting.tsconfig.json
The base configuration that should include all project references.
1{
2 "files": [],
3 "references": [
4 { "path": "./tsconfig.app.json" },
5 // Omit if you don't create this file
6 { "path": "./tsconfig.mcp.json" },
7 // Omit if you don't create this file
8 { "path": "./tsconfig.openai.json" },
9 ],
10}Vite configuration
The @apollo/client-ai-apps package includes a Vite plugin that configures your application for developing ChatGPT and MCP apps.
To use it, import apolloClientAiApps from @apollo/client-ai-apps/vite and provide it to the plugins option in your Vite configuration. Set the targets option to define what environments you are building for.
mcp- Creates a build artifact for MCP apps.openai- Creates a build artifact for ChatGPT apps.
1import { defineConfig } from "vite";
2import { apolloClientAiApps } from "@apollo/client-ai-apps/vite";
3
4export default defineConfig({
5 plugins: [
6 apolloClientAiApps({
7 targets: ["mcp", "openai"],
8 }),
9 // ... other plugins
10 ],
11 // ... other config
12});Development mode
You can only run one environment in development mode at a time. Set the devTarget option to define what environment you are developing for.
1apolloClientAiApps({
2 devTarget: "openai",
3 // ...
4});targets option only includes one value, you can omit the devTarget option because the plugin infers the devTarget as the value in targets.Use environment variables to set devTarget
If you are developing both ChatGPT and MCP apps, switching the devTarget each time you want to develop for a different environment is cumbersome. We recommend that you use environment variables to configure those kinds of dynamic values so that you can easily change the environment without having to edit the build configuration.
1apolloClientAiApps({
2 devTarget: process.env.DEV_TARGET,
3 // ...
4});However, this results in a TypeScript error. devTarget only accepts the values mcp or openai, but process.env.DEV_TARGET is a string type. To fix this issue, use the devTarget helper exported from @apollo/client-ai-apps/vite which validates the input and returns the correctly typed value.
1import { apolloClientAiApps, devTarget } from "@apollo/client-ai-apps/vite";
2
3apolloClientAiApps({
4 devTarget: devTarget(process.env.DEV_TARGET),
5 // ...
6});package.json that target each environment so you don't need to remember to set the environment variable.1{
2 "scripts": {
3 "dev:mcp": "DEV_TARGET=mcp vite",
4 "dev:openai": "DEV_TARGET=openai vite"
5 }
6}