@agent-native/core 0.105.0 → 0.106.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 (139) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +33 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/client/AssistantChat.tsx +4 -0
  5. package/corpus/core/src/client/MultiTabAssistantChat.tsx +5 -1
  6. package/corpus/core/src/client/composer/PromptComposer.tsx +8 -0
  7. package/corpus/core/src/client/composer/TiptapComposer.tsx +38 -1
  8. package/corpus/core/src/client/resources/McpIntegrationDialog.tsx +107 -32
  9. package/corpus/core/src/client/resources/mcp-integration-catalog.ts +33 -0
  10. package/corpus/core/src/client/resources/use-mcp-servers.ts +1 -0
  11. package/corpus/core/src/client/use-chat-models.ts +10 -2
  12. package/corpus/core/src/connections/catalog.ts +107 -6
  13. package/corpus/core/src/localization/default-messages.ts +2 -0
  14. package/corpus/core/src/mcp/actions/call-mcp-tool.ts +18 -0
  15. package/corpus/core/src/mcp/actions/list-mcp-tools.ts +18 -0
  16. package/corpus/core/src/mcp-client/app-api.ts +136 -0
  17. package/corpus/core/src/mcp-client/index.ts +21 -0
  18. package/corpus/core/src/mcp-client/oauth-client.ts +462 -0
  19. package/corpus/core/src/mcp-client/oauth-routes.ts +307 -0
  20. package/corpus/core/src/mcp-client/remote-store.ts +93 -118
  21. package/corpus/core/src/mcp-client/remote-url.ts +97 -0
  22. package/corpus/core/src/mcp-client/routes.ts +11 -0
  23. package/corpus/core/src/oauth-tokens/store.ts +10 -4
  24. package/corpus/core/src/provider-api/index.ts +690 -55
  25. package/corpus/core/src/secrets/register-framework-secrets.ts +30 -0
  26. package/corpus/core/src/server/action-discovery.ts +2 -0
  27. package/corpus/core/src/server/core-routes-plugin.ts +9 -1
  28. package/corpus/core/src/server/workspace-provider-oauth.ts +448 -61
  29. package/corpus/core/src/templates/default/AGENTS.md +9 -7
  30. package/corpus/core/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +46 -0
  31. package/corpus/templates/analytics/app/lib/data-source-status.ts +2 -0
  32. package/corpus/templates/analytics/app/pages/DataSources.tsx +61 -0
  33. package/corpus/templates/analytics/server/handlers/jira.ts +7 -0
  34. package/corpus/templates/analytics/server/lib/jira.ts +14 -42
  35. package/corpus/templates/analytics/server/routes/api/test-connection.post.ts +11 -0
  36. package/corpus/templates/dispatch/app/routes/integrations.tsx +57 -4
  37. package/dist/client/AssistantChat.d.ts +2 -0
  38. package/dist/client/AssistantChat.d.ts.map +1 -1
  39. package/dist/client/AssistantChat.js +2 -2
  40. package/dist/client/AssistantChat.js.map +1 -1
  41. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  42. package/dist/client/MultiTabAssistantChat.js +5 -2
  43. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  44. package/dist/client/composer/PromptComposer.d.ts +2 -0
  45. package/dist/client/composer/PromptComposer.d.ts.map +1 -1
  46. package/dist/client/composer/PromptComposer.js +5 -2
  47. package/dist/client/composer/PromptComposer.js.map +1 -1
  48. package/dist/client/composer/TiptapComposer.d.ts +4 -1
  49. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  50. package/dist/client/composer/TiptapComposer.js +13 -5
  51. package/dist/client/composer/TiptapComposer.js.map +1 -1
  52. package/dist/client/resources/McpIntegrationDialog.d.ts.map +1 -1
  53. package/dist/client/resources/McpIntegrationDialog.js +58 -14
  54. package/dist/client/resources/McpIntegrationDialog.js.map +1 -1
  55. package/dist/client/resources/mcp-integration-catalog.d.ts +9 -0
  56. package/dist/client/resources/mcp-integration-catalog.d.ts.map +1 -1
  57. package/dist/client/resources/mcp-integration-catalog.js +13 -0
  58. package/dist/client/resources/mcp-integration-catalog.js.map +1 -1
  59. package/dist/client/resources/use-mcp-servers.d.ts +1 -0
  60. package/dist/client/resources/use-mcp-servers.d.ts.map +1 -1
  61. package/dist/client/resources/use-mcp-servers.js.map +1 -1
  62. package/dist/client/use-chat-models.d.ts +1 -0
  63. package/dist/client/use-chat-models.d.ts.map +1 -1
  64. package/dist/client/use-chat-models.js +8 -2
  65. package/dist/client/use-chat-models.js.map +1 -1
  66. package/dist/collab/awareness.d.ts +2 -2
  67. package/dist/collab/awareness.d.ts.map +1 -1
  68. package/dist/connections/catalog.d.ts +78 -7
  69. package/dist/connections/catalog.d.ts.map +1 -1
  70. package/dist/connections/catalog.js +97 -6
  71. package/dist/connections/catalog.js.map +1 -1
  72. package/dist/localization/default-messages.d.ts +2 -0
  73. package/dist/localization/default-messages.d.ts.map +1 -1
  74. package/dist/localization/default-messages.js +2 -0
  75. package/dist/localization/default-messages.js.map +1 -1
  76. package/dist/mcp/actions/call-mcp-tool.d.ts +7 -0
  77. package/dist/mcp/actions/call-mcp-tool.d.ts.map +1 -0
  78. package/dist/mcp/actions/call-mcp-tool.js +15 -0
  79. package/dist/mcp/actions/call-mcp-tool.js.map +1 -0
  80. package/dist/mcp/actions/list-mcp-tools.d.ts +5 -0
  81. package/dist/mcp/actions/list-mcp-tools.d.ts.map +1 -0
  82. package/dist/mcp/actions/list-mcp-tools.js +16 -0
  83. package/dist/mcp/actions/list-mcp-tools.js.map +1 -0
  84. package/dist/mcp-client/app-api.d.ts +34 -0
  85. package/dist/mcp-client/app-api.d.ts.map +1 -0
  86. package/dist/mcp-client/app-api.js +94 -0
  87. package/dist/mcp-client/app-api.js.map +1 -0
  88. package/dist/mcp-client/index.d.ts +3 -1
  89. package/dist/mcp-client/index.d.ts.map +1 -1
  90. package/dist/mcp-client/index.js +3 -1
  91. package/dist/mcp-client/index.js.map +1 -1
  92. package/dist/mcp-client/oauth-client.d.ts +113 -0
  93. package/dist/mcp-client/oauth-client.d.ts.map +1 -0
  94. package/dist/mcp-client/oauth-client.js +316 -0
  95. package/dist/mcp-client/oauth-client.js.map +1 -0
  96. package/dist/mcp-client/oauth-routes.d.ts +25 -0
  97. package/dist/mcp-client/oauth-routes.d.ts.map +1 -0
  98. package/dist/mcp-client/oauth-routes.js +218 -0
  99. package/dist/mcp-client/oauth-routes.js.map +1 -0
  100. package/dist/mcp-client/remote-store.d.ts +21 -6
  101. package/dist/mcp-client/remote-store.d.ts.map +1 -1
  102. package/dist/mcp-client/remote-store.js +72 -124
  103. package/dist/mcp-client/remote-store.js.map +1 -1
  104. package/dist/mcp-client/remote-url.d.ts +8 -0
  105. package/dist/mcp-client/remote-url.d.ts.map +1 -0
  106. package/dist/mcp-client/remote-url.js +103 -0
  107. package/dist/mcp-client/remote-url.js.map +1 -0
  108. package/dist/mcp-client/routes.d.ts +1 -0
  109. package/dist/mcp-client/routes.d.ts.map +1 -1
  110. package/dist/mcp-client/routes.js +9 -0
  111. package/dist/mcp-client/routes.js.map +1 -1
  112. package/dist/oauth-tokens/store.d.ts +2 -2
  113. package/dist/oauth-tokens/store.d.ts.map +1 -1
  114. package/dist/oauth-tokens/store.js +10 -6
  115. package/dist/oauth-tokens/store.js.map +1 -1
  116. package/dist/provider-api/index.d.ts +14 -0
  117. package/dist/provider-api/index.d.ts.map +1 -1
  118. package/dist/provider-api/index.js +499 -61
  119. package/dist/provider-api/index.js.map +1 -1
  120. package/dist/resources/handlers.d.ts +1 -1
  121. package/dist/secrets/register-framework-secrets.d.ts.map +1 -1
  122. package/dist/secrets/register-framework-secrets.js +28 -0
  123. package/dist/secrets/register-framework-secrets.js.map +1 -1
  124. package/dist/server/action-discovery.d.ts.map +1 -1
  125. package/dist/server/action-discovery.js +2 -0
  126. package/dist/server/action-discovery.js.map +1 -1
  127. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  128. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  129. package/dist/server/core-routes-plugin.js +9 -1
  130. package/dist/server/core-routes-plugin.js.map +1 -1
  131. package/dist/server/workspace-provider-oauth.d.ts +10 -3
  132. package/dist/server/workspace-provider-oauth.d.ts.map +1 -1
  133. package/dist/server/workspace-provider-oauth.js +325 -43
  134. package/dist/server/workspace-provider-oauth.js.map +1 -1
  135. package/dist/templates/default/AGENTS.md +9 -7
  136. package/dist/templates/workspace-core/.agents/skills/external-agents/SKILL.md +46 -0
  137. package/package.json +2 -2
  138. package/src/templates/default/AGENTS.md +9 -7
  139. package/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +46 -0
