feat: add hm-options package for Home Manager options
Add a new MCP server for Home Manager options, mirroring the functionality of nixos-options but targeting the home-manager repository. Changes: - Add shared options.Indexer interface for both implementations - Add internal/homemanager package with indexer and channel aliases - Add cmd/hm-options CLI entry point - Parameterize MCP server with ServerConfig for name/instructions - Parameterize nix/package.nix for building both packages - Add hm-options package and NixOS module to flake.nix - Add nix/hm-options-module.nix for systemd deployment Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,9 +10,62 @@ import (
|
||||
"git.t-juice.club/torjus/labmcp/internal/database"
|
||||
)
|
||||
|
||||
// ServerConfig contains configuration for the MCP server.
|
||||
type ServerConfig struct {
|
||||
// Name is the server name reported in initialization.
|
||||
Name string
|
||||
// Version is the server version.
|
||||
Version string
|
||||
// Instructions are the server instructions sent to clients.
|
||||
Instructions string
|
||||
// DefaultChannel is the default channel to use when no revision is specified.
|
||||
DefaultChannel string
|
||||
// SourceName is the name of the source repository (e.g., "nixpkgs", "home-manager").
|
||||
SourceName string
|
||||
}
|
||||
|
||||
// DefaultNixOSConfig returns the default configuration for NixOS options server.
|
||||
func DefaultNixOSConfig() ServerConfig {
|
||||
return ServerConfig{
|
||||
Name: "nixos-options",
|
||||
Version: "0.1.0",
|
||||
DefaultChannel: "nixos-stable",
|
||||
SourceName: "nixpkgs",
|
||||
Instructions: `NixOS Options MCP Server - Search and query NixOS configuration options.
|
||||
|
||||
If the current project contains a flake.lock file, you can index the exact nixpkgs revision used by the project:
|
||||
1. Read the flake.lock file to find the nixpkgs "rev" field
|
||||
2. Call index_revision with that git hash to index options for that specific version
|
||||
|
||||
Example: If flake.lock contains "rev": "abc123...", call index_revision with revision "abc123...".
|
||||
|
||||
This ensures option documentation matches the nixpkgs version the project actually uses.`,
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultHomeManagerConfig returns the default configuration for Home Manager options server.
|
||||
func DefaultHomeManagerConfig() ServerConfig {
|
||||
return ServerConfig{
|
||||
Name: "hm-options",
|
||||
Version: "0.1.0",
|
||||
DefaultChannel: "hm-stable",
|
||||
SourceName: "home-manager",
|
||||
Instructions: `Home Manager Options MCP Server - Search and query Home Manager configuration options.
|
||||
|
||||
If the current project contains a flake.lock file, you can index the exact home-manager revision used by the project:
|
||||
1. Read the flake.lock file to find the home-manager "rev" field
|
||||
2. Call index_revision with that git hash to index options for that specific version
|
||||
|
||||
Example: If flake.lock contains "rev": "abc123...", call index_revision with revision "abc123...".
|
||||
|
||||
This ensures option documentation matches the home-manager version the project actually uses.`,
|
||||
}
|
||||
}
|
||||
|
||||
// Server is an MCP server that handles JSON-RPC requests.
|
||||
type Server struct {
|
||||
store database.Store
|
||||
config ServerConfig
|
||||
tools map[string]ToolHandler
|
||||
initialized bool
|
||||
logger *log.Logger
|
||||
@@ -21,13 +74,14 @@ type Server struct {
|
||||
// ToolHandler is a function that handles a tool call.
|
||||
type ToolHandler func(ctx context.Context, args map[string]interface{}) (CallToolResult, error)
|
||||
|
||||
// NewServer creates a new MCP server.
|
||||
func NewServer(store database.Store, logger *log.Logger) *Server {
|
||||
// NewServer creates a new MCP server with the given configuration.
|
||||
func NewServer(store database.Store, logger *log.Logger, config ServerConfig) *Server {
|
||||
if logger == nil {
|
||||
logger = log.New(io.Discard, "", 0)
|
||||
}
|
||||
s := &Server{
|
||||
store: store,
|
||||
config: config,
|
||||
tools: make(map[string]ToolHandler),
|
||||
logger: logger,
|
||||
}
|
||||
@@ -126,18 +180,10 @@ func (s *Server) handleInitialize(req *Request) *Response {
|
||||
},
|
||||
},
|
||||
ServerInfo: Implementation{
|
||||
Name: "nixos-options",
|
||||
Version: "0.1.0",
|
||||
Name: s.config.Name,
|
||||
Version: s.config.Version,
|
||||
},
|
||||
Instructions: `NixOS Options MCP Server - Search and query NixOS configuration options.
|
||||
|
||||
If the current project contains a flake.lock file, you can index the exact nixpkgs revision used by the project:
|
||||
1. Read the flake.lock file to find the nixpkgs "rev" field
|
||||
2. Call index_revision with that git hash to index options for that specific version
|
||||
|
||||
Example: If flake.lock contains "rev": "abc123...", call index_revision with revision "abc123...".
|
||||
|
||||
This ensures option documentation matches the nixpkgs version the project actually uses.`,
|
||||
Instructions: s.config.Instructions,
|
||||
}
|
||||
|
||||
return &Response{
|
||||
@@ -159,10 +205,27 @@ func (s *Server) handleToolsList(req *Request) *Response {
|
||||
|
||||
// getToolDefinitions returns the tool definitions.
|
||||
func (s *Server) getToolDefinitions() []Tool {
|
||||
// Determine naming based on source
|
||||
optionType := "NixOS"
|
||||
sourceRepo := "nixpkgs"
|
||||
exampleOption := "services.nginx.enable"
|
||||
exampleNamespace := "services.nginx"
|
||||
exampleFilePath := "nixos/modules/services/web-servers/nginx/default.nix"
|
||||
exampleChannels := "'nixos-unstable', 'nixos-24.05'"
|
||||
|
||||
if s.config.SourceName == "home-manager" {
|
||||
optionType = "Home Manager"
|
||||
sourceRepo = "home-manager"
|
||||
exampleOption = "programs.git.enable"
|
||||
exampleNamespace = "programs.git"
|
||||
exampleFilePath = "modules/programs/git.nix"
|
||||
exampleChannels = "'hm-unstable', 'release-24.11'"
|
||||
}
|
||||
|
||||
return []Tool{
|
||||
{
|
||||
Name: "search_options",
|
||||
Description: "Search for NixOS configuration options by name or description",
|
||||
Description: fmt.Sprintf("Search for %s configuration options by name or description", optionType),
|
||||
InputSchema: InputSchema{
|
||||
Type: "object",
|
||||
Properties: map[string]Property{
|
||||
@@ -172,7 +235,7 @@ func (s *Server) getToolDefinitions() []Tool {
|
||||
},
|
||||
"revision": {
|
||||
Type: "string",
|
||||
Description: "Git hash or channel name (e.g., 'nixos-unstable'). Uses default if not specified.",
|
||||
Description: fmt.Sprintf("Git hash or channel name (e.g., %s). Uses default if not specified.", exampleChannels),
|
||||
},
|
||||
"type": {
|
||||
Type: "string",
|
||||
@@ -180,7 +243,7 @@ func (s *Server) getToolDefinitions() []Tool {
|
||||
},
|
||||
"namespace": {
|
||||
Type: "string",
|
||||
Description: "Filter by namespace prefix (e.g., 'services.nginx')",
|
||||
Description: fmt.Sprintf("Filter by namespace prefix (e.g., '%s')", exampleNamespace),
|
||||
},
|
||||
"limit": {
|
||||
Type: "integer",
|
||||
@@ -193,13 +256,13 @@ func (s *Server) getToolDefinitions() []Tool {
|
||||
},
|
||||
{
|
||||
Name: "get_option",
|
||||
Description: "Get full details for a specific NixOS option including its children",
|
||||
Description: fmt.Sprintf("Get full details for a specific %s option including its children", optionType),
|
||||
InputSchema: InputSchema{
|
||||
Type: "object",
|
||||
Properties: map[string]Property{
|
||||
"name": {
|
||||
Type: "string",
|
||||
Description: "Full option path (e.g., 'services.nginx.enable')",
|
||||
Description: fmt.Sprintf("Full option path (e.g., '%s')", exampleOption),
|
||||
},
|
||||
"revision": {
|
||||
Type: "string",
|
||||
@@ -216,13 +279,13 @@ func (s *Server) getToolDefinitions() []Tool {
|
||||
},
|
||||
{
|
||||
Name: "get_file",
|
||||
Description: "Fetch the contents of a file from nixpkgs",
|
||||
Description: fmt.Sprintf("Fetch the contents of a file from %s", sourceRepo),
|
||||
InputSchema: InputSchema{
|
||||
Type: "object",
|
||||
Properties: map[string]Property{
|
||||
"path": {
|
||||
Type: "string",
|
||||
Description: "File path relative to nixpkgs root (e.g., 'nixos/modules/services/web-servers/nginx/default.nix')",
|
||||
Description: fmt.Sprintf("File path relative to %s root (e.g., '%s')", sourceRepo, exampleFilePath),
|
||||
},
|
||||
"revision": {
|
||||
Type: "string",
|
||||
@@ -234,13 +297,13 @@ func (s *Server) getToolDefinitions() []Tool {
|
||||
},
|
||||
{
|
||||
Name: "index_revision",
|
||||
Description: "Index a nixpkgs revision to make its options searchable",
|
||||
Description: fmt.Sprintf("Index a %s revision to make its options searchable", sourceRepo),
|
||||
InputSchema: InputSchema{
|
||||
Type: "object",
|
||||
Properties: map[string]Property{
|
||||
"revision": {
|
||||
Type: "string",
|
||||
Description: "Git hash (full or short) or channel name (e.g., 'nixos-unstable', 'nixos-24.05')",
|
||||
Description: fmt.Sprintf("Git hash (full or short) or channel name (e.g., %s)", exampleChannels),
|
||||
},
|
||||
},
|
||||
Required: []string{"revision"},
|
||||
@@ -248,7 +311,7 @@ func (s *Server) getToolDefinitions() []Tool {
|
||||
},
|
||||
{
|
||||
Name: "list_revisions",
|
||||
Description: "List all indexed nixpkgs revisions",
|
||||
Description: fmt.Sprintf("List all indexed %s revisions", sourceRepo),
|
||||
InputSchema: InputSchema{
|
||||
Type: "object",
|
||||
Properties: map[string]Property{},
|
||||
|
||||
Reference in New Issue
Block a user