From 2cc5f3e9cc674041df08f939b7fd51054e184cc6 Mon Sep 17 00:00:00 2001 From: Alex Jones Date: Fri, 25 Apr 2025 16:37:07 +0100 Subject: [PATCH] feat: added http sse support Signed-off-by: Alex Jones --- cmd/serve/serve.go | 4 +- go.mod | 17 +-- go.sum | 35 ----- .../{example => client_example}/README.md | 0 pkg/server/client_example/main.go | 110 ++++++++++++++++ pkg/server/config/integration.go | 7 +- pkg/server/example/main.go | 68 ---------- pkg/server/mcp.go | 120 ++++++++++++++---- 8 files changed, 208 insertions(+), 153 deletions(-) rename pkg/server/{example => client_example}/README.md (100%) create mode 100644 pkg/server/client_example/main.go delete mode 100644 pkg/server/example/main.go diff --git a/cmd/serve/serve.go b/cmd/serve/serve.go index a16809b..c650f85 100644 --- a/cmd/serve/serve.go +++ b/cmd/serve/serve.go @@ -40,6 +40,7 @@ var ( enableHttp bool enableMCP bool mcpPort string + mcpHTTP bool ) var ServeCmd = &cobra.Command{ @@ -187,7 +188,7 @@ var ServeCmd = &cobra.Command{ if enableMCP { // Create and start MCP server - mcpServer, err := k8sgptserver.NewMCPServer(mcpPort, aiProvider) + mcpServer, err := k8sgptserver.NewMCPServer(mcpPort, aiProvider, mcpHTTP, logger) if err != nil { color.Red("Error creating MCP server: %v", err) os.Exit(1) @@ -235,4 +236,5 @@ func init() { ServeCmd.Flags().BoolVarP(&enableHttp, "http", "", false, "Enable REST/http using gppc-gateway") ServeCmd.Flags().BoolVarP(&enableMCP, "mcp", "", false, "Enable Mission Control Protocol server") ServeCmd.Flags().StringVarP(&mcpPort, "mcp-port", "", "8089", "Port to run the MCP server on") + ServeCmd.Flags().BoolVarP(&mcpHTTP, "mcp-http", "", false, "Enable HTTP mode for MCP server") } diff --git a/go.mod b/go.mod index 9c0d12a..ba6cac9 100644 --- a/go.mod +++ b/go.mod @@ -40,6 +40,7 @@ require ( github.com/cohere-ai/cohere-go/v2 v2.12.2 github.com/go-logr/zapr v1.3.0 github.com/google/generative-ai-go v0.19.0 + github.com/google/martian v2.1.0+incompatible github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 github.com/hupe1980/go-huggingface v0.0.15 github.com/kyverno/policy-reporter-kyverno-plugin v1.6.4 @@ -82,11 +83,7 @@ require ( github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/buger/jsonparser v1.1.1 // indirect - github.com/bytedance/sonic v1.11.6 // indirect - github.com/bytedance/sonic/loader v0.1.1 // indirect github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect - github.com/cloudwego/base64x v0.1.4 // indirect - github.com/cloudwego/iasm v0.2.0 // indirect github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect github.com/containerd/console v1.0.4 // indirect github.com/containerd/continuity v0.4.3 // indirect @@ -102,13 +99,6 @@ require ( github.com/expr-lang/expr v1.17.2 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.3 // indirect - github.com/gin-contrib/sse v0.1.0 // indirect - github.com/gin-gonic/gin v1.10.0 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.22.1 // indirect - github.com/goccy/go-json v0.10.2 // indirect github.com/gofrs/flock v0.12.1 // indirect github.com/golang-jwt/jwt/v5 v5.2.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -122,9 +112,7 @@ require ( github.com/invopop/jsonschema v0.12.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jpillora/backoff v1.0.0 // indirect - github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/kylelemons/godebug v1.1.0 // indirect - github.com/leodido/go-urn v1.4.0 // indirect github.com/lithammer/fuzzysearch v1.1.8 // indirect github.com/moby/sys/mountinfo v0.7.1 // indirect github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect @@ -143,8 +131,6 @@ require ( github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect - github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.2.12 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect @@ -156,7 +142,6 @@ require ( go.opentelemetry.io/otel/metric v1.34.0 // indirect go.opentelemetry.io/otel/sdk v1.34.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.32.0 // indirect - golang.org/x/arch v0.8.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect diff --git a/go.sum b/go.sum index ce43486..bfb44d3 100644 --- a/go.sum +++ b/go.sum @@ -761,10 +761,6 @@ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembj github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= -github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= -github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= -github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= -github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= @@ -782,10 +778,6 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= -github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= -github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= -github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -898,13 +890,7 @@ github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/ github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= -github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= -github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= @@ -937,14 +923,6 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= -github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-resty/resty/v2 v2.16.3 h1:zacNT7lt4b8M/io2Ahj6yPypL7bqx9n1iprfQuodV+E= github.com/go-resty/resty/v2 v2.16.3/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= @@ -957,8 +935,6 @@ github.com/go-zookeeper/zk v1.0.4/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= -github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= @@ -1204,7 +1180,6 @@ github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuOb github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -1227,8 +1202,6 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= -github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= -github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= @@ -1472,10 +1445,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/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= -github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs= github.com/vultr/govultr/v2 v2.17.2/go.mod h1:ZFOKGWmgjytfyjeyAdhQlSWwTjh2ig+X49cAp50dzXI= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= @@ -1548,9 +1517,6 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= -golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -2300,7 +2266,6 @@ modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= -nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= oras.land/oras-go v1.2.5 h1:XpYuAwAb0DfQsunIyMfeET92emK8km3W4yEzZvUbsTo= oras.land/oras-go v1.2.5/go.mod h1:PuAwRShRZCsZb7g8Ar3jKKQR/2A/qN+pkYxIOd/FAoo= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/pkg/server/example/README.md b/pkg/server/client_example/README.md similarity index 100% rename from pkg/server/example/README.md rename to pkg/server/client_example/README.md diff --git a/pkg/server/client_example/main.go b/pkg/server/client_example/main.go new file mode 100644 index 0000000..ea5be6e --- /dev/null +++ b/pkg/server/client_example/main.go @@ -0,0 +1,110 @@ +/* +Copyright 2024 The K8sGPT Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "bytes" + "encoding/json" + "flag" + "fmt" + "io" + "log" + "net/http" + "time" +) + +// AnalyzeRequest represents the input parameters for the analyze tool +type AnalyzeRequest struct { + Namespace string `json:"namespace,omitempty"` + Backend string `json:"backend,omitempty"` + Language string `json:"language,omitempty"` + Filters []string `json:"filters,omitempty"` + LabelSelector string `json:"labelSelector,omitempty"` + NoCache bool `json:"noCache,omitempty"` + Explain bool `json:"explain,omitempty"` + MaxConcurrency int `json:"maxConcurrency,omitempty"` + WithDoc bool `json:"withDoc,omitempty"` + InteractiveMode bool `json:"interactiveMode,omitempty"` + CustomHeaders []string `json:"customHeaders,omitempty"` + WithStats bool `json:"withStats,omitempty"` +} + +// AnalyzeResponse represents the output of the analyze tool +type AnalyzeResponse struct { + Content []struct { + Text string `json:"text"` + Type string `json:"type"` + } `json:"content"` +} + +func main() { + // Parse command line flags + serverPort := flag.String("port", "8089", "Port of the MCP server") + namespace := flag.String("namespace", "", "Kubernetes namespace to analyze") + backend := flag.String("backend", "", "AI backend to use") + language := flag.String("language", "english", "Language for analysis") + flag.Parse() + + // Create analyze request + req := AnalyzeRequest{ + Namespace: *namespace, + Backend: *backend, + Language: *language, + Explain: true, + MaxConcurrency: 10, + } + + // Convert request to JSON + reqJSON, err := json.Marshal(req) + if err != nil { + log.Fatalf("Failed to marshal request: %v", err) + } + + // Create HTTP client with timeout + client := &http.Client{ + Timeout: 5 * time.Minute, + } + + // Send request to MCP server + resp, err := client.Post( + fmt.Sprintf("http://localhost:%s/mcp/analyze", *serverPort), + "application/json", + bytes.NewBuffer(reqJSON), + ) + if err != nil { + log.Fatalf("Failed to send request: %v", err) + } + defer resp.Body.Close() + + // Read and print raw response for debugging + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Fatalf("Failed to read response body: %v", err) + } + fmt.Printf("Raw response: %s\n", string(body)) + + // Parse response + var analyzeResp AnalyzeResponse + if err := json.Unmarshal(body, &analyzeResp); err != nil { + log.Fatalf("Failed to decode response: %v", err) + } + + // Print results + fmt.Println("Analysis Results:") + if len(analyzeResp.Content) > 0 { + fmt.Println(analyzeResp.Content[0].Text) + } else { + fmt.Println("No results returned") + } +} diff --git a/pkg/server/config/integration.go b/pkg/server/config/integration.go index 47c794d..0e2b2c9 100644 --- a/pkg/server/config/integration.go +++ b/pkg/server/config/integration.go @@ -1,18 +1,15 @@ package config import ( - schemav1 "buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1" "context" "fmt" + + schemav1 "buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1" "github.com/k8sgpt-ai/k8sgpt/pkg/integration" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) -//const ( -// trivyName = "trivy" -//) - // syncIntegration is aware of the following events // A new integration added // An integration removed from the Integration block diff --git a/pkg/server/example/main.go b/pkg/server/example/main.go deleted file mode 100644 index 849d4b5..0000000 --- a/pkg/server/example/main.go +++ /dev/null @@ -1,68 +0,0 @@ -/* -Copyright 2024 The K8sGPT Authors. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package main - -import ( - "context" - "fmt" - "log" - "os" - "os/signal" - "syscall" - - mcp_golang "github.com/metoro-io/mcp-golang" - "github.com/metoro-io/mcp-golang/transport/stdio" -) - -func main() { - // Create transport and client - transport := stdio.NewStdioServerTransport() - - // Create a new client with the transport - client := mcp_golang.NewClient(transport) - - // Initialize the client - if _, err := client.Initialize(context.Background()); err != nil { - log.Fatalf("Failed to initialize client: %v", err) - } - - // Call analyze tool - response, err := client.CallTool(context.Background(), "analyze", map[string]interface{}{ - "namespace": "default", - }) - if err != nil { - log.Fatalf("Failed to call analyze tool: %v", err) - } - - // Print response - if response != nil && len(response.Content) > 0 { - fmt.Printf("Analysis results: %s\n", response.Content[0].TextContent.Text) - } - - // Get cluster info - clusterInfo, err := client.CallTool(context.Background(), "cluster-info", nil) - if err != nil { - log.Fatalf("Failed to get cluster info: %v", err) - } - - // Print cluster info - if clusterInfo != nil && len(clusterInfo.Content) > 0 { - fmt.Printf("Cluster info: %s\n", clusterInfo.Content[0].TextContent.Text) - } - - // Handle graceful shutdown - sigChan := make(chan os.Signal, 1) - signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) - <-sigChan -} diff --git a/pkg/server/mcp.go b/pkg/server/mcp.go index e11ced9..a36641d 100644 --- a/pkg/server/mcp.go +++ b/pkg/server/mcp.go @@ -15,15 +15,19 @@ package server import ( "context" + "encoding/json" "fmt" + "net/http" schemav1 "buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1" "github.com/k8sgpt-ai/k8sgpt/pkg/ai" "github.com/k8sgpt-ai/k8sgpt/pkg/analysis" + "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" "github.com/k8sgpt-ai/k8sgpt/pkg/server/config" mcp_golang "github.com/metoro-io/mcp-golang" "github.com/metoro-io/mcp-golang/transport/stdio" "github.com/spf13/viper" + "go.uber.org/zap" ) // MCPServer represents an MCP server for k8sgpt @@ -31,40 +35,23 @@ type MCPServer struct { server *mcp_golang.Server port string aiProvider *ai.AIProvider - analysis *analysis.Analysis + useHTTP bool + logger *zap.Logger } // NewMCPServer creates a new MCP server -func NewMCPServer(port string, aiProvider *ai.AIProvider) (*MCPServer, error) { +func NewMCPServer(port string, aiProvider *ai.AIProvider, useHTTP bool, logger *zap.Logger) (*MCPServer, error) { // Create MCP server with stdio transport transport := stdio.NewStdioServerTransport() server := mcp_golang.NewServer(transport) - // Initialize analysis configuration - analysis, err := analysis.NewAnalysis( - aiProvider.Name, // backend - "english", // language - []string{}, // filters - "", // namespace - "", // labelSelector - false, // nocache - false, // explain - 10, // maxConcurrency - false, // withDoc - false, // interactiveMode - []string{}, // customHeaders - false, // withStats - ) - if err != nil { - return nil, fmt.Errorf("failed to initialize analysis: %v", err) - } - return &MCPServer{ server: server, port: port, aiProvider: aiProvider, - analysis: analysis, + useHTTP: useHTTP, + logger: logger, }, nil } @@ -99,6 +86,18 @@ func (s *MCPServer) Start() error { return fmt.Errorf("failed to register prompts: %v", err) } + if s.useHTTP { + // Start HTTP server + go func() { + http.HandleFunc("/mcp/analyze", s.handleAnalyzeHTTP) + http.HandleFunc("/mcp", s.handleSSE) + s.logger.Info("Starting MCP server on port", zap.String("port", s.port)) + if err := http.ListenAndServe(fmt.Sprintf(":%s", s.port), nil); err != nil { + s.logger.Error("Error starting HTTP server", zap.Error(err)) + } + }() + } + // Start the server return s.server.Serve() } @@ -170,7 +169,6 @@ func (s *MCPServer) handleAnalyze(ctx context.Context, request *AnalyzeRequest) if err := viper.UnmarshalKey("ai", &configAI); err != nil { return mcp_golang.NewToolResponse(mcp_golang.NewTextContent(fmt.Sprintf("Failed to load AI configuration: %v", err))), nil } - // Use stored configuration if not specified in request if request.Backend == "" { if configAI.DefaultProvider != "" { @@ -182,6 +180,7 @@ func (s *MCPServer) handleAnalyze(ctx context.Context, request *AnalyzeRequest) } } + request.Explain = true // Get stored filters if not specified if len(request.Filters) == 0 { request.Filters = viper.GetStringSlice("active_filters") @@ -221,8 +220,14 @@ func (s *MCPServer) handleAnalyze(ctx context.Context, request *AnalyzeRequest) // handleClusterInfo handles the cluster-info tool func (s *MCPServer) handleClusterInfo(ctx context.Context, request *ClusterInfoRequest) (*mcp_golang.ToolResponse, error) { + // Create a new Kubernetes client + client, err := kubernetes.NewClient("", "") + if err != nil { + return mcp_golang.NewToolResponse(mcp_golang.NewTextContent(fmt.Sprintf("failed to create Kubernetes client: %v", err))), nil + } + // Get cluster info from the client - version, err := s.analysis.Client.Client.Discovery().ServerVersion() + version, err := client.Client.Discovery().ServerVersion() if err != nil { return mcp_golang.NewToolResponse(mcp_golang.NewTextContent(fmt.Sprintf("failed to get cluster version: %v", err))), nil } @@ -309,8 +314,14 @@ func (s *MCPServer) registerResources() error { } func (s *MCPServer) getClusterInfo(ctx context.Context) (interface{}, error) { + // Create a new Kubernetes client + client, err := kubernetes.NewClient("", "") + if err != nil { + return nil, fmt.Errorf("failed to create Kubernetes client: %v", err) + } + // Get cluster info from the client - version, err := s.analysis.Client.Client.Discovery().ServerVersion() + version, err := client.Client.Discovery().ServerVersion() if err != nil { return nil, fmt.Errorf("failed to get cluster version: %v", err) } @@ -322,10 +333,63 @@ func (s *MCPServer) getClusterInfo(ctx context.Context) (interface{}, error) { }, nil } +// handleSSE handles Server-Sent Events for MCP +func (s *MCPServer) handleSSE(w http.ResponseWriter, r *http.Request) { + // Set headers for SSE + w.Header().Set("Content-Type", "text/event-stream") + w.Header().Set("Cache-Control", "no-cache") + w.Header().Set("Connection", "keep-alive") + w.Header().Set("Access-Control-Allow-Origin", "*") + + // Create a channel to receive messages + msgChan := make(chan string) + defer close(msgChan) + + // Start a goroutine to handle the stdio transport + go func() { + // TODO: Implement message handling between HTTP and stdio transport + // This would require implementing a custom transport that bridges HTTP and stdio + }() + + // Send messages to the client + for msg := range msgChan { + fmt.Fprintf(w, "data: %s\n\n", msg) + w.(http.Flusher).Flush() + } +} + +// handleAnalyzeHTTP handles HTTP requests for the analyze endpoint +func (s *MCPServer) handleAnalyzeHTTP(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + // Parse the request body + var req AnalyzeRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, fmt.Sprintf("Failed to decode request: %v", err), http.StatusBadRequest) + return + } + + // Call the analyze handler + resp, err := s.handleAnalyze(r.Context(), &req) + if err != nil { + http.Error(w, fmt.Sprintf("Failed to analyze: %v", err), http.StatusInternalServerError) + return + } + + // Set response headers + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + // Write the response + if err := json.NewEncoder(w).Encode(resp); err != nil { + s.logger.Error("Failed to encode response", zap.Error(err)) + } +} + // Close closes the MCP server and releases resources func (s *MCPServer) Close() error { - if s.analysis != nil { - s.analysis.Close() - } return nil }