@stigmer/react 0.0.76 → 0.0.78

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 (40) hide show
  1. package/index.d.ts +2 -2
  2. package/index.d.ts.map +1 -1
  3. package/index.js +2 -2
  4. package/index.js.map +1 -1
  5. package/mcp-server/McpServerConfigPanel.d.ts +28 -1
  6. package/mcp-server/McpServerConfigPanel.d.ts.map +1 -1
  7. package/mcp-server/McpServerConfigPanel.js +23 -2
  8. package/mcp-server/McpServerConfigPanel.js.map +1 -1
  9. package/mcp-server/McpServerDetailView.d.ts.map +1 -1
  10. package/mcp-server/McpServerDetailView.js +96 -9
  11. package/mcp-server/McpServerDetailView.js.map +1 -1
  12. package/mcp-server/McpServerPicker.d.ts.map +1 -1
  13. package/mcp-server/McpServerPicker.js +34 -2
  14. package/mcp-server/McpServerPicker.js.map +1 -1
  15. package/mcp-server/OAuthCallbackHandler.d.ts +52 -0
  16. package/mcp-server/OAuthCallbackHandler.d.ts.map +1 -0
  17. package/mcp-server/OAuthCallbackHandler.js +98 -0
  18. package/mcp-server/OAuthCallbackHandler.js.map +1 -0
  19. package/mcp-server/index.d.ts +6 -2
  20. package/mcp-server/index.d.ts.map +1 -1
  21. package/mcp-server/index.js +2 -0
  22. package/mcp-server/index.js.map +1 -1
  23. package/mcp-server/useMcpServerCredentials.d.ts +59 -7
  24. package/mcp-server/useMcpServerCredentials.d.ts.map +1 -1
  25. package/mcp-server/useMcpServerCredentials.js +37 -10
  26. package/mcp-server/useMcpServerCredentials.js.map +1 -1
  27. package/mcp-server/useMcpServerOAuthConnect.d.ts +81 -0
  28. package/mcp-server/useMcpServerOAuthConnect.d.ts.map +1 -0
  29. package/mcp-server/useMcpServerOAuthConnect.js +187 -0
  30. package/mcp-server/useMcpServerOAuthConnect.js.map +1 -0
  31. package/package.json +4 -4
  32. package/src/index.ts +9 -1
  33. package/src/mcp-server/McpServerConfigPanel.tsx +153 -3
  34. package/src/mcp-server/McpServerDetailView.tsx +283 -97
  35. package/src/mcp-server/McpServerPicker.tsx +40 -2
  36. package/src/mcp-server/OAuthCallbackHandler.tsx +237 -0
  37. package/src/mcp-server/index.ts +17 -1
  38. package/src/mcp-server/useMcpServerCredentials.ts +86 -13
  39. package/src/mcp-server/useMcpServerOAuthConnect.ts +298 -0
  40. package/styles.css +1 -1
