Skip to content

Commit f5c9fe1

Browse files
authored
Merge pull request #204 from kagent-dev/peterj/brokenmcp
fix the agent creation with mcp tools
2 parents 9f746a4 + 5b9d8c0 commit f5c9fe1

File tree

9 files changed

+245
-179
lines changed

9 files changed

+245
-179
lines changed

ui/src/app/actions/teams.ts

+18-6
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,21 @@ export async function deleteTeam(teamLabel: string) {
3333
}
3434
}
3535

36+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
37+
function processConfigObject(config: { [key: string]: any }): { [key: string]: any } {
38+
return Object.entries(config).reduce((acc, [key, value]) => {
39+
// If value is an object and not null, process it recursively
40+
if (typeof value === "object" && value !== null) {
41+
acc[key] = processConfigObject(value);
42+
} else {
43+
// For primitive values, convert to string
44+
acc[key] = String(value);
45+
}
46+
return acc;
47+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
48+
}, {} as { [key: string]: any });
49+
}
50+
3651
function fromAgentFormDataToAgent(agentFormData: AgentFormData): Agent {
3752
return {
3853
metadata: {
@@ -45,12 +60,7 @@ function fromAgentFormDataToAgent(agentFormData: AgentFormData): Agent {
4560
tools: agentFormData.tools.map((tool) => ({
4661
provider: tool.provider,
4762
description: tool.description ?? "No description provided",
48-
config: tool.config
49-
? Object.entries(tool.config).reduce((acc, [key, value]) => {
50-
acc[key] = String(value);
51-
return acc;
52-
}, {} as { [key: string]: string })
53-
: {},
63+
config: tool.config ? processConfigObject(tool.config) : {},
5464
})),
5565
},
5666
};
@@ -61,6 +71,8 @@ export async function createAgent(agentConfig: AgentFormData, update: boolean =
6171

6272
try {
6373
agentSpec = fromAgentFormDataToAgent(agentConfig);
74+
75+
console.log("Converted agent data:", agentSpec);
6476
} catch (ex) {
6577
console.error("Error converting agent data:", ex);
6678
return { success: false, error: "Failed to convert agent data. Please try again." };

ui/src/app/agents/new/page.tsx

+7-19
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import type { AgentTool } from "@/types/datamodel";
1616
import { LoadingState } from "@/components/LoadingState";
1717
import { ErrorState } from "@/components/ErrorState";
1818
import KagentLogo from "@/components/kagent-logo";
19+
import { extractSocietyOfMindAgentTools } from "@/lib/toolUtils";
1920

2021
interface ValidationErrors {
2122
name?: string;
@@ -79,9 +80,7 @@ function AgentPageContent() {
7980
setName(agent.metadata.name || "");
8081
setDescription(agent.spec.description || "");
8182
setSystemPrompt(agent.spec.systemMessage || "");
82-
83-
// Extract and set tools - these are already AgentTool objects
84-
setSelectedTools(agent.spec.tools || []);
83+
setSelectedTools(extractSocietyOfMindAgentTools(agentResponse) || []);
8584

8685
setSelectedModel({
8786
model: agentResponse.model,
@@ -128,6 +127,9 @@ function AgentPageContent() {
128127
try {
129128
setIsSubmitting(true);
130129
setGeneralError("");
130+
if (!selectedModel) {
131+
throw new Error("Model is required to create the agent.");
132+
}
131133

132134
const agentData = {
133135
name,
@@ -141,24 +143,10 @@ function AgentPageContent() {
141143

142144
if (isEditMode && agentId) {
143145
// Update existing agent
144-
if (!selectedModel) {
145-
throw new Error("Model is required to update the agent.");
146-
}
147-
result = await updateAgent(agentId, {
148-
...agentData,
149-
model: selectedModel,
150-
tools: selectedTools,
151-
});
146+
result = await updateAgent(agentId, agentData);
152147
} else {
153148
// Create new agent
154-
if (!selectedModel) {
155-
throw new Error("Model is required to create the agent.");
156-
}
157-
result = await createNewAgent({
158-
...agentData,
159-
model: selectedModel,
160-
tools: selectedTools,
161-
});
149+
result = await createNewAgent(agentData);
162150
}
163151

164152
if (!result.success) {

ui/src/components/create/SelectToolsDialog.tsx

+30-39
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { AgentTool, Component, ToolConfig } from "@/types/datamodel";
1010
import ProviderFilter from "./ProviderFilter";
1111
import ToolItem from "./ToolItem";
1212
import { findComponentForAgentTool } from "@/lib/toolUtils";
13+
import { getToolDisplayName, getToolDescription, getToolIdentifier } from "@/lib/data";
1314

1415
// Maximum number of tools that can be selected
1516
const MAX_TOOLS_LIMIT = 10;
@@ -37,7 +38,6 @@ export const SelectToolsDialog: React.FC<SelectToolsDialogProps> = ({ open, onOp
3738
const [searchTerm, setSearchTerm] = useState("");
3839
const [activeTab, setActiveTab] = useState("all");
3940
const [localSelectedComponents, setLocalSelectedComponents] = useState<Component<ToolConfig>[]>([]);
40-
const [newlyDiscoveredTools, setNewlyDiscoveredTools] = useState<Component<ToolConfig>[]>([]);
4141
const [providers, setProviders] = useState<Set<string>>(new Set());
4242
const [selectedProviders, setSelectedProviders] = useState<Set<string>>(new Set());
4343
const [showFilters, setShowFilters] = useState(false);
@@ -51,7 +51,6 @@ export const SelectToolsDialog: React.FC<SelectToolsDialogProps> = ({ open, onOp
5151
.map((agentTool) => findComponentForAgentTool(agentTool, availableTools))
5252
.filter((tool): tool is Component<ToolConfig> => tool !== undefined);
5353

54-
setNewlyDiscoveredTools([]);
5554
setLocalSelectedComponents(initialComponents);
5655
setSearchTerm("");
5756

@@ -82,37 +81,29 @@ export const SelectToolsDialog: React.FC<SelectToolsDialogProps> = ({ open, onOp
8281
const searchLower = searchTerm.toLowerCase();
8382

8483
return availableTools.filter((tool) => {
85-
// Search matching
86-
const matchesSearch = tool.provider.toLowerCase().includes(searchLower) || (tool.description?.toLowerCase().includes(searchLower) ?? false);
84+
// Search matching - use getToolDisplayName and getToolDescription
85+
const toolName = getToolDisplayName(tool).toLowerCase();
86+
const toolDescription = getToolDescription(tool)?.toLowerCase() ?? "";
87+
const matchesSearch = toolName.includes(searchLower) || toolDescription.includes(searchLower) || tool.provider.toLowerCase().includes(searchLower);
8788

8889
// Tab matching
89-
const isSelected = localSelectedComponents.some((t) => t.provider === tool.provider);
90-
const isNew = newlyDiscoveredTools.some((t) => t.provider === tool.provider);
91-
92-
const matchesTab = activeTab === "all" || (activeTab === "selected" && isSelected) || (activeTab === "new" && isNew);
90+
const isSelected = localSelectedComponents.some((t) => getToolIdentifier(t) === getToolIdentifier(tool));
91+
const matchesTab = activeTab === "all" || (activeTab === "selected" && isSelected);
9392

9493
// Provider matching
9594
const matchesProvider = selectedProviders.size === 0 || selectedProviders.has(tool.provider);
9695

9796
return matchesSearch && matchesTab && matchesProvider;
9897
});
99-
}, [availableTools, searchTerm, activeTab, localSelectedComponents, newlyDiscoveredTools, selectedProviders]);
98+
}, [availableTools, searchTerm, activeTab, localSelectedComponents, selectedProviders]);
10099

101100
// Group tools by category
102101
const groupedTools = useMemo(() => {
103102
const groups: { [key: string]: Component<ToolConfig>[] } = {};
104103

105104
// Sort tools first - new tools at the top within each category
106105
const sortedTools = [...filteredTools].sort((a, b) => {
107-
const aIsNew = newlyDiscoveredTools.some((t) => t.provider === a.provider);
108-
const bIsNew = newlyDiscoveredTools.some((t) => t.provider === b.provider);
109-
110-
// Primary sort: new tools first
111-
if (aIsNew && !bIsNew) return -1;
112-
if (!aIsNew && bIsNew) return 1;
113-
114-
// Secondary sort: alphabetical by name
115-
return (a.provider || "").localeCompare(b.provider || "");
106+
return getToolDisplayName(a).localeCompare(getToolDisplayName(b));
116107
});
117108

118109
// Group by categories
@@ -125,13 +116,13 @@ export const SelectToolsDialog: React.FC<SelectToolsDialogProps> = ({ open, onOp
125116
});
126117

127118
return groups;
128-
}, [filteredTools, newlyDiscoveredTools]);
119+
}, [filteredTools]);
129120

130121
// Check if selection limit is reached
131122
const isLimitReached = localSelectedComponents.length >= MAX_TOOLS_LIMIT;
132123

133124
// Helper functions for tool state
134-
const isToolSelected = (tool: Component<ToolConfig>) => localSelectedComponents.some((t) => t.provider === tool.provider);
125+
const isToolSelected = (tool: Component<ToolConfig>) => localSelectedComponents.some((t) => getToolIdentifier(t) === getToolIdentifier(tool));
135126

136127
// Event handlers
137128
const handleToggleTool = (tool: Component<ToolConfig>) => {
@@ -142,7 +133,13 @@ export const SelectToolsDialog: React.FC<SelectToolsDialogProps> = ({ open, onOp
142133
return;
143134
}
144135

145-
setLocalSelectedComponents((prev) => (isCurrentlySelected ? prev.filter((t) => t.provider !== tool.provider) : [...prev, tool]));
136+
setLocalSelectedComponents((prev) => {
137+
if (isCurrentlySelected) {
138+
return prev.filter((t) => getToolIdentifier(t) !== getToolIdentifier(tool));
139+
} else {
140+
return [...prev, tool];
141+
}
142+
});
146143
};
147144

148145
const handleSave = () => {
@@ -188,22 +185,21 @@ export const SelectToolsDialog: React.FC<SelectToolsDialogProps> = ({ open, onOp
188185
// Stats
189186
const totalTools = availableTools.length;
190187
const selectedCount = localSelectedComponents.length;
191-
const newToolsCount = newlyDiscoveredTools.length;
192188

193189
return (
194190
<Dialog
195191
open={open}
196192
onOpenChange={(isOpen) => {
197193
// Auto-save if closing with newly discovered tools
198-
if (!isOpen && newToolsCount > 0) {
194+
if (!isOpen) {
199195
onToolsSelected(localSelectedComponents);
200196
}
201197
onOpenChange(isOpen);
202198
}}
203199
>
204200
<DialogContent className="max-w-4xl max-h-[85vh] h-auto">
205201
<DialogHeader>
206-
<DialogTitle className="text-xl">{newToolsCount > 0 ? "Select Discovered Tools" : "Select Tools"}</DialogTitle>
202+
<DialogTitle className="text-xl">Select Tools</DialogTitle>
207203
</DialogHeader>
208204

209205
{/* Tool limit warning */}
@@ -250,14 +246,6 @@ export const SelectToolsDialog: React.FC<SelectToolsDialogProps> = ({ open, onOp
250246
{selectedCount}
251247
</Badge>
252248
</TabsTrigger>
253-
{newToolsCount > 0 && (
254-
<TabsTrigger value="new">
255-
New
256-
<Badge variant="outline" className="ml-1 bg-background">
257-
{newToolsCount}
258-
</Badge>
259-
</TabsTrigger>
260-
)}
261249
</TabsList>
262250
</Tabs>
263251

@@ -293,7 +281,15 @@ export const SelectToolsDialog: React.FC<SelectToolsDialogProps> = ({ open, onOp
293281
{expandedCategories[category] && (
294282
<div className="divide-y">
295283
{tools.map((tool) => (
296-
<ToolItem key={tool.provider} tool={tool} isSelected={isToolSelected(tool)} onToggle={handleToggleTool} disabled={!isToolSelected(tool) && isLimitReached} />
284+
<ToolItem
285+
key={getToolIdentifier(tool)}
286+
tool={tool}
287+
isSelected={isToolSelected(tool)}
288+
onToggle={handleToggleTool}
289+
disabled={!isToolSelected(tool) && isLimitReached}
290+
displayName={getToolDisplayName(tool)}
291+
description={getToolDescription(tool)}
292+
/>
297293
))}
298294
</div>
299295
)}
@@ -319,13 +315,8 @@ export const SelectToolsDialog: React.FC<SelectToolsDialogProps> = ({ open, onOp
319315
<span className="text-muted-foreground">(Maximum: {MAX_TOOLS_LIMIT})</span>
320316
</div>
321317
<div className="flex gap-2">
322-
{newToolsCount === 0 && (
323-
<Button variant="outline" onClick={() => onOpenChange(false)}>
324-
Cancel
325-
</Button>
326-
)}
327318
<Button className="bg-violet-500 hover:bg-violet-600 text-white" onClick={handleSave}>
328-
{newToolsCount > 0 ? "Add Selected Tools" : "Save Selection"}
319+
Save Selection
329320
</Button>
330321
</div>
331322
</div>

ui/src/components/create/ToolItem.tsx

+25-58
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,46 @@
1-
import { isMcpTool } from "@/lib/data";
2-
import { Check } from "lucide-react";
1+
import React from "react";
2+
import { CheckCircle } from "lucide-react";
33
import { Badge } from "@/components/ui/badge";
4-
import { Button } from "@/components/ui/button";
5-
import { Component, ToolConfig } from "@/types/datamodel";
6-
4+
import { Component, ToolConfig } from "@/types/datamodel";
75

86
interface ToolItemProps {
97
tool: Component<ToolConfig>;
108
isSelected: boolean;
119
onToggle: (tool: Component<ToolConfig>) => void;
1210
disabled?: boolean;
11+
displayName?: string;
12+
description?: string;
1313
}
1414

15-
const ToolItem = ({ tool, isSelected, onToggle, disabled = false }: ToolItemProps) => {
16-
const displayName = tool.provider; // getToolDisplayName(tool) || "Unnamed Tool";
17-
const displayDescription = tool.description; // getToolDescription(tool) || "No description available";
18-
const toolId = tool.provider; // getToolIdentifier(tool);
19-
20-
// Determine classes based on selection and disabled states
21-
const containerClasses = `p-4 rounded-lg border transition-all ${
22-
isSelected
23-
? "bg-primary/10 border-primary/30"
24-
: disabled
25-
? "border-border bg-gray-50 opacity-60"
26-
: "border-border hover:bg-secondary/50"
27-
} ${disabled && !isSelected ? "cursor-not-allowed" : "cursor-pointer"}`;
28-
29-
const handleClick = () => {
30-
if (!disabled || isSelected) {
31-
onToggle(tool);
32-
}
33-
};
34-
15+
const ToolItem: React.FC<ToolItemProps> = ({ tool, isSelected, onToggle, disabled = false, displayName, description }) => {
16+
const toolName = displayName || tool.label;
17+
const toolDescription = description || tool.description;
18+
3519
return (
3620
<div
37-
className={containerClasses}
38-
onClick={handleClick}
21+
className={`p-3 hover:bg-secondary/20 transition-colors cursor-pointer ${disabled && !isSelected ? "opacity-50 cursor-not-allowed" : ""}`}
22+
onClick={() => !disabled && onToggle(tool)}
3923
>
40-
<div className="flex items-start justify-between gap-4">
24+
<div className="flex items-start justify-between">
4125
<div className="flex-1">
42-
<div className="font-medium flex items-center flex-wrap gap-2">
43-
<span className="mr-1">{displayName}</span>
44-
<div className="flex flex-wrap gap-1">
45-
{isMcpTool(tool) ? (
46-
<Badge variant="outline" className="bg-blue-500/10 text-blue-500 border-blue-500/20 hover:bg-blue-500/20">
47-
MCP
48-
</Badge>
49-
) : (
50-
<Badge variant="outline" className="bg-gray-100 text-gray-500 border-gray-200">
51-
{tool.provider}
52-
</Badge>
53-
)}
54-
</div>
26+
<div className="flex items-center">
27+
<h4 className="font-medium">{toolName}</h4>
28+
{tool.provider.startsWith("autogen_ext.tools.mcp") && (
29+
<Badge variant="outline" className="ml-2 bg-purple-50 text-purple-700 border-purple-200">
30+
MCP
31+
</Badge>
32+
)}
5533
</div>
56-
<div className="text-muted-foreground mt-2">{displayDescription}</div>
57-
<div className="text-xs text-muted-foreground mt-1 opacity-70">{toolId}</div>
34+
<p className="text-sm text-muted-foreground mt-1">{toolDescription}</p>
35+
<div className="text-xs text-muted-foreground mt-2">{tool.provider}</div>
5836
</div>
59-
<Button
60-
variant={isSelected ? "secondary" : "outline"}
61-
size="sm"
62-
className={`mt-0 ${isSelected ? "bg-primary text-primary-foreground hover:bg-primary/90" : ""}`}
63-
disabled={disabled && !isSelected}
64-
onClick={(e) => {
65-
e.stopPropagation();
66-
handleClick();
67-
}}
68-
>
37+
<div className="ml-4 flex-shrink-0">
6938
{isSelected ? (
70-
<>
71-
<Check className="w-4 h-4 mr-1" /> Selected
72-
</>
39+
<CheckCircle className="h-5 w-5 text-primary" />
7340
) : (
74-
"Select"
41+
<div className="h-5 w-5 rounded-full border-2 border-muted"></div>
7542
)}
76-
</Button>
43+
</div>
7744
</div>
7845
</div>
7946
);

0 commit comments

Comments
 (0)