@stigmer/react 0.0.78 → 0.0.80
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/agent/AgentDetailView.js +2 -2
- package/agent/AgentDetailView.js.map +1 -1
- package/agent/agentSetupReducer.d.ts +3 -3
- package/agent/agentSetupReducer.d.ts.map +1 -1
- package/agent/index.d.ts +1 -1
- package/agent/index.d.ts.map +1 -1
- package/agent/index.js +1 -1
- package/agent/index.js.map +1 -1
- package/agent/useAgentSetup.d.ts +4 -4
- package/agent/useAgentSetup.js +9 -9
- package/agent/useAgentSetup.js.map +1 -1
- package/environment/EnvVarForm.d.ts +9 -3
- package/environment/EnvVarForm.d.ts.map +1 -1
- package/environment/EnvVarForm.js +1 -1
- package/environment/EnvVarForm.js.map +1 -1
- package/environment/EnvironmentListPanel.d.ts +19 -4
- package/environment/EnvironmentListPanel.d.ts.map +1 -1
- package/environment/EnvironmentListPanel.js +7 -3
- package/environment/EnvironmentListPanel.js.map +1 -1
- package/environment/{diffEnvSpec.d.ts → diffEnv.d.ts} +9 -8
- package/environment/diffEnv.d.ts.map +1 -0
- package/environment/{diffEnvSpec.js → diffEnv.js} +10 -9
- package/environment/diffEnv.js.map +1 -0
- package/environment/index.d.ts +1 -1
- package/environment/index.d.ts.map +1 -1
- package/environment/index.js +1 -1
- package/environment/index.js.map +1 -1
- package/execution/SessionVariablesInput.d.ts +1 -1
- package/index.d.ts +3 -3
- package/index.d.ts.map +1 -1
- package/index.js +3 -3
- package/index.js.map +1 -1
- package/library/parse-resource-yaml.d.ts +1 -1
- package/library/parse-resource-yaml.d.ts.map +1 -1
- package/library/parse-resource-yaml.js +26 -16
- package/library/parse-resource-yaml.js.map +1 -1
- package/library/serialize-resource-yaml.js +17 -21
- package/library/serialize-resource-yaml.js.map +1 -1
- package/mcp-server/McpServerDetailView.d.ts +8 -1
- package/mcp-server/McpServerDetailView.d.ts.map +1 -1
- package/mcp-server/McpServerDetailView.js +27 -6
- package/mcp-server/McpServerDetailView.js.map +1 -1
- package/mcp-server/McpServerPicker.d.ts +8 -1
- package/mcp-server/McpServerPicker.d.ts.map +1 -1
- package/mcp-server/McpServerPicker.js +2 -2
- package/mcp-server/McpServerPicker.js.map +1 -1
- package/mcp-server/OAuthCallbackHandler.d.ts +2 -0
- package/mcp-server/OAuthCallbackHandler.d.ts.map +1 -1
- package/mcp-server/OAuthCallbackHandler.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/mcp-server/mcpServerSetupReducer.d.ts +4 -4
- 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 +32 -17
- package/mcp-server/useMcpServerCredentials.d.ts.map +1 -1
- package/mcp-server/useMcpServerCredentials.js +30 -19
- package/mcp-server/useMcpServerCredentials.js.map +1 -1
- package/mcp-server/useMcpServerOAuthConnect.d.ts +4 -3
- package/mcp-server/useMcpServerOAuthConnect.d.ts.map +1 -1
- package/mcp-server/useMcpServerOAuthConnect.js +16 -4
- package/mcp-server/useMcpServerOAuthConnect.js.map +1 -1
- package/mcp-server/useMcpServerSetup.d.ts +5 -5
- package/mcp-server/useMcpServerSetup.d.ts.map +1 -1
- package/mcp-server/useMcpServerSetup.js +33 -13
- package/mcp-server/useMcpServerSetup.js.map +1 -1
- package/mcp-server/useOAuthGrantStatus.d.ts +41 -0
- package/mcp-server/useOAuthGrantStatus.d.ts.map +1 -0
- package/mcp-server/useOAuthGrantStatus.js +91 -0
- package/mcp-server/useOAuthGrantStatus.js.map +1 -0
- package/package.json +4 -4
- package/src/agent/AgentDetailView.tsx +5 -5
- package/src/agent/agentSetupReducer.ts +3 -3
- package/src/agent/index.ts +1 -1
- package/src/agent/useAgentSetup.ts +11 -11
- package/src/environment/EnvVarForm.tsx +9 -3
- package/src/environment/EnvironmentListPanel.tsx +27 -9
- package/src/environment/{diffEnvSpec.ts → diffEnv.ts} +10 -9
- package/src/environment/index.ts +1 -1
- package/src/execution/SessionVariablesInput.tsx +1 -1
- package/src/index.ts +4 -2
- package/src/library/parse-resource-yaml.ts +27 -18
- package/src/library/serialize-resource-yaml.ts +20 -27
- package/src/mcp-server/McpServerDetailView.tsx +39 -7
- package/src/mcp-server/McpServerPicker.tsx +9 -1
- package/src/mcp-server/OAuthCallbackHandler.tsx +2 -0
- package/src/mcp-server/index.ts +3 -0
- package/src/mcp-server/mcpServerSetupReducer.ts +4 -4
- package/src/mcp-server/useMcpServerConnect.ts +25 -22
- package/src/mcp-server/useMcpServerCredentials.ts +65 -32
- package/src/mcp-server/useMcpServerOAuthConnect.ts +20 -6
- package/src/mcp-server/useMcpServerSetup.ts +38 -15
- package/src/mcp-server/useOAuthGrantStatus.ts +125 -0
- package/environment/diffEnvSpec.d.ts.map +0 -1
- package/environment/diffEnvSpec.js.map +0 -1
|
@@ -6,6 +6,7 @@ import type { McpServer } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/
|
|
|
6
6
|
import { ConnectInputSchema } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/io_pb";
|
|
7
7
|
import type { EnvVarInput } from "@stigmer/sdk";
|
|
8
8
|
import { useStigmer } from "../hooks";
|
|
9
|
+
import { resolveSystemEnvVarValues } from "../environment/systemEnvVars";
|
|
9
10
|
import { toError } from "../internal/toError";
|
|
10
11
|
|
|
11
12
|
/** Return value of {@link useMcpServerConnect}. */
|
|
@@ -18,13 +19,15 @@ export interface UseMcpServerConnectReturn {
|
|
|
18
19
|
* agent-runner. The RPC blocks until the workflow completes
|
|
19
20
|
* (typically 5-15 seconds, ~30s timeout).
|
|
20
21
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
22
|
+
* Platform system env vars (`STIGMER_SERVER_ADDRESS`,
|
|
23
|
+
* `STIGMER_API_KEY`) are always injected automatically from
|
|
24
|
+
* the current SDK context. When `runtimeEnv` is also provided,
|
|
25
|
+
* those values are merged on top (caller values win). The backend
|
|
26
|
+
* merges the result on top of the user's personal environment so
|
|
27
|
+
* saved credentials (e.g., OAuth tokens) are still resolved.
|
|
25
28
|
*
|
|
26
29
|
* @param mcpServerId - System-generated ID of the MCP server (metadata.id).
|
|
27
|
-
* @param runtimeEnv - Optional
|
|
30
|
+
* @param runtimeEnv - Optional additional environment variables.
|
|
28
31
|
* @returns The updated McpServer with populated status.discovered_capabilities
|
|
29
32
|
* and status.tool_approvals.
|
|
30
33
|
*/
|
|
@@ -48,13 +51,14 @@ export interface UseMcpServerConnectReturn {
|
|
|
48
51
|
* server's tools and resource templates, then classifies each tool's
|
|
49
52
|
* approval policy via a structured-output LLM call.
|
|
50
53
|
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
54
|
+
* Platform system env vars (`STIGMER_SERVER_ADDRESS`,
|
|
55
|
+
* `STIGMER_API_KEY`) are always injected from the current SDK
|
|
56
|
+
* context. The backend merges these on top of the caller's personal
|
|
57
|
+
* environment, so saved credentials (e.g., OAuth tokens) are still
|
|
58
|
+
* resolved while platform addresses are always up to date.
|
|
59
|
+
*
|
|
60
|
+
* Additional one-time credentials can be passed via `runtimeEnv`
|
|
61
|
+
* and will override both system vars and personal env values.
|
|
58
62
|
*
|
|
59
63
|
* @example
|
|
60
64
|
* ```tsx
|
|
@@ -90,21 +94,20 @@ export function useMcpServerConnect(): UseMcpServerConnectReturn {
|
|
|
90
94
|
setError(null);
|
|
91
95
|
|
|
92
96
|
try {
|
|
97
|
+
const systemEnv = await resolveSystemEnvVarValues(stigmer);
|
|
98
|
+
const mergedEnv = { ...systemEnv, ...(runtimeEnv ?? {}) };
|
|
99
|
+
|
|
93
100
|
const runtimeEnvMap: Record<string, { value: string; isSecret: boolean }> = {};
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
+
for (const [key, envInput] of Object.entries(mergedEnv)) {
|
|
102
|
+
runtimeEnvMap[key] = {
|
|
103
|
+
value: envInput.value,
|
|
104
|
+
isSecret: envInput.isSecret ?? false,
|
|
105
|
+
};
|
|
101
106
|
}
|
|
102
107
|
|
|
103
108
|
const input = create(ConnectInputSchema, {
|
|
104
109
|
mcpServerId,
|
|
105
|
-
|
|
106
|
-
runtimeEnv: runtimeEnvMap,
|
|
107
|
-
}),
|
|
110
|
+
runtimeEnv: runtimeEnvMap,
|
|
108
111
|
});
|
|
109
112
|
|
|
110
113
|
return await stigmer.mcpServer.connect(input);
|
|
@@ -4,9 +4,10 @@ import { useCallback, useMemo } from "react";
|
|
|
4
4
|
import type { EnvVarInput } from "@stigmer/sdk";
|
|
5
5
|
import type { McpServer } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
|
|
6
6
|
import { usePersonalEnvironment } from "../environment/usePersonalEnvironment";
|
|
7
|
-
import {
|
|
7
|
+
import { diffEnv } from "../environment/diffEnv";
|
|
8
8
|
import { SYSTEM_ENV_VAR_KEYS } from "../environment/systemEnvVars";
|
|
9
9
|
import type { EnvVarFormVariable } from "../environment/EnvVarForm";
|
|
10
|
+
import { useOAuthGrantStatus } from "./useOAuthGrantStatus";
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Credential acquisition mode for an MCP server.
|
|
@@ -34,37 +35,50 @@ export interface UseMcpServerCredentialsReturn {
|
|
|
34
35
|
*/
|
|
35
36
|
readonly oauthTargetEnvVar: string | null;
|
|
36
37
|
/**
|
|
37
|
-
* `true` when the
|
|
38
|
-
*
|
|
38
|
+
* `true` when the user has an active OAuth grant for this server.
|
|
39
|
+
* Derived from the `getOAuthGrantStatus` API, not from personal
|
|
40
|
+
* environment key presence. Always `false` when `authMode` is `"manual"`.
|
|
39
41
|
*/
|
|
40
42
|
readonly isOAuthConnected: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* When the OAuth access token expires (Unix timestamp seconds).
|
|
45
|
+
* `BigInt(0)` when no grant exists, `authMode` is `"manual"`, or the token
|
|
46
|
+
* does not expire. Useful for showing actual expiry in the UI.
|
|
47
|
+
*/
|
|
48
|
+
readonly accessTokenExpiresAt: bigint;
|
|
41
49
|
/**
|
|
42
50
|
* Informational hint about expected token lifetime, or `null`.
|
|
43
51
|
* Sourced from `spec.auth.token_lifetime_hint`.
|
|
44
52
|
*/
|
|
45
53
|
readonly tokenLifetimeHint: string | null;
|
|
46
54
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
55
|
+
* Required variables (non-optional) missing from the user's personal
|
|
56
|
+
* environment. Empty when all required variables are present, the
|
|
57
|
+
* server has no `env` declarations, or all declarations are optional.
|
|
50
58
|
*
|
|
51
59
|
* When `authMode` is `"oauth"`, the OAuth-managed `target_env_var`
|
|
52
60
|
* is excluded from this list — it is acquired via the OAuth flow,
|
|
53
|
-
* not via a manual form. Only additional non-OAuth vars
|
|
61
|
+
* not via a manual form. Only additional non-OAuth required vars
|
|
62
|
+
* appear here.
|
|
63
|
+
*
|
|
64
|
+
* Optional env vars are never included — they are discoverable in
|
|
65
|
+
* the read-only EnvSection but do not block connect.
|
|
54
66
|
*
|
|
55
67
|
* Suitable as direct input to {@link EnvVarForm}.
|
|
56
68
|
*/
|
|
57
69
|
readonly missingVariables: EnvVarFormVariable[];
|
|
58
70
|
/**
|
|
59
|
-
* `true` when all required credentials are available
|
|
60
|
-
* OAuth-managed and manual variables. For OAuth servers this
|
|
61
|
-
* the OAuth
|
|
62
|
-
* vars are
|
|
71
|
+
* `true` when all required (non-optional) credentials are available
|
|
72
|
+
* — both OAuth-managed and manual variables. For OAuth servers this
|
|
73
|
+
* means the OAuth grant is connected AND any additional required
|
|
74
|
+
* manual vars are present in the personal environment.
|
|
75
|
+
*
|
|
76
|
+
* Servers whose env vars are all optional are always ready.
|
|
63
77
|
*/
|
|
64
78
|
readonly isReady: boolean;
|
|
65
|
-
/** `true` while the personal environment is being fetched. */
|
|
79
|
+
/** `true` while the personal environment or grant status is being fetched. */
|
|
66
80
|
readonly isLoading: boolean;
|
|
67
|
-
/** Error from the personal environment fetch, or `null`. */
|
|
81
|
+
/** Error from the personal environment or grant status fetch, or `null`. */
|
|
68
82
|
readonly error: Error | null;
|
|
69
83
|
/**
|
|
70
84
|
* Save the provided credentials to the user's personal environment.
|
|
@@ -81,7 +95,7 @@ export interface UseMcpServerCredentialsReturn {
|
|
|
81
95
|
|
|
82
96
|
/**
|
|
83
97
|
* Checks the user's personal environment against an MCP server's
|
|
84
|
-
* `
|
|
98
|
+
* `env` declarations and provides a mechanism to save missing credentials.
|
|
85
99
|
*
|
|
86
100
|
* Designed for the discovery flow on the MCP server detail page:
|
|
87
101
|
* before triggering discovery, the UI needs to ensure all required
|
|
@@ -90,10 +104,12 @@ export interface UseMcpServerCredentialsReturn {
|
|
|
90
104
|
* them.
|
|
91
105
|
*
|
|
92
106
|
* **Auth-mode-aware**: when `spec.auth` is configured, the hook
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
107
|
+
* composes {@link useOAuthGrantStatus} to determine whether the
|
|
108
|
+
* OAuth-managed variable (`target_env_var`) is connected via an
|
|
109
|
+
* active grant. The OAuth variable is excluded from `missingVariables`
|
|
110
|
+
* — it is acquired via {@link useMcpServerOAuthConnect}, not a manual
|
|
111
|
+
* form. Additional non-OAuth vars still appear in `missingVariables`
|
|
112
|
+
* (mixed mode).
|
|
97
113
|
*
|
|
98
114
|
* Unlike {@link useMcpServerSetup} which manages multi-server setup
|
|
99
115
|
* for session creation, this hook is scoped to a single server and
|
|
@@ -134,32 +150,43 @@ export function useMcpServerCredentials(
|
|
|
134
150
|
const oauthTargetEnvVar = auth?.targetEnvVar || null;
|
|
135
151
|
const tokenLifetimeHint = auth?.tokenLifetimeHint || null;
|
|
136
152
|
|
|
153
|
+
const grantStatus = useOAuthGrantStatus(
|
|
154
|
+
authMode === "oauth" ? (mcpServer?.metadata?.id ?? null) : null,
|
|
155
|
+
authMode === "oauth" ? org : null,
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
const isOAuthConnected = authMode === "oauth" && grantStatus.connected;
|
|
159
|
+
|
|
137
160
|
const existingKeys = useMemo(
|
|
138
161
|
() => new Set(Object.keys(personalEnv.environment?.spec?.data ?? {})),
|
|
139
162
|
[personalEnv.environment],
|
|
140
163
|
);
|
|
141
164
|
|
|
142
|
-
const isOAuthConnected = authMode === "oauth"
|
|
143
|
-
&& oauthTargetEnvVar !== null
|
|
144
|
-
&& existingKeys.has(oauthTargetEnvVar);
|
|
145
|
-
|
|
146
165
|
const allMissingVariables = useMemo(() => {
|
|
147
166
|
if (!mcpServer) return [];
|
|
148
|
-
const
|
|
149
|
-
if (!
|
|
167
|
+
const envDeclarations = mcpServer.spec?.env;
|
|
168
|
+
if (!envDeclarations || Object.keys(envDeclarations).length === 0) return [];
|
|
150
169
|
|
|
151
|
-
return
|
|
170
|
+
return diffEnv(envDeclarations, existingKeys).filter(
|
|
152
171
|
(v) => !SYSTEM_ENV_VAR_KEYS.has(v.key),
|
|
153
172
|
);
|
|
154
173
|
}, [mcpServer, existingKeys]);
|
|
155
174
|
|
|
175
|
+
const requiredMissing = useMemo(
|
|
176
|
+
() => allMissingVariables.filter((v) => !v.optional),
|
|
177
|
+
[allMissingVariables],
|
|
178
|
+
);
|
|
179
|
+
|
|
156
180
|
const missingVariables = useMemo(() => {
|
|
157
|
-
if (!oauthTargetEnvVar) return
|
|
158
|
-
return
|
|
159
|
-
}, [
|
|
181
|
+
if (!oauthTargetEnvVar) return requiredMissing;
|
|
182
|
+
return requiredMissing.filter((v) => v.key !== oauthTargetEnvVar);
|
|
183
|
+
}, [requiredMissing, oauthTargetEnvVar]);
|
|
160
184
|
|
|
161
185
|
const isReady =
|
|
162
|
-
!personalEnv.isLoading &&
|
|
186
|
+
!personalEnv.isLoading &&
|
|
187
|
+
!grantStatus.isLoading &&
|
|
188
|
+
missingVariables.length === 0 &&
|
|
189
|
+
(authMode === "manual" || isOAuthConnected);
|
|
163
190
|
|
|
164
191
|
const saveCredentials = useCallback(
|
|
165
192
|
async (values: Record<string, EnvVarInput>): Promise<void> => {
|
|
@@ -169,17 +196,23 @@ export function useMcpServerCredentials(
|
|
|
169
196
|
[personalEnv],
|
|
170
197
|
);
|
|
171
198
|
|
|
199
|
+
const refetch = useCallback(() => {
|
|
200
|
+
personalEnv.refetch();
|
|
201
|
+
grantStatus.refetch();
|
|
202
|
+
}, [personalEnv, grantStatus]);
|
|
203
|
+
|
|
172
204
|
return {
|
|
173
205
|
authMode,
|
|
174
206
|
oauthTargetEnvVar,
|
|
175
207
|
isOAuthConnected,
|
|
208
|
+
accessTokenExpiresAt: grantStatus.accessTokenExpiresAt,
|
|
176
209
|
tokenLifetimeHint,
|
|
177
210
|
missingVariables,
|
|
178
211
|
isReady,
|
|
179
|
-
isLoading: personalEnv.isLoading,
|
|
180
|
-
error: personalEnv.error,
|
|
212
|
+
isLoading: personalEnv.isLoading || grantStatus.isLoading,
|
|
213
|
+
error: personalEnv.error ?? grantStatus.error,
|
|
181
214
|
saveCredentials,
|
|
182
215
|
isSaving: personalEnv.isMutating,
|
|
183
|
-
refetch
|
|
216
|
+
refetch,
|
|
184
217
|
};
|
|
185
218
|
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
ConnectInputSchema,
|
|
10
10
|
} from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/io_pb";
|
|
11
11
|
import { useStigmer } from "../hooks";
|
|
12
|
+
import { resolveSystemEnvVarValues } from "../environment/systemEnvVars";
|
|
12
13
|
import { toError } from "../internal/toError";
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -53,14 +54,15 @@ export interface UseMcpServerOAuthConnectReturn {
|
|
|
53
54
|
* blockers.
|
|
54
55
|
*
|
|
55
56
|
* @param mcpServerId - System-generated ID (metadata.id) of the MCP server.
|
|
57
|
+
* @param org - Organization context for token storage (caller's active org).
|
|
56
58
|
* @returns The updated McpServer after tool discovery completes.
|
|
57
59
|
*/
|
|
58
|
-
readonly startOAuth: (mcpServerId: string) => Promise<McpServer>;
|
|
60
|
+
readonly startOAuth: (mcpServerId: string, org: string) => Promise<McpServer>;
|
|
59
61
|
/** `true` while any phase of the OAuth flow is in progress. */
|
|
60
62
|
readonly isInProgress: boolean;
|
|
61
63
|
/** Current phase of the OAuth flow. */
|
|
62
64
|
readonly phase: OAuthConnectPhase;
|
|
63
|
-
/** Error from the most recent
|
|
65
|
+
/** Error from the most recent unsuccessful attempt, or `null`. */
|
|
64
66
|
readonly error: Error | null;
|
|
65
67
|
/** Reset the hook to idle state, clearing any error. */
|
|
66
68
|
readonly clearError: () => void;
|
|
@@ -91,7 +93,7 @@ const POPUP_CALLBACK_TIMEOUT_MS = 120_000;
|
|
|
91
93
|
*
|
|
92
94
|
* async function handleSignIn() {
|
|
93
95
|
* try {
|
|
94
|
-
* await oauth.startOAuth(mcpServer.metadata.id);
|
|
96
|
+
* await oauth.startOAuth(mcpServer.metadata.id, org);
|
|
95
97
|
* refetch();
|
|
96
98
|
* } catch {
|
|
97
99
|
* // error is available via oauth.error
|
|
@@ -123,7 +125,7 @@ export function useMcpServerOAuthConnect(): UseMcpServerOAuthConnectReturn {
|
|
|
123
125
|
}, []);
|
|
124
126
|
|
|
125
127
|
const startOAuth = useCallback(
|
|
126
|
-
async (mcpServerId: string): Promise<McpServer> => {
|
|
128
|
+
async (mcpServerId: string, org: string): Promise<McpServer> => {
|
|
127
129
|
setPhase("initiating");
|
|
128
130
|
setError(null);
|
|
129
131
|
|
|
@@ -151,7 +153,7 @@ export function useMcpServerOAuthConnect(): UseMcpServerOAuthConnectReturn {
|
|
|
151
153
|
|
|
152
154
|
try {
|
|
153
155
|
const initOutput = await stigmer.mcpServer.initiateOAuthConnect(
|
|
154
|
-
create(InitiateOAuthConnectInputSchema, { mcpServerId }),
|
|
156
|
+
create(InitiateOAuthConnectInputSchema, { mcpServerId, org }),
|
|
155
157
|
);
|
|
156
158
|
|
|
157
159
|
popup.location.href = initOutput.authorizationUrl;
|
|
@@ -177,8 +179,20 @@ export function useMcpServerOAuthConnect(): UseMcpServerOAuthConnectReturn {
|
|
|
177
179
|
|
|
178
180
|
setPhase("connecting");
|
|
179
181
|
|
|
182
|
+
const systemEnv = await resolveSystemEnvVarValues(stigmer);
|
|
183
|
+
const runtimeEnvMap: Record<string, { value: string; isSecret: boolean }> = {};
|
|
184
|
+
for (const [key, envInput] of Object.entries(systemEnv)) {
|
|
185
|
+
runtimeEnvMap[key] = {
|
|
186
|
+
value: envInput.value,
|
|
187
|
+
isSecret: envInput.isSecret ?? false,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
180
191
|
const server = await stigmer.mcpServer.connect(
|
|
181
|
-
create(ConnectInputSchema, {
|
|
192
|
+
create(ConnectInputSchema, {
|
|
193
|
+
mcpServerId,
|
|
194
|
+
runtimeEnv: runtimeEnvMap,
|
|
195
|
+
}),
|
|
182
196
|
);
|
|
183
197
|
|
|
184
198
|
setPhase("done");
|
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
import { useCallback, useEffect, useMemo, useReducer, useRef } from "react";
|
|
4
4
|
import type { EnvVarInput, McpServerUsageInput, ResourceRef } from "@stigmer/sdk";
|
|
5
|
+
import { create } from "@bufbuild/protobuf";
|
|
5
6
|
import type { McpServer } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
|
|
7
|
+
import { GetOAuthGrantStatusInputSchema } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/io_pb";
|
|
6
8
|
import type { DiscoveredTool } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/status_pb";
|
|
7
9
|
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
8
10
|
import { useStigmer } from "../hooks";
|
|
9
11
|
import { usePersonalEnvironment } from "../environment/usePersonalEnvironment";
|
|
10
|
-
import {
|
|
12
|
+
import { diffEnv } from "../environment/diffEnv";
|
|
11
13
|
import { toError } from "../internal/toError";
|
|
12
14
|
import {
|
|
13
15
|
mcpServerSetupReducer,
|
|
@@ -57,8 +59,8 @@ export interface UseMcpServerSetupReturn {
|
|
|
57
59
|
/**
|
|
58
60
|
* Add an MCP server to the setup flow.
|
|
59
61
|
*
|
|
60
|
-
* Fetches the full server resource, checks `
|
|
61
|
-
* personal environment, and resolves the entry to either `ready`
|
|
62
|
+
* Fetches the full server resource, checks `env` declarations against
|
|
63
|
+
* the personal environment, and resolves the entry to either `ready`
|
|
62
64
|
* (no credentials needed or all present) or `needsSetup` (missing
|
|
63
65
|
* variables). Also extracts discovered tools and approval policies
|
|
64
66
|
* for the tool selector.
|
|
@@ -171,8 +173,8 @@ function computeDefaultEnabledTools(
|
|
|
171
173
|
* MCP servers selected in the {@link McpServerPicker}.
|
|
172
174
|
*
|
|
173
175
|
* When a user toggles an MCP server ON, this hook fetches the server's
|
|
174
|
-
* full resource, checks its `
|
|
175
|
-
* (via {@link
|
|
176
|
+
* full resource, checks its `env` declarations against the personal
|
|
177
|
+
* environment (via {@link diffEnv}), and determines whether credentials are
|
|
176
178
|
* needed. It also extracts discovered tools and approval policies for
|
|
177
179
|
* the tool selector UI.
|
|
178
180
|
*
|
|
@@ -198,7 +200,7 @@ function computeDefaultEnabledTools(
|
|
|
198
200
|
* @param org - Organization slug. Pass `null` to disable.
|
|
199
201
|
* @param poolKeys - Optional set of env-var keys already available
|
|
200
202
|
* from the session env pool (manual secrets, one-time env vars from
|
|
201
|
-
* other components). When provided, servers whose `
|
|
203
|
+
* other components). When provided, servers whose `env` keys
|
|
202
204
|
* are fully covered by `poolKeys` + personal env auto-resolve to
|
|
203
205
|
* `ready` without prompting. Reactive — when `poolKeys` changes,
|
|
204
206
|
* `needsSetup` entries are re-evaluated.
|
|
@@ -264,9 +266,9 @@ export function useMcpServerSetup(
|
|
|
264
266
|
const discoveredTools =
|
|
265
267
|
mcpServer.status?.discoveredCapabilities?.tools ?? [];
|
|
266
268
|
const toolApprovals = mcpServer.spec?.pinnedToolApprovals ?? [];
|
|
267
|
-
const
|
|
269
|
+
const envDeclarations = mcpServer.spec?.env;
|
|
268
270
|
|
|
269
|
-
if (!
|
|
271
|
+
if (!envDeclarations || Object.keys(envDeclarations).length === 0) {
|
|
270
272
|
dispatch({
|
|
271
273
|
type: "RESOLVE_READY",
|
|
272
274
|
key,
|
|
@@ -284,9 +286,29 @@ export function useMcpServerSetup(
|
|
|
284
286
|
const existingKeys = new Set(
|
|
285
287
|
Object.keys(personalEnv.environment?.spec?.data ?? {}),
|
|
286
288
|
);
|
|
287
|
-
const missingVariables = diffEnvSpec(envSpecData, existingKeys, poolKeys);
|
|
288
289
|
|
|
289
|
-
|
|
290
|
+
const auth = mcpServer.spec?.auth;
|
|
291
|
+
if (auth?.targetEnvVar && mcpServer.metadata?.id) {
|
|
292
|
+
try {
|
|
293
|
+
const grantStatus = await stigmer.mcpServer.getOAuthGrantStatus(
|
|
294
|
+
create(GetOAuthGrantStatusInputSchema, {
|
|
295
|
+
resourceId: mcpServer.metadata.id,
|
|
296
|
+
org,
|
|
297
|
+
}),
|
|
298
|
+
);
|
|
299
|
+
if (grantStatus.connected) {
|
|
300
|
+
existingKeys.add(auth.targetEnvVar);
|
|
301
|
+
}
|
|
302
|
+
} catch {
|
|
303
|
+
// Non-fatal: if grant status lookup fails, the OAuth var stays
|
|
304
|
+
// in missingVariables and the UI shows the sign-in button.
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const allMissing = diffEnv(envDeclarations, existingKeys, poolKeys);
|
|
309
|
+
const requiredMissing = allMissing.filter((v) => !v.optional);
|
|
310
|
+
|
|
311
|
+
if (requiredMissing.length === 0) {
|
|
290
312
|
dispatch({
|
|
291
313
|
type: "RESOLVE_READY",
|
|
292
314
|
key,
|
|
@@ -305,7 +327,7 @@ export function useMcpServerSetup(
|
|
|
305
327
|
type: "RESOLVE_NEEDS_SETUP",
|
|
306
328
|
key,
|
|
307
329
|
mcpServer,
|
|
308
|
-
missingVariables,
|
|
330
|
+
missingVariables: requiredMissing,
|
|
309
331
|
discoveredTools,
|
|
310
332
|
toolApprovals,
|
|
311
333
|
});
|
|
@@ -420,15 +442,16 @@ export function useMcpServerSetup(
|
|
|
420
442
|
for (const [key, entry] of Object.entries(entriesRef.current)) {
|
|
421
443
|
if (entry.status !== "needsSetup") continue;
|
|
422
444
|
|
|
423
|
-
const
|
|
424
|
-
if (!
|
|
445
|
+
const envDeclarations = entry.mcpServer.spec?.env;
|
|
446
|
+
if (!envDeclarations) continue;
|
|
425
447
|
|
|
426
|
-
const
|
|
448
|
+
const allMissing = diffEnv(envDeclarations, personalKeys, poolKeys);
|
|
449
|
+
const requiredMissing = allMissing.filter((v) => !v.optional);
|
|
427
450
|
const enabledTools = computeDefaultEnabledTools(
|
|
428
451
|
entry.mcpServer,
|
|
429
452
|
entry.discoveredTools,
|
|
430
453
|
);
|
|
431
|
-
dispatch({ type: "POOL_RESOLVE", key, missingVariables, enabledTools });
|
|
454
|
+
dispatch({ type: "POOL_RESOLVE", key, missingVariables: requiredMissing, enabledTools });
|
|
432
455
|
}
|
|
433
456
|
}, [poolKeys, personalEnv.environment]);
|
|
434
457
|
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useState } from "react";
|
|
4
|
+
import { create } from "@bufbuild/protobuf";
|
|
5
|
+
import { GetOAuthGrantStatusInputSchema } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/io_pb";
|
|
6
|
+
import { useStigmer } from "../hooks";
|
|
7
|
+
import { toError } from "../internal/toError";
|
|
8
|
+
|
|
9
|
+
/** Return value of {@link useOAuthGrantStatus}. */
|
|
10
|
+
export interface UseOAuthGrantStatusReturn {
|
|
11
|
+
/** Whether the user has an active OAuth grant for this resource + org. */
|
|
12
|
+
readonly connected: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* When the access token expires (Unix timestamp seconds).
|
|
15
|
+
* `BigInt(0)` when no grant exists or the token does not expire.
|
|
16
|
+
*/
|
|
17
|
+
readonly accessTokenExpiresAt: bigint;
|
|
18
|
+
/** The env var name managed by OAuth, or empty string when no grant exists. */
|
|
19
|
+
readonly targetEnvVar: string;
|
|
20
|
+
/** Auth method used (`"mcp_oauth"` or `"vendor_oauth"`), or empty string. */
|
|
21
|
+
readonly authMethod: string;
|
|
22
|
+
/** `true` while the grant status is being fetched. */
|
|
23
|
+
readonly isLoading: boolean;
|
|
24
|
+
/** Error from the last failed request, or `null` when healthy. */
|
|
25
|
+
readonly error: Error | null;
|
|
26
|
+
/** Discard cached data and re-fetch the grant status. */
|
|
27
|
+
readonly refetch: () => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const BIGINT_ZERO = BigInt(0);
|
|
31
|
+
|
|
32
|
+
const IDLE: UseOAuthGrantStatusReturn = {
|
|
33
|
+
connected: false,
|
|
34
|
+
accessTokenExpiresAt: BIGINT_ZERO,
|
|
35
|
+
targetEnvVar: "",
|
|
36
|
+
authMethod: "",
|
|
37
|
+
isLoading: false,
|
|
38
|
+
error: null,
|
|
39
|
+
refetch: () => {},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Data hook that fetches the OAuth grant status for a single MCP server.
|
|
44
|
+
*
|
|
45
|
+
* Wraps `stigmer.mcpServer.getOAuthGrantStatus()` with loading, error,
|
|
46
|
+
* and idle state management. When `resourceId` or `org` changes, the
|
|
47
|
+
* previous in-flight request is discarded and a fresh fetch begins.
|
|
48
|
+
*
|
|
49
|
+
* Pass `null` for either parameter to skip fetching (stable no-op).
|
|
50
|
+
* This is useful when the MCP server resource has not loaded yet.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```tsx
|
|
54
|
+
* const grantStatus = useOAuthGrantStatus(mcpServer?.metadata?.id ?? null, org);
|
|
55
|
+
*
|
|
56
|
+
* if (grantStatus.isLoading) return <Spinner />;
|
|
57
|
+
* if (grantStatus.connected) return <span>Connected via OAuth</span>;
|
|
58
|
+
* return <button onClick={startOAuth}>Sign in</button>;
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export function useOAuthGrantStatus(
|
|
62
|
+
resourceId: string | null,
|
|
63
|
+
org: string | null,
|
|
64
|
+
): UseOAuthGrantStatusReturn {
|
|
65
|
+
const stigmer = useStigmer();
|
|
66
|
+
const [connected, setConnected] = useState(false);
|
|
67
|
+
const [accessTokenExpiresAt, setAccessTokenExpiresAt] = useState(BIGINT_ZERO);
|
|
68
|
+
const [targetEnvVar, setTargetEnvVar] = useState("");
|
|
69
|
+
const [authMethod, setAuthMethod] = useState("");
|
|
70
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
71
|
+
const [error, setError] = useState<Error | null>(null);
|
|
72
|
+
const [fetchKey, setFetchKey] = useState(0);
|
|
73
|
+
|
|
74
|
+
const refetch = useCallback(() => setFetchKey((k) => k + 1), []);
|
|
75
|
+
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (!resourceId || !org) {
|
|
78
|
+
setConnected(false);
|
|
79
|
+
setAccessTokenExpiresAt(BIGINT_ZERO);
|
|
80
|
+
setTargetEnvVar("");
|
|
81
|
+
setAuthMethod("");
|
|
82
|
+
setIsLoading(false);
|
|
83
|
+
setError(null);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const cancelled = { current: false };
|
|
88
|
+
setIsLoading(true);
|
|
89
|
+
setError(null);
|
|
90
|
+
|
|
91
|
+
stigmer.mcpServer
|
|
92
|
+
.getOAuthGrantStatus(create(GetOAuthGrantStatusInputSchema, { resourceId, org }))
|
|
93
|
+
.then(
|
|
94
|
+
(result) => {
|
|
95
|
+
if (cancelled.current) return;
|
|
96
|
+
setConnected(result.connected);
|
|
97
|
+
setAccessTokenExpiresAt(result.accessTokenExpiresAt);
|
|
98
|
+
setTargetEnvVar(result.targetEnvVar);
|
|
99
|
+
setAuthMethod(result.authMethod);
|
|
100
|
+
setIsLoading(false);
|
|
101
|
+
},
|
|
102
|
+
(err) => {
|
|
103
|
+
if (cancelled.current) return;
|
|
104
|
+
setError(toError(err));
|
|
105
|
+
setIsLoading(false);
|
|
106
|
+
},
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
return () => {
|
|
110
|
+
cancelled.current = true;
|
|
111
|
+
};
|
|
112
|
+
}, [resourceId, org, stigmer, fetchKey]);
|
|
113
|
+
|
|
114
|
+
if (!resourceId || !org) return { ...IDLE, refetch };
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
connected,
|
|
118
|
+
accessTokenExpiresAt,
|
|
119
|
+
targetEnvVar,
|
|
120
|
+
authMethod,
|
|
121
|
+
isLoading,
|
|
122
|
+
error,
|
|
123
|
+
refetch,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"diffEnvSpec.d.ts","sourceRoot":"","sources":["../../src/environment/diffEnvSpec.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,WAAW,CACzB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,EACxE,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,EACzB,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GACrB,kBAAkB,EAAE,CAetB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"diffEnvSpec.js","sourceRoot":"","sources":["../../src/environment/diffEnvSpec.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,UAAU,WAAW,CACzB,WAAwE,EACxE,YAAyB,EACzB,QAAsB;IAEtB,MAAM,OAAO,GAAyB,EAAE,CAAC;IAEzC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;QACvD,IAAI,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAS;QACpC,IAAI,QAAQ,EAAE,GAAG,CAAC,GAAG,CAAC;YAAE,SAAS;QAEjC,OAAO,CAAC,IAAI,CAAC;YACX,GAAG;YACH,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,GAAG,CAAC,KAAK,CAAC,WAAW,IAAI,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC;SAC7D,CAAC,CAAC;IACL,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
|