package tools
import (
"context"
"fmt"
"strings"
"time"
"skraak_mcp/db"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
type CreateLocationInput struct {
DatasetID string `json:"dataset_id" jsonschema:"required,ID of the parent dataset (12-character nanoid)"`
Name string `json:"name" jsonschema:"required,Location name (max 140 characters)"`
Latitude float64 `json:"latitude" jsonschema:"required,Latitude in decimal degrees (-90 to 90)"`
Longitude float64 `json:"longitude" jsonschema:"required,Longitude in decimal degrees (-180 to 180)"`
TimezoneID string `json:"timezone_id" jsonschema:"required,IANA timezone ID (e.g. 'Pacific/Auckland')"`
Description *string `json:"description,omitempty" jsonschema:"Optional location description (max 255 characters)"`
}
type CreateLocationOutput struct {
Location db.Location `json:"location" jsonschema:"The created location with generated ID and timestamps"`
Message string `json:"message" jsonschema:"Success message"`
}
func CreateLocation(
ctx context.Context,
req *mcp.CallToolRequest,
input CreateLocationInput,
) (*mcp.CallToolResult, CreateLocationOutput, error) {
var output CreateLocationOutput
if strings.TrimSpace(input.Name) == "" {
return nil, output, fmt.Errorf("name cannot be empty")
}
if len(input.Name) > 140 {
return nil, output, fmt.Errorf("name must be 140 characters or less (got %d)", len(input.Name))
}
if input.Description != nil && len(*input.Description) > 255 {
return nil, output, fmt.Errorf("description must be 255 characters or less (got %d)", len(*input.Description))
}
if input.Latitude < -90 || input.Latitude > 90 {
return nil, output, fmt.Errorf("latitude must be between -90 and 90 (got %f)", input.Latitude)
}
if input.Longitude < -180 || input.Longitude > 180 {
return nil, output, fmt.Errorf("longitude must be between -180 and 180 (got %f)", input.Longitude)
}
if _, err := time.LoadLocation(input.TimezoneID); err != nil {
return nil, output, fmt.Errorf("invalid timezone_id '%s': %w", input.TimezoneID, err)
}
if strings.TrimSpace(input.DatasetID) == "" {
return nil, output, fmt.Errorf("dataset_id cannot be empty")
}
database, err := db.OpenWriteableDB(dbPath)
if err != nil {
return nil, output, fmt.Errorf("database connection failed: %w", err)
}
defer database.Close()
tx, err := database.BeginTx(ctx, nil)
if err != nil {
return nil, output, fmt.Errorf("failed to begin transaction: %w", err)
}
defer func() {
if err != nil {
tx.Rollback()
}
}()
var datasetExists bool
var datasetActive bool
var datasetName string
err = tx.QueryRowContext(ctx,
"SELECT EXISTS(SELECT 1 FROM dataset WHERE id = ?), active, name FROM dataset WHERE id = ?",
input.DatasetID, input.DatasetID,
).Scan(&datasetExists, &datasetActive, &datasetName)
if err != nil {
return nil, output, fmt.Errorf("failed to verify dataset: %w", err)
}
if !datasetExists {
return nil, output, fmt.Errorf("dataset with ID '%s' does not exist", input.DatasetID)
}
if !datasetActive {
return nil, output, fmt.Errorf("dataset '%s' (ID: %s) is not active", datasetName, input.DatasetID)
}
id, err := db.GenerateID()
if err != nil {
return nil, output, fmt.Errorf("failed to generate ID: %w", err)
}
_, err = tx.ExecContext(ctx,
"INSERT INTO location (id, dataset_id, name, latitude, longitude, timezone_id, description, created_at, last_modified, active) VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, TRUE)",
id, input.DatasetID, input.Name, input.Latitude, input.Longitude, input.TimezoneID, input.Description,
)
if err != nil {
return nil, output, fmt.Errorf("failed to create location: %w", err)
}
var location db.Location
err = tx.QueryRowContext(ctx,
"SELECT id, dataset_id, name, latitude, longitude, description, created_at, last_modified, active, timezone_id FROM location WHERE id = ?",
id,
).Scan(&location.ID, &location.DatasetID, &location.Name, &location.Latitude, &location.Longitude,
&location.Description, &location.CreatedAt, &location.LastModified, &location.Active, &location.TimezoneID)
if err != nil {
return nil, output, fmt.Errorf("failed to fetch created location: %w", err)
}
if err = tx.Commit(); err != nil {
return nil, output, fmt.Errorf("failed to commit transaction: %w", err)
}
output.Location = location
output.Message = fmt.Sprintf("Successfully created location '%s' with ID %s in dataset '%s' (%.6f, %.6f, %s)",
location.Name, location.ID, datasetName, location.Latitude, location.Longitude, location.TimezoneID)
return &mcp.CallToolResult{}, output, nil
}