@@ -40,6 +40,13 @@ type CreateAgentRequest struct {
4040 MCPServers map [string ]MCPServerConfig `json:"mcpServers,omitempty"` // Optional
4141}
4242
43+ // UpdateAgentRequest defines the structure of the request body for updating an agent
44+ type UpdateAgentRequest struct {
45+ LLM string `json:"llm"` // Required
46+ SystemPrompt string `json:"systemPrompt"` // Required
47+ MCPServers map [string ]MCPServerConfig `json:"mcpServers,omitempty"` // Optional
48+ }
49+
4350// MCPServerConfig defines the configuration for an MCP server
4451type MCPServerConfig struct {
4552 Transport string `json:"transport"` // Required: "stdio" or "http"
@@ -103,6 +110,7 @@ func (s *APIServer) registerRoutes() {
103110 agents .GET ("" , s .listAgents )
104111 agents .GET ("/:name" , s .getAgent )
105112 agents .POST ("" , s .createAgent )
113+ agents .PUT ("/:name" , s .updateAgent )
106114}
107115
108116// processMCPServers creates MCP servers and their secrets based on the given configuration
@@ -618,6 +626,218 @@ func defaultIfEmpty(val, defaultVal string) string {
618626 return val
619627}
620628
629+ // updateAgent handles updating an existing agent and its associated MCP servers
630+ func (s * APIServer ) updateAgent (c * gin.Context ) {
631+ ctx := c .Request .Context ()
632+ logger := log .FromContext (ctx )
633+
634+ // Get namespace and name
635+ namespace := c .Query ("namespace" )
636+ if namespace == "" {
637+ c .JSON (http .StatusBadRequest , gin.H {"error" : "namespace query parameter is required" })
638+ return
639+ }
640+ name := c .Param ("name" )
641+ if name == "" {
642+ c .JSON (http .StatusBadRequest , gin.H {"error" : "agent name is required" })
643+ return
644+ }
645+
646+ // Read the raw data for validation
647+ var rawData []byte
648+ if data , err := c .GetRawData (); err == nil {
649+ rawData = data
650+ } else {
651+ c .JSON (http .StatusBadRequest , gin.H {"error" : "Failed to read request body: " + err .Error ()})
652+ return
653+ }
654+
655+ // Parse request
656+ var req UpdateAgentRequest
657+ if err := json .Unmarshal (rawData , & req ); err != nil {
658+ c .JSON (http .StatusBadRequest , gin.H {"error" : "Invalid request body: " + err .Error ()})
659+ return
660+ }
661+
662+ // Validate for unknown fields
663+ decoder := json .NewDecoder (bytes .NewReader (rawData ))
664+ decoder .DisallowUnknownFields ()
665+ if err := decoder .Decode (& req ); err != nil {
666+ if strings .Contains (err .Error (), "unknown field" ) {
667+ c .JSON (http .StatusBadRequest , gin.H {"error" : "Unknown field in request: " + err .Error ()})
668+ return
669+ }
670+ c .JSON (http .StatusBadRequest , gin.H {"error" : "Invalid JSON format: " + err .Error ()})
671+ return
672+ }
673+
674+ // Validate required fields
675+ if req .LLM == "" || req .SystemPrompt == "" {
676+ c .JSON (http .StatusBadRequest , gin.H {"error" : "llm and systemPrompt are required" })
677+ return
678+ }
679+
680+ // Fetch current agent
681+ var currentAgent acp.Agent
682+ if err := s .client .Get (ctx , client.ObjectKey {Namespace : namespace , Name : name }, & currentAgent ); err != nil {
683+ if apierrors .IsNotFound (err ) {
684+ c .JSON (http .StatusNotFound , gin.H {"error" : "Agent not found" })
685+ return
686+ }
687+ logger .Error (err , "Failed to get agent" , "name" , name )
688+ c .JSON (http .StatusInternalServerError , gin.H {"error" : "Failed to get agent: " + err .Error ()})
689+ return
690+ }
691+
692+ // Verify LLM exists
693+ if err := s .client .Get (ctx , client.ObjectKey {Namespace : namespace , Name : req .LLM }, & acp.LLM {}); err != nil {
694+ if apierrors .IsNotFound (err ) {
695+ c .JSON (http .StatusNotFound , gin.H {"error" : "LLM not found" })
696+ return
697+ }
698+ logger .Error (err , "Failed to check LLM" )
699+ c .JSON (http .StatusInternalServerError , gin.H {"error" : "Failed to check LLM: " + err .Error ()})
700+ return
701+ }
702+
703+ // Track current MCP servers for this agent
704+ currentMCPServers := make (map [string ]struct {})
705+ for _ , ref := range currentAgent .Spec .MCPServers {
706+ currentMCPServers [ref .Name ] = struct {}{}
707+ }
708+
709+ // Process new/updated MCP servers
710+ desiredMCPServers := make (map [string ]MCPServerConfig )
711+ for key , config := range req .MCPServers {
712+ mcpName := fmt .Sprintf ("%s-%s" , name , key )
713+ if err := validateMCPConfig (config ); err != nil {
714+ c .JSON (http .StatusBadRequest , gin.H {"error" : fmt .Sprintf ("Invalid MCP server config for '%s': %s" , key , err .Error ())})
715+ return
716+ }
717+ desiredMCPServers [mcpName ] = config
718+ }
719+
720+ // Create or update MCP servers
721+ for mcpName , config := range desiredMCPServers {
722+ secretName := fmt .Sprintf ("%s-secrets" , mcpName )
723+ var mcpServer acp.MCPServer
724+ err := s .client .Get (ctx , client.ObjectKey {Namespace : namespace , Name : mcpName }, & mcpServer )
725+ if apierrors .IsNotFound (err ) {
726+ // Create new MCP server and secret
727+ if len (config .Secrets ) > 0 {
728+ secret := createSecret (secretName , namespace , config .Secrets )
729+ if err := s .client .Create (ctx , secret ); err != nil {
730+ logger .Error (err , "Failed to create secret" , "name" , secretName )
731+ c .JSON (http .StatusInternalServerError , gin.H {"error" : "Failed to create secret: " + err .Error ()})
732+ return
733+ }
734+ }
735+ mcpServer := createMCPServer (mcpName , namespace , config , secretName )
736+ if err := s .client .Create (ctx , mcpServer ); err != nil {
737+ logger .Error (err , "Failed to create MCP server" , "name" , mcpName )
738+ c .JSON (http .StatusInternalServerError , gin.H {"error" : "Failed to create MCP server: " + err .Error ()})
739+ return
740+ }
741+ } else if err == nil {
742+ // Update existing MCP server
743+ updatedMCP := createMCPServer (mcpName , namespace , config , secretName )
744+ updatedMCP .ObjectMeta = mcpServer .ObjectMeta
745+ if err := s .client .Update (ctx , updatedMCP ); err != nil {
746+ logger .Error (err , "Failed to update MCP server" , "name" , mcpName )
747+ c .JSON (http .StatusInternalServerError , gin.H {"error" : "Failed to update MCP server: " + err .Error ()})
748+ return
749+ }
750+ // Handle secret
751+ if len (config .Secrets ) > 0 {
752+ var secret corev1.Secret
753+ err := s .client .Get (ctx , client.ObjectKey {Namespace : namespace , Name : secretName }, & secret )
754+ if apierrors .IsNotFound (err ) {
755+ secret := createSecret (secretName , namespace , config .Secrets )
756+ if err := s .client .Create (ctx , secret ); err != nil {
757+ logger .Error (err , "Failed to create secret" , "name" , secretName )
758+ c .JSON (http .StatusInternalServerError , gin.H {"error" : "Failed to create secret: " + err .Error ()})
759+ return
760+ }
761+ } else if err == nil {
762+ for k , v := range config .Secrets {
763+ if secret .Data == nil {
764+ secret .Data = make (map [string ][]byte )
765+ }
766+ secret .Data [k ] = []byte (v )
767+ }
768+ if err := s .client .Update (ctx , & secret ); err != nil {
769+ logger .Error (err , "Failed to update secret" , "name" , secretName )
770+ c .JSON (http .StatusInternalServerError , gin.H {"error" : "Failed to update secret: " + err .Error ()})
771+ return
772+ }
773+ } else {
774+ logger .Error (err , "Failed to get secret" , "name" , secretName )
775+ c .JSON (http .StatusInternalServerError , gin.H {"error" : "Failed to get secret: " + err .Error ()})
776+ return
777+ }
778+ } else {
779+ // Delete secret if it exists and no secrets are specified
780+ var secret corev1.Secret
781+ if err := s .client .Get (ctx , client.ObjectKey {Namespace : namespace , Name : secretName }, & secret ); err == nil {
782+ if err := s .client .Delete (ctx , & secret ); err != nil {
783+ logger .Error (err , "Failed to delete secret" , "name" , secretName )
784+ c .JSON (http .StatusInternalServerError , gin.H {"error" : "Failed to delete secret: " + err .Error ()})
785+ return
786+ }
787+ }
788+ }
789+ } else {
790+ logger .Error (err , "Failed to get MCP server" , "name" , mcpName )
791+ c .JSON (http .StatusInternalServerError , gin.H {"error" : "Failed to get MCP server: " + err .Error ()})
792+ return
793+ }
794+ delete (currentMCPServers , mcpName )
795+ }
796+
797+ // Delete removed MCP servers
798+ for mcpName := range currentMCPServers {
799+ var mcpServer acp.MCPServer
800+ if err := s .client .Get (ctx , client.ObjectKey {Namespace : namespace , Name : mcpName }, & mcpServer ); err == nil {
801+ if err := s .client .Delete (ctx , & mcpServer ); err != nil {
802+ logger .Error (err , "Failed to delete MCP server" , "name" , mcpName )
803+ c .JSON (http .StatusInternalServerError , gin.H {"error" : "Failed to delete MCP server: " + err .Error ()})
804+ return
805+ }
806+ }
807+ secretName := fmt .Sprintf ("%s-secrets" , mcpName )
808+ var secret corev1.Secret
809+ if err := s .client .Get (ctx , client.ObjectKey {Namespace : namespace , Name : secretName }, & secret ); err == nil {
810+ if err := s .client .Delete (ctx , & secret ); err != nil {
811+ logger .Error (err , "Failed to delete secret" , "name" , secretName )
812+ c .JSON (http .StatusInternalServerError , gin.H {"error" : "Failed to delete secret: " + err .Error ()})
813+ return
814+ }
815+ }
816+ }
817+
818+ // Update agent spec
819+ currentAgent .Spec .LLMRef = acp.LocalObjectReference {Name : req .LLM }
820+ currentAgent .Spec .System = req .SystemPrompt
821+ currentAgent .Spec .MCPServers = []acp.LocalObjectReference {}
822+ for mcpName := range desiredMCPServers {
823+ currentAgent .Spec .MCPServers = append (currentAgent .Spec .MCPServers , acp.LocalObjectReference {Name : mcpName })
824+ }
825+
826+ if err := s .client .Update (ctx , & currentAgent ); err != nil {
827+ logger .Error (err , "Failed to update agent" , "name" , name )
828+ c .JSON (http .StatusInternalServerError , gin.H {"error" : "Failed to update agent: " + err .Error ()})
829+ return
830+ }
831+
832+ c .JSON (http .StatusOK , AgentResponse {
833+ Namespace : namespace ,
834+ Name : name ,
835+ LLM : req .LLM ,
836+ SystemPrompt : req .SystemPrompt ,
837+ MCPServers : req .MCPServers ,
838+ })
839+ }
840+
621841// createTask handles the creation of a new task
622842func (s * APIServer ) createTask (c * gin.Context ) {
623843 ctx := c .Request .Context ()
0 commit comments