@stigmer/react 0.0.77 → 0.0.79
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/index.d.ts +2 -2
- package/index.d.ts.map +1 -1
- package/index.js +2 -2
- package/index.js.map +1 -1
- package/mcp-server/McpServerConfigPanel.d.ts +28 -1
- package/mcp-server/McpServerConfigPanel.d.ts.map +1 -1
- package/mcp-server/McpServerConfigPanel.js +23 -2
- package/mcp-server/McpServerConfigPanel.js.map +1 -1
- package/mcp-server/McpServerDetailView.d.ts.map +1 -1
- package/mcp-server/McpServerDetailView.js +92 -13
- package/mcp-server/McpServerDetailView.js.map +1 -1
- package/mcp-server/McpServerPicker.d.ts.map +1 -1
- package/mcp-server/McpServerPicker.js +34 -2
- package/mcp-server/McpServerPicker.js.map +1 -1
- package/mcp-server/OAuthCallbackHandler.d.ts +54 -0
- package/mcp-server/OAuthCallbackHandler.d.ts.map +1 -0
- package/mcp-server/OAuthCallbackHandler.js +98 -0
- package/mcp-server/OAuthCallbackHandler.js.map +1 -0
- package/mcp-server/index.d.ts +6 -2
- package/mcp-server/index.d.ts.map +1 -1
- package/mcp-server/index.js +2 -0
- package/mcp-server/index.js.map +1 -1
- package/mcp-server/useMcpServerConnect.d.ts +15 -12
- package/mcp-server/useMcpServerConnect.d.ts.map +1 -1
- package/mcp-server/useMcpServerConnect.js +17 -17
- package/mcp-server/useMcpServerConnect.js.map +1 -1
- package/mcp-server/useMcpServerCredentials.d.ts +59 -7
- package/mcp-server/useMcpServerCredentials.d.ts.map +1 -1
- package/mcp-server/useMcpServerCredentials.js +37 -10
- package/mcp-server/useMcpServerCredentials.js.map +1 -1
- package/mcp-server/useMcpServerOAuthConnect.d.ts +82 -0
- package/mcp-server/useMcpServerOAuthConnect.d.ts.map +1 -0
- package/mcp-server/useMcpServerOAuthConnect.js +199 -0
- package/mcp-server/useMcpServerOAuthConnect.js.map +1 -0
- package/package.json +4 -4
- package/src/index.ts +9 -1
- package/src/mcp-server/McpServerConfigPanel.tsx +153 -3
- package/src/mcp-server/McpServerDetailView.tsx +231 -172
- package/src/mcp-server/McpServerPicker.tsx +40 -2
- package/src/mcp-server/OAuthCallbackHandler.tsx +239 -0
- package/src/mcp-server/index.ts +17 -1
- package/src/mcp-server/useMcpServerConnect.ts +25 -22
- package/src/mcp-server/useMcpServerCredentials.ts +86 -13
- package/src/mcp-server/useMcpServerOAuthConnect.ts +312 -0
- package/styles.css +1 -1
|
@@ -13,6 +13,12 @@ import { SYSTEM_ENV_VAR_KEYS } from "../environment/systemEnvVars";
|
|
|
13
13
|
* computes the missing set and exposes `saveCredentials` to persist
|
|
14
14
|
* them.
|
|
15
15
|
*
|
|
16
|
+
* **Auth-mode-aware**: when `spec.auth` is configured, the hook
|
|
17
|
+
* identifies the OAuth-managed variable (`target_env_var`) and
|
|
18
|
+
* excludes it from `missingVariables` — that variable is acquired
|
|
19
|
+
* via {@link useMcpServerOAuthConnect}, not a manual form. Additional
|
|
20
|
+
* non-OAuth vars still appear in `missingVariables` (mixed mode).
|
|
21
|
+
*
|
|
16
22
|
* Unlike {@link useMcpServerSetup} which manages multi-server setup
|
|
17
23
|
* for session creation, this hook is scoped to a single server and
|
|
18
24
|
* always persists to the personal environment (no one-time option).
|
|
@@ -21,15 +27,20 @@ import { SYSTEM_ENV_VAR_KEYS } from "../environment/systemEnvVars";
|
|
|
21
27
|
*
|
|
22
28
|
* @example
|
|
23
29
|
* ```tsx
|
|
24
|
-
* const
|
|
25
|
-
*
|
|
30
|
+
* const creds = useMcpServerCredentials("acme", mcpServer);
|
|
31
|
+
*
|
|
32
|
+
* // OAuth server — sign-in button + optional manual form
|
|
33
|
+
* if (creds.authMode === "oauth" && !creds.isOAuthConnected) {
|
|
34
|
+
* return <button onClick={startOAuth}>Sign in</button>;
|
|
35
|
+
* }
|
|
26
36
|
*
|
|
27
|
-
*
|
|
37
|
+
* // Manual vars still needed (mixed mode or manual-only)
|
|
38
|
+
* if (creds.missingVariables.length > 0) {
|
|
28
39
|
* return (
|
|
29
40
|
* <EnvVarForm
|
|
30
|
-
* variables={missingVariables}
|
|
31
|
-
* onSubmit={(values) => saveCredentials(values)}
|
|
32
|
-
* isSubmitting={isSaving}
|
|
41
|
+
* variables={creds.missingVariables}
|
|
42
|
+
* onSubmit={(values) => creds.saveCredentials(values)}
|
|
43
|
+
* isSubmitting={creds.isSaving}
|
|
33
44
|
* hideSaveToggle
|
|
34
45
|
* />
|
|
35
46
|
* );
|
|
@@ -38,21 +49,37 @@ import { SYSTEM_ENV_VAR_KEYS } from "../environment/systemEnvVars";
|
|
|
38
49
|
*/
|
|
39
50
|
export function useMcpServerCredentials(org, mcpServer) {
|
|
40
51
|
const personalEnv = usePersonalEnvironment(org);
|
|
41
|
-
const
|
|
52
|
+
const auth = mcpServer?.spec?.auth;
|
|
53
|
+
const authMode = auth ? "oauth" : "manual";
|
|
54
|
+
const oauthTargetEnvVar = auth?.targetEnvVar || null;
|
|
55
|
+
const tokenLifetimeHint = auth?.tokenLifetimeHint || null;
|
|
56
|
+
const existingKeys = useMemo(() => new Set(Object.keys(personalEnv.environment?.spec?.data ?? {})), [personalEnv.environment]);
|
|
57
|
+
const isOAuthConnected = authMode === "oauth"
|
|
58
|
+
&& oauthTargetEnvVar !== null
|
|
59
|
+
&& existingKeys.has(oauthTargetEnvVar);
|
|
60
|
+
const allMissingVariables = useMemo(() => {
|
|
42
61
|
if (!mcpServer)
|
|
43
62
|
return [];
|
|
44
63
|
const envSpecData = mcpServer.spec?.envSpec?.data;
|
|
45
64
|
if (!envSpecData || Object.keys(envSpecData).length === 0)
|
|
46
65
|
return [];
|
|
47
|
-
const existingKeys = new Set(Object.keys(personalEnv.environment?.spec?.data ?? {}));
|
|
48
66
|
return diffEnvSpec(envSpecData, existingKeys).filter((v) => !SYSTEM_ENV_VAR_KEYS.has(v.key));
|
|
49
|
-
}, [mcpServer,
|
|
50
|
-
const
|
|
67
|
+
}, [mcpServer, existingKeys]);
|
|
68
|
+
const missingVariables = useMemo(() => {
|
|
69
|
+
if (!oauthTargetEnvVar)
|
|
70
|
+
return allMissingVariables;
|
|
71
|
+
return allMissingVariables.filter((v) => v.key !== oauthTargetEnvVar);
|
|
72
|
+
}, [allMissingVariables, oauthTargetEnvVar]);
|
|
73
|
+
const isReady = !personalEnv.isLoading && allMissingVariables.length === 0;
|
|
51
74
|
const saveCredentials = useCallback(async (values) => {
|
|
52
75
|
await personalEnv.getOrCreate();
|
|
53
76
|
await personalEnv.addVariables(values);
|
|
54
77
|
}, [personalEnv]);
|
|
55
78
|
return {
|
|
79
|
+
authMode,
|
|
80
|
+
oauthTargetEnvVar,
|
|
81
|
+
isOAuthConnected,
|
|
82
|
+
tokenLifetimeHint,
|
|
56
83
|
missingVariables,
|
|
57
84
|
isReady,
|
|
58
85
|
isLoading: personalEnv.isLoading,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMcpServerCredentials.js","sourceRoot":"","sources":["../../src/mcp-server/useMcpServerCredentials.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAG7C,OAAO,EAAE,sBAAsB,EAAE,MAAM,uCAAuC,CAAC;AAC/E,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"useMcpServerCredentials.js","sourceRoot":"","sources":["../../src/mcp-server/useMcpServerCredentials.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { McpServer } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
|
|
2
|
+
/**
|
|
3
|
+
* Message type posted by {@link OAuthCallbackHandler} to the opener window.
|
|
4
|
+
*
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
export declare const OAUTH_CALLBACK_MESSAGE_TYPE = "stigmer:oauth:callback";
|
|
8
|
+
/**
|
|
9
|
+
* Shape of the `postMessage` payload sent from the OAuth callback popup.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
export interface OAuthCallbackMessage {
|
|
14
|
+
readonly type: typeof OAUTH_CALLBACK_MESSAGE_TYPE;
|
|
15
|
+
readonly code: string;
|
|
16
|
+
readonly state: string;
|
|
17
|
+
}
|
|
18
|
+
/** Progress phases of the OAuth connect flow. */
|
|
19
|
+
export type OAuthConnectPhase = "idle" | "initiating" | "awaiting-callback" | "completing" | "connecting" | "done";
|
|
20
|
+
/** Return value of {@link useMcpServerOAuthConnect}. */
|
|
21
|
+
export interface UseMcpServerOAuthConnectReturn {
|
|
22
|
+
/**
|
|
23
|
+
* Start the OAuth connect flow for an MCP server.
|
|
24
|
+
*
|
|
25
|
+
* Opens a popup for the OAuth consent screen, waits for the callback,
|
|
26
|
+
* exchanges the authorization code for tokens, then chains to the
|
|
27
|
+
* `connect` RPC for tool discovery.
|
|
28
|
+
*
|
|
29
|
+
* **Must be called from a synchronous user-gesture handler** (e.g.,
|
|
30
|
+
* an `onClick` callback) so the browser allows the popup. The popup
|
|
31
|
+
* is opened synchronously before any async work to avoid popup
|
|
32
|
+
* blockers.
|
|
33
|
+
*
|
|
34
|
+
* @param mcpServerId - System-generated ID (metadata.id) of the MCP server.
|
|
35
|
+
* @param org - Organization context for token storage (caller's active org).
|
|
36
|
+
* @returns The updated McpServer after tool discovery completes.
|
|
37
|
+
*/
|
|
38
|
+
readonly startOAuth: (mcpServerId: string, org: string) => Promise<McpServer>;
|
|
39
|
+
/** `true` while any phase of the OAuth flow is in progress. */
|
|
40
|
+
readonly isInProgress: boolean;
|
|
41
|
+
/** Current phase of the OAuth flow. */
|
|
42
|
+
readonly phase: OAuthConnectPhase;
|
|
43
|
+
/** Error from the most recent unsuccessful attempt, or `null`. */
|
|
44
|
+
readonly error: Error | null;
|
|
45
|
+
/** Reset the hook to idle state, clearing any error. */
|
|
46
|
+
readonly clearError: () => void;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Action hook that orchestrates the full OAuth popup flow for MCP servers.
|
|
50
|
+
*
|
|
51
|
+
* Handles the complete lifecycle:
|
|
52
|
+
* 1. Calls `initiateOAuthConnect` to get the authorization URL
|
|
53
|
+
* 2. Opens a popup window to the OAuth consent screen
|
|
54
|
+
* 3. Listens for the callback via `window.postMessage`
|
|
55
|
+
* 4. Calls `completeOAuthConnect` to exchange the code for tokens
|
|
56
|
+
* 5. Chains to `connect` for tool discovery
|
|
57
|
+
*
|
|
58
|
+
* The popup is opened **synchronously** before the `initiateOAuthConnect`
|
|
59
|
+
* RPC to avoid browser popup blockers. A blank page is shown briefly
|
|
60
|
+
* while the RPC resolves, then the popup navigates to the auth URL.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```tsx
|
|
64
|
+
* const oauth = useMcpServerOAuthConnect();
|
|
65
|
+
* const { refetch } = useMcpServer(org, slug);
|
|
66
|
+
*
|
|
67
|
+
* async function handleSignIn() {
|
|
68
|
+
* try {
|
|
69
|
+
* await oauth.startOAuth(mcpServer.metadata.id, org);
|
|
70
|
+
* refetch();
|
|
71
|
+
* } catch {
|
|
72
|
+
* // error is available via oauth.error
|
|
73
|
+
* }
|
|
74
|
+
* }
|
|
75
|
+
*
|
|
76
|
+
* <button onClick={handleSignIn} disabled={oauth.isInProgress}>
|
|
77
|
+
* {oauth.isInProgress ? "Signing in..." : "Sign in with OAuth"}
|
|
78
|
+
* </button>
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export declare function useMcpServerOAuthConnect(): UseMcpServerOAuthConnectReturn;
|
|
82
|
+
//# sourceMappingURL=useMcpServerOAuthConnect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useMcpServerOAuthConnect.d.ts","sourceRoot":"","sources":["../../src/mcp-server/useMcpServerOAuthConnect.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wDAAwD,CAAC;AAUxF;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,2BAA2B,CAAC;AAEpE;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,OAAO,2BAA2B,CAAC;IAClD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,iDAAiD;AACjD,MAAM,MAAM,iBAAiB,GACzB,MAAM,GACN,YAAY,GACZ,mBAAmB,GACnB,YAAY,GACZ,YAAY,GACZ,MAAM,CAAC;AAEX,wDAAwD;AACxD,MAAM,WAAW,8BAA8B;IAC7C;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,UAAU,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;IAC9E,+DAA+D;IAC/D,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,uCAAuC;IACvC,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAC;IAClC,kEAAkE;IAClE,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAC7B,wDAAwD;IACxD,QAAQ,CAAC,UAAU,EAAE,MAAM,IAAI,CAAC;CACjC;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,wBAAwB,IAAI,8BAA8B,CAiHzE"}
|
|
@@ -0,0 +1,199 @@
|
|
|
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 { resolveSystemEnvVarValues } from "../environment/systemEnvVars";
|
|
7
|
+
import { toError } from "../internal/toError";
|
|
8
|
+
/**
|
|
9
|
+
* Message type posted by {@link OAuthCallbackHandler} to the opener window.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
export const OAUTH_CALLBACK_MESSAGE_TYPE = "stigmer:oauth:callback";
|
|
14
|
+
const POPUP_WIDTH = 600;
|
|
15
|
+
const POPUP_HEIGHT = 700;
|
|
16
|
+
const POPUP_CALLBACK_TIMEOUT_MS = 120_000;
|
|
17
|
+
/**
|
|
18
|
+
* Action hook that orchestrates the full OAuth popup flow for MCP servers.
|
|
19
|
+
*
|
|
20
|
+
* Handles the complete lifecycle:
|
|
21
|
+
* 1. Calls `initiateOAuthConnect` to get the authorization URL
|
|
22
|
+
* 2. Opens a popup window to the OAuth consent screen
|
|
23
|
+
* 3. Listens for the callback via `window.postMessage`
|
|
24
|
+
* 4. Calls `completeOAuthConnect` to exchange the code for tokens
|
|
25
|
+
* 5. Chains to `connect` for tool discovery
|
|
26
|
+
*
|
|
27
|
+
* The popup is opened **synchronously** before the `initiateOAuthConnect`
|
|
28
|
+
* RPC to avoid browser popup blockers. A blank page is shown briefly
|
|
29
|
+
* while the RPC resolves, then the popup navigates to the auth URL.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```tsx
|
|
33
|
+
* const oauth = useMcpServerOAuthConnect();
|
|
34
|
+
* const { refetch } = useMcpServer(org, slug);
|
|
35
|
+
*
|
|
36
|
+
* async function handleSignIn() {
|
|
37
|
+
* try {
|
|
38
|
+
* await oauth.startOAuth(mcpServer.metadata.id, org);
|
|
39
|
+
* refetch();
|
|
40
|
+
* } catch {
|
|
41
|
+
* // error is available via oauth.error
|
|
42
|
+
* }
|
|
43
|
+
* }
|
|
44
|
+
*
|
|
45
|
+
* <button onClick={handleSignIn} disabled={oauth.isInProgress}>
|
|
46
|
+
* {oauth.isInProgress ? "Signing in..." : "Sign in with OAuth"}
|
|
47
|
+
* </button>
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export function useMcpServerOAuthConnect() {
|
|
51
|
+
const stigmer = useStigmer();
|
|
52
|
+
const [phase, setPhase] = useState("idle");
|
|
53
|
+
const [error, setError] = useState(null);
|
|
54
|
+
const popupRef = useRef(null);
|
|
55
|
+
const cleanupRef = useRef(null);
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
return () => {
|
|
58
|
+
cleanupRef.current?.();
|
|
59
|
+
};
|
|
60
|
+
}, []);
|
|
61
|
+
const clearError = useCallback(() => {
|
|
62
|
+
setPhase("idle");
|
|
63
|
+
setError(null);
|
|
64
|
+
}, []);
|
|
65
|
+
const startOAuth = useCallback(async (mcpServerId, org) => {
|
|
66
|
+
setPhase("initiating");
|
|
67
|
+
setError(null);
|
|
68
|
+
cleanupRef.current?.();
|
|
69
|
+
const left = window.screenX + (window.outerWidth - POPUP_WIDTH) / 2;
|
|
70
|
+
const top = window.screenY + (window.outerHeight - POPUP_HEIGHT) / 2;
|
|
71
|
+
const popup = window.open("about:blank", "stigmer_oauth", `width=${POPUP_WIDTH},height=${POPUP_HEIGHT},left=${left},top=${top},popup=yes`);
|
|
72
|
+
if (!popup) {
|
|
73
|
+
const blocked = new Error("Your browser blocked the authentication popup. " +
|
|
74
|
+
"Please allow popups for this site and try again.");
|
|
75
|
+
setError(blocked);
|
|
76
|
+
setPhase("idle");
|
|
77
|
+
throw blocked;
|
|
78
|
+
}
|
|
79
|
+
popupRef.current = popup;
|
|
80
|
+
try {
|
|
81
|
+
const initOutput = await stigmer.mcpServer.initiateOAuthConnect(create(InitiateOAuthConnectInputSchema, { mcpServerId, org }));
|
|
82
|
+
popup.location.href = initOutput.authorizationUrl;
|
|
83
|
+
setPhase("awaiting-callback");
|
|
84
|
+
const { code, state } = await waitForOAuthCallback(popup, initOutput.state, (dispose) => {
|
|
85
|
+
cleanupRef.current = dispose;
|
|
86
|
+
});
|
|
87
|
+
setPhase("completing");
|
|
88
|
+
await stigmer.mcpServer.completeOAuthConnect(create(CompleteOAuthConnectInputSchema, {
|
|
89
|
+
mcpServerId,
|
|
90
|
+
authorizationCode: code,
|
|
91
|
+
state,
|
|
92
|
+
}));
|
|
93
|
+
setPhase("connecting");
|
|
94
|
+
const systemEnv = await resolveSystemEnvVarValues(stigmer);
|
|
95
|
+
const runtimeEnvMap = {};
|
|
96
|
+
for (const [key, envInput] of Object.entries(systemEnv)) {
|
|
97
|
+
runtimeEnvMap[key] = {
|
|
98
|
+
value: envInput.value,
|
|
99
|
+
isSecret: envInput.isSecret ?? false,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const server = await stigmer.mcpServer.connect(create(ConnectInputSchema, {
|
|
103
|
+
mcpServerId,
|
|
104
|
+
runtimeEnv: runtimeEnvMap,
|
|
105
|
+
}));
|
|
106
|
+
setPhase("done");
|
|
107
|
+
return server;
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
const wrapped = toError(err);
|
|
111
|
+
setError(wrapped);
|
|
112
|
+
setPhase("idle");
|
|
113
|
+
closePopup(popup);
|
|
114
|
+
throw wrapped;
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
popupRef.current = null;
|
|
118
|
+
cleanupRef.current = null;
|
|
119
|
+
}
|
|
120
|
+
}, [stigmer]);
|
|
121
|
+
return {
|
|
122
|
+
startOAuth,
|
|
123
|
+
isInProgress: phase !== "idle" && phase !== "done",
|
|
124
|
+
phase,
|
|
125
|
+
error,
|
|
126
|
+
clearError,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
// Popup callback listener
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
function waitForOAuthCallback(popup, expectedState, onDispose) {
|
|
133
|
+
return new Promise((resolve, reject) => {
|
|
134
|
+
let settled = false;
|
|
135
|
+
let timeoutId;
|
|
136
|
+
let pollId;
|
|
137
|
+
function cleanup() {
|
|
138
|
+
if (timeoutId)
|
|
139
|
+
clearTimeout(timeoutId);
|
|
140
|
+
if (pollId)
|
|
141
|
+
clearInterval(pollId);
|
|
142
|
+
window.removeEventListener("message", onMessage);
|
|
143
|
+
}
|
|
144
|
+
function settle(outcome) {
|
|
145
|
+
if (settled)
|
|
146
|
+
return;
|
|
147
|
+
settled = true;
|
|
148
|
+
cleanup();
|
|
149
|
+
if (outcome instanceof Error) {
|
|
150
|
+
reject(outcome);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
resolve(outcome);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
onDispose(() => {
|
|
157
|
+
settle(new Error("OAuth flow was cancelled."));
|
|
158
|
+
closePopup(popup);
|
|
159
|
+
});
|
|
160
|
+
function onMessage(event) {
|
|
161
|
+
if (event.origin !== window.location.origin)
|
|
162
|
+
return;
|
|
163
|
+
const data = event.data;
|
|
164
|
+
if (data?.type !== OAUTH_CALLBACK_MESSAGE_TYPE)
|
|
165
|
+
return;
|
|
166
|
+
if (data.state !== expectedState) {
|
|
167
|
+
settle(new Error("OAuth state mismatch — the callback did not match the " +
|
|
168
|
+
"initiated flow. Please try again."));
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (!data.code) {
|
|
172
|
+
settle(new Error("No authorization code received from the OAuth provider."));
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
settle({ code: data.code, state: data.state });
|
|
176
|
+
}
|
|
177
|
+
window.addEventListener("message", onMessage);
|
|
178
|
+
timeoutId = setTimeout(() => {
|
|
179
|
+
settle(new Error("OAuth authentication timed out. Ensure your callback page " +
|
|
180
|
+
"renders <OAuthCallbackHandler /> from @stigmer/react at " +
|
|
181
|
+
"the URL configured as your OAuth redirect URI."));
|
|
182
|
+
closePopup(popup);
|
|
183
|
+
}, POPUP_CALLBACK_TIMEOUT_MS);
|
|
184
|
+
pollId = setInterval(() => {
|
|
185
|
+
if (popup.closed) {
|
|
186
|
+
settle(new Error("The authentication window was closed before completing sign-in."));
|
|
187
|
+
}
|
|
188
|
+
}, 500);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
function closePopup(popup) {
|
|
192
|
+
try {
|
|
193
|
+
popup?.close();
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
// Cross-origin popup may throw on close — safe to ignore.
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
//# 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.
|
|
3
|
+
"version": "0.0.79",
|
|
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.
|
|
38
|
+
"@stigmer/theme": "0.0.79",
|
|
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.
|
|
47
|
-
"@stigmer/sdk": "0.0.
|
|
46
|
+
"@stigmer/protos": "0.0.79",
|
|
47
|
+
"@stigmer/sdk": "0.0.79",
|
|
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
|
|
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
|
// ---------------------------------------------------------------------------
|