Skip to content

Commit b032433

Browse files
authored
Merge pull request #361 from maleck13/session-store
Session mgmt revamp
2 parents 142257c + 52e9532 commit b032433

35 files changed

+1782
-466
lines changed

Makefile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,12 @@ deploy-broker: install-crd ## Deploy only the broker/router (without controller)
108108
kubectl apply -f config/mcp-system/deployment-broker.yaml
109109
kubectl apply -k config/mcp-system/ --dry-run=client -o yaml | kubectl apply -f - -l app=mcp-gateway
110110

111+
.PHONY: configure-redis
112+
configure-redis: ## patch deployment with redis connection
113+
kubectl apply -f config/mcp-system/redis-deployment.yaml
114+
kubectl apply -f config/mcp-system/redis-service.yaml
115+
kubectl patch deployment mcp-broker-router -n mcp-system --patch-file config/mcp-system/deployment-controller-redis-patch.yaml
116+
111117
# Deploy only the controller
112118
deploy-controller: install-crd ## Deploy only the controller
113119
kubectl apply -f config/mcp-system/namespace.yaml
@@ -148,6 +154,7 @@ deploy-example: install-crd ## Deploy example MCPServer resource
148154
@kubectl wait --for=condition=ready pod -n mcp-test -l app=mcp-custom-path-server --timeout=60s 2>/dev/null || true
149155
@kubectl wait --for=condition=ready pod -n mcp-test -l app=mcp-oidc-server --timeout=60s
150156
@kubectl wait --for=condition=ready pod -n mcp-test -l app=everything-server --timeout=60s
157+
@kubectl wait --for=condition=ready pod -n mcp-test -l app=mcp-custom-response --timeout=60s
151158
@echo "All test servers ready, deploying MCPServer resources..."
152159
kubectl apply -f config/samples/mcpserver-test-servers.yaml
153160
@echo "Waiting for controller to process MCPServer..."
@@ -167,6 +174,8 @@ build-test-servers: ## Build test server Docker images locally
167174
cd tests/servers/custom-path-server && $(CONTAINER_ENGINE) build $(CONTAINER_ENGINE_EXTRA_FLAGS) -t ghcr.io/kagenti/mcp-gateway/test-custom-path-server:latest .
168175
cd tests/servers/oidc-server && $(CONTAINER_ENGINE) build $(CONTAINER_ENGINE_EXTRA_FLAGS) -t ghcr.io/kagenti/mcp-gateway/test-oidc-server:latest .
169176
cd tests/servers/everything-server && $(CONTAINER_ENGINE) build $(CONTAINER_ENGINE_EXTRA_FLAGS) -t ghcr.io/kagenti/mcp-gateway/test-everything-server:latest .
177+
cd tests/servers/custom-response-server && $(CONTAINER_ENGINE) build $(CONTAINER_ENGINE_EXTRA_FLAGS) -t ghcr.io/kagenti/mcp-gateway/custom-response-server:latest .
178+
170179

171180
# Load test server images into Kind cluster
172181
kind-load-test-servers: kind build-test-servers ## Load test server images into Kind cluster
@@ -179,6 +188,7 @@ kind-load-test-servers: kind build-test-servers ## Load test server images into
179188
$(call load-image,ghcr.io/kagenti/mcp-gateway/test-custom-path-server:latest)
180189
$(call load-image,ghcr.io/kagenti/mcp-gateway/test-oidc-server:latest)
181190
$(call load-image,ghcr.io/kagenti/mcp-gateway/test-everything-server:latest)
191+
$(call load-image,ghcr.io/kagenti/mcp-gateway/custom-response-server:latest)
182192

183193
# Deploy test servers
184194
deploy-test-servers: kind-load-test-servers ## Deploy test MCP servers for local testing

cmd/mcp-broker-router/main.go

Lines changed: 72 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
extProcV3 "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3"
1919
"github.com/fsnotify/fsnotify"
2020
"github.com/kagenti/mcp-gateway/internal/broker"
21+
"github.com/kagenti/mcp-gateway/internal/clients"
2122
config "github.com/kagenti/mcp-gateway/internal/config"
2223
mcpRouter "github.com/kagenti/mcp-gateway/internal/mcp-router"
2324
"github.com/kagenti/mcp-gateway/internal/session"
@@ -49,20 +50,25 @@ func init() {
4950
_ = gatewayv1.Install(scheme)
5051
}
5152

