Skip to main content
Glama

CentralMind/Gateway

converter.go12.2 kB
package swaggerator import ( "context" "embed" "fmt" "io/fs" "net/http" "path" "strings" "github.com/centralmind/gateway/connectors" "github.com/sirupsen/logrus" "golang.org/x/xerrors" "github.com/centralmind/gateway/model" "github.com/centralmind/gateway/plugins" "github.com/danielgtaylor/huma/v2" ) //go:embed dist var swagfs embed.FS // Schema dynamically generates an OpenAPI 3.1 schema based on the given table schema. func Schema(schema model.Config, prefix string, addresses ...string) (*huma.OpenAPI, error) { api := huma.DefaultConfig(schema.API.Name, "3.1.0").OpenAPI api.Info.Title = schema.API.Name api.Info.Description = "Config that dynamically generates accessor for data" api.Info.Version = schema.API.Version connector, err := connectors.New(schema.Database.Type, schema.Database.Connection) if err != nil { return nil, xerrors.Errorf("unable to init connector: %w", err) } // Add all server addresses for i, address := range addresses { var description string switch { case i == 0 && strings.HasPrefix(address, "http://localhost"): description = "Local development server" case strings.Contains(address, "dev"): description = "Development server" case strings.Contains(address, "stage"): description = "Staging server" case strings.Contains(address, "prod"): description = "Production server" default: description = "Server " + address } api.Servers = append(api.Servers, &huma.Server{ URL: address, Description: description, }) } // If no servers were provided, add a default one if len(api.Servers) == 0 { api.Servers = append(api.Servers, &huma.Server{ URL: "http://localhost:9090", Description: "localhost", }) } // Iterate through tables and generate OpenAPI schemas allEndpoints := schema.Database.GetAllEndpoints() for _, endpoint := range allEndpoints { cols, err := connector.InferQuery(context.Background(), endpoint.Query) if err != nil { logrus.Warnf("unable to infer query %s: %v", endpoint.Query, err) } schemaProps := map[string]*huma.Schema{} for _, col := range cols { schemaProps[col.Name] = &huma.Schema{ Type: string(col.Type), } if col.Type == model.TypeDatetime { schemaProps[col.Name] = &huma.Schema{ Type: "string", Format: "date-time", } } } var params []*huma.Param bodyProps := map[string]*huma.Schema{} for _, param := range endpoint.Params { if param.Location == "" { param.Location = "query" } if param.Location == "body" { bodyProps[param.Name] = &huma.Schema{ Type: param.Type, Format: param.Format, Default: param.Default, } continue } params = append(params, &huma.Param{ Name: param.Name, In: param.Location, Required: param.Required, Schema: &huma.Schema{ Type: param.Type, Format: param.Format, Default: param.Default, }, }) } if len(bodyProps) > 0 { params = append(params, &huma.Param{ Name: "body", In: "body", Required: true, Schema: &huma.Schema{ Type: "object", Properties: bodyProps, }, }) } resSchema := &huma.Schema{ Type: "object", Properties: schemaProps, } if endpoint.IsArrayResult { resSchema = &huma.Schema{ Type: "array", Items: resSchema, } } operation := &huma.Operation{ Summary: endpoint.Summary, Description: endpoint.Description, OperationID: endpoint.MCPMethod, Tags: []string{endpoint.Group}, Parameters: params, Responses: map[string]*huma.Response{ "200": { Description: "Success", Content: map[string]*huma.MediaType{ "application/json": { Schema: resSchema, }, }, }, "404": { Description: "Not Found", Content: map[string]*huma.MediaType{ "application/json": { Schema: &huma.Schema{ Type: "object", Properties: map[string]*huma.Schema{ "error": {Type: "string"}, }, }, }, }, }, "500": { Description: "Error", Content: map[string]*huma.MediaType{ "application/json": { Schema: &huma.Schema{ Type: "object", Properties: map[string]*huma.Schema{ "error": {Type: "string"}, }, }, }, }, }, }, } httpPath := endpoint.HTTPPath if prefix != "" { httpPath = path.Join("/", prefix, httpPath) } if api.Paths == nil { api.Paths = make(map[string]*huma.PathItem) } if _, ok := api.Paths[httpPath]; !ok { api.Paths[httpPath] = &huma.PathItem{} } switch endpoint.HTTPMethod { case "GET": api.Paths[httpPath].Get = operation case "DELETE": api.Paths[httpPath].Delete = operation case "POST": api.Paths[httpPath].Post = operation case "PATCH": api.Paths[httpPath].Patch = operation case "PUT": api.Paths[httpPath].Put = operation } } api, err = plugins.Enrich(schema.Plugins, api) if err != nil { return nil, xerrors.Errorf("unable to enrich swagger schema: %w", err) } return api, nil } // AddRawEndpoints adds Raw API endpoints to an existing OpenAPI schema func AddRawEndpoints(api *huma.OpenAPI, schema model.Config, prefix string) (*huma.OpenAPI, error) { // Define Raw API endpoints rawPath := "/raw" if prefix != "" { rawPath = path.Join("/", prefix, "raw") } // List Tables endpoint listTablesOperation := &huma.Operation{ Summary: "List available tables", Description: fmt.Sprintf("Return list of tables that available for data in %s database", schema.Database.Type), OperationID: "list_tables", Tags: []string{"Raw"}, Responses: map[string]*huma.Response{ "200": { Description: "Success", Content: map[string]*huma.MediaType{ "application/json": { Schema: &huma.Schema{ Type: "array", Items: &huma.Schema{ Type: "object", Properties: map[string]*huma.Schema{ "name": {Type: "string"}, "columns": { Type: "array", Items: &huma.Schema{ Type: "object", Properties: map[string]*huma.Schema{ "name": {Type: "string"}, "type": {Type: "string"}, }, }, }, "row_count": {Type: "integer"}, }, }, }, }, }, }, "500": { Description: "Error", Content: map[string]*huma.MediaType{ "application/json": { Schema: &huma.Schema{ Type: "object", Properties: map[string]*huma.Schema{ "error": {Type: "string"}, }, }, }, }, }, }, } // Discover Data endpoint discoverDataOperation := &huma.Operation{ Summary: "Discover data structure", Description: fmt.Sprintf("Discover data structure for connected %s gateway", schema.Database.Type), OperationID: "discover_data", Tags: []string{"Raw"}, Parameters: []*huma.Param{ { Name: "tables_list", In: "query", Required: false, Schema: &huma.Schema{ Type: "string", Description: "Comma separated table names to fetch data samples", }, }, }, Responses: map[string]*huma.Response{ "200": { Description: "Success", Content: map[string]*huma.MediaType{ "application/json": { Schema: &huma.Schema{ Type: "array", Items: &huma.Schema{ Type: "object", Properties: map[string]*huma.Schema{ "name": {Type: "string"}, "columns": { Type: "array", Items: &huma.Schema{ Type: "object", Properties: map[string]*huma.Schema{ "name": {Type: "string"}, "type": {Type: "string"}, }, }, }, "sample": { Type: "array", Items: &huma.Schema{ Type: "object", }, }, "row_count": {Type: "integer"}, }, }, }, }, }, }, "500": { Description: "Error", Content: map[string]*huma.MediaType{ "application/json": { Schema: &huma.Schema{ Type: "object", Properties: map[string]*huma.Schema{ "error": {Type: "string"}, }, }, }, }, }, }, } // Prepare Query endpoint prepareQueryOperation := &huma.Operation{ Summary: "Verify and prepare query", Description: fmt.Sprintf("Verify query and prepare output structure for query in %s database", schema.Database.Type), OperationID: "prepare_query", Tags: []string{"Raw"}, Parameters: []*huma.Param{ { Name: "query", In: "query", Required: true, Schema: &huma.Schema{ Type: "string", Description: "SQL query to verify", }, }, }, Responses: map[string]*huma.Response{ "200": { Description: "Success", Content: map[string]*huma.MediaType{ "application/json": { Schema: &huma.Schema{ Type: "array", Items: &huma.Schema{ Type: "object", Properties: map[string]*huma.Schema{ "name": {Type: "string"}, "type": {Type: "string"}, }, }, }, }, }, }, "500": { Description: "Error", Content: map[string]*huma.MediaType{ "application/json": { Schema: &huma.Schema{ Type: "object", Properties: map[string]*huma.Schema{ "error": {Type: "string"}, }, }, }, }, }, }, } // Query endpoint queryOperation := &huma.Operation{ Summary: "Execute query", Description: fmt.Sprintf("Query data structure for connected %s gateway", schema.Database.Type), OperationID: "query", Tags: []string{"Raw"}, Parameters: []*huma.Param{ { Name: "query", In: "query", Required: true, Schema: &huma.Schema{ Type: "string", Description: "SQL query to execute", }, }, }, Responses: map[string]*huma.Response{ "200": { Description: "Success", Content: map[string]*huma.MediaType{ "application/json": { Schema: &huma.Schema{ Type: "array", Items: &huma.Schema{ Type: "object", }, }, }, }, }, "500": { Description: "Error", Content: map[string]*huma.MediaType{ "application/json": { Schema: &huma.Schema{ Type: "object", Properties: map[string]*huma.Schema{ "error": {Type: "string"}, }, }, }, }, }, }, } // Add operations to paths if api.Paths == nil { api.Paths = make(map[string]*huma.PathItem) } api.Paths[path.Join(rawPath, "list_tables")] = &huma.PathItem{ Get: listTablesOperation, } api.Paths[path.Join(rawPath, "discover_data")] = &huma.PathItem{ Get: discoverDataOperation, } api.Paths[path.Join(rawPath, "prepare_query")] = &huma.PathItem{ Get: prepareQueryOperation, } api.Paths[path.Join(rawPath, "query")] = &huma.PathItem{ Get: queryOperation, } return api, nil } func byteHandler(b []byte) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Write(b) } } func RegisterRoute(mux *http.ServeMux, prefix string, spec []byte) { // render the index template with the proper spec name inserted static, err := fs.Sub(swagfs, "dist") if err != nil { logrus.Errorf("Failed to access embedded swagger files: %v", err) return } // Handle empty prefix properly var swaggerPath string if prefix == "" { swaggerPath = "/swagger" } else { swaggerPath = path.Join("/", prefix, "swagger") } apiJsonPath := path.Join(swaggerPath, "open_api.json") // Register the API JSON endpoint mux.HandleFunc(apiJsonPath, byteHandler(spec)) // Create a simple handler that redirects /swagger to /swagger/ rootHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == swaggerPath { http.Redirect(w, r, swaggerPath+"/", http.StatusMovedPermanently) return } http.StripPrefix(swaggerPath, http.FileServer(http.FS(static))).ServeHTTP(w, r) }) // Register handlers mux.Handle(swaggerPath, rootHandler) mux.Handle(swaggerPath+"/", http.StripPrefix(swaggerPath, http.FileServer(http.FS(static)))) }

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/centralmind/gateway'

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