@agent-native/toolkit 0.210.1-nightly-20261020093350 → 0.210.1

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 (56) hide show
  1. package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
  2. package/dist/app/i18n/catalogs/ar-SA.js +13 -1
  3. package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
  4. package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
  5. package/dist/app/i18n/catalogs/de-DE.js +13 -1
  6. package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
  7. package/dist/app/i18n/catalogs/en-US.d.ts +13 -1
  8. package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
  9. package/dist/app/i18n/catalogs/en-US.js +13 -1
  10. package/dist/app/i18n/catalogs/en-US.js.map +1 -1
  11. package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
  12. package/dist/app/i18n/catalogs/es-ES.js +13 -1
  13. package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
  14. package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
  15. package/dist/app/i18n/catalogs/fr-FR.js +13 -1
  16. package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
  17. package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
  18. package/dist/app/i18n/catalogs/hi-IN.js +13 -1
  19. package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
  20. package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
  21. package/dist/app/i18n/catalogs/ja-JP.js +13 -1
  22. package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
  23. package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
  24. package/dist/app/i18n/catalogs/ko-KR.js +13 -1
  25. package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
  26. package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
  27. package/dist/app/i18n/catalogs/pt-BR.js +13 -1
  28. package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
  29. package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
  30. package/dist/app/i18n/catalogs/zh-CN.js +13 -1
  31. package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
  32. package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
  33. package/dist/app/i18n/catalogs/zh-TW.js +13 -1
  34. package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
  35. package/dist/app/integrations/IntegrationDetailPage.js +4 -0
  36. package/dist/app/integrations/IntegrationDetailPage.js.map +1 -1
  37. package/dist/app/resources/McpIntegrationDialog.d.ts.map +1 -1
  38. package/dist/app/resources/McpIntegrationDialog.js +106 -32
  39. package/dist/app/resources/McpIntegrationDialog.js.map +1 -1
  40. package/package.json +4 -4
  41. package/src/app/i18n/catalogs/ar-SA.ts +19 -1
  42. package/src/app/i18n/catalogs/de-DE.ts +22 -1
  43. package/src/app/i18n/catalogs/en-US.ts +19 -1
  44. package/src/app/i18n/catalogs/es-ES.ts +22 -1
  45. package/src/app/i18n/catalogs/fr-FR.ts +22 -1
  46. package/src/app/i18n/catalogs/hi-IN.ts +19 -1
  47. package/src/app/i18n/catalogs/ja-JP.ts +20 -1
  48. package/src/app/i18n/catalogs/ko-KR.ts +20 -1
  49. package/src/app/i18n/catalogs/pt-BR.ts +22 -1
  50. package/src/app/i18n/catalogs/zh-CN.ts +18 -1
  51. package/src/app/i18n/catalogs/zh-TW.ts +18 -1
  52. package/src/app/integrations/IntegrationDetailPage.spec.tsx +39 -23
  53. package/src/app/integrations/IntegrationDetailPage.tsx +4 -0
  54. package/src/app/resources/McpIntegrationDialog.hardening.spec.tsx +14 -0
  55. package/src/app/resources/McpIntegrationDialog.spec.tsx +135 -1
  56. package/src/app/resources/McpIntegrationDialog.tsx +257 -78
@@ -1,9 +1,11 @@
1
1
  import { agentNativePath } from "@agent-native/core/client/api-path";
2
+ import { useActionQuery } from "@agent-native/core/client/hooks";
2
3
  import { useT } from "@agent-native/core/client/i18n";
