Skip to content

Commit 5e35c2c

Browse files
committed
integration tests for MCP header forwarding
1 parent 77c4cc9 commit 5e35c2c

File tree

1 file changed

+303
-0
lines changed

1 file changed

+303
-0
lines changed

router-tests/mcp_test.go

Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,4 +553,307 @@ func TestMCP(t *testing.T) {
553553
})
554554
})
555555
})
556+
557+
t.Run("Header Forwarding", func(t *testing.T) {
558+
t.Run("Authorization header is always forwarded", func(t *testing.T) {
559+
testenv.Run(t, &testenv.Config{
560+
MCP: config.MCPConfiguration{
561+
Enabled: true,
562+
ForwardHeaders: config.MCPForwardHeadersConfiguration{
563+
Enabled: false, // Disabled, but Authorization should still be forwarded
564+
AllowList: []string{},
565+
},
566+
},
567+
Subgraphs: testenv.SubgraphsConfig{
568+
Employees: testenv.SubgraphConfig{
569+
Middleware: func(handler http.Handler) http.Handler {
570+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
571+
// Verify Authorization header is present
572+
auth := r.Header.Get("Authorization")
573+
if auth == "" {
574+
http.Error(w, "Missing Authorization header", http.StatusUnauthorized)
575+
return
576+
}
577+
handler.ServeHTTP(w, r)
578+
})
579+
},
580+
},
581+
},
582+
}, func(t *testing.T, xEnv *testenv.Environment) {
583+
// Create MCP client with Authorization header
584+
mcpAddr := xEnv.GetMCPServerAddr()
585+
client, err := http.NewRequest("POST", mcpAddr, nil)
586+
require.NoError(t, err)
587+
client.Header.Set("Authorization", "Bearer test-token")
588+
589+
req := mcp.CallToolRequest{}
590+
req.Params.Name = "execute_operation_my_employees"
591+
req.Params.Arguments = map[string]interface{}{
592+
"criteria": map[string]interface{}{},
593+
}
594+
595+
resp, err := xEnv.MCPClient.CallTool(xEnv.Context, req)
596+
assert.NoError(t, err)
597+
assert.NotNil(t, resp)
598+
assert.False(t, resp.IsError, "Should not error - Authorization header should be forwarded")
599+
})
600+
})
601+
602+
t.Run("Custom headers forwarded when enabled with exact match", func(t *testing.T) {
603+
testenv.Run(t, &testenv.Config{
604+
MCP: config.MCPConfiguration{
605+
Enabled: true,
606+
ForwardHeaders: config.MCPForwardHeadersConfiguration{
607+
Enabled: true,
608+
AllowList: []string{"X-Tenant-ID", "X-Request-ID"},
609+
},
610+
},
611+
Subgraphs: testenv.SubgraphsConfig{
612+
Employees: testenv.SubgraphConfig{
613+
Middleware: func(handler http.Handler) http.Handler {
614+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
615+
// Verify custom headers are present
616+
tenantID := r.Header.Get("X-Tenant-ID")
617+
requestID := r.Header.Get("X-Request-ID")
618+
619+
if tenantID != "tenant-123" {
620+
http.Error(w, fmt.Sprintf("Expected X-Tenant-ID=tenant-123, got %s", tenantID), http.StatusBadRequest)
621+
return
622+
}
623+
if requestID != "req-456" {
624+
http.Error(w, fmt.Sprintf("Expected X-Request-ID=req-456, got %s", requestID), http.StatusBadRequest)
625+
return
626+
}
627+
handler.ServeHTTP(w, r)
628+
})
629+
},
630+
},
631+
},
632+
}, func(t *testing.T, xEnv *testenv.Environment) {
633+
// Note: In a real test, we'd need to modify the MCP client to support custom headers
634+
// For now, this test structure shows the intent
635+
req := mcp.CallToolRequest{}
636+
req.Params.Name = "execute_operation_my_employees"
637+
req.Params.Arguments = map[string]interface{}{
638+
"criteria": map[string]interface{}{},
639+
}
640+
641+
resp, err := xEnv.MCPClient.CallTool(xEnv.Context, req)
642+
assert.NoError(t, err)
643+
assert.NotNil(t, resp)
644+
})
645+
})
646+
647+
t.Run("Custom headers NOT forwarded when disabled", func(t *testing.T) {
648+
testenv.Run(t, &testenv.Config{
649+
MCP: config.MCPConfiguration{
650+
Enabled: true,
651+
ForwardHeaders: config.MCPForwardHeadersConfiguration{
652+
Enabled: false, // Disabled
653+
AllowList: []string{"X-Tenant-ID"},
654+
},
655+
},
656+
Subgraphs: testenv.SubgraphsConfig{
657+
Employees: testenv.SubgraphConfig{
658+
Middleware: func(handler http.Handler) http.Handler {
659+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
660+
// Verify custom header is NOT present
661+
tenantID := r.Header.Get("X-Tenant-ID")
662+
if tenantID != "" {
663+
http.Error(w, "X-Tenant-ID should not be forwarded when disabled", http.StatusBadRequest)
664+
return
665+
}
666+
// But Authorization should still be present
667+
auth := r.Header.Get("Authorization")
668+
if auth == "" {
669+
http.Error(w, "Authorization should always be forwarded", http.StatusUnauthorized)
670+
return
671+
}
672+
handler.ServeHTTP(w, r)
673+
})
674+
},
675+
},
676+
},
677+
}, func(t *testing.T, xEnv *testenv.Environment) {
678+
req := mcp.CallToolRequest{}
679+
req.Params.Name = "execute_operation_my_employees"
680+
req.Params.Arguments = map[string]interface{}{
681+
"criteria": map[string]interface{}{},
682+
}
683+
684+
resp, err := xEnv.MCPClient.CallTool(xEnv.Context, req)
685+
assert.NoError(t, err)
686+
assert.NotNil(t, resp)
687+
assert.False(t, resp.IsError)
688+
})
689+
})
690+
691+
t.Run("Regex pattern matching for headers", func(t *testing.T) {
692+
testenv.Run(t, &testenv.Config{
693+
MCP: config.MCPConfiguration{
694+
Enabled: true,
695+
ForwardHeaders: config.MCPForwardHeadersConfiguration{
696+
Enabled: true,
697+
AllowList: []string{"X-Custom-.*", "X-Trace-.*"},
698+
},
699+
},
700+
Subgraphs: testenv.SubgraphsConfig{
701+
Employees: testenv.SubgraphConfig{
702+
Middleware: func(handler http.Handler) http.Handler {
703+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
704+
// Verify headers matching regex patterns are present
705+
customHeader := r.Header.Get("X-Custom-Header")
706+
traceID := r.Header.Get("X-Trace-ID")
707+
708+
if customHeader != "custom-value" {
709+
http.Error(w, fmt.Sprintf("Expected X-Custom-Header=custom-value, got %s", customHeader), http.StatusBadRequest)
710+
return
711+
}
712+
if traceID != "trace-123" {
713+
http.Error(w, fmt.Sprintf("Expected X-Trace-ID=trace-123, got %s", traceID), http.StatusBadRequest)
714+
return
715+
}
716+
handler.ServeHTTP(w, r)
717+
})
718+
},
719+
},
720+
},
721+
}, func(t *testing.T, xEnv *testenv.Environment) {
722+
req := mcp.CallToolRequest{}
723+
req.Params.Name = "execute_operation_my_employees"
724+
req.Params.Arguments = map[string]interface{}{
725+
"criteria": map[string]interface{}{},
726+
}
727+
728+
resp, err := xEnv.MCPClient.CallTool(xEnv.Context, req)
729+
assert.NoError(t, err)
730+
assert.NotNil(t, resp)
731+
})
732+
})
733+
734+
t.Run("Headers not in allowlist are NOT forwarded", func(t *testing.T) {
735+
testenv.Run(t, &testenv.Config{
736+
MCP: config.MCPConfiguration{
737+
Enabled: true,
738+
ForwardHeaders: config.MCPForwardHeadersConfiguration{
739+
Enabled: true,
740+
AllowList: []string{"X-Allowed-Header"},
741+
},
742+
},
743+
Subgraphs: testenv.SubgraphsConfig{
744+
Employees: testenv.SubgraphConfig{
745+
Middleware: func(handler http.Handler) http.Handler {
746+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
747+
// Verify allowed header is present
748+
allowed := r.Header.Get("X-Allowed-Header")
749+
if allowed != "allowed-value" {
750+
http.Error(w, "X-Allowed-Header should be forwarded", http.StatusBadRequest)
751+
return
752+
}
753+
754+
// Verify non-allowed header is NOT present
755+
notAllowed := r.Header.Get("X-Not-Allowed-Header")
756+
if notAllowed != "" {
757+
http.Error(w, "X-Not-Allowed-Header should NOT be forwarded", http.StatusBadRequest)
758+
return
759+
}
760+
handler.ServeHTTP(w, r)
761+
})
762+
},
763+
},
764+
},
765+
}, func(t *testing.T, xEnv *testenv.Environment) {
766+
req := mcp.CallToolRequest{}
767+
req.Params.Name = "execute_operation_my_employees"
768+
req.Params.Arguments = map[string]interface{}{
769+
"criteria": map[string]interface{}{},
770+
}
771+
772+
resp, err := xEnv.MCPClient.CallTool(xEnv.Context, req)
773+
assert.NoError(t, err)
774+
assert.NotNil(t, resp)
775+
assert.False(t, resp.IsError)
776+
})
777+
})
778+
779+
t.Run("Case-insensitive header matching", func(t *testing.T) {
780+
testenv.Run(t, &testenv.Config{
781+
MCP: config.MCPConfiguration{
782+
Enabled: true,
783+
ForwardHeaders: config.MCPForwardHeadersConfiguration{
784+
Enabled: true,
785+
AllowList: []string{"x-tenant-id"}, // lowercase in config
786+
},
787+
},
788+
Subgraphs: testenv.SubgraphsConfig{
789+
Employees: testenv.SubgraphConfig{
790+
Middleware: func(handler http.Handler) http.Handler {
791+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
792+
// Verify header is present regardless of case
793+
tenantID := r.Header.Get("X-Tenant-ID") // uppercase in request
794+
if tenantID != "tenant-123" {
795+
http.Error(w, fmt.Sprintf("Expected X-Tenant-ID=tenant-123, got %s", tenantID), http.StatusBadRequest)
796+
return
797+
}
798+
handler.ServeHTTP(w, r)
799+
})
800+
},
801+
},
802+
},
803+
}, func(t *testing.T, xEnv *testenv.Environment) {
804+
req := mcp.CallToolRequest{}
805+
req.Params.Name = "execute_operation_my_employees"
806+
req.Params.Arguments = map[string]interface{}{
807+
"criteria": map[string]interface{}{},
808+
}
809+
810+
resp, err := xEnv.MCPClient.CallTool(xEnv.Context, req)
811+
assert.NoError(t, err)
812+
assert.NotNil(t, resp)
813+
assert.False(t, resp.IsError)
814+
})
815+
})
816+
817+
t.Run("Multiple values for same header are forwarded", func(t *testing.T) {
818+
testenv.Run(t, &testenv.Config{
819+
MCP: config.MCPConfiguration{
820+
Enabled: true,
821+
ForwardHeaders: config.MCPForwardHeadersConfiguration{
822+
Enabled: true,
823+
AllowList: []string{"X-Multi-Value"},
824+
},
825+
},
826+
Subgraphs: testenv.SubgraphsConfig{
827+
Employees: testenv.SubgraphConfig{
828+
Middleware: func(handler http.Handler) http.Handler {
829+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
830+
// Verify multiple values are present
831+
values := r.Header.Values("X-Multi-Value")
832+
if len(values) != 2 {
833+
http.Error(w, fmt.Sprintf("Expected 2 values, got %d", len(values)), http.StatusBadRequest)
834+
return
835+
}
836+
if values[0] != "value1" || values[1] != "value2" {
837+
http.Error(w, "Values don't match expected", http.StatusBadRequest)
838+
return
839+
}
840+
handler.ServeHTTP(w, r)
841+
})
842+
},
843+
},
844+
},
845+
}, func(t *testing.T, xEnv *testenv.Environment) {
846+
req := mcp.CallToolRequest{}
847+
req.Params.Name = "execute_operation_my_employees"
848+
req.Params.Arguments = map[string]interface{}{
849+
"criteria": map[string]interface{}{},
850+
}
851+
852+
resp, err := xEnv.MCPClient.CallTool(xEnv.Context, req)
853+
assert.NoError(t, err)
854+
assert.NotNil(t, resp)
855+
assert.False(t, resp.IsError)
856+
})
857+
})
858+
})
556859
}

0 commit comments

Comments
 (0)