Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions go/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ require (
github.com/jedib0t/go-pretty/v6 v6.6.8
github.com/kagent-dev/kmcp v0.2.2
github.com/kagent-dev/mockllm v0.0.3
github.com/mark3labs/mcp-go v0.40.0
github.com/modelcontextprotocol/go-sdk v1.2.0
github.com/muesli/reflow v0.3.0
github.com/prometheus/client_golang v1.23.2
github.com/spf13/cobra v1.10.1
Expand All @@ -43,8 +43,6 @@ require (
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/aymanbagabas/go-udiff v0.3.1 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/charmbracelet/colorprofile v0.3.2 // indirect
github.com/charmbracelet/x/ansi v0.10.1 // indirect
Expand All @@ -53,7 +51,7 @@ require (
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/evanphx/json-patch v5.9.11+incompatible // indirect
github.com/invopop/jsonschema v0.13.0 // indirect
github.com/google/jsonschema-go v0.3.0 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
Expand All @@ -66,7 +64,6 @@ require (
github.com/tidwall/match v1.2.0 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.9-0.20240815153524-6ea36470d1bd // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
Expand Down
14 changes: 4 additions & 10 deletions go/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,12 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/briandowns/spinner v1.23.2 h1:Zc6ecUnI+YzLmJniCfDNaMbW0Wid1d5+qcTq4L2FW8w=
github.com/briandowns/spinner v1.23.2/go.mod h1:LaZeM4wm2Ywy6vO571mvhQNRcWfRUnXOs0RcKV0wYKM=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
Expand Down Expand Up @@ -128,6 +124,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/jsonschema-go v0.3.0 h1:6AH2TxVNtk3IlvkkhjrtbUc4S8AvO0Xii0DxIygDg+Q=
github.com/google/jsonschema-go v0.3.0/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
github.com/google/pprof v0.0.0-20250923004556-9e5a51aed1e8 h1:ZI8gCoCjGzPsum4L21jHdQs8shFBIQih1TM9Rd/c+EQ=
github.com/google/pprof v0.0.0-20250923004556-9e5a51aed1e8/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
Expand All @@ -143,8 +141,6 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
Expand Down Expand Up @@ -193,8 +189,6 @@ github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQ
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8=
github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/mark3labs/mcp-go v0.40.0 h1:M0oqK412OHBKut9JwXSsj4KanSmEKpzoW8TcxoPOkAU=
github.com/mark3labs/mcp-go v0.40.0/go.mod h1:T7tUa2jO6MavG+3P25Oy/jR7iCeJPHImCZHRymCn39g=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
Expand All @@ -206,6 +200,8 @@ github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75/go.mod h1:
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.17 h1:78v8ZlW0bP43XfmAfPsdXcoNCelfMHsDmd/pkENfrjQ=
github.com/mattn/go-runewidth v0.0.17/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/modelcontextprotocol/go-sdk v1.2.0 h1:Y23co09300CEk8iZ/tMxIX1dVmKZkzoSBZOpJwUnc/s=
github.com/modelcontextprotocol/go-sdk v1.2.0/go.mod h1:6fM3LCm3yV7pAs8isnKLn07oKtB0MP9LHd3DfAcKw10=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
Expand Down Expand Up @@ -299,8 +295,6 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/wk8/go-ordered-map/v2 v2.1.9-0.20240815153524-6ea36470d1bd h1:dLuIF2kX9c+KknGJUdJi1Il1SDiTSK158/BB9kdgAew=
github.com/wk8/go-ordered-map/v2 v2.1.9-0.20240815153524-6ea36470d1bd/go.mod h1:DbzwytT4g/odXquuOCqroKvtxxldI4nb3nuesHF/Exo=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
Expand Down
79 changes: 55 additions & 24 deletions go/internal/controller/reconciler/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/hex"
"errors"
"fmt"
"net/http"
"reflect"
"slices"
"strings"
Expand All @@ -27,9 +28,7 @@ import (
"github.com/kagent-dev/kagent/go/internal/database"
"github.com/kagent-dev/kagent/go/internal/utils"
"github.com/kagent-dev/kagent/go/internal/version"
mcp_client "github.com/mark3labs/mcp-go/client"
"github.com/mark3labs/mcp-go/client/transport"
"github.com/mark3labs/mcp-go/mcp"
"github.com/modelcontextprotocol/go-sdk/mcp"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
Expand Down Expand Up @@ -665,41 +664,73 @@ func (a *kagentReconciler) upsertToolServerForRemoteMCPServer(ctx context.Contex
return tools, nil
}

func (a *kagentReconciler) createMcpTransport(ctx context.Context, s *v1alpha2.RemoteMCPServerSpec, namespace string) (transport.Interface, error) {
func (a *kagentReconciler) createMcpTransport(ctx context.Context, s *v1alpha2.RemoteMCPServerSpec, namespace string) (mcp.Transport, error) {
headers, err := s.ResolveHeaders(ctx, a.kube, namespace)
if err != nil {
return nil, err
}

httpClient := newHTTPClient(headers)

switch s.Protocol {
case v1alpha2.RemoteMCPServerProtocolSse:
return transport.NewSSE(s.URL, transport.WithHeaders(headers))
return &mcp.SSEClientTransport{
Endpoint: s.URL,
HTTPClient: httpClient,
}, nil
default:
return transport.NewStreamableHTTP(s.URL, transport.WithHTTPHeaders(headers))
return &mcp.StreamableClientTransport{
Endpoint: s.URL,
HTTPClient: httpClient,
}, nil
}
}

func (a *kagentReconciler) listTools(ctx context.Context, tsp transport.Interface, toolServer *database.ToolServer) ([]*v1alpha2.MCPTool, error) {
client := mcp_client.NewClient(tsp)
err := client.Start(ctx)
if err != nil {
return nil, fmt.Errorf("failed to start client for toolServer %s: %v", toolServer.Name, err)
}
defer client.Close()
_, err = client.Initialize(ctx, mcp.InitializeRequest{
Params: mcp.InitializeParams{
ProtocolVersion: mcp.LATEST_PROTOCOL_VERSION,
Capabilities: mcp.ClientCapabilities{},
ClientInfo: mcp.Implementation{
Name: "kagent-controller",
Version: version.Version,
},
// go-sdk does not have a WithHeaders option when initializing transport
// so we need to create a custom HTTP client that adds headers to all requests.
func newHTTPClient(headers map[string]string) *http.Client {
if len(headers) == 0 {
return http.DefaultClient
}
return &http.Client{
Transport: &headerTransport{
headers: headers,
base: http.DefaultTransport,
},
})
}
}

// headerTransport is an http.RoundTripper that adds custom headers to requests.
type headerTransport struct {
headers map[string]string
base http.RoundTripper
}

func (t *headerTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req = req.Clone(req.Context())
for k, v := range t.headers {
req.Header.Set(k, v)
}
if t.base == nil {
t.base = http.DefaultTransport
}
return t.base.RoundTrip(req)
}

func (a *kagentReconciler) listTools(ctx context.Context, tsp mcp.Transport, toolServer *database.ToolServer) ([]*v1alpha2.MCPTool, error) {
impl := &mcp.Implementation{
Name: "kagent-controller",
Version: version.Version,
}
client := mcp.NewClient(impl, nil)

session, err := client.Connect(ctx, tsp, nil)
if err != nil {
return nil, fmt.Errorf("failed to initialize client for toolServer %s: %v", toolServer.Name, err)
return nil, fmt.Errorf("failed to connect client for toolServer %s: %v", toolServer.Name, err)
}
result, err := client.ListTools(ctx, mcp.ListToolsRequest{})
defer session.Close()

result, err := session.ListTools(ctx, &mcp.ListToolsParams{})
if err != nil {
return nil, fmt.Errorf("failed to list tools for toolServer %s: %v", toolServer.Name, err)
}
Expand Down
8 changes: 8 additions & 0 deletions go/internal/httpserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/kagent-dev/kagent/go/internal/a2a"
"github.com/kagent-dev/kagent/go/internal/database"
"github.com/kagent-dev/kagent/go/internal/httpserver/handlers"
"github.com/kagent-dev/kagent/go/internal/mcp"
common "github.com/kagent-dev/kagent/go/internal/utils"
"github.com/kagent-dev/kagent/go/internal/version"
"github.com/kagent-dev/kagent/go/pkg/auth"
Expand All @@ -35,6 +36,7 @@ const (
APIPathMemories = "/api/memories"
APIPathNamespaces = "/api/namespaces"
APIPathA2A = "/api/a2a"
APIPathMCP = "/mcp"
APIPathFeedback = "/api/feedback"
APIPathLangGraph = "/api/langgraph"
APIPathCrewAI = "/api/crewai"
Expand All @@ -51,6 +53,7 @@ type ServerConfig struct {
BindAddr string
KubeClient ctrl_client.Client
A2AHandler a2a.A2AHandlerMux
MCPHandler *mcp.MCPHandler
WatchedNamespaces []string
DbClient database.Client
Authenticator auth.AuthProvider
Expand Down Expand Up @@ -225,6 +228,11 @@ func (s *HTTPServer) setupRoutes() {
// A2A
s.router.PathPrefix(APIPathA2A + "/{namespace}/{name}").Handler(s.config.A2AHandler)

// MCP
if s.config.MCPHandler != nil {
s.router.PathPrefix(APIPathMCP).Handler(s.config.MCPHandler)
}

// Use middleware for common functionality
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The MCP handler route is registered before the authentication middleware is applied (line 237). In gorilla/mux, middleware added via Use() applies to routes registered before the Use() call, but the order can be confusing. While the current code appears correct (middleware applies to all routes including MCP), this pattern is fragile. Consider either: (1) explicitly documenting that routes must be registered before middleware, or (2) applying middleware before route registration for clarity and to prevent future security issues.

Suggested change
// Use middleware for common functionality
// Use middleware for common functionality.
//
// NOTE: gorilla/mux applies middleware added via Use() to all routes
// registered on this router, including those registered *before* the Use()
// call. To avoid confusion and potential security issues where routes might
// accidentally be registered without authentication, all routes for this
// server MUST be registered above this middleware block. Do not add new
// routes below these Use() calls.

Copilot uses AI. Check for mistakes.
s.router.Use(auth.AuthnMiddleware(s.authenticator))
s.router.Use(contentTypeMiddleware)
Expand Down
Loading
Loading