@stigmer/react 0.0.42 → 0.0.44
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/execution/ArtifactContentRenderer.d.ts +58 -0
- package/execution/ArtifactContentRenderer.d.ts.map +1 -0
- package/execution/ArtifactContentRenderer.js +163 -0
- package/execution/ArtifactContentRenderer.js.map +1 -0
- package/execution/ArtifactPreviewModal.d.ts +3 -2
- package/execution/ArtifactPreviewModal.d.ts.map +1 -1
- package/execution/ArtifactPreviewModal.js +11 -62
- package/execution/ArtifactPreviewModal.js.map +1 -1
- package/execution/ExecutionPhaseBadge.js +1 -1
- package/execution/ExecutionPhaseBadge.js.map +1 -1
- package/execution/MessageThread.d.ts.map +1 -1
- package/execution/MessageThread.js +21 -2
- package/execution/MessageThread.js.map +1 -1
- package/execution/SetupProgress.d.ts +35 -0
- package/execution/SetupProgress.d.ts.map +1 -0
- package/execution/SetupProgress.js +65 -0
- package/execution/SetupProgress.js.map +1 -0
- package/execution/ToolCallGroup.d.ts.map +1 -1
- package/execution/ToolCallGroup.js +9 -1
- package/execution/ToolCallGroup.js.map +1 -1
- package/execution/ToolCallItem.js +8 -3
- package/execution/ToolCallItem.js.map +1 -1
- package/execution/artifact-utils.d.ts +50 -0
- package/execution/artifact-utils.d.ts.map +1 -1
- package/execution/artifact-utils.js +67 -5
- package/execution/artifact-utils.js.map +1 -1
- package/execution/index.d.ts +6 -1
- package/execution/index.d.ts.map +1 -1
- package/execution/index.js +3 -1
- package/execution/index.js.map +1 -1
- package/index.d.ts +4 -4
- package/index.d.ts.map +1 -1
- package/index.js +2 -2
- package/index.js.map +1 -1
- package/internal/markdown-components.d.ts +10 -0
- package/internal/markdown-components.d.ts.map +1 -1
- package/internal/markdown-components.js +13 -0
- package/internal/markdown-components.js.map +1 -1
- package/mcp-server/ApprovalPolicyGeneratorPanel.d.ts +34 -0
- package/mcp-server/ApprovalPolicyGeneratorPanel.d.ts.map +1 -0
- package/mcp-server/ApprovalPolicyGeneratorPanel.js +55 -0
- package/mcp-server/ApprovalPolicyGeneratorPanel.js.map +1 -0
- package/mcp-server/McpServerDetailView.d.ts.map +1 -1
- package/mcp-server/McpServerDetailView.js +101 -2
- package/mcp-server/McpServerDetailView.js.map +1 -1
- package/mcp-server/index.d.ts +8 -0
- package/mcp-server/index.d.ts.map +1 -1
- package/mcp-server/index.js +4 -0
- package/mcp-server/index.js.map +1 -1
- package/mcp-server/useDiscoverCapabilities.d.ts +59 -0
- package/mcp-server/useDiscoverCapabilities.d.ts.map +1 -0
- package/mcp-server/useDiscoverCapabilities.js +77 -0
- package/mcp-server/useDiscoverCapabilities.js.map +1 -0
- package/mcp-server/useMcpServerCredentials.d.ts +63 -0
- package/mcp-server/useMcpServerCredentials.d.ts.map +1 -0
- package/mcp-server/useMcpServerCredentials.js +64 -0
- package/mcp-server/useMcpServerCredentials.js.map +1 -0
- package/mcp-server/useTriggerApprovalPolicySession.d.ts +42 -0
- package/mcp-server/useTriggerApprovalPolicySession.d.ts.map +1 -0
- package/mcp-server/useTriggerApprovalPolicySession.js +111 -0
- package/mcp-server/useTriggerApprovalPolicySession.js.map +1 -0
- package/package.json +4 -4
- package/session/__tests__/useSessionConversation.test.js +223 -2
- package/session/__tests__/useSessionConversation.test.js.map +1 -1
- package/session/useSessionConversation.d.ts +8 -1
- package/session/useSessionConversation.d.ts.map +1 -1
- package/session/useSessionConversation.js +77 -6
- package/session/useSessionConversation.js.map +1 -1
- package/skill/SkillDetailView.js +2 -2
- package/skill/SkillDetailView.js.map +1 -1
- package/src/execution/ArtifactContentRenderer.tsx +376 -0
- package/src/execution/ArtifactPreviewModal.tsx +22 -114
- package/src/execution/ExecutionPhaseBadge.tsx +1 -1
- package/src/execution/MessageThread.tsx +35 -3
- package/src/execution/SetupProgress.tsx +120 -0
- package/src/execution/ToolCallGroup.tsx +15 -1
- package/src/execution/ToolCallItem.tsx +10 -3
- package/src/execution/artifact-utils.ts +88 -4
- package/src/execution/index.ts +9 -0
- package/src/index.ts +16 -0
- package/src/internal/markdown-components.tsx +15 -0
- package/src/mcp-server/ApprovalPolicyGeneratorPanel.tsx +164 -0
- package/src/mcp-server/McpServerDetailView.tsx +428 -2
- package/src/mcp-server/index.ts +15 -0
- package/src/mcp-server/useDiscoverCapabilities.ts +117 -0
- package/src/mcp-server/useMcpServerCredentials.ts +108 -0
- package/src/mcp-server/useTriggerApprovalPolicySession.ts +161 -0
- package/src/session/__tests__/useSessionConversation.test.tsx +355 -2
- package/src/session/useSessionConversation.ts +104 -9
- package/src/skill/SkillDetailView.tsx +2 -2
- package/styles.css +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useEffect, useRef } from "react";
|
|
3
|
+
import { useCallback, useEffect, 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";
|
|
@@ -8,11 +8,19 @@ import type {
|
|
|
8
8
|
DiscoveredTool,
|
|
9
9
|
DiscoveredResourceTemplate,
|
|
10
10
|
} from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/status_pb";
|
|
11
|
+
import type { ToolApprovalPolicy } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/spec_pb";
|
|
11
12
|
import { ValidationState } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/status_pb";
|
|
12
13
|
import type { EnvironmentValue } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/spec_pb";
|
|
13
14
|
import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
|
|
14
15
|
import { useMcpServer } from "./useMcpServer";
|
|
16
|
+
import { useDiscoverCapabilities } from "./useDiscoverCapabilities";
|
|
17
|
+
import { useMcpServerCredentials } from "./useMcpServerCredentials";
|
|
18
|
+
import { useTriggerApprovalPolicySession } from "./useTriggerApprovalPolicySession";
|
|
19
|
+
import { ApprovalPolicyGeneratorPanel } from "./ApprovalPolicyGeneratorPanel";
|
|
20
|
+
import { serializeMcpServerYaml } from "../library/serialize-resource-yaml";
|
|
15
21
|
import { ErrorMessage } from "../error/ErrorMessage";
|
|
22
|
+
import { EnvVarForm } from "../environment/EnvVarForm";
|
|
23
|
+
import type { EnvVarFormVariable } from "../environment/EnvVarForm";
|
|
16
24
|
import { VisibilityToggle } from "../library/VisibilityToggle";
|
|
17
25
|
|
|
18
26
|
export interface McpServerDetailViewProps {
|
|
@@ -73,6 +81,14 @@ export function McpServerDetailView({
|
|
|
73
81
|
className,
|
|
74
82
|
}: McpServerDetailViewProps) {
|
|
75
83
|
const { mcpServer, isLoading, error, refetch } = useMcpServer(org, slug);
|
|
84
|
+
const credentials = useMcpServerCredentials(org, mcpServer ?? null);
|
|
85
|
+
const discovery = useDiscoverCapabilities();
|
|
86
|
+
const policySession = useTriggerApprovalPolicySession();
|
|
87
|
+
|
|
88
|
+
const [showCredentialForm, setShowCredentialForm] = useState(false);
|
|
89
|
+
const [policyPanelExecutionId, setPolicyPanelExecutionId] = useState<
|
|
90
|
+
string | null
|
|
91
|
+
>(null);
|
|
76
92
|
|
|
77
93
|
const onResourceLoadRef = useRef(onResourceLoad);
|
|
78
94
|
onResourceLoadRef.current = onResourceLoad;
|
|
@@ -83,6 +99,66 @@ export function McpServerDetailView({
|
|
|
83
99
|
}
|
|
84
100
|
}, [mcpServer]);
|
|
85
101
|
|
|
102
|
+
const handleDiscoverClick = useCallback(async () => {
|
|
103
|
+
if (!mcpServer?.metadata?.id) return;
|
|
104
|
+
|
|
105
|
+
if (!credentials.isReady) {
|
|
106
|
+
setShowCredentialForm(true);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
await discovery.discover(mcpServer.metadata.id);
|
|
112
|
+
refetch();
|
|
113
|
+
} catch {
|
|
114
|
+
// error state is managed by the hook
|
|
115
|
+
}
|
|
116
|
+
}, [mcpServer, credentials.isReady, discovery, refetch]);
|
|
117
|
+
|
|
118
|
+
const handleCredentialSubmit = useCallback(
|
|
119
|
+
async (
|
|
120
|
+
values: Record<string, import("@stigmer/sdk").EnvVarInput>,
|
|
121
|
+
options: { saveForFuture: boolean },
|
|
122
|
+
) => {
|
|
123
|
+
try {
|
|
124
|
+
if (options.saveForFuture) {
|
|
125
|
+
await credentials.saveCredentials(values);
|
|
126
|
+
credentials.refetch();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
setShowCredentialForm(false);
|
|
130
|
+
|
|
131
|
+
if (mcpServer?.metadata?.id) {
|
|
132
|
+
if (options.saveForFuture) {
|
|
133
|
+
await discovery.discover(mcpServer.metadata.id);
|
|
134
|
+
} else {
|
|
135
|
+
await discovery.discover(mcpServer.metadata.id, values);
|
|
136
|
+
}
|
|
137
|
+
refetch();
|
|
138
|
+
}
|
|
139
|
+
} catch {
|
|
140
|
+
// error state is managed by the hooks
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
[credentials, mcpServer, discovery, refetch],
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
const handleGenerateApprovalPolicies = useCallback(async () => {
|
|
147
|
+
if (!mcpServer) return;
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
const yaml = serializeMcpServerYaml(mcpServer);
|
|
151
|
+
const result = await policySession.trigger(yaml, org, slug);
|
|
152
|
+
setPolicyPanelExecutionId(result.executionId);
|
|
153
|
+
} catch {
|
|
154
|
+
// error state is managed by the hook
|
|
155
|
+
}
|
|
156
|
+
}, [mcpServer, org, slug, policySession]);
|
|
157
|
+
|
|
158
|
+
const handlePolicyPanelComplete = useCallback(() => {
|
|
159
|
+
refetch();
|
|
160
|
+
}, [refetch]);
|
|
161
|
+
|
|
86
162
|
if (isLoading) return <LoadingSkeleton className={className} />;
|
|
87
163
|
if (error)
|
|
88
164
|
return <ErrorMessage error={error} retry={refetch} className={className} />;
|
|
@@ -92,6 +168,9 @@ export function McpServerDetailView({
|
|
|
92
168
|
const status = mcpServer.status;
|
|
93
169
|
const specAudit = status?.audit?.specAudit;
|
|
94
170
|
const capabilities = status?.discoveredCapabilities;
|
|
171
|
+
const toolApprovals = spec?.defaultToolApprovals ?? [];
|
|
172
|
+
const hasDiscoveredTools =
|
|
173
|
+
capabilities != null && capabilities.tools.length > 0;
|
|
95
174
|
|
|
96
175
|
return (
|
|
97
176
|
<div className={cn("flex flex-col gap-6", className)}>
|
|
@@ -117,11 +196,26 @@ export function McpServerDetailView({
|
|
|
117
196
|
isVisibilityPending={isVisibilityPending}
|
|
118
197
|
/>
|
|
119
198
|
|
|
199
|
+
{/* Discovery action */}
|
|
200
|
+
<DiscoverySection
|
|
201
|
+
isDiscovering={discovery.isDiscovering}
|
|
202
|
+
discoveryError={discovery.error}
|
|
203
|
+
onDiscover={handleDiscoverClick}
|
|
204
|
+
onClearDiscoveryError={discovery.clearError}
|
|
205
|
+
hasDiscoveredTools={hasDiscoveredTools}
|
|
206
|
+
showCredentialForm={showCredentialForm}
|
|
207
|
+
credentialVariables={credentials.missingVariables}
|
|
208
|
+
isSavingCredentials={credentials.isSaving}
|
|
209
|
+
onCredentialSubmit={handleCredentialSubmit}
|
|
210
|
+
onCredentialCancel={() => setShowCredentialForm(false)}
|
|
211
|
+
credentialsLoading={credentials.isLoading}
|
|
212
|
+
/>
|
|
213
|
+
|
|
120
214
|
{spec?.serverType.case && (
|
|
121
215
|
<ServerConfigSection serverType={spec.serverType} />
|
|
122
216
|
)}
|
|
123
217
|
|
|
124
|
-
{
|
|
218
|
+
{hasDiscoveredTools && (
|
|
125
219
|
<ToolsSection tools={capabilities.tools} />
|
|
126
220
|
)}
|
|
127
221
|
|
|
@@ -131,6 +225,31 @@ export function McpServerDetailView({
|
|
|
131
225
|
/>
|
|
132
226
|
)}
|
|
133
227
|
|
|
228
|
+
{/* Approval Policies */}
|
|
229
|
+
{toolApprovals.length > 0 && (
|
|
230
|
+
<ApprovalPoliciesSection policies={toolApprovals} />
|
|
231
|
+
)}
|
|
232
|
+
|
|
233
|
+
{/* Generate Approval Policies */}
|
|
234
|
+
{hasDiscoveredTools && (
|
|
235
|
+
<GenerateApprovalPoliciesSection
|
|
236
|
+
hasPolicies={toolApprovals.length > 0}
|
|
237
|
+
isTriggering={policySession.isTriggering}
|
|
238
|
+
triggerError={policySession.error}
|
|
239
|
+
onGenerate={handleGenerateApprovalPolicies}
|
|
240
|
+
onClearError={policySession.clearError}
|
|
241
|
+
/>
|
|
242
|
+
)}
|
|
243
|
+
|
|
244
|
+
{/* Inline approval policy generation panel */}
|
|
245
|
+
{policyPanelExecutionId && (
|
|
246
|
+
<ApprovalPolicyGeneratorPanel
|
|
247
|
+
executionId={policyPanelExecutionId}
|
|
248
|
+
onClose={() => setPolicyPanelExecutionId(null)}
|
|
249
|
+
onComplete={handlePolicyPanelComplete}
|
|
250
|
+
/>
|
|
251
|
+
)}
|
|
252
|
+
|
|
134
253
|
{spec?.envSpec && Object.keys(spec.envSpec.data).length > 0 && (
|
|
135
254
|
<EnvSpecSection data={spec.envSpec.data} />
|
|
136
255
|
)}
|
|
@@ -450,6 +569,223 @@ function TagsSection({ tags }: { readonly tags: readonly string[] }) {
|
|
|
450
569
|
);
|
|
451
570
|
}
|
|
452
571
|
|
|
572
|
+
// ---------------------------------------------------------------------------
|
|
573
|
+
// Discovery section
|
|
574
|
+
// ---------------------------------------------------------------------------
|
|
575
|
+
|
|
576
|
+
function DiscoverySection({
|
|
577
|
+
isDiscovering,
|
|
578
|
+
discoveryError,
|
|
579
|
+
onDiscover,
|
|
580
|
+
onClearDiscoveryError,
|
|
581
|
+
hasDiscoveredTools,
|
|
582
|
+
showCredentialForm,
|
|
583
|
+
credentialVariables,
|
|
584
|
+
isSavingCredentials,
|
|
585
|
+
onCredentialSubmit,
|
|
586
|
+
onCredentialCancel,
|
|
587
|
+
credentialsLoading,
|
|
588
|
+
}: {
|
|
589
|
+
readonly isDiscovering: boolean;
|
|
590
|
+
readonly discoveryError: Error | null;
|
|
591
|
+
readonly onDiscover: () => void;
|
|
592
|
+
readonly onClearDiscoveryError: () => void;
|
|
593
|
+
readonly hasDiscoveredTools: boolean;
|
|
594
|
+
readonly showCredentialForm: boolean;
|
|
595
|
+
readonly credentialVariables: EnvVarFormVariable[];
|
|
596
|
+
readonly isSavingCredentials: boolean;
|
|
597
|
+
readonly onCredentialSubmit: (
|
|
598
|
+
values: Record<string, import("@stigmer/sdk").EnvVarInput>,
|
|
599
|
+
options: { saveForFuture: boolean },
|
|
600
|
+
) => void;
|
|
601
|
+
readonly onCredentialCancel: () => void;
|
|
602
|
+
readonly credentialsLoading: boolean;
|
|
603
|
+
}) {
|
|
604
|
+
return (
|
|
605
|
+
<section className="flex flex-col gap-3">
|
|
606
|
+
<div className="flex items-center justify-between">
|
|
607
|
+
<h3 className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
|
608
|
+
Discovery
|
|
609
|
+
</h3>
|
|
610
|
+
<button
|
|
611
|
+
type="button"
|
|
612
|
+
onClick={onDiscover}
|
|
613
|
+
disabled={isDiscovering || credentialsLoading}
|
|
614
|
+
className={cn(
|
|
615
|
+
"inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium",
|
|
616
|
+
"border border-border bg-background text-foreground",
|
|
617
|
+
"hover:bg-accent hover:text-accent-foreground",
|
|
618
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
619
|
+
)}
|
|
620
|
+
>
|
|
621
|
+
{isDiscovering ? (
|
|
622
|
+
<>
|
|
623
|
+
<DiscoverSpinner />
|
|
624
|
+
Discovering...
|
|
625
|
+
</>
|
|
626
|
+
) : hasDiscoveredTools ? (
|
|
627
|
+
<>
|
|
628
|
+
<RefreshIcon className="size-3.5" />
|
|
629
|
+
Re-discover
|
|
630
|
+
</>
|
|
631
|
+
) : (
|
|
632
|
+
<>
|
|
633
|
+
<DiscoverIcon className="size-3.5" />
|
|
634
|
+
Discover Tools
|
|
635
|
+
</>
|
|
636
|
+
)}
|
|
637
|
+
</button>
|
|
638
|
+
</div>
|
|
639
|
+
|
|
640
|
+
{discoveryError && (
|
|
641
|
+
<div className="flex items-start gap-2 rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2">
|
|
642
|
+
<WarningIcon className="mt-0.5 size-3.5 shrink-0 text-destructive" />
|
|
643
|
+
<p className="flex-1 text-xs text-destructive">
|
|
644
|
+
{discoveryError.message}
|
|
645
|
+
</p>
|
|
646
|
+
<button
|
|
647
|
+
type="button"
|
|
648
|
+
onClick={onClearDiscoveryError}
|
|
649
|
+
className="shrink-0 text-xs text-destructive/70 hover:text-destructive"
|
|
650
|
+
aria-label="Dismiss error"
|
|
651
|
+
>
|
|
652
|
+
Dismiss
|
|
653
|
+
</button>
|
|
654
|
+
</div>
|
|
655
|
+
)}
|
|
656
|
+
|
|
657
|
+
{showCredentialForm && credentialVariables.length > 0 && (
|
|
658
|
+
<div className="rounded-lg border border-border p-4">
|
|
659
|
+
<EnvVarForm
|
|
660
|
+
title="Credentials Required"
|
|
661
|
+
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."
|
|
662
|
+
variables={credentialVariables}
|
|
663
|
+
onSubmit={(values, options) => onCredentialSubmit(values, options)}
|
|
664
|
+
onCancel={onCredentialCancel}
|
|
665
|
+
isSubmitting={isSavingCredentials}
|
|
666
|
+
className="w-full max-w-md"
|
|
667
|
+
/>
|
|
668
|
+
</div>
|
|
669
|
+
)}
|
|
670
|
+
|
|
671
|
+
{!hasDiscoveredTools && !isDiscovering && (
|
|
672
|
+
<p className="text-xs text-muted-foreground">
|
|
673
|
+
No tools discovered yet. Click "Discover Tools" to
|
|
674
|
+
connect to this MCP server and list its available tools and
|
|
675
|
+
resources.
|
|
676
|
+
</p>
|
|
677
|
+
)}
|
|
678
|
+
</section>
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// ---------------------------------------------------------------------------
|
|
683
|
+
// Approval policies section
|
|
684
|
+
// ---------------------------------------------------------------------------
|
|
685
|
+
|
|
686
|
+
function ApprovalPoliciesSection({
|
|
687
|
+
policies,
|
|
688
|
+
}: {
|
|
689
|
+
readonly policies: readonly ToolApprovalPolicy[];
|
|
690
|
+
}) {
|
|
691
|
+
return (
|
|
692
|
+
<Section title={`Approval Policies (${policies.length})`}>
|
|
693
|
+
<div className="flex flex-col divide-y divide-border">
|
|
694
|
+
{policies.map((policy) => (
|
|
695
|
+
<div key={policy.toolName} className="px-3 py-2.5">
|
|
696
|
+
<div className="flex items-baseline gap-2">
|
|
697
|
+
<code className="font-mono text-sm font-medium text-foreground">
|
|
698
|
+
{policy.toolName}
|
|
699
|
+
</code>
|
|
700
|
+
<ShieldIcon className="size-3 text-amber-500 dark:text-amber-400" />
|
|
701
|
+
</div>
|
|
702
|
+
{policy.message && (
|
|
703
|
+
<p className="mt-0.5 text-xs text-muted-foreground">
|
|
704
|
+
{policy.message}
|
|
705
|
+
</p>
|
|
706
|
+
)}
|
|
707
|
+
</div>
|
|
708
|
+
))}
|
|
709
|
+
</div>
|
|
710
|
+
</Section>
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// ---------------------------------------------------------------------------
|
|
715
|
+
// Generate approval policies section
|
|
716
|
+
// ---------------------------------------------------------------------------
|
|
717
|
+
|
|
718
|
+
function GenerateApprovalPoliciesSection({
|
|
719
|
+
hasPolicies,
|
|
720
|
+
isTriggering,
|
|
721
|
+
triggerError,
|
|
722
|
+
onGenerate,
|
|
723
|
+
onClearError,
|
|
724
|
+
}: {
|
|
725
|
+
readonly hasPolicies: boolean;
|
|
726
|
+
readonly isTriggering: boolean;
|
|
727
|
+
readonly triggerError: Error | null;
|
|
728
|
+
readonly onGenerate: () => void;
|
|
729
|
+
readonly onClearError: () => void;
|
|
730
|
+
}) {
|
|
731
|
+
return (
|
|
732
|
+
<section className="flex flex-col gap-2">
|
|
733
|
+
<div className="flex items-center justify-between">
|
|
734
|
+
<div>
|
|
735
|
+
<h3 className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
|
736
|
+
AI-Generated Policies
|
|
737
|
+
</h3>
|
|
738
|
+
<p className="mt-0.5 text-xs text-muted-foreground">
|
|
739
|
+
{hasPolicies
|
|
740
|
+
? "Regenerate approval policies using AI to classify each tool's risk level."
|
|
741
|
+
: "Automatically generate approval policies for the discovered tools using AI."}
|
|
742
|
+
</p>
|
|
743
|
+
</div>
|
|
744
|
+
<button
|
|
745
|
+
type="button"
|
|
746
|
+
onClick={onGenerate}
|
|
747
|
+
disabled={isTriggering}
|
|
748
|
+
className={cn(
|
|
749
|
+
"inline-flex shrink-0 items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium",
|
|
750
|
+
"bg-primary text-primary-foreground",
|
|
751
|
+
"hover:bg-primary/90",
|
|
752
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
753
|
+
)}
|
|
754
|
+
>
|
|
755
|
+
{isTriggering ? (
|
|
756
|
+
<>
|
|
757
|
+
<DiscoverSpinner />
|
|
758
|
+
Starting...
|
|
759
|
+
</>
|
|
760
|
+
) : (
|
|
761
|
+
<>
|
|
762
|
+
<SparklesIcon className="size-3.5" />
|
|
763
|
+
{hasPolicies ? "Regenerate Policies" : "Generate Policies"}
|
|
764
|
+
</>
|
|
765
|
+
)}
|
|
766
|
+
</button>
|
|
767
|
+
</div>
|
|
768
|
+
|
|
769
|
+
{triggerError && (
|
|
770
|
+
<div className="flex items-start gap-2 rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2">
|
|
771
|
+
<WarningIcon className="mt-0.5 size-3.5 shrink-0 text-destructive" />
|
|
772
|
+
<p className="flex-1 text-xs text-destructive">
|
|
773
|
+
{triggerError.message}
|
|
774
|
+
</p>
|
|
775
|
+
<button
|
|
776
|
+
type="button"
|
|
777
|
+
onClick={onClearError}
|
|
778
|
+
className="shrink-0 text-xs text-destructive/70 hover:text-destructive"
|
|
779
|
+
aria-label="Dismiss error"
|
|
780
|
+
>
|
|
781
|
+
Dismiss
|
|
782
|
+
</button>
|
|
783
|
+
</div>
|
|
784
|
+
)}
|
|
785
|
+
</section>
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
|
|
453
789
|
// ---------------------------------------------------------------------------
|
|
454
790
|
// Shared layout primitives
|
|
455
791
|
// ---------------------------------------------------------------------------
|
|
@@ -604,3 +940,93 @@ function WarningIcon({ className }: { readonly className?: string }) {
|
|
|
604
940
|
</svg>
|
|
605
941
|
);
|
|
606
942
|
}
|
|
943
|
+
|
|
944
|
+
function DiscoverIcon({ className }: { readonly className?: string }) {
|
|
945
|
+
return (
|
|
946
|
+
<svg
|
|
947
|
+
className={className}
|
|
948
|
+
viewBox="0 0 16 16"
|
|
949
|
+
fill="none"
|
|
950
|
+
stroke="currentColor"
|
|
951
|
+
strokeWidth="1.5"
|
|
952
|
+
strokeLinecap="round"
|
|
953
|
+
strokeLinejoin="round"
|
|
954
|
+
aria-hidden="true"
|
|
955
|
+
>
|
|
956
|
+
<circle cx="7" cy="7" r="4.5" />
|
|
957
|
+
<path d="m10.5 10.5 3 3" />
|
|
958
|
+
</svg>
|
|
959
|
+
);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
function RefreshIcon({ className }: { readonly className?: string }) {
|
|
963
|
+
return (
|
|
964
|
+
<svg
|
|
965
|
+
className={className}
|
|
966
|
+
viewBox="0 0 16 16"
|
|
967
|
+
fill="none"
|
|
968
|
+
stroke="currentColor"
|
|
969
|
+
strokeWidth="1.5"
|
|
970
|
+
strokeLinecap="round"
|
|
971
|
+
strokeLinejoin="round"
|
|
972
|
+
aria-hidden="true"
|
|
973
|
+
>
|
|
974
|
+
<path d="M2.5 8a5.5 5.5 0 0 1 9.36-3.92" />
|
|
975
|
+
<path d="M13.5 8a5.5 5.5 0 0 1-9.36 3.92" />
|
|
976
|
+
<path d="M12 2v3h-3" />
|
|
977
|
+
<path d="M4 14v-3h3" />
|
|
978
|
+
</svg>
|
|
979
|
+
);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
function ShieldIcon({ className }: { readonly className?: string }) {
|
|
983
|
+
return (
|
|
984
|
+
<svg
|
|
985
|
+
className={className}
|
|
986
|
+
viewBox="0 0 16 16"
|
|
987
|
+
fill="none"
|
|
988
|
+
stroke="currentColor"
|
|
989
|
+
strokeWidth="1.5"
|
|
990
|
+
strokeLinecap="round"
|
|
991
|
+
strokeLinejoin="round"
|
|
992
|
+
aria-hidden="true"
|
|
993
|
+
>
|
|
994
|
+
<path d="M8 1.5 2.5 4v4c0 3.31 2.35 6.4 5.5 7 3.15-.6 5.5-3.69 5.5-7V4L8 1.5Z" />
|
|
995
|
+
</svg>
|
|
996
|
+
);
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
function SparklesIcon({ className }: { readonly className?: string }) {
|
|
1000
|
+
return (
|
|
1001
|
+
<svg
|
|
1002
|
+
className={className}
|
|
1003
|
+
viewBox="0 0 16 16"
|
|
1004
|
+
fill="none"
|
|
1005
|
+
stroke="currentColor"
|
|
1006
|
+
strokeWidth="1.5"
|
|
1007
|
+
strokeLinecap="round"
|
|
1008
|
+
strokeLinejoin="round"
|
|
1009
|
+
aria-hidden="true"
|
|
1010
|
+
>
|
|
1011
|
+
<path d="M8 1v2M8 13v2M1 8h2M13 8h2M3.05 3.05l1.41 1.41M11.54 11.54l1.41 1.41M3.05 12.95l1.41-1.41M11.54 4.46l1.41-1.41" />
|
|
1012
|
+
</svg>
|
|
1013
|
+
);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
function DiscoverSpinner() {
|
|
1017
|
+
return (
|
|
1018
|
+
<svg
|
|
1019
|
+
width="14"
|
|
1020
|
+
height="14"
|
|
1021
|
+
viewBox="0 0 16 16"
|
|
1022
|
+
fill="none"
|
|
1023
|
+
stroke="currentColor"
|
|
1024
|
+
strokeWidth="2"
|
|
1025
|
+
strokeLinecap="round"
|
|
1026
|
+
className="animate-spin"
|
|
1027
|
+
aria-hidden="true"
|
|
1028
|
+
>
|
|
1029
|
+
<path d="M8 2a6 6 0 1 0 6 6" />
|
|
1030
|
+
</svg>
|
|
1031
|
+
);
|
|
1032
|
+
}
|
package/src/mcp-server/index.ts
CHANGED
|
@@ -45,3 +45,18 @@ export type {
|
|
|
45
45
|
|
|
46
46
|
export { McpServerDetailView } from "./McpServerDetailView";
|
|
47
47
|
export type { McpServerDetailViewProps } from "./McpServerDetailView";
|
|
48
|
+
|
|
49
|
+
export { useDiscoverCapabilities } from "./useDiscoverCapabilities";
|
|
50
|
+
export type { UseDiscoverCapabilitiesReturn } from "./useDiscoverCapabilities";
|
|
51
|
+
|
|
52
|
+
export { useMcpServerCredentials } from "./useMcpServerCredentials";
|
|
53
|
+
export type { UseMcpServerCredentialsReturn } from "./useMcpServerCredentials";
|
|
54
|
+
|
|
55
|
+
export { useTriggerApprovalPolicySession } from "./useTriggerApprovalPolicySession";
|
|
56
|
+
export type {
|
|
57
|
+
UseTriggerApprovalPolicySessionReturn,
|
|
58
|
+
TriggerApprovalPolicyResult,
|
|
59
|
+
} from "./useTriggerApprovalPolicySession";
|
|
60
|
+
|
|
61
|
+
export { ApprovalPolicyGeneratorPanel } from "./ApprovalPolicyGeneratorPanel";
|
|
62
|
+
export type { ApprovalPolicyGeneratorPanelProps } from "./ApprovalPolicyGeneratorPanel";
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useState } from "react";
|
|
4
|
+
import { create } from "@bufbuild/protobuf";
|
|
5
|
+
import type { McpServer } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
|
|
6
|
+
import { DiscoverCapabilitiesInputSchema } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/io_pb";
|
|
7
|
+
import type { EnvVarInput } from "@stigmer/sdk";
|
|
8
|
+
import { useStigmer } from "../hooks";
|
|
9
|
+
import { toError } from "../internal/toError";
|
|
10
|
+
|
|
11
|
+
export interface UseDiscoverCapabilitiesReturn {
|
|
12
|
+
/**
|
|
13
|
+
* Trigger server-side MCP discovery for the given MCP server.
|
|
14
|
+
*
|
|
15
|
+
* When `runtimeEnv` is provided, the values are passed directly to
|
|
16
|
+
* the backend as one-time credentials (not persisted to any
|
|
17
|
+
* environment). When omitted, the backend resolves credentials from
|
|
18
|
+
* the user's personal environment.
|
|
19
|
+
*
|
|
20
|
+
* Blocks until discovery completes (~30s timeout).
|
|
21
|
+
*
|
|
22
|
+
* @param mcpServerId - System-generated ID of the MCP server (metadata.id).
|
|
23
|
+
* @param runtimeEnv - Optional one-time environment variables for discovery.
|
|
24
|
+
* @returns The updated McpServer with populated discovered_capabilities.
|
|
25
|
+
*/
|
|
26
|
+
readonly discover: (
|
|
27
|
+
mcpServerId: string,
|
|
28
|
+
runtimeEnv?: Record<string, EnvVarInput>,
|
|
29
|
+
) => Promise<McpServer>;
|
|
30
|
+
/** `true` while a discovery RPC is in flight. */
|
|
31
|
+
readonly isDiscovering: boolean;
|
|
32
|
+
/** Error from the most recent failed discovery, or `null`. */
|
|
33
|
+
readonly error: Error | null;
|
|
34
|
+
/** Clear the error state. */
|
|
35
|
+
readonly clearError: () => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Action hook for triggering server-side MCP server capability discovery.
|
|
40
|
+
*
|
|
41
|
+
* Calls the `discoverCapabilities` RPC which delegates to the agent-runner
|
|
42
|
+
* via a Temporal workflow. The RPC blocks until discovery completes
|
|
43
|
+
* (typically 5-15 seconds, ~30s timeout).
|
|
44
|
+
*
|
|
45
|
+
* Supports two modes:
|
|
46
|
+
* - **Save for future** (default): Credentials are saved to the personal
|
|
47
|
+
* environment first, then `discover(id)` is called without `runtimeEnv`.
|
|
48
|
+
* - **One-time use**: `discover(id, runtimeEnv)` passes credentials
|
|
49
|
+
* directly to the backend. They are used for this discovery only and
|
|
50
|
+
* not persisted to any environment.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```tsx
|
|
54
|
+
* const { discover, isDiscovering, error } = useDiscoverCapabilities();
|
|
55
|
+
* const { refetch } = useMcpServer(org, slug);
|
|
56
|
+
*
|
|
57
|
+
* // Save for future: credentials already in personal environment
|
|
58
|
+
* async function handleDiscoverSaved() {
|
|
59
|
+
* await discover(mcpServer.metadata.id);
|
|
60
|
+
* refetch();
|
|
61
|
+
* }
|
|
62
|
+
*
|
|
63
|
+
* // One-time use: pass credentials directly
|
|
64
|
+
* async function handleDiscoverTemporary(values: Record<string, EnvVarInput>) {
|
|
65
|
+
* await discover(mcpServer.metadata.id, values);
|
|
66
|
+
* refetch();
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export function useDiscoverCapabilities(): UseDiscoverCapabilitiesReturn {
|
|
71
|
+
const stigmer = useStigmer();
|
|
72
|
+
const [isDiscovering, setIsDiscovering] = useState(false);
|
|
73
|
+
const [error, setError] = useState<Error | null>(null);
|
|
74
|
+
|
|
75
|
+
const clearError = useCallback(() => setError(null), []);
|
|
76
|
+
|
|
77
|
+
const discover = useCallback(
|
|
78
|
+
async (
|
|
79
|
+
mcpServerId: string,
|
|
80
|
+
runtimeEnv?: Record<string, EnvVarInput>,
|
|
81
|
+
): Promise<McpServer> => {
|
|
82
|
+
setIsDiscovering(true);
|
|
83
|
+
setError(null);
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
const runtimeEnvMap: Record<string, { value: string; isSecret: boolean }> = {};
|
|
87
|
+
if (runtimeEnv) {
|
|
88
|
+
for (const [key, input] of Object.entries(runtimeEnv)) {
|
|
89
|
+
runtimeEnvMap[key] = {
|
|
90
|
+
value: input.value,
|
|
91
|
+
isSecret: input.isSecret ?? false,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const input = create(DiscoverCapabilitiesInputSchema, {
|
|
97
|
+
mcpServerId,
|
|
98
|
+
...(Object.keys(runtimeEnvMap).length > 0 && {
|
|
99
|
+
runtimeEnv: runtimeEnvMap,
|
|
100
|
+
}),
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const result = await stigmer.mcpServer.discoverCapabilities(input);
|
|
104
|
+
return result;
|
|
105
|
+
} catch (err) {
|
|
106
|
+
const wrapped = toError(err);
|
|
107
|
+
setError(wrapped);
|
|
108
|
+
throw wrapped;
|
|
109
|
+
} finally {
|
|
110
|
+
setIsDiscovering(false);
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
[stigmer],
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
return { discover, isDiscovering, error, clearError };
|
|
117
|
+
}
|