Skip to main content
Glama

MCPJungle mcp gateway

by mcpjungle
Mozilla Public License 2.0
638
  • Apple
server.go•8.97 kB
// Package api provides HTTP API functionality for the MCPJungle server. package api import ( "fmt" "net/http" "sync" "github.com/gin-gonic/gin" "github.com/mark3labs/mcp-go/server" "github.com/mcpjungle/mcpjungle/internal/model" "github.com/mcpjungle/mcpjungle/internal/service/config" "github.com/mcpjungle/mcpjungle/internal/service/mcp" "github.com/mcpjungle/mcpjungle/internal/service/mcpclient" "github.com/mcpjungle/mcpjungle/internal/service/toolgroup" "github.com/mcpjungle/mcpjungle/internal/service/user" "github.com/mcpjungle/mcpjungle/internal/telemetry" "github.com/mcpjungle/mcpjungle/pkg/types" "github.com/mcpjungle/mcpjungle/pkg/version" "github.com/prometheus/client_golang/prometheus/promhttp" "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" ) const ( V0PathPrefix = "/v0" V0ApiPathPrefix = "/api" + V0PathPrefix ) type ServerOptions struct { // Port is the HTTP ports to bind the server to Port string // MCPProxyServer is the MCP proxy server instance that contains tools for all MCP servers // using the stdio or streamable http transport. MCPProxyServer *server.MCPServer // SseMcpProxyServer is the MCP proxy server instance that contains tools for all MCP servers // using the SSE transport. // sse tools are kept separate because SSE is supported for backward compatibility reasons, and // we don't want it to interfere with the usual mcp proxy server. // Both sse & streamable http use http, and we don't want to mix them up either. SseMcpProxyServer *server.MCPServer MCPService *mcp.MCPService MCPClientService *mcpclient.McpClientService ConfigService *config.ServerConfigService UserService *user.UserService ToolGroupService *toolgroup.ToolGroupService OtelProviders *telemetry.Providers Metrics telemetry.CustomMetrics } // Server represents the MCPJungle registry server that handles MCP proxy and API requests type Server struct { port string router *gin.Engine mcpProxyServer *server.MCPServer sseMcpProxyServer *server.MCPServer mcpService *mcp.MCPService mcpClientService *mcpclient.McpClientService configService *config.ServerConfigService userService *user.UserService toolGroupService *toolgroup.ToolGroupService otelProviders *telemetry.Providers metrics telemetry.CustomMetrics // groupMcpServers keeps track of mcp-go's server.SSEServer instances created for each tool group. // These instances serve the requests made to tool groups' SSE tools. // We need to maintain one instance for each group for sse to work correctly. groupSseServers sync.Map } // NewServer initializes a new Gin server for MCPJungle registry and MCP proxy func NewServer(opts *ServerOptions) (*Server, error) { s := &Server{ port: opts.Port, mcpProxyServer: opts.MCPProxyServer, sseMcpProxyServer: opts.SseMcpProxyServer, mcpService: opts.MCPService, mcpClientService: opts.MCPClientService, configService: opts.ConfigService, userService: opts.UserService, toolGroupService: opts.ToolGroupService, otelProviders: opts.OtelProviders, metrics: opts.Metrics, } // Set up the router after the server is fully initialized r, err := s.setupRouter() if err != nil { return nil, err } s.router = r return s, nil } // IsInitialized returns true if the server is initialized func (s *Server) IsInitialized() (bool, error) { c, err := s.configService.GetConfig() if err != nil { return false, fmt.Errorf("failed to get server config: %w", err) } return c.Initialized, nil } // GetMode returns the server mode if the server is initialized, otherwise an error func (s *Server) GetMode() (model.ServerMode, error) { ok, err := s.IsInitialized() if err != nil { return "", fmt.Errorf("failed to check if server is initialized: %w", err) } if !ok { return "", fmt.Errorf("server is not initialized") } c, err := s.configService.GetConfig() if err != nil { return "", fmt.Errorf("failed to get server config: %w", err) } return c.Mode, nil } // InitDev initializes the server configuration in the Development mode. // This method does not create an admin user because that is irrelevant in dev mode. func (s *Server) InitDev() error { _, err := s.configService.Init(model.ModeDev) if err != nil { return fmt.Errorf("failed to initialize server config in dev mode: %w", err) } return nil } // Start runs the Gin server (blocking call) func (s *Server) Start() error { if err := s.router.Run(":" + s.port); err != nil { return fmt.Errorf("failed to run the server: %w", err) } return nil } // setupRouter sets up the Gin router with the MCP proxy server and API endpoints. func (s *Server) setupRouter() (*gin.Engine, error) { gin.SetMode(gin.ReleaseMode) r := gin.Default() // if otel is enabled, setup prometheus metrics endpoint if s.otelProviders != nil && s.otelProviders.IsEnabled() { // instrument gin r.Use(otelgin.Middleware(s.otelProviders.ServiceName())) // expose prometheus metrics endpoint r.GET("/metrics", gin.WrapH(promhttp.Handler())) } r.GET( "/health", func(c *gin.Context) { c.JSON(200, gin.H{"status": "ok"}) }, ) r.GET( "/metadata", func(c *gin.Context) { m := &types.ServerMetadata{ Version: version.GetVersion(), } c.JSON(http.StatusOK, m) }, ) r.POST("/init", s.registerInitServerHandler()) requireEnterpriseMode := s.requireServerMode(model.ModeEnterprise) // Set up the MCP proxy server on /mcp streamableHTTPServer := server.NewStreamableHTTPServer(s.mcpProxyServer) r.Any( "/mcp", s.requireInitialized(), s.checkAuthForMcpProxyAccess(), gin.WrapH(streamableHTTPServer), ) r.Any( V0PathPrefix+"/groups/:name/mcp", s.requireInitialized(), s.checkAuthForMcpProxyAccess(), s.toolGroupMCPServerCallHandler(), ) // Set up the SSE transport-based MCP proxy server for the global /sse endpoint sseServer := server.NewSSEServer(s.sseMcpProxyServer) r.Any( "/sse", s.requireInitialized(), s.checkAuthForMcpProxyAccess(), gin.WrapH(sseServer.SSEHandler()), ) r.Any( "/message", s.requireInitialized(), s.checkAuthForMcpProxyAccess(), gin.WrapH(sseServer.MessageHandler()), ) r.Any( V0PathPrefix+"/groups/:name/sse", s.requireInitialized(), s.checkAuthForMcpProxyAccess(), s.toolGroupSseMCPServerCallHandler(), ) r.Any( V0PathPrefix+"/groups/:name/message", s.requireInitialized(), s.checkAuthForMcpProxyAccess(), s.toolGroupSseMCPServerCallMessageHandler(), ) // Setup /v0 API endpoints apiV0 := r.Group( V0ApiPathPrefix, s.requireInitialized(), s.verifyUserAuthForAPIAccess(), ) // endpoints accessible by a standard user in enterprise mode or anyone in development mode userAPI := apiV0.Group("/") { userAPI.GET("/servers", s.listServersHandler()) userAPI.GET("/tools", s.listToolsHandler()) userAPI.POST("/tools/invoke", s.invokeToolHandler()) userAPI.GET("/tool", s.getToolHandler()) // Prompt endpoints userAPI.GET("/prompts", s.listPromptsHandler()) userAPI.GET("/prompt", s.getPromptHandler()) userAPI.POST("/prompts/render", s.getPromptWithArgsHandler()) userAPI.GET("/users/whoami", requireEnterpriseMode, s.whoAmIHandler()) } // endpoints only accessible by an admin user in enterprise mode or anyone in development mode adminAPI := apiV0.Group("/", s.requireAdminUser()) { adminAPI.POST("/servers", s.registerServerHandler()) adminAPI.DELETE("/servers/:name", s.deregisterServerHandler()) adminAPI.POST("/servers/:name/enable", s.enableServerHandler()) adminAPI.POST("/servers/:name/disable", s.disableServerHandler()) adminAPI.POST("/tools/enable", s.enableToolsHandler()) adminAPI.POST("/tools/disable", s.disableToolsHandler()) adminAPI.POST("/prompts/enable", s.enablePromptsHandler()) adminAPI.POST("/prompts/disable", s.disablePromptsHandler()) // endpoints for managing MCP clients (enterprise mode only) adminAPI.GET( "/clients", requireEnterpriseMode, s.listMcpClientsHandler(), ) adminAPI.POST( "/clients", requireEnterpriseMode, s.createMcpClientHandler(), ) adminAPI.DELETE( "/clients/:name", requireEnterpriseMode, s.deleteMcpClientHandler(), ) // endpoints for managing human users (enterprise mode only) adminAPI.POST("/users", requireEnterpriseMode, s.createUserHandler(), ) adminAPI.GET("/users", requireEnterpriseMode, s.listUsersHandler(), ) adminAPI.DELETE("/users/:username", requireEnterpriseMode, s.deleteUserHandler(), ) // endpoints for managing tool groups adminAPI.POST("/tool-groups", s.createToolGroupHandler()) adminAPI.GET("/tool-groups/:name", s.getToolGroupHandler()) adminAPI.GET("/tool-groups", s.listToolGroupsHandler()) adminAPI.DELETE("/tool-groups/:name", s.deleteToolGroupHandler()) adminAPI.PUT("/tool-groups/:name", s.updateToolGroupHandler()) } return r, nil }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mcpjungle/MCPJungle'

If you have feedback or need assistance with the MCP directory API, please join our Discord server