@@ -0,0 +1,187 @@
1
+ "use client";
2
+ import { useCallback, useEffect, useRef, useState } from "react";
3
+ import { create } from "@bufbuild/protobuf";
4
+ import { InitiateOAuthConnectInputSchema, CompleteOAuthConnectInputSchema, ConnectInputSchema, } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/io_pb";
5
+ import { useStigmer } from "../hooks";
6
+ import { toError } from "../internal/toError";
7
+ /**
8
+ * Message type posted by {@link OAuthCallbackHandler} to the opener window.
9
+ *
10
+ * @internal
11
+ */
12
+ export const OAUTH_CALLBACK_MESSAGE_TYPE = "stigmer:oauth:callback";
13
+ const POPUP_WIDTH = 600;
14
+ const POPUP_HEIGHT = 700;
15
+ const POPUP_CALLBACK_TIMEOUT_MS = 120_000;
16
+ /**
17
+ * Action hook that orchestrates the full OAuth popup flow for MCP servers.
18
+ *
19
+ * Handles the complete lifecycle:
20
+ * 1. Calls `initiateOAuthConnect` to get the authorization URL
21
+ * 2. Opens a popup window to the OAuth consent screen
22
+ * 3. Listens for the callback via `window.postMessage`
23
+ * 4. Calls `completeOAuthConnect` to exchange the code for tokens
24
+ * 5. Chains to `connect` for tool discovery
25
+ *
26
+ * The popup is opened **synchronously** before the `initiateOAuthConnect`
27
+ * RPC to avoid browser popup blockers. A blank page is shown briefly
28
+ * while the RPC resolves, then the popup navigates to the auth URL.
29
+ *
30
+ * @example
31
+ * ```tsx
32
+ * const oauth = useMcpServerOAuthConnect();
33
+ * const { refetch } = useMcpServer(org, slug);
34
+ *
35
+ * async function handleSignIn() {
36
+ * try {
37
+ * await oauth.startOAuth(mcpServer.metadata.id);
38
+ * refetch();
39
+ * } catch {
40
+ * // error is available via oauth.error
41
+ * }
42
+ * }
43
+ *
44
+ * <button onClick={handleSignIn} disabled={oauth.isInProgress}>
45
+ * {oauth.isInProgress ? "Signing in..." : "Sign in with OAuth"}
46
+ * </button>
47
+ * ```
48
+ */
49
+ export function useMcpServerOAuthConnect() {
50
+ const stigmer = useStigmer();
51
+ const [phase, setPhase] = useState("idle");
52
+ const [error, setError] = useState(null);
53
+ const popupRef = useRef(null);
54
+ const cleanupRef = useRef(null);
55
+ useEffect(() => {
56
+ return () => {
57
+ cleanupRef.current?.();
58
+ };
59
+ }, []);
60
+ const clearError = useCallback(() => {
61
+ setPhase("idle");
62
+ setError(null);
63
+ }, []);
64
+ const startOAuth = useCallback(async (mcpServerId) => {
65
+ setPhase("initiating");
66
+ setError(null);
67
+ cleanupRef.current?.();
68
+ const left = window.screenX + (window.outerWidth - POPUP_WIDTH) / 2;
69
+ const top = window.screenY + (window.outerHeight - POPUP_HEIGHT) / 2;
70
+ const popup = window.open("about:blank", "stigmer_oauth", `width=${POPUP_WIDTH},height=${POPUP_HEIGHT},left=${left},top=${top},popup=yes`);
71
+ if (!popup) {
72
+ const blocked = new Error("Your browser blocked the authentication popup. " +
73
+ "Please allow popups for this site and try again.");
74
+ setError(blocked);
75
+ setPhase("idle");
76
+ throw blocked;
77
+ }
78
+ popupRef.current = popup;
79
+ try {
80
+ const initOutput = await stigmer.mcpServer.initiateOAuthConnect(create(InitiateOAuthConnectInputSchema, { mcpServerId }));
81
+ popup.location.href = initOutput.authorizationUrl;
82
+ setPhase("awaiting-callback");
83
+ const { code, state } = await waitForOAuthCallback(popup, initOutput.state, (dispose) => {
84
+ cleanupRef.current = dispose;
85
+ });
86
+ setPhase("completing");
87
+ await stigmer.mcpServer.completeOAuthConnect(create(CompleteOAuthConnectInputSchema, {
88
+ mcpServerId,
89
+ authorizationCode: code,
90
+ state,
91
+ }));
92
+ setPhase("connecting");
93
+ const server = await stigmer.mcpServer.connect(create(ConnectInputSchema, { mcpServerId }));
94
+ setPhase("done");
95
+ return server;
96
+ }
97
+ catch (err) {
98
+ const wrapped = toError(err);
99
+ setError(wrapped);
100
+ setPhase("idle");
101
+ closePopup(popup);
102
+ throw wrapped;
103
+ }
104
+ finally {
105
+ popupRef.current = null;
106
+ cleanupRef.current = null;
107
+ }
108
+ }, [stigmer]);
109
+ return {
110
+ startOAuth,
111
+ isInProgress: phase !== "idle" && phase !== "done",
112
+ phase,
113
+ error,
114
+ clearError,
115
+ };
116
+ }
117
+ // ---------------------------------------------------------------------------
118
+ // Popup callback listener
119
+ // ---------------------------------------------------------------------------
120
+ function waitForOAuthCallback(popup, expectedState, onDispose) {
121
+ return new Promise((resolve, reject) => {
122
+ let settled = false;
123
+ let timeoutId;
124
+ let pollId;
125
+ function cleanup() {
126
+ if (timeoutId)
127
+ clearTimeout(timeoutId);
128
+ if (pollId)
129
+ clearInterval(pollId);
130
+ window.removeEventListener("message", onMessage);
131
+ }
132
+ function settle(outcome) {
133
+ if (settled)
134
+ return;
135
+ settled = true;
136
+ cleanup();
137
+ if (outcome instanceof Error) {
138
+ reject(outcome);
139
+ }
140
+ else {
141
+ resolve(outcome);
142
+ }
143
+ }
144
+ onDispose(() => {
145
+ settle(new Error("OAuth flow was cancelled."));
146
+ closePopup(popup);
147
+ });
148
+ function onMessage(event) {
149
+ if (event.origin !== window.location.origin)
150
+ return;
151
+ const data = event.data;
152
+ if (data?.type !== OAUTH_CALLBACK_MESSAGE_TYPE)
153
+ return;
154
+ if (data.state !== expectedState) {
155
+ settle(new Error("OAuth state mismatch — the callback did not match the " +
156
+ "initiated flow. Please try again."));
157
+ return;
158
+ }
159
+ if (!data.code) {
160
+ settle(new Error("No authorization code received from the OAuth provider."));
161
+ return;
162
+ }
163
+ settle({ code: data.code, state: data.state });
164
+ }
165
+ window.addEventListener("message", onMessage);
166
+ timeoutId = setTimeout(() => {
167
+ settle(new Error("OAuth authentication timed out. Ensure your callback page " +
168
+ "renders <OAuthCallbackHandler /> from @stigmer/react at " +
169
+ "the URL configured as your OAuth redirect URI."));
170
+ closePopup(popup);
171
+ }, POPUP_CALLBACK_TIMEOUT_MS);
172
+ pollId = setInterval(() => {
173
+ if (popup.closed) {
174
+ settle(new Error("The authentication window was closed before completing sign-in."));
175
+ }
176
+ }, 500);
177
+ });
178
+ }
179
+ function closePopup(popup) {
180
+ try {
181
+ popup?.close();
182
+ }
183
+ catch {
184
+ // Cross-origin popup may throw on close — safe to ignore.
185
+ }
186
+ }
187
+ //# sourceMappingURL=useMcpServerOAuthConnect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useMcpServerOAuthConnect.js","sourceRoot":"","sources":["../../src/mcp-server/useMcpServerOAuthConnect.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stigmer/react",
3
- "version": "0.0.76",
3
+ "version": "0.0.78",
4
4
  "description": "React provider and client hook for the Stigmer platform SDK",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -35,7 +35,7 @@