package/corpus/README.md CHANGED
@@ -27,5 +27,5 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
27
27
 
28
28
  ## Generated Counts
29
29
 
30
- - core files: 2277
30
+ - core files: 2283
31
31
  - template files: 6054
@@ -1,5 +1,38 @@
1
1
  # @agent-native/core
2
2
 
3
+ ## 0.106.0
4
+
5
+ ### Minor Changes
6
+
7
+ - a485fbe: Add the Creative Context package for importing, versioning, searching, and reusing workspace decks, designs, assets, websites, and content across creative apps.
8
+
9
+ Expose reusable ingestion, prompt-context provider, and search utility seams from core for package-driven creative context.
10
+
11
+ Includes bounded image-region cropping for durable localized creative-context fallbacks.
12
+
13
+ - a485fbe: Add one-click OAuth connections for standard remote MCP servers and shared workspace providers, including GitHub, Figma, Google Drive, HubSpot, Jira Cloud, Notion, and Sentry. OAuth credentials are encrypted and refreshed per user or organization without exposing tokens to browsers or MCP App iframes. Existing API-key and token credentials remain available as explicit fallbacks, and provider API requests can use an OAuth-backed workspace connection.
14
+ - a485fbe: Redesign the agent workspace with plain files first, dedicated resource collections, and a snapshots view for recent context inspection.
15
+ - a485fbe: Add public session replay context and timestamped Analytics links, support host-owned tracking identity, and allow Analytics replay pages to open at a requested timeline offset.
16
+
17
+ ### Patch Changes
18
+
19
+ - a485fbe: Fail serverless builds that bundle Vitest test runtime code into production SSR output.
20
+ - a485fbe: Disable app-shell transitions across the shared AgentSidebar subtree while the sidebar is being resized.
21
+ - a485fbe: Count explicitly failed action tool events as errors in observability traces and summaries even when their result text does not start with `Error`.
22
+ - a485fbe: Fix managed Slack OAuth to resolve scoped app credentials within the signed-in request context.
23
+ - a485fbe: Show a skeleton while the agent chat model picker is loading its provider and model list.
24
+ - a485fbe: Render dashboard, form, and other resource context as a normal composer chip instead of a scoped-chat badge and history partition.
25
+ - a485fbe: Protect existing extension visual design during data-only repairs by requiring explicit intent for full-body replacements, stop deterministic edit failures before the agent retries the same arguments, and resume cleanly when a provider closes while an action input is still being assembled.
26
+ - a485fbe: Teach Plan agents to fence destructive edits against fresh plan revisions and verify persisted content before closing feedback.
27
+ - a485fbe: Inline `AgentChatSurface` and `AgentChatHome` instances now hide the legacy Chat/Workspace header and chat tab row by default.
28
+ - a485fbe: Share package-registered actions across runtime module instances and mount them alongside explicit app action registries.
29
+ - a485fbe: Hide sidebar chat tabs until a second chat is open so a lone chat does not show a “New chat” tab.
30
+ - a485fbe: Re-enable the sticky 80/20 Sonnet 5 versus GPT-5.6 Luna default-model experiment across first-party hosted templates.
31
+ - a485fbe: Avoid repeated desktop sidebar width transitions while the agent sidebar is being resized.
32
+ - a485fbe: Prevent duplicate recovery submissions and reject late worker events after an agent run has already reached a terminal state.
33
+ - Updated dependencies [a485fbe]
34
+ - @agent-native/recap-cli@0.4.4
35
+
3
36
  ## 0.105.0
