# Task ID: 8

**Title:** Implement mcpctl Server Management Commands

**Status:** pending

**Dependencies:** 7, 4

**Priority:** high

**Description:** Add kubectl-style commands for listing, describing, and managing MCP servers (get, describe, apply, delete).

**Details:**

Add server management commands:

```typescript
// commands/servers.ts
program
  .command('get')
  .description('Display resources')
  .argument('<resource>', 'Resource type (servers, projects, profiles, instances)')
  .option('-o, --output <format>', 'Output format (json, yaml, table)', 'table')
  .action(async (resource, options) => {
    const client = getClient();
    const data = await client.get(`/api/${resource}`);
    formatOutput(data, options.output);
  });

program
  .command('describe')
  .description('Show detailed information')
  .argument('<resource>', 'Resource type')
  .argument('<name>', 'Resource name or ID')
  .action(async (resource, name) => {
    const client = getClient();
    const data = await client.get(`/api/${resource}/${name}`);
    console.log(yaml.dump(data));
  });

program
  .command('apply')
  .description('Apply configuration from file')
  .option('-f, --file <path>', 'Path to config file')
  .action(async (options) => {
    const config = yaml.load(fs.readFileSync(options.file, 'utf8'));
    const client = getClient();
    // Determine resource type and apply
    const result = await client.post(`/api/${config.kind.toLowerCase()}s`, config.spec);
    console.log(`${config.kind} "${result.name}" created/updated`);
  });

// Resource definition format (kubectl-style)
// server.yaml:
// kind: McpServer
// spec:
//   name: slack
//   type: slack
//   command: npx
//   args: ["@anthropic/mcp-server-slack"]
//   envTemplate:
//     SLACK_TOKEN: "required"
```

Output formatters:
```typescript
function formatOutput(data: any[], format: string) {
  if (format === 'json') return console.log(JSON.stringify(data, null, 2));
  if (format === 'yaml') return console.log(yaml.dump(data));
  // Table format
  console.table(data.map(d => ({ NAME: d.name, TYPE: d.type, STATUS: d.status })));
}
```

**Test Strategy:**

Test each command with mock API responses. Test output formatting for all formats. Test apply command with various YAML configurations.

## Subtasks

### 8.1. Write TDD test suites for output formatters and resource type validation

**Status:** pending  
**Dependencies:** None  

Create comprehensive Vitest test suites for the output formatting system (JSON, YAML, table formats) and resource type validation BEFORE implementing the actual formatters. Tests must cover all output modes, --no-headers option, exit codes, field selection, and filtering capabilities.

**Details:**

Create src/cli/tests/unit/formatters directory with the following test files:

1. formatters/output-formatter.test.ts:
   - Test JSON output produces valid, jq-parseable JSON with proper indentation
   - Test YAML output produces valid yaml.dump() output
   - Test table output produces awk/grep-parseable format with consistent column widths
   - Test --no-headers option removes header row from table output
   - Test --field flag filters output to only specified fields (e.g., --field name,status)
   - Test formatOutput() handles empty arrays gracefully
   - Test formatOutput() handles single object vs array correctly

2. formatters/resource-types.test.ts:
   - Test valid resource types (servers, projects, profiles, instances) are accepted
   - Test invalid resource types throw appropriate error with helpful message
   - Test resource type normalization (singular to plural: server -> servers)
   - Test case-insensitive resource matching

3. Create src/cli/tests/fixtures/mock-resources.ts with sample data:
   - mockServers: Array of McpServer objects with name, type, status fields
   - mockProfiles: Array of McpProfile objects
   - mockProjects: Array of Project objects
   - mockInstances: Array of McpInstance objects with running/stopped status

4. Create exit-codes.test.ts:
   - Test exit code 0 for successful operations
   - Test exit code 1 for general errors
   - Test exit code 2 for resource not found
   - Test exit code 3 for connection/network errors

All tests should initially fail (TDD red phase). Use Vitest mocks for console.log/console.table to capture output.

### 8.2. Write TDD test suites for get, describe, apply, and delete commands

**Status:** pending  
**Dependencies:** 8.1  

