Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ Tiger CLI is a Go-based command-line interface for managing Tiger, the modern da
- **Command Structure**: `internal/tiger/cmd/` - Cobra-based command definitions
- `root.go` - Root command with global flags and configuration initialization
- `auth.go` - Authentication commands (login, logout, whoami)
- `service.go` - Service management commands (list, create, describe, fork, delete, update-password)
- `service.go` - Service management commands (list, create, get, fork, delete, update-password)
- `db.go` - Database operation commands (connection-string, connect, test-connection)
- `config.go` - Configuration management commands (show, set, unset, reset)
- `mcp.go` - MCP server commands (install, start)
Expand All @@ -160,7 +160,7 @@ Tiger CLI is a Go-based command-line interface for managing Tiger, the modern da
- **API Client**: `internal/tiger/api/` - Generated OpenAPI client with mocks
- **MCP Server**: `internal/tiger/mcp/` - Model Context Protocol server implementation
- `server.go` - MCP server initialization, tool registration, and lifecycle management
- `service_tools.go` - Service management tools (list, show, create, update-password)
- `service_tools.go` - Service management tools (list, get, create, update-password)
- `db_tools.go` - Database operation tools (execute-query)
- `proxy.go` - Proxy client that forwards tools/resources/prompts from remote docs MCP server
- **Password Storage**: `internal/tiger/password/` - Secure password storage utilities
Expand Down Expand Up @@ -193,7 +193,7 @@ The Tiger MCP server provides AI assistants with programmatic access to Tiger re
**Two Types of Tools:**

1. **Direct Tiger Tools** - Native tools for Tiger operations
- `service_tools.go` - Service management (list, show, create, update-password)
- `service_tools.go` - Service management (list, get, create, update-password)
- `db_tools.go` - Database operations (execute-query)
2. **Proxied Documentation Tools** (`proxy.go`) - Tools forwarded from a remote docs MCP server (see `proxy.go` for implementation)

Expand Down Expand Up @@ -380,7 +380,7 @@ buildRootCmd() → Complete CLI with all commands and flags
│ └── buildWhoamiCmd()
├── buildServiceCmd()
│ ├── buildServiceListCmd()
│ ├── buildServiceDescribeCmd()
│ ├── buildServiceGetCmd()
│ ├── buildServiceCreateCmd()
│ ├── buildServiceForkCmd()
│ ├── buildServiceDeleteCmd()
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ Tiger CLI provides the following commands:
- `tiger service` - Service lifecycle management
- `list` - List all services
- `create` - Create a new service
- `describe` - Show detailed service information
- `get` - Show detailed service information (aliases: `describe`, `show`)
- `fork` - Fork an existing service
- `delete` - Delete a service
- `update-password` - Update service master password
Expand Down Expand Up @@ -162,7 +162,7 @@ The MCP server exposes the following tools to AI assistants:

**Service Management:**
- `service_list` - List all database services in your project
- `service_show` - Show detailed information about a specific service
- `service_get` - Get detailed information about a specific service
- `service_create` - Create new database services with configurable resources
- `service_update_password` - Update the master password for a service

Expand Down
2 changes: 1 addition & 1 deletion docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ go test ./internal/tiger/cmd -v -run Integration
### What Integration Tests Cover

- **Authentication lifecycle**: Login with credentials, verify authentication, logout
- **Service management**: Create, list, describe, and delete database services
- **Service management**: Create, list, get, and delete database services
- **Password management**: Update service passwords with keychain storage
- **Database connectivity**: Generate connection strings and execute psql commands
- **Output formats**: Validate JSON, YAML, and table output formats
Expand Down
5 changes: 3 additions & 2 deletions docs/mcp_feedback.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ The TigerData MCP tools provide useful database management functionality but hav
### Recommendations
- **WHY**: Consistency reduces cognitive load and makes the API more predictable
- Standardize on snake_case throughout: `get_guide`, `semantic_search_postgres_docs`
- Use consistent verbs: `service_get` instead of `service_show` for alignment with REST conventions
- ~~Use consistent verbs: `service_get` instead of `service_show` for alignment with REST conventions~~ ✅ COMPLETED

