@agent-native/core 0.105.0 → 0.106.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 (194) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +39 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/application-state/index.ts +2 -0
  5. package/corpus/core/src/application-state/script-helpers.ts +12 -0
  6. package/corpus/core/src/application-state/store.ts +38 -0
  7. package/corpus/core/src/client/AssistantChat.tsx +4 -0
  8. package/corpus/core/src/client/MultiTabAssistantChat.tsx +5 -1
  9. package/corpus/core/src/client/composer/PromptComposer.tsx +8 -0
  10. package/corpus/core/src/client/composer/TiptapComposer.tsx +38 -1
  11. package/corpus/core/src/client/resources/McpIntegrationDialog.tsx +107 -32
  12. package/corpus/core/src/client/resources/mcp-integration-catalog.ts +33 -0
  13. package/corpus/core/src/client/resources/use-mcp-servers.ts +1 -0
  14. package/corpus/core/src/client/review/ReviewCommentComposer.tsx +10 -3
  15. package/corpus/core/src/client/use-chat-models.ts +10 -2
  16. package/corpus/core/src/connections/catalog.ts +107 -6
  17. package/corpus/core/src/localization/default-messages.ts +2 -0
  18. package/corpus/core/src/mcp/actions/call-mcp-tool.ts +18 -0
  19. package/corpus/core/src/mcp/actions/list-mcp-tools.ts +18 -0
  20. package/corpus/core/src/mcp-client/app-api.ts +136 -0
  21. package/corpus/core/src/mcp-client/index.ts +21 -0
  22. package/corpus/core/src/mcp-client/oauth-client.ts +462 -0
  23. package/corpus/core/src/mcp-client/oauth-routes.ts +307 -0
  24. package/corpus/core/src/mcp-client/remote-store.ts +93 -118
  25. package/corpus/core/src/mcp-client/remote-url.ts +97 -0
  26. package/corpus/core/src/mcp-client/routes.ts +11 -0
  27. package/corpus/core/src/oauth-tokens/store.ts +10 -4
  28. package/corpus/core/src/provider-api/index.ts +690 -55
  29. package/corpus/core/src/secrets/register-framework-secrets.ts +30 -0
  30. package/corpus/core/src/server/action-discovery.ts +2 -0
  31. package/corpus/core/src/server/core-routes-plugin.ts +9 -1
  32. package/corpus/core/src/server/workspace-provider-oauth.ts +448 -61
  33. package/corpus/core/src/templates/default/AGENTS.md +9 -7
  34. package/corpus/core/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +46 -0
  35. package/corpus/templates/analytics/app/lib/data-source-status.ts +2 -0
  36. package/corpus/templates/analytics/app/pages/DataSources.tsx +61 -0
  37. package/corpus/templates/analytics/server/handlers/jira.ts +7 -0
  38. package/corpus/templates/analytics/server/lib/jira.ts +14 -42
  39. package/corpus/templates/analytics/server/routes/api/test-connection.post.ts +11 -0
  40. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +22 -0
  41. package/corpus/templates/design/AGENTS.md +16 -0
  42. package/corpus/templates/design/actions/cancel-node-rewrite-request.ts +54 -0
  43. package/corpus/templates/design/actions/propose-node-rewrite.ts +290 -0
  44. package/corpus/templates/design/actions/resolve-node-rewrite.ts +257 -0
  45. package/corpus/templates/design/actions/view-screen.ts +98 -2
  46. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +71 -1
  47. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +19 -0
  48. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +33 -0
  49. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +194 -0
  50. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +21 -4
  51. package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +18 -0
  52. package/corpus/templates/design/app/components/design/multi-screen/types.ts +4 -0
  53. package/corpus/templates/design/app/components/visual-editor/NodeRewriteProposal.tsx +638 -0
  54. package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +535 -54
  55. package/corpus/templates/design/app/components/visual-editor/index.ts +5 -1
  56. package/corpus/templates/design/app/components/visual-editor/review-canvas-state.ts +21 -3
  57. package/corpus/templates/design/app/i18n/ar-SA.ts +35 -0
  58. package/corpus/templates/design/app/i18n/de-DE.ts +35 -0
  59. package/corpus/templates/design/app/i18n/es-ES.ts +35 -0
  60. package/corpus/templates/design/app/i18n/fr-FR.ts +35 -0
  61. package/corpus/templates/design/app/i18n/hi-IN.ts +35 -0
  62. package/corpus/templates/design/app/i18n/ja-JP.ts +35 -0
  63. package/corpus/templates/design/app/i18n/ko-KR.ts +35 -0
  64. package/corpus/templates/design/app/i18n/pt-BR.ts +35 -0
  65. package/corpus/templates/design/app/i18n/zh-CN.ts +35 -0
  66. package/corpus/templates/design/app/i18n/zh-TW.ts +32 -0
  67. package/corpus/templates/design/app/i18n-data.ts +32 -0
  68. package/corpus/templates/design/app/lib/node-reprompt.ts +110 -0
  69. package/corpus/templates/design/changelog/2026-07-16-design-editing-and-review-are-now-more-predictable.md +6 -0
  70. package/corpus/templates/design/server/lib/reprompt-action-guard.ts +107 -0
  71. package/corpus/templates/design/server/plugins/agent-chat.ts +9 -1
  72. package/corpus/templates/design/shared/code-layer.ts +15 -0
  73. package/corpus/templates/design/shared/node-rewrite.ts +213 -0
  74. package/corpus/templates/design/shared/review-anchor.ts +26 -3
  75. package/corpus/templates/dispatch/app/routes/integrations.tsx +57 -4
  76. package/dist/application-state/index.d.ts +2 -2
  77. package/dist/application-state/index.d.ts.map +1 -1
  78. package/dist/application-state/index.js +2 -2
  79. package/dist/application-state/index.js.map +1 -1
  80. package/dist/application-state/script-helpers.d.ts +1 -0
  81. package/dist/application-state/script-helpers.d.ts.map +1 -1
  82. package/dist/application-state/script-helpers.js +7 -1
  83. package/dist/application-state/script-helpers.js.map +1 -1
  84. package/dist/application-state/store.d.ts +1 -0
  85. package/dist/application-state/store.d.ts.map +1 -1
  86. package/dist/application-state/store.js +28 -0
  87. package/dist/application-state/store.js.map +1 -1
  88. package/dist/client/AssistantChat.d.ts +2 -0
  89. package/dist/client/AssistantChat.d.ts.map +1 -1
  90. package/dist/client/AssistantChat.js +2 -2
  91. package/dist/client/AssistantChat.js.map +1 -1
  92. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  93. package/dist/client/MultiTabAssistantChat.js +5 -2
  94. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  95. package/dist/client/composer/PromptComposer.d.ts +2 -0
  96. package/dist/client/composer/PromptComposer.d.ts.map +1 -1
  97. package/dist/client/composer/PromptComposer.js +5 -2
  98. package/dist/client/composer/PromptComposer.js.map +1 -1
  99. package/dist/client/composer/TiptapComposer.d.ts +4 -1
  100. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  101. package/dist/client/composer/TiptapComposer.js +13 -5
  102. package/dist/client/composer/TiptapComposer.js.map +1 -1
  103. package/dist/client/resources/McpIntegrationDialog.d.ts.map +1 -1
  104. package/dist/client/resources/McpIntegrationDialog.js +58 -14
  105. package/dist/client/resources/McpIntegrationDialog.js.map +1 -1
  106. package/dist/client/resources/mcp-integration-catalog.d.ts +9 -0
  107. package/dist/client/resources/mcp-integration-catalog.d.ts.map +1 -1
  108. package/dist/client/resources/mcp-integration-catalog.js +13 -0
  109. package/dist/client/resources/mcp-integration-catalog.js.map +1 -1
  110. package/dist/client/resources/use-mcp-servers.d.ts +1 -0
  111. package/dist/client/resources/use-mcp-servers.d.ts.map +1 -1
  112. package/dist/client/resources/use-mcp-servers.js.map +1 -1
  113. package/dist/client/review/ReviewCommentComposer.d.ts +4 -1
  114. package/dist/client/review/ReviewCommentComposer.d.ts.map +1 -1
  115. package/dist/client/review/ReviewCommentComposer.js +3 -3
  116. package/dist/client/review/ReviewCommentComposer.js.map +1 -1
  117. package/dist/client/use-chat-models.d.ts +1 -0
  118. package/dist/client/use-chat-models.d.ts.map +1 -1
  119. package/dist/client/use-chat-models.js +8 -2
  120. package/dist/client/use-chat-models.js.map +1 -1
  121. package/dist/collab/struct-routes.d.ts +1 -1
  122. package/dist/connections/catalog.d.ts +78 -7
  123. package/dist/connections/catalog.d.ts.map +1 -1
  124. package/dist/connections/catalog.js +97 -6
  125. package/dist/connections/catalog.js.map +1 -1
  126. package/dist/localization/default-messages.d.ts +2 -0
  127. package/dist/localization/default-messages.d.ts.map +1 -1
  128. package/dist/localization/default-messages.js +2 -0
  129. package/dist/localization/default-messages.js.map +1 -1
  130. package/dist/mcp/actions/call-mcp-tool.d.ts +7 -0
  131. package/dist/mcp/actions/call-mcp-tool.d.ts.map +1 -0
  132. package/dist/mcp/actions/call-mcp-tool.js +15 -0
  133. package/dist/mcp/actions/call-mcp-tool.js.map +1 -0
  134. package/dist/mcp/actions/list-mcp-tools.d.ts +5 -0
  135. package/dist/mcp/actions/list-mcp-tools.d.ts.map +1 -0
  136. package/dist/mcp/actions/list-mcp-tools.js +16 -0
  137. package/dist/mcp/actions/list-mcp-tools.js.map +1 -0
  138. package/dist/mcp-client/app-api.d.ts +34 -0
  139. package/dist/mcp-client/app-api.d.ts.map +1 -0
  140. package/dist/mcp-client/app-api.js +94 -0
  141. package/dist/mcp-client/app-api.js.map +1 -0
  142. package/dist/mcp-client/index.d.ts +3 -1
  143. package/dist/mcp-client/index.d.ts.map +1 -1
  144. package/dist/mcp-client/index.js +3 -1
  145. package/dist/mcp-client/index.js.map +1 -1
  146. package/dist/mcp-client/oauth-client.d.ts +113 -0
  147. package/dist/mcp-client/oauth-client.d.ts.map +1 -0
  148. package/dist/mcp-client/oauth-client.js +316 -0
  149. package/dist/mcp-client/oauth-client.js.map +1 -0
  150. package/dist/mcp-client/oauth-routes.d.ts +25 -0
  151. package/dist/mcp-client/oauth-routes.d.ts.map +1 -0
  152. package/dist/mcp-client/oauth-routes.js +218 -0
  153. package/dist/mcp-client/oauth-routes.js.map +1 -0
  154. package/dist/mcp-client/remote-store.d.ts +21 -6
  155. package/dist/mcp-client/remote-store.d.ts.map +1 -1
  156. package/dist/mcp-client/remote-store.js +72 -124
  157. package/dist/mcp-client/remote-store.js.map +1 -1
  158. package/dist/mcp-client/remote-url.d.ts +8 -0
  159. package/dist/mcp-client/remote-url.d.ts.map +1 -0
  160. package/dist/mcp-client/remote-url.js +103 -0
  161. package/dist/mcp-client/remote-url.js.map +1 -0
  162. package/dist/mcp-client/routes.d.ts +1 -0
  163. package/dist/mcp-client/routes.d.ts.map +1 -1
  164. package/dist/mcp-client/routes.js +9 -0
  165. package/dist/mcp-client/routes.js.map +1 -1
  166. package/dist/notifications/routes.d.ts +2 -2
  167. package/dist/oauth-tokens/store.d.ts +2 -2
  168. package/dist/oauth-tokens/store.d.ts.map +1 -1
  169. package/dist/oauth-tokens/store.js +10 -6
  170. package/dist/oauth-tokens/store.js.map +1 -1
  171. package/dist/observability/routes.d.ts +5 -5
  172. package/dist/provider-api/index.d.ts +14 -0
  173. package/dist/provider-api/index.d.ts.map +1 -1
  174. package/dist/provider-api/index.js +499 -61
  175. package/dist/provider-api/index.js.map +1 -1
  176. package/dist/secrets/register-framework-secrets.d.ts.map +1 -1
  177. package/dist/secrets/register-framework-secrets.js +28 -0
  178. package/dist/secrets/register-framework-secrets.js.map +1 -1
  179. package/dist/secrets/routes.d.ts +9 -9
  180. package/dist/server/action-discovery.d.ts.map +1 -1
  181. package/dist/server/action-discovery.js +2 -0
  182. package/dist/server/action-discovery.js.map +1 -1
  183. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  184. package/dist/server/core-routes-plugin.js +9 -1
  185. package/dist/server/core-routes-plugin.js.map +1 -1
  186. package/dist/server/workspace-provider-oauth.d.ts +10 -3
  187. package/dist/server/workspace-provider-oauth.d.ts.map +1 -1
  188. package/dist/server/workspace-provider-oauth.js +325 -43
  189. package/dist/server/workspace-provider-oauth.js.map +1 -1
  190. package/dist/templates/default/AGENTS.md +9 -7
  191. package/dist/templates/workspace-core/.agents/skills/external-agents/SKILL.md +46 -0
  192. package/package.json +2 -2
  193. package/src/templates/default/AGENTS.md +9 -7
  194. package/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +46 -0
