@stigmer/react 0.0.68 → 0.0.70
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/README.md +1 -1
- package/composer/SessionComposer.d.ts.map +1 -1
- package/composer/SessionComposer.js +12 -3
- package/composer/SessionComposer.js.map +1 -1
- package/demo/__tests__/demo-client.test.d.ts +2 -0
- package/demo/__tests__/demo-client.test.d.ts.map +1 -0
- package/demo/__tests__/demo-client.test.js +133 -0
- package/demo/__tests__/demo-client.test.js.map +1 -0
- package/demo/__tests__/fixtures.test.d.ts +2 -0
- package/demo/__tests__/fixtures.test.d.ts.map +1 -0
- package/demo/__tests__/fixtures.test.js +135 -0
- package/demo/__tests__/fixtures.test.js.map +1 -0
- package/demo/__tests__/samples.test.d.ts +2 -0
- package/demo/__tests__/samples.test.d.ts.map +1 -0
- package/demo/__tests__/samples.test.js +152 -0
- package/demo/__tests__/samples.test.js.map +1 -0
- package/demo/client.d.ts +29 -0
- package/demo/client.d.ts.map +1 -0
- package/demo/client.js +52 -0
- package/demo/client.js.map +1 -0
- package/demo/fixtures.d.ts +190 -0
- package/demo/fixtures.d.ts.map +1 -0
- package/demo/fixtures.js +263 -0
- package/demo/fixtures.js.map +1 -0
- package/demo/index.d.ts +6 -0
- package/demo/index.d.ts.map +1 -0
- package/demo/index.js +6 -0
- package/demo/index.js.map +1 -0
- package/demo/samples.d.ts +166 -0
- package/demo/samples.d.ts.map +1 -0
- package/demo/samples.js +308 -0
- package/demo/samples.js.map +1 -0
- package/demo/transport.d.ts +59 -0
- package/demo/transport.d.ts.map +1 -0
- package/demo/transport.js +75 -0
- package/demo/transport.js.map +1 -0
- package/demo/types.d.ts +62 -0
- package/demo/types.d.ts.map +1 -0
- package/demo/types.js +16 -0
- package/demo/types.js.map +1 -0
- package/environment/EnvVarForm.d.ts.map +1 -1
- package/environment/EnvVarForm.js +1 -1
- package/environment/EnvVarForm.js.map +1 -1
- package/environment/__tests__/systemEnvVars.test.d.ts +2 -0
- package/environment/__tests__/systemEnvVars.test.d.ts.map +1 -0
- package/environment/__tests__/systemEnvVars.test.js +76 -0
- package/environment/__tests__/systemEnvVars.test.js.map +1 -0
- package/environment/index.d.ts +1 -0
- package/environment/index.d.ts.map +1 -1
- package/environment/index.js +1 -0
- package/environment/index.js.map +1 -1
- package/environment/systemEnvVars.d.ts +52 -0
- package/environment/systemEnvVars.d.ts.map +1 -0
- package/environment/systemEnvVars.js +91 -0
- package/environment/systemEnvVars.js.map +1 -0
- package/execution/ApprovalCard.d.ts.map +1 -1
- package/execution/ApprovalCard.js +3 -3
- package/execution/ApprovalCard.js.map +1 -1
- package/index.d.ts +1 -1
- package/index.d.ts.map +1 -1
- package/index.js +2 -2
- package/index.js.map +1 -1
- package/internal/Tabs.d.ts +41 -0
- package/internal/Tabs.d.ts.map +1 -0
- package/internal/Tabs.js +65 -0
- package/internal/Tabs.js.map +1 -0
- package/mcp-server/McpServerDetailView.d.ts +33 -7
- package/mcp-server/McpServerDetailView.d.ts.map +1 -1
- package/mcp-server/McpServerDetailView.js +53 -37
- package/mcp-server/McpServerDetailView.js.map +1 -1
- package/mcp-server/useMcpServerCredentials.d.ts.map +1 -1
- package/mcp-server/useMcpServerCredentials.js +2 -1
- package/mcp-server/useMcpServerCredentials.js.map +1 -1
- package/models/index.d.ts +1 -1
- package/models/index.d.ts.map +1 -1
- package/models/index.js +1 -1
- package/models/index.js.map +1 -1
- package/models/registry.d.ts +10 -0
- package/models/registry.d.ts.map +1 -1
- package/models/registry.js +13 -0
- package/models/registry.js.map +1 -1
- package/models/useModelRegistry.d.ts.map +1 -1
- package/models/useModelRegistry.js +7 -3
- package/models/useModelRegistry.js.map +1 -1
- package/package.json +9 -5
- package/src/composer/SessionComposer.tsx +21 -3
- package/src/demo/__tests__/demo-client.test.tsx +213 -0
- package/src/demo/__tests__/fixtures.test.ts +214 -0
- package/src/demo/__tests__/samples.test.ts +171 -0
- package/src/demo/client.ts +78 -0
- package/src/demo/fixtures.ts +401 -0
- package/src/demo/index.ts +12 -0
- package/src/demo/samples.ts +470 -0
- package/src/demo/transport.ts +116 -0
- package/src/demo/types.ts +69 -0
- package/src/environment/EnvVarForm.tsx +1 -0
- package/src/environment/__tests__/systemEnvVars.test.ts +120 -0
- package/src/environment/index.ts +6 -0
- package/src/environment/systemEnvVars.ts +104 -0
- package/src/execution/ApprovalCard.tsx +4 -0
- package/src/index.ts +5 -1
- package/src/internal/Tabs.tsx +166 -0
- package/src/mcp-server/McpServerDetailView.tsx +273 -204
- package/src/mcp-server/useMcpServerCredentials.ts +4 -1
- package/src/models/index.ts +1 -1
- package/src/models/registry.ts +14 -0
- package/src/models/useModelRegistry.ts +7 -2
- package/styles.css +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { cn } from "@stigmer/theme";
|
|
5
5
|
import { timestampDate } from "@bufbuild/protobuf/wkt";
|
|
6
6
|
import type { McpServer } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
|
|
@@ -22,6 +22,9 @@ import { ErrorMessage } from "../error/ErrorMessage";
|
|
|
22
22
|
import { EnvVarForm } from "../environment/EnvVarForm";
|
|
23
23
|
import type { EnvVarFormVariable } from "../environment/EnvVarForm";
|
|
24
24
|
import { VisibilityToggle } from "../library/VisibilityToggle";
|
|
25
|
+
import { Tabs, type TabItem } from "../internal/Tabs";
|
|
26
|
+
|
|
27
|
+
type CapabilityTab = "tools" | "policies" | "resources";
|
|
25
28
|
|
|
26
29
|
export interface McpServerDetailViewProps {
|
|
27
30
|
/** Organization slug that owns the MCP server. */
|
|
@@ -60,6 +63,29 @@ export interface McpServerDetailViewProps {
|
|
|
60
63
|
sessionId: string;
|
|
61
64
|
executionId: string;
|
|
62
65
|
}) => void;
|
|
66
|
+
/**
|
|
67
|
+
* Initial active capability tab. Defaults to `"tools"`.
|
|
68
|
+
* Useful for deep-linking or demo scenarios that need to start on
|
|
69
|
+
* a specific tab.
|
|
70
|
+
*/
|
|
71
|
+
readonly defaultCapabilityTab?: CapabilityTab;
|
|
72
|
+
/**
|
|
73
|
+
* When `true`, the credential form inside the Tools tab opens
|
|
74
|
+
* immediately on mount (instead of waiting for a Discover click).
|
|
75
|
+
* Useful for guided tours and demo scenarios that need to show the
|
|
76
|
+
* credential entry step without user interaction.
|
|
77
|
+
* @default false
|
|
78
|
+
*/
|
|
79
|
+
readonly defaultShowCredentialForm?: boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Pre-fill function for credential form inputs. When provided, the
|
|
82
|
+
* {@link EnvVarForm} receives these as `poolValues`, populating
|
|
83
|
+
* fields on mount. Useful for demo scenarios that simulate a user
|
|
84
|
+
* having already entered credentials.
|
|
85
|
+
*/
|
|
86
|
+
readonly credentialPoolValues?: (
|
|
87
|
+
key: string,
|
|
88
|
+
) => import("@stigmer/sdk").EnvVarInput | undefined;
|
|
63
89
|
/** Additional CSS classes for the root container. */
|
|
64
90
|
readonly className?: string;
|
|
65
91
|
}
|
|
@@ -68,13 +94,16 @@ export interface McpServerDetailViewProps {
|
|
|
68
94
|
* Read-only detail view for an MCP Server integration.
|
|
69
95
|
*
|
|
70
96
|
* Fetches the server via {@link useMcpServer} internally and renders
|
|
71
|
-
* its full configuration
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
97
|
+
* its full configuration: validation banner (if invalid), header,
|
|
98
|
+
* a **tabbed capabilities panel** (Tools, Policies, and optionally
|
|
99
|
+
* Resources), server configuration, environment variables, and tags.
|
|
100
|
+
*
|
|
101
|
+
* The capabilities panel groups the three core concerns of an MCP
|
|
102
|
+
* server into tabs, each co-locating its action with its output:
|
|
75
103
|
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
104
|
+
* - **Tools** — Discover button + discovered tool list
|
|
105
|
+
* - **Policies** — Existing approval policies + AI generate action
|
|
106
|
+
* - **Resources** — Discovered resource templates (tab hidden when empty)
|
|
78
107
|
*
|
|
79
108
|
* Handles loading, error, and not-found states automatically.
|
|
80
109
|
* Zero Console dependencies — safe for platform builder embedding.
|
|
@@ -93,6 +122,9 @@ export function McpServerDetailView({
|
|
|
93
122
|
onVisibilityChange,
|
|
94
123
|
isVisibilityPending,
|
|
95
124
|
onPolicySessionCreated,
|
|
125
|
+
defaultCapabilityTab = "tools",
|
|
126
|
+
defaultShowCredentialForm = false,
|
|
127
|
+
credentialPoolValues,
|
|
96
128
|
className,
|
|
97
129
|
}: McpServerDetailViewProps) {
|
|
98
130
|
const { mcpServer, isLoading, error, refetch } = useMcpServer(org, slug);
|
|
@@ -100,10 +132,11 @@ export function McpServerDetailView({
|
|
|
100
132
|
const discovery = useDiscoverCapabilities();
|
|
101
133
|
const policySession = useTriggerApprovalPolicySession();
|
|
102
134
|
|
|
103
|
-
const [showCredentialForm, setShowCredentialForm] = useState(
|
|
135
|
+
const [showCredentialForm, setShowCredentialForm] = useState(defaultShowCredentialForm);
|
|
104
136
|
const [policyPanelExecutionId, setPolicyPanelExecutionId] = useState<
|
|
105
137
|
string | null
|
|
106
138
|
>(null);
|
|
139
|
+
const [capabilityTab, setCapabilityTab] = useState<CapabilityTab>(defaultCapabilityTab);
|
|
107
140
|
|
|
108
141
|
const onResourceLoadRef = useRef(onResourceLoad);
|
|
109
142
|
onResourceLoadRef.current = onResourceLoad;
|
|
@@ -181,19 +214,35 @@ export function McpServerDetailView({
|
|
|
181
214
|
refetch();
|
|
182
215
|
}, [refetch]);
|
|
183
216
|
|
|
217
|
+
const spec = mcpServer?.spec;
|
|
218
|
+
const status = mcpServer?.status;
|
|
219
|
+
const specAudit = status?.audit?.specAudit;
|
|
220
|
+
const capabilities = status?.discoveredCapabilities;
|
|
221
|
+
const toolApprovals = spec?.defaultToolApprovals ?? [];
|
|
222
|
+
const tools = capabilities?.tools ?? [];
|
|
223
|
+
const resourceTemplates = capabilities?.resourceTemplates ?? [];
|
|
224
|
+
const hasDiscoveredTools = tools.length > 0;
|
|
225
|
+
|
|
226
|
+
const capabilityTabs: TabItem[] = useMemo(() => {
|
|
227
|
+
const items: TabItem[] = [
|
|
228
|
+
{ id: "tools", label: "Tools", badge: tools.length },
|
|
229
|
+
{ id: "policies", label: "Policies", badge: toolApprovals.length },
|
|
230
|
+
];
|
|
231
|
+
if (resourceTemplates.length > 0) {
|
|
232
|
+
items.push({
|
|
233
|
+
id: "resources",
|
|
234
|
+
label: "Resources",
|
|
235
|
+
badge: resourceTemplates.length,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
return items;
|
|
239
|
+
}, [tools.length, toolApprovals.length, resourceTemplates.length]);
|
|
240
|
+
|
|
184
241
|
if (isLoading) return <LoadingSkeleton className={className} />;
|
|
185
242
|
if (error)
|
|
186
243
|
return <ErrorMessage error={error} retry={refetch} className={className} />;
|
|
187
244
|
if (!mcpServer) return <NotFoundState className={className} />;
|
|
188
245
|
|
|
189
|
-
const spec = mcpServer.spec;
|
|
190
|
-
const status = mcpServer.status;
|
|
191
|
-
const specAudit = status?.audit?.specAudit;
|
|
192
|
-
const capabilities = status?.discoveredCapabilities;
|
|
193
|
-
const toolApprovals = spec?.defaultToolApprovals ?? [];
|
|
194
|
-
const hasDiscoveredTools =
|
|
195
|
-
capabilities != null && capabilities.tools.length > 0;
|
|
196
|
-
|
|
197
246
|
return (
|
|
198
247
|
<div className={cn("flex flex-col gap-6", className)}>
|
|
199
248
|
{status?.validationState === ValidationState.invalid &&
|
|
@@ -218,63 +267,63 @@ export function McpServerDetailView({
|
|
|
218
267
|
isVisibilityPending={isVisibilityPending}
|
|
219
268
|
/>
|
|
220
269
|
|
|
221
|
-
{/* Discovery action */}
|
|
222
|
-
<DiscoverySection
|
|
223
|
-
isDiscovering={discovery.isDiscovering}
|
|
224
|
-
discoveryError={discovery.error}
|
|
225
|
-
onDiscover={handleDiscoverClick}
|
|
226
|
-
onClearDiscoveryError={discovery.clearError}
|
|
227
|
-
hasDiscoveredTools={hasDiscoveredTools}
|
|
228
|
-
showCredentialForm={showCredentialForm}
|
|
229
|
-
credentialVariables={credentials.missingVariables}
|
|
230
|
-
isSavingCredentials={credentials.isSaving}
|
|
231
|
-
onCredentialSubmit={handleCredentialSubmit}
|
|
232
|
-
onCredentialCancel={() => setShowCredentialForm(false)}
|
|
233
|
-
credentialsLoading={credentials.isLoading}
|
|
234
|
-
/>
|
|
235
|
-
|
|
236
270
|
{spec?.serverType.case && (
|
|
237
271
|
<ServerConfigSection serverType={spec.serverType} />
|
|
238
272
|
)}
|
|
239
273
|
|
|
240
|
-
{
|
|
241
|
-
<
|
|
242
|
-
)}
|
|
243
|
-
|
|
244
|
-
{capabilities && capabilities.resourceTemplates.length > 0 && (
|
|
245
|
-
<ResourceTemplatesSection
|
|
246
|
-
templates={capabilities.resourceTemplates}
|
|
247
|
-
/>
|
|
248
|
-
)}
|
|
249
|
-
|
|
250
|
-
{/* Approval Policies */}
|
|
251
|
-
{toolApprovals.length > 0 && (
|
|
252
|
-
<ApprovalPoliciesSection policies={toolApprovals} />
|
|
274
|
+
{spec?.envSpec && Object.keys(spec.envSpec.data).length > 0 && (
|
|
275
|
+
<EnvSpecSection data={spec.envSpec.data} />
|
|
253
276
|
)}
|
|
254
277
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
278
|
+
<section>
|
|
279
|
+
<h3 className="mb-2 text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
|
280
|
+
Capabilities
|
|
281
|
+
</h3>
|
|
282
|
+
<div className="overflow-hidden rounded-lg border border-border">
|
|
283
|
+
<Tabs
|
|
284
|
+
tabs={capabilityTabs}
|
|
285
|
+
activeTab={capabilityTab}
|
|
286
|
+
onTabChange={(id) => setCapabilityTab(id as CapabilityTab)}
|
|
287
|
+
aria-label="MCP server capabilities"
|
|
288
|
+
>
|
|
289
|
+
{capabilityTab === "tools" && (
|
|
290
|
+
<ToolsTabContent
|
|
291
|
+
tools={tools}
|
|
292
|
+
isDiscovering={discovery.isDiscovering}
|
|
293
|
+
discoveryError={discovery.error}
|
|
294
|
+
onDiscover={handleDiscoverClick}
|
|
295
|
+
onClearDiscoveryError={discovery.clearError}
|
|
296
|
+
hasDiscoveredTools={hasDiscoveredTools}
|
|
297
|
+
showCredentialForm={showCredentialForm}
|
|
298
|
+
credentialVariables={credentials.missingVariables}
|
|
299
|
+
isSavingCredentials={credentials.isSaving}
|
|
300
|
+
onCredentialSubmit={handleCredentialSubmit}
|
|
301
|
+
onCredentialCancel={() => setShowCredentialForm(false)}
|
|
302
|
+
credentialsLoading={credentials.isLoading}
|
|
303
|
+
credentialPoolValues={credentialPoolValues}
|
|
304
|
+
/>
|
|
305
|
+
)}
|
|
265
306
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
307
|
+
{capabilityTab === "policies" && (
|
|
308
|
+
<PoliciesTabContent
|
|
309
|
+
policies={toolApprovals}
|
|
310
|
+
hasDiscoveredTools={hasDiscoveredTools}
|
|
311
|
+
isTriggering={policySession.isTriggering}
|
|
312
|
+
triggerError={policySession.error}
|
|
313
|
+
onGenerate={handleGenerateApprovalPolicies}
|
|
314
|
+
onClearTriggerError={policySession.clearError}
|
|
315
|
+
policyPanelExecutionId={policyPanelExecutionId}
|
|
316
|
+
onPolicyPanelClose={() => setPolicyPanelExecutionId(null)}
|
|
317
|
+
onPolicyPanelComplete={handlePolicyPanelComplete}
|
|
318
|
+
/>
|
|
319
|
+
)}
|
|
274
320
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
321
|
+
{capabilityTab === "resources" && (
|
|
322
|
+
<ResourceTemplatesList templates={resourceTemplates} />
|
|
323
|
+
)}
|
|
324
|
+
</Tabs>
|
|
325
|
+
</div>
|
|
326
|
+
</section>
|
|
278
327
|
|
|
279
328
|
{spec && spec.tags.length > 0 && <TagsSection tags={spec.tags} />}
|
|
280
329
|
</div>
|
|
@@ -487,58 +536,33 @@ function ServerConfigSection({
|
|
|
487
536
|
);
|
|
488
537
|
}
|
|
489
538
|
|
|
490
|
-
function
|
|
491
|
-
tools,
|
|
492
|
-
}: {
|
|
493
|
-
readonly tools: readonly DiscoveredTool[];
|
|
494
|
-
}) {
|
|
495
|
-
return (
|
|
496
|
-
<Section title={`Tools (${tools.length})`}>
|
|
497
|
-
<div className="flex flex-col divide-y divide-border">
|
|
498
|
-
{tools.map((tool) => (
|
|
499
|
-
<div key={tool.name} className="px-3 py-2.5">
|
|
500
|
-
<code className="font-mono text-sm font-medium text-foreground">
|
|
501
|
-
{tool.name}
|
|
502
|
-
</code>
|
|
503
|
-
{tool.description && (
|
|
504
|
-
<p className="mt-0.5 text-xs text-muted-foreground">
|
|
505
|
-
{tool.description}
|
|
506
|
-
</p>
|
|
507
|
-
)}
|
|
508
|
-
</div>
|
|
509
|
-
))}
|
|
510
|
-
</div>
|
|
511
|
-
</Section>
|
|
512
|
-
);
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
function ResourceTemplatesSection({
|
|
539
|
+
function ResourceTemplatesList({
|
|
516
540
|
templates,
|
|
517
541
|
}: {
|
|
518
542
|
readonly templates: readonly DiscoveredResourceTemplate[];
|
|
519
543
|
}) {
|
|
544
|
+
if (templates.length === 0) return null;
|
|
545
|
+
|
|
520
546
|
return (
|
|
521
|
-
<
|
|
522
|
-
|
|
523
|
-
{
|
|
524
|
-
<div
|
|
525
|
-
<
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
</code>
|
|
532
|
-
</div>
|
|
533
|
-
{tpl.description && (
|
|
534
|
-
<p className="mt-0.5 text-xs text-muted-foreground">
|
|
535
|
-
{tpl.description}
|
|
536
|
-
</p>
|
|
537
|
-
)}
|
|
547
|
+
<div className="flex flex-col divide-y divide-border">
|
|
548
|
+
{templates.map((tpl) => (
|
|
549
|
+
<div key={tpl.uriTemplate || tpl.name} className="px-3 py-2.5">
|
|
550
|
+
<div className="flex items-baseline gap-2">
|
|
551
|
+
<span className="text-sm font-medium text-foreground">
|
|
552
|
+
{tpl.name}
|
|
553
|
+
</span>
|
|
554
|
+
<code className="font-mono text-[10px] text-muted-foreground">
|
|
555
|
+
{tpl.uriTemplate}
|
|
556
|
+
</code>
|
|
538
557
|
</div>
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
558
|
+
{tpl.description && (
|
|
559
|
+
<p className="mt-0.5 text-xs text-muted-foreground">
|
|
560
|
+
{tpl.description}
|
|
561
|
+
</p>
|
|
562
|
+
)}
|
|
563
|
+
</div>
|
|
564
|
+
))}
|
|
565
|
+
</div>
|
|
542
566
|
);
|
|
543
567
|
}
|
|
544
568
|
|
|
@@ -592,10 +616,11 @@ function TagsSection({ tags }: { readonly tags: readonly string[] }) {
|
|
|
592
616
|
}
|
|
593
617
|
|
|
594
618
|
// ---------------------------------------------------------------------------
|
|
595
|
-
//
|
|
619
|
+
// Capability tab contents
|
|
596
620
|
// ---------------------------------------------------------------------------
|
|
597
621
|
|
|
598
|
-
function
|
|
622
|
+
function ToolsTabContent({
|
|
623
|
+
tools,
|
|
599
624
|
isDiscovering,
|
|
600
625
|
discoveryError,
|
|
601
626
|
onDiscover,
|
|
@@ -607,7 +632,9 @@ function DiscoverySection({
|
|
|
607
632
|
onCredentialSubmit,
|
|
608
633
|
onCredentialCancel,
|
|
609
634
|
credentialsLoading,
|
|
635
|
+
credentialPoolValues,
|
|
610
636
|
}: {
|
|
637
|
+
readonly tools: readonly DiscoveredTool[];
|
|
611
638
|
readonly isDiscovering: boolean;
|
|
612
639
|
readonly discoveryError: Error | null;
|
|
613
640
|
readonly onDiscover: () => void;
|
|
@@ -622,19 +649,26 @@ function DiscoverySection({
|
|
|
622
649
|
) => void;
|
|
623
650
|
readonly onCredentialCancel: () => void;
|
|
624
651
|
readonly credentialsLoading: boolean;
|
|
652
|
+
readonly credentialPoolValues?: (
|
|
653
|
+
key: string,
|
|
654
|
+
) => import("@stigmer/sdk").EnvVarInput | undefined;
|
|
625
655
|
}) {
|
|
626
656
|
return (
|
|
627
|
-
<
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
657
|
+
<div className="flex flex-col">
|
|
658
|
+
{/* Discovery action bar */}
|
|
659
|
+
<div className="flex items-center justify-between border-b border-border px-3 py-2">
|
|
660
|
+
<span className="text-xs text-muted-foreground">
|
|
661
|
+
{hasDiscoveredTools
|
|
662
|
+
? `${tools.length} tool${tools.length !== 1 ? "s" : ""} discovered`
|
|
663
|
+
: "No tools discovered yet"}
|
|
664
|
+
</span>
|
|
632
665
|
<button
|
|
633
666
|
type="button"
|
|
634
667
|
onClick={onDiscover}
|
|
635
668
|
disabled={isDiscovering || credentialsLoading}
|
|
669
|
+
data-cursor-target="discover-button"
|
|
636
670
|
className={cn(
|
|
637
|
-
"inline-flex items-center gap-1.5 rounded-md px-
|
|
671
|
+
"inline-flex items-center gap-1.5 rounded-md px-2.5 py-1 text-xs font-medium",
|
|
638
672
|
"border border-border bg-background text-foreground",
|
|
639
673
|
"hover:bg-accent hover:text-accent-foreground",
|
|
640
674
|
"disabled:pointer-events-none disabled:opacity-50",
|
|
@@ -653,14 +687,14 @@ function DiscoverySection({
|
|
|
653
687
|
) : (
|
|
654
688
|
<>
|
|
655
689
|
<DiscoverIcon className="size-3.5" />
|
|
656
|
-
Discover
|
|
690
|
+
Discover
|
|
657
691
|
</>
|
|
658
692
|
)}
|
|
659
693
|
</button>
|
|
660
694
|
</div>
|
|
661
695
|
|
|
662
696
|
{discoveryError && (
|
|
663
|
-
<div className="flex items-start gap-2
|
|
697
|
+
<div className="flex items-start gap-2 border-b border-destructive/20 bg-destructive/5 px-3 py-2">
|
|
664
698
|
<WarningIcon className="mt-0.5 size-3.5 shrink-0 text-destructive" />
|
|
665
699
|
<p className="flex-1 text-xs text-destructive">
|
|
666
700
|
{discoveryError.message}
|
|
@@ -677,7 +711,10 @@ function DiscoverySection({
|
|
|
677
711
|
)}
|
|
678
712
|
|
|
679
713
|
{showCredentialForm && credentialVariables.length > 0 && (
|
|
680
|
-
<div
|
|
714
|
+
<div
|
|
715
|
+
className="border-b border-border p-4"
|
|
716
|
+
data-cursor-target="credential-form"
|
|
717
|
+
>
|
|
681
718
|
<EnvVarForm
|
|
682
719
|
title="Credentials Required"
|
|
683
720
|
description="Enter the credentials needed to connect to this MCP server. Toggle "Save for future runs" to persist them in your personal environment, or leave it off to use them for this discovery only."
|
|
@@ -685,118 +722,111 @@ function DiscoverySection({
|
|
|
685
722
|
onSubmit={(values, options) => onCredentialSubmit(values, options)}
|
|
686
723
|
onCancel={onCredentialCancel}
|
|
687
724
|
isSubmitting={isSavingCredentials}
|
|
725
|
+
poolValues={credentialPoolValues}
|
|
688
726
|
className="w-full max-w-md"
|
|
689
727
|
/>
|
|
690
728
|
</div>
|
|
691
729
|
)}
|
|
692
730
|
|
|
693
|
-
{
|
|
694
|
-
<
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
resources.
|
|
698
|
-
</p>
|
|
699
|
-
)}
|
|
700
|
-
</section>
|
|
701
|
-
);
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
// ---------------------------------------------------------------------------
|
|
705
|
-
// Approval policies section
|
|
706
|
-
// ---------------------------------------------------------------------------
|
|
707
|
-
|
|
708
|
-
function ApprovalPoliciesSection({
|
|
709
|
-
policies,
|
|
710
|
-
}: {
|
|
711
|
-
readonly policies: readonly ToolApprovalPolicy[];
|
|
712
|
-
}) {
|
|
713
|
-
return (
|
|
714
|
-
<Section title={`Approval Policies (${policies.length})`}>
|
|
715
|
-
<div className="flex flex-col divide-y divide-border">
|
|
716
|
-
{policies.map((policy) => (
|
|
717
|
-
<div key={policy.toolName} className="px-3 py-2.5">
|
|
718
|
-
<div className="flex items-baseline gap-2">
|
|
731
|
+
{hasDiscoveredTools ? (
|
|
732
|
+
<div className="flex flex-col divide-y divide-border">
|
|
733
|
+
{tools.map((tool) => (
|
|
734
|
+
<div key={tool.name} className="px-3 py-2.5">
|
|
719
735
|
<code className="font-mono text-sm font-medium text-foreground">
|
|
720
|
-
{
|
|
736
|
+
{tool.name}
|
|
721
737
|
</code>
|
|
722
|
-
|
|
738
|
+
{tool.description && (
|
|
739
|
+
<p className="mt-0.5 text-xs text-muted-foreground">
|
|
740
|
+
{tool.description}
|
|
741
|
+
</p>
|
|
742
|
+
)}
|
|
723
743
|
</div>
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
744
|
+
))}
|
|
745
|
+
</div>
|
|
746
|
+
) : (
|
|
747
|
+
!isDiscovering && (
|
|
748
|
+
<div className="px-3 py-8 text-center">
|
|
749
|
+
<DiscoverIcon className="mx-auto mb-2 size-6 text-muted-foreground/40" />
|
|
750
|
+
<p className="text-xs text-muted-foreground">
|
|
751
|
+
Click "Discover" to connect to this MCP server and
|
|
752
|
+
list its available tools and resources.
|
|
753
|
+
</p>
|
|
729
754
|
</div>
|
|
730
|
-
)
|
|
731
|
-
|
|
732
|
-
</
|
|
755
|
+
)
|
|
756
|
+
)}
|
|
757
|
+
</div>
|
|
733
758
|
);
|
|
734
759
|
}
|
|
735
760
|
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
function GenerateApprovalPoliciesSection({
|
|
741
|
-
hasPolicies,
|
|
761
|
+
function PoliciesTabContent({
|
|
762
|
+
policies,
|
|
763
|
+
hasDiscoveredTools,
|
|
742
764
|
isTriggering,
|
|
743
765
|
triggerError,
|
|
744
766
|
onGenerate,
|
|
745
|
-
|
|
767
|
+
onClearTriggerError,
|
|
768
|
+
policyPanelExecutionId,
|
|
769
|
+
onPolicyPanelClose,
|
|
770
|
+
onPolicyPanelComplete,
|
|
746
771
|
}: {
|
|
747
|
-
readonly
|
|
772
|
+
readonly policies: readonly ToolApprovalPolicy[];
|
|
773
|
+
readonly hasDiscoveredTools: boolean;
|
|
748
774
|
readonly isTriggering: boolean;
|
|
749
775
|
readonly triggerError: Error | null;
|
|
750
776
|
readonly onGenerate: () => void;
|
|
751
|
-
readonly
|
|
777
|
+
readonly onClearTriggerError: () => void;
|
|
778
|
+
readonly policyPanelExecutionId: string | null;
|
|
779
|
+
readonly onPolicyPanelClose: () => void;
|
|
780
|
+
readonly onPolicyPanelComplete: () => void;
|
|
752
781
|
}) {
|
|
782
|
+
const hasPolicies = policies.length > 0;
|
|
783
|
+
|
|
753
784
|
return (
|
|
754
|
-
<
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
</h3>
|
|
760
|
-
<p className="mt-0.5 text-xs text-muted-foreground">
|
|
785
|
+
<div className="flex flex-col">
|
|
786
|
+
{/* Generate action bar */}
|
|
787
|
+
{hasDiscoveredTools && (
|
|
788
|
+
<div className="flex items-center justify-between border-b border-border px-3 py-2">
|
|
789
|
+
<span className="text-xs text-muted-foreground">
|
|
761
790
|
{hasPolicies
|
|
762
|
-
? "Regenerate
|
|
763
|
-
: "
|
|
764
|
-
</
|
|
791
|
+
? "Regenerate to reclassify each tool\u2019s risk level with AI."
|
|
792
|
+
: "Generate approval policies for discovered tools using AI."}
|
|
793
|
+
</span>
|
|
794
|
+
<button
|
|
795
|
+
type="button"
|
|
796
|
+
onClick={onGenerate}
|
|
797
|
+
disabled={isTriggering}
|
|
798
|
+
data-cursor-target="generate-policies-button"
|
|
799
|
+
className={cn(
|
|
800
|
+
"inline-flex shrink-0 items-center gap-1.5 rounded-md px-2.5 py-1 text-xs font-medium",
|
|
801
|
+
"bg-primary text-primary-foreground",
|
|
802
|
+
"hover:bg-primary/90",
|
|
803
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
804
|
+
)}
|
|
805
|
+
>
|
|
806
|
+
{isTriggering ? (
|
|
807
|
+
<>
|
|
808
|
+
<DiscoverSpinner />
|
|
809
|
+
Starting...
|
|
810
|
+
</>
|
|
811
|
+
) : (
|
|
812
|
+
<>
|
|
813
|
+
<SparklesIcon className="size-3.5" />
|
|
814
|
+
{hasPolicies ? "Regenerate" : "Generate"}
|
|
815
|
+
</>
|
|
816
|
+
)}
|
|
817
|
+
</button>
|
|
765
818
|
</div>
|
|
766
|
-
|
|
767
|
-
type="button"
|
|
768
|
-
onClick={onGenerate}
|
|
769
|
-
disabled={isTriggering}
|
|
770
|
-
className={cn(
|
|
771
|
-
"inline-flex shrink-0 items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium",
|
|
772
|
-
"bg-primary text-primary-foreground",
|
|
773
|
-
"hover:bg-primary/90",
|
|
774
|
-
"disabled:pointer-events-none disabled:opacity-50",
|
|
775
|
-
)}
|
|
776
|
-
>
|
|
777
|
-
{isTriggering ? (
|
|
778
|
-
<>
|
|
779
|
-
<DiscoverSpinner />
|
|
780
|
-
Starting...
|
|
781
|
-
</>
|
|
782
|
-
) : (
|
|
783
|
-
<>
|
|
784
|
-
<SparklesIcon className="size-3.5" />
|
|
785
|
-
{hasPolicies ? "Regenerate Policies" : "Generate Policies"}
|
|
786
|
-
</>
|
|
787
|
-
)}
|
|
788
|
-
</button>
|
|
789
|
-
</div>
|
|
819
|
+
)}
|
|
790
820
|
|
|
791
821
|
{triggerError && (
|
|
792
|
-
<div className="flex items-start gap-2
|
|
822
|
+
<div className="flex items-start gap-2 border-b border-destructive/20 bg-destructive/5 px-3 py-2">
|
|
793
823
|
<WarningIcon className="mt-0.5 size-3.5 shrink-0 text-destructive" />
|
|
794
824
|
<p className="flex-1 text-xs text-destructive">
|
|
795
825
|
{triggerError.message}
|
|
796
826
|
</p>
|
|
797
827
|
<button
|
|
798
828
|
type="button"
|
|
799
|
-
onClick={
|
|
829
|
+
onClick={onClearTriggerError}
|
|
800
830
|
className="shrink-0 text-xs text-destructive/70 hover:text-destructive"
|
|
801
831
|
aria-label="Dismiss error"
|
|
802
832
|
>
|
|
@@ -804,7 +834,46 @@ function GenerateApprovalPoliciesSection({
|
|
|
804
834
|
</button>
|
|
805
835
|
</div>
|
|
806
836
|
)}
|
|
807
|
-
|
|
837
|
+
|
|
838
|
+
{hasPolicies ? (
|
|
839
|
+
<div className="flex flex-col divide-y divide-border">
|
|
840
|
+
{policies.map((policy) => (
|
|
841
|
+
<div key={policy.toolName} className="px-3 py-2.5">
|
|
842
|
+
<div className="flex items-baseline gap-2">
|
|
843
|
+
<code className="font-mono text-sm font-medium text-foreground">
|
|
844
|
+
{policy.toolName}
|
|
845
|
+
</code>
|
|
846
|
+
<ShieldIcon className="size-3 text-amber-500 dark:text-amber-400" />
|
|
847
|
+
</div>
|
|
848
|
+
{policy.message && (
|
|
849
|
+
<p className="mt-0.5 text-xs text-muted-foreground">
|
|
850
|
+
{policy.message}
|
|
851
|
+
</p>
|
|
852
|
+
)}
|
|
853
|
+
</div>
|
|
854
|
+
))}
|
|
855
|
+
</div>
|
|
856
|
+
) : (
|
|
857
|
+
<div className="px-3 py-8 text-center">
|
|
858
|
+
<ShieldIcon className="mx-auto mb-2 size-6 text-muted-foreground/40" />
|
|
859
|
+
<p className="text-xs text-muted-foreground">
|
|
860
|
+
{hasDiscoveredTools
|
|
861
|
+
? "No approval policies yet. Generate them to add human oversight for sensitive tools."
|
|
862
|
+
: "Discover tools first, then generate approval policies."}
|
|
863
|
+
</p>
|
|
864
|
+
</div>
|
|
865
|
+
)}
|
|
866
|
+
|
|
867
|
+
{policyPanelExecutionId && (
|
|
868
|
+
<div className="border-t border-border">
|
|
869
|
+
<ApprovalPolicyGeneratorPanel
|
|
870
|
+
executionId={policyPanelExecutionId}
|
|
871
|
+
onClose={onPolicyPanelClose}
|
|
872
|
+
onComplete={onPolicyPanelComplete}
|
|
873
|
+
/>
|
|
874
|
+
</div>
|
|
875
|
+
)}
|
|
876
|
+
</div>
|
|
808
877
|
);
|
|
809
878
|
}
|
|
810
879
|
|
|
@@ -5,6 +5,7 @@ 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
7
|
import { diffEnvSpec } from "../environment/diffEnvSpec";
|
|
8
|
+
import { SYSTEM_ENV_VAR_KEYS } from "../environment/systemEnvVars";
|
|
8
9
|
import type { EnvVarFormVariable } from "../environment/EnvVarForm";
|
|
9
10
|
|
|
10
11
|
export interface UseMcpServerCredentialsReturn {
|
|
@@ -82,7 +83,9 @@ export function useMcpServerCredentials(
|
|
|
82
83
|
const existingKeys = new Set(
|
|
83
84
|
Object.keys(personalEnv.environment?.spec?.data ?? {}),
|
|
84
85
|
);
|
|
85
|
-
return diffEnvSpec(envSpecData, existingKeys)
|
|
86
|
+
return diffEnvSpec(envSpecData, existingKeys).filter(
|
|
87
|
+
(v) => !SYSTEM_ENV_VAR_KEYS.has(v.key),
|
|
88
|
+
);
|
|
86
89
|
}, [mcpServer, personalEnv.environment]);
|
|
87
90
|
|
|
88
91
|
const isReady =
|