@@ -17,20 +17,26 @@ limitations under the License.
1717package e2e
1818
1919import (
20+ "context"
2021 "encoding/json"
2122 "fmt"
23+ "net/http"
2224 "os"
2325 "os/exec"
2426 "path/filepath"
2527 "strings"
2628 "time"
2729
30+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31+ "kagent.dev/kmcp/api/v1alpha1"
32+
33+ "github.com/mark3labs/mcp-go/client"
34+ "github.com/mark3labs/mcp-go/mcp"
2835 . "github.com/onsi/ginkgo/v2"
2936 . "github.com/onsi/gomega"
3037
3138 appsv1 "k8s.io/api/apps/v1"
3239 corev1 "k8s.io/api/core/v1"
33- "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
3440 "kagent.dev/kmcp/test/utils"
3541 "sigs.k8s.io/yaml"
3642)
@@ -279,31 +285,29 @@ var _ = Describe("Manager", Ordered, func() {
279285 })
280286
281287 Context ("MCPServer CRD" , func () {
282- FIt ("should create all resources for stdio transport MCPServer" , func () {
283- mcpServerName := "test-stdio-server"
284-
285- By ("creating an MCPServer with stdio transport" )
286- mcpServer := & unstructured.Unstructured {
287- Object : map [string ]interface {}{
288- "apiVersion" : "kagent.dev/v1alpha1" ,
289- "kind" : "MCPServer" ,
290- "metadata" : map [string ]interface {}{
291- "name" : mcpServerName ,
292- "namespace" : namespace ,
293- },
294- "spec" : map [string ]interface {}{
295- "deployment" : map [string ]interface {}{
296- "image" : "docker.io/mcp/everything" ,
297- "port" : 3000 ,
298- "cmd" : "npx" ,
299- "args" : []string {
300- "-y" ,
301- "@modelcontextprotocol/server-filesystem" ,
302- "/" ,
303- },
304- },
305- "transportType" : "stdio" ,
288+ It ("deploy a working MCP server" , func () {
289+ mcpServerName := "test-mcp-client-server"
290+ var portForwardCmd * exec.Cmd
291+ var localPort int = 8080
292+
293+ By ("creating an MCPServer for client testing" )
294+ mcpServer := & v1alpha1.MCPServer {
295+ TypeMeta : metav1.TypeMeta {
296+ APIVersion : "kagent.dev/v1alpha1" ,
297+ Kind : "MCPServer" ,
298+ },
299+ ObjectMeta : metav1.ObjectMeta {
300+ Name : mcpServerName ,
301+ Namespace : namespace ,
302+ },
303+ Spec : v1alpha1.MCPServerSpec {
304+ Deployment : v1alpha1.MCPServerDeployment {
305+ Image : "docker.io/mcp/everything" ,
306+ Port : 3000 ,
307+ Cmd : "npx" ,
308+ Args : []string {"-y" , "@modelcontextprotocol/server-filesystem" , "/" },
306309 },
310+ TransportType : "stdio" ,
307311 },
308312 }
309313
@@ -312,87 +316,75 @@ var _ = Describe("Manager", Ordered, func() {
312316 _ , err := utils .Run (cmd )
313317 Expect (err ).NotTo (HaveOccurred (), "Failed to create MCPServer" )
314318
315- By ("verifying the Deployment is created with correct configuration " )
319+ By ("waiting for the deployment to be ready " )
316320 Eventually (func (g Gomega ) {
317321 deployment := getDeployment (mcpServerName , namespace )
318322 g .Expect (deployment ).NotTo (BeNil ())
323+ g .Expect (deployment .Status .ReadyReplicas ).To (Equal (int32 (1 )))
324+ }, 3 * time .Minute ).Should (Succeed ())
319325
320- // Verify deployment has correct labels
321- g .Expect (deployment .Spec .Selector .MatchLabels ).To (Equal (map [string ]string {
322- "app.kubernetes.io/name" : mcpServerName ,
323- "app.kubernetes.io/instance" : mcpServerName ,
324- }))
325-
326- // Verify pod template has correct labels
327- g .Expect (deployment .Spec .Template .Labels ).To (HaveKeyWithValue ("app.kubernetes.io/name" , mcpServerName ))
328- g .Expect (deployment .Spec .Template .Labels ).To (HaveKeyWithValue ("app.kubernetes.io/instance" , mcpServerName ))
329- g .Expect (deployment .Spec .Template .Labels ).To (HaveKeyWithValue ("app.kubernetes.io/managed-by" , "kmcp" ))
330-
331- // Verify stdio transport configuration: init container + main container
332- g .Expect (deployment .Spec .Template .Spec .InitContainers ).To (HaveLen (1 ))
333- g .Expect (deployment .Spec .Template .Spec .Containers ).To (HaveLen (1 ))
334-
335- // Verify init container copies agentgateway binary
336- initContainer := deployment .Spec .Template .Spec .InitContainers [0 ]
337- g .Expect (initContainer .Name ).To (Equal ("copy-binary" ))
338- g .Expect (initContainer .Image ).To (ContainSubstring ("agentgateway" ))
339- g .Expect (initContainer .Command ).To (ContainElement ("sh" ))
340- g .Expect (initContainer .Args ).To (ContainElement (ContainSubstring ("cp /usr/bin/agentgateway /agentbin/agentgateway" )))
341-
342- // Verify main container configuration
343- mainContainer := deployment .Spec .Template .Spec .Containers [0 ]
344- g .Expect (mainContainer .Name ).To (Equal ("mcp-server" ))
345- g .Expect (mainContainer .Image ).To (Equal ("docker.io/mcp/everything" ))
346- g .Expect (mainContainer .Command ).To (ContainElement ("sh" ))
347- g .Expect (mainContainer .Args ).To (ContainElement (ContainSubstring ("/agentbin/agentgateway -f /config/local.yaml" )))
348-
349- // Verify volumes
350- g .Expect (deployment .Spec .Template .Spec .Volumes ).To (HaveLen (2 ))
351- volumeNames := []string {}
352- for _ , volume := range deployment .Spec .Template .Spec .Volumes {
353- volumeNames = append (volumeNames , volume .Name )
354- }
355- g .Expect (volumeNames ).To (ContainElements ("config" , "binary" ))
356- }).Should (Succeed ())
357-
358- By ("verifying the Service is created with correct configuration" )
326+ By ("waiting for the service to be ready" )
359327 Eventually (func (g Gomega ) {
360328 service := getService (mcpServerName , namespace )
361329 g .Expect (service ).NotTo (BeNil ())
362-
363- // Verify service selector
364- g .Expect (service .Spec .Selector ).To (Equal (map [string ]string {
365- "app.kubernetes.io/name" : mcpServerName ,
366- "app.kubernetes.io/instance" : mcpServerName ,
367- }))
368-
369- // Verify service ports
370330 g .Expect (service .Spec .Ports ).To (HaveLen (1 ))
371- g .Expect (service .Spec .Ports [0 ].Name ).To (Equal ("http" ))
372331 g .Expect (service .Spec .Ports [0 ].Port ).To (Equal (int32 (3000 )))
373- g .Expect (service .Spec .Ports [0 ].TargetPort .IntVal ).To (Equal (int32 (3000 )))
374- g .Expect (service .Spec .Ports [0 ].Protocol ).To (Equal (corev1 .ProtocolTCP ))
375332 }).Should (Succeed ())
376333
377- By ("verifying the ConfigMap is created with correct configuration" )
378- Eventually (func (g Gomega ) {
379- configMap := getConfigMap (mcpServerName , namespace )
380- g .Expect (configMap ).NotTo (BeNil ())
381-
382- // Verify configmap contains local.yaml
383- g .Expect (configMap .Data ).To (HaveKey ("local.yaml" ))
384-
385- // Parse and verify the configuration content
386- configYaml := configMap .Data ["local.yaml" ]
387- g .Expect (configYaml ).To (ContainSubstring ("config: {}" ))
388- g .Expect (configYaml ).To (ContainSubstring ("port: 3000" ))
389- g .Expect (configYaml ).To (ContainSubstring ("stdio:" ))
390- g .Expect (configYaml ).To (ContainSubstring ("cmd: npx" ))
391- g .Expect (configYaml ).To (ContainSubstring ("args:" ))
392- g .Expect (configYaml ).To (ContainSubstring ("- -y" ))
393- g .Expect (configYaml ).To (ContainSubstring ("- '@modelcontextprotocol/server-filesystem'" ))
394- g .Expect (configYaml ).To (ContainSubstring ("- /" ))
395- }).Should (Succeed ())
334+ By ("setting up kubectl port-forward to access the MCP server" )
335+ portForwardCmd = exec .Command ("kubectl" , "port-forward" ,
336+ fmt .Sprintf ("service/%s" , mcpServerName ),
337+ fmt .Sprintf ("%d:3000" , localPort ),
338+ "-n" , namespace )
339+
340+ err = portForwardCmd .Start ()
341+ Expect (err ).NotTo (HaveOccurred (), "Failed to start port-forward" )
342+
343+ // Wait for port-forward to be ready
344+ Eventually (func () error {
345+ resp , err := http .Get (fmt .Sprintf ("http://localhost:%d" , localPort ))
346+ if err != nil {
347+ return err
348+ }
349+ resp .Body .Close ()
350+ return nil
351+ }, 30 * time .Second , 1 * time .Second ).Should (Succeed ())
352+
353+ By ("creating MCP client and testing connection" )
354+ mcpClient , err := client .NewStreamableHttpClient (fmt .Sprintf ("http://localhost:%d/mcp" , localPort ))
355+ Expect (err ).NotTo (HaveOccurred (), "Failed to create MCP client" )
356+
357+ ctx := context .Background ()
358+
359+ By ("initializing the MCP client" )
360+ initResponse , err := mcpClient .Initialize (ctx , mcp.InitializeRequest {
361+ Params : mcp.InitializeParams {
362+ ProtocolVersion : mcp .LATEST_PROTOCOL_VERSION ,
363+ ClientInfo : mcp.Implementation {
364+ Name : "kmcp-e2e-test" ,
365+ Version : "1.0.0" ,
366+ },
367+ },
368+ })
369+ Expect (err ).NotTo (HaveOccurred (), "Failed to initialize MCP client" )
370+ Expect (initResponse ).NotTo (BeNil ())
371+
372+ By ("listing available tools from the MCP server" )
373+ toolsResponse , err := mcpClient .ListTools (ctx , mcp.ListToolsRequest {})
374+ Expect (err ).NotTo (HaveOccurred (), "Failed to list tools" )
375+ Expect (toolsResponse ).NotTo (BeNil ())
376+ Expect (toolsResponse .Tools ).NotTo (BeEmpty (), "Expected at least one tool to be available" )
377+
378+ // Log the available tools for debugging
379+ for _ , tool := range toolsResponse .Tools {
380+ fmt .Fprintf (GinkgoWriter , "Available tool: %s - %s\n " , tool .Name , tool .Description )
381+ }
382+
383+ By ("cleaning up port-forward" )
384+ if portForwardCmd != nil && portForwardCmd .Process != nil {
385+ err = portForwardCmd .Process .Kill ()
386+ Expect (err ).NotTo (HaveOccurred (), "Failed to kill port-forward process" )
387+ }
396388
397389 By ("cleaning up the MCPServer" )
398390 cmd = exec .Command ("kubectl" , "delete" , "mcpserver" , mcpServerName , "-n" , namespace )
@@ -504,8 +496,8 @@ func getConfigMap(name, namespace string) *corev1.ConfigMap {
504496 return & configMap
505497}
506498
507- func mcpServerToYAML (mcpServer * unstructured. Unstructured ) string {
508- yamlBytes , err := yaml .Marshal (mcpServer . Object )
499+ func mcpServerToYAML (mcpServer interface {} ) string {
500+ yamlBytes , err := yaml .Marshal (mcpServer )
509501 if err != nil {
510502 return ""
511503 }
0 commit comments