Skip to main content
Glama
MIT License
20
config.go6.99 kB
package graphql import ( "fmt" "net/textproto" "os" "regexp" "github.com/spf13/viper" ) type SchemaPointer struct { URL string `yaml:"-"` // URL will be set programmatically Headers map[string]string `yaml:"headers,omitempty"` } type GraphQLProject struct { Schema []SchemaPointer `yaml:"schema"` Documents []string `yaml:"documents"` Extensions map[string]any `yaml:"extensions"` Include []string `yaml:"include"` Exclude []string `yaml:"exclude"` } type GraphQLProjects struct { Projects map[string]GraphQLProject `yaml:"projects"` } type GraphQLConfig struct { SingleProject *GraphQLProject `yaml:",inline"` MultiProjects *GraphQLProjects `yaml:",inline"` } // normalizeHeader returns the canonical format of the header key // This uses the standard library's textproto.CanonicalMIMEHeaderKey // which handles proper capitalization of HTTP headers func normalizeHeader(key string) string { return textproto.CanonicalMIMEHeaderKey(key) } // expandEnvVars replaces ${VAR}, $VAR, or ${VAR:-default} in a string with the value from the environment or a default func expandEnvVars(s string) string { re := regexp.MustCompile(`\$\{([A-Za-z0-9_]+)(:-([^}]*))?\}|\$([A-Za-z0-9_]+)`) // matches ${VAR}, ${VAR:-default}, or $VAR return re.ReplaceAllStringFunc(s, func(m string) string { if m[0] == '$' && len(m) > 1 && m[1] == '{' { // ${VAR} or ${VAR:-default} inner := m[2 : len(m)-1] if idx := regexp.MustCompile(`:-`).FindStringIndex(inner); idx != nil { varName := inner[:idx[0]] defaultVal := inner[idx[1]:] if val, ok := os.LookupEnv(varName); ok { return val } return defaultVal } else { varName := inner if val, ok := os.LookupEnv(varName); ok { return val } return m // leave as is if not found } } else if m[0] == '$' { // $VAR varName := m[1:] if val, ok := os.LookupEnv(varName); ok { return val } return m // leave as is if not found } return m }) } func LoadGraphQLConfig(path string) (*GraphQLConfig, error) { viper.SetConfigFile(path) viper.SetConfigType("yaml") if err := viper.ReadInConfig(); err != nil { return nil, fmt.Errorf("error reading config: %v", err) } // Create a new config object config := &GraphQLConfig{ SingleProject: &GraphQLProject{}, } // Parse schema configuration if err := parseSchema(config); err != nil { return nil, err } // Parse documents configuration if err := parseDocuments(config); err != nil { return nil, err } // Parse include configuration if err := parseInclude(config); err != nil { return nil, err } // Parse exclude configuration if err := parseExclude(config); err != nil { return nil, err } return config, nil } // parseSchema handles the schema configuration which can be either a string or an array func parseSchema(config *GraphQLConfig) error { schema := viper.Get("schema") if schema == nil { return nil } // Case 1: schema is a simple string URL if urlStr, ok := schema.(string); ok { config.SingleProject.Schema = append(config.SingleProject.Schema, SchemaPointer{ URL: expandEnvVars(urlStr), }) return nil } // Case 2: schema is an array schemaArr, ok := schema.([]interface{}) if !ok { return fmt.Errorf("schema must be a string URL or an array") } for _, item := range schemaArr { // Case 2.1: array item is a simple string URL if urlStr, ok := item.(string); ok { config.SingleProject.Schema = append(config.SingleProject.Schema, SchemaPointer{ URL: expandEnvVars(urlStr), }) continue } // Case 2.2: array item is a map with URL as key urlMap, ok := item.(map[string]interface{}) if !ok { continue // Skip invalid items } for url, value := range urlMap { schemaPtr := SchemaPointer{URL: expandEnvVars(url)} // Extract headers if they exist if valueMap, ok := value.(map[string]interface{}); ok { if headersMap, ok := valueMap["headers"].(map[string]interface{}); ok { schemaPtr.Headers = make(map[string]string) for key, val := range headersMap { if valStr, ok := val.(string); ok { // Use standard header normalization normalKey := normalizeHeader(key) // Expand env vars in header values schemaPtr.Headers[normalKey] = expandEnvVars(valStr) } } } // Expand env vars in other string fields in valueMap if needed } config.SingleProject.Schema = append(config.SingleProject.Schema, schemaPtr) break // Only process the first URL in the map } } return nil } // parseDocuments handles the documents configuration which can be either a string or an array func parseDocuments(config *GraphQLConfig) error { documents := viper.Get("documents") if documents == nil { return nil } // Case 1: documents is a simple string if docStr, ok := documents.(string); ok { config.SingleProject.Documents = append(config.SingleProject.Documents, expandEnvVars(docStr)) return nil } // Case 2: documents is an array docArr, ok := documents.([]interface{}) if !ok { return fmt.Errorf("documents must be a string path or an array") } for _, item := range docArr { if docStr, ok := item.(string); ok { config.SingleProject.Documents = append(config.SingleProject.Documents, expandEnvVars(docStr)) } } return nil } // parseInclude handles the include configuration which can be either a string or an array func parseInclude(config *GraphQLConfig) error { include := viper.Get("include") if include == nil { return nil } if config.SingleProject == nil { config.SingleProject = &GraphQLProject{} } // Case 1: include is a simple string if incStr, ok := include.(string); ok { config.SingleProject.Include = append(config.SingleProject.Include, expandEnvVars(incStr)) return nil } // Case 2: include is an array incArr, ok := include.([]interface{}) if !ok { return fmt.Errorf("include must be a string path or an array") } for _, item := range incArr { if incStr, ok := item.(string); ok { config.SingleProject.Include = append(config.SingleProject.Include, expandEnvVars(incStr)) } } return nil } // parseExclude handles the exclude configuration which can be either a string or an array func parseExclude(config *GraphQLConfig) error { exclude := viper.Get("exclude") if exclude == nil { return nil } if config.SingleProject == nil { config.SingleProject = &GraphQLProject{} } // Case 1: exclude is a simple string if excStr, ok := exclude.(string); ok { config.SingleProject.Exclude = append(config.SingleProject.Exclude, expandEnvVars(excStr)) return nil } // Case 2: exclude is an array excArr, ok := exclude.([]interface{}) if !ok { return fmt.Errorf("exclude must be a string path or an array") } for _, item := range excArr { if excStr, ok := item.(string); ok { config.SingleProject.Exclude = append(config.SingleProject.Exclude, expandEnvVars(excStr)) } } return 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/fotoetienne/gqai'

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