@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
package/src/agent/index.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { useStigmer } from "../hooks";
|
|
|
10
10
|
import { toError } from "../internal/toError";
|
|
11
11
|
import { usePersonalEnvironment } from "../environment/usePersonalEnvironment";
|
|
12
12
|
import { buildPersonalInstanceInput } from "../agent-instance/buildPersonalInstanceInput";
|
|
13
|
-
import {
|
|
13
|
+
import { diffEnv } from "../environment/diffEnv";
|
|
14
14
|
import {
|
|
15
15
|
agentSetupReducer,
|
|
16
16
|
INITIAL_STATE,
|
|
@@ -106,8 +106,8 @@ export interface UseAgentSetupReturn {
|
|
|
106
106
|
/**
|
|
107
107
|
* Evaluate whether an agent is ready to use or needs env var collection.
|
|
108
108
|
*
|
|
109
|
-
* Fetches the full agent to read its `
|
|
110
|
-
* personal instance, and diffs
|
|
109
|
+
* Fetches the full agent to read its `env` declarations, checks for an
|
|
110
|
+
* existing personal instance, and diffs env keys against the personal
|
|
111
111
|
* environment. Returns `"ready"` when the agent can be used immediately,
|
|
112
112
|
* or `"needsEnvVars"` when the caller should present {@link AgentEnvForm}.
|
|
113
113
|
*/
|
|
@@ -143,7 +143,7 @@ export interface UseAgentSetupReturn {
|
|
|
143
143
|
*
|
|
144
144
|
* When a user picks an agent in the {@link AgentPicker}, this hook
|
|
145
145
|
* determines whether the agent requires credentials (via its
|
|
146
|
-
* `
|
|
146
|
+
* `env` declarations), checks what the user has already provided in their
|
|
147
147
|
* personal environment, and either reports the agent as ready or
|
|
148
148
|
* identifies the missing variables so the caller can render
|
|
149
149
|
* {@link AgentEnvForm}.
|
|
@@ -173,7 +173,7 @@ export interface UseAgentSetupReturn {
|
|
|
173
173
|
* @param org - Organization slug. Pass `null` to disable.
|
|
174
174
|
* @param poolKeys - Optional set of env-var keys already available
|
|
175
175
|
* from the session env pool (manual secrets, one-time env vars from
|
|
176
|
-
* other components). When provided, agents whose `
|
|
176
|
+
* other components). When provided, agents whose `env` keys
|
|
177
177
|
* are fully covered by `poolKeys` + personal env auto-resolve to
|
|
178
178
|
* `ready` without prompting. Reactive — when `poolKeys` changes,
|
|
179
179
|
* `needsEnvVars` is re-evaluated.
|
|
@@ -221,10 +221,10 @@ export function useAgentSetup(
|
|
|
221
221
|
try {
|
|
222
222
|
const agent = await stigmer.agent.getByReference(ref);
|
|
223
223
|
const agentName = agent.metadata?.name ?? ref.slug;
|
|
224
|
-
const
|
|
224
|
+
const envDeclarations = agent.spec?.env;
|
|
225
225
|
|
|
226
|
-
// No
|
|
227
|
-
if (!
|
|
226
|
+
// No env declarations — agent is immediately ready (direct mode).
|
|
227
|
+
if (!envDeclarations || Object.keys(envDeclarations).length === 0) {
|
|
228
228
|
const resolution: AgentResolution = { mode: "direct" };
|
|
229
229
|
dispatch({
|
|
230
230
|
type: "RESOLVE_READY",
|
|
@@ -235,7 +235,7 @@ export function useAgentSetup(
|
|
|
235
235
|
return { status: "ready", agentRef: ref, agentName, resolution };
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
-
// Agent has
|
|
238
|
+
// Agent has env declarations — check for existing personal instance.
|
|
239
239
|
const agentLabel = `${ref.org}/${ref.slug}`;
|
|
240
240
|
const instanceList = await stigmer.agentInstance.list(
|
|
241
241
|
create(ListAgentInstancesRequestSchema, {
|
|
@@ -265,8 +265,8 @@ export function useAgentSetup(
|
|
|
265
265
|
const existingKeys = new Set(
|
|
266
266
|
Object.keys(personalEnv.environment?.spec?.data ?? {}),
|
|
267
267
|
);
|
|
268
|
-
const personalOnlyMissing =
|
|
269
|
-
const missingVariables =
|
|
268
|
+
const personalOnlyMissing = diffEnv(envDeclarations, existingKeys);
|
|
269
|
+
const missingVariables = diffEnv(envDeclarations, existingKeys, poolKeys);
|
|
270
270
|
|
|
271
271
|
if (personalOnlyMissing.length === 0) {
|
|
272
272
|
// Personal env covers all keys — create personal instance.
|
|
@@ -9,7 +9,7 @@ import { ScrollFade } from "../internal/ScrollFade";
|
|
|
9
9
|
/**
|
|
10
10
|
* Describes a single environment variable the form should collect.
|
|
11
11
|
*
|
|
12
|
-
* Typically derived from a resource's `
|
|
12
|
+
* Typically derived from a resource's `env` entries (Agent,
|
|
13
13
|
* McpServer, or any resource that declares required environment
|
|
14
14
|
* variables). The caller is responsible for filtering out variables
|
|
15
15
|
* the user has already provided (i.e. only pass the *missing* ones).
|
|
@@ -19,8 +19,14 @@ export interface EnvVarFormVariable {
|
|
|
19
19
|
readonly key: string;
|
|
20
20
|
/** When true, the input renders as a password field with a visibility toggle. */
|
|
21
21
|
readonly isSecret: boolean;
|
|
22
|
-
/** Help text shown below the input. From the resource's
|
|
22
|
+
/** Help text shown below the input. From the resource's env declaration description. */
|
|
23
23
|
readonly description?: string;
|
|
24
|
+
/**
|
|
25
|
+
* When true, this variable is not required for the resource to function.
|
|
26
|
+
* Callers can use this to filter optional vars out of forms or to show
|
|
27
|
+
* them separately from required vars.
|
|
28
|
+
*/
|
|
29
|
+
readonly optional?: boolean;
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
/** Options reported by the form alongside the collected values on submit. */
|
|
@@ -110,7 +116,7 @@ export interface EnvVarFormProps {
|
|
|
110
116
|
|
|
111
117
|
/**
|
|
112
118
|
* Compact form that collects environment variable values for any
|
|
113
|
-
* resource that declares
|
|
119
|
+
* resource that declares `env` variables (Agents, MCP servers, etc.).
|
|
114
120
|
*
|
|
115
121
|
* Renders one labeled input per variable. Secret variables use a
|
|
116
122
|
* password field with a visibility toggle. The form validates that
|
|
@@ -18,11 +18,26 @@ export interface EnvironmentListPanelProps {
|
|
|
18
18
|
/** Optional label filter — only environments matching ALL labels are shown. */
|
|
19
19
|
readonly labels?: Record<string, string>;
|
|
20
20
|
/**
|
|
21
|
-
* Exclude environments whose labels
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* Exclude environments whose labels match one or more label sets.
|
|
22
|
+
*
|
|
23
|
+
* A single record uses AND semantics — the environment must match
|
|
24
|
+
* **all** key-value pairs to be excluded. Pass an array of records
|
|
25
|
+
* for OR-of-AND semantics: the environment is excluded when **any**
|
|
26
|
+
* record fully matches.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```tsx
|
|
30
|
+
* // Exclude personal envs only (single record — backward-compatible)
|
|
31
|
+
* excludeLabels={{ "stigmer.ai/personal": "true" }}
|
|
32
|
+
*
|
|
33
|
+
* // Exclude personal AND managed envs (array of records)
|
|
34
|
+
* excludeLabels={[
|
|
35
|
+
* { "stigmer.ai/personal": "true" },
|
|
36
|
+
* { "stigmer.ai/managed": "true" },
|
|
37
|
+
* ]}
|
|
38
|
+
* ```
|
|
24
39
|
*/
|
|
25
|
-
readonly excludeLabels?: Record<string, string
|
|
40
|
+
readonly excludeLabels?: Record<string, string> | Record<string, string>[];
|
|
26
41
|
/** Fired when a user selects (expands) an environment. */
|
|
27
42
|
readonly onEnvironmentSelect?: (env: Environment) => void;
|
|
28
43
|
/** When `true`, variable editors render in read-only mode. */
|
|
@@ -82,13 +97,16 @@ export function EnvironmentListPanel({
|
|
|
82
97
|
}
|
|
83
98
|
|
|
84
99
|
const filtered = useMemo(() => {
|
|
85
|
-
if (!excludeLabels
|
|
86
|
-
|
|
87
|
-
|
|
100
|
+
if (!excludeLabels) return environments;
|
|
101
|
+
const labelSets = Array.isArray(excludeLabels)
|
|
102
|
+
? excludeLabels
|
|
103
|
+
: [excludeLabels];
|
|
104
|
+
if (labelSets.length === 0) return environments;
|
|
105
|
+
|
|
88
106
|
return environments.filter((env) => {
|
|
89
107
|
const envLabels = env.metadata?.labels ?? {};
|
|
90
|
-
const shouldExclude =
|
|
91
|
-
([k, v]) => envLabels[k] === v,
|
|
108
|
+
const shouldExclude = labelSets.some((labelSet) =>
|
|
109
|
+
Object.entries(labelSet).every(([k, v]) => envLabels[k] === v),
|
|
92
110
|
);
|
|
93
111
|
return !shouldExclude;
|
|
94
112
|
});
|
|
@@ -4,38 +4,38 @@ import type { EnvVarFormVariable } from "./EnvVarForm";
|
|
|
4
4
|
* Computes the list of environment variables a resource requires that
|
|
5
5
|
* the user has not yet provided.
|
|
6
6
|
*
|
|
7
|
-
* Compares a resource's declared `
|
|
7
|
+
* Compares a resource's declared `env` keys against a set of
|
|
8
8
|
* keys already present in the user's personal environment **and** an
|
|
9
9
|
* optional session-level env pool (manual secrets, one-time env vars
|
|
10
10
|
* from other components). Variables whose keys are missing from both
|
|
11
11
|
* sources are returned as {@link EnvVarFormVariable} entries suitable
|
|
12
12
|
* for rendering in {@link EnvVarForm}.
|
|
13
13
|
*
|
|
14
|
-
* Works with any resource that declares
|
|
14
|
+
* Works with any resource that declares env var declarations — Agents,
|
|
15
15
|
* MCP servers, or future resource types.
|
|
16
16
|
*
|
|
17
17
|
* This is a pure function with no side effects — it can be unit-tested
|
|
18
18
|
* independently of hooks, providers, or API calls.
|
|
19
19
|
*
|
|
20
|
-
* @param
|
|
21
|
-
* Each entry declares a variable the resource needs, with `isSecret
|
|
22
|
-
* and an optional
|
|
20
|
+
* @param envDeclarations - The resource's `spec.env` record.
|
|
21
|
+
* Each entry declares a variable the resource needs, with `isSecret`,
|
|
22
|
+
* an optional `description`, and an `optional` flag.
|
|
23
23
|
* @param existingKeys - Keys already present in the user's personal
|
|
24
24
|
* environment (or any environment being checked against).
|
|
25
25
|
* @param poolKeys - Optional additional keys from the session env pool
|
|
26
26
|
* (manual secrets, one-time env vars from other agents/MCP servers).
|
|
27
27
|
* When provided, variables satisfied by the pool are also excluded
|
|
28
28
|
* from the "missing" list.
|
|
29
|
-
* @returns Variables from `
|
|
29
|
+
* @returns Variables from `envDeclarations` not found in either key set.
|
|
30
30
|
*/
|
|
31
|
-
export function
|
|
32
|
-
|
|
31
|
+
export function diffEnv(
|
|
32
|
+
envDeclarations: Record<string, { isSecret: boolean; description?: string; optional?: boolean }>,
|
|
33
33
|
existingKeys: Set<string>,
|
|
34
34
|
poolKeys?: Set<string>,
|
|
35
35
|
): EnvVarFormVariable[] {
|
|
36
36
|
const missing: EnvVarFormVariable[] = [];
|
|
37
37
|
|
|
38
|
-
for (const [key, value] of Object.entries(
|
|
38
|
+
for (const [key, value] of Object.entries(envDeclarations)) {
|
|
39
39
|
if (existingKeys.has(key)) continue;
|
|
40
40
|
if (poolKeys?.has(key)) continue;
|
|
41
41
|
|
|
@@ -43,6 +43,7 @@ export function diffEnvSpec(
|
|
|
43
43
|
key,
|
|
44
44
|
isSecret: value.isSecret,
|
|
45
45
|
...(value.description && { description: value.description }),
|
|
46
|
+
...(value.optional && { optional: true }),
|
|
46
47
|
});
|
|
47
48
|
}
|
|
48
49
|
|
package/src/environment/index.ts
CHANGED
|
@@ -35,7 +35,7 @@ export type {
|
|
|
35
35
|
EnvVarFormVariable,
|
|
36
36
|
EnvVarFormSubmitOptions,
|
|
37
37
|
} from "./EnvVarForm";
|
|
38
|
-
export {
|
|
38
|
+
export { diffEnv } from "./diffEnv";
|
|
39
39
|
export { useSessionEnvPool } from "./useSessionEnvPool";
|
|
40
40
|
export type {
|
|
41
41
|
SessionEnvPoolInput,
|
|
@@ -22,7 +22,7 @@ export interface SessionVariablesInputProps {
|
|
|
22
22
|
* between the variable and the agent/MCP server that needs it.
|
|
23
23
|
*
|
|
24
24
|
* Built by `SessionComposer` from the selected agent's and MCP
|
|
25
|
-
* servers' `
|
|
25
|
+
* servers' `env` declarations.
|
|
26
26
|
*
|
|
27
27
|
* @example
|
|
28
28
|
* ```ts
|
package/src/index.ts
CHANGED
|
@@ -221,6 +221,7 @@ export {
|
|
|
221
221
|
useMcpServerConnect,
|
|
222
222
|
useMcpServerOAuthConnect,
|
|
223
223
|
useMcpServerCredentials,
|
|
224
|
+
useOAuthGrantStatus,
|
|
224
225
|
OAuthCallbackHandler,
|
|
225
226
|
McpServerPicker,
|
|
226
227
|
McpServerConfigPanel,
|
|
@@ -255,6 +256,7 @@ export type {
|
|
|
255
256
|
OAuthCallbackHandlerProps,
|
|
256
257
|
OAuthCallbackParams,
|
|
257
258
|
UseMcpServerCredentialsReturn,
|
|
259
|
+
UseOAuthGrantStatusReturn,
|
|
258
260
|
McpServerAuthMode,
|
|
259
261
|
} from "./mcp-server";
|
|
260
262
|
|
|
@@ -298,7 +300,7 @@ export type {
|
|
|
298
300
|
GitHubRepoPickerProps,
|
|
299
301
|
} from "./github";
|
|
300
302
|
|
|
301
|
-
// Agent — data hook, count hook, list hook, search hook, picker, detail view, env form, setup orchestration,
|
|
303
|
+
// Agent — data hook, count hook, list hook, search hook, picker, detail view, env form, setup orchestration, env diffing, and default agent
|
|
302
304
|
export {
|
|
303
305
|
useAgent,
|
|
304
306
|
useAgentCount,
|
|
@@ -307,7 +309,7 @@ export {
|
|
|
307
309
|
AgentPicker,
|
|
308
310
|
AgentDetailView,
|
|
309
311
|
AgentEnvForm,
|
|
310
|
-
|
|
312
|
+
diffEnv,
|
|
311
313
|
useAgentSetup,
|
|
312
314
|
useDefaultAgent,
|
|
313
315
|
} from "./agent";
|
|
@@ -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);
|
|
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);
|
|
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
|
|
@@ -7,7 +7,9 @@ import type { OAuthCallbackMessage } from "./useMcpServerOAuthConnect";
|
|
|
7
7
|
|
|
8
8
|
/** Parameters extracted from the OAuth callback URL. */
|
|
9
9
|
export interface OAuthCallbackParams {
|
|
10
|
+
/** The authorization code returned by the OAuth provider. */
|
|
10
11
|
readonly code: string;
|
|
12
|
+
/** The opaque state token used to correlate the callback with the originating request. */
|
|
11
13
|
readonly state: string;
|
|
12
14
|
}
|
|
13
15
|
|
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
|
| {
|