@apollo/client-ai-apps 0.5.4 → 0.6.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 (71) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/dist/config/defineConfig.d.ts +1 -0
  3. package/dist/config/defineConfig.d.ts.map +1 -1
  4. package/dist/config/schema.d.ts +1 -0
  5. package/dist/config/schema.d.ts.map +1 -1
  6. package/dist/config/schema.js +1 -0
  7. package/dist/config/schema.js.map +1 -1
  8. package/dist/mcp/core/McpAppManager.d.ts +2 -1
  9. package/dist/mcp/core/McpAppManager.d.ts.map +1 -1
  10. package/dist/mcp/core/McpAppManager.js +11 -1
  11. package/dist/mcp/core/McpAppManager.js.map +1 -1
  12. package/dist/mcp/react/hooks/useHostContext.d.ts +2 -0
  13. package/dist/mcp/react/hooks/useHostContext.d.ts.map +1 -0
  14. package/dist/mcp/react/hooks/useHostContext.js +7 -0
  15. package/dist/mcp/react/hooks/useHostContext.js.map +1 -0
  16. package/dist/mcp/react/index.d.ts +1 -0
  17. package/dist/mcp/react/index.d.ts.map +1 -1
  18. package/dist/mcp/react/index.js +1 -0
  19. package/dist/mcp/react/index.js.map +1 -1
  20. package/dist/openai/core/McpAppManager.d.ts +2 -1
  21. package/dist/openai/core/McpAppManager.d.ts.map +1 -1
  22. package/dist/openai/core/McpAppManager.js +11 -1
  23. package/dist/openai/core/McpAppManager.js.map +1 -1
  24. package/dist/openai/react/hooks/useHostContext.d.ts +2 -0
  25. package/dist/openai/react/hooks/useHostContext.d.ts.map +1 -0
  26. package/dist/openai/react/hooks/useHostContext.js +7 -0
  27. package/dist/openai/react/hooks/useHostContext.js.map +1 -0
  28. package/dist/openai/react/index.d.ts +1 -0
  29. package/dist/openai/react/index.d.ts.map +1 -1
  30. package/dist/openai/react/index.js +1 -0
  31. package/dist/openai/react/index.js.map +1 -1
  32. package/dist/react/index.d.ts +1 -0
  33. package/dist/react/index.d.ts.map +1 -1
  34. package/dist/react/index.js +1 -0
  35. package/dist/react/index.js.map +1 -1
  36. package/dist/react/index.mcp.d.ts +1 -1
  37. package/dist/react/index.mcp.d.ts.map +1 -1
  38. package/dist/react/index.mcp.js +1 -1
  39. package/dist/react/index.mcp.js.map +1 -1
  40. package/dist/react/index.openai.d.ts +1 -1
  41. package/dist/react/index.openai.d.ts.map +1 -1
  42. package/dist/react/index.openai.js +1 -1
  43. package/dist/react/index.openai.js.map +1 -1
  44. package/dist/types/application-manifest.d.ts +1 -0
  45. package/dist/types/application-manifest.d.ts.map +1 -1
  46. package/dist/types/application-manifest.js.map +1 -1
  47. package/dist/vite/__tests__/utilities/build.d.ts.map +1 -1
  48. package/dist/vite/__tests__/utilities/build.js +0 -1
  49. package/dist/vite/__tests__/utilities/build.js.map +1 -1
  50. package/dist/vite/apolloClientAiApps.d.ts +1 -0
  51. package/dist/vite/apolloClientAiApps.d.ts.map +1 -1
  52. package/dist/vite/apolloClientAiApps.js +28 -15
  53. package/dist/vite/apolloClientAiApps.js.map +1 -1
  54. package/package.json +1 -4
  55. package/src/config/schema.ts +1 -0
  56. package/src/mcp/core/McpAppManager.ts +23 -1
  57. package/src/mcp/react/hooks/__tests__/useHostContext.test.tsx +95 -0
  58. package/src/mcp/react/hooks/useHostContext.ts +14 -0
  59. package/src/mcp/react/index.ts +1 -0
  60. package/src/openai/core/McpAppManager.ts +22 -1
  61. package/src/openai/react/hooks/useHostContext.ts +14 -0
  62. package/src/openai/react/index.ts +1 -0
  63. package/src/react/index.mcp.ts +1 -0
  64. package/src/react/index.openai.ts +1 -0
  65. package/src/react/index.ts +3 -0
  66. package/src/testing/internal/mcp/mockMcpHost.ts +12 -0
  67. package/src/testing/internal/utilities/mockApplicationManifest.ts +1 -0
  68. package/src/types/application-manifest.ts +1 -0
  69. package/src/vite/__tests__/apolloClientAiApps.test.ts +279 -61
  70. package/src/vite/__tests__/utilities/build.ts +0 -1
  71. package/src/vite/apolloClientAiApps.ts +54 -17