35
35
  }
36
36
  },
37
37
  "dependencies": {
38
- "@stigmer/theme": "0.0.76",
38
+ "@stigmer/theme": "0.0.78",
39
39
  "react-markdown": "^10.1.0",
40
40
  "remark-gfm": "^4.0.1",
41
41
  "yaml": "^2.8.2"
@@ -43,8 +43,8 @@
43
43
  "peerDependencies": {
44
44
  "@base-ui/react": "^1.0.0",
45
45
  "@bufbuild/protobuf": "^2.0.0",
46
- "@stigmer/protos": "0.0.76",
47
- "@stigmer/sdk": "0.0.76",
46
+ "@stigmer/protos": "0.0.78",
47
+ "@stigmer/sdk": "0.0.78",
48
48
  "react": "^19.0.0",
49
49
  "react-dom": "^19.0.0"
50
50
  }
package/src/index.ts CHANGED
@@ -211,7 +211,7 @@ export type {
211
211
  SessionComposerSubmitContext,
212
212
  } from "./composer";
213
213
 
214
- // MCP Server — data hook, count hook, list hook, search hook, picker, config panel, tool selector, detail view, and setup orchestration
214
+ // MCP Server — data hook, count hook, list hook, search hook, picker, config panel, tool selector, detail view, setup orchestration, and OAuth connect
215
215
  export {
216
216
  useMcpServer,
217
217
  useMcpServerCount,
@@ -219,7 +219,9 @@ export {
219
219
  useMcpServerSearch,
220
220
  useMcpServerSetup,
221
221
  useMcpServerConnect,
222
+ useMcpServerOAuthConnect,
222
223
  useMcpServerCredentials,
224
+ OAuthCallbackHandler,
223
225
  McpServerPicker,
224
226
  McpServerConfigPanel,
225
227
  McpServerDetailView,
@@ -243,11 +245,17 @@ export type {
243
245
  McpServerSetupIntegration,
244
246
  McpServerConfigPanelProps,
245
247
  McpServerCredentialsProps,
248
+ McpServerOAuthSignInProps,
246
249
  McpServerDetailViewProps,
247
250
  CapabilityTab,
248
251
  McpToolSelectorProps,
249
252
  UseMcpServerConnectReturn,
253
+ UseMcpServerOAuthConnectReturn,
254
+ OAuthConnectPhase,
255
+ OAuthCallbackHandlerProps,
256
+ OAuthCallbackParams,
250
257
  UseMcpServerCredentialsReturn,
258
+ McpServerAuthMode,
251
259
  } from "./mcp-server";
252
260
 
253
261
  // Skill — data hook, count hook, list hook, search hook, picker, and detail view component
@@ -12,6 +12,7 @@ import {
12
12
  type EnvVarFormSubmitOptions,
13
13
  } from "../environment/EnvVarForm";
14
14
  import { McpToolSelector } from "./McpToolSelector";
15
+ import type { OAuthConnectPhase } from "./useMcpServerOAuthConnect";
15
16
 
16
17
  // ---------------------------------------------------------------------------
17
18
  // Credential sub-props
@@ -54,6 +55,29 @@ export interface McpServerCredentialsProps {
54
55
  readonly poolValues?: (key: string) => EnvVarInput | undefined;
55
56
  }
56
57
 
58
+ // ---------------------------------------------------------------------------
59
+ // OAuth sign-in sub-props
60
+ // ---------------------------------------------------------------------------
61
+
62
+ /**
63
+ * Props for the inline OAuth sign-in action shown when a server requires
64
+ * OAuth authentication. Presence controls rendering — when `oauthSignIn`
65
+ * is provided on {@link McpServerConfigPanelProps}, the OAuth button
66
+ * is rendered above the credentials form.
67
+ */
68
+ export interface McpServerOAuthSignInProps {
69
+ /** Initiates the OAuth popup flow. Must be called from a click handler. */
70
+ readonly onSignIn: () => void;
71
+ /** Current phase of the OAuth flow. */
72
+ readonly phase: OAuthConnectPhase;
73
+ /** `true` when the OAuth token already exists in the personal environment. */
74
+ readonly isConnected: boolean;
75
+ /** Error from the most recent failed OAuth attempt, or `null`. */
76
+ readonly error: Error | null;
77
+ /** Clear the OAuth error state. */
78
+ readonly onClearError: () => void;
79
+ }
80
+
57
81
  // ---------------------------------------------------------------------------
58
82
  // Component props
59
83
  // ---------------------------------------------------------------------------
@@ -71,6 +95,14 @@ export interface McpServerConfigPanelProps {
71
95
  * step 2 (tool customization).
72
96
  */
73
97
  readonly credentials?: McpServerCredentialsProps;
98
+ /**
99
+ * When provided, an inline OAuth sign-in button is rendered above the
100
+ * credentials form. The button initiates the popup-based OAuth flow.
101
+ *
102
+ * Use alongside `credentials` for mixed-mode servers that require
103
+ * both OAuth and manual env vars, or on its own for OAuth-only servers.
104
+ */
105
+ readonly oauthSignIn?: McpServerOAuthSignInProps;
74
106
  /** Discovered tools from `status.discovered_capabilities.tools`. */
75
107
  readonly discoveredTools: DiscoveredTool[];
76
108
  /** Approval policies from `status.tool_approvals` and `spec.pinned_tool_approvals`. */
@@ -141,6 +173,7 @@ export interface McpServerConfigPanelProps {
141
173
  export function McpServerConfigPanel({
142
174
  mcpServer,
143
175
  credentials,
176
+ oauthSignIn,
144
177
  discoveredTools,
145
178
  toolApprovals,
146
179
  enabledTools,
@@ -155,6 +188,13 @@ export function McpServerConfigPanel({
155
188
  const description = mcpServer.spec?.description;
156
189
  const isDisabled = disabled || credentials?.isSubmitting;
157
190
 
191
+ const isOAuthBusy = oauthSignIn
192
+ ? oauthSignIn.phase === "initiating" ||
193
+ oauthSignIn.phase === "awaiting-callback" ||
194
+ oauthSignIn.phase === "completing" ||
195
+ oauthSignIn.phase === "connecting"
196
+ : false;
197
+
158
198
  const handleCredentialSubmit = useCallback(
159
199
  (values: Record<string, EnvVarInput>, options: EnvVarFormSubmitOptions) => {
160
200
  credentials?.onSubmit(values, options);
@@ -173,7 +213,7 @@ export function McpServerConfigPanel({
173
213
  <button
174
214
  type="button"
175
215
  onClick={onBack}
176
- disabled={credentials?.isSubmitting}
216
+ disabled={credentials?.isSubmitting || isOAuthBusy}
177
217
  className={cn(
178
218
  "mt-0.5 shrink-0 rounded p-0.5",
179
219
  "text-muted-foreground hover:text-foreground hover:bg-accent/50",
@@ -208,13 +248,25 @@ export function McpServerConfigPanel({
208
248
  </div>
209
249
  </div>
210
250
 
251
+ {/* OAuth sign-in — shown when the server requires OAuth authentication */}
252
+ {oauthSignIn && (
253
+ <InlineOAuthSignIn
254
+ serverName={serverName}
255
+ isConnected={oauthSignIn.isConnected}
256
+ phase={oauthSignIn.phase}
257
+ onSignIn={oauthSignIn.onSignIn}
258
+ error={oauthSignIn.error}
259
+ onClearError={oauthSignIn.onClearError}
260
+ />
261
+ )}
262
+
211
263
  {/* Credentials form — only when credentials prop is provided */}
212
264
  {credentials && (
213
265
  <EnvVarForm
214
266
  variables={credentials.variables}
215
267
  onSubmit={handleCredentialSubmit}
216
268
  isSubmitting={credentials.isSubmitting}
217
- disabled={disabled}
269
+ disabled={disabled || isOAuthBusy}
218
270
  title="Credentials required"
219
271
  defaultSaveForFuture={credentials.defaultSaveForFuture}
220
272
  hideSaveToggle={credentials.hideSaveToggle}
@@ -239,12 +291,110 @@ export function McpServerConfigPanel({
239
291
  toolApprovals={toolApprovals}
240
292
  enabledTools={enabledTools}
241
293
  onChange={onEnabledToolsChange}
242
- disabled={!!isDisabled || !!credentials}
294
+ disabled={!!isDisabled || !!credentials || isOAuthBusy}
243
295
  />
244
296
  </div>
245
297
  );
246
298
  }
247
299
 
300
+ // ---------------------------------------------------------------------------
301
+ // Inline OAuth sign-in (compact, for config panel context)
302
+ // ---------------------------------------------------------------------------
303
+
304
+ function InlineOAuthSignIn({
305
+ serverName,
306
+ isConnected,
307
+ phase,
308
+ onSignIn,
309
+ error,
310
+ onClearError,
311
+ }: {
312
+ readonly serverName: string;
313
+ readonly isConnected: boolean;
314
+ readonly phase: OAuthConnectPhase;
315
+ readonly onSignIn: () => void;
316
+ readonly error: Error | null;
317
+ readonly onClearError: () => void;
318
+ }) {
319
+ const isBusy =
320
+ phase === "initiating" ||
321
+ phase === "awaiting-callback" ||
322
+ phase === "completing" ||
323
+ phase === "connecting";
324
+
325
+ return (
326
+ <div className="space-y-1.5">
327
+ <div className="flex items-center justify-between">
328
+ <span
329
+ className={cn(
330
+ "inline-flex items-center gap-1 text-[0.65rem] font-medium",
331
+ isConnected ? "text-success" : "text-muted-foreground",
332
+ )}
333
+ >
334
+ <span
335
+ className={cn(
336
+ "size-1.5 rounded-full",
337
+ isConnected ? "bg-success" : "bg-muted-foreground",
338
+ )}
339
+ aria-hidden="true"
340
+ />
341
+ {isConnected ? "Signed in" : "Sign-in required"}
342
+ </span>
343
+ <button
344
+ type="button"
345
+ onClick={onSignIn}
346
+ disabled={isBusy}
347
+ className={cn(
348
+ "inline-flex items-center gap-1 rounded px-2 py-0.5 text-[0.65rem] font-medium",
349
+ isConnected
350
+ ? "text-muted-foreground hover:text-foreground hover:bg-accent/50"
351
+ : "bg-primary text-primary-foreground hover:bg-primary-hover",
352
+ "disabled:pointer-events-none disabled:opacity-50",
353
+ )}
354
+ >
355
+ {isBusy ? (
356
+ <InlineSpinner />
357
+ ) : isConnected ? (
358
+ "Re-authenticate"
359
+ ) : (
360
+ `Sign in with ${serverName}`
361
+ )}
362
+ </button>
363
+ </div>
364
+ {error && (
365
+ <div className="flex items-start gap-1.5 text-[0.65rem] text-destructive">
366
+ <span className="flex-1">{error.message}</span>
367
+ <button
368
+ type="button"
369
+ onClick={onClearError}
370
+ className="shrink-0 underline underline-offset-2 hover:no-underline"
371
+ >
372
+ Dismiss
373
+ </button>
374
+ </div>
375
+ )}
376
+ </div>
377
+ );
378
+ }
379
+
380
+ function InlineSpinner() {
381
+ return (
382
+ <svg
383
+ width="10"
384
+ height="10"
385
+ viewBox="0 0 16 16"
386
+ fill="none"
387
+ stroke="currentColor"
388
+ strokeWidth="2"
389
+ strokeLinecap="round"
390
+ className="animate-spin"
391
+ aria-hidden="true"
392
+ >
393
+ <path d="M8 2a6 6 0 1 0 6 6" />
394
+ </svg>
395
+ );
396
+ }
397
+
248
398
  // ---------------------------------------------------------------------------
249
399
  // Icons (internal to this module)
250
400
  // ---------------------------------------------------------------------------