|
| 1 | +package main |
| 2 | + |
| 3 | +import ( |
| 4 | + "fmt" |
| 5 | + "net/http" |
| 6 | + "os" |
| 7 | + "time" |
| 8 | + |
| 9 | + mcpv1alpha1 "github.com/Kuadrant/mcp-gateway/api/v1alpha1" |
| 10 | + "github.com/Kuadrant/mcp-gateway/internal/broker" |
| 11 | + "github.com/mark3labs/mcp-go/server" |
| 12 | + "go.opentelemetry.io/otel" |
| 13 | + "go.opentelemetry.io/otel/propagation" |
| 14 | +) |
| 15 | + |
| 16 | +func (a *app) createBroker() { |
| 17 | + invalidToolPolicy := mcpv1alpha1.InvalidToolPolicy(a.brokerCfg.invalidToolPolicy) |
| 18 | + if invalidToolPolicy != mcpv1alpha1.InvalidToolPolicyFilterOut && invalidToolPolicy != mcpv1alpha1.InvalidToolPolicyRejectServer { |
| 19 | + panic("--invalid-tool-policy must be FilterOut or RejectServer") |
| 20 | + } |
| 21 | + |
| 22 | + managerTickerInterval := time.Duration(a.brokerCfg.managerTickerIntervalSecs) * time.Second |
| 23 | + if managerTickerInterval <= 0 { |
| 24 | + panic("flag mcp-check-interval cannot be 0 or less seconds") |
| 25 | + } |
| 26 | + |
| 27 | + a.mcpBroker = broker.NewBroker(a.logger.With("component", "broker"), |
| 28 | + broker.WithEnforceCapabilityFilter(a.brokerCfg.enforceCapabilityFiltering), |
| 29 | + broker.WithTrustedHeadersPublicKey(os.Getenv("TRUSTED_HEADER_PUBLIC_KEY")), |
| 30 | + broker.WithManagerTickerInterval(managerTickerInterval), |
| 31 | + broker.WithInvalidToolPolicy(invalidToolPolicy), |
| 32 | + broker.WithElicitationEnabled(a.brokerCfg.enableURLElicitation), |
| 33 | + broker.WithDiscoveryToolsEnabled(a.brokerCfg.discoveryToolsEnabled), |
| 34 | + broker.WithDiscoveryToolThreshold(a.brokerCfg.discoveryToolThreshold), |
| 35 | + broker.WithSessionCache(a.sessionCache), |
| 36 | + ) |
| 37 | + a.tokenHandler = broker.NewTokenHandler(a.sessionCache, a.tokenElicitMap, *a.logger) |
| 38 | + a.elicitHandler = &broker.ElicitationHandler{ |
| 39 | + ElicitationMap: a.tokenElicitMap, |
| 40 | + Config: a.mcpConfig, |
| 41 | + } |
| 42 | + a.setUpHTTPServer() |
| 43 | +} |
| 44 | + |
| 45 | +func (a *app) setUpHTTPServer() { |
| 46 | + cfg := &a.brokerCfg |
| 47 | + mux := http.NewServeMux() |
| 48 | + |
| 49 | + mux.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) { |
| 50 | + _, _ = fmt.Fprint(w, "Hello, World! BTW, the MCP server is on /mcp") |
| 51 | + }) |
| 52 | + |
| 53 | + mux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) { |
| 54 | + w.WriteHeader(http.StatusOK) |
| 55 | + }) |
| 56 | + |
| 57 | + mux.HandleFunc("/readyz", func(w http.ResponseWriter, _ *http.Request) { |
| 58 | + if !a.mcpBroker.IsReady() { |
| 59 | + w.WriteHeader(http.StatusServiceUnavailable) |
| 60 | + return |
| 61 | + } |
| 62 | + w.WriteHeader(http.StatusOK) |
| 63 | + }) |
| 64 | + |
| 65 | + oauthHandler := broker.ProtectedResourceHandler{Logger: a.logger} |
| 66 | + mux.HandleFunc("/.well-known/oauth-protected-resource", oauthHandler.Handle) |
| 67 | + mux.HandleFunc("/.well-known/oauth-protected-resource/", oauthHandler.Handle) |
| 68 | + |
| 69 | + // WriteTimeout of 0 (disabled) is important for SSE connections (GET /mcp). |
| 70 | + // SSE streams notifications indefinitely - any write timeout would kill the connection. |
| 71 | + writeTimeout := time.Duration(cfg.writeTimeoutSecs) * time.Second |
| 72 | + |
| 73 | + httpSrv := &http.Server{ |
| 74 | + Addr: cfg.addr, |
| 75 | + Handler: mux, |
| 76 | + ReadTimeout: 5 * time.Second, |
| 77 | + WriteTimeout: writeTimeout, |
| 78 | + } |
| 79 | + |
| 80 | + streamableHTTPOpts := []server.StreamableHTTPOption{ |
| 81 | + server.WithStreamableHTTPServer(httpSrv), |
| 82 | + } |
| 83 | + if a.jwtMgr != nil { |
| 84 | + streamableHTTPOpts = append(streamableHTTPOpts, server.WithSessionIdManager(a.jwtMgr)) |
| 85 | + } |
| 86 | + streamableHTTPServer := server.NewStreamableHTTPServer(a.mcpBroker.MCPServer(), streamableHTTPOpts...) |
| 87 | + |
| 88 | + mux.HandleFunc("OPTIONS /mcp", func(w http.ResponseWriter, r *http.Request) { |
| 89 | + a.logger.Debug("Handling OPTIONS", "Mcp-Session-Id", r.Header.Get("Mcp-Session-Id")) |
| 90 | + w.WriteHeader(http.StatusOK) |
| 91 | + }) |
| 92 | + |
| 93 | + mux.HandleFunc("/status", a.mcpBroker.HandleStatusRequest) |
| 94 | + mux.HandleFunc("/status/", a.mcpBroker.HandleStatusRequest) |
| 95 | + if cfg.enableURLElicitation { |
| 96 | + mux.Handle("/tokens", a.tokenHandler) |
| 97 | + mux.Handle("/mcp/elicitation", a.elicitHandler) |
| 98 | + } |
| 99 | + mux.Handle("/mcp", traceContextMiddleware(streamableHTTPServer)) |
| 100 | + |
| 101 | + a.brokerServer = httpSrv |
| 102 | + a.mcpServer = streamableHTTPServer |
| 103 | +} |
| 104 | + |
| 105 | +func traceContextMiddleware(next http.Handler) http.Handler { |
| 106 | + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 107 | + ctx := otel.GetTextMapPropagator().Extract(r.Context(), propagation.HeaderCarrier(r.Header)) |
| 108 | + next.ServeHTTP(w, r.WithContext(ctx)) |
| 109 | + }) |
| 110 | +} |
0 commit comments