Create comprehensive Vitest test suites for all four server management commands BEFORE implementation. Tests must mock API responses and verify correct CLI argument parsing, option handling, error states, and output generation.

**Details:**

Create src/cli/tests/unit/commands directory with the following test files:

1. commands/get.test.ts:
   - Test 'mcpctl get servers' calls GET /api/servers
   - Test 'mcpctl get profiles' calls GET /api/profiles
   - Test 'mcpctl get projects' calls GET /api/projects
   - Test 'mcpctl get instances' calls GET /api/instances
   - Test '-o json' outputs JSON format
   - Test '-o yaml' outputs YAML format
   - Test '-o table' (default) outputs table format
   - Test '--no-headers' removes table header
   - Test '--field name,status' filters columns
   - Test invalid resource type shows error and exits with code 2
   - Test network error exits with code 3

2. commands/describe.test.ts:
   - Test 'mcpctl describe server slack' calls GET /api/servers/slack
   - Test output is always YAML format for detailed view
   - Test 404 response shows 'Resource not found' message
   - Test includes all resource fields in output

3. commands/apply.test.ts:
   - Test '-f server.yaml' reads file and sends POST/PUT request
   - Test validates 'kind' field in YAML (McpServer, McpProfile, Project)
   - Test validates required 'spec' field exists
   - Test creates new resource when name doesn't exist (POST)
   - Test updates existing resource when name exists (PUT)
   - Test SECURITY: rejects file paths with directory traversal (../, etc.)
   - Test SECURITY: validates YAML doesn't contain shell injection patterns
   - Test SECURITY: limits file size to prevent DoS
   - Test handles malformed YAML with clear error message

4. commands/delete.test.ts:
   - Test 'mcpctl delete server slack' calls DELETE /api/servers/slack
   - Test prompts for confirmation unless --force is passed
   - Test --force skips confirmation
   - Test 404 shows appropriate 'not found' message

Create src/cli/tests/fixtures/yaml-configs/ directory with sample YAML files for testing apply command.

### 8.3. Implement output formatters with reusable architecture and SRE-friendly features

**Status:** pending  
**Dependencies:** 8.1  

Implement the output formatting system with JSON, YAML, and table formats. Include --no-headers option for scripting, parseable exit codes, and --field flag for field selection. Design for reusability across all CLI commands.

**Details:**

Create src/cli/src/formatters directory with the following modules:

1. formatters/output-formatter.ts:
```typescript
export type OutputFormat = 'json' | 'yaml' | 'table';

export interface FormatOptions {
  format: OutputFormat;
  noHeaders?: boolean;
  fields?: string[];
}

export function formatOutput<T extends Record<string, unknown>>(data: T | T[], options: FormatOptions): string;
export function printOutput<T extends Record<string, unknown>>(data: T | T[], options: FormatOptions): void;
```

2. formatters/json-formatter.ts:
   - Produce properly indented JSON (2 spaces)
   - Ensure jq pipeline compatibility
   - Support field filtering before output

3. formatters/yaml-formatter.ts:
   - Use js-yaml for YAML output
   - Ensure kubectl-compatible YAML formatting
   - Support field filtering

4. formatters/table-formatter.ts:
   - Fixed-width columns for awk/grep parseability
   - Tab-separated values for reliable parsing
   - UPPERCASE header row (NAME, TYPE, STATUS)
   - --no-headers support for scripting
   - Auto-truncate long values with ellipsis

5. formatters/field-selector.ts:
   - Parse --field flag (comma-separated field names)
   - Support nested fields with dot notation (spec.name)
   - Validate fields exist in data schema

6. Create exit-codes.ts:
```typescript
export const EXIT_CODES = {
  SUCCESS: 0,
  ERROR: 1,
  NOT_FOUND: 2,
  NETWORK_ERROR: 3,
  VALIDATION_ERROR: 4
} as const;
```

7. Create resource-types.ts:
   - Define valid resource types enum
   - Singular to plural normalization
   - Validation function with helpful error messages

Ensure all formatters are pure functions for easy unit testing. Export via barrel file formatters/index.ts.

### 8.4. Implement get, describe, apply, and delete commands with security hardening

**Status:** pending  
**Dependencies:** 8.2, 8.3  