4
37
 
5
38
  ### Minor Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.105.0",
3
+ "version": "0.106.0",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -1800,6 +1800,8 @@ export interface AssistantChatProps {
1800
1800
  models: string[];
1801
1801
  configured: boolean;
1802
1802
  }>;
1803
+ /** Whether the model list is still being resolved. */
1804
+ modelListLoading?: boolean;
1803
1805
  /** Callback when user picks a model from the picker */
1804
1806
  onModelChange?: (model: string, engine: string) => void;
1805
1807
  /** Callback when user picks a reasoning effort from the picker */
@@ -2174,6 +2176,7 @@ const AssistantChatInner = forwardRef<
2174
2176
  defaultModel,
2175
2177
  selectedEffort,
2176
2178
  availableModels,
2179
+ modelListLoading,
2177
2180
  onModelChange,
2178
2181
  onEffortChange,
2179
2182
  imageModelMenu,
@@ -5531,6 +5534,7 @@ const AssistantChatInner = forwardRef<
5531
5534
  selectedModel={selectedModel ?? defaultModel}
5532
5535
  selectedEffort={selectedEffort}
5533
5536
  availableModels={availableModels}
5537
+ modelListLoading={modelListLoading}
5534
5538
  onModelChange={onModelChange}
5535
5539
  onEffortChange={onEffortChange}