@@ -2,6 +2,7 @@ import { Button } from "@agent-native/toolkit/ui/button";
2
2
  import { Spinner } from "@agent-native/toolkit/ui/spinner";
3
3
  import { Textarea } from "@agent-native/toolkit/ui/textarea";
4
4
  import { IconFocus2, IconMessageCircle, IconSend } from "@tabler/icons-react";
5
+ import type { ReactNode } from "react";
5
6
 
6
7
  import type { ReviewResolutionTarget } from "../../review/types.js";
7
8
  import { cn } from "../utils.js";
@@ -13,12 +14,14 @@ export interface ReviewCommentComposerProps {
13
14
  submittingTarget?: ReviewResolutionTarget | null;
14
15
  disabled?: boolean;
15
16
  showAgentAction?: boolean;
17
+ agentAction?: ReactNode;
16
18
  placeholder?: string;
17
19
  commentLabel?: string;
18
20
  agentLabel?: string;
19
21
  contextLabel?: string;
20
22
  autoFocus?: boolean;
21
23
  submitOnEnter?: boolean;
24
+ enterSubmitTarget?: ReviewResolutionTarget;
22
25
  onEscape?: () => void;
23
26
  className?: string;
24
27
  }
@@ -30,12 +33,14 @@ export function ReviewCommentComposer({
30
33
  submittingTarget = null,
31
34
  disabled = false,
32
35
  showAgentAction = false,
36
+ agentAction,
33
37
  placeholder = "Add a comment...",
34
38
  commentLabel = "Comment",
35
39
  agentLabel = "Send to agent",
36
40
  contextLabel,
37
41
  autoFocus = false,
38
42
  submitOnEnter = false,
43
+ enterSubmitTarget = "human",
39
44
  onEscape,
40
45
  className,
41
46
  }: ReviewCommentComposerProps) {
@@ -75,7 +80,7 @@ export function ReviewCommentComposer({
75
80
  }
76
81
  if (submitOnEnter && event.key === "Enter" && !event.shiftKey) {
77
82
  event.preventDefault();
78
- submit("human");
83
+ submit(enterSubmitTarget);
79
84
  }
80
85
  }}