## Input Parameters

Expand Down Expand Up @@ -99,6 +99,7 @@ The TigerData MCP tools provide useful database management functionality but hav
### High Priority
1. Fix parameter type issues in `semanticSearchPostgresDocs`
2. Standardize naming conventions across all tools
- ~~service_get~~ ✅ COMPLETED
3. Add service deletion capability
4. ~~Rename timeout to timeout_minutes~~ ✅ COMPLETED

Expand All @@ -125,4 +126,4 @@ The TigerData MCP tools provide useful database management functionality but hav
2. **Reliability**: Proper typing prevents runtime failures
3. **Usability**: Clear documentation reduces trial-and-error
4. **Completeness**: Full CRUD operations enable real workflows
5. **Efficiency**: Appropriate content sizing preserves LLM context
5. **Efficiency**: Appropriate content sizing preserves LLM context
26 changes: 13 additions & 13 deletions internal/tiger/cmd/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func executeIntegrationCommand(args ...string) (string, error) {
}

// TestServiceLifecycleIntegration tests the complete authentication and service lifecycle:
// login -> whoami -> create -> describe -> update-password -> delete -> logout
// login -> whoami -> create -> get -> update-password -> delete -> logout
func TestServiceLifecycleIntegration(t *testing.T) {
config.SetTestServiceName(t)
// Check for required environment variables
Expand Down Expand Up @@ -215,22 +215,22 @@ func TestServiceLifecycleIntegration(t *testing.T) {
}
})

