package main
import (
"context"
"fmt"
"log"
"os"
"path/filepath"
"skraak_mcp/prompts"
"skraak_mcp/resources"
"skraak_mcp/tools"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
var dbPath string
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "Usage: %s <path-to-duckdb-database>\n", os.Args[0])
fmt.Fprintf(os.Stderr, "Example: %s ./data/mydb.duckdb\n", os.Args[0])
os.Exit(1)
}
dbPath = os.Args[1]
tools.SetDBPath(dbPath)
schemaPath := filepath.Join(filepath.Dir(os.Args[0]), "db", "schema.sql")
resources.SetSchemaPath(schemaPath)
server := mcp.NewServer(&mcp.Implementation{
Name: "skraak_mcp",
Version: "v1.0.0",
}, nil)
mcp.AddTool(server, &mcp.Tool{
Name: "get_current_time",
Description: "Get the current system time with timezone information",
}, tools.GetCurrentTime)
mcp.AddTool(server, &mcp.Tool{
Name: "execute_sql",
Description: "Execute arbitrary SQL SELECT queries against the database. Supports parameterized queries with ? placeholders. Database is read-only. Results limited to 1000 rows by default (max 10000). Use with schema resources to construct queries.",
}, tools.ExecuteSQL)
mcp.AddTool(server, &mcp.Tool{
Name: "create_or_update_dataset",
Description: "Create or update a dataset. Omit 'id' to create (name required), provide 'id' to update. Returns the dataset with timestamps.",
}, tools.CreateOrUpdateDataset)
mcp.AddTool(server, &mcp.Tool{
Name: "create_or_update_location",
Description: "Create or update a location. Omit 'id' to create (dataset_id, name, latitude, longitude, timezone_id required), provide 'id' to update. Location must belong to the specified dataset when creating.",
}, tools.CreateOrUpdateLocation)
mcp.AddTool(server, &mcp.Tool{
Name: "create_or_update_cluster",
Description: "Create or update a cluster. Omit 'id' to create (dataset_id, location_id, name, sample_rate required), provide 'id' to update. Query existing patterns first with execute_sql before setting cyclic_recording_pattern_id.",
}, tools.CreateOrUpdateCluster)
mcp.AddTool(server, &mcp.Tool{
Name: "create_or_update_pattern",
Description: "Create or update a cyclic recording pattern. Omit 'id' to create (record_seconds, sleep_seconds required), provide 'id' to update. Returns existing pattern if duplicate record/sleep values found.",
}, tools.CreateOrUpdatePattern)
mcp.AddTool(server, &mcp.Tool{
Name: "import_file",
Description: "Import a single WAV file into the database. Automatically parses AudioMoth and filename timestamps, calculates hash, extracts metadata, and computes astronomical data. Skips if duplicate (by hash).",
}, tools.ImportFile)
mcp.AddTool(server, &mcp.Tool{
Name: "import_audio_files",
Description: "Batch import WAV files from a folder into the database. Automatically parses AudioMoth and filename timestamps, calculates hashes, extracts metadata, and computes astronomical data. Files are imported in a single transaction. Duplicate files (by hash) are skipped.",
}, tools.ImportAudioFiles)
mcp.AddTool(server, &mcp.Tool{
Name: "bulk_file_import",
Description: "Batch import WAV files across multiple locations/clusters using a CSV file. CSV must have columns (in order): location_name, location_id, directory_path, date_range, sample_rate, file_count. Auto-creates clusters using date_range as cluster name. Logs progress to file for monitoring. Synchronous/fail-fast execution.",
}, tools.BulkFileImport)
mcp.AddTool(server, &mcp.Tool{
Name: "import_ml_selections",
Description: "Import hand sorted ML-detected kiwi call selections from folder structure organized by species/call-type with WAV/PNG pairs",
}, tools.ImportMLSelections)
schemaResource, schemaTemplate := resources.GetSchemaResources()
server.AddResource(schemaResource, resources.SchemaResourceHandler)
server.AddResourceTemplate(schemaTemplate, resources.SchemaResourceHandler)
server.AddPrompt(prompts.GetQueryDatasetsPrompt(), prompts.QueryDatasetsPromptHandler)
server.AddPrompt(prompts.GetExploreSchemaPrompt(), prompts.ExploreSchemaPromptHandler)
server.AddPrompt(prompts.GetExploreLocationHierarchyPrompt(), prompts.ExploreLocationHierarchyPromptHandler)
server.AddPrompt(prompts.GetQueryLocationDataPrompt(), prompts.QueryLocationDataPromptHandler)
server.AddPrompt(prompts.GetAnalyzeClusterFilesPrompt(), prompts.AnalyzeClusterFilesPromptHandler)
server.AddPrompt(prompts.GetSystemStatusPrompt(), prompts.SystemStatusPromptHandler)
if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil {
log.Fatalf("Server error: %v", err)
}
}