@apollo/client-ai-apps 0.6.4 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (272) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/CONTRIBUTING.md +195 -0
  3. package/README.md +74 -0
  4. package/dist/core/AbstractApolloClient.d.ts +33 -0
  5. package/dist/core/AbstractApolloClient.d.ts.map +1 -0
  6. package/dist/core/AbstractApolloClient.js +129 -0
  7. package/dist/core/AbstractApolloClient.js.map +1 -0
  8. package/dist/core/ApolloClient.d.ts +3 -7
  9. package/dist/core/ApolloClient.d.ts.map +1 -1
  10. package/dist/core/ApolloClient.js +5 -4
  11. package/dist/core/ApolloClient.js.map +1 -1
  12. package/dist/core/McpAppManager.d.ts +42 -0
  13. package/dist/core/McpAppManager.d.ts.map +1 -0
  14. package/dist/core/McpAppManager.js +56 -0
  15. package/dist/core/McpAppManager.js.map +1 -0
  16. package/dist/core/typeRegistration.d.ts +0 -14
  17. package/dist/core/typeRegistration.d.ts.map +1 -1
  18. package/dist/core/typeRegistration.js.map +1 -1
  19. package/dist/core/types.d.ts +2 -1
  20. package/dist/core/types.d.ts.map +1 -1
  21. package/dist/core/types.js.map +1 -1
  22. package/dist/index.d.ts +1 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/index.mcp.d.ts +0 -1
  26. package/dist/index.mcp.d.ts.map +1 -1
  27. package/dist/index.mcp.js +0 -1
  28. package/dist/index.mcp.js.map +1 -1
  29. package/dist/index.openai.d.ts +0 -1
  30. package/dist/index.openai.d.ts.map +1 -1
  31. package/dist/index.openai.js +0 -1
  32. package/dist/index.openai.js.map +1 -1
  33. package/dist/link/ToolCallLink.d.ts +6 -1
  34. package/dist/link/ToolCallLink.d.ts.map +1 -1
  35. package/dist/link/ToolCallLink.js +17 -4
  36. package/dist/link/ToolCallLink.js.map +1 -1
  37. package/dist/link/ToolHydrationLink.d.ts +21 -0
  38. package/dist/link/ToolHydrationLink.d.ts.map +1 -0
  39. package/dist/link/ToolHydrationLink.js +57 -0
  40. package/dist/link/ToolHydrationLink.js.map +1 -0
  41. package/dist/mcp/core/ApolloClient.d.ts +3 -20
  42. package/dist/mcp/core/ApolloClient.d.ts.map +1 -1
  43. package/dist/mcp/core/ApolloClient.js +20 -98
  44. package/dist/mcp/core/ApolloClient.js.map +1 -1
  45. package/dist/mcp/index.d.ts +0 -1
  46. package/dist/mcp/index.d.ts.map +1 -1
  47. package/dist/mcp/index.js +0 -1
  48. package/dist/mcp/index.js.map +1 -1
  49. package/dist/openai/core/ApolloClient.d.ts +3 -20
  50. package/dist/openai/core/ApolloClient.d.ts.map +1 -1
  51. package/dist/openai/core/ApolloClient.js +36 -98
  52. package/dist/openai/core/ApolloClient.js.map +1 -1
  53. package/dist/openai/index.d.ts +0 -1
  54. package/dist/openai/index.d.ts.map +1 -1
  55. package/dist/openai/index.js +0 -1
  56. package/dist/openai/index.js.map +1 -1
  57. package/dist/openai/react/index.d.ts +0 -7
  58. package/dist/openai/react/index.d.ts.map +1 -1
  59. package/dist/openai/react/index.js +0 -7
  60. package/dist/openai/react/index.js.map +1 -1
  61. package/dist/react/ApolloProvider.d.ts.map +1 -1
  62. package/dist/react/ApolloProvider.js +1 -1
  63. package/dist/react/ApolloProvider.js.map +1 -1
  64. package/dist/{mcp/react/hooks → react}/createHydrationUtils.d.ts +1 -1
  65. package/dist/react/createHydrationUtils.d.ts.map +1 -0
  66. package/dist/{mcp/react/hooks → react}/createHydrationUtils.js +7 -9
  67. package/dist/react/createHydrationUtils.js.map +1 -0
  68. package/dist/react/hooks/internal/useApolloClient.d.ts +3 -0
  69. package/dist/react/hooks/internal/useApolloClient.d.ts.map +1 -0
  70. package/dist/{mcp/react/hooks → react/hooks/internal}/useApolloClient.js +3 -3
  71. package/dist/react/hooks/internal/useApolloClient.js.map +1 -0
  72. package/dist/react/hooks/useApp.d.ts.map +1 -0
  73. package/dist/react/hooks/useApp.js +5 -0
  74. package/dist/react/hooks/useApp.js.map +1 -0
  75. package/dist/react/hooks/useHostContext.d.ts.map +1 -0
  76. package/dist/{openai/react → react}/hooks/useHostContext.js +1 -1
  77. package/dist/react/hooks/useHostContext.js.map +1 -0
  78. package/dist/react/hooks/useToolInfo.d.ts +3 -0
  79. package/dist/react/hooks/useToolInfo.d.ts.map +1 -0
  80. package/dist/react/hooks/useToolInfo.js +5 -0
  81. package/dist/react/hooks/useToolInfo.js.map +1 -0
  82. package/dist/react/hooks/useToolMetadata.d.ts +2 -0
  83. package/dist/react/hooks/useToolMetadata.d.ts.map +1 -0
  84. package/dist/react/hooks/useToolMetadata.js +5 -0
  85. package/dist/react/hooks/useToolMetadata.js.map +1 -0
  86. package/dist/react/index.d.ts +5 -16
  87. package/dist/react/index.d.ts.map +1 -1
  88. package/dist/react/index.js +5 -19
  89. package/dist/react/index.js.map +1 -1
  90. package/dist/types/application-manifest.d.ts +1 -0
  91. package/dist/types/application-manifest.d.ts.map +1 -1
  92. package/dist/types/application-manifest.js.map +1 -1
  93. package/dist/utilities/connectToHost.d.ts +3 -0
  94. package/dist/utilities/connectToHost.d.ts.map +1 -0
  95. package/dist/utilities/connectToHost.js +11 -0
  96. package/dist/utilities/connectToHost.js.map +1 -0
  97. package/dist/utilities/index.d.ts +1 -0
  98. package/dist/utilities/index.d.ts.map +1 -1
  99. package/dist/utilities/index.js +1 -0
  100. package/dist/utilities/index.js.map +1 -1
  101. package/dist/vite/apolloClientAiApps.d.ts.map +1 -1
  102. package/dist/vite/apolloClientAiApps.js +2 -0
  103. package/dist/vite/apolloClientAiApps.js.map +1 -1
  104. package/package.json +5 -22
  105. package/src/core/AbstractApolloClient.ts +217 -0
  106. package/src/core/ApolloClient.ts +8 -10
  107. package/src/core/McpAppManager.ts +106 -0
  108. package/src/core/typeRegistration.ts +0 -15
  109. package/src/core/types.ts +2 -1
  110. package/src/index.mcp.ts +0 -1
  111. package/src/index.openai.ts +0 -1
  112. package/src/index.ts +1 -6
  113. package/src/link/ToolCallLink.ts +27 -5
  114. package/src/link/ToolHydrationLink.ts +90 -0
  115. package/src/link/__tests__/ToolCallLink.test.ts +99 -0
  116. package/src/mcp/core/ApolloClient.ts +32 -165
  117. package/src/mcp/core/__tests__/ApolloClient.test.ts +571 -71
  118. package/src/mcp/index.ts +0 -1
  119. package/src/openai/core/ApolloClient.ts +48 -161
  120. package/src/openai/core/__tests__/ApolloClient.test.ts +916 -118
  121. package/src/openai/index.ts +0 -1
  122. package/src/openai/react/index.ts +0 -7
  123. package/src/react/ApolloProvider.tsx +1 -6
  124. package/src/react/__tests__/ApolloProvider/mcp.test.tsx +66 -29
  125. package/src/react/__tests__/ApolloProvider/openai.test.tsx +16 -41
  126. package/src/react/__tests__/createHydrationUtils.test.tsx +1260 -0
  127. package/src/{mcp/react/hooks → react}/createHydrationUtils.ts +7 -10
  128. package/src/react/hooks/__tests__/useApp.test.tsx +46 -0
  129. package/src/react/hooks/__tests__/useHostContext.test.tsx +99 -0
  130. package/src/react/hooks/__tests__/useToolInfo.test.tsx +98 -0
  131. package/src/react/hooks/__tests__/useToolMetadata.test.tsx +58 -0
  132. package/src/{mcp/react/hooks → react/hooks/internal}/useApolloClient.ts +3 -3
  133. package/src/{mcp/react → react}/hooks/useApp.ts +1 -1
  134. package/src/{openai/react → react}/hooks/useHostContext.ts +1 -1
  135. package/src/react/hooks/useToolInfo.ts +6 -0
  136. package/src/react/hooks/useToolMetadata.ts +5 -0
  137. package/src/react/index.ts +5 -36
  138. package/src/testing/internal/graphql/parseManifestOperation.ts +87 -0
  139. package/src/testing/internal/index.ts +3 -0
  140. package/src/testing/internal/matchers/index.ts +1 -0
  141. package/src/testing/internal/matchers/toEmitAnything.ts +43 -0
  142. package/src/testing/internal/matchers/types.ts +1 -0
  143. package/src/testing/internal/mcp/mockMcpHost.ts +25 -4
  144. package/src/testing/internal/tests/eachHostEnv.ts +22 -0
  145. package/src/testing/internal/utilities/createHostEnv.ts +117 -0
  146. package/src/types/application-manifest.ts +1 -0
  147. package/src/utilities/connectToHost.ts +13 -0
  148. package/src/utilities/index.ts +1 -0
  149. package/src/vite/__tests__/apolloClientAiApps.test.ts +56 -0
  150. package/src/vite/apolloClientAiApps.ts +5 -0
  151. package/tsconfig.vite.json +1 -1
  152. package/vitest.config.ts +13 -0
  153. package/dist/mcp/core/McpAppManager.d.ts +0 -30
  154. package/dist/mcp/core/McpAppManager.d.ts.map +0 -1
  155. package/dist/mcp/core/McpAppManager.js +0 -82
  156. package/dist/mcp/core/McpAppManager.js.map +0 -1
  157. package/dist/mcp/link/ToolCallLink.d.ts +0 -28
  158. package/dist/mcp/link/ToolCallLink.d.ts.map +0 -1
  159. package/dist/mcp/link/ToolCallLink.js +0 -35
  160. package/dist/mcp/link/ToolCallLink.js.map +0 -1
  161. package/dist/mcp/react/hooks/createHydrationUtils.d.ts.map +0 -1
  162. package/dist/mcp/react/hooks/createHydrationUtils.js.map +0 -1
  163. package/dist/mcp/react/hooks/useApolloClient.d.ts +0 -3
  164. package/dist/mcp/react/hooks/useApolloClient.d.ts.map +0 -1
  165. package/dist/mcp/react/hooks/useApolloClient.js.map +0 -1
  166. package/dist/mcp/react/hooks/useApp.d.ts.map +0 -1
  167. package/dist/mcp/react/hooks/useApp.js +0 -5
  168. package/dist/mcp/react/hooks/useApp.js.map +0 -1
  169. package/dist/mcp/react/hooks/useHostContext.d.ts.map +0 -1
  170. package/dist/mcp/react/hooks/useHostContext.js +0 -7
  171. package/dist/mcp/react/hooks/useHostContext.js.map +0 -1
  172. package/dist/mcp/react/hooks/useToolInfo.d.ts +0 -3
  173. package/dist/mcp/react/hooks/useToolInfo.d.ts.map +0 -1
  174. package/dist/mcp/react/hooks/useToolInfo.js +0 -10
  175. package/dist/mcp/react/hooks/useToolInfo.js.map +0 -1
  176. package/dist/mcp/react/hooks/useToolInput.d.ts +0 -7
  177. package/dist/mcp/react/hooks/useToolInput.d.ts.map +0 -1
  178. package/dist/mcp/react/hooks/useToolInput.js +0 -9
  179. package/dist/mcp/react/hooks/useToolInput.js.map +0 -1
  180. package/dist/mcp/react/hooks/useToolMetadata.d.ts +0 -2
  181. package/dist/mcp/react/hooks/useToolMetadata.d.ts.map +0 -1
  182. package/dist/mcp/react/hooks/useToolMetadata.js +0 -5
  183. package/dist/mcp/react/hooks/useToolMetadata.js.map +0 -1
  184. package/dist/mcp/react/hooks/useToolName.d.ts +0 -7
  185. package/dist/mcp/react/hooks/useToolName.d.ts.map +0 -1
  186. package/dist/mcp/react/hooks/useToolName.js +0 -9
  187. package/dist/mcp/react/hooks/useToolName.js.map +0 -1
  188. package/dist/mcp/react/index.d.ts +0 -8
  189. package/dist/mcp/react/index.d.ts.map +0 -1
  190. package/dist/mcp/react/index.js +0 -8
  191. package/dist/mcp/react/index.js.map +0 -1
  192. package/dist/openai/core/McpAppManager.d.ts +0 -29
  193. package/dist/openai/core/McpAppManager.d.ts.map +0 -1
  194. package/dist/openai/core/McpAppManager.js +0 -91
  195. package/dist/openai/core/McpAppManager.js.map +0 -1
  196. package/dist/openai/link/ToolCallLink.d.ts +0 -28
  197. package/dist/openai/link/ToolCallLink.d.ts.map +0 -1
  198. package/dist/openai/link/ToolCallLink.js +0 -35
  199. package/dist/openai/link/ToolCallLink.js.map +0 -1
  200. package/dist/openai/react/hooks/createHydrationUtils.d.ts +0 -15
  201. package/dist/openai/react/hooks/createHydrationUtils.d.ts.map +0 -1
  202. package/dist/openai/react/hooks/createHydrationUtils.js +0 -113
  203. package/dist/openai/react/hooks/createHydrationUtils.js.map +0 -1
  204. package/dist/openai/react/hooks/useApp.d.ts +0 -2
  205. package/dist/openai/react/hooks/useApp.d.ts.map +0 -1
  206. package/dist/openai/react/hooks/useApp.js +0 -5
  207. package/dist/openai/react/hooks/useApp.js.map +0 -1
  208. package/dist/openai/react/hooks/useHostContext.d.ts +0 -2
  209. package/dist/openai/react/hooks/useHostContext.d.ts.map +0 -1
  210. package/dist/openai/react/hooks/useHostContext.js.map +0 -1
  211. package/dist/openai/react/hooks/useToolInfo.d.ts +0 -3
  212. package/dist/openai/react/hooks/useToolInfo.d.ts.map +0 -1
  213. package/dist/openai/react/hooks/useToolInfo.js +0 -10
  214. package/dist/openai/react/hooks/useToolInfo.js.map +0 -1
  215. package/dist/openai/react/hooks/useToolInput.d.ts +0 -7
  216. package/dist/openai/react/hooks/useToolInput.d.ts.map +0 -1
  217. package/dist/openai/react/hooks/useToolInput.js +0 -9
  218. package/dist/openai/react/hooks/useToolInput.js.map +0 -1
  219. package/dist/openai/react/hooks/useToolMetadata.d.ts +0 -2
  220. package/dist/openai/react/hooks/useToolMetadata.d.ts.map +0 -1
  221. package/dist/openai/react/hooks/useToolMetadata.js +0 -5
  222. package/dist/openai/react/hooks/useToolMetadata.js.map +0 -1
  223. package/dist/openai/react/hooks/useToolName.d.ts +0 -7
  224. package/dist/openai/react/hooks/useToolName.d.ts.map +0 -1
  225. package/dist/openai/react/hooks/useToolName.js +0 -9
  226. package/dist/openai/react/hooks/useToolName.js.map +0 -1
  227. package/dist/react/index.mcp.d.ts +0 -3
  228. package/dist/react/index.mcp.d.ts.map +0 -1
  229. package/dist/react/index.mcp.js +0 -3
  230. package/dist/react/index.mcp.js.map +0 -1
  231. package/dist/react/index.openai.d.ts +0 -3
  232. package/dist/react/index.openai.d.ts.map +0 -1
  233. package/dist/react/index.openai.js +0 -3
  234. package/dist/react/index.openai.js.map +0 -1
  235. package/dist/react/missingHook.d.ts +0 -2
  236. package/dist/react/missingHook.d.ts.map +0 -1
  237. package/dist/react/missingHook.js +0 -6
  238. package/dist/react/missingHook.js.map +0 -1
  239. package/src/mcp/core/McpAppManager.ts +0 -129
  240. package/src/mcp/link/ToolCallLink.ts +0 -40
  241. package/src/mcp/link/__tests__/ToolCallLink.test.ts +0 -62
  242. package/src/mcp/react/hooks/__tests__/createHydrationUtils.test.tsx +0 -1228
  243. package/src/mcp/react/hooks/__tests__/useApp.test.tsx +0 -46
  244. package/src/mcp/react/hooks/__tests__/useHostContext.test.tsx +0 -95
  245. package/src/mcp/react/hooks/__tests__/useToolInfo.test.tsx +0 -53
  246. package/src/mcp/react/hooks/__tests__/useToolInput.test.tsx +0 -50
  247. package/src/mcp/react/hooks/__tests__/useToolMetadata.test.tsx +0 -53
  248. package/src/mcp/react/hooks/__tests__/useToolName.test.tsx +0 -50
  249. package/src/mcp/react/hooks/useHostContext.ts +0 -14
  250. package/src/mcp/react/hooks/useToolInfo.ts +0 -13
  251. package/src/mcp/react/hooks/useToolInput.ts +0 -10
  252. package/src/mcp/react/hooks/useToolMetadata.ts +0 -5
  253. package/src/mcp/react/hooks/useToolName.ts +0 -10
  254. package/src/mcp/react/index.ts +0 -7
  255. package/src/openai/core/McpAppManager.ts +0 -139
  256. package/src/openai/link/ToolCallLink.ts +0 -40
  257. package/src/openai/react/hooks/__tests__/createHydrationUtils.test.tsx +0 -1333
  258. package/src/openai/react/hooks/__tests__/useToolInfo.test.tsx +0 -92
  259. package/src/openai/react/hooks/__tests__/useToolInput.test.tsx +0 -85
  260. package/src/openai/react/hooks/__tests__/useToolMetadata.test.tsx +0 -86
  261. package/src/openai/react/hooks/__tests__/useToolName.test.tsx +0 -50
  262. package/src/openai/react/hooks/createHydrationUtils.ts +0 -182
  263. package/src/openai/react/hooks/useApp.ts +0 -5
  264. package/src/openai/react/hooks/useToolInfo.ts +0 -13
  265. package/src/openai/react/hooks/useToolInput.ts +0 -10
  266. package/src/openai/react/hooks/useToolMetadata.ts +0 -5
  267. package/src/openai/react/hooks/useToolName.ts +0 -10
  268. package/src/react/index.mcp.ts +0 -10
  269. package/src/react/index.openai.ts +0 -10
  270. package/src/react/missingHook.ts +0 -9
  271. /package/dist/{mcp/react → react}/hooks/useApp.d.ts +0 -0
  272. /package/dist/{mcp/react → react}/hooks/useHostContext.d.ts +0 -0