t.Run("DescribeService", func(t *testing.T) {
t.Run("GetService", func(t *testing.T) {
if serviceID == "" {
t.Skip("No service ID available from create test")
}

t.Logf("Describing service: %s", serviceID)
t.Logf("Getting service details: %s", serviceID)

output, err := executeIntegrationCommand(
"service", "describe", serviceID,
"service", "get", serviceID,
"--output", "json",
)

t.Logf("Raw service describe output: %s", output)
t.Logf("Raw service get output: %s", output)

if err != nil {
t.Fatalf("Service describe failed: %v\nOutput: %s", err, output)
t.Fatalf("Service get failed: %v\nOutput: %s", err, output)
}

// Parse JSON to verify service details
Expand Down Expand Up @@ -389,14 +389,14 @@ func TestServiceLifecycleIntegration(t *testing.T) {

t.Logf("Verifying service %s no longer exists", deletedServiceID)

// Try to describe the deleted service - should fail
// Try to get the deleted service - should fail
output, err := executeIntegrationCommand(
"service", "describe", deletedServiceID,
"service", "get", deletedServiceID,
)

// We expect this to fail since the service should be deleted
if err == nil {
t.Errorf("Expected service describe to fail for deleted service, but got output: %s", output)
t.Errorf("Expected service get to fail for deleted service, but got output: %s", output)
}

// Check that error indicates service not found
Expand Down Expand Up @@ -533,8 +533,8 @@ func TestServiceNotFound(t *testing.T) {
reason string
}{
{
name: "service describe",
args: []string{"service", "describe", nonExistentServiceID},
name: "service get",
args: []string{"service", "get", nonExistentServiceID},
expectedExitCode: ExitServiceNotFound,
},
{
Expand Down Expand Up @@ -707,8 +707,8 @@ func TestAuthenticationErrorsIntegration(t *testing.T) {
args: []string{"service", "list", "--project-id", projectID},
},
{
name: "service describe",
args: []string{"service", "describe", "non-existent-service", "--project-id", projectID},
name: "service get",
args: []string{"service", "get", "non-existent-service", "--project-id", projectID},
},
{
name: "service create",
Expand Down
23 changes: 12 additions & 11 deletions internal/tiger/cmd/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func buildServiceCmd() *cobra.Command {
}

// Add all subcommands
cmd.AddCommand(buildServiceDescribeCmd())
cmd.AddCommand(buildServiceGetCmd())
cmd.AddCommand(buildServiceListCmd())
cmd.AddCommand(buildServiceCreateCmd())
cmd.AddCommand(buildServiceDeleteCmd())
Expand All @@ -44,31 +44,32 @@ func buildServiceCmd() *cobra.Command {
return cmd
}

// serviceDescribeCmd represents the describe command under service
func buildServiceDescribeCmd() *cobra.Command {
// buildServiceGetCmd represents the get command under service
func buildServiceGetCmd() *cobra.Command {
var withPassword bool

cmd := &cobra.Command{
Use: "describe [service-id]",
Short: "Show detailed information about a service",
Use: "get [service-id]",
Aliases: []string{"describe", "show"},
Short: "Show detailed information about a service",
Long: `Show detailed information about a specific database service.

The service ID can be provided as an argument or will use the default service
from your configuration. This command displays comprehensive information about
the service including configuration, status, endpoints, and resource usage.

Examples:
# Describe default service
tiger service describe
# Get default service details
tiger service get

# Describe specific service
tiger service describe svc-12345
# Get specific service details
tiger service get svc-12345

# Get service details in JSON format
tiger service describe svc-12345 --output json
tiger service get svc-12345 --output json

# Get service details in YAML format
tiger service describe svc-12345 --output yaml`,
tiger service get svc-12345 --output yaml`,
RunE: func(cmd *cobra.Command, args []string) error {
// Get config
cfg, err := config.Load()
Expand Down
12 changes: 6 additions & 6 deletions internal/tiger/cmd/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,7 @@ func TestAutoGeneratedServiceName(t *testing.T) {
}
}

func TestServiceDescribe_NoServiceID(t *testing.T) {
func TestServiceGet_NoServiceID(t *testing.T) {
tmpDir := setupServiceTest(t)

// Set up config with project ID but no default service ID
Expand All @@ -577,8 +577,8 @@ func TestServiceDescribe_NoServiceID(t *testing.T) {
}
defer func() { getAPIKeyForService = originalGetAPIKey }()

// Execute service describe command without service ID
_, err, _ = executeServiceCommand("service", "describe")
// Execute service get command without service ID
_, err, _ = executeServiceCommand("service", "get")
if err == nil {
t.Fatal("Expected error when no service ID is provided or configured")
}
Expand All @@ -588,7 +588,7 @@ func TestServiceDescribe_NoServiceID(t *testing.T) {
}
}

func TestServiceDescribe_NoAuth(t *testing.T) {
func TestServiceGet_NoAuth(t *testing.T) {
tmpDir := setupServiceTest(t)

// Set up config with project ID and service ID
Expand All @@ -608,8 +608,8 @@ func TestServiceDescribe_NoAuth(t *testing.T) {
}
defer func() { getAPIKeyForService = originalGetAPIKey }()

// Execute service describe command
_, err, _ = executeServiceCommand("service", "describe")
// Execute service get command
_, err, _ = executeServiceCommand("service", "get")
if err == nil {
t.Fatal("Expected error when not authenticated")
}
Expand Down
46 changes: 23 additions & 23 deletions internal/tiger/mcp/service_tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,24 +79,24 @@ func setServiceIDSchemaProperties(schema *jsonschema.Schema) {
schema.Properties["service_id"].Pattern = "^[a-z0-9]{10}$"
}

// ServiceShowInput represents input for service_show
type ServiceShowInput struct {
// ServiceGetInput represents input for service_get
type ServiceGetInput struct {
ServiceID string `json:"service_id"`
}

func (ServiceShowInput) Schema() *jsonschema.Schema {
schema := util.Must(jsonschema.For[ServiceShowInput](nil))
func (ServiceGetInput) Schema() *jsonschema.Schema {
schema := util.Must(jsonschema.For[ServiceGetInput](nil))
setServiceIDSchemaProperties(schema)
return schema
}

// ServiceShowOutput represents output for service_show
type ServiceShowOutput struct {
// ServiceGetOutput represents output for service_get
type ServiceGetOutput struct {
Service ServiceDetail `json:"service"`
}

func (ServiceShowOutput) Schema() *jsonschema.Schema {
return util.Must(jsonschema.For[ServiceShowOutput](nil))
func (ServiceGetOutput) Schema() *jsonschema.Schema {
return util.Must(jsonschema.For[ServiceGetOutput](nil))
}

// ServiceDetail represents detailed service information
Expand Down Expand Up @@ -231,19 +231,19 @@ func (s *Server) registerServiceTools() {
},
}, s.handleServiceList)

// service_show
// service_get
mcp.AddTool(s.mcpServer, &mcp.Tool{
Name: "service_show",
Title: "Show Service Details",
Name: "service_get",
Title: "Get Service Details",
Description: "Get detailed information for a specific database service. " +
"Returns connection endpoints, replica configuration, resource allocation, creation time, and status.",
InputSchema: ServiceShowInput{}.Schema(),
OutputSchema: ServiceShowOutput{}.Schema(),
InputSchema: ServiceGetInput{}.Schema(),
OutputSchema: ServiceGetOutput{}.Schema(),
Annotations: &mcp.ToolAnnotations{
ReadOnlyHint: true,
Title: "Show Service Details",
Title: "Get Service Details",
},
}, s.handleServiceShow)
}, s.handleServiceGet)

// service_create
mcp.AddTool(s.mcpServer, &mcp.Tool{
Expand Down Expand Up @@ -327,21 +327,21 @@ func (s *Server) handleServiceList(ctx context.Context, req *mcp.CallToolRequest
return nil, output, nil
}

// handleServiceShow handles the service_show MCP tool
func (s *Server) handleServiceShow(ctx context.Context, req *mcp.CallToolRequest, input ServiceShowInput) (*mcp.CallToolResult, ServiceShowOutput, error) {
// handleServiceGet handles the service_get MCP tool
func (s *Server) handleServiceGet(ctx context.Context, req *mcp.CallToolRequest, input ServiceGetInput) (*mcp.CallToolResult, ServiceGetOutput, error) {
// Load config and validate project ID
cfg, err := s.loadConfigWithProjectID()
if err != nil {
return nil, ServiceShowOutput{}, err
return nil, ServiceGetOutput{}, err
}

// Create fresh API client with current credentials
apiClient, err := s.createAPIClient()
if err != nil {
return nil, ServiceShowOutput{}, err
return nil, ServiceGetOutput{}, err
}

logging.Debug("MCP: Showing service details",
logging.Debug("MCP: Getting service details",
zap.String("project_id", cfg.ProjectID),
zap.String("service_id", input.ServiceID))

Expand All @@ -351,16 +351,16 @@ func (s *Server) handleServiceShow(ctx context.Context, req *mcp.CallToolRequest

resp, err := apiClient.GetProjectsProjectIdServicesServiceIdWithResponse(ctx, cfg.ProjectID, input.ServiceID)
if err != nil {
return nil, ServiceShowOutput{}, fmt.Errorf("failed to get service details: %w", err)
return nil, ServiceGetOutput{}, fmt.Errorf("failed to get service details: %w", err)
}

// Handle API response
if resp.StatusCode() != 200 {
return nil, ServiceShowOutput{}, resp.JSON4XX
return nil, ServiceGetOutput{}, resp.JSON4XX
}

service := *resp.JSON200
output := ServiceShowOutput{
output := ServiceGetOutput{
Service: s.convertToServiceDetail(service),
}

Expand Down
Loading