Configuration Loading
Configuration Loading
This page details the example found in the /examples/configuration
directory, demonstrating how to load server settings and tool definitions from external configuration files (JSON, TOML, or YAML) instead of defining them directly in code.
This approach allows for easier management and modification of server capabilities without recompiling the server binary.
Configuration Server (examples/configuration/server
)
This example uses the Viper library to read configuration files and then dynamically registers tools based on the loaded definitions.
Key parts:
package main
import (
"context"
"fmt"
"log"
"net/http"
"strings"
"github.com/localrivet/gomcp/protocol"
"github.com/localrivet/gomcp/server"
"github.com/localrivet/gomcp/transport/sse" // Using SSE transport here
"github.com/localrivet/gomcp/types"
"github.com/spf13/viper" // For config loading
)
// Generic handler for configured tools (example implementation)
func handleConfiguredTool(toolName string) server.ToolHandlerFunc {
return func(ctx context.Context, args map[string]interface{}) ([]protocol.Content, error) {
log.Printf("Executing configured tool '%s' with args: %v", toolName, args)
// In a real scenario, dispatch to specific logic based on toolName
responseText := fmt.Sprintf("Executed tool '%s'. Args: %v", toolName, args)
return []protocol.Content{protocol.TextContent{Type: "text", Text: responseText}}, nil
}
}
func main() {
// --- Load Configuration using Viper ---
viper.SetConfigName("config") // Name of config file (without extension)
viper.AddConfigPath(".") // Look in the current directory
viper.AddConfigPath("../") // Look in the parent directory (where config.* files are)
viper.AutomaticEnv() // Read in environment variables that match
if err := viper.ReadInConfig(); err != nil {
log.Fatalf("Error reading config file: %s", err)
}
log.Printf("Using config file: %s", viper.ConfigFileUsed())
// --- Setup MCP Server ---
serverInfo := types.Implementation{Name: "config-server", Version: "0.1.0"}
opts := server.NewServerOptions(serverInfo)
opts.Capabilities.Tools = &protocol.ToolsCaps{} // Enable tool capability
srv := server.NewServer(opts)
// --- Register Tools from Configuration ---
// Assuming config structure like:
// tools:
// - name: tool1
// description: "Description for tool1"
// inputSchema: { ... }
// - name: tool2
// description: "Description for tool2"
// inputSchema: { ... }
var configuredTools []protocol.Tool
if err := viper.UnmarshalKey("tools", &configuredTools); err != nil {
log.Fatalf("Unable to decode tools from config: %v", err)
}
for _, tool := range configuredTools {
log.Printf("Registering tool from config: %s", tool.Name)
// Need a local copy for the closure
localTool := tool
if err := srv.RegisterTool(localTool, handleConfiguredTool(localTool.Name)); err != nil {
log.Printf("WARN: Failed to register tool '%s': %v", localTool.Name, err)
}
}
// --- Setup Transport (SSE+HTTP) and Run ---
sseServer := sse.NewServer(srv, opts.Logger)
mux := http.NewServeMux()
mux.HandleFunc("/events", sseServer.HTTPHandler)
mux.HandleFunc("/message", srv.HTTPHandler)
log.Println("Starting config MCP server on :8080...")
if err := http.ListenAndServe(":8080", mux); err != nil {
log.Fatalf("HTTP server error: %v", err)
}
}
To Run:
- Navigate to
examples/configuration/server
. - Ensure one of the config files (
../config.json
,../config.toml
,../config.yaml
) exists in the parent directory. - Run
go run main.go
. Viper will automatically detect and load the configuration file.
This example demonstrates a powerful pattern for managing complex server setups where capabilities might change frequently or need to be defined outside the main application code.