@@ -1,4 +1,5 @@
1
1
  import { beforeEach, describe, expect, test, vi } from "vitest";
2
+ import { spyOnConsole } from "../../testing/internal/index.js";
2
3
  import fs from "node:fs";
3
4
  import { gql, type DocumentNode } from "@apollo/client";
4
5
  import { getMainDefinition, print } from "@apollo/client/utilities";
@@ -6,13 +7,11 @@ import { getOperationName } from "@apollo/client/utilities/internal";
6
7
  import { vol } from "memfs";
7
8
  import { apolloClientAiApps } from "../apolloClientAiApps.js";
8
9
  import { buildApp, setupServer } from "./utilities/build.js";
9
- import type {
10
- ApplicationManifest,
11
- ManifestWidgetSettings,
12
- } from "../../types/application-manifest.js";
10
+ import type { ApplicationManifest } from "../../types/application-manifest.js";
13
11
  import { explorer } from "../utilities/config.js";
14
12
  import { invariant } from "@apollo/client/utilities/invariant";
15
13
  import { Kind } from "graphql";
14
+ import type { ApolloClientAiAppsConfig } from "../../config/types.js";
16
15
 
17
16
  beforeEach(() => {
18
17
  explorer.clearCaches();
@@ -29,12 +28,19 @@ describe("operations", () => {
29
28
  invoked: "Tested global!",
30
29
  },
31
30
  },
31
+ csp: {
32
+ baseUriDomains: ["https://base.example.com"],
33
+ connectDomains: ["https://connect.example.com"],
34
+ frameDomains: ["https://frame.example.com"],
35
+ redirectDomains: ["https://redirect.example.com"],
36
+ resourceDomains: ["https://resource.example.com"],
37
+ },
32
38
  widgetSettings: {
33
39
  description: "Test",
34
40
  domain: "https://example.com",
35
41
  prefersBorder: true,
36
- } satisfies ManifestWidgetSettings,
37
- },
42
+ },
43
+ } satisfies ApolloClientAiAppsConfig.Config,
38
44
  }),