5536
5540
  imageModelMenu={imageModelMenu}
@@ -911,6 +911,7 @@ export function MultiTabAssistantChat({
911
911
  const [availableModels, setAvailableModels] = useState<EngineModelGroup[]>(
912
912
  [],
913
913
  );
914
+ const [modelListLoading, setModelListLoading] = useState(true);
914
915
  const [defaultModel, setDefaultModel] = useState<string>(DEFAULT_MODEL);
915
916
  const threadModelRef = useRef<
916
917
  Map<string, { model: string; engine?: string; effort?: ReasoningEffort }>
@@ -1035,6 +1036,7 @@ export function MultiTabAssistantChat({
1035
1036
  );
1036
1037
 
1037
1038
  const refreshEngines = useCallback(() => {
1039
+ setModelListLoading(true);
1038
1040
  Promise.all([
1039
1041
  callAction("manage-agent-engine" as any, { action: "list" } as any).catch(
1040
1042
  () => null,
@@ -1068,7 +1070,8 @@ export function MultiTabAssistantChat({
1068
1070
  setAvailableModels(groups);
1069
1071
  setDefaultModel(currentModel ?? DEFAULT_MODEL);
1070
1072
  })
1071
- .catch(() => {});
1073
+ .catch(() => {})
1074
+ .finally(() => setModelListLoading(false));
1072
1075
  }, []);
1073
1076
 
1074
1077
  useEffect(() => {
@@ -2522,6 +2525,7 @@ export function MultiTabAssistantChat({
2522
2525
  composerSlot={props.composerSlot}
2523
2526
  defaultModel={defaultModel}
2524
2527
  availableModels={availableModels}
2528
+ modelListLoading={modelListLoading}
2525
2529
  onModelChange={handleModelChange}
2526
2530
  onEffortChange={handleEffortChange}
2527
2531
  onForkChat={() => handleForkChat(tabId)}
@@ -136,6 +136,8 @@ export interface PromptComposerProps {
136
136
  onSlashCommand?: (command: string) => void;
137
137
  /** External model list for hosts that already resolve models outside the app. */
138
138
  availableModels?: EngineModelGroup[];
139
+ /** Whether the external model list is still being resolved. */
140
+ modelListLoading?: boolean;
139
141
  selectedModel?: string;
140
142
  selectedEngine?: string;
141
143
  selectedEffort?: ReasoningEffort;
@@ -468,6 +470,7 @@ function PromptComposerInner({
468
470
  includeDefaultSlashSkills,
469
471
  onSlashCommand,
470
472
  availableModels,
473
+ modelListLoading,
471
474
  selectedModel,
472
475
  selectedEngine,
473
476
  selectedEffort,
@@ -501,6 +504,10 @@ function PromptComposerInner({
501
504
  const composerModelGroups = showModelSelector
502
505
  ? (availableModels ?? models.availableModels)
503
506
  : undefined;
507
+ const composerModelListLoading =
508
+ showModelSelector &&
509
+ (modelListLoading ??
510
+ (availableModels ? availableModels.length === 0 : models.isLoading));
504
511
  const handleModelChange = showModelSelector
505
512
  ? (onModelChange ?? models.onModelChange)
506
513
  : undefined;
@@ -645,6 +652,7 @@ function PromptComposerInner({
645
652
  selectedModel={composerModel}
646
653
  selectedEffort={composerEffort}
647
654
  availableModels={composerModelGroups}
655
+ modelListLoading={composerModelListLoading}
648
656
  onModelChange={handleModelChange}
649
657
  onEffortChange={handleEffortChange}
650
658
  providerConnectStatusEnabled={resolvedModelStatusChecksEnabled}
@@ -1,3 +1,4 @@
1
+ import { Skeleton } from "@agent-native/toolkit/ui/skeleton";
1
2
  import { useComposer, useComposerRuntime } from "@assistant-ui/react";
2
3
  import {
3
4
  IconArrowUp,
@@ -615,6 +616,8 @@ export interface TiptapComposerProps {
615
616
  models: string[];
616
617
  configured: boolean;
617
618
  }>;
619
+ /** Whether the model list is still being resolved. */
620
+ modelListLoading?: boolean;
618
621
  /** Callback when user picks a model */
619
622
  onModelChange?: (model: string, engine: string) => void;
620
623
  /** Callback when user picks a reasoning effort */
@@ -823,6 +826,13 @@ export const MODEL_SELECTOR_POPOVER_STYLE = {
823
826
  maxHeight: "min(500px, var(--radix-popover-content-available-height, 500px))",
824
827
  } satisfies React.CSSProperties;
825
828
 
829
+ export function shouldShowModelSelectorSkeleton(
830
+ isLoading: boolean,
831
+ engineCount: number,
832
+ ): boolean {
833
+ return isLoading && engineCount === 0;
834
+ }
835
+
826
836
  function friendlyModelName(model: string): string {
827
837
  if (FRIENDLY_MODEL_NAMES[model]) return FRIENDLY_MODEL_NAMES[model];
828
838
  // Claude: claude-{tier}-{major}[-minor][-dateYYYYMMDD] → Tier Major[.Minor]
@@ -950,6 +960,7 @@ function ModelSelector({
950
960
  model,
951
961
  effort = DEFAULT_REASONING_EFFORT,
952
962
  engines,
963
+ modelListLoading = false,
953
964
  onChange,
954
965
  onEffortChange,
955
966
  providerConnectStatusEnabled = true,
@@ -965,6 +976,7 @@ function ModelSelector({
965
976
  models: string[];
966
977
  configured: boolean;
967
978
  }>;
979
+ modelListLoading?: boolean;
968
980
  onChange: (model: string, engine: string) => void;
969
981
  onEffortChange?: (effort: ReasoningEffort) => void;
970
982
  providerConnectStatusEnabled?: boolean;
@@ -1034,6 +1046,10 @@ function ModelSelector({
1034
1046
  const imageModelLabel =
1035
1047
  imageModel?.options.find((option) => option.value === imageModel.value)
1036
1048
  ?.label ?? imageModel?.value;
1049
+ const showModelListSkeleton = shouldShowModelSelectorSkeleton(
1050
+ modelListLoading,
1051
+ engines.length,
1052
+ );
1037
1053
 
1038
1054
  // When Builder.io isn't connected, surface a one-click connect path —
1039
1055
  // it unlocks every model family (Claude, OpenAI, Gemini) without the
@@ -1200,6 +1216,7 @@ function ModelSelector({
1200
1216
  <div className="my-1 border-t border-border" />
1201
1217
  </>
1202
1218
  )}
1219
+ {showModelListSkeleton && <ModelSelectorSkeleton />}
1203
1220
  {autoModelGroup && (
1204
1221
  <button
1205
1222
  type="button"
@@ -1304,7 +1321,7 @@ function ModelSelector({
1304
1321
  </div>
1305
1322
  );
1306
1323
  })}
1307
- {effortOptions.length > 0 && (
1324
+ {!showModelListSkeleton && effortOptions.length > 0 && (
1308
1325
  <>
1309
1326
  <div className="my-1 border-t border-border" />
1310
1327
  <div className="flex items-center hover:bg-accent/30">
@@ -1352,6 +1369,24 @@ function ModelSelector({
1352
1369
  );
1353
1370
  }
1354
1371
 
1372
+ function ModelSelectorSkeleton() {
1373
+ return (
1374
+ <div
1375
+ className="space-y-1 px-2 py-2"
1376
+ role="status"
1377
+ aria-label="Loading models"
1378
+ >
1379
+ <span className="sr-only">Loading models…</span>
1380
+ {["w-24", "w-32", "w-20", "w-28"].map((width, index) => (
1381
+ <div key={index} className="flex items-center gap-1.5 px-1 py-1.5">
1382
+ <Skeleton className="size-3 rounded-sm" />
1383
+ <Skeleton className={`h-3 ${width}`} />
1384
+ </div>
1385
+ ))}
1386
+ </div>
1387
+ );
1388
+ }
1389
+
1355
1390
  type PopoverState = {
1356
1391
  type: "@" | "/";
1357
1392
  position: { top: number; left: number };
@@ -1389,6 +1424,7 @@ export function TiptapComposer({
1389
1424
  selectedModel,
1390
1425
  selectedEffort,
1391
1426
  availableModels,
1427
+ modelListLoading,
1392
1428
  onModelChange,
1393
1429
  onEffortChange,
1394
1430
  providerConnectStatusEnabled,
@@ -2791,6 +2827,7 @@ export function TiptapComposer({
2791
2827
  model={selectedModel}
2792
2828
  effort={selectedEffort}
2793
2829
  engines={availableModels}
2830
+ modelListLoading={modelListLoading}
2794
2831
  onChange={onModelChange}
2795
2832
  onEffortChange={onEffortChange}
2796
2833
  providerConnectStatusEnabled={providerConnectStatusEnabled}
@@ -7,6 +7,7 @@ import {
7
7
  } from "@tabler/icons-react";
8
8
  import { useEffect, useMemo, useRef, useState } from "react";
9
9
 
10
+ import { agentNativePath } from "../api-path.js";
10
11
  import {
11
12
  Dialog,
12
13
  DialogContent,
@@ -22,10 +23,12 @@ import {
22
23
  import { useT } from "../i18n.js";
23
24
  import { cn } from "../utils.js";
24
25
  import {
26
+ buildMcpOAuthStartUrl,
25
27
  createMcpIntegrationFormDefaults,
26
28
  filterMcpIntegrations,
27
29
  getDefaultMcpIntegrations,
28
30
  isCustomMcpIntegrationEnabled,
31
+ resolveMcpIntegrationScope,
29
32
  type DefaultMcpIntegration,
30
33
  } from "./mcp-integration-catalog.js";
31
34
  import {
@@ -92,7 +95,12 @@ export function McpIntegrationDialog({
92
95
  const [mode, setMode] = useState<DialogMode>("catalog");
93
96
  const [query, setQuery] = useState("");
94
97
  const [selected, setSelected] = useState<DefaultMcpIntegration | null>(null);
95
- const [scope, setScope] = useState<McpServerScope>(defaultScope);
98
+ const safeDefaultScope = resolveMcpIntegrationScope(
99
+ defaultScope,
100
+ hasOrg,
101
+ canCreateOrgMcp,
102
+ );
103
+ const [scope, setScope] = useState<McpServerScope>(safeDefaultScope);
96
104
  const [name, setName] = useState("");
97
105
  const [url, setUrl] = useState("");
98
106
  const [description, setDescription] = useState("");
@@ -135,7 +143,7 @@ export function McpIntegrationDialog({
135
143
  setMode(showCatalog ? "catalog" : "form");
136
144
  setQuery("");
137
145
  setSelected(null);
138
- setScope(defaultScope);
146
+ setScope(safeDefaultScope);
139
147
  setName("");
140
148
  setUrl("");
141
149
  setDescription("");
@@ -144,7 +152,7 @@ export function McpIntegrationDialog({
144
152
  setQuickBusyId(null);
145
153
  setError(null);
146
154
  setTestResult(null);
147
- }, [defaultScope, open, showCatalog]);
155
+ }, [open, safeDefaultScope, showCatalog]);
148
156
 
149
157
  useEffect(() => {
150
158
  if (open && mode === "form") {
@@ -161,7 +169,7 @@ export function McpIntegrationDialog({
161
169
  const openForm = (integration?: DefaultMcpIntegration | null) => {
162
170
  const defaults = createMcpIntegrationFormDefaults(integration);
163
171
  setSelected(integration ?? null);
164
- setScope(defaultScope);
172
+ setScope(safeDefaultScope);
165
173
  setName(defaults.name);
166
174
  setUrl(defaults.url);
167
175
  setDescription(defaults.description);
@@ -171,6 +179,55 @@ export function McpIntegrationDialog({
171
179
  setMode("form");
172
180
  };
173
181
 
182
+ const beginOAuth = (args: {
183
+ name: string;
184
+ url: string;
185
+ description: string;
186
+ }) => {
187
+ const validationError = getMcpUrlValidationError(args.url);
188
+ if (validationError) {
189
+ setError(validationError);
190
+ setTestResult(null);
191
+ return;
192
+ }
193
+ const returnUrl =
194
+ typeof window === "undefined"
195
+ ? "/"
196
+ : window.location.pathname +
197
+ window.location.search +
198
+ window.location.hash;
199
+ window.location.assign(
200
+ agentNativePath(
201
+ buildMcpOAuthStartUrl({
202
+ name: args.name,
203
+ url: args.url,
204
+ description: args.description,
205
+ scope: safeDefaultScope,
206
+ returnUrl,
207
+ }),
208
+ ),
209
+ );
210
+ };
211
+
212
+ const connectWithOAuth = (integration: DefaultMcpIntegration) =>
213
+ beginOAuth({
214
+ name: integration.name,
215
+ url: integration.url,
216
+ description: integration.description,
217
+ });
218
+
219
+ const connectCustomWithOAuth = () => {
220
+ if (!name.trim()) {
221
+ setError(t("mcpIntegrations.serverNameRequired"));
222
+ return;
223
+ }
224
+ beginOAuth({
225
+ name: name.trim(),
226
+ url: url.trim(),
227
+ description: description.trim(),
228
+ });
229
+ };
230
+
174
231
  const createServer = async (
175
232
  args: CreateMcpServerArgs,
176
233
  options?: { quickId?: string },
@@ -211,13 +268,17 @@ export function McpIntegrationDialog({
211
268
  };
212
269
 
213
270
  const quickConnect = (integration: DefaultMcpIntegration) => {
214
- if (integration.authMode !== "none") {
271
+ if (integration.authMode === "oauth") {
272
+ connectWithOAuth(integration);
273
+ return;
274
+ }
275
+ if (integration.authMode === "headers") {
215
276
  openForm(integration);
216
277
  return;
217
278
  }
218
279
  void createServer(
219
280
  {
220
- scope: defaultScope,
281
+ scope: safeDefaultScope,
221
282
  name: integration.name,
222
283
  url: integration.url,
223
284
  description: integration.description,
@@ -350,7 +411,7 @@ export function McpIntegrationDialog({
350
411
  const connected = connectedUrls.has(
351
412
  compareUrl(integration.url),
352
413
  );
353
- const requiresConfig = integration.authMode !== "none";
414
+ const requiresHeaders = integration.authMode === "headers";
354
415
  return (
355
416
  <article
356
417
  key={integration.id}
@@ -381,9 +442,11 @@ export function McpIntegrationDialog({
381
442
  ) : null}
382
443
  {connected
383
444
  ? t("mcpIntegrations.connected")
384
- : requiresConfig
385
- ? t("mcpIntegrations.configure")
386
- : t("mcpIntegrations.connect")}
445
+ : integration.authMode === "oauth"
446
+ ? t("mcpIntegrations.connectWithOAuth")
447
+ : requiresHeaders
448
+ ? t("mcpIntegrations.configure")
449
+ : t("mcpIntegrations.connect")}
387
450
  </button>
388
451
  {integration.docsUrl && (
389
452
  <Tooltip>
@@ -507,28 +570,30 @@ export function McpIntegrationDialog({
507
570
  placeholder={t("mcpIntegrations.descriptionPlaceholder")}
508
571
  />
509
572
  </label>
510
- <label className="block">
511
- <span className="mb-1 block text-[10px] font-medium text-muted-foreground">
512
- {t("mcpIntegrations.headers")}
513
- </span>
514
- <textarea
515
- value={headersText}
516
- onChange={(event) => {
517
- setHeadersText(event.target.value);
518
- clearFeedback();
519
- }}
520
- rows={3}
521
- className="w-full resize-y rounded-md border border-border bg-background px-2.5 py-1.5 text-[12px] text-foreground outline-none placeholder:text-muted-foreground/50 focus:ring-1 focus:ring-ring"
522
- style={{
523
- fontFamily:
524
- 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace',
525
- }}
526
- placeholder={
527
- selected?.headerPlaceholder ??
528
- t("mcpIntegrations.headersPlaceholder")
529
- }
530
- />
531
- </label>
573
+ {selected?.authMode !== "oauth" && (
574
+ <label className="block">
575
+ <span className="mb-1 block text-[10px] font-medium text-muted-foreground">
576
+ {t("mcpIntegrations.headers")}
577
+ </span>
578
+ <textarea
579
+ value={headersText}
580
+ onChange={(event) => {
581
+ setHeadersText(event.target.value);
582
+ clearFeedback();
583
+ }}
584
+ rows={3}
585
+ className="w-full resize-y rounded-md border border-border bg-background px-2.5 py-1.5 text-[12px] text-foreground outline-none placeholder:text-muted-foreground/50 focus:ring-1 focus:ring-ring"
586
+ style={{
587
+ fontFamily:
588
+ 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace',
589
+ }}
590
+ placeholder={
591
+ selected?.headerPlaceholder ??
592
+ t("mcpIntegrations.headersPlaceholder")
593
+ }
594
+ />
595
+ </label>
596
+ )}
532
597
  {selected?.docsUrl && (
533
598
  <a
534
599
  href={selected.docsUrl}
@@ -573,6 +638,16 @@ export function McpIntegrationDialog({
573
638
  >
574
639
  {t("mcpIntegrations.test")}
575
640
  </button>
641
+ {!selected && (
642
+ <button
643
+ type="button"
644
+ onClick={connectCustomWithOAuth}
645
+ disabled={!name.trim() || !url.trim() || busy}
646
+ className="rounded-md border border-border bg-background px-3 py-1.5 text-[12px] font-medium text-foreground hover:bg-accent disabled:pointer-events-none disabled:opacity-40"
647
+ >
648
+ {t("mcpIntegrations.connectWithOAuth")}
649
+ </button>
650
+ )}
576
651
  <button
577
652
  type="button"
578
653
  onClick={submitForm}
@@ -33,6 +33,14 @@ export interface McpIntegrationFormDefaults {
33
33
  headersText: string;
34
34
  }
35
35
 
36
+ export interface McpOAuthStartParams {
37
+ name: string;
38
+ url: string;
39
+ description: string;
40
+ scope: "user" | "org";
41
+ returnUrl: string;
42
+ }
43
+
36
44
  export const DEFAULT_MCP_INTEGRATIONS: DefaultMcpIntegration[] = [
37
45
  {
38
46
  id: "context7",
@@ -215,6 +223,31 @@ export function mcpIntegrationAuthLabel(mode: McpIntegrationAuthMode): string {
215
223
  return "OAuth";
216
224
  }
217
225
 
226
+ export function buildMcpOAuthStartUrl({
227
+ name,
228
+ url,
229
+ description,
230
+ scope,
231
+ returnUrl,
232
+ }: McpOAuthStartParams): string {
233
+ const params = new URLSearchParams({
234
+ name,
235
+ url,
236
+ description,
237
+ scope,
238
+ return: returnUrl,
239
+ });
240
+ return `/_agent-native/mcp/servers/oauth/start?${params.toString()}`;
241
+ }
242
+
243
+ export function resolveMcpIntegrationScope(
244
+ defaultScope: "user" | "org",
245
+ hasOrg: boolean,
246
+ canCreateOrgMcp: boolean,
247
+ ): "user" | "org" {
248
+ return defaultScope === "org" && hasOrg && canCreateOrgMcp ? "org" : "user";
249
+ }
250
+
218
251
  export function filterMcpIntegrations(
219
252
  query: string,
220
253
  integrations: DefaultMcpIntegration[] = getDefaultMcpIntegrations(),
@@ -20,6 +20,7 @@ export interface McpServer {
20
20
  name: string;
21
21
  url: string;
22
22
  headers?: Record<string, { set: true }>;
23
+ authMode: "none" | "headers" | "oauth";
23
24
  description?: string;
24
25
  firstParty?: boolean;
25
26
  createdAt: number;
@@ -22,6 +22,7 @@ export interface UseChatModelsResult {
22
22
  selectedModel: string;
23
23
  selectedEngine: string;
24
24
  selectedEffort: ReasoningEffort;
25
+ isLoading: boolean;
25
26
  onModelChange: (model: string, engine: string) => void;
26
27
  onEffortChange: (effort: ReasoningEffort) => void;
27
28
  refreshEngines: () => void;
@@ -78,6 +79,7 @@ export function useChatModels({
78
79
  const [availableModels, setAvailableModels] = useState<EngineModelGroup[]>(
79
80
  [],
80
81
  );
82
+ const [isLoading, setIsLoading] = useState(enabled);
81
83
  const [defaultModel, setDefaultModel] = useState<string>(DEFAULT_MODEL);
82
84
 
83
85
  const initialPersisted = readPersisted(storageKey);
@@ -140,6 +142,7 @@ export function useChatModels({
140
142
 
141
143
  const refreshEngines = useCallback(() => {
142
144
  if (!enabled) return;
145
+ setIsLoading(true);
143
146
  Promise.all([
144
147
  callAction("manage-agent-engine" as any, { action: "list" } as any).catch(
145
148
  () => null,
@@ -223,11 +226,15 @@ export function useChatModels({
223
226
  });
224
227
  }
225
228
  })
226
- .catch(() => {});
229
+ .catch(() => {})
230
+ .finally(() => setIsLoading(false));
227
231
  }, [enabled, storageKey]);
228
232
 
229
233
  useEffect(() => {
230
- if (!enabled) return;
234
+ if (!enabled) {
235
+ setIsLoading(false);
236
+ return;
237
+ }
231
238
  refreshEngines();
232
239
  }, [enabled, refreshEngines]);
233
240
 
@@ -237,6 +244,7 @@ export function useChatModels({
237
244
  selectedModel,
238
245
  selectedEngine,
239
246
  selectedEffort,
247
+ isLoading,
240
248
  onModelChange,
241
249
  onEffortChange,
242
250
  refreshEngines,