53+
var (
54+
mcpRouterAddrFlag string
55+
mcpBrokerAddrFlag string
56+
mcpConfigAddrFlag string
57+
mcpRoutePublicHost string
58+
mcpRoutePrivateHost string
59+
mcpRouterKey string
60+
cacheConnectionStringFlag string
61+
mcpConfigFile string
62+
jwtSigningKeyFlag string
63+
sessionDurationInMins int64
64+
loglevel int
65+
logFormat string
66+
controllerMode bool
67+
enforceToolFilteringFlag bool
68+
)
69+
5270
func main() {
53-
var (
54-
mcpRouterAddrFlag string
55-
mcpBrokerAddrFlag string
56-
mcpConfigAddrFlag string
57-
mcpRoutePublicHost string
58-
mcpConfigFile string
59-
jwtSigningKeyFlag string
60-
sessionDurationInHours int64
61-
loglevel int
62-
logFormat string
63-
controllerMode bool
64-
enforceToolFilteringFlag bool
65-
)
71+
6672
flag.StringVar(
6773
&mcpRouterAddrFlag,
6874
"mcp-router-address",
@@ -81,6 +87,20 @@ func main() {
8187
"",
8288
"The public host the MCP Gateway is exposing MCP servers on. The gateway router will always set the :authority header to this value to ensure the broker component cannot be bypassed.",
8389
)
90+
flag.StringVar(
91+
&mcpRoutePrivateHost,
92+
"mcp-gateway-private-host",
93+
"mcp-gateway-istio.gateway-system.svc.cluster.local:8080",
94+
"The private host the MCP Gateway. The gateway router will use this to hairpin request to initialize MCP servers etc.",
95+
)
96+
97+
// TODO ick not sure how to describe this
98+
flag.StringVar(
99+
&mcpRouterKey,
100+
"mcp-router-key",
101+
goenv.GetDefault("MCP_ROUTER_API_KEY", "secret-api-key"),
102+
"this key is used to allow the router to send request through the gateway and be trusted by the router",
103+
)
84104
flag.StringVar(
85105
&mcpConfigAddrFlag,
86106
"mcp-broker-config-address",
@@ -99,9 +119,20 @@ func main() {
99119
int(slog.LevelInfo),
100120
"set the log level 0=info, 4=warn , 8=error and -4=debug",
101121
)
122+
flag.StringVar(&jwtSigningKeyFlag,
123+
"session-signing-key",
124+
goenv.GetDefault("JWT_SESSION_SIGNING_KEY", defaultJWTSigningKey),
125+
"JWT signing key for session tokens (env: JWT_SESSION_SIGNING_KEY)",
126+
)
127+
//"redis://redis.mcp-system.svc.cluster.local:6379
128+
flag.StringVar(&cacheConnectionStringFlag,
129+
"cache-connection-string",
130+
goenv.GetDefault("CACHE_CONNECTION_STRING", ""),
131+
"redis based cache connection string redis://<user>:<pass>@localhost:6379/<db> (env: CACHE_CONNECTION_STRING). If not set defaults to in memory storage",
132+
)
102133
flag.StringVar(&logFormat, "log-format", "txt", "switch to json logs with --log-format=json")
103-
flag.StringVar(&jwtSigningKeyFlag, "session-signing-key", goenv.GetDefault("JWT_SESSION_SIGNING_KEY", defaultJWTSigningKey), "JWT signing key for session tokens (env: JWT_SESSION_SIGNING_KEY)")
104-
flag.Int64Var(&sessionDurationInHours, "session-length", 24, "default session length with the gateway in hours")
134+
135+
flag.Int64Var(&sessionDurationInMins, "session-length", 60*24, "default session length with the gateway in minutes. Default 24h")
105136
flag.BoolVar(&controllerMode, "controller", false, "Run in controller mode")
106137
flag.BoolVar(&enforceToolFilteringFlag, "enforce-tool-filtering", false, "when enabled an x-authorized-tools header will be needed to return any tools")
107138
flag.Parse()
@@ -140,6 +171,19 @@ func main() {
140171
}
141172

142173
ctx := context.Background()
174+
175+
sessionCache, err := session.NewCache()
176+
if err != nil {
177+
panic("failed to setup session cache" + err.Error())
178+
}
179+
if cacheConnectionStringFlag != "" {
180+
logger.Info("session cache using external store")
181+
sessionCache, err = session.NewCache(session.WithConnectionString(cacheConnectionStringFlag))
182+
if err != nil {
183+
panic("failed to setup session cache" + err.Error())
184+
}
185+
}
186+
143187
var jwtSessionMgr *session.JWTManager
144188
if jwtSigningKeyFlag == "" {
145189
panic("jwt session signing key is empty. Cannot proceed")
@@ -148,22 +192,24 @@ func main() {
148192
logger.Warn("jwt session signing key is set to the default value. This is not recommended for production")
149193
}
150194

151-
jwtmgr, err := session.NewJWTManager(jwtSigningKeyFlag, sessionDurationInHours, logger)
195+
jwtmgr, err := session.NewJWTManager(jwtSigningKeyFlag, sessionDurationInMins, logger, sessionCache)
152196
if err != nil {
153197
panic("failed to setup jwt manager " + err.Error())
154198
}
155199
jwtSessionMgr = jwtmgr
156200

157201
configServer := setUpConfigServer(mcpConfigAddrFlag)
158202
brokerServer, mcpBroker, mcpServer := setUpBroker(mcpBrokerAddrFlag, enforceToolFilteringFlag, jwtSessionMgr)
159-
routerGRPCServer, router := setUpRouter(mcpBroker, logger, jwtSessionMgr)
203+
routerGRPCServer, router := setUpRouter(mcpBroker, logger, jwtSessionMgr, sessionCache)
160204
mcpConfig.RegisterObserver(router)
161205
mcpConfig.RegisterObserver(mcpBroker)
162206
if mcpRoutePublicHost == "" {
163207
panic("--mcp-gateway-public-host cannot be empty. The mcp gateway needs to be informed of what public host to expect requests from so it can ensure routing and session mgmt happens. Set --mcp-gateway-public-host")
164208
}
165209

166-
mcpConfig.MCPGatewayHostname = mcpRoutePublicHost
210+
mcpConfig.MCPGatewayExternalHostname = mcpRoutePublicHost
211+
mcpConfig.MCPGatewayInternalHostname = mcpRoutePrivateHost
212+
mcpConfig.RouterAPIKey = mcpRouterKey
167213

168214
// Only load config and run broker/router in standalone mode
169215
LoadConfig(mcpConfigFile)
@@ -296,20 +342,19 @@ func setUpConfigServer(address string) *http.Server {
296342
}
297343
}
298344

299-
func setUpRouter(broker broker.MCPBroker, logger *slog.Logger, jwtManager *session.JWTManager) (*grpc.Server, *mcpRouter.ExtProcServer) {
300-
grpcSrv := grpc.NewServer()
345+
func setUpRouter(broker broker.MCPBroker, logger *slog.Logger, jwtManager *session.JWTManager, sessionCache *session.Cache) (*grpc.Server, *mcpRouter.ExtProcServer) {
301346

347+
grpcSrv := grpc.NewServer()
302348
// Create the ExtProcServer instance
303349
server := &mcpRouter.ExtProcServer{
304350
RoutingConfig: mcpConfig,
305-
// TODO this seems wrong. Why does the router need to be passed an instance of the broker?
306-
Broker: broker,
307-
Logger: logger,
308-
JWTManager: jwtManager,
309-
}
351+
Logger: logger,
352+
JWTManager: jwtManager,
353+
InitForClient: clients.Initialize,
354+
SessionCache: sessionCache,
355+
Broker: broker, // TODO we shouldn't need a handle to broker in the router
310356

311-
// Setup the session cache with proper initialization
312-
server.SetupSessionCache()
357+
}
313358

314359
extProcV3.RegisterExternalProcessorServer(grpcSrv, server)
315360
return grpcSrv, server
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: mcp-broker-router
5+
namespace: mcp-system
6+
spec:
7+
template:
8+
spec:
9+
containers:
10+
- name: mcp-broker-router
11+
command:
12+
- ./mcp_gateway
13+
- --mcp-gateway-config=/config/config.yaml
14+
- --mcp-broker-public-address=0.0.0.0:8080
15+
- --mcp-broker-config-address=0.0.0.0:8181
16+
- --mcp-gateway-public-host=mcp.127-0-0-1.sslip.io
17+
- --mcp-router-address=0.0.0.0:50051
18+
- --log-level=-4
19+
- --cache-connection-string=redis://redis.mcp-system.svc.cluster.local:6379
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: redis
5+
namespace: mcp-system
6+
labels:
7+
app: redis
8+
component: cache
9+
spec:
10+
replicas: 1
11+
selector:
12+
matchLabels:
13+
app: redis
14+
component: cache
15+
template:
16+
metadata:
17+
labels:
18+
app: redis
19+
component: cache
20+
spec:
21+
containers:
22+
- name: redis
23+
image: redis:7-alpine
24+
imagePullPolicy: IfNotPresent
25+
ports:
26+
- name: redis
27+
containerPort: 6379
28+
protocol: TCP
29+
resources:
30+
requests:
31+
memory: '64Mi'
32+
cpu: '50m'
33+
limits:
34+
memory: '256Mi'
35+
cpu: '200m'
36+
livenessProbe:
37+
tcpSocket:
38+
port: 6379
39+
initialDelaySeconds: 10
40+
periodSeconds: 30
41+
readinessProbe:
42+
exec:
43+
command:
44+
- redis-cli
45+
- ping
46+
initialDelaySeconds: 5
47+
periodSeconds: 10
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
apiVersion: v1
2+
kind: Service
3+
metadata:
4+
name: redis
5+
namespace: mcp-system
6+
labels:
7+
app: redis
8+
component: cache
9+
spec:
10+
type: ClusterIP
11+
ports:
12+
- port: 6379
13+
targetPort: 6379
14+
protocol: TCP
15+
name: redis
16+
selector:
17+
app: redis
18+
component: cache

config/samples/mcpserver-test-servers.yaml

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ metadata:
55
name: test-server1
66
namespace: mcp-test
77
labels:
8-
"kagenti/mcp": "true"
8+
'kagenti/mcp': 'true'
99
spec:
1010
# Tool prefix applied to all servers in this MCPServer
1111
toolPrefix: test_
@@ -21,7 +21,7 @@ metadata:
2121
name: test-server2
2222
namespace: mcp-test
2323
labels:
24-
"kagenti/mcp": "true"
24+
'kagenti/mcp': 'true'
2525
spec:
2626
toolPrefix: test2_
2727
targetRef:
@@ -36,7 +36,7 @@ metadata:
3636
name: test-server3
3737
namespace: mcp-test
3838
labels:
39-
"kagenti/mcp": "true"
39+
'kagenti/mcp': 'true'
4040
spec:
4141
toolPrefix: test3_
4242
targetRef:
@@ -51,7 +51,7 @@ metadata:
5151
name: api-key-server
5252
namespace: mcp-test
5353
labels:
54-
"kagenti/mcp": "true"
54+
'kagenti/mcp': 'true'
5555
spec:
5656
toolPrefix: apikey_
5757
targetRef:
@@ -69,7 +69,7 @@ metadata:
6969
name: custom-path-server
7070
namespace: mcp-test
7171
labels:
72-
"kagenti/mcp": "true"
72+
'kagenti/mcp': 'true'
7373
spec:
7474
# custom path - this server uses /v1/special/mcp instead of /mcp
7575
path: /v1/special/mcp
@@ -86,7 +86,7 @@ metadata:
8686
name: oidc-server
8787
namespace: mcp-test
8888
labels:
89-
"kagenti/mcp": "true"
89+
'kagenti/mcp': 'true'
9090
spec:
9191
# Validates OpenID Connect (OIDC) Bearer tokens issued by a configured OIDC provider
9292
toolPrefix: oidc_
@@ -104,11 +104,26 @@ metadata:
104104
name: everything-server
105105
namespace: mcp-test
106106
labels:
107-
"kagenti/mcp": "true"
107+
'kagenti/mcp': 'true'
108108
spec:
109109
toolPrefix: everything_
110110
targetRef:
111111
# MCP Everything Server - Typescript SDK based (prompts, tools, resources, sampling)
112112
group: gateway.networking.k8s.io
113113
kind: HTTPRoute
114114
name: everything-server-route
115+
---
116+
apiVersion: mcp.kagenti.com/v1alpha1
117+
kind: MCPServer
118+
metadata:
119+
name: custom-response
120+
namespace: mcp-test
121+
labels:
122+
'kagenti/mcp': 'true'
123+
spec:
124+
toolPrefix: custom_resp_
125+
targetRef:
126+
# MCP Everything Server - Typescript SDK based (prompts, tools, resources, sampling)
127+
group: gateway.networking.k8s.io
128+
kind: HTTPRoute
129+
name: mcp-custom-response-route

0 commit comments

Comments
 (0)