39
45
  "src/my-component.tsx": declareOperation(gql`
40
46
  query HelloWorldQuery($name: string!)
@@ -61,7 +67,9 @@ describe("operations", () => {
61
67
  });
62
68
 
63
69
  await using server = await setupServer({
64
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
70
+ plugins: [
71
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
72
+ ],
65
73
  });
66
74
  await server.listen();
67
75
 
@@ -70,10 +78,21 @@ describe("operations", () => {
70
78
  {
71
79
  "appVersion": "1.0.0",
72
80
  "csp": {
73
- "connectDomains": [],
74
- "frameDomains": [],
75
- "redirectDomains": [],
76
- "resourceDomains": [],
81
+ "baseUriDomains": [
82
+ "https://base.example.com",
83
+ ],
84
+ "connectDomains": [
85
+ "https://connect.example.com",
86
+ ],
87
+ "frameDomains": [
88
+ "https://frame.example.com",
89
+ ],
90
+ "redirectDomains": [
91
+ "https://redirect.example.com",
92
+ ],
93
+ "resourceDomains": [
94
+ "https://resource.example.com",
95
+ ],
77
96
  },
78
97
  "format": "apollo-ai-app-manifest",
79
98
  "hash": "abc",
@@ -81,6 +100,7 @@ describe("operations", () => {
81
100
  "toolInvocation/invoked": "Tested global!",
82
101
  "toolInvocation/invoking": "Testing global...",
83
102
  },
103
+ "name": "my-app",
84
104
  "operations": [
85
105
  {
86
106
  "body": "query HelloWorldQuery($name: string!) {
@@ -145,7 +165,9 @@ describe("operations", () => {
145
165
  });
146
166
 
147
167
  await using server = await setupServer({
148
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
168
+ plugins: [
169
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
170
+ ],
149
171
  });
150
172
  await server.listen();
151
173
 
@@ -154,6 +176,7 @@ describe("operations", () => {
154
176
  {
155
177
  "appVersion": "1.0.0",
156
178
  "csp": {
179
+ "baseUriDomains": [],
157
180
  "connectDomains": [],
158
181
  "frameDomains": [],
159
182
  "redirectDomains": [],
@@ -161,6 +184,7 @@ describe("operations", () => {
161
184
  },
162
185
  "format": "apollo-ai-app-manifest",
163
186
  "hash": "abc",
187
+ "name": "my-app",
164
188
  "operations": [
165
189
  {
166
190
  "body": "query HelloWorldQuery {
@@ -229,7 +253,9 @@ describe("operations", () => {
229
253
  });
230
254
 
231
255
  await using server = await setupServer({
232
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
256
+ plugins: [
257
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
258
+ ],
233
259
  });
234
260
  await server.listen();
235
261
 
@@ -238,6 +264,7 @@ describe("operations", () => {
238
264
  {
239
265
  "appVersion": "1.0.0",
240
266
  "csp": {
267
+ "baseUriDomains": [],
241
268
  "connectDomains": [],
242
269
  "frameDomains": [],
243
270
  "redirectDomains": [],
@@ -245,6 +272,7 @@ describe("operations", () => {
245
272
  },
246
273
  "format": "apollo-ai-app-manifest",
247
274
  "hash": "abc",
275
+ "name": "my-app",
248
276
  "operations": [
249
277
  {
250
278
  "body": "query HelloWorldQuery {
@@ -303,7 +331,9 @@ describe("operations", () => {
303
331
 
304
332
  await buildApp({
305
333
  mode: "production",
306
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
334
+ plugins: [
335
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
336
+ ],
307
337
  });
308
338
 
309
339
  const manifest = readManifestFile();
@@ -320,7 +350,9 @@ describe("operations", () => {
320
350
  });
321
351
 
322
352
  await using server = await setupServer({
323
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
353
+ plugins: [
354
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
355
+ ],
324
356
  });
325
357
  await server.listen();
326
358
 
@@ -329,6 +361,7 @@ describe("operations", () => {
329
361
  {
330
362
  "appVersion": "1.0.0",
331
363
  "csp": {
364
+ "baseUriDomains": [],
332
365
  "connectDomains": [],
333
366
  "frameDomains": [],
334
367
  "redirectDomains": [],
@@ -336,6 +369,7 @@ describe("operations", () => {
336
369
  },
337
370
  "format": "apollo-ai-app-manifest",
338
371
  "hash": "abc",
372
+ "name": "my-app",
339
373
  "operations": [],
340
374
  "resource": "http://localhost:3333",
341
375
  "version": "1",
@@ -354,7 +388,9 @@ describe("operations", () => {
354
388
  });
355
389
 
356
390
  await using server = await setupServer({
357
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
391
+ plugins: [
392
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
393
+ ],
358
394
  });
359
395
  await server.listen();
360
396
 
@@ -363,6 +399,7 @@ describe("operations", () => {
363
399
  {
364
400
  "appVersion": "1.0.0",
365
401
  "csp": {
402
+ "baseUriDomains": [],
366
403
  "connectDomains": [],
367
404
  "frameDomains": [],
368
405
  "redirectDomains": [],
@@ -370,6 +407,7 @@ describe("operations", () => {
370
407
  },
371
408
  "format": "apollo-ai-app-manifest",
372
409
  "hash": "abc",
410
+ "name": "my-app",
373
411
  "operations": [
374
412
  {
375
413
  "body": "query HelloWorldQuery {
@@ -401,7 +439,9 @@ describe("operations", () => {
401
439
  });
402
440
 
403
441
  await using server = await setupServer({
404
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
442
+ plugins: [
443
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
444
+ ],
405
445
  });
406
446
  await server.listen();
407
447
 
@@ -410,6 +450,7 @@ describe("operations", () => {
410
450
  {
411
451
  "appVersion": "1.0.0",
412
452
  "csp": {
453
+ "baseUriDomains": [],
413
454
  "connectDomains": [],
414
455
  "frameDomains": [],
415
456
  "redirectDomains": [],
@@ -417,6 +458,7 @@ describe("operations", () => {
417
458
  },
418
459
  "format": "apollo-ai-app-manifest",
419
460
  "hash": "abc",
461
+ "name": "my-app",
420
462
  "operations": [
421
463
  {
422
464
  "body": "mutation HelloWorldQuery {
@@ -454,7 +496,9 @@ describe("operations", () => {
454
496
 
455
497
  await expect(async () => {
456
498
  await using server = await setupServer({
457
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
499
+ plugins: [
500
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
501
+ ],
458
502
  });
459
503
  await server.listen();
460
504
  }).rejects.toThrowErrorMatchingInlineSnapshot(
@@ -482,7 +526,9 @@ describe("operations", () => {
482
526
  });
483
527
 
484
528
  await using server = await setupServer({
485
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
529
+ plugins: [
530
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
531
+ ],
486
532
  });
487
533
  await server.listen();
488
534
 
@@ -491,6 +537,7 @@ describe("operations", () => {
491
537
  {
492
538
  "appVersion": "1.0.0",
493
539
  "csp": {
540
+ "baseUriDomains": [],
494
541
  "connectDomains": [],
495
542
  "frameDomains": [],
496
543
  "redirectDomains": [],
@@ -498,6 +545,7 @@ describe("operations", () => {
498
545
  },
499
546
  "format": "apollo-ai-app-manifest",
500
547
  "hash": "abc",
548
+ "name": "my-app",
501
549
  "operations": [
502
550
  {
503
551
  "body": "query HelloWorldQuery {
@@ -557,7 +605,9 @@ describe("@prefetch", () => {
557
605
  });
558
606
 
559
607
  await using server = await setupServer({
560
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
608
+ plugins: [
609
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
610
+ ],
561
611
  });
562
612
  await server.listen();
563
613
 
@@ -566,6 +616,7 @@ describe("@prefetch", () => {
566
616
  {
567
617
  "appVersion": "1.0.0",
568
618
  "csp": {
619
+ "baseUriDomains": [],
569
620
  "connectDomains": [],
570
621
  "frameDomains": [],
571
622
  "redirectDomains": [],
@@ -573,6 +624,7 @@ describe("@prefetch", () => {
573
624
  },
574
625
  "format": "apollo-ai-app-manifest",
575
626
  "hash": "abc",
627
+ "name": "my-app",
576
628
  "operations": [
577
629
  {
578
630
  "body": "query HelloWorldQuery {
@@ -612,7 +664,9 @@ describe("@prefetch", () => {
612
664
 
613
665
  await expect(async () => {
614
666
  await using server = await setupServer({
615
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
667
+ plugins: [
668
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
669
+ ],
616
670
  });
617
671
  await server.listen();
618
672
  }).rejects.toThrowErrorMatchingInlineSnapshot(
@@ -634,7 +688,9 @@ describe("@tool validation", () => {
634
688
 
635
689
  await expect(async () => {
636
690
  await using server = await setupServer({
637
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
691
+ plugins: [
692
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
693
+ ],
638
694
  });
639
695
  await server.listen();
640
696
  }).rejects.toThrowErrorMatchingInlineSnapshot(
@@ -654,7 +710,9 @@ describe("@tool validation", () => {
654
710
 
655
711
  await expect(async () => {
656
712
  await using server = await setupServer({
657
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
713
+ plugins: [
714
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
715
+ ],
658
716
  });
659
717
  await server.listen();
660
718
  }).rejects.toThrowErrorMatchingInlineSnapshot(
@@ -675,7 +733,9 @@ describe("@tool validation", () => {
675
733
 
676
734
  await expect(async () => {
677
735
  await using server = await setupServer({
678
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
736
+ plugins: [
737
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
738
+ ],
679
739
  });
680
740
  await server.listen();
681
741
  }).rejects.toThrowErrorMatchingInlineSnapshot(
@@ -698,7 +758,9 @@ describe("@tool validation", () => {
698
758
 
699
759
  await expect(async () => {
700
760
  await using server = await setupServer({
701
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
761
+ plugins: [
762
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
763
+ ],
702
764
  });
703
765
  await server.listen();
704
766
  }).rejects.toThrowErrorMatchingInlineSnapshot(
@@ -718,7 +780,9 @@ describe("@tool validation", () => {
718
780
 
719
781
  await expect(async () => {
720
782
  await using server = await setupServer({
721
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
783
+ plugins: [
784
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
785
+ ],
722
786
  });
723
787
  await server.listen();
724
788
  }).rejects.toThrowErrorMatchingInlineSnapshot(
@@ -739,7 +803,9 @@ describe("@tool validation", () => {
739
803
 
740
804
  await expect(async () => {
741
805
  await using server = await setupServer({
742
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
806
+ plugins: [
807
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
808
+ ],
743
809
  });
744
810
  await server.listen();
745
811
  }).rejects.toThrowErrorMatchingInlineSnapshot(
@@ -764,7 +830,9 @@ describe("@tool validation", () => {
764
830
 
765
831
  await expect(async () => {
766
832
  await using server = await setupServer({
767
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
833
+ plugins: [
834
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
835
+ ],
768
836
  });
769
837
  await server.listen();
770
838
  }).rejects.toThrowErrorMatchingInlineSnapshot(
@@ -792,7 +860,9 @@ describe("config validation", () => {
792
860
 
793
861
  await expect(async () => {
794
862
  await using server = await setupServer({
795
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
863
+ plugins: [
864
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
865
+ ],
796
866
  });
797
867
  await server.listen();
798
868
  }).rejects.toThrowErrorMatchingInlineSnapshot(
@@ -821,7 +891,9 @@ describe("config validation", () => {
821
891
 
822
892
  await expect(async () => {
823
893
  await using server = await setupServer({
824
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
894
+ plugins: [
895
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
896
+ ],
825
897
  });
826
898
  await server.listen();
827
899
  }).rejects.toThrowErrorMatchingInlineSnapshot(
@@ -850,7 +922,9 @@ describe("config validation", () => {
850
922
 
851
923
  await expect(async () => {
852
924
  await using server = await setupServer({
853
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
925
+ plugins: [
926
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
927
+ ],
854
928
  });
855
929
  await server.listen();
856
930
  }).rejects.toThrowErrorMatchingInlineSnapshot(
@@ -874,7 +948,9 @@ describe("config validation", () => {
874
948
  });
875
949
 
876
950
  await using server = await setupServer({
877
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
951
+ plugins: [
952
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
953
+ ],
878
954
  });
879
955
  await server.listen();
880
956
 
@@ -902,7 +978,9 @@ describe("config validation", () => {
902
978
 
903
979
  await expect(async () => {
904
980
  await using server = await setupServer({
905
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
981
+ plugins: [
982
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
983
+ ],
906
984
  });
907
985
  await server.listen();
908
986
  }).rejects.toThrowErrorMatchingInlineSnapshot(
@@ -930,7 +1008,9 @@ describe("config validation", () => {
930
1008
 
931
1009
  await expect(async () => {
932
1010
  await using server = await setupServer({
933
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
1011
+ plugins: [
1012
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
1013
+ ],
934
1014
  });
935
1015
  await server.listen();
936
1016
  }).rejects.toThrowErrorMatchingInlineSnapshot(
@@ -961,7 +1041,9 @@ describe("config validation", () => {
961
1041
 
962
1042
  await expect(async () => {
963
1043
  await using server = await setupServer({
964
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
1044
+ plugins: [
1045
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
1046
+ ],
965
1047
  });
966
1048
  await server.listen();
967
1049
  }).rejects.toThrowErrorMatchingInlineSnapshot(
@@ -989,7 +1071,9 @@ describe("config validation", () => {
989
1071
 
990
1072
  await expect(async () => {
991
1073
  await using server = await setupServer({
992
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
1074
+ plugins: [
1075
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
1076
+ ],
993
1077
  });
994
1078
  await server.listen();
995
1079
  }).rejects.toThrowErrorMatchingInlineSnapshot(
@@ -1012,7 +1096,9 @@ describe("config validation", () => {
1012
1096
  });
1013
1097
 
1014
1098
  await using server = await setupServer({
1015
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
1099
+ plugins: [
1100
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
1101
+ ],
1016
1102
  });
1017
1103
  await server.listen();
1018
1104
 
@@ -1041,7 +1127,9 @@ describe("entry points", () => {
1041
1127
 
1042
1128
  await using server = await setupServer({
1043
1129
  mode: "staging",
1044
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
1130
+ plugins: [
1131
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
1132
+ ],
1045
1133
  });
1046
1134
  await server.listen();
1047
1135
 
@@ -1070,7 +1158,13 @@ describe("entry points", () => {
1070
1158
 
1071
1159
  await using server = await setupServer({
1072
1160
  mode: "staging",
1073
- plugins: [apolloClientAiApps({ targets: ["mcp"], devTarget: "mcp" })],
1161
+ plugins: [
1162
+ apolloClientAiApps({
1163
+ targets: ["mcp"],
1164
+ devTarget: "mcp",
1165
+ appsOutDir: "dist/apps",
1166
+ }),
1167
+ ],
1074
1168
  });
1075
1169
  await server.listen();
1076
1170
 
@@ -1091,7 +1185,9 @@ describe("entry points", () => {
1091
1185
 
1092
1186
  await using server = await setupServer({
1093
1187
  server: { https: {}, port: 5678 },
1094
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
1188
+ plugins: [
1189
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
1190
+ ],
1095
1191
  });
1096
1192
  await server.listen();
1097
1193
 
@@ -1112,7 +1208,9 @@ describe("entry points", () => {
1112
1208
 
1113
1209
  await using server = await setupServer({
1114
1210
  server: { port: 5678, host: "0.0.0.0" },
1115
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
1211
+ plugins: [
1212
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
1213
+ ],
1116
1214
  });
1117
1215
  await server.listen();
1118
1216
 
@@ -1139,7 +1237,9 @@ describe("entry points", () => {
1139
1237
 
1140
1238
  await buildApp({
1141
1239
  mode: "staging",
1142
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
1240
+ plugins: [
1241
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
1242
+ ],
1143
1243
  });
1144
1244
 
1145
1245
  const manifest = readManifestFile();
@@ -1168,7 +1268,12 @@ describe("entry points", () => {
1168
1268
 
1169
1269
  await buildApp({
1170
1270
  mode: "staging",
1171
- plugins: [apolloClientAiApps({ targets: ["mcp", "openai"] })],
1271
+ plugins: [
1272
+ apolloClientAiApps({
1273
+ targets: ["mcp", "openai"],
1274
+ appsOutDir: "dist/apps",
1275
+ }),
1276
+ ],
1172
1277
  });
1173
1278
 
1174
1279
  const manifest = readManifestFile();
@@ -1197,7 +1302,12 @@ describe("entry points", () => {
1197
1302
 
1198
1303
  await buildApp({
1199
1304
  mode: "staging",
1200
- plugins: [apolloClientAiApps({ targets: ["mcp", "openai"] })],
1305
+ plugins: [
1306
+ apolloClientAiApps({
1307
+ targets: ["mcp", "openai"],
1308
+ appsOutDir: "dist/apps",
1309
+ }),
1310
+ ],
1201
1311
  });
1202
1312
 
1203
1313
  const manifest = readManifestFile();
@@ -1228,7 +1338,9 @@ describe("entry points", () => {
1228
1338
 
1229
1339
  await buildApp({
1230
1340
  mode: "staging",
1231
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
1341
+ plugins: [
1342
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
1343
+ ],
1232
1344
  });
1233
1345
 
1234
1346
  const manifest = readManifestFile();
@@ -1250,7 +1362,9 @@ describe("entry points", () => {
1250
1362
 
1251
1363
  await buildApp({
1252
1364
  mode: "production",
1253
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
1365
+ plugins: [
1366
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
1367
+ ],
1254
1368
  });
1255
1369
 
1256
1370
  const manifest = readManifestFile();
@@ -1270,7 +1384,12 @@ describe("entry points", () => {
1270
1384
 
1271
1385
  await buildApp({
1272
1386
  mode: "production",
1273
- plugins: [apolloClientAiApps({ targets: ["mcp", "openai"] })],
1387
+ plugins: [
1388
+ apolloClientAiApps({
1389
+ targets: ["mcp", "openai"],
1390
+ appsOutDir: "dist/apps",
1391
+ }),
1392
+ ],
1274
1393
  });
1275
1394
 
1276
1395
  const manifest = readManifestFile();
@@ -1295,7 +1414,9 @@ describe("entry points", () => {
1295
1414
  async () =>
1296
1415
  await buildApp({
1297
1416
  mode: "staging",
1298
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
1417
+ plugins: [
1418
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
1419
+ ],
1299
1420
  })
1300
1421
  ).rejects.toThrowError(
1301
1422
  `[@apollo/client-ai-apps/vite] No entry point found for mode "staging". Entry points other than "development" and "production" must be defined in package.json file.`
@@ -1315,11 +1436,15 @@ describe("entry points", () => {
1315
1436
 
1316
1437
  await buildApp({
1317
1438
  mode: "production",
1318
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
1439
+ plugins: [
1440
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
1441
+ ],
1319
1442
  });
1320
1443
 
1321
1444
  expect(vol.existsSync(".application-manifest.json")).toBe(true);
1322
- expect(vol.existsSync("dist/.application-manifest.json")).toBe(true);
1445
+ expect(vol.existsSync("dist/apps/my-app/.application-manifest.json")).toBe(
1446
+ true
1447
+ );
1323
1448
  });
1324
1449
 
1325
1450
  test("writes to both locations when running in build mode with multiple targets", async () => {
@@ -1335,11 +1460,90 @@ describe("entry points", () => {
1335
1460
 
1336
1461
  await buildApp({
1337
1462
  mode: "production",
1338
- plugins: [apolloClientAiApps({ targets: ["mcp", "openai"] })],
1463
+ plugins: [
1464
+ apolloClientAiApps({
1465
+ targets: ["mcp", "openai"],
1466
+ appsOutDir: "dist/apps",
1467
+ }),
1468
+ ],
1339
1469
  });
1340
1470
 
1341
1471
  expect(vol.existsSync(".application-manifest.json")).toBe(true);
1342
- expect(vol.existsSync("dist/.application-manifest.json")).toBe(true);
1472
+ expect(vol.existsSync("dist/apps/my-app/.application-manifest.json")).toBe(
1473
+ true
1474
+ );
1475
+ });
1476
+ });
1477
+
1478
+ describe("appsOutDir", () => {
1479
+ test("errors when last segment is not `apps`", () => {
1480
+ expect(() =>
1481
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/output" })
1482
+ ).toThrowError(
1483
+ "`appsOutDir` must end with `apps` as the final path segment (e.g. `path/to/apps`)."
1484
+ );
1485
+ });
1486
+
1487
+ test("accepts trailing slash", async () => {
1488
+ vol.fromJSON({
1489
+ "package.json": mockPackageJson(),
1490
+ });
1491
+
1492
+ await expect(
1493
+ buildApp({
1494
+ mode: "production",
1495
+ build: { write: false },
1496
+ plugins: [
1497
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps/" }),
1498
+ ],
1499
+ })
1500
+ ).resolves.not.toThrowError();
1501
+ });
1502
+
1503
+ test("warns when `build.outDir` is set", async () => {
1504
+ vol.fromJSON({
1505
+ "package.json": mockPackageJson(),
1506
+ });
1507
+
1508
+ using _ = spyOnConsole("warn");
1509
+
1510
+ await buildApp({
1511
+ mode: "production",
1512
+ build: { outDir: "custom-out", write: false },
1513
+ plugins: [
1514
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
1515
+ ],
1516
+ });
1517
+
1518
+ expect(console.warn).toHaveBeenCalledWith(
1519
+ expect.stringContaining(
1520
+ "`build.outDir` is set in your Vite config but will be ignored"
1521
+ )
1522
+ );
1523
+ });
1524
+
1525
+ test("places output under `appsOutDir`", async () => {
1526
+ vol.fromJSON({
1527
+ "package.json": mockPackageJson(),
1528
+ "src/my-component.tsx": declareOperation(gql`
1529
+ query HelloWorldQuery
1530
+ @tool(name: "hello-world", description: "This is an awesome tool!") {
1531
+ helloWorld
1532
+ }
1533
+ `),
1534
+ });
1535
+
1536
+ await buildApp({
1537
+ mode: "production",
1538
+ plugins: [
1539
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
1540
+ ],
1541
+ });
1542
+
1543
+ expect(vol.existsSync("dist/apps/my-app/.application-manifest.json")).toBe(
1544
+ true
1545
+ );
1546
+ expect(vol.existsSync(".application-manifest.json")).toBe(true);
1343
1547
  });
1344
1548
  });
1345
1549
 
@@ -1387,7 +1591,9 @@ export default config;
1387
1591
 
1388
1592
  await buildApp({
1389
1593
  mode: "production",
1390
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
1594
+ plugins: [
1595
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
1596
+ ],
1391
1597
  });
1392
1598
 
1393
1599
  const manifest = readManifestFile();
@@ -1423,7 +1629,9 @@ export default config;
1423
1629
 
1424
1630
  await buildApp({
1425
1631
  mode: "production",
1426
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
1632
+ plugins: [
1633
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
1634
+ ],
1427
1635
  });
1428
1636
 
1429
1637
  const manifest = readManifestFile();
@@ -1457,7 +1665,9 @@ describe("file watching", () => {
1457
1665
  });
1458
1666
 
1459
1667
  await using server = await setupServer({
1460
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
1668
+ plugins: [
1669
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
1670
+ ],
1461
1671
  });
1462
1672
  await server.listen();
1463
1673
 
@@ -1495,7 +1705,9 @@ describe("html transforms", () => {
1495
1705
  server: {
1496
1706
  origin: "http://localhost:3000",
1497
1707
  },
1498
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
1708
+ plugins: [
1709
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
1710
+ ],
1499
1711
  });
1500
1712
 
1501
1713
  const html = `<html><head><script type="module" src="/@vite/client"></script></head><body><script module src="/assets/main.ts?t=12345"></script></body></html>`;
@@ -1519,7 +1731,9 @@ describe("html transforms", () => {
1519
1731
  server: {
1520
1732
  port: 3000,
1521
1733
  },
1522
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
1734
+ plugins: [
1735
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
1736
+ ],
1523
1737
  });
1524
1738
 
1525
1739
  await server.listen();
@@ -1547,7 +1761,9 @@ window.$RefreshSig$ = () => (type) => type;</script></head></html>`;
1547
1761
  server: {
1548
1762
  origin: "http://localhost:3000",
1549
1763
  },
1550
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
1764
+ plugins: [
1765
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
1766
+ ],
1551
1767
  });