Implement all four server management commands using Commander.js. The apply command must include comprehensive security validation for file paths and YAML content to prevent injection attacks and path traversal vulnerabilities.

**Details:**

Create src/cli/src/commands/resources.ts with all four commands:

1. 'get' command implementation:
   - Register as 'mcpctl get <resource> [options]'
   - Options: -o/--output (json|yaml|table), --no-headers, --field <fields>
   - Call getClient().get(`/api/${resource}`) from cli-client
   - Pass result to formatOutput() with options
   - Handle errors with appropriate exit codes

2. 'describe' command implementation:
   - Register as 'mcpctl describe <resource> <name>'
   - Call getClient().get(`/api/${resource}/${name}`)
   - Output always in YAML format with full details
   - Handle 404 with NOT_FOUND exit code

3. 'apply' command implementation with SECURITY HARDENING:
   - Register as 'mcpctl apply -f <file>'
   - SECURITY: Validate file path
     - Reject paths containing '..' (directory traversal)
     - Reject absolute paths outside allowed directories
     - Validate file extension is .yaml or .yml
     - Check file size < 1MB to prevent DoS
   - SECURITY: Validate YAML content
     - Parse with yaml.load() using safe schema
     - Validate 'kind' is in allowed list (McpServer, McpProfile, Project)
     - Validate 'spec' object has expected structure
     - Reject YAML with embedded functions or anchors if not needed
   - Determine create vs update by checking if resource exists
   - POST for create, PUT for update
   - Output success message with resource name

4. 'delete' command implementation:
   - Register as 'mcpctl delete <resource> <name>'
   - Prompt for confirmation using inquirer
   - Support --force flag to skip confirmation
   - Call DELETE /api/${resource}/${name}
   - Output success/failure message

5. Register all commands in command registry from Task 7.

Create src/cli/src/utils/path-validator.ts for reusable path validation.
Create src/cli/src/utils/yaml-validator.ts for YAML security checks.

### 8.5. Create integration tests with mock API server and comprehensive security review

**Status:** pending  
**Dependencies:** 8.4  

Build complete integration test suite running all commands against a mock mcpd API server. Perform and document comprehensive security review of the apply command and path handling. Ensure all SRE and data engineer requirements are met.

**Details:**

Create src/cli/tests/integration directory with:

1. integration/mock-mcpd-server.ts:
   - Express server mocking all required endpoints:
     - GET/POST /api/servers, GET/PUT/DELETE /api/servers/:name
     - GET/POST /api/profiles, GET/PUT/DELETE /api/profiles/:name  
     - GET/POST /api/projects, GET/PUT/DELETE /api/projects/:name
     - GET /api/instances
   - Configurable responses for testing error scenarios
   - Realistic latency simulation

2. integration/commands.test.ts:
   - Full command execution using execSync against built CLI
   - Test get/describe/apply/delete for all resource types
   - Test all output formats work correctly
   - Test error handling and exit codes
   - Test --no-headers and --field flags

3. integration/sre-compatibility.test.ts:
   - Test output is grep-friendly: 'mcpctl get servers | grep running'
   - Test output is awk-friendly: 'mcpctl get servers | awk "{print $1}"'
   - Test JSON is jq-friendly: 'mcpctl get servers -o json | jq .[]'
   - Test exit codes work with shell scripts: 'mcpctl get servers || echo failed'
   - Test --no-headers works for scripting

4. integration/data-engineer.test.ts:
   - Test --field flag for selecting specific columns
   - Test filtering capabilities for data pipeline inspection
   - Test describe provides full resource details

5. Update src/cli/SECURITY_REVIEW.md:
   - Document apply command security measures:
     - Path traversal prevention with test evidence
     - File size limits
     - YAML injection prevention
   - Document how credentials are NOT logged
   - Document safe handling of user-supplied input
   - Include security test results and findings

6. Verify all requirements from task context:
   - TDD: All unit and integration tests pass
   - LOCAL DEV: Mock server works offline
   - SECURITY: Document YAML injection risks and mitigations
   - ARCHITECTURE: Formatters are reusable across commands
   - SRE: Output is parseable, exit codes documented
   - DATA ENGINEER: Field selection and filtering works