3
4
  import {
4
5
  allowsMcpIntegrationPersonalScope,
5
6
  buildMcpOAuthStartUrl,
6
7
  createMcpIntegrationFormDefaults,
8
+ DEFAULT_MCP_INTEGRATIONS,
7
9
  filterMcpIntegrations,
8
10
  getMcpIntegrationApiFallback,
9
11
  getDefaultMcpIntegrations,
@@ -98,6 +100,10 @@ interface TestResult {
98
100
  message: string;
99
101
  }
100
102
 
103
+ interface ManagedMcpOAuthReadiness {
104
+ hubspot: "configured" | "missing";
105
+ }
106
+
101
107
  function parseHeaderLines(text: string): Record<string, string> | undefined {
102
108
  const out: Record<string, string> = {};
103
109
  for (const line of text.split(/\r?\n/)) {
@@ -113,6 +119,12 @@ function parseHeaderLines(text: string): Record<string, string> | undefined {
113
119
  return Object.keys(out).length > 0 ? out : undefined;
114
120
  }
115
121
 
122
+ function isManagedHubSpotMcpUrl(serverUrl: string): boolean {
123
+ return /^https:\/\/mcp\.hubspot\.com(?::443)?(?:[/?#]|$)/i.test(
124
+ serverUrl.trim(),
125
+ );
126
+ }
127
+
116
128
  function requiresMcpIntegrationSetup(
117
129
  integration: DefaultMcpIntegration,
118
130
  ): boolean {
@@ -206,6 +218,39 @@ export function McpIntegrationDialog({
206
218
  [],
207
219
  );
208
220
  const showCatalog = defaultIntegrations.length > 0;
221
+ const hubspotOAuthReadinessQuery = useActionQuery<ManagedMcpOAuthReadiness>(
222
+ "get-managed-mcp-oauth-readiness" as never,
223
+ undefined,
224
+ {
225
+ enabled:
226
+ open &&
227
+ mcpServersQuery.isSuccess &&
228
+ (customIntegrationEnabled ||
229
+ defaultIntegrations.some(({ id }) => id === "hubspot")),
230
+ queryKeyScope: [mcpServersQuery.data?.orgId ?? "no-org"],
231
+ staleTime: 0,
232
+ refetchOnMount: "always",
233
+ retry: false,
234
+ batchRefetches: false,
235
+ },
236
+ );
237
+ const hubspotSetupStatus =
238
+ mcpServersQuery.isError || hubspotOAuthReadinessQuery.isError
239
+ ? "unavailable"
240
+ : !mcpServersQuery.isSuccess ||
241
+ hubspotOAuthReadinessQuery.isFetching ||
242
+ !hubspotOAuthReadinessQuery.isSuccess
243
+ ? "checking"
244
+ : hubspotOAuthReadinessQuery.data?.hubspot === "configured"
245
+ ? "configured"
246
+ : hubspotOAuthReadinessQuery.data?.hubspot === "missing"
247
+ ? "missing"
248
+ : "unavailable";
249
+
250
+ const refreshHubspotSetupStatus = () => {
251
+ if (mcpServersQuery.isError) void mcpServersQuery.refetch();
252
+ else void hubspotOAuthReadinessQuery.refetch();
253
+ };
209
254
 
210
255
  const connectedServers = useMemo(() => {
211
256
  const servers = [
@@ -221,13 +266,15 @@ export function McpIntegrationDialog({
221
266
  );
222
267
 
223
268
  const selectedRequiresSetup = Boolean(
224
- selected && requiresMcpIntegrationSetup(selected),
269
+ selected &&
270
+ (requiresMcpIntegrationSetup(selected) ||
271
+ (selected.id === "hubspot" && hubspotSetupStatus !== "configured")),
225
272
  );
226
273
  const selectedApiFallback = selected
227
274
  ? getMcpIntegrationApiFallback(selected)
228
275
  : null;
229
276
 
230
- const openApiFallback = (secretKey: string) => {
277
+ const openSecretSettings = (secretKey: string) => {
231
278
  onOpenChange(false);
232
279
  openAgentSettings(`secrets:${secretKey}`);
233
280
  };
@@ -345,10 +392,20 @@ export function McpIntegrationDialog({
345
392
  );
346
393
  if (restricted) {
347
394
  const fallback = getMcpIntegrationApiFallback(restricted);
348
- if (fallback) openApiFallback(fallback.secretKey);
395
+ if (fallback) openSecretSettings(fallback.secretKey);
349
396
  else openForm(restricted);
350
397
  return;
351
398
  }
399
+ if (
400
+ isManagedHubSpotMcpUrl(args.url) &&
401
+ hubspotSetupStatus !== "configured"
402
+ ) {
403
+ const hubspot =
404
+ defaultIntegrations.find(({ id }) => id === "hubspot") ??
405
+ DEFAULT_MCP_INTEGRATIONS.find(({ id }) => id === "hubspot");
406
+ openForm(hubspot, { scope: options?.scope });
407
+ return;
408
+ }
352
409
  if (!oauthReady) return;
353
410
  const validationError = getMcpUrlValidationError(args.url);
354
411
  if (validationError) {
@@ -543,7 +600,7 @@ export function McpIntegrationDialog({
543
600
  if (requiresMcpIntegrationSetup(integration)) {
544
601
  const apiFallback = getMcpIntegrationApiFallback(integration);
545
602
  if (apiFallback) {
546
- openApiFallback(apiFallback.secretKey);
603
+ openSecretSettings(apiFallback.secretKey);
547
604
  } else {
548
605
  openForm(integration);
549
606
  }
@@ -644,7 +701,7 @@ export function McpIntegrationDialog({
644
701
  if (requiresMcpIntegrationSetup(integration)) {
645
702
  const apiFallback = getMcpIntegrationApiFallback(integration);
646
703
  if (apiFallback) {
647
- openApiFallback(apiFallback.secretKey);
704
+ openSecretSettings(apiFallback.secretKey);
648
705
  } else {
649
706
  openForm(integration, { scope: "user" });
650
707
  }
@@ -784,19 +841,21 @@ export function McpIntegrationDialog({
784
841
  };
785
842
 
786
843
  const primaryAction = selectedRequiresSetup
787
- ? selectedApiFallback
788
- ? {
789
- run: () => openApiFallback(selectedApiFallback.secretKey),
790
- disabled: busy,
791
- label: t("mcpIntegrations.useApiToken"),
792
- }
793
- : selected && isMcpIntegrationOAuthAvailable(selected)
844
+ ? selected?.id === "hubspot"
845
+ ? null
846
+ : selectedApiFallback
794
847
  ? {
795
- run: () => connectWithOAuth(selected),
796
- disabled: !oauthReady || busy,
797
- label: t("mcpIntegrations.continueToConnect"),
848
+ run: () => openSecretSettings(selectedApiFallback.secretKey),
849
+ disabled: busy,
850
+ label: t("mcpIntegrations.useApiToken"),
798
851
  }
799
- : null
852
+ : selected && isMcpIntegrationOAuthAvailable(selected)
853
+ ? {
854
+ run: () => connectWithOAuth(selected),
855
+ disabled: !oauthReady || busy,
856
+ label: t("mcpIntegrations.continueToConnect"),
857
+ }
858
+ : null
800
859
  : selected?.authMode === "oauth" ||
801
860
  (!selected && customAuthMode === "oauth")
802
861
  ? {
@@ -963,6 +1022,13 @@ export function McpIntegrationDialog({
963
1022
  isMcpIntegrationUrl(integration, server.url),
964
1023
  );
965
1024
  const setupOnly = requiresMcpIntegrationSetup(integration);
1025
+ const isHubSpot = integration.id === "hubspot";
1026
+ const hubSpotSetupRequired =
1027
+ isHubSpot && hubspotSetupStatus === "missing";
1028
+ const hubSpotSetupUnavailable =
1029
+ isHubSpot && hubspotSetupStatus === "unavailable";
1030
+ const hubSpotSetupChecking =
1031
+ isHubSpot && hubspotSetupStatus === "checking";
966
1032
  const apiFallback =
967
1033
  getMcpIntegrationApiFallback(integration);
968
1034
  return {
@@ -980,21 +1046,41 @@ export function McpIntegrationDialog({
980
1046
  ),
981
1047
  status: connected
982
1048
  ? t("mcpIntegrations.connected")
983
- : setupOnly
984
- ? t("mcpIntegrations.status.setupRequired")
985
- : undefined,
1049
+ : hubSpotSetupRequired
1050
+ ? t("mcpIntegrations.hubspot.workspaceSetupStatus")
1051
+ : hubSpotSetupUnavailable
1052
+ ? t("mcpIntegrations.hubspot.setupUnavailable")
1053
+ : hubSpotSetupChecking
1054
+ ? t("mcpIntegrations.hubspot.setupChecking")
1055
+ : setupOnly
1056
+ ? t("mcpIntegrations.status.setupRequired")
1057
+ : undefined,
986
1058
  statusClassName: connected
987
1059
  ? "text-emerald-600 dark:text-emerald-400"
988
1060
  : "text-muted-foreground",
989
1061
  actionLabel: connected
990
1062
  ? "Manage"
991
- : setupOnly && apiFallback
992
- ? t("mcpIntegrations.useApiToken")
993
- : setupOnly
994
- ? t("mcpIntegrations.viewSetup")
995
- : t("mcpIntegrations.connect"),
996
- disabled: connected || busy || !mcpServersQuery.isSuccess,
1063
+ : hubSpotSetupUnavailable
1064
+ ? t("mcpIntegrations.hubspot.retrySetupStatus")
1065
+ : hubSpotSetupChecking
1066
+ ? t("mcpIntegrations.hubspot.setupChecking")
1067
+ : hubSpotSetupRequired
1068
+ ? t("mcpIntegrations.viewSetup")
1069
+ : setupOnly && apiFallback
1070
+ ? t("mcpIntegrations.useApiToken")
1071
+ : setupOnly
1072
+ ? t("mcpIntegrations.viewSetup")
1073
+ : t("mcpIntegrations.connect"),
1074
+ disabled:
1075
+ connected ||
1076
+ busy ||
1077
+ !mcpServersQuery.isSuccess ||
1078
+ hubSpotSetupChecking,
997
1079
  onAction: () => {
1080
+ if (hubSpotSetupUnavailable) {
1081
+ refreshHubspotSetupStatus();
1082
+ return;
1083
+ }
998
1084
  if (connected) {
999
1085
  openForm(integration);
1000
1086
  return;
@@ -1039,9 +1125,11 @@ export function McpIntegrationDialog({
1039
1125
  <DialogTitle>
1040
1126
  {selected
1041
1127
  ? selectedRequiresSetup
1042
- ? t("mcpIntegrations.setupTitle", {
1043
- name: selected.name,
1044
- })
1128
+ ? selected.id === "hubspot"
1129
+ ? t("mcpIntegrations.hubspot.setupTitle")
1130
+ : t("mcpIntegrations.setupTitle", {
1131
+ name: selected.name,
1132
+ })
1045
1133
  : t("mcpIntegrations.configureTitle", {
1046
1134
  name: selected.name,
1047
1135
  })
@@ -1050,7 +1138,9 @@ export function McpIntegrationDialog({
1050
1138
  <DialogDescription>
1051
1139
  {selected
1052
1140
  ? selectedRequiresSetup
1053
- ? t("mcpIntegrations.providerSetupFormDescription")
1141
+ ? selected.id === "hubspot"
1142
+ ? t("mcpIntegrations.hubspot.dialogDescription")
1143
+ : t("mcpIntegrations.providerSetupFormDescription")
1054
1144
  : selected.authMode === "none"
1055
1145
  ? t("mcpIntegrations.presetNoAuthDescription")
1056
1146
  : t("mcpIntegrations.presetAuthDescription")
@@ -1077,59 +1167,148 @@ export function McpIntegrationDialog({
1077
1167
  {t(selected.setupNoteKey)}
1078
1168
  </p>
1079
1169
  ) : null}
1080
- {selectedRequiresSetup && selected && (
1081
- <div
1082
- className={cn(
1083
- "mx-auto grid w-full max-w-xl gap-4",
1084
- presentation === "takeover" ? "py-8" : "py-1",
1085
- )}
1086
- >
1087
- {presentation === "takeover" ? (
1088
- <div>
1089
- <p className="text-base font-semibold tracking-[-0.02em] text-foreground">
1090
- {t("mcpIntegrations.providerSetupRequired")}
1091
- </p>
1092
- <p className="mt-1 text-sm leading-6 text-muted-foreground">
1093
- {t("mcpIntegrations.providerSetupDescription", {
1094
- name: selected.name,
1095
- })}
1096
- </p>
1097
- </div>
1098
- ) : null}
1099
- {selected.setupNoteKey ? (
1100
- <p className="text-sm leading-6 text-muted-foreground">
1101
- {t(selected.setupNoteKey)}
1102
- </p>
1103
- ) : null}
1104
- <div className="flex items-center justify-between gap-3 rounded-md border border-border px-3 py-2">
1105
- <div className="min-w-0">
1106
- <p className="text-sm font-medium text-foreground">
1107
- {t("mcpIntegrations.personalConnection")}
1170
+ {selectedRequiresSetup && selected ? (
1171
+ selected.id === "hubspot" ? (
1172
+ <div
1173
+ className={cn(
1174
+ "mx-auto grid w-full max-w-xl gap-4",
1175
+ presentation === "takeover" ? "py-8" : "py-1",
1176
+ )}
1177
+ >
1178
+ {hubspotSetupStatus === "checking" ? (
1179
+ <p
1180
+ role="status"
1181
+ className="flex items-center gap-2 text-sm text-muted-foreground"
1182
+ >
1183
+ <Spinner aria-hidden="true" />
1184
+ {t("mcpIntegrations.hubspot.setupChecking")}
1108
1185
  </p>
1109
- <p className="text-xs leading-5 text-muted-foreground">
1110
- {t("mcpIntegrations.personalDescription")}
1186
+ ) : hubspotSetupStatus === "unavailable" ? (
1187
+ <Alert>
1188
+ <IconAlertCircle aria-hidden="true" />
1189
+ <AlertDescription className="grid gap-3">
1190
+ <span>
1191
+ {t(
1192
+ "mcpIntegrations.hubspot.setupUnavailableDescription",
1193
+ )}
1194
+ </span>
1195
+ <Button
1196
+ type="button"
1197
+ variant="outline"
1198
+ size="sm"
1199
+ className="w-fit"
1200
+ onClick={() => refreshHubspotSetupStatus()}
1201
+ >
1202
+ {t("mcpIntegrations.hubspot.retrySetupStatus")}
1203
+ </Button>
1204
+ </AlertDescription>
1205
+ </Alert>
1206
+ ) : (
1207
+ <>
1208
+ <p className="text-sm leading-6 text-muted-foreground">
1209
+ {t(
1210
+ !hasOrg
1211
+ ? "mcpIntegrations.hubspot.noWorkspaceDescription"
1212
+ : canCreateOrgMcp
1213
+ ? "mcpIntegrations.hubspot.ownerSetupDescription"
1214
+ : "mcpIntegrations.hubspot.memberSetupDescription",
1215
+ )}
1216
+ </p>
1217
+ <div className="flex flex-wrap items-center gap-2">
1218
+ {canCreateOrgMcp ? (
1219
+ <Button
1220
+ type="button"
1221
+ variant="secondary"
1222
+ onClick={() =>
1223
+ openSecretSettings("HUBSPOT_MCP_CLIENT_ID")
1224
+ }
1225
+ >
1226
+ {t(
1227
+ "mcpIntegrations.hubspot.configureWorkspaceCredentials",
1228
+ )}
1229
+ </Button>
1230
+ ) : null}
1231
+ <Button
1232
+ type="button"
1233
+ variant="ghost"
1234
+ onClick={() => refreshHubspotSetupStatus()}
1235
+ >
1236
+ {t(
1237
+ "mcpIntegrations.hubspot.refreshSetupStatus",
1238
+ )}
1239
+ </Button>
1240
+ </div>
1241
+ </>
1242
+ )}
1243
+ {selected.docsUrl ? (
1244
+ <a
1245
+ href={selected.docsUrl}
1246
+ target="_blank"
1247
+ rel="noopener noreferrer"
1248
+ className="inline-flex w-fit items-center gap-1 text-sm font-medium text-foreground underline underline-offset-4 hover:text-muted-foreground"
1249
+ >
1250
+ {t("mcpIntegrations.openSetupDocs")}
1251
+ <IconExternalLink
1252
+ aria-hidden="true"
1253
+ className="size-3.5"
1254
+ />
1255
+ </a>
1256
+ ) : null}
1257
+ </div>
1258
+ ) : (
1259
+ <div
1260
+ className={cn(
1261
+ "mx-auto grid w-full max-w-xl gap-4",
1262
+ presentation === "takeover" ? "py-8" : "py-1",
1263
+ )}
1264
+ >
1265
+ {presentation === "takeover" ? (
1266
+ <div>
1267
+ <p className="text-base font-semibold tracking-[-0.02em] text-foreground">
1268
+ {t("mcpIntegrations.providerSetupRequired")}
1269
+ </p>
1270
+ <p className="mt-1 text-sm leading-6 text-muted-foreground">
1271
+ {t("mcpIntegrations.providerSetupDescription", {
1272
+ name: selected.name,
1273
+ })}
1274
+ </p>
1275
+ </div>
1276
+ ) : null}
1277
+ {selected.setupNoteKey ? (
1278
+ <p className="text-sm leading-6 text-muted-foreground">
1279
+ {t(selected.setupNoteKey)}
1111
1280
  </p>
1281
+ ) : null}
1282
+ <div className="flex items-center justify-between gap-3 rounded-md border border-border px-3 py-2">
1283
+ <div className="min-w-0">
1284
+ <p className="text-sm font-medium text-foreground">
1285
+ {t("mcpIntegrations.personalConnection")}
1286
+ </p>
1287
+ <p className="text-xs leading-5 text-muted-foreground">
1288
+ {t("mcpIntegrations.personalDescription")}
1289
+ </p>
1290
+ </div>
1291
+ <span className="shrink-0 text-xs text-muted-foreground">
1292
+ {t("mcpIntegrations.personal")}
1293
+ </span>
1112
1294
  </div>
1113
- <span className="shrink-0 text-xs text-muted-foreground">
1114
- {t("mcpIntegrations.personal")}
1115
- </span>
1295
+ {selected.docsUrl ? (
1296
+ <a
1297
+ href={selected.docsUrl}
1298
+ target="_blank"
1299
+ rel="noopener noreferrer"
1300
+ className="inline-flex w-fit items-center gap-1 text-sm font-medium text-foreground underline underline-offset-4 hover:text-muted-foreground"
1301
+ >
1302
+ {t("mcpIntegrations.viewSetup")}
1303
+ <IconExternalLink
1304
+ aria-hidden="true"
1305
+ className="size-3.5"
1306
+ />
1307
+ </a>
1308
+ ) : null}
1116
1309
  </div>
1117
- {selected.docsUrl ? (
1118
- <a
1119
- href={selected.docsUrl}
1120
- target="_blank"
1121
- rel="noopener noreferrer"
1122
- className="inline-flex w-fit items-center gap-1 text-sm font-medium text-foreground underline underline-offset-4 hover:text-muted-foreground"
1123
- >
1124
- {t("mcpIntegrations.viewSetup")}
1125
- <IconExternalLink
1126
- aria-hidden="true"
1127
- className="size-3.5"
1128
- />
1129
- </a>
1130
- ) : null}
1131
- </div>
1132
- )}
1310
+ )
1311
+ ) : null}
1133
1312
  {!selected && (
1134
1313
  <div className="flex items-center justify-between gap-3 text-sm">
1135
1314
  <span className="text-muted-foreground">