@stigmer/react 0.0.79 → 0.0.81
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 +2 -2
- package/environment/index.d.ts.map +1 -1
- package/environment/index.js +2 -2
- package/environment/index.js.map +1 -1
- package/environment/systemEnvVars.d.ts +16 -0
- package/environment/systemEnvVars.d.ts.map +1 -1
- package/environment/systemEnvVars.js +32 -0
- package/environment/systemEnvVars.js.map +1 -1
- package/execution/SessionVariablesInput.d.ts +1 -1
- package/index.d.ts +4 -4
- package/index.d.ts.map +1 -1
- package/index.js +4 -4
- 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 +34 -9
- 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/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 +22 -12
- package/mcp-server/useMcpServerConnect.d.ts.map +1 -1
- package/mcp-server/useMcpServerConnect.js +17 -10
- 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 +3 -1
- package/mcp-server/useMcpServerOAuthConnect.d.ts.map +1 -1
- package/mcp-server/useMcpServerOAuthConnect.js +12 -6
- 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 +2 -1
- package/src/environment/systemEnvVars.ts +39 -0
- package/src/execution/SessionVariablesInput.tsx +1 -1
- package/src/index.ts +5 -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 +46 -10
- package/src/mcp-server/McpServerPicker.tsx +9 -1
- package/src/mcp-server/index.ts +3 -0
- package/src/mcp-server/mcpServerSetupReducer.ts +4 -4
- package/src/mcp-server/useMcpServerConnect.ts +33 -14
- package/src/mcp-server/useMcpServerCredentials.ts +65 -32
- package/src/mcp-server/useMcpServerOAuthConnect.ts +17 -10
- 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
|
@@ -10,7 +10,7 @@ import type {
|
|
|
10
10
|
} from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/status_pb";
|
|
11
11
|
import type { ToolApprovalPolicy, McpServerSpec } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/spec_pb";
|
|
12
12
|
import { ValidationState } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/status_pb";
|
|
13
|
-
import type {
|
|
13
|
+
import type { EnvVarDeclaration } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/spec_pb";
|
|
14
14
|
import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
|
|
15
15
|
import { useMcpServer } from "./useMcpServer";
|
|
16
16
|
import { useMcpServerConnect } from "./useMcpServerConnect";
|
|
@@ -73,6 +73,13 @@ export interface McpServerDetailViewProps {
|
|
|
73
73
|
readonly credentialPoolValues?: (
|
|
74
74
|
key: string,
|
|
75
75
|
) => import("@stigmer/sdk").EnvVarInput | undefined;
|
|
76
|
+
/**
|
|
77
|
+
* The authenticated user's active organization slug.
|
|
78
|
+
* Used for OAuth token storage — tokens are stored in the user's personal
|
|
79
|
+
* environment within this org, not the MCP server's org.
|
|
80
|
+
* When omitted, falls back to the `org` prop (MCP server's org).
|
|
81
|
+
*/
|
|
82
|
+
readonly activeOrg?: string;
|
|
76
83
|
/** Additional CSS classes for the root container. */
|
|
77
84
|
readonly className?: string;
|
|
78
85
|
}
|
|
@@ -110,6 +117,7 @@ export function McpServerDetailView({
|
|
|
110
117
|
defaultCapabilityTab = "tools",
|
|
111
118
|
defaultShowCredentialForm = false,
|
|
112
119
|
credentialPoolValues,
|
|
120
|
+
activeOrg,
|
|
113
121
|
className,
|
|
114
122
|
}: McpServerDetailViewProps) {
|
|
115
123
|
const { mcpServer, isLoading, error, refetch } = useMcpServer(org, slug);
|
|
@@ -132,8 +140,9 @@ export function McpServerDetailView({
|
|
|
132
140
|
const handleOAuthSignIn = useCallback(async () => {
|
|
133
141
|
if (!mcpServer?.metadata?.id) return;
|
|
134
142
|
|
|
143
|
+
const envKeys = Object.keys(mcpServer.spec?.env ?? {});
|
|
135
144
|
try {
|
|
136
|
-
await oauth.startOAuth(mcpServer.metadata.id, org);
|
|
145
|
+
await oauth.startOAuth(mcpServer.metadata.id, activeOrg ?? org, envKeys);
|
|
137
146
|
credentials.refetch();
|
|
138
147
|
refetch();
|
|
139
148
|
} catch {
|
|
@@ -154,8 +163,9 @@ export function McpServerDetailView({
|
|
|
154
163
|
return;
|
|
155
164
|
}
|
|
156
165
|
|
|
166
|
+
const envKeys = Object.keys(mcpServer.spec?.env ?? {});
|
|
157
167
|
try {
|
|
158
|
-
await connection.connect(mcpServer.metadata.id);
|
|
168
|
+
await connection.connect(mcpServer.metadata.id, activeOrg ?? org, undefined, envKeys);
|
|
159
169
|
refetch();
|
|
160
170
|
} catch {
|
|
161
171
|
// error state is managed by the hook
|
|
@@ -176,10 +186,12 @@ export function McpServerDetailView({
|
|
|
176
186
|
setShowCredentialForm(false);
|
|
177
187
|
|
|
178
188
|
if (mcpServer?.metadata?.id) {
|
|
189
|
+
const envKeys = Object.keys(mcpServer.spec?.env ?? {});
|
|
190
|
+
const connectOrg = activeOrg ?? org;
|
|
179
191
|
if (options.saveForFuture) {
|
|
180
|
-
await connection.connect(mcpServer.metadata.id);
|
|
192
|
+
await connection.connect(mcpServer.metadata.id, connectOrg, undefined, envKeys);
|
|
181
193
|
} else {
|
|
182
|
-
await connection.connect(mcpServer.metadata.id, values);
|
|
194
|
+
await connection.connect(mcpServer.metadata.id, connectOrg, values, envKeys);
|
|
183
195
|
}
|
|
184
196
|
refetch();
|
|
185
197
|
}
|
|
@@ -258,9 +270,9 @@ export function McpServerDetailView({
|
|
|
258
270
|
<ServerConfigSection serverType={spec.serverType} />
|
|
259
271
|
)}
|
|
260
272
|
|
|
261
|
-
{spec?.
|
|
262
|
-
<
|
|
263
|
-
data={spec.
|
|
273
|
+
{spec?.env && Object.keys(spec.env).length > 0 && (
|
|
274
|
+
<EnvSection
|
|
275
|
+
data={spec.env}
|
|
264
276
|
oauthTargetEnvVar={credentials.oauthTargetEnvVar}
|
|
265
277
|
/>
|
|
266
278
|
)}
|
|
@@ -278,6 +290,7 @@ export function McpServerDetailView({
|
|
|
278
290
|
oauthPhase={oauth.phase}
|
|
279
291
|
authMode={credentials.authMode}
|
|
280
292
|
isOAuthConnected={credentials.isOAuthConnected}
|
|
293
|
+
accessTokenExpiresAt={credentials.accessTokenExpiresAt}
|
|
281
294
|
tokenLifetimeHint={credentials.tokenLifetimeHint}
|
|
282
295
|
/>
|
|
283
296
|
|
|
@@ -346,6 +359,7 @@ function ConnectBar({
|
|
|
346
359
|
oauthPhase,
|
|
347
360
|
authMode,
|
|
348
361
|
isOAuthConnected,
|
|
362
|
+
accessTokenExpiresAt,
|
|
349
363
|
tokenLifetimeHint,
|
|
350
364
|
}: {
|
|
351
365
|
readonly isConnecting: boolean;
|
|
@@ -359,6 +373,7 @@ function ConnectBar({
|
|
|
359
373
|
readonly oauthPhase: OAuthConnectPhase;
|
|
360
374
|
readonly authMode: "manual" | "oauth";
|
|
361
375
|
readonly isOAuthConnected: boolean;
|
|
376
|
+
readonly accessTokenExpiresAt: bigint;
|
|
362
377
|
readonly tokenLifetimeHint: string | null;
|
|
363
378
|
}) {
|
|
364
379
|
const isOAuthBusy =
|
|
@@ -384,6 +399,8 @@ function ConnectBar({
|
|
|
384
399
|
|
|
385
400
|
const statusText = (() => {
|
|
386
401
|
if (authMode === "oauth" && isOAuthConnected) {
|
|
402
|
+
const expiryLabel = formatTokenExpiry(accessTokenExpiresAt);
|
|
403
|
+
if (expiryLabel) return `Tokens refresh automatically \u00B7 ${expiryLabel}`;
|
|
387
404
|
const hint = tokenLifetimeHint && tokenLifetimeHint !== "never"
|
|
388
405
|
? ` \u00B7 Session lasts ~${tokenLifetimeHint}`
|
|
389
406
|
: "";
|
|
@@ -480,6 +497,20 @@ function formatConnectionSummary(toolCount: number, policyCount: number): string
|
|
|
480
497
|
return `${toolLabel}, ${policyLabel}`;
|
|
481
498
|
}
|
|
482
499
|
|
|
500
|
+
function formatTokenExpiry(expiresAtSeconds: bigint): string | null {
|
|
501
|
+
if (expiresAtSeconds === BigInt(0)) return null;
|
|
502
|
+
const nowSeconds = BigInt(Math.floor(Date.now() / 1000));
|
|
503
|
+
const remainingSeconds = expiresAtSeconds - nowSeconds;
|
|
504
|
+
if (remainingSeconds <= BigInt(0)) return "Token expired";
|
|
505
|
+
const minutes = Number(remainingSeconds / BigInt(60));
|
|
506
|
+
if (minutes < 1) return "Expires in <1 min";
|
|
507
|
+
if (minutes < 60) return `Expires in ${minutes} min`;
|
|
508
|
+
const hours = Math.floor(minutes / 60);
|
|
509
|
+
if (hours < 24) return `Expires in ${hours}h ${minutes % 60}m`;
|
|
510
|
+
const days = Math.floor(hours / 24);
|
|
511
|
+
return `Expires in ${days}d`;
|
|
512
|
+
}
|
|
513
|
+
|
|
483
514
|
// ---------------------------------------------------------------------------
|
|
484
515
|
// Internal section components
|
|
485
516
|
// ---------------------------------------------------------------------------
|
|
@@ -760,11 +791,11 @@ function ResourceTemplatesList({
|
|
|
760
791
|
);
|
|
761
792
|
}
|
|
762
793
|
|
|
763
|
-
function
|
|
794
|
+
function EnvSection({
|
|
764
795
|
data,
|
|
765
796
|
oauthTargetEnvVar,
|
|
766
797
|
}: {
|
|
767
|
-
readonly data: { [key: string]:
|
|
798
|
+
readonly data: { [key: string]: EnvVarDeclaration };
|
|
768
799
|
readonly oauthTargetEnvVar: string | null;
|
|
769
800
|
}) {
|
|
770
801
|
const entries = Object.entries(data).sort(([a], [b]) =>
|
|
@@ -789,6 +820,11 @@ function EnvSpecSection({
|
|
|
789
820
|
oauth
|
|
790
821
|
</span>
|
|
791
822
|
)}
|
|
823
|
+
{env.optional && (
|
|
824
|
+
<span className="shrink-0 rounded bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground/70">
|
|
825
|
+
optional
|
|
826
|
+
</span>
|
|
827
|
+
)}
|
|
792
828
|
{env.description && (
|
|
793
829
|
<span className="text-xs text-muted-foreground">
|
|
794
830
|
{env.description}
|
|
@@ -155,6 +155,13 @@ export interface McpServerPickerProps {
|
|
|
155
155
|
* pre-populated.
|
|
156
156
|
*/
|
|
157
157
|
readonly poolValues?: (key: string) => EnvVarInput | undefined;
|
|
158
|
+
/**
|
|
159
|
+
* The authenticated user's active organization slug.
|
|
160
|
+
* Used for OAuth token storage — tokens are stored in the user's personal
|
|
161
|
+
* environment within this org, not the MCP server's org.
|
|
162
|
+
* When omitted, falls back to the `org` prop.
|
|
163
|
+
*/
|
|
164
|
+
readonly activeOrg?: string;
|
|
158
165
|
}
|
|
159
166
|
|
|
160
167
|
// ---------------------------------------------------------------------------
|
|
@@ -250,6 +257,7 @@ export function McpServerPicker({
|
|
|
250
257
|
setup,
|
|
251
258
|
initialServerKey,
|
|
252
259
|
poolValues,
|
|
260
|
+
activeOrg,
|
|
253
261
|
}: McpServerPickerProps) {
|
|
254
262
|
const instanceId = useId();
|
|
255
263
|
const listId = `${instanceId}-list`;
|
|
@@ -408,7 +416,7 @@ export function McpServerPicker({
|
|
|
408
416
|
onSignIn: async () => {
|
|
409
417
|
if (!entry.mcpServer.metadata?.id) return;
|
|
410
418
|
try {
|
|
411
|
-
await oauth.startOAuth(entry.mcpServer.metadata.id, org);
|
|
419
|
+
await oauth.startOAuth(entry.mcpServer.metadata.id, activeOrg ?? org);
|
|
412
420
|
setup.onServerAdded(ref);
|
|
413
421
|
} catch {
|
|
414
422
|
// error state managed by oauth hook
|
package/src/mcp-server/index.ts
CHANGED
|
@@ -19,6 +19,9 @@ export type {
|
|
|
19
19
|
export { useMcpServer } from "./useMcpServer";
|
|
20
20
|
export type { UseMcpServerReturn } from "./useMcpServer";
|
|
21
21
|
|
|
22
|
+
export { useOAuthGrantStatus } from "./useOAuthGrantStatus";
|
|
23
|
+
export type { UseOAuthGrantStatusReturn } from "./useOAuthGrantStatus";
|
|
24
|
+
|
|
22
25
|
export { McpServerPicker } from "./McpServerPicker";
|
|
23
26
|
export type {
|
|
24
27
|
McpServerPickerProps,
|
|
@@ -35,15 +35,15 @@ export function toServerKey(ref: ResourceRef): string {
|
|
|
35
35
|
*
|
|
36
36
|
* Phases:
|
|
37
37
|
* - `"loading"` — Fetching the full `McpServer` resource (spec + status).
|
|
38
|
-
* - `"needsSetup"` — The server has
|
|
39
|
-
* from the user's personal environment. The UI should present
|
|
40
|
-
* credential collection form.
|
|
38
|
+
* - `"needsSetup"` — The server has `env` declarations with variables
|
|
39
|
+
* missing from the user's personal environment. The UI should present
|
|
40
|
+
* a credential collection form.
|
|
41
41
|
* - `"submitting"` — Environment variables are being persisted (save path)
|
|
42
42
|
* or collected (one-time path). The UI should show a loading indicator
|
|
43
43
|
* on the submit button.
|
|
44
44
|
* - `"ready"` — The server is fully configured and ready for session
|
|
45
45
|
* creation. Carries the effective `enabledTools` list. Covers both
|
|
46
|
-
* the "no
|
|
46
|
+
* the "no env needed" and "env vars resolved" cases.
|
|
47
47
|
*/
|
|
48
48
|
export type McpServerSetupPhase =
|
|
49
49
|
| {
|
|
@@ -6,7 +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 {
|
|
9
|
+
import { resolveDeclaredSystemEnvVars } from "../environment/systemEnvVars";
|
|
10
10
|
import { toError } from "../internal/toError";
|
|
11
11
|
|
|
12
12
|
/** Return value of {@link useMcpServerConnect}. */
|
|
@@ -20,20 +20,30 @@ export interface UseMcpServerConnectReturn {
|
|
|
20
20
|
* (typically 5-15 seconds, ~30s timeout).
|
|
21
21
|
*
|
|
22
22
|
* Platform system env vars (`STIGMER_SERVER_ADDRESS`,
|
|
23
|
-
* `STIGMER_API_KEY`) are
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
23
|
+
* `STIGMER_API_KEY`) are injected only when the MCP server
|
|
24
|
+
* declares them in its `spec.env`. Pass `declaredEnvKeys` so the
|
|
25
|
+
* hook can determine which system vars the server actually needs.
|
|
26
|
+
* When omitted, no system vars are injected.
|
|
27
|
+
*
|
|
28
|
+
* When `runtimeEnv` is provided, those values are merged on top
|
|
29
|
+
* (caller values win). The backend merges the result on top of
|
|
30
|
+
* the user's personal environment so saved credentials (e.g.,
|
|
31
|
+
* OAuth tokens) are still resolved.
|
|
28
32
|
*
|
|
29
33
|
* @param mcpServerId - System-generated ID of the MCP server (metadata.id).
|
|
34
|
+
* @param org - The caller's active organization slug. Required for
|
|
35
|
+
* OAuth grant lookup and personal environment resolution.
|
|
30
36
|
* @param runtimeEnv - Optional additional environment variables.
|
|
37
|
+
* @param declaredEnvKeys - Keys from the server's `spec.env` declaration.
|
|
38
|
+
* System vars are only injected when declared here.
|
|
31
39
|
* @returns The updated McpServer with populated status.discovered_capabilities
|
|
32
40
|
* and status.tool_approvals.
|
|
33
41
|
*/
|
|
34
42
|
readonly connect: (
|
|
35
43
|
mcpServerId: string,
|
|
44
|
+
org: string,
|
|
36
45
|
runtimeEnv?: Record<string, EnvVarInput>,
|
|
46
|
+
declaredEnvKeys?: readonly string[],
|
|
37
47
|
) => Promise<McpServer>;
|
|
38
48
|
/** `true` while the connect RPC is in flight. */
|
|
39
49
|
readonly isConnecting: boolean;
|
|
@@ -52,10 +62,10 @@ export interface UseMcpServerConnectReturn {
|
|
|
52
62
|
* approval policy via a structured-output LLM call.
|
|
53
63
|
*
|
|
54
64
|
* Platform system env vars (`STIGMER_SERVER_ADDRESS`,
|
|
55
|
-
* `STIGMER_API_KEY`) are
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
65
|
+
* `STIGMER_API_KEY`) are injected only when the target MCP server
|
|
66
|
+
* declares them in `spec.env`. Pass the server's declared env keys
|
|
67
|
+
* via `declaredEnvKeys` so the hook knows which system vars to
|
|
68
|
+
* include. When not provided, no system vars are injected.
|
|
59
69
|
*
|
|
60
70
|
* Additional one-time credentials can be passed via `runtimeEnv`
|
|
61
71
|
* and will override both system vars and personal env values.
|
|
@@ -67,13 +77,15 @@ export interface UseMcpServerConnectReturn {
|
|
|
67
77
|
*
|
|
68
78
|
* // Saved credentials: already in personal environment
|
|
69
79
|
* async function handleConnectSaved() {
|
|
70
|
-
*
|
|
80
|
+
* const envKeys = Object.keys(mcpServer.spec?.env ?? {});
|
|
81
|
+
* await connect(mcpServer.metadata.id, undefined, envKeys);
|
|
71
82
|
* refetch();
|
|
72
83
|
* }
|
|
73
84
|
*
|
|
74
85
|
* // One-time use: pass credentials directly
|
|
75
86
|
* async function handleConnectTemporary(values: Record<string, EnvVarInput>) {
|
|
76
|
-
*
|
|
87
|
+
* const envKeys = Object.keys(mcpServer.spec?.env ?? {});
|
|
88
|
+
* await connect(mcpServer.metadata.id, values, envKeys);
|
|
77
89
|
* refetch();
|
|
78
90
|
* }
|
|
79
91
|
* ```
|
|
@@ -88,13 +100,17 @@ export function useMcpServerConnect(): UseMcpServerConnectReturn {
|
|
|
88
100
|
const connect = useCallback(
|
|
89
101
|
async (
|
|
90
102
|
mcpServerId: string,
|
|
103
|
+
org: string,
|
|
91
104
|
runtimeEnv?: Record<string, EnvVarInput>,
|
|
105
|
+
declaredEnvKeys?: readonly string[],
|
|
92
106
|
): Promise<McpServer> => {
|
|
93
107
|
setIsConnecting(true);
|
|
94
108
|
setError(null);
|
|
95
109
|
|
|
96
110
|
try {
|
|
97
|
-
const systemEnv =
|
|
111
|
+
const systemEnv = declaredEnvKeys
|
|
112
|
+
? await resolveDeclaredSystemEnvVars(stigmer, declaredEnvKeys)
|
|
113
|
+
: {};
|
|
98
114
|
const mergedEnv = { ...systemEnv, ...(runtimeEnv ?? {}) };
|
|
99
115
|
|
|
100
116
|
const runtimeEnvMap: Record<string, { value: string; isSecret: boolean }> = {};
|
|
@@ -107,7 +123,10 @@ export function useMcpServerConnect(): UseMcpServerConnectReturn {
|
|
|
107
123
|
|
|
108
124
|
const input = create(ConnectInputSchema, {
|
|
109
125
|
mcpServerId,
|
|
110
|
-
|
|
126
|
+
org,
|
|
127
|
+
...(Object.keys(runtimeEnvMap).length > 0
|
|
128
|
+
? { runtimeEnv: runtimeEnvMap }
|
|
129
|
+
: {}),
|
|
111
130
|
});
|
|
112
131
|
|
|
113
132
|
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,7 +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 {
|
|
12
|
+
import { resolveDeclaredSystemEnvVars } from "../environment/systemEnvVars";
|
|
13
13
|
import { toError } from "../internal/toError";
|
|
14
14
|
|
|
15
15
|
/**
|
|
@@ -55,9 +55,11 @@ export interface UseMcpServerOAuthConnectReturn {
|
|
|
55
55
|
*
|
|
56
56
|
* @param mcpServerId - System-generated ID (metadata.id) of the MCP server.
|
|
57
57
|
* @param org - Organization context for token storage (caller's active org).
|
|
58
|
+
* @param declaredEnvKeys - Keys from the server's `spec.env` declaration.
|
|
59
|
+
* System vars are only injected when declared here.
|
|
58
60
|
* @returns The updated McpServer after tool discovery completes.
|
|
59
61
|
*/
|
|
60
|
-
readonly startOAuth: (mcpServerId: string, org: string) => Promise<McpServer>;
|
|
62
|
+
readonly startOAuth: (mcpServerId: string, org: string, declaredEnvKeys?: readonly string[]) => Promise<McpServer>;
|
|
61
63
|
/** `true` while any phase of the OAuth flow is in progress. */
|
|
62
64
|
readonly isInProgress: boolean;
|
|
63
65
|
/** Current phase of the OAuth flow. */
|
|
@@ -125,7 +127,7 @@ export function useMcpServerOAuthConnect(): UseMcpServerOAuthConnectReturn {
|
|
|
125
127
|
}, []);
|
|
126
128
|
|
|
127
129
|
const startOAuth = useCallback(
|
|
128
|
-
async (mcpServerId: string, org: string): Promise<McpServer> => {
|
|
130
|
+
async (mcpServerId: string, org: string, declaredEnvKeys?: readonly string[]): Promise<McpServer> => {
|
|
129
131
|
setPhase("initiating");
|
|
130
132
|
setError(null);
|
|
131
133
|
|
|
@@ -179,7 +181,9 @@ export function useMcpServerOAuthConnect(): UseMcpServerOAuthConnectReturn {
|
|
|
179
181
|
|
|
180
182
|
setPhase("connecting");
|
|
181
183
|
|
|
182
|
-
const systemEnv =
|
|
184
|
+
const systemEnv = declaredEnvKeys
|
|
185
|
+
? await resolveDeclaredSystemEnvVars(stigmer, declaredEnvKeys)
|
|
186
|
+
: {};
|
|
183
187
|
const runtimeEnvMap: Record<string, { value: string; isSecret: boolean }> = {};
|
|
184
188
|
for (const [key, envInput] of Object.entries(systemEnv)) {
|
|
185
189
|
runtimeEnvMap[key] = {
|
|
@@ -188,12 +192,15 @@ export function useMcpServerOAuthConnect(): UseMcpServerOAuthConnectReturn {
|
|
|
188
192
|
};
|
|
189
193
|
}
|
|
190
194
|
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
195
|
+
const input = create(ConnectInputSchema, {
|
|
196
|
+
mcpServerId,
|
|
197
|
+
org,
|
|
198
|
+
...(Object.keys(runtimeEnvMap).length > 0
|
|
199
|
+
? { runtimeEnv: runtimeEnvMap }
|
|
200
|
+
: {}),
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const server = await stigmer.mcpServer.connect(input);
|
|
197
204
|
|
|
198
205
|
setPhase("done");
|
|
199
206
|
return server;
|