@stigmer/react 0.0.79 → 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/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/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/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/index.ts +3 -0
- package/src/mcp-server/mcpServerSetupReducer.ts +4 -4
- package/src/mcp-server/useMcpServerCredentials.ts +65 -32
- 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
|
@@ -9,8 +9,6 @@ import type {
|
|
|
9
9
|
StdioServerConfigInput,
|
|
10
10
|
HttpServerConfigInput,
|
|
11
11
|
ToolApprovalPolicyInput,
|
|
12
|
-
EnvSpecInput,
|
|
13
|
-
EnvVarInput,
|
|
14
12
|
ResourceRef,
|
|
15
13
|
} from "@stigmer/sdk";
|
|
16
14
|
import type { StigmerResourceKind } from "./detect-stigmer-resource";
|
|
@@ -47,7 +45,7 @@ export type ParsedResource =
|
|
|
47
45
|
*
|
|
48
46
|
* Handles the conversion from proto-style snake_case YAML field names to
|
|
49
47
|
* the camelCase TypeScript SDK input types, including nested structures
|
|
50
|
-
* like `mcp_server_usages`, `sub_agents`, and `
|
|
48
|
+
* like `mcp_server_usages`, `sub_agents`, and `env`.
|
|
51
49
|
*
|
|
52
50
|
* The `org` parameter **always overrides** `metadata.org` in the YAML.
|
|
53
51
|
* This matches the UX intent of "Apply to [my-org]" — the user explicitly
|
|
@@ -207,7 +205,7 @@ function buildAgentInput(
|
|
|
207
205
|
...optionalField("mcpServerUsages", extractMcpServerUsages(spec)),
|
|
208
206
|
...optionalField("skillRefs", extractResourceRefs(spec, "skill_refs")),
|
|
209
207
|
...optionalField("subAgents", extractSubAgents(spec)),
|
|
210
|
-
...optionalField("
|
|
208
|
+
...optionalField("env", extractEnv(spec)),
|
|
211
209
|
};
|
|
212
210
|
}
|
|
213
211
|
|
|
@@ -333,7 +331,7 @@ function buildMcpServerInput(
|
|
|
333
331
|
"pinnedToolApprovals",
|
|
334
332
|
extractToolApprovalPolicy,
|
|
335
333
|
),
|
|
336
|
-
...optionalField("
|
|
334
|
+
...optionalField("env", extractEnv(spec)),
|
|
337
335
|
};
|
|
338
336
|
}
|
|
339
337
|
|
|
@@ -389,35 +387,46 @@ function extractToolApprovalPolicy(
|
|
|
389
387
|
// Shared extractors
|
|
390
388
|
// ---------------------------------------------------------------------------
|
|
391
389
|
|
|
392
|
-
function
|
|
390
|
+
function extractEnv(
|
|
393
391
|
spec: Record<string, unknown>,
|
|
394
|
-
):
|
|
395
|
-
|
|
396
|
-
|
|
392
|
+
): Record<string, { isSecret?: boolean; description?: string; optional?: boolean }> | undefined {
|
|
393
|
+
// Support both new flat `env` and legacy nested `env_spec.data` / `envSpec.data`.
|
|
394
|
+
let envMap: Record<string, unknown> | undefined;
|
|
395
|
+
|
|
396
|
+
const envRaw = spec.env;
|
|
397
|
+
if (isPlainObject(envRaw)) {
|
|
398
|
+
envMap = envRaw;
|
|
399
|
+
} else {
|
|
400
|
+
const envSpecRaw = spec.env_spec ?? spec.envSpec;
|
|
401
|
+
if (isPlainObject(envSpecRaw)) {
|
|
402
|
+
const data = envSpecRaw.data ?? envSpecRaw.variables;
|
|
403
|
+
if (isPlainObject(data)) {
|
|
404
|
+
envMap = data;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
397
408
|
|
|
398
|
-
|
|
399
|
-
const data = envSpecRaw.data ?? envSpecRaw.variables;
|
|
400
|
-
if (!isPlainObject(data)) return undefined;
|
|
409
|
+
if (!envMap) return undefined;
|
|
401
410
|
|
|
402
|
-
const
|
|
411
|
+
const result: Record<string, { isSecret?: boolean; description?: string; optional?: boolean }> = {};
|
|
403
412
|
let hasEntries = false;
|
|
404
413
|
|
|
405
|
-
for (const [key, val] of Object.entries(
|
|
414
|
+
for (const [key, val] of Object.entries(envMap)) {
|
|
406
415
|
if (!isPlainObject(val)) continue;
|
|
407
416
|
|
|
408
|
-
const value = typeof val.value === "string" ? val.value : "";
|
|
409
417
|
const isSecret = val.is_secret ?? val.isSecret;
|
|
410
418
|
const description = val.description;
|
|
419
|
+
const optional = val.optional;
|
|
411
420
|
|
|
412
|
-
|
|
413
|
-
value,
|
|
421
|
+
result[key] = {
|
|
414
422
|
...(typeof isSecret === "boolean" && { isSecret }),
|
|
415
423
|
...(typeof description === "string" && { description }),
|
|
424
|
+
...(typeof optional === "boolean" && { optional }),
|
|
416
425
|
};
|
|
417
426
|
hasEntries = true;
|
|
418
427
|
}
|
|
419
428
|
|
|
420
|
-
return hasEntries ?
|
|
429
|
+
return hasEntries ? result : undefined;
|
|
421
430
|
}
|
|
422
431
|
|
|
423
432
|
function extractResourceRef(raw: Record<string, unknown>): ResourceRef {
|
|
@@ -15,7 +15,7 @@ import type {
|
|
|
15
15
|
ToolApprovalPolicy,
|
|
16
16
|
} from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/spec_pb";
|
|
17
17
|
import type { ApiResourceReference } from "@stigmer/protos/ai/stigmer/commons/apiresource/io_pb";
|
|
18
|
-
import type {
|
|
18
|
+
import type { EnvVarDeclaration } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/spec_pb";
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Serializes a proto `Agent` into the canonical Stigmer YAML format.
|
|
@@ -147,10 +147,10 @@ function buildAgentSpec(
|
|
|
147
147
|
result.sub_agents = spec.subAgents.map(serializeSubAgent);
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
if (spec.
|
|
151
|
-
const
|
|
152
|
-
if (
|
|
153
|
-
result.
|
|
150
|
+
if (hasEntries(spec.env)) {
|
|
151
|
+
const env = serializeEnv(spec.env);
|
|
152
|
+
if (env) {
|
|
153
|
+
result.env = env;
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
|
|
@@ -271,10 +271,10 @@ function buildMcpServerSpec(
|
|
|
271
271
|
spec.pinnedToolApprovals.map(serializeToolApprovalPolicy);
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
-
if (spec.
|
|
275
|
-
const
|
|
276
|
-
if (
|
|
277
|
-
result.
|
|
274
|
+
if (hasEntries(spec.env)) {
|
|
275
|
+
const env = serializeEnv(spec.env);
|
|
276
|
+
if (env) {
|
|
277
|
+
result.env = env;
|
|
278
278
|
}
|
|
279
279
|
}
|
|
280
280
|
|
|
@@ -361,20 +361,17 @@ function serializeResourceRef(
|
|
|
361
361
|
return result;
|
|
362
362
|
}
|
|
363
363
|
|
|
364
|
-
function
|
|
365
|
-
|
|
366
|
-
): Record<string, unknown
|
|
367
|
-
|
|
364
|
+
function serializeEnv(
|
|
365
|
+
env: { [key: string]: EnvVarDeclaration },
|
|
366
|
+
): Record<string, Record<string, unknown>> | undefined {
|
|
367
|
+
const keys = Object.keys(env);
|
|
368
|
+
if (keys.length === 0) return undefined;
|
|
368
369
|
|
|
369
|
-
const
|
|
370
|
+
const result: Record<string, Record<string, unknown>> = {};
|
|
370
371
|
|
|
371
|
-
for (const [key, val] of Object.entries(
|
|
372
|
+
for (const [key, val] of Object.entries(env)) {
|
|
372
373
|
const entry: Record<string, unknown> = {};
|
|
373
374
|
|
|
374
|
-
if (val.value) {
|
|
375
|
-
entry.value = val.value;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
375
|
if (val.isSecret) {
|
|
379
376
|
entry.is_secret = val.isSecret;
|
|
380
377
|
}
|
|
@@ -383,17 +380,13 @@ function serializeEnvSpec(
|
|
|
383
380
|
entry.description = val.description;
|
|
384
381
|
}
|
|
385
382
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
const result: Record<string, unknown> = {};
|
|
383
|
+
if (val.optional) {
|
|
384
|
+
entry.optional = val.optional;
|
|
385
|
+
}
|
|
390
386
|
|
|
391
|
-
|
|
392
|
-
result.description = envSpec.description;
|
|
387
|
+
result[key] = entry;
|
|
393
388
|
}
|
|
394
389
|
|
|
395
|
-
result.data = data;
|
|
396
|
-
|
|
397
390
|
return result;
|
|
398
391
|
}
|
|
399
392
|
|
|
@@ -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);
|
|
@@ -133,7 +141,7 @@ export function McpServerDetailView({
|
|
|
133
141
|
if (!mcpServer?.metadata?.id) return;
|
|
134
142
|
|
|
135
143
|
try {
|
|
136
|
-
await oauth.startOAuth(mcpServer.metadata.id, org);
|
|
144
|
+
await oauth.startOAuth(mcpServer.metadata.id, activeOrg ?? org);
|
|
137
145
|
credentials.refetch();
|
|
138
146
|
refetch();
|
|
139
147
|
} catch {
|
|
@@ -258,9 +266,9 @@ export function McpServerDetailView({
|
|
|
258
266
|
<ServerConfigSection serverType={spec.serverType} />
|
|
259
267
|
)}
|
|
260
268
|
|
|
261
|
-
{spec?.
|
|
262
|
-
<
|
|
263
|
-
data={spec.
|
|
269
|
+
{spec?.env && Object.keys(spec.env).length > 0 && (
|
|
270
|
+
<EnvSection
|
|
271
|
+
data={spec.env}
|
|
264
272
|
oauthTargetEnvVar={credentials.oauthTargetEnvVar}
|
|
265
273
|
/>
|
|
266
274
|
)}
|
|
@@ -278,6 +286,7 @@ export function McpServerDetailView({
|
|
|
278
286
|
oauthPhase={oauth.phase}
|
|
279
287
|
authMode={credentials.authMode}
|
|
280
288
|
isOAuthConnected={credentials.isOAuthConnected}
|
|
289
|
+
accessTokenExpiresAt={credentials.accessTokenExpiresAt}
|
|
281
290
|
tokenLifetimeHint={credentials.tokenLifetimeHint}
|
|
282
291
|
/>
|
|
283
292
|
|
|
@@ -346,6 +355,7 @@ function ConnectBar({
|
|
|
346
355
|
oauthPhase,
|
|
347
356
|
authMode,
|
|
348
357
|
isOAuthConnected,
|
|
358
|
+
accessTokenExpiresAt,
|
|
349
359
|
tokenLifetimeHint,
|
|
350
360
|
}: {
|
|
351
361
|
readonly isConnecting: boolean;
|
|
@@ -359,6 +369,7 @@ function ConnectBar({
|
|
|
359
369
|
readonly oauthPhase: OAuthConnectPhase;
|
|
360
370
|
readonly authMode: "manual" | "oauth";
|
|
361
371
|
readonly isOAuthConnected: boolean;
|
|
372
|
+
readonly accessTokenExpiresAt: bigint;
|
|
362
373
|
readonly tokenLifetimeHint: string | null;
|
|
363
374
|
}) {
|
|
364
375
|
const isOAuthBusy =
|
|
@@ -384,6 +395,8 @@ function ConnectBar({
|
|
|
384
395
|
|
|
385
396
|
const statusText = (() => {
|
|
386
397
|
if (authMode === "oauth" && isOAuthConnected) {
|
|
398
|
+
const expiryLabel = formatTokenExpiry(accessTokenExpiresAt);
|
|
399
|
+
if (expiryLabel) return `Tokens refresh automatically \u00B7 ${expiryLabel}`;
|
|
387
400
|
const hint = tokenLifetimeHint && tokenLifetimeHint !== "never"
|
|
388
401
|
? ` \u00B7 Session lasts ~${tokenLifetimeHint}`
|
|
389
402
|
: "";
|
|
@@ -480,6 +493,20 @@ function formatConnectionSummary(toolCount: number, policyCount: number): string
|
|
|
480
493
|
return `${toolLabel}, ${policyLabel}`;
|
|
481
494
|
}
|
|
482
495
|
|
|
496
|
+
function formatTokenExpiry(expiresAtSeconds: bigint): string | null {
|
|
497
|
+
if (expiresAtSeconds === BigInt(0)) return null;
|
|
498
|
+
const nowSeconds = BigInt(Math.floor(Date.now() / 1000));
|
|
499
|
+
const remainingSeconds = expiresAtSeconds - nowSeconds;
|
|
500
|
+
if (remainingSeconds <= BigInt(0)) return "Token expired";
|
|
501
|
+
const minutes = Number(remainingSeconds / BigInt(60));
|
|
502
|
+
if (minutes < 1) return "Expires in <1 min";
|
|
503
|
+
if (minutes < 60) return `Expires in ${minutes} min`;
|
|
504
|
+
const hours = Math.floor(minutes / 60);
|
|
505
|
+
if (hours < 24) return `Expires in ${hours}h ${minutes % 60}m`;
|
|
506
|
+
const days = Math.floor(hours / 24);
|
|
507
|
+
return `Expires in ${days}d`;
|
|
508
|
+
}
|
|
509
|
+
|
|
483
510
|
// ---------------------------------------------------------------------------
|
|
484
511
|
// Internal section components
|
|
485
512
|
// ---------------------------------------------------------------------------
|
|
@@ -760,11 +787,11 @@ function ResourceTemplatesList({
|
|
|
760
787
|
);
|
|
761
788
|
}
|
|
762
789
|
|
|
763
|
-
function
|
|
790
|
+
function EnvSection({
|
|
764
791
|
data,
|
|
765
792
|
oauthTargetEnvVar,
|
|
766
793
|
}: {
|
|
767
|
-
readonly data: { [key: string]:
|
|
794
|
+
readonly data: { [key: string]: EnvVarDeclaration };
|
|
768
795
|
readonly oauthTargetEnvVar: string | null;
|
|
769
796
|
}) {
|
|
770
797
|
const entries = Object.entries(data).sort(([a], [b]) =>
|
|
@@ -789,6 +816,11 @@ function EnvSpecSection({
|
|
|
789
816
|
oauth
|
|
790
817
|
</span>
|
|
791
818
|
)}
|
|
819
|
+
{env.optional && (
|
|
820
|
+
<span className="shrink-0 rounded bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground/70">
|
|
821
|
+
optional
|
|
822
|
+
</span>
|
|
823
|
+
)}
|
|
792
824
|
{env.description && (
|
|
793
825
|
<span className="text-xs text-muted-foreground">
|
|
794
826
|
{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
|
| {
|
|
@@ -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
|
}
|