feat: add package indexing to MCP index_revision tool
The options server's index_revision now also indexes packages when running under nixpkgs-search, matching the CLI behavior. The packages server gets its own index_revision tool for standalone package indexing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -144,7 +144,7 @@ labmcp/
|
|||||||
| `search_options` | Full-text search across option names and descriptions |
|
| `search_options` | Full-text search across option names and descriptions |
|
||||||
| `get_option` | Get full details for a specific option with children |
|
| `get_option` | Get full details for a specific option with children |
|
||||||
| `get_file` | Fetch source file contents from indexed repository |
|
| `get_file` | Fetch source file contents from indexed repository |
|
||||||
| `index_revision` | Index a revision (by hash or channel name) |
|
| `index_revision` | Index a revision (options, files, and packages for nixpkgs) |
|
||||||
| `list_revisions` | List all indexed revisions |
|
| `list_revisions` | List all indexed revisions |
|
||||||
| `delete_revision` | Delete an indexed revision |
|
| `delete_revision` | Delete an indexed revision |
|
||||||
|
|
||||||
@@ -155,6 +155,7 @@ labmcp/
|
|||||||
| `search_packages` | Full-text search across package names and descriptions |
|
| `search_packages` | Full-text search across package names and descriptions |
|
||||||
| `get_package` | Get full details for a specific package by attr path |
|
| `get_package` | Get full details for a specific package by attr path |
|
||||||
| `get_file` | Fetch source file contents from nixpkgs |
|
| `get_file` | Fetch source file contents from nixpkgs |
|
||||||
|
| `index_revision` | Index a revision to make its packages searchable |
|
||||||
| `list_revisions` | List all indexed revisions |
|
| `list_revisions` | List all indexed revisions |
|
||||||
| `delete_revision` | Delete an indexed revision |
|
| `delete_revision` | Delete an indexed revision |
|
||||||
|
|
||||||
|
|||||||
@@ -384,7 +384,7 @@ hm-options -d "sqlite://my.db" index hm-unstable
|
|||||||
| `search_options` | Search for options by name or description |
|
| `search_options` | Search for options by name or description |
|
||||||
| `get_option` | Get full details for a specific option |
|
| `get_option` | Get full details for a specific option |
|
||||||
| `get_file` | Fetch source file contents from the repository |
|
| `get_file` | Fetch source file contents from the repository |
|
||||||
| `index_revision` | Index a revision |
|
| `index_revision` | Index a revision (options, files, and packages for nixpkgs) |
|
||||||
| `list_revisions` | List all indexed revisions |
|
| `list_revisions` | List all indexed revisions |
|
||||||
| `delete_revision` | Delete an indexed revision |
|
| `delete_revision` | Delete an indexed revision |
|
||||||
|
|
||||||
@@ -395,7 +395,7 @@ hm-options -d "sqlite://my.db" index hm-unstable
|
|||||||
| `search_packages` | Search for packages by name or description |
|
| `search_packages` | Search for packages by name or description |
|
||||||
| `get_package` | Get full details for a specific package |
|
| `get_package` | Get full details for a specific package |
|
||||||
| `get_file` | Fetch source file contents from nixpkgs |
|
| `get_file` | Fetch source file contents from nixpkgs |
|
||||||
| `index_revision` | Index a revision |
|
| `index_revision` | Index a revision to make its packages searchable |
|
||||||
| `list_revisions` | List all indexed revisions |
|
| `list_revisions` | List all indexed revisions |
|
||||||
| `delete_revision` | Delete an indexed revision |
|
| `delete_revision` | Delete an indexed revision |
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
defaultDatabase = "sqlite://nixpkgs-search.db"
|
defaultDatabase = "sqlite://nixpkgs-search.db"
|
||||||
version = "0.3.0"
|
version = "0.4.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -310,7 +310,8 @@ func runOptionsServe(c *cli.Context) error {
|
|||||||
server := mcp.NewServer(store, logger, config)
|
server := mcp.NewServer(store, logger, config)
|
||||||
|
|
||||||
indexer := nixos.NewIndexer(store)
|
indexer := nixos.NewIndexer(store)
|
||||||
server.RegisterHandlers(indexer)
|
pkgIndexer := packages.NewIndexer(store)
|
||||||
|
server.RegisterHandlersWithPackages(indexer, pkgIndexer)
|
||||||
|
|
||||||
transport := c.String("transport")
|
transport := c.String("transport")
|
||||||
switch transport {
|
switch transport {
|
||||||
|
|||||||
@@ -14,12 +14,29 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// RegisterHandlers registers all tool handlers on the server for options mode.
|
// RegisterHandlers registers all tool handlers on the server for options mode.
|
||||||
|
// Used by legacy nixos-options and hm-options servers (no package indexing).
|
||||||
func (s *Server) RegisterHandlers(indexer options.Indexer) {
|
func (s *Server) RegisterHandlers(indexer options.Indexer) {
|
||||||
|
s.registerOptionsHandlers(indexer, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterHandlersWithPackages registers all tool handlers for options mode
|
||||||
|
// with additional package indexing support. When pkgIndexer is non-nil,
|
||||||
|
// index_revision will also index packages, and list_revisions will show package counts.
|
||||||
|
func (s *Server) RegisterHandlersWithPackages(indexer options.Indexer, pkgIndexer *packages.Indexer) {
|
||||||
|
s.registerOptionsHandlers(indexer, pkgIndexer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// registerOptionsHandlers is the shared implementation for RegisterHandlers and RegisterHandlersWithPackages.
|
||||||
|
func (s *Server) registerOptionsHandlers(indexer options.Indexer, pkgIndexer *packages.Indexer) {
|
||||||
s.tools["search_options"] = s.handleSearchOptions
|
s.tools["search_options"] = s.handleSearchOptions
|
||||||
s.tools["get_option"] = s.handleGetOption
|
s.tools["get_option"] = s.handleGetOption
|
||||||
s.tools["get_file"] = s.handleGetFile
|
s.tools["get_file"] = s.handleGetFile
|
||||||
s.tools["index_revision"] = s.makeIndexHandler(indexer)
|
s.tools["index_revision"] = s.makeIndexHandler(indexer, pkgIndexer)
|
||||||
|
if pkgIndexer != nil {
|
||||||
|
s.tools["list_revisions"] = s.handleListRevisionsWithPackages
|
||||||
|
} else {
|
||||||
s.tools["list_revisions"] = s.handleListRevisions
|
s.tools["list_revisions"] = s.handleListRevisions
|
||||||
|
}
|
||||||
s.tools["delete_revision"] = s.handleDeleteRevision
|
s.tools["delete_revision"] = s.handleDeleteRevision
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,6 +45,7 @@ func (s *Server) RegisterPackageHandlers(pkgIndexer *packages.Indexer) {
|
|||||||
s.tools["search_packages"] = s.handleSearchPackages
|
s.tools["search_packages"] = s.handleSearchPackages
|
||||||
s.tools["get_package"] = s.handleGetPackage
|
s.tools["get_package"] = s.handleGetPackage
|
||||||
s.tools["get_file"] = s.handleGetFile
|
s.tools["get_file"] = s.handleGetFile
|
||||||
|
s.tools["index_revision"] = s.makePackageIndexHandler(pkgIndexer)
|
||||||
s.tools["list_revisions"] = s.handleListRevisionsWithPackages
|
s.tools["list_revisions"] = s.handleListRevisionsWithPackages
|
||||||
s.tools["delete_revision"] = s.handleDeleteRevision
|
s.tools["delete_revision"] = s.handleDeleteRevision
|
||||||
}
|
}
|
||||||
@@ -246,7 +264,8 @@ func (s *Server) handleGetFile(ctx context.Context, args map[string]interface{})
|
|||||||
}
|
}
|
||||||
|
|
||||||
// makeIndexHandler creates the index_revision handler with the indexer.
|
// makeIndexHandler creates the index_revision handler with the indexer.
|
||||||
func (s *Server) makeIndexHandler(indexer options.Indexer) ToolHandler {
|
// If pkgIndexer is non-nil, it will also index packages after options and files.
|
||||||
|
func (s *Server) makeIndexHandler(indexer options.Indexer, pkgIndexer *packages.Indexer) ToolHandler {
|
||||||
return func(ctx context.Context, args map[string]interface{}) (CallToolResult, error) {
|
return func(ctx context.Context, args map[string]interface{}) (CallToolResult, error) {
|
||||||
revision, _ := args["revision"].(string)
|
revision, _ := args["revision"].(string)
|
||||||
if revision == "" {
|
if revision == "" {
|
||||||
@@ -278,6 +297,17 @@ func (s *Server) makeIndexHandler(indexer options.Indexer) ToolHandler {
|
|||||||
s.logger.Printf("Warning: file indexing failed: %v", err)
|
s.logger.Printf("Warning: file indexing failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Index packages if package indexer is available
|
||||||
|
var packageCount int
|
||||||
|
if pkgIndexer != nil {
|
||||||
|
pkgResult, pkgErr := pkgIndexer.IndexPackages(ctx, result.Revision.ID, result.Revision.GitHash)
|
||||||
|
if pkgErr != nil {
|
||||||
|
s.logger.Printf("Warning: package indexing failed: %v", pkgErr)
|
||||||
|
} else {
|
||||||
|
packageCount = pkgResult.PackageCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
sb.WriteString(fmt.Sprintf("Indexed revision: %s\n", result.Revision.GitHash))
|
sb.WriteString(fmt.Sprintf("Indexed revision: %s\n", result.Revision.GitHash))
|
||||||
if result.Revision.ChannelName != "" {
|
if result.Revision.ChannelName != "" {
|
||||||
@@ -285,6 +315,9 @@ func (s *Server) makeIndexHandler(indexer options.Indexer) ToolHandler {
|
|||||||
}
|
}
|
||||||
sb.WriteString(fmt.Sprintf("Options: %d\n", result.OptionCount))
|
sb.WriteString(fmt.Sprintf("Options: %d\n", result.OptionCount))
|
||||||
sb.WriteString(fmt.Sprintf("Files: %d\n", fileCount))
|
sb.WriteString(fmt.Sprintf("Files: %d\n", fileCount))
|
||||||
|
if packageCount > 0 {
|
||||||
|
sb.WriteString(fmt.Sprintf("Packages: %d\n", packageCount))
|
||||||
|
}
|
||||||
// Handle Duration which may be time.Duration or interface{}
|
// Handle Duration which may be time.Duration or interface{}
|
||||||
if dur, ok := result.Duration.(time.Duration); ok {
|
if dur, ok := result.Duration.(time.Duration); ok {
|
||||||
sb.WriteString(fmt.Sprintf("Duration: %s\n", dur.Round(time.Millisecond)))
|
sb.WriteString(fmt.Sprintf("Duration: %s\n", dur.Round(time.Millisecond)))
|
||||||
@@ -296,6 +329,85 @@ func (s *Server) makeIndexHandler(indexer options.Indexer) ToolHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// makePackageIndexHandler creates an index_revision handler for the packages-only server.
|
||||||
|
// It creates a revision record if needed, then indexes packages.
|
||||||
|
func (s *Server) makePackageIndexHandler(pkgIndexer *packages.Indexer) ToolHandler {
|
||||||
|
return func(ctx context.Context, args map[string]interface{}) (CallToolResult, error) {
|
||||||
|
revision, _ := args["revision"].(string)
|
||||||
|
if revision == "" {
|
||||||
|
return ErrorContent(fmt.Errorf("revision is required")), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := packages.ValidateRevision(revision); err != nil {
|
||||||
|
return ErrorContent(err), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve channel aliases to git ref
|
||||||
|
ref := pkgIndexer.ResolveRevision(revision)
|
||||||
|
|
||||||
|
// Check if revision already exists
|
||||||
|
rev, err := s.store.GetRevision(ctx, ref)
|
||||||
|
if err != nil {
|
||||||
|
return ErrorContent(fmt.Errorf("failed to check revision: %w", err)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if rev == nil {
|
||||||
|
// Also try by channel name
|
||||||
|
rev, err = s.store.GetRevisionByChannel(ctx, revision)
|
||||||
|
if err != nil {
|
||||||
|
return ErrorContent(fmt.Errorf("failed to check revision: %w", err)), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rev == nil {
|
||||||
|
// Create a new revision record
|
||||||
|
commitDate, _ := pkgIndexer.GetCommitDate(ctx, ref)
|
||||||
|
rev = &database.Revision{
|
||||||
|
GitHash: ref,
|
||||||
|
ChannelName: pkgIndexer.GetChannelName(revision),
|
||||||
|
CommitDate: commitDate,
|
||||||
|
}
|
||||||
|
if err := s.store.CreateRevision(ctx, rev); err != nil {
|
||||||
|
return ErrorContent(fmt.Errorf("failed to create revision: %w", err)), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if packages are already indexed for this revision
|
||||||
|
if rev.PackageCount > 0 {
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.WriteString(fmt.Sprintf("Revision already indexed: %s\n", rev.GitHash))
|
||||||
|
if rev.ChannelName != "" {
|
||||||
|
sb.WriteString(fmt.Sprintf("Channel: %s\n", rev.ChannelName))
|
||||||
|
}
|
||||||
|
sb.WriteString(fmt.Sprintf("Packages: %d\n", rev.PackageCount))
|
||||||
|
sb.WriteString(fmt.Sprintf("Indexed at: %s\n", rev.IndexedAt.Format("2006-01-02 15:04")))
|
||||||
|
return CallToolResult{
|
||||||
|
Content: []Content{TextContent(sb.String())},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index packages
|
||||||
|
pkgResult, err := pkgIndexer.IndexPackages(ctx, rev.ID, rev.GitHash)
|
||||||
|
if err != nil {
|
||||||
|
return ErrorContent(fmt.Errorf("package indexing failed: %w", err)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.WriteString(fmt.Sprintf("Indexed revision: %s\n", rev.GitHash))
|
||||||
|
if rev.ChannelName != "" {
|
||||||
|
sb.WriteString(fmt.Sprintf("Channel: %s\n", rev.ChannelName))
|
||||||
|
}
|
||||||
|
sb.WriteString(fmt.Sprintf("Packages: %d\n", pkgResult.PackageCount))
|
||||||
|
if dur, ok := pkgResult.Duration.(time.Duration); ok {
|
||||||
|
sb.WriteString(fmt.Sprintf("Duration: %s\n", dur.Round(time.Millisecond)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return CallToolResult{
|
||||||
|
Content: []Content{TextContent(sb.String())},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// handleListRevisions handles the list_revisions tool.
|
// handleListRevisions handles the list_revisions tool.
|
||||||
func (s *Server) handleListRevisions(ctx context.Context, args map[string]interface{}) (CallToolResult, error) {
|
func (s *Server) handleListRevisions(ctx context.Context, args map[string]interface{}) (CallToolResult, error) {
|
||||||
revisions, err := s.store.ListRevisions(ctx)
|
revisions, err := s.store.ListRevisions(ctx)
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ type ServerConfig struct {
|
|||||||
func DefaultNixOSConfig() ServerConfig {
|
func DefaultNixOSConfig() ServerConfig {
|
||||||
return ServerConfig{
|
return ServerConfig{
|
||||||
Name: "nixos-options",
|
Name: "nixos-options",
|
||||||
Version: "0.3.0",
|
Version: "0.4.0",
|
||||||
DefaultChannel: "nixos-stable",
|
DefaultChannel: "nixos-stable",
|
||||||
SourceName: "nixpkgs",
|
SourceName: "nixpkgs",
|
||||||
Mode: ModeOptions,
|
Mode: ModeOptions,
|
||||||
@@ -57,7 +57,9 @@ If the current project contains a flake.lock file, you can index the exact nixpk
|
|||||||
|
|
||||||
Example: If flake.lock contains "rev": "abc123...", call index_revision with revision "abc123...".
|
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.`,
|
This ensures option documentation matches the nixpkgs version the project actually uses.
|
||||||
|
|
||||||
|
Note: index_revision also indexes packages when available, so both options and packages become searchable.`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +67,7 @@ This ensures option documentation matches the nixpkgs version the project actual
|
|||||||
func DefaultNixpkgsPackagesConfig() ServerConfig {
|
func DefaultNixpkgsPackagesConfig() ServerConfig {
|
||||||
return ServerConfig{
|
return ServerConfig{
|
||||||
Name: "nixpkgs-packages",
|
Name: "nixpkgs-packages",
|
||||||
Version: "0.3.0",
|
Version: "0.4.0",
|
||||||
DefaultChannel: "nixos-stable",
|
DefaultChannel: "nixos-stable",
|
||||||
SourceName: "nixpkgs",
|
SourceName: "nixpkgs",
|
||||||
Mode: ModePackages,
|
Mode: ModePackages,
|
||||||
@@ -73,7 +75,9 @@ func DefaultNixpkgsPackagesConfig() ServerConfig {
|
|||||||
|
|
||||||
If the current project contains a flake.lock file, you can search packages from the exact nixpkgs revision used by the project:
|
If the current project contains a flake.lock file, you can search packages from the exact nixpkgs revision used by the project:
|
||||||
1. Read the flake.lock file to find the nixpkgs "rev" field
|
1. Read the flake.lock file to find the nixpkgs "rev" field
|
||||||
2. Ensure the revision is indexed (packages are indexed separately from options)
|
2. Call index_revision with that git hash to index packages for that specific version
|
||||||
|
|
||||||
|
Example: If flake.lock contains "rev": "abc123...", call index_revision with revision "abc123...".
|
||||||
|
|
||||||
This ensures package information matches the nixpkgs version the project actually uses.`,
|
This ensures package information matches the nixpkgs version the project actually uses.`,
|
||||||
}
|
}
|
||||||
@@ -427,7 +431,7 @@ func (s *Server) getOptionToolDefinitions() []Tool {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "index_revision",
|
Name: "index_revision",
|
||||||
Description: fmt.Sprintf("Index a %s revision to make its options searchable", sourceRepo),
|
Description: s.indexRevisionDescription(sourceRepo),
|
||||||
InputSchema: InputSchema{
|
InputSchema: InputSchema{
|
||||||
Type: "object",
|
Type: "object",
|
||||||
Properties: map[string]Property{
|
Properties: map[string]Property{
|
||||||
@@ -464,6 +468,15 @@ func (s *Server) getOptionToolDefinitions() []Tool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// indexRevisionDescription returns the description for the index_revision tool,
|
||||||
|
// adjusted based on whether packages are also indexed.
|
||||||
|
func (s *Server) indexRevisionDescription(sourceRepo string) string {
|
||||||
|
if s.config.SourceName == "nixpkgs" {
|
||||||
|
return fmt.Sprintf("Index a %s revision to make its options and packages searchable", sourceRepo)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Index a %s revision to make its options searchable", sourceRepo)
|
||||||
|
}
|
||||||
|
|
||||||
// getPackageToolDefinitions returns the tool definitions for packages mode.
|
// getPackageToolDefinitions returns the tool definitions for packages mode.
|
||||||
func (s *Server) getPackageToolDefinitions() []Tool {
|
func (s *Server) getPackageToolDefinitions() []Tool {
|
||||||
exampleChannels := "'nixos-unstable', 'nixos-24.05'"
|
exampleChannels := "'nixos-unstable', 'nixos-24.05'"
|
||||||
@@ -547,6 +560,20 @@ func (s *Server) getPackageToolDefinitions() []Tool {
|
|||||||
Required: []string{"path"},
|
Required: []string{"path"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "index_revision",
|
||||||
|
Description: "Index a nixpkgs revision to make its packages searchable",
|
||||||
|
InputSchema: InputSchema{
|
||||||
|
Type: "object",
|
||||||
|
Properties: map[string]Property{
|
||||||
|
"revision": {
|
||||||
|
Type: "string",
|
||||||
|
Description: fmt.Sprintf("Git hash (full or short) or channel name (e.g., %s)", exampleChannels),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Required: []string{"revision"},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "list_revisions",
|
Name: "list_revisions",
|
||||||
Description: "List all indexed nixpkgs revisions",
|
Description: "List all indexed nixpkgs revisions",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"git.t-juice.club/torjus/labmcp/internal/database"
|
"git.t-juice.club/torjus/labmcp/internal/database"
|
||||||
"git.t-juice.club/torjus/labmcp/internal/nixos"
|
"git.t-juice.club/torjus/labmcp/internal/nixos"
|
||||||
|
"git.t-juice.club/torjus/labmcp/internal/packages"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestServerInitialize(t *testing.T) {
|
func TestServerInitialize(t *testing.T) {
|
||||||
@@ -145,6 +146,110 @@ func TestServerNotification(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPackagesServerToolsList(t *testing.T) {
|
||||||
|
store := setupTestStore(t)
|
||||||
|
server := NewServer(store, nil, DefaultNixpkgsPackagesConfig())
|
||||||
|
|
||||||
|
pkgIndexer := packages.NewIndexer(store)
|
||||||
|
server.RegisterPackageHandlers(pkgIndexer)
|
||||||
|
|
||||||
|
input := `{"jsonrpc":"2.0","id":1,"method":"tools/list"}`
|
||||||
|
|
||||||
|
resp := runRequest(t, server, input)
|
||||||
|
|
||||||
|
if resp.Error != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", resp.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, ok := resp.Result.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Expected map result, got %T", resp.Result)
|
||||||
|
}
|
||||||
|
|
||||||
|
tools, ok := result["tools"].([]interface{})
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Expected tools array, got %T", result["tools"])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should have 6 tools (search_packages, get_package, get_file, index_revision, list_revisions, delete_revision)
|
||||||
|
if len(tools) != 6 {
|
||||||
|
t.Errorf("Expected 6 tools, got %d", len(tools))
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedTools := map[string]bool{
|
||||||
|
"search_packages": false,
|
||||||
|
"get_package": false,
|
||||||
|
"get_file": false,
|
||||||
|
"index_revision": false,
|
||||||
|
"list_revisions": false,
|
||||||
|
"delete_revision": false,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tool := range tools {
|
||||||
|
toolMap := tool.(map[string]interface{})
|
||||||
|
name := toolMap["name"].(string)
|
||||||
|
if _, ok := expectedTools[name]; ok {
|
||||||
|
expectedTools[name] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, found := range expectedTools {
|
||||||
|
if !found {
|
||||||
|
t.Errorf("Tool %q not found in tools list", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOptionsServerWithPackagesToolsList(t *testing.T) {
|
||||||
|
store := setupTestStore(t)
|
||||||
|
server := NewServer(store, nil, DefaultNixOSConfig())
|
||||||
|
|
||||||
|
indexer := nixos.NewIndexer(store)
|
||||||
|
pkgIndexer := packages.NewIndexer(store)
|
||||||
|
server.RegisterHandlersWithPackages(indexer, pkgIndexer)
|
||||||
|
|
||||||
|
input := `{"jsonrpc":"2.0","id":1,"method":"tools/list"}`
|
||||||
|
|
||||||
|
resp := runRequest(t, server, input)
|
||||||
|
|
||||||
|
if resp.Error != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", resp.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, ok := resp.Result.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Expected map result, got %T", resp.Result)
|
||||||
|
}
|
||||||
|
|
||||||
|
tools, ok := result["tools"].([]interface{})
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Expected tools array, got %T", result["tools"])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should still have 6 tools (same as options-only)
|
||||||
|
if len(tools) != 6 {
|
||||||
|
t.Errorf("Expected 6 tools, got %d", len(tools))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify index_revision is present
|
||||||
|
found := false
|
||||||
|
for _, tool := range tools {
|
||||||
|
toolMap := tool.(map[string]interface{})
|
||||||
|
if toolMap["name"].(string) == "index_revision" {
|
||||||
|
found = true
|
||||||
|
// For nixpkgs source, description should mention packages
|
||||||
|
desc := toolMap["description"].(string)
|
||||||
|
if !strings.Contains(desc, "packages") {
|
||||||
|
t.Errorf("index_revision description should mention packages, got: %s", desc)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Error("index_revision tool not found in tools list")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetFilePathValidation(t *testing.T) {
|
func TestGetFilePathValidation(t *testing.T) {
|
||||||
store := setupTestStore(t)
|
store := setupTestStore(t)
|
||||||
server := setupTestServer(t, store)
|
server := setupTestServer(t, store)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
buildGoModule {
|
buildGoModule {
|
||||||
inherit pname src;
|
inherit pname src;
|
||||||
version = "0.3.0";
|
version = "0.4.0";
|
||||||
|
|
||||||
vendorHash = "sha256-XrTtiaQT5br+0ZXz8//rc04GZn/HlQk7l8Nx/+Uil/I=";
|
vendorHash = "sha256-XrTtiaQT5br+0ZXz8//rc04GZn/HlQk7l8Nx/+Uil/I=";
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user