1552
1768
 
1553
1769
  const html = `<html><head> <script type="module">import { injectIntoGlobalHook } from "/@react-refresh";
@@ -1573,7 +1789,9 @@ window.$RefreshSig$ = () => (type) => type;</script></head></html>`;
1573
1789
  server: {
1574
1790
  port: 3000,
1575
1791
  },
1576
- plugins: [apolloClientAiApps({ targets: ["mcp"] })],
1792
+ plugins: [
1793
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
1794
+ ],
1577
1795
  });
1578
1796
 
1579
1797
  await server.listen();
@@ -1604,7 +1822,7 @@ window.$RefreshSig$ = () => (type) => type;</script></head></html>`;
1604
1822
  origin: "http://localhost:3000",
1605
1823
  },
1606
1824
  plugins: [
1607
- apolloClientAiApps({ targets: ["mcp"] }),
1825
+ apolloClientAiApps({ targets: ["mcp"], appsOutDir: "dist/apps" }),
1608
1826
  {
1609
1827
  name: "capture-html",
1610
1828
  transformIndexHtml: {
@@ -1643,7 +1861,7 @@ function declareFragment(fragment: DocumentNode) {
1643
1861
  }
1644
1862
 
1645
1863
  function mockPackageJson(config?: Record<string, unknown>) {
1646
- return JSON.stringify({ version: "1.0.0", ...config });
1864
+ return JSON.stringify({ version: "1.0.0", name: "my-app", ...config });
1647
1865
  }
1648
1866
 
1649
1867
  function readManifestFile(