81
86
  />
@@ -84,7 +89,7 @@ export function ReviewCommentComposer({
84
89
  type="submit"
85
90
  size="sm"
86
91
  disabled={!canSubmit}
87
- className="h-8 w-full min-w-0 gap-1.5 @2xs/review:w-auto"
92
+ className="h-8 w-full gap-1.5 @2xs/review:w-auto @2xs/review:min-w-28 @2xs/review:shrink-0"
88
93
  >
89
94
  {submittingTarget === "human" ? (
90
95
  <Spinner className="size-3.5" />
@@ -93,7 +98,9 @@ export function ReviewCommentComposer({
93
98
  )}
94
99
  <span className="truncate">{commentLabel}</span>
95
100
  </Button>
96
- {showAgentAction ? (
101
+ {showAgentAction && agentAction ? (
102
+ agentAction
103
+ ) : showAgentAction ? (
97
104
  <Button
98
105
  type="button"
99
106
  size="sm"
@@ -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,
@@ -27,6 +27,8 @@ export type WorkspaceConnectionProviderId =
27
27
  | "gmail"
28
28
  | "google_drive"
29
29
  | "hubspot"
30
+ | "jira"
31
+ | "sentry"
30
32
  | "granola"
31
33
  | "clips"
32
34
  | "generic";
@@ -91,12 +93,18 @@ export const WORKSPACE_CONNECTION_PROVIDERS = [
91
93
  credentialKeys: [
92
94
  {
93
95
  key: "GITHUB_TOKEN",
94
- label: "GitHub token",
96
+ label: "GitHub token (fallback)",
95
97
  description:
96
- "Fine-grained token or app credential scoped to the repositories the workspace should access.",
97
- required: true,
98
+ "Optional fine-grained token fallback. OAuth is preferred for new connections.",
99
+ required: false,
98
100
  },
99
101
  ],
102
+ oauth: {
103
+ provider: "github",
104
+ authorizationUrl: "https://github.com/login/oauth/authorize",
105
+ tokenUrl: "https://github.com/login/oauth/access_token",
106
+ scopes: ["repo", "read:org", "read:user", "user:email"],
107
+ },
100
108
  capabilities: ["search", "import", "code", "docs"],
101
109
  recommendedTemplateUses: ["brain", "analytics", "dispatch"],
102
110
  }),
@@ -199,15 +207,108 @@ export const WORKSPACE_CONNECTION_PROVIDERS = [
199
207
  credentialKeys: [
200
208
  {
201
209
  key: "HUBSPOT_PRIVATE_APP_TOKEN",
202
- label: "HubSpot private app token",
210
+ label: "HubSpot private app token (fallback)",
203
211
  description:
204
- "Private app token scoped to the CRM objects the workspace needs.",
205
- required: true,
212
+ "Optional private app token fallback. OAuth is preferred for new connections.",
213
+ required: false,
214
+ },
215
+ {
216
+ key: "HUBSPOT_ACCESS_TOKEN",
217
+ label: "HubSpot access token (fallback)",
218
+ description:
219
+ "Optional legacy access token fallback for existing HubSpot setups.",
220
+ required: false,
206
221
  },
207
222
  ],
223
+ oauth: {
224
+ provider: "hubspot",
225
+ authorizationUrl: "https://app.hubspot.com/oauth/authorize",
226
+ tokenUrl: "https://api.hubapi.com/oauth/v3/token",
227
+ refreshUrl: "https://api.hubapi.com/oauth/v3/token",
228
+ scopes: [
229
+ "oauth",
230
+ "crm.objects.contacts.read",
231
+ "crm.objects.companies.read",
232
+ "crm.objects.deals.read",
233
+ "crm.objects.tickets.read",
234
+ "crm.schemas.contacts.read",
235
+ "crm.schemas.companies.read",
236
+ "crm.schemas.deals.read",
237
+ "crm.schemas.tickets.read",
238
+ ],
239
+ },
208
240
  capabilities: ["search", "import", "crm"],
209
241
  recommendedTemplateUses: ["analytics", "brain", "mail", "dispatch"],
210
242
  }),
243
+ defineWorkspaceConnectionProvider({
244
+ id: "jira",
245
+ label: "Jira Cloud",
246
+ description:
247
+ "Projects, issues, sprints, and delivery context for engineering analytics and product workflows.",
248
+ credentialKeys: [
249
+ {
250
+ key: "JIRA_BASE_URL",
251
+ label: "Jira base URL (fallback)",
252
+ description:
253
+ "Optional site URL for API-token fallback connections. OAuth connections discover their site automatically.",
254
+ required: false,
255
+ },
256
+ {
257
+ key: "JIRA_USER_EMAIL",
258
+ label: "Jira email (fallback)",
259
+ description:
260
+ "Optional Atlassian account email for API-token fallback connections.",
261
+ required: false,
262
+ },
263
+ {
264
+ key: "JIRA_API_TOKEN",
265
+ label: "Jira API token (fallback)",
266
+ description:
267
+ "Optional API token fallback for existing Jira Cloud setups. OAuth is preferred for new connections.",
268
+ required: false,
269
+ },
270
+ ],
271
+ oauth: {
272
+ provider: "jira",
273
+ authorizationUrl: "https://auth.atlassian.com/authorize",
274
+ tokenUrl: "https://auth.atlassian.com/oauth/token",
275
+ refreshUrl: "https://auth.atlassian.com/oauth/token",
276
+ scopes: ["read:jira-work", "read:jira-user", "offline_access"],
277
+ },
278
+ capabilities: ["search", "import", "code", "docs"],
279
+ recommendedTemplateUses: ["analytics", "brain", "dispatch"],
280
+ }),
281
+ defineWorkspaceConnectionProvider({
282
+ id: "sentry",
283
+ label: "Sentry",
284
+ description:
285
+ "Error tracking, performance issues, projects, and release context for engineering analytics.",
286
+ credentialKeys: [
287
+ {
288
+ key: "SENTRY_AUTH_TOKEN",
289
+ label: "Sentry auth token (fallback)",
290
+ description:
291
+ "Optional auth token fallback for self-hosted or local Sentry setups. OAuth is preferred for sentry.io.",
292
+ required: false,
293
+ },
294
+ {
295
+ key: "SENTRY_SERVER_TOKEN",
296
+ label: "Sentry server token (fallback)",
297
+ description:
298
+ "Optional server token fallback for existing Sentry deployments.",
299
+ required: false,
300
+ },
301
+ ],
302
+ oauth: {
303
+ provider: "sentry",
304
+ authorizationUrl: "https://sentry.io/oauth/authorize/",
305
+ tokenUrl: "https://sentry.io/oauth/token/",
306
+ refreshUrl: "https://sentry.io/oauth/token/",
307
+ scopes: ["org:read", "project:read", "event:read", "team:read"],
308
+ },
309
+ capabilities: ["search", "import", "docs"],
310
+ recommendedTemplateUses: ["analytics", "brain", "dispatch"],
311
+ }),
211
312
  defineWorkspaceConnectionProvider({
212
313
  id: "granola",
213
314
  label: "Granola",
@@ -690,6 +690,7 @@ const messages = {
690
690
  connected: "Connected",
691
691
  configure: "Configure",
692
692
  connect: "Connect",
693
+ connectWithOAuth: "Connect with OAuth",
693
694
  backToIntegrations: "Back to integrations",
694
695
  customTitle: "Add custom MCP server",
695
696
  configureTitle: "Configure {{name}}",
@@ -705,6 +706,7 @@ const messages = {
705
706
  organization: "Organization",
706
707
  orgNoOrg: "Join an organization to share MCP servers",
707
708
  orgAdminOnly: "Only owners and admins can add org-scope servers",
709
+ serverNameRequired: "Enter a server name before connecting with OAuth.",
708
710
  serverName: "Server name",
709
711
  url: "URL",
710
712
  fieldDescription: "Description",
@@ -0,0 +1,18 @@
1
+ import { z } from "zod";
2
+
3
+ import { defineAction } from "../../action.js";
4
+ import { callMcpTool } from "../../mcp-client/app-api.js";
5
+
6
+ export default defineAction({
7
+ description:
8
+ "Call an MCP tool visible to the authenticated app request by server id and original tool name. Server credentials and tokens are never exposed.",
9
+ agentTool: false,
10
+ requiresAuth: true,
11
+ schema: z.object({
12
+ serverId: z.string().trim().min(1),
13
+ toolName: z.string().trim().min(1),
14
+ arguments: z.record(z.string(), z.unknown()).default({}),
15
+ }),
16
+ run: ({ serverId, toolName, arguments: args }) =>
17
+ callMcpTool(serverId, toolName, args),
18
+ });
@@ -0,0 +1,18 @@
1
+ import { z } from "zod";
2
+
3
+ import { defineAction } from "../../action.js";
4
+ import { listVisibleMcpTools } from "../../mcp-client/app-api.js";
5
+
6
+ export default defineAction({
7
+ description:
8
+ "List MCP tools visible to the authenticated app request. Returns tool contracts only; server credentials and tokens are never exposed.",
9
+ agentTool: false,
10
+ requiresAuth: true,
11
+ schema: z.object({
12
+ serverId: z.string().trim().min(1).optional(),
13
+ }),
14
+ http: { method: "GET" },
15
+ readOnly: true,
16
+ parallelSafe: true,
17
+ run: ({ serverId }) => listVisibleMcpTools({ serverId }),
18
+ });
@@ -0,0 +1,136 @@
1
+ import { isToolVisibilityModelOnly } from "@modelcontextprotocol/ext-apps/app-bridge";
2
+
3
+ import { getGlobalMcpManager } from "../server/agent-chat/mcp-glue.js";
4
+ import { getRequestContext } from "../server/request-context.js";
5
+ import {
6
+ buildMcpToolName,
7
+ type McpClientManager,
8
+ type McpTool,
9
+ } from "./manager.js";
10
+ import { parseMergedKey } from "./remote-store.js";
11
+ import { isMcpToolAllowedForRequest } from "./visibility.js";
12
+
13
+ export interface AppMcpTool {
14
+ /** Configured MCP server id. */
15
+ serverId: string;
16
+ /** Original, unprefixed name reported by the MCP server. */
17
+ name: string;
18
+ title?: string;
19
+ description: string;
20
+ inputSchema: Record<string, unknown>;
21
+ outputSchema?: Record<string, unknown>;
22
+ annotations?: Record<string, unknown>;
23
+ _meta?: Record<string, unknown>;
24
+ }
25
+
26
+ export interface ListVisibleMcpToolsOptions {
27
+ /** Restrict the result to one configured server. */
28
+ serverId?: string;
29
+ }
30
+
31
+ export class McpAppApiError extends Error {
32
+ readonly statusCode: 401 | 403 | 503;
33
+
34
+ constructor(message: string, statusCode: 401 | 403 | 503) {
35
+ super(message);
36
+ this.name = "McpAppApiError";
37
+ this.statusCode = statusCode;
38
+ }
39
+ }
40
+
41
+ /**
42
+ * List MCP tools that the authenticated request may expose to an app.
43
+ *
44
+ * The manager owns connection state and credentials; this API deliberately
45
+ * projects only the tool contract and never returns server configuration.
46
+ */
47
+ export async function listVisibleMcpTools(
48
+ options: ListVisibleMcpToolsOptions = {},
49
+ ): Promise<AppMcpTool[]> {
50
+ const context = requireAuthenticatedRequest();
51
+ const manager = requireMcpManager();
52
+ const tools = options.serverId
53
+ ? manager.getToolsForServer(options.serverId)
54
+ : manager.getTools();
55
+
56
+ return tools
57
+ .filter((tool) => isToolVisibleToApp(tool, context))
58
+ .map(toAppMcpTool);
59
+ }
60
+
61
+ /**
62
+ * Call an app-visible MCP tool by server id and its original server-reported
63
+ * name. The prefixed manager name is built only after the tool is found in
64
+ * that server's current, request-visible tool list.
65
+ */
66
+ export async function callMcpTool(
67
+ serverId: string,
68
+ originalToolName: string,
69
+ args: Record<string, unknown> = {},
70
+ ): Promise<unknown> {
71
+ const context = requireAuthenticatedRequest();
72
+ const manager = requireMcpManager();
73
+ const tool = manager
74
+ .getToolsForServer(serverId)
75
+ .find((candidate) => candidate.originalName === originalToolName);
76
+
77
+ if (!tool || !isToolVisibleToApp(tool, context)) {
78
+ throw new McpAppApiError(
79
+ "MCP tool is not available in this request scope.",
80
+ 403,
81
+ );
82
+ }
83
+
84
+ return manager.callTool(buildMcpToolName(serverId, originalToolName), args);
85
+ }
86
+
87
+ function requireAuthenticatedRequest() {
88
+ const context = getRequestContext();
89
+ if (!context?.userEmail?.trim()) {
90
+ throw new McpAppApiError("Authentication required.", 401);
91
+ }
92
+ return context;
93
+ }
94
+
95
+ function requireMcpManager(): McpClientManager {
96
+ const manager = getGlobalMcpManager();
97
+ if (!manager) {
98
+ throw new McpAppApiError("MCP client is not configured.", 503);
99
+ }
100
+ return manager;
101
+ }
102
+
103
+ function isToolVisibleToApp(
104
+ tool: McpTool,
105
+ context: ReturnType<typeof getRequestContext>,
106
+ ): boolean {
107
+ if (!context) return false;
108
+
109
+ // `isMcpToolAllowedForRequest` intentionally permits missing identity in
110
+ // development for CLI/startup enumeration. App calls are stricter: an
111
+ // active org-scoped tool requires an active org even in development.
112
+ if (!isMcpToolAllowedForRequest(tool.name)) return false;
113
+ const merged = parseMergedKey(tool.name);
114
+ if (merged?.scope === "user" && !context.userEmail?.trim()) return false;
115
+ if (merged?.scope === "org" && !context.orgId?.trim()) return false;
116
+
117
+ try {
118
+ // A malformed visibility declaration is not safe to expose to an app.
119
+ return !isToolVisibilityModelOnly(tool.raw as any);
120
+ } catch {
121
+ return false;
122
+ }
123
+ }
124
+
125
+ function toAppMcpTool(tool: McpTool): AppMcpTool {
126
+ return {
127
+ serverId: tool.source,
128
+ name: tool.originalName,
129
+ ...(tool.title ? { title: tool.title } : {}),
130
+ description: tool.description,
131
+ inputSchema: tool.inputSchema,
132
+ ...(tool.outputSchema ? { outputSchema: tool.outputSchema } : {}),
133
+ ...(tool.annotations ? { annotations: tool.annotations } : {}),
134
+ ...(tool._meta ? { _meta: tool._meta } : {}),
135
+ };
136
+ }
@@ -24,6 +24,7 @@ export {
24
24
  export {
25
25
  listRemoteServers,
26
26
  addRemoteServer,
27
+ addOAuthRemoteServer,
27
28
  addFirstPartyRemoteServer,
28
29
  isFirstPartyRemoteEndpointTrusted,
29
30
  removeRemoteServer,
@@ -39,6 +40,19 @@ export {
39
40
  type StoredRemoteMcpServer,
40
41
  } from "./remote-store.js";
41
42
 
43
+ export {
44
+ finishMcpOAuthAuthorization,
45
+ getMcpOAuthAccessToken,
46
+ readMcpOAuthCredentials,
47
+ saveMcpOAuthCredentials,
48
+ startMcpOAuthAuthorization,
49
+ type McpOAuthCallbackResult,
50
+ type McpOAuthCredentialBundle,
51
+ type McpOAuthDiscoveryState,
52
+ type McpOAuthProviderOptions,
53
+ type McpOAuthStartResult,
54
+ } from "./oauth-client.js";
55
+
42
56
  export {
43
57
  areBuiltinMcpCapabilitiesSupported,
44
58
  BUILTIN_MCP_CAPABILITIES,
@@ -80,6 +94,13 @@ export {
80
94
  export { fetchHubServers } from "./hub-client.js";
81
95
 
82
96
  export { isMcpToolAllowedForRequest } from "./visibility.js";
97
+ export {
98
+ callMcpTool,
99
+ listVisibleMcpTools,
100
+ McpAppApiError,
101
+ type AppMcpTool,
102
+ type ListVisibleMcpToolsOptions,
103
+ } from "./app-api.js";
83
104
  import { isMcpToolAllowedForRequest } from "./visibility.js";
84
105
  export {
85
106
  classifyMcpToolCall,