@stigmer/react 0.0.84 → 0.0.86
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.
- package/demo/fixtures.d.ts +4 -0
- package/demo/fixtures.d.ts.map +1 -1
- package/demo/fixtures.js +4 -0
- package/demo/fixtures.js.map +1 -1
- package/demo/samples.d.ts +1 -0
- package/demo/samples.d.ts.map +1 -1
- package/demo/samples.js +1 -0
- package/demo/samples.js.map +1 -1
- package/execution/ArtifactPreviewModal.d.ts +78 -18
- package/execution/ArtifactPreviewModal.d.ts.map +1 -1
- package/execution/ArtifactPreviewModal.js +82 -60
- package/execution/ArtifactPreviewModal.js.map +1 -1
- package/execution/index.d.ts +2 -2
- package/execution/index.d.ts.map +1 -1
- package/execution/index.js +1 -1
- package/execution/index.js.map +1 -1
- package/index.d.ts +6 -4
- package/index.d.ts.map +1 -1
- package/index.js +4 -2
- package/index.js.map +1 -1
- package/library/ResourceListView.js +1 -1
- package/library/ResourceListView.js.map +1 -1
- package/mcp-server/McpServerConnectDialog.d.ts +51 -0
- package/mcp-server/McpServerConnectDialog.d.ts.map +1 -0
- package/mcp-server/McpServerConnectDialog.js +164 -0
- package/mcp-server/McpServerConnectDialog.js.map +1 -0
- package/mcp-server/McpServerDetailView.js +2 -2
- package/mcp-server/McpServerDetailView.js.map +1 -1
- package/mcp-server/McpServerPicker.d.ts.map +1 -1
- package/mcp-server/McpServerPicker.js +7 -1
- package/mcp-server/McpServerPicker.js.map +1 -1
- package/mcp-server/index.d.ts +2 -0
- package/mcp-server/index.d.ts.map +1 -1
- package/mcp-server/index.js +1 -0
- package/mcp-server/index.js.map +1 -1
- package/oauth-app/CreateOAuthAppForm.d.ts +41 -0
- package/oauth-app/CreateOAuthAppForm.d.ts.map +1 -0
- package/oauth-app/CreateOAuthAppForm.js +140 -0
- package/oauth-app/CreateOAuthAppForm.js.map +1 -0
- package/oauth-app/OAuthAppDetailPanel.d.ts +43 -0
- package/oauth-app/OAuthAppDetailPanel.d.ts.map +1 -0
- package/oauth-app/OAuthAppDetailPanel.js +202 -0
- package/oauth-app/OAuthAppDetailPanel.js.map +1 -0
- package/oauth-app/OAuthAppListPanel.d.ts +43 -0
- package/oauth-app/OAuthAppListPanel.d.ts.map +1 -0
- package/oauth-app/OAuthAppListPanel.js +79 -0
- package/oauth-app/OAuthAppListPanel.js.map +1 -0
- package/oauth-app/index.d.ts +15 -0
- package/oauth-app/index.d.ts.map +1 -0
- package/oauth-app/index.js +8 -0
- package/oauth-app/index.js.map +1 -0
- package/oauth-app/useCreateOAuthApp.d.ts +39 -0
- package/oauth-app/useCreateOAuthApp.d.ts.map +1 -0
- package/oauth-app/useCreateOAuthApp.js +50 -0
- package/oauth-app/useCreateOAuthApp.js.map +1 -0
- package/oauth-app/useDeleteOAuthApp.d.ts +31 -0
- package/oauth-app/useDeleteOAuthApp.d.ts.map +1 -0
- package/oauth-app/useDeleteOAuthApp.js +43 -0
- package/oauth-app/useDeleteOAuthApp.js.map +1 -0
- package/oauth-app/useOAuthAppList.d.ts +32 -0
- package/oauth-app/useOAuthAppList.d.ts.map +1 -0
- package/oauth-app/useOAuthAppList.js +61 -0
- package/oauth-app/useOAuthAppList.js.map +1 -0
- package/oauth-app/useUpdateOAuthApp.d.ts +38 -0
- package/oauth-app/useUpdateOAuthApp.d.ts.map +1 -0
- package/oauth-app/useUpdateOAuthApp.js +49 -0
- package/oauth-app/useUpdateOAuthApp.js.map +1 -0
- package/package.json +4 -4
- package/src/demo/fixtures.ts +8 -0
- package/src/demo/samples.ts +2 -0
- package/src/execution/ArtifactPreviewModal.tsx +206 -128
- package/src/execution/index.ts +2 -2
- package/src/index.ts +24 -0
- package/src/library/ResourceListView.tsx +8 -8
- package/src/mcp-server/McpServerConnectDialog.tsx +527 -0
- package/src/mcp-server/McpServerDetailView.tsx +2 -1
- package/src/mcp-server/McpServerPicker.tsx +8 -1
- package/src/mcp-server/index.ts +3 -0
- package/src/oauth-app/CreateOAuthAppForm.tsx +449 -0
- package/src/oauth-app/OAuthAppDetailPanel.tsx +671 -0
- package/src/oauth-app/OAuthAppListPanel.tsx +237 -0
- package/src/oauth-app/index.ts +14 -0
- package/src/oauth-app/useCreateOAuthApp.ts +70 -0
- package/src/oauth-app/useDeleteOAuthApp.ts +62 -0
- package/src/oauth-app/useOAuthAppList.ts +84 -0
- package/src/oauth-app/useUpdateOAuthApp.ts +69 -0
- package/styles.css +1 -1
package/src/execution/index.ts
CHANGED
|
@@ -114,8 +114,8 @@ export type { ArtifactCardProps } from "./ArtifactCard";
|
|
|
114
114
|
export { ArtifactContentRenderer } from "./ArtifactContentRenderer";
|
|
115
115
|
export type { ArtifactContentRendererProps } from "./ArtifactContentRenderer";
|
|
116
116
|
|
|
117
|
-
export { ArtifactPreviewModal } from "./ArtifactPreviewModal";
|
|
118
|
-
export type { ArtifactPreviewModalProps } from "./ArtifactPreviewModal";
|
|
117
|
+
export { ArtifactPreviewContent, ArtifactPreviewModal } from "./ArtifactPreviewModal";
|
|
118
|
+
export type { ArtifactPreviewContentProps, ArtifactPreviewModalProps } from "./ArtifactPreviewModal";
|
|
119
119
|
|
|
120
120
|
export { ArtifactsWidget } from "./ArtifactsWidget";
|
|
121
121
|
export type { ArtifactsWidgetProps } from "./ArtifactsWidget";
|
package/src/index.ts
CHANGED
|
@@ -112,6 +112,7 @@ export {
|
|
|
112
112
|
ApprovalCard,
|
|
113
113
|
ArtifactCard,
|
|
114
114
|
ArtifactContentRenderer,
|
|
115
|
+
ArtifactPreviewContent,
|
|
115
116
|
ArtifactPreviewModal,
|
|
116
117
|
ArtifactsWidget,
|
|
117
118
|
WriteBacksWidget,
|
|
@@ -162,6 +163,7 @@ export type {
|
|
|
162
163
|
ArtifactCardProps,
|
|
163
164
|
ArtifactContentRendererProps,
|
|
164
165
|
ArtifactRenderMode,
|
|
166
|
+
ArtifactPreviewContentProps,
|
|
165
167
|
ArtifactPreviewModalProps,
|
|
166
168
|
ArtifactsWidgetProps,
|
|
167
169
|
WriteBacksWidgetProps,
|
|
@@ -227,6 +229,7 @@ export {
|
|
|
227
229
|
McpServerPicker,
|
|
228
230
|
McpServerConfigPanel,
|
|
229
231
|
McpServerDetailView,
|
|
232
|
+
McpServerConnectDialog,
|
|
230
233
|
McpToolSelector,
|
|
231
234
|
toServerKey,
|
|
232
235
|
} from "./mcp-server";
|
|
@@ -249,6 +252,7 @@ export type {
|
|
|
249
252
|
McpServerCredentialsProps,
|
|
250
253
|
McpServerOAuthSignInProps,
|
|
251
254
|
McpServerDetailViewProps,
|
|
255
|
+
McpServerConnectDialogProps,
|
|
252
256
|
CapabilityTab,
|
|
253
257
|
McpToolSelectorProps,
|
|
254
258
|
UseMcpServerConnectReturn,
|
|
@@ -446,6 +450,26 @@ export type {
|
|
|
446
450
|
ApiKeyCreatedAlertProps,
|
|
447
451
|
} from "./api-key";
|
|
448
452
|
|
|
453
|
+
// OAuth App — data hooks, mutation hooks, and styled components for OAuth app management
|
|
454
|
+
export {
|
|
455
|
+
useOAuthAppList,
|
|
456
|
+
useCreateOAuthApp,
|
|
457
|
+
useUpdateOAuthApp,
|
|
458
|
+
useDeleteOAuthApp,
|
|
459
|
+
OAuthAppListPanel,
|
|
460
|
+
CreateOAuthAppForm,
|
|
461
|
+
OAuthAppDetailPanel,
|
|
462
|
+
} from "./oauth-app";
|
|
463
|
+
export type {
|
|
464
|
+
UseOAuthAppListReturn,
|
|
465
|
+
UseCreateOAuthAppReturn,
|
|
466
|
+
UseUpdateOAuthAppReturn,
|
|
467
|
+
UseDeleteOAuthAppReturn,
|
|
468
|
+
OAuthAppListPanelProps,
|
|
469
|
+
CreateOAuthAppFormProps,
|
|
470
|
+
OAuthAppDetailPanelProps,
|
|
471
|
+
} from "./oauth-app";
|
|
472
|
+
|
|
449
473
|
// Identity Provider — data hooks, mutation hooks, styled components, presets, and guided wizard for IdP management and SSO discovery
|
|
450
474
|
export {
|
|
451
475
|
useIdentityProviderList,
|
|
@@ -537,14 +537,9 @@ function DefaultResourceCard({
|
|
|
537
537
|
<div className="flex items-start gap-3">
|
|
538
538
|
<ResourceIcon kind={item.kind} iconUrl={item.iconUrl} />
|
|
539
539
|
<div className="min-w-0 flex-1">
|
|
540
|
-
<
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
</span>
|
|
544
|
-
{item.visibility === ApiResourceVisibility.visibility_public && (
|
|
545
|
-
<VisibilityBadge />
|
|
546
|
-
)}
|
|
547
|
-
</div>
|
|
540
|
+
<span className="line-clamp-2 text-sm font-semibold leading-snug text-foreground">
|
|
541
|
+
{displayName}
|
|
542
|
+
</span>
|
|
548
543
|
<span className="mt-0.5 block text-xs text-muted-foreground">
|
|
549
544
|
{item.org}
|
|
550
545
|
</span>
|
|
@@ -556,6 +551,11 @@ function DefaultResourceCard({
|
|
|
556
551
|
{item.description}
|
|
557
552
|
</p>
|
|
558
553
|
)}
|
|
554
|
+
{item.visibility === ApiResourceVisibility.visibility_public && (
|
|
555
|
+
<div className="mt-auto">
|
|
556
|
+
<VisibilityBadge />
|
|
557
|
+
</div>
|
|
558
|
+
)}
|
|
559
559
|
</div>
|
|
560
560
|
);
|
|
561
561
|
}
|
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
4
|
+
import { cn } from "@stigmer/theme";
|
|
5
|
+
import type { EnvVarInput } from "@stigmer/sdk";
|
|
6
|
+
import { useMcpServer } from "./useMcpServer";
|
|
7
|
+
import { useMcpServerCredentials } from "./useMcpServerCredentials";
|
|
8
|
+
import { useMcpServerOAuthConnect } from "./useMcpServerOAuthConnect";
|
|
9
|
+
import type { OAuthConnectPhase } from "./useMcpServerOAuthConnect";
|
|
10
|
+
import { useMcpServerConnect } from "./useMcpServerConnect";
|
|
11
|
+
import { useDisconnectOAuth } from "./useDisconnectOAuth";
|
|
12
|
+
import { EnvVarForm } from "../environment/EnvVarForm";
|
|
13
|
+
import { ErrorMessage } from "../error/ErrorMessage";
|
|
14
|
+
|
|
15
|
+
/** Props for {@link McpServerConnectDialog}. */
|
|
16
|
+
export interface McpServerConnectDialogProps {
|
|
17
|
+
/** Organization slug that owns the MCP server. */
|
|
18
|
+
readonly org: string;
|
|
19
|
+
/** MCP server slug. */
|
|
20
|
+
readonly slug: string;
|
|
21
|
+
/**
|
|
22
|
+
* The authenticated user's active organization slug.
|
|
23
|
+
* Used for credential storage — tokens are stored in the user's
|
|
24
|
+
* personal environment within this org.
|
|
25
|
+
* Falls back to `org` when omitted.
|
|
26
|
+
*/
|
|
27
|
+
readonly activeOrg?: string;
|
|
28
|
+
/** Whether the dialog is open. */
|
|
29
|
+
readonly open: boolean;
|
|
30
|
+
/** Called when the dialog should close (backdrop click, cancel, or success). */
|
|
31
|
+
readonly onClose: () => void;
|
|
32
|
+
/** Called after a successful connect with the server name. */
|
|
33
|
+
readonly onConnected?: (serverName: string) => void;
|
|
34
|
+
/** Additional CSS classes for the dialog element. */
|
|
35
|
+
readonly className?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type DialogPhase = "credentials" | "connecting" | "success" | "error";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Modal dialog for connecting to an MCP server.
|
|
42
|
+
*
|
|
43
|
+
* Fetches the server, determines whether credentials or OAuth are
|
|
44
|
+
* needed, and walks the user through the connect flow — all without
|
|
45
|
+
* navigating away from the current page.
|
|
46
|
+
*
|
|
47
|
+
* Uses the native `<dialog>` element for accessibility (focus trap,
|
|
48
|
+
* Escape to close, backdrop click).
|
|
49
|
+
*
|
|
50
|
+
* Built on existing hooks: {@link useMcpServer},
|
|
51
|
+
* {@link useMcpServerCredentials}, {@link useMcpServerOAuthConnect},
|
|
52
|
+
* and {@link useMcpServerConnect}.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```tsx
|
|
56
|
+
* const [connectTarget, setConnectTarget] = useState<{ org: string; slug: string } | null>(null);
|
|
57
|
+
*
|
|
58
|
+
* <McpServerConnectDialog
|
|
59
|
+
* org={connectTarget?.org ?? ""}
|
|
60
|
+
* slug={connectTarget?.slug ?? ""}
|
|
61
|
+
* open={connectTarget !== null}
|
|
62
|
+
* onClose={() => setConnectTarget(null)}
|
|
63
|
+
* onConnected={(name) => toast(`Connected to ${name}`)}
|
|
64
|
+
* />
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export function McpServerConnectDialog({
|
|
68
|
+
org,
|
|
69
|
+
slug,
|
|
70
|
+
activeOrg,
|
|
71
|
+
open,
|
|
72
|
+
onClose,
|
|
73
|
+
onConnected,
|
|
74
|
+
className,
|
|
75
|
+
}: McpServerConnectDialogProps) {
|
|
76
|
+
const dialogRef = useRef<HTMLDialogElement>(null);
|
|
77
|
+
const resolvedOrg = activeOrg || org;
|
|
78
|
+
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
const dialog = dialogRef.current;
|
|
81
|
+
if (!dialog) return;
|
|
82
|
+
|
|
83
|
+
if (open && !dialog.open) {
|
|
84
|
+
dialog.showModal();
|
|
85
|
+
} else if (!open && dialog.open) {
|
|
86
|
+
dialog.close();
|
|
87
|
+
}
|
|
88
|
+
}, [open]);
|
|
89
|
+
|
|
90
|
+
const handleDialogClose = useCallback(() => {
|
|
91
|
+
onClose();
|
|
92
|
+
}, [onClose]);
|
|
93
|
+
|
|
94
|
+
const handleBackdropClick = useCallback(
|
|
95
|
+
(e: React.MouseEvent<HTMLDialogElement>) => {
|
|
96
|
+
if (e.target === dialogRef.current) {
|
|
97
|
+
onClose();
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
[onClose],
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
if (!open) return null;
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<dialog
|
|
107
|
+
ref={dialogRef}
|
|
108
|
+
onClose={handleDialogClose}
|
|
109
|
+
onClick={handleBackdropClick}
|
|
110
|
+
className={cn(
|
|
111
|
+
"m-auto max-h-[85vh] w-full max-w-md overflow-visible rounded-lg border border-border bg-card p-0 text-foreground shadow-lg",
|
|
112
|
+
"backdrop:bg-black/50",
|
|
113
|
+
className,
|
|
114
|
+
)}
|
|
115
|
+
>
|
|
116
|
+
<div
|
|
117
|
+
className="flex max-h-[85vh] flex-col overflow-y-auto p-6"
|
|
118
|
+
onClick={(e) => e.stopPropagation()}
|
|
119
|
+
>
|
|
120
|
+
<ConnectDialogContent
|
|
121
|
+
org={org}
|
|
122
|
+
slug={slug}
|
|
123
|
+
activeOrg={resolvedOrg}
|
|
124
|
+
onClose={onClose}
|
|
125
|
+
onConnected={onConnected}
|
|
126
|
+
/>
|
|
127
|
+
</div>
|
|
128
|
+
</dialog>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function ConnectDialogContent({
|
|
133
|
+
org,
|
|
134
|
+
slug,
|
|
135
|
+
activeOrg,
|
|
136
|
+
onClose,
|
|
137
|
+
onConnected,
|
|
138
|
+
}: {
|
|
139
|
+
readonly org: string;
|
|
140
|
+
readonly slug: string;
|
|
141
|
+
readonly activeOrg: string;
|
|
142
|
+
readonly onClose: () => void;
|
|
143
|
+
readonly onConnected?: (serverName: string) => void;
|
|
144
|
+
}) {
|
|
145
|
+
const { mcpServer, isLoading: isServerLoading, error: serverError } = useMcpServer(org, slug);
|
|
146
|
+
const creds = useMcpServerCredentials(activeOrg, mcpServer);
|
|
147
|
+
const { connect, isConnecting, error: connectError, clearError: clearConnectError } = useMcpServerConnect();
|
|
148
|
+
const oauth = useMcpServerOAuthConnect();
|
|
149
|
+
const disconnect = useDisconnectOAuth();
|
|
150
|
+
|
|
151
|
+
const [phase, setPhase] = useState<DialogPhase>("credentials");
|
|
152
|
+
|
|
153
|
+
const serverName = mcpServer?.metadata?.name ?? slug;
|
|
154
|
+
const serverId = mcpServer?.metadata?.id ?? "";
|
|
155
|
+
const declaredEnvKeys = Object.keys(mcpServer?.spec?.env ?? {});
|
|
156
|
+
|
|
157
|
+
const handleConnect = useCallback(async () => {
|
|
158
|
+
if (!serverId) return;
|
|
159
|
+
|
|
160
|
+
setPhase("connecting");
|
|
161
|
+
try {
|
|
162
|
+
await connect(serverId, activeOrg, undefined, declaredEnvKeys);
|
|
163
|
+
setPhase("success");
|
|
164
|
+
onConnected?.(serverName);
|
|
165
|
+
} catch {
|
|
166
|
+
setPhase("error");
|
|
167
|
+
}
|
|
168
|
+
}, [serverId, activeOrg, declaredEnvKeys, connect, onConnected, serverName]);
|
|
169
|
+
|
|
170
|
+
const handleCredentialSubmit = useCallback(
|
|
171
|
+
async (values: Record<string, EnvVarInput>) => {
|
|
172
|
+
await creds.saveCredentials(values);
|
|
173
|
+
creds.refetch();
|
|
174
|
+
},
|
|
175
|
+
[creds],
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
const handleOAuthSignIn = useCallback(() => {
|
|
179
|
+
if (!serverId) return;
|
|
180
|
+
oauth.startOAuth(serverId, activeOrg, declaredEnvKeys).then(
|
|
181
|
+
() => {
|
|
182
|
+
creds.refetch();
|
|
183
|
+
setPhase("success");
|
|
184
|
+
onConnected?.(serverName);
|
|
185
|
+
},
|
|
186
|
+
() => {
|
|
187
|
+
setPhase("error");
|
|
188
|
+
},
|
|
189
|
+
);
|
|
190
|
+
}, [serverId, activeOrg, declaredEnvKeys, oauth, creds, onConnected, serverName]);
|
|
191
|
+
|
|
192
|
+
// Auto-trigger connect when credentials become ready (manual-only servers)
|
|
193
|
+
useEffect(() => {
|
|
194
|
+
if (
|
|
195
|
+
phase === "credentials" &&
|
|
196
|
+
creds.isReady &&
|
|
197
|
+
!creds.isLoading &&
|
|
198
|
+
mcpServer &&
|
|
199
|
+
creds.authMode === "manual"
|
|
200
|
+
) {
|
|
201
|
+
// Don't auto-connect; let user click the button
|
|
202
|
+
}
|
|
203
|
+
}, [phase, creds.isReady, creds.isLoading, mcpServer, creds.authMode]);
|
|
204
|
+
|
|
205
|
+
if (isServerLoading || creds.isLoading) {
|
|
206
|
+
return (
|
|
207
|
+
<>
|
|
208
|
+
<DialogHeader title="Connect MCP Server" onClose={onClose} />
|
|
209
|
+
<div className="flex flex-col items-center gap-3 py-8">
|
|
210
|
+
<LoadingSpinner />
|
|
211
|
+
<p className="text-sm text-muted-foreground">Loading server details...</p>
|
|
212
|
+
</div>
|
|
213
|
+
</>
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (serverError) {
|
|
218
|
+
return (
|
|
219
|
+
<>
|
|
220
|
+
<DialogHeader title="Connect MCP Server" onClose={onClose} />
|
|
221
|
+
<div className="py-4">
|
|
222
|
+
<ErrorMessage error={serverError} />
|
|
223
|
+
</div>
|
|
224
|
+
</>
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (!mcpServer) {
|
|
229
|
+
return (
|
|
230
|
+
<>
|
|
231
|
+
<DialogHeader title="Connect MCP Server" onClose={onClose} />
|
|
232
|
+
<p className="py-4 text-sm text-muted-foreground">MCP server not found.</p>
|
|
233
|
+
</>
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (phase === "success") {
|
|
238
|
+
return (
|
|
239
|
+
<>
|
|
240
|
+
<DialogHeader title="Connected" onClose={onClose} />
|
|
241
|
+
<div className="flex flex-col items-center gap-3 py-6">
|
|
242
|
+
<SuccessIcon />
|
|
243
|
+
<p className="text-sm font-medium text-foreground">
|
|
244
|
+
Successfully connected to {serverName}
|
|
245
|
+
</p>
|
|
246
|
+
<p className="text-xs text-muted-foreground">
|
|
247
|
+
Tools and capabilities have been discovered.
|
|
248
|
+
</p>
|
|
249
|
+
<button
|
|
250
|
+
type="button"
|
|
251
|
+
onClick={onClose}
|
|
252
|
+
className={cn(
|
|
253
|
+
"mt-2 inline-flex items-center rounded-md px-4 py-2 text-sm font-medium",
|
|
254
|
+
"bg-primary text-primary-foreground",
|
|
255
|
+
"hover:bg-primary/90",
|
|
256
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
257
|
+
)}
|
|
258
|
+
>
|
|
259
|
+
Done
|
|
260
|
+
</button>
|
|
261
|
+
</div>
|
|
262
|
+
</>
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const isConnectingPhase = phase === "connecting" || isConnecting || oauth.isInProgress;
|
|
267
|
+
const activeError = connectError ?? oauth.error;
|
|
268
|
+
|
|
269
|
+
return (
|
|
270
|
+
<>
|
|
271
|
+
<DialogHeader title={serverName} onClose={onClose} />
|
|
272
|
+
|
|
273
|
+
{mcpServer.spec?.description && (
|
|
274
|
+
<p className="mb-4 text-xs leading-relaxed text-muted-foreground">
|
|
275
|
+
{mcpServer.spec.description}
|
|
276
|
+
</p>
|
|
277
|
+
)}
|
|
278
|
+
|
|
279
|
+
{activeError && (
|
|
280
|
+
<div className="mb-4">
|
|
281
|
+
<ErrorMessage
|
|
282
|
+
error={activeError}
|
|
283
|
+
retry={() => {
|
|
284
|
+
clearConnectError();
|
|
285
|
+
oauth.clearError();
|
|
286
|
+
setPhase("credentials");
|
|
287
|
+
}}
|
|
288
|
+
/>
|
|
289
|
+
</div>
|
|
290
|
+
)}
|
|
291
|
+
|
|
292
|
+
{creds.authMode === "oauth" && !creds.manualOverride && (
|
|
293
|
+
<OAuthSection
|
|
294
|
+
isConnected={creds.isOAuthConnected}
|
|
295
|
+
phase={oauth.phase}
|
|
296
|
+
onSignIn={handleOAuthSignIn}
|
|
297
|
+
isVendorApprovalBlocked={creds.isVendorApprovalBlocked}
|
|
298
|
+
onSwitchToManual={() => creds.setManualOverride(true)}
|
|
299
|
+
disabled={isConnectingPhase}
|
|
300
|
+
/>
|
|
301
|
+
)}
|
|
302
|
+
|
|
303
|
+
{creds.missingVariables.length > 0 && (
|
|
304
|
+
<div className="mt-2">
|
|
305
|
+
<EnvVarForm
|
|
306
|
+
variables={creds.missingVariables}
|
|
307
|
+
onSubmit={(values) => handleCredentialSubmit(values)}
|
|
308
|
+
isSubmitting={creds.isSaving}
|
|
309
|
+
disabled={isConnectingPhase}
|
|
310
|
+
hideSaveToggle
|
|
311
|
+
/>
|
|
312
|
+
</div>
|
|
313
|
+
)}
|
|
314
|
+
|
|
315
|
+
{creds.authMode === "oauth" && creds.manualOverride && (
|
|
316
|
+
<button
|
|
317
|
+
type="button"
|
|
318
|
+
onClick={() => creds.setManualOverride(false)}
|
|
319
|
+
disabled={isConnectingPhase}
|
|
320
|
+
className="mt-2 text-xs text-muted-foreground underline hover:text-foreground"
|
|
321
|
+
>
|
|
322
|
+
Sign in with OAuth instead
|
|
323
|
+
</button>
|
|
324
|
+
)}
|
|
325
|
+
|
|
326
|
+
{creds.isReady && creds.authMode === "manual" && (
|
|
327
|
+
<button
|
|
328
|
+
type="button"
|
|
329
|
+
onClick={handleConnect}
|
|
330
|
+
disabled={isConnectingPhase}
|
|
331
|
+
className={cn(
|
|
332
|
+
"mt-4 inline-flex w-full items-center justify-center gap-2 rounded-md px-4 py-2 text-sm font-medium",
|
|
333
|
+
"bg-primary text-primary-foreground",
|
|
334
|
+
"hover:bg-primary/90",
|
|
335
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
336
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
337
|
+
)}
|
|
338
|
+
>
|
|
339
|
+
{isConnectingPhase && <LoadingSpinner size="sm" />}
|
|
340
|
+
{isConnectingPhase ? "Connecting..." : "Connect"}
|
|
341
|
+
</button>
|
|
342
|
+
)}
|
|
343
|
+
|
|
344
|
+
{creds.isReady && creds.authMode === "oauth" && creds.isOAuthConnected && (
|
|
345
|
+
<button
|
|
346
|
+
type="button"
|
|
347
|
+
onClick={handleConnect}
|
|
348
|
+
disabled={isConnectingPhase}
|
|
349
|
+
className={cn(
|
|
350
|
+
"mt-4 inline-flex w-full items-center justify-center gap-2 rounded-md px-4 py-2 text-sm font-medium",
|
|
351
|
+
"bg-primary text-primary-foreground",
|
|
352
|
+
"hover:bg-primary/90",
|
|
353
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
354
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
355
|
+
)}
|
|
356
|
+
>
|
|
357
|
+
{isConnectingPhase && <LoadingSpinner size="sm" />}
|
|
358
|
+
{isConnectingPhase ? "Discovering tools..." : "Discover Tools"}
|
|
359
|
+
</button>
|
|
360
|
+
)}
|
|
361
|
+
|
|
362
|
+
{!creds.isReady &&
|
|
363
|
+
creds.missingVariables.length === 0 &&
|
|
364
|
+
creds.authMode === "manual" && (
|
|
365
|
+
<p className="mt-4 text-center text-xs text-muted-foreground">
|
|
366
|
+
No credentials required — this server is ready to connect.
|
|
367
|
+
</p>
|
|
368
|
+
)}
|
|
369
|
+
</>
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// ---------------------------------------------------------------------------
|
|
374
|
+
// Sub-components
|
|
375
|
+
// ---------------------------------------------------------------------------
|
|
376
|
+
|
|
377
|
+
function DialogHeader({
|
|
378
|
+
title,
|
|
379
|
+
onClose,
|
|
380
|
+
}: {
|
|
381
|
+
readonly title: string;
|
|
382
|
+
readonly onClose: () => void;
|
|
383
|
+
}) {
|
|
384
|
+
return (
|
|
385
|
+
<div className="mb-4 flex items-start justify-between gap-3">
|
|
386
|
+
<h2 className="text-base font-semibold text-foreground">{title}</h2>
|
|
387
|
+
<button
|
|
388
|
+
type="button"
|
|
389
|
+
onClick={onClose}
|
|
390
|
+
aria-label="Close"
|
|
391
|
+
className="inline-flex size-7 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
392
|
+
>
|
|
393
|
+
<CloseIcon />
|
|
394
|
+
</button>
|
|
395
|
+
</div>
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function OAuthSection({
|
|
400
|
+
isConnected,
|
|
401
|
+
phase,
|
|
402
|
+
onSignIn,
|
|
403
|
+
isVendorApprovalBlocked,
|
|
404
|
+
onSwitchToManual,
|
|
405
|
+
disabled,
|
|
406
|
+
}: {
|
|
407
|
+
readonly isConnected: boolean;
|
|
408
|
+
readonly phase: OAuthConnectPhase;
|
|
409
|
+
readonly onSignIn: () => void;
|
|
410
|
+
readonly isVendorApprovalBlocked: boolean;
|
|
411
|
+
readonly onSwitchToManual: () => void;
|
|
412
|
+
readonly disabled?: boolean;
|
|
413
|
+
}) {
|
|
414
|
+
if (isConnected) {
|
|
415
|
+
return (
|
|
416
|
+
<div className="flex items-center gap-2 rounded-md border border-border bg-muted/30 px-3 py-2">
|
|
417
|
+
<span className="size-2 shrink-0 rounded-full bg-green-500" />
|
|
418
|
+
<span className="text-sm text-foreground">OAuth connected</span>
|
|
419
|
+
</div>
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const isInProgress = phase !== "idle" && phase !== "done";
|
|
424
|
+
const phaseLabel: Record<string, string> = {
|
|
425
|
+
initiating: "Starting OAuth...",
|
|
426
|
+
"awaiting-callback": "Waiting for authorization...",
|
|
427
|
+
completing: "Completing OAuth...",
|
|
428
|
+
connecting: "Discovering tools...",
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
return (
|
|
432
|
+
<div className="flex flex-col gap-2">
|
|
433
|
+
<button
|
|
434
|
+
type="button"
|
|
435
|
+
onClick={onSignIn}
|
|
436
|
+
disabled={disabled || isInProgress || isVendorApprovalBlocked}
|
|
437
|
+
className={cn(
|
|
438
|
+
"inline-flex w-full items-center justify-center gap-2 rounded-md px-4 py-2 text-sm font-medium",
|
|
439
|
+
"bg-primary text-primary-foreground",
|
|
440
|
+
"hover:bg-primary/90",
|
|
441
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
442
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
443
|
+
)}
|
|
444
|
+
>
|
|
445
|
+
{isInProgress && <LoadingSpinner size="sm" />}
|
|
446
|
+
{isInProgress ? (phaseLabel[phase] ?? "Connecting...") : "Sign in with OAuth"}
|
|
447
|
+
</button>
|
|
448
|
+
{isVendorApprovalBlocked && (
|
|
449
|
+
<p className="text-xs text-muted-foreground">
|
|
450
|
+
OAuth sign-in is pending vendor approval. You can enter your token manually instead.
|
|
451
|
+
</p>
|
|
452
|
+
)}
|
|
453
|
+
<button
|
|
454
|
+
type="button"
|
|
455
|
+
onClick={onSwitchToManual}
|
|
456
|
+
disabled={disabled || isInProgress}
|
|
457
|
+
className="text-xs text-muted-foreground underline hover:text-foreground"
|
|
458
|
+
>
|
|
459
|
+
Enter token manually
|
|
460
|
+
</button>
|
|
461
|
+
</div>
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function LoadingSpinner({ size = "md" }: { readonly size?: "sm" | "md" }) {
|
|
466
|
+
const cls = size === "sm" ? "size-3.5" : "size-5";
|
|
467
|
+
return (
|
|
468
|
+
<svg
|
|
469
|
+
className={cn(cls, "animate-spin text-current")}
|
|
470
|
+
viewBox="0 0 24 24"
|
|
471
|
+
fill="none"
|
|
472
|
+
aria-hidden="true"
|
|
473
|
+
>
|
|
474
|
+
<circle
|
|
475
|
+
cx="12"
|
|
476
|
+
cy="12"
|
|
477
|
+
r="10"
|
|
478
|
+
stroke="currentColor"
|
|
479
|
+
strokeWidth="3"
|
|
480
|
+
strokeLinecap="round"
|
|
481
|
+
className="opacity-25"
|
|
482
|
+
/>
|
|
483
|
+
<path
|
|
484
|
+
d="M12 2a10 10 0 0 1 10 10"
|
|
485
|
+
stroke="currentColor"
|
|
486
|
+
strokeWidth="3"
|
|
487
|
+
strokeLinecap="round"
|
|
488
|
+
className="opacity-75"
|
|
489
|
+
/>
|
|
490
|
+
</svg>
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function SuccessIcon() {
|
|
495
|
+
return (
|
|
496
|
+
<svg
|
|
497
|
+
className="size-10 text-green-500"
|
|
498
|
+
viewBox="0 0 24 24"
|
|
499
|
+
fill="none"
|
|
500
|
+
stroke="currentColor"
|
|
501
|
+
strokeWidth="2"
|
|
502
|
+
strokeLinecap="round"
|
|
503
|
+
strokeLinejoin="round"
|
|
504
|
+
aria-hidden="true"
|
|
505
|
+
>
|
|
506
|
+
<circle cx="12" cy="12" r="10" />
|
|
507
|
+
<path d="m9 12 2 2 4-4" />
|
|
508
|
+
</svg>
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function CloseIcon() {
|
|
513
|
+
return (
|
|
514
|
+
<svg
|
|
515
|
+
className="size-4"
|
|
516
|
+
viewBox="0 0 16 16"
|
|
517
|
+
fill="none"
|
|
518
|
+
stroke="currentColor"
|
|
519
|
+
strokeWidth="1.5"
|
|
520
|
+
strokeLinecap="round"
|
|
521
|
+
strokeLinejoin="round"
|
|
522
|
+
aria-hidden="true"
|
|
523
|
+
>
|
|
524
|
+
<path d="m4 4 8 8M12 4l-8 8" />
|
|
525
|
+
</svg>
|
|
526
|
+
);
|
|
527
|
+
}
|
|
@@ -409,7 +409,7 @@ export function McpServerDetailView({
|
|
|
409
409
|
ref={byoaDialogRef}
|
|
410
410
|
onCancel={handleByoaDialogCancel}
|
|
411
411
|
className={cn(
|
|
412
|
-
"w-full max-w-md rounded-lg border border-border bg-background p-6 shadow-lg",
|
|
412
|
+
"m-auto w-full max-w-md rounded-lg border border-border bg-background p-6 shadow-lg",
|
|
413
413
|
"backdrop:bg-black/50",
|
|
414
414
|
)}
|
|
415
415
|
>
|
|
@@ -868,6 +868,7 @@ function ConnectBar({
|
|
|
868
868
|
<button
|
|
869
869
|
type="button"
|
|
870
870
|
onClick={onBringOwnApp}
|
|
871
|
+
data-cursor-target="byoa-cta-button"
|
|
871
872
|
className="mt-1.5 inline-flex items-center gap-1 rounded-md bg-amber-600 px-2.5 py-1 text-[11px] font-medium text-white hover:bg-amber-700 dark:bg-amber-500 dark:text-amber-950 dark:hover:bg-amber-400"
|
|
872
873
|
>
|
|
873
874
|
Use your own OAuth app
|
|
@@ -13,6 +13,7 @@ import { cn } from "@stigmer/theme";
|
|
|
13
13
|
import type { EnvVarInput, McpServerUsageInput, ResourceRef } from "@stigmer/sdk";
|
|
14
14
|
import type { SearchResult } from "@stigmer/protos/ai/stigmer/search/v1/io_pb";
|
|
15
15
|
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
16
|
+
import { VendorApprovalStatus } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/spec_pb";
|
|
16
17
|
import type { EnvVarFormSubmitOptions } from "../environment/EnvVarForm";
|
|
17
18
|
import { useMcpServerSearch } from "./useMcpServerSearch";
|
|
18
19
|
import { useScrollShadows } from "../internal/useScrollShadows";
|
|
@@ -418,7 +419,12 @@ export function McpServerPicker({
|
|
|
418
419
|
|
|
419
420
|
const oauthStatus = entry.mcpServer.status?.oauthStatus;
|
|
420
421
|
const isVendorApprovalPending =
|
|
421
|
-
hasOAuth &&
|
|
422
|
+
hasOAuth &&
|
|
423
|
+
oauthStatus?.vendorApprovalStatus === VendorApprovalStatus.PENDING;
|
|
424
|
+
const isVendorApprovalBlocked =
|
|
425
|
+
hasOAuth &&
|
|
426
|
+
(oauthStatus?.vendorApprovalStatus === VendorApprovalStatus.PENDING ||
|
|
427
|
+
oauthStatus?.vendorApprovalStatus === VendorApprovalStatus.REJECTED);
|
|
422
428
|
|
|
423
429
|
const oauthSignInProps =
|
|
424
430
|
hasOAuth && !isManualOverride
|
|
@@ -440,6 +446,7 @@ export function McpServerPicker({
|
|
|
440
446
|
error: oauth.error,
|
|
441
447
|
onClearError: oauth.clearError,
|
|
442
448
|
isVendorApprovalPending,
|
|
449
|
+
isVendorApprovalBlocked,
|
|
443
450
|
vendorApprovalDocsUrl: oauthStatus?.vendorApprovalDocsUrl || null,
|
|
444
451
|
}
|
|
445
452
|
: undefined;
|
package/src/mcp-server/index.ts
CHANGED