@@ -0,0 +1,217 @@
1
+ import {
2
+ ApolloLink,
3
+ ApolloClient as NativeApolloClient,
4
+ DocumentTransform,
5
+ ObservableQuery,
6
+ type OperationVariables,
7
+ type WatchQueryOptions,
8
+ } from "@apollo/client";
9
+ import { __DEV__ } from "@apollo/client/utilities/environment";
10
+ import { removeDirectivesFromDocument } from "@apollo/client/utilities/internal";
11
+ import { parse, visit } from "graphql";
12
+ import { equal } from "@wry/equality";
13
+ import type { ApplicationManifest } from "../types/application-manifest.js";
14
+ import { aiClientSymbol } from "../utilities/constants.js";
15
+ import {
16
+ McpAppManager,
17
+ type ConnectToHostImplementation,
18
+ } from "./McpAppManager.js";
19
+ import { ToolHydrationLink } from "../link/ToolHydrationLink.js";
20
+ import { ToolCallLink } from "../link/ToolCallLink.js";
21
+ import {
22
+ cacheAsync,
23
+ getToolNamesFromDocument,
24
+ getVariableNamesFromDocument,
25
+ getVariablesForOperationFromToolInput,
26
+ warnOnVariableMismatch,
27
+ } from "../utilities/index.js";
28
+ import type { ApolloMcpServerApps } from "./types.js";
29
+ import type { ToolInfo } from "./typeRegistration.js";
30
+
31
+ export declare namespace AbstractApolloClient {
32
+ export interface Options extends Omit<NativeApolloClient.Options, "link"> {
33
+ link?: NativeApolloClient.Options["link"];
34
+ manifest: ApplicationManifest;
35
+ }
36
+ }
37
+
38
+ export class AbstractApolloClient extends NativeApolloClient {
39
+ manifest: ApplicationManifest;
40
+ private readonly appManager: McpAppManager;
41
+
42
+ /** @internal */
43
+ readonly [aiClientSymbol] = true;
44
+
45
+ #hydratedToolInput: Record<string, unknown> | undefined;
46
+ #toolHydrationLink: ToolHydrationLink;
47
+
48
+ #toolInfo: ToolInfo | undefined;
49
+ #toolMetadata: ApolloMcpServerApps.CallToolResult["_meta"] | undefined;
50
+
51
+ constructor(
52
+ options: AbstractApolloClient.Options,
53
+ connectToHost: ConnectToHostImplementation
54
+ ) {
55
+ const toolHydrationLink = new ToolHydrationLink();
56
+ const link = options.link ?? new ToolCallLink();
57
+
58
+ if (__DEV__) {
59
+ validateTerminatingLink(link);
60
+ }
61
+
62
+ super({
63
+ ...options,
64
+ link: toolHydrationLink.concat(link),
65
+ // Strip out the prefetch/tool directives so they don't get sent with the operation to the server
66
+ documentTransform: new DocumentTransform((document) => {
67
+ const serverDocument = removeDirectivesFromDocument(
68
+ [{ name: "prefetch" }, { name: "tool" }],
69
+ document
70
+ )!;
71
+
72
+ return visit(serverDocument, {
73
+ OperationDefinition(node) {
74
+ return { ...node, description: undefined };
75
+ },
76
+ });
77
+ }).concat(options.documentTransform ?? DocumentTransform.identity()),
78
+ });
79
+
80
+ this.#toolHydrationLink = toolHydrationLink;
81
+ this.manifest = options.manifest;
82
+ this.appManager = new McpAppManager(this.manifest, connectToHost);
83
+ }
84
+
85
+ get toolInfo() {
86
+ return this.#toolInfo;
87
+ }
88
+
89
+ get toolMetadata() {
90
+ return this.#toolMetadata;
91
+ }
92
+
93
+ setLink(newLink: ApolloLink): void {
94
+ super.setLink(this.#toolHydrationLink.concat(newLink));
95
+ }
96
+
97
+ stop() {
98
+ super.stop();
99
+ this.appManager.close().catch(() => {});
100
+ }
101
+
102
+ protected get hydratedToolInput() {
103
+ return this.#hydratedToolInput;
104
+ }
105
+
106
+ protected clearHydratedToolInput() {
107
+ this.#hydratedToolInput = undefined;
108
+ }
109
+
110
+ watchQuery<
111
+ T = any,
112
+ TVariables extends OperationVariables = OperationVariables,
113
+ >(options: WatchQueryOptions<TVariables, T>): ObservableQuery<T, TVariables> {
114
+ if (__DEV__) {
115
+ const toolInput = this.#hydratedToolInput;
116
+
117
+ if (toolInput) {
118
+ const toolName = this.toolInfo?.toolName;
119
+ const hasMatchingTool =
120
+ !!toolName && getToolNamesFromDocument(options.query).has(toolName);
121
+
122
+ if (hasMatchingTool) {
123
+ // Clear after first matching comparison so this only fires once and
124
+ // remounting doesn't produce spurious warnings.
125
+ this.#hydratedToolInput = undefined;
126
+
127
+ const variableNames = getVariableNamesFromDocument(options.query);
128
+
129
+ if (variableNames.size > 0) {
130
+ const { variables } = options;
131
+ const toolInputVariables = Object.entries(toolInput).filter(
132
+ ([key]) => variableNames.has(key)
133
+ );
134
+
135
+ const hasToolInputMismatch = toolInputVariables.some(
136
+ ([key, value]) => !equal(value, variables?.[key])
137
+ );
138
+
139
+ if (hasToolInputMismatch) {
140
+ warnOnVariableMismatch(
141
+ options.query,
142
+ Object.fromEntries(toolInputVariables),
143
+ variables
144
+ );
145
+ }
146
+ }
147
+ }
148
+ }
149
+ }
150
+
151
+ return super.watchQuery(options);
152
+ }
153
+
154
+ connect = cacheAsync(async () => {
155
+ const { structuredContent, toolName, toolInput, _meta } =
156
+ await this.appManager.connect();
157
+
158
+ this.#hydratedToolInput = toolInput;
159
+ this.#toolMetadata = _meta;
160
+
161
+ if (toolName != null) {
162
+ this.#toolInfo = { toolName, toolInput };
163
+ }
164
+
165
+ this.manifest.operations.forEach((operation) => {
166
+ if (
167
+ operation.prefetchID &&
168
+ structuredContent.prefetch?.[operation.prefetchID]
169
+ ) {
170
+ this.writeQuery({
171
+ query: parse(operation.body),
172
+ data: structuredContent.prefetch[operation.prefetchID].data,
173
+ });
174
+ this.#toolHydrationLink.hydrate(operation, {
175
+ result: structuredContent.prefetch[operation.prefetchID],
176
+ variables: {},
177
+ });
178
+ }
179
+
180
+ if (
181
+ structuredContent.result &&
182
+ operation.tools.find((tool) => tool.name === toolName)
183
+ ) {
184
+ this.#toolHydrationLink.hydrate(operation, {
185
+ result: structuredContent.result,
186
+ variables: getVariablesForOperationFromToolInput(
187
+ operation,
188
+ toolInput
189
+ ),
190
+ });
191
+ }
192
+ });
193
+
194
+ this.#toolHydrationLink.complete();
195
+ });
196
+ }
197
+
198
+ function validateTerminatingLink(link: ApolloLink) {
199
+ let terminatingLink = link;
200
+
201
+ while (terminatingLink.right) {
202
+ terminatingLink = terminatingLink.right;
203
+ }
204
+
205
+ if (
206
+ !isNamedLink(terminatingLink) ||
207
+ terminatingLink.name !== "ToolCallLink"
208
+ ) {
209
+ throw new Error(
210
+ "The terminating link must be a `ToolCallLink`. If you are using a `split` link, ensure the `right` branch uses a `ToolCallLink` as the terminating link."
211
+ );
212
+ }
213
+ }
214
+
215
+ function isNamedLink(link: ApolloLink): link is ApolloLink & { name: string } {
216
+ return "name" in link;
217
+ }
@@ -1,25 +1,23 @@
1
- import { ApolloClient as BaseApolloClient } from "@apollo/client";
2
- import type { ApplicationManifest } from "../types/application-manifest.js";
1
+ import { AbstractApolloClient } from "./AbstractApolloClient.js";
3
2
  import { aiClientSymbol } from "../utilities/constants.js";
4
3
 
5
4
  export declare namespace ApolloClient {
6
- export interface Options extends Omit<BaseApolloClient.Options, "link"> {
7
- link?: BaseApolloClient.Options["link"];
8
- manifest: ApplicationManifest;
9
- }
5
+ export interface Options extends AbstractApolloClient.Options {}
10
6
  }
11
7
 
12
- export class ApolloClient extends BaseApolloClient {
8
+ export class ApolloClient extends AbstractApolloClient {
13
9
  /** @internal */
14
10
  readonly [aiClientSymbol] = true;
15
11
 
16
12
  constructor(options: ApolloClient.Options) {
17
- super(options as any);
13
+ super(options as any, () => {
14
+ throw new Error(
15
+ "Cannot connect an `ApolloClient` instance from `@apollo/client-ai-apps` without export conditions. Please set conditions or import from the `/openai` or `/mcp` subpath directly."
16
+ );
17
+ });
18
18
 
19
19
  throw new Error(
20
20
  "Cannot construct an `ApolloClient` instance from `@apollo/client-ai-apps` without export conditions. Please set conditions or import from the `/openai` or `/mcp` subpath directly."
21
21
  );
22
22
  }
23
-
24
- async connect() {}
25
23
  }
@@ -0,0 +1,106 @@
1
+ import {
2
+ App,
3
+ type McpUiHostContextChangedNotification,
4
+ } from "@modelcontextprotocol/ext-apps";
5
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
6
+ import type { ApplicationManifest } from "../types/application-manifest.js";
7
+ import type { FormattedExecutionResult } from "graphql";
8
+ import type { DocumentNode, OperationVariables } from "@apollo/client";
9
+ import { print } from "@apollo/client/utilities";
10
+ import { cacheAsync } from "../utilities/index.js";
11
+ import type { ApolloMcpServerApps } from "./types.js";
12
+
13
+ type ExecuteQueryCallToolResult = Omit<CallToolResult, "structuredContent"> & {
14
+ structuredContent: FormattedExecutionResult;
15
+ _meta?: { structuredContent?: FormattedExecutionResult };
16
+ };
17
+
18
+ interface ConnectToHostResult {
19
+ structuredContent: ApolloMcpServerApps.StructuredContent;
20
+ toolInput: Record<string, unknown> | undefined;
21
+ _meta: ApolloMcpServerApps.Meta | undefined;
22
+ }
23
+
24
+ export type ConnectToHostImplementation = (
25
+ app: App
26
+ ) => Promise<ConnectToHostResult>;
27
+
28
+ /** @internal */
29
+ export class McpAppManager {
30
+ readonly app: App;
31
+
32
+ #connectToHost: ConnectToHostImplementation;
33
+
34
+ #hostContextCallbacks = new Set<
35
+ (params: McpUiHostContextChangedNotification["params"]) => void
36
+ >();
37
+
38
+ constructor(
39
+ manifest: ApplicationManifest,
40
+ connectToHost: ConnectToHostImplementation
41
+ ) {
42
+ this.app = new App({ name: manifest.name, version: manifest.appVersion });
43
+ this.#connectToHost = connectToHost;
44
+ }
45
+
46
+ onHostContextChanged(
47
+ cb: (params: McpUiHostContextChangedNotification["params"]) => void
48
+ ) {
49
+ this.#hostContextCallbacks.add(cb);
50
+
51
+ return () => {
52
+ this.#hostContextCallbacks.delete(cb);
53
+ };
54
+ }
55
+
56
+ connect = cacheAsync(async () => {
57
+ this.app.onhostcontextchanged = (params) => {
58
+ this.#hostContextCallbacks.forEach((cb) => cb(params));
59
+ };
60
+
61
+ const { structuredContent, _meta, toolInput } = await this.#connectToHost(
62
+ this.app
63
+ );
64
+
65
+ return {
66
+ structuredContent: {
67
+ ...structuredContent,
68
+ ..._meta?.structuredContent,
69
+ },
70
+ toolInput,
71
+ // Some hosts do not provide toolInfo in the ui/initialize response, so we
72
+ // fallback to `_meta.toolName` provided by Apollo MCP server if the value
73
+ // is not available.
74
+ toolName:
75
+ this.app.getHostContext()?.toolInfo?.tool.name ??
76
+ _meta?.toolName ??
77
+ // Some hosts do not forward `_meta` nor do they provide `toolInfo`. Our
78
+ // MCP server provides `toolName` in `structuredContent` as a workaround
79
+ // that we can use if all else fails
80
+ structuredContent.toolName,
81
+ _meta,
82
+ };
83
+ });
84
+
85
+ close() {
86
+ return this.app.close();
87
+ }
88
+
89
+ async executeQuery({
90
+ query,
91
+ variables,
92
+ }: {
93
+ query: DocumentNode;
94
+ variables: OperationVariables | undefined;
95
+ }) {
96
+ const result = (await this.app.callServerTool({
97
+ name: "execute",
98
+ arguments: { query: print(query), variables },
99
+ })) as ExecuteQueryCallToolResult;
100
+
101
+ return {
102
+ ...result.structuredContent,
103
+ ...result._meta?.structuredContent,
104
+ };
105
+ }
106
+ }
@@ -1,24 +1,9 @@
1
1
  export interface Register {}
2
2
 
3
- /**
4
- * @deprecated Please use the `ToolInfo` type instead. `ToolName` will be
5
- * removed in the next major version.
6
- */
7
- export type ToolName =
8
- Register extends { toolName: infer T extends string } ? T : string;
9
-
10
3
  type RegisteredToolInputs =
11
4
  Register extends { toolInputs: infer T extends Record<string, unknown> } ? T
12
5
  : never;
13
6
 
14
- /**
15
- * @deprecated Please use the `ToolInfo` type instead. `ToolInput` will be
16
- * removed in the next major version.
17
- */
18
- export type ToolInput =
19
- [RegisteredToolInputs] extends [never] ? Record<string, unknown>
20
- : RegisteredToolInputs[keyof RegisteredToolInputs];
21
-
22
7
  type ToolInfoFromInputs<T extends Record<string, unknown>> = {
23
8
  [K in keyof T]: {
24
9
  toolName: K;
package/src/core/types.ts CHANGED
@@ -4,11 +4,12 @@ import type { FormattedExecutionResult } from "graphql";
4
4
  export namespace ApolloMcpServerApps {
5
5
  export interface Meta {
6
6
  toolName: string;
7
+ structuredContent?: ApolloMcpServerApps.StructuredContent;
7
8
  [x: string]: unknown;
8
9
  }
9
10
 
10
11
  export interface StructuredContent {
11
- result: FormattedExecutionResult;
12
+ result?: FormattedExecutionResult;
12
13
  prefetch?: Record<string, FormattedExecutionResult>;
13
14
  toolName?: string;
14
15
  [x: string]: unknown;
package/src/index.mcp.ts CHANGED
@@ -2,4 +2,3 @@ export * from "./index.js";
2
2
 
3
3
  export { Platform } from "./mcp/core/Platform.js";
4
4
  export { ApolloClient } from "./mcp/core/ApolloClient.js";
5
- export { ToolCallLink } from "./mcp/link/ToolCallLink.js";
@@ -2,4 +2,3 @@ export * from "./index.js";
2
2
 
3
3
  export { Platform } from "./openai/core/Platform.js";
4
4
  export { ApolloClient } from "./openai/core/ApolloClient.js";
5
- export { ToolCallLink } from "./openai/link/ToolCallLink.js";
package/src/index.ts CHANGED
@@ -8,12 +8,7 @@ export type {
8
8
  ManifestWidgetSettings,
9
9
  } from "./types/application-manifest.js";
10
10
 
11
- export type {
12
- Register,
13
- ToolInfo,
14
- ToolInput,
15
- ToolName,
16
- } from "./core/typeRegistration.js";
11
+ export type { Register, ToolInfo } from "./core/typeRegistration.js";
17
12
 
18
13
  export { ApolloClient } from "./core/ApolloClient.js";
19
14
  export { ToolCallLink } from "./link/ToolCallLink.js";
@@ -1,4 +1,9 @@
1
- import { ApolloLink } from "@apollo/client";
1
+ import { ApolloClient, ApolloLink } from "@apollo/client";
2
+ import { __DEV__ } from "@apollo/client/utilities/environment";
3
+ import { from } from "rxjs";
4
+
5
+ import type { McpAppManager } from "../core/McpAppManager";
6
+ import { aiClientSymbol, invariant } from "../utilities/index.js";
2
7
 
3
8
  /**
4
9
  * A terminating link that sends a GraphQL request through an agent tool call.
@@ -23,11 +28,28 @@ import { ApolloLink } from "@apollo/client";
23
28
  * ```
24
29
  */
25
30
  export class ToolCallLink extends ApolloLink {
26
- constructor() {
27
- super();
31
+ readonly name = "ToolCallLink";
32
+ request(operation: ApolloLink.Operation) {
33
+ const client = getPrivateAccess(operation.client);
34
+
35
+ return from(
36
+ client.appManager.executeQuery({
37
+ query: operation.query,
38
+ variables: operation.variables,
39
+ })
40
+ );
41
+ }
42
+ }
28
43
 
29
- throw new Error(
30
- "Cannot construct a `ToolCallLink` from `@apollo/client-ai-apps` without export conditions. Please set export conditions or import from the `/openai` or `/mcp` subpath directly."
44
+ function getPrivateAccess(
45
+ client: ApolloClient
46
+ ): ApolloClient & { appManager: McpAppManager } {
47
+ if (__DEV__) {
48
+ invariant(
49
+ (client as any)[aiClientSymbol],
50
+ 'The "client" instance used with `ToolCallLink` is the wrong instance. You might have imported `ApolloClient` from `@apollo/client`. Please import `ApolloClient` from `@apollo/client-ai-apps` instead.'
31
51
  );
32
52
  }
53
+
54
+ return client as any;
33
55
  }
@@ -0,0 +1,90 @@
1
+ import { ApolloLink, Observable } from "@apollo/client";
2
+ import type { OperationVariables } from "@apollo/client";
3
+ import { canonicalStringify } from "@apollo/client/utilities";
4
+ import type { FormattedExecutionResult } from "graphql";
5
+ import { of } from "rxjs";
6
+ import type { ManifestOperation } from "../types/application-manifest.js";
7
+
8
+ type ResolveFn = () => void;
9
+ type OperationKey = string & { __type: "PendingKey" };
10
+
11
+ /**
12
+ * @internal
13
+ * Holds requests until the client is fully hydrated after a tool call. It is
14
+ * particularly useful for `network-only` and `cache-and-network` fetch policies
15
+ * where the request makes it to the link chain because it prevents a followup
16
+ * tool call to the MCP server for the just fetched data from the MCP server.
17
+ */
18
+ export class ToolHydrationLink extends ApolloLink {
19
+ #hydrated = false;
20
+ #pending: ResolveFn[] = [];
21
+ #operations = new Map<OperationKey, FormattedExecutionResult>();
22
+
23
+ hydrate(
24
+ operation: ManifestOperation,
25
+ {
26
+ result,
27
+ variables,
28
+ }: { result: FormattedExecutionResult; variables: OperationVariables }
29
+ ) {
30
+ this.#operations.set(
31
+ getKey({ operationName: operation.name, variables }),
32
+ result
33
+ );
34
+ }
35
+
36
+ complete(): void {
37
+ this.#hydrated = true;
38
+
39
+ for (const resolve of this.#pending.splice(0)) {
40
+ resolve();
41
+ }
42
+ }
43
+
44
+ request(
45
+ operation: ApolloLink.Operation,
46
+ forward: ApolloLink.ForwardFunction
47
+ ): Observable<ApolloLink.Result> {
48
+ const maybeSendToTerminatingLink = (): Observable<ApolloLink.Result> => {
49
+ const key = getKey(operation);
50
+ const result = this.#operations.get(key);
51
+
52
+ if (result) {
53
+ this.#operations.delete(key);
54
+ return of(result);
55
+ }
56
+
57
+ return forward(operation);
58
+ };
59
+
60
+ if (this.#hydrated) {
61
+ return maybeSendToTerminatingLink();
62
+ }
63
+
64
+ return new Observable((observer) => {
65
+ let active = true;
66
+
67
+ const resolve = () => {
68
+ if (!active) return;
69
+ maybeSendToTerminatingLink().subscribe(observer);
70
+ };
71
+ this.#pending.push(resolve);
72
+
73
+ return () => {
74
+ active = false;
75
+ const idx = this.#pending.indexOf(resolve);
76
+ if (idx !== -1) this.#pending.splice(idx, 1);
77
+ };
78
+ });
79
+ }
80
+ }
81
+
82
+ function getKey({
83
+ operationName,
84
+ variables,
85
+ }: {
86
+ operationName: string | undefined;
87
+ variables?: OperationVariables;
88
+ }): OperationKey {
89
+ return `${operationName}:${canonicalStringify(variables ?? {})}` as OperationKey;
90
+ }
@@ -0,0 +1,99 @@
1
+ import { expect, test } from "vitest";
2
+ import { gql, InMemoryCache } from "@apollo/client";
3
+ import { execute } from "@apollo/client/link";
4
+ import {
5
+ eachHostEnv,
6
+ mockApplicationManifest,
7
+ ObservableStream,
8
+ spyOnConsole,
9
+ } from "../../testing/internal/index.js";
10
+ import { ToolCallLink } from "../ToolCallLink.js";
11
+
12
+ eachHostEnv((setupHost, ApolloClient) => {
13
+ test("merges _meta.structuredContent into result for @private fields", async () => {
14
+ using _ = spyOnConsole("debug");
15
+ const query = gql`
16
+ query GreetingQuery {
17
+ greeting @private
18
+ }
19
+ `;
20
+
21
+ const client = new ApolloClient({
22
+ cache: new InMemoryCache(),
23
+ manifest: mockApplicationManifest(),
24
+ });
25
+
26
+ using env = await setupHost({
27
+ client,
28
+ toolCall: {
29
+ name: "GetProduct",
30
+ result: { structuredContent: { result: { data: { product: null } } } },
31
+ },
32
+ });
33
+ const { host, params } = env;
34
+
35
+ host.sendToolInput(params.toolInput);
36
+ host.sendToolResult(params.toolResult);
37
+
38
+ host.mockToolCall("execute", () => ({
39
+ structuredContent: {},
40
+ _meta: {
41
+ structuredContent: { data: { greeting: "Hello, private world" } },
42
+ },
43
+ }));
44
+
45
+ await client.connect();
46
+
47
+ const observable = execute(new ToolCallLink(), { query }, { client });
48
+ const stream = new ObservableStream(observable);
49
+
50
+ await expect(stream).toEmitValue({
51
+ data: { greeting: "Hello, private world" },
52
+ });
53
+
54
+ await expect(stream).toComplete();
55
+ });
56
+
57
+ test("delegates query execution to MCP host", async () => {
58
+ using _ = spyOnConsole("debug");
59
+ const query = gql`
60
+ query GreetingQuery {
61
+ greeting
62
+ }
63
+ `;
64
+
65
+ const client = new ApolloClient({
66
+ cache: new InMemoryCache(),
67
+ manifest: mockApplicationManifest(),
68
+ });
69
+
70
+ using env = await setupHost({
71
+ client,
72
+ toolCall: {
73
+ name: "GetProduct",
74
+ result: { structuredContent: { result: { data: { product: null } } } },
75
+ },
76
+ });
77
+ const { host, params } = env;
78
+
79
+ host.sendToolInput(params.toolInput);
80
+ host.sendToolResult(params.toolResult);
81
+
82
+ host.mockToolCall("execute", () => ({
83
+ structuredContent: {
84
+ data: { greeting: "Hello, world" },
85
+ },
86
+ }));
87
+
88
+ await client.connect();
89
+
90
+ const observable = execute(new ToolCallLink(), { query }, { client });
91
+ const stream = new ObservableStream(observable);
92
+
93
+ await expect(stream).toEmitValue({
94
+ data: { greeting: "Hello, world" },
95
+ });
96
+
97
+ await expect(stream).toComplete();
98
+ });
99
+ });