Skip to content

Latest commit

 

History

History
524 lines (400 loc) · 10.5 KB

File metadata and controls

524 lines (400 loc) · 10.5 KB

Error Handling

← Back to Index


Understanding and handling errors when using the HacknPlan MCP server.

Error Types

1. HacknPlanAPIError

API returned an error response.

Status Codes:

  • 400 - Bad Request (invalid parameters)
  • 401 - Unauthorized (invalid API key)
  • 403 - Forbidden (insufficient permissions)
  • 404 - Not Found (entity doesn't exist)
  • 429 - Rate Limited (too many requests)
  • 500-599 - Server Error (HacknPlan API issue)

Example:

try {
  await callTool('get_work_item', {
    workItemId: 99999  // Doesn't exist
  });
} catch (error) {
  console.error(`API Error ${error.status}: ${error.message}`);
  console.log('Suggestion:', error.suggestion);
}

// Output:
// API Error 404: Work item #99999 not found
// Suggestion: Use list_work_items to see available work items

2. ValidationError

Input validation failed before API call.

Common Causes:

  • Missing required fields
  • Invalid field types
  • Out-of-range values
  • Malformed data

Example:

try {
  await callTool('create_work_items', {
    items: [{
      title: 'My Task'
      // Missing required field: categoryId
    }]
  });
} catch (error) {
  console.error('Validation failed:');
  error.errors.forEach(err => {
    console.log(`  - ${err.field}: ${err.message}`);
  });
}

// Output:
// Validation failed:
//   - categoryId: Required field missing

Best Practice: Validate inputs client-side before calling tools.


3. EntityNotFoundError

Entity (project, work item, board, etc.) not found.

Example:

try {
  await callTool('get_board', {
    boardId: 99999
  });
} catch (error) {
  console.error(error.message);
  console.log('Suggestion:', error.suggestion);
  console.log('Available:', error.context.availableBoards);
}

// Output:
// Board #99999 not found
// Suggestion: Use list_boards to see available boards
// Available: [
//   { boardId: 650299, name: "Sprint 3" },
//   { boardId: 650300, name: "Sprint 4" }
// ]

Smart Error Messages: The MCP automatically fetches available entities to help you correct the error.


4. AmbiguousMatchError

Multiple entities match search term (name resolution).

Example:

try {
  await callTool('start_task', {
    workItemId: 'Implement authentication'  // Multiple matches
  });
} catch (error) {
  console.error(error.message);
  console.log('Matches found:');
  error.context.matches.forEach(match => {
    console.log(`  #${match.workItemId}: ${match.title}`);
  });
  console.log('\nSuggestion:', error.suggestion);
}

// Output:
// Multiple work items match "Implement authentication"
// Matches found:
//   #123: Implement authentication backend
//   #124: Implement authentication frontend
//
// Suggestion: Use exact title or numeric ID instead

Solution: Be more specific or use numeric ID.


5. ConfigurationError

Missing required configuration (API key, environment variables).

Example:

// HACKNPLAN_API_KEY not set

try {
  await callTool('list_projects', {});
} catch (error) {
  console.error(error.message);
  console.log('Fix:', error.suggestion);
}

// Output:
// Missing required configuration: HACKNPLAN_API_KEY
// Fix: Set HACKNPLAN_API_KEY environment variable or pass API key in config

Solution: Check environment variables and server configuration.


Automatic Retry Logic

All HTTP requests automatically retry on transient failures:

Rate Limits (429)

Behavior:

  • Exponential backoff: 1s → 2s → 4s (max 10s)
  • Max 3 retries
  • Honors Retry-After header if present

Example:

// Triggers rate limit on 1st attempt
await callTool('list_work_items', { projectId: 230954 });

// Server automatically retries:
// Attempt 1: Immediate (429 Rate Limited)
// Attempt 2: Wait 1s → Success
// ✅ Returns result without error

User Experience: Transparent - you don't see the retries.


Server Errors (5xx)

Behavior:

  • Exponential backoff: 1s → 2s → 4s
  • Max 3 retries
  • Gives up after 3 failures

Example:

try {
  await callTool('get_work_item', { workItemId: 123 });
} catch (error) {
  // Only thrown after 3 failed retries
  console.error('Server error after retries:', error.message);
}

Network Errors

Behavior:

  • DNS failures, timeouts, connection refused
  • Automatic retry with backoff
  • Max 3 retries

Timeout: 60 seconds per request (configurable)


Error Handling Patterns

Pattern 1: Basic Try-Catch

try {
  const result = await callTool('create_work_items', {
    items: [{ title: 'Task', categoryId: 'programming' }]
  });

  console.log('Created:', result.items[0].workItemId);

} catch (error) {
  console.error('Failed to create work item:', error.message);

  if (error.suggestion) {
    console.log('Suggestion:', error.suggestion);
  }
}

Pattern 2: Error Type Checking

try {
  await callTool('get_work_item', { workItemId: 99999 });

} catch (error) {
  if (error.name === 'EntityNotFoundError') {
    console.log('Work item not found');
    console.log('Available items:', error.context.availableItems);

  } else if (error.name === 'ValidationError') {
    console.log('Invalid input:', error.errors);

  } else if (error.status === 429) {
    console.log('Rate limited - request retried automatically');

  } else {
    console.error('Unexpected error:', error);
  }
}

Pattern 3: Graceful Degradation

async function getWorkItem(id) {
  try {
    return await callTool('get_work_item', { workItemId: id });

  } catch (error) {
    if (error.status === 404) {
      // Work item deleted or doesn't exist
      return null;
    }

    // Re-throw other errors
    throw error;
  }
}

// Usage
const task = await getWorkItem(123);
if (task) {
  console.log('Task:', task.title);
} else {
  console.log('Task not found (may be deleted)');
}

Pattern 4: Retry with User Confirmation

async function deleteWithRetry(workItemIds) {
  try {
    // Stage deletion
    const staged = await callTool('delete_work_items', {
      workItemIds: workItemIds
    });

    // Show preview to user
    console.log(`Deleting ${staged.preview.totalItems} items:`);
    staged.preview.samples.forEach(item => {
      console.log(`  - ${item.title} (${item.status})`);
    });

    // User confirms (simplified)
    const confirmed = true; // In real app: await getUserConfirmation()

    if (confirmed) {
      // Confirm deletion
      return await callTool('confirm_deletion', {
        confirmationToken: staged.confirmationToken,
        acknowledged: 'DELETE'
      });
    } else {
      // Cancel
      return await callTool('cancel_deletion', {
        confirmationToken: staged.confirmationToken
      });
    }

  } catch (error) {
    if (error.name === 'ValidationError') {
      console.error('Invalid work item IDs:', error.errors);
    } else {
      throw error;
    }
  }
}

Pattern 5: Batch Error Handling

const result = await callTool('create_work_items', {
  items: [
    { title: 'Task 1', categoryId: 'programming' },    // ✅ Valid
    { title: 'Task 2', categoryId: 'nonexistent' },    // ❌ Invalid
    { title: 'Task 3', categoryId: 'design' }          // ✅ Valid
  ]
});

// Check for partial failures
if (result.failed > 0) {
  console.error(`${result.failed} items failed:`);

  result.errors.forEach(err => {
    console.error(`  Item ${err.index}: ${err.error}`);
    console.error(`  Input:`, err.item);
  });
}

// Process successful items
if (result.successful > 0) {
  console.log(`✅ Created ${result.successful} items`);

  result.items
    .filter(item => item !== null)
    .forEach(item => {
      console.log(`  #${item.workItemId}: ${item.title}`);
    });
}

Common Error Scenarios

Scenario 1: Invalid API Key

Error:

HacknPlanAPIError: 401 Unauthorized
Invalid or expired API key

Solution:

# Check environment variable
echo $HACKNPLAN_API_KEY

# Update if needed
export HACKNPLAN_API_KEY="your-new-key"

# Restart MCP server
claude mcp restart hacknplan

Scenario 2: Rate Limit Exceeded

Error:

HacknPlanAPIError: 429 Too Many Requests
Rate limit exceeded. Retry after 60 seconds.

Solution:

  • Server automatically retries with backoff
  • If persistent: reduce request frequency
  • Consider caching results locally

Scenario 3: Work Item Not Found

Error:

EntityNotFoundError: Work item #99999 not found

Solution:

// List available work items
const items = await callTool('list_work_items', {
  projectId: 230954
});

// Find correct ID
const target = items.items.find(i => i.title.includes('authentication'));
console.log('Correct ID:', target.workItemId);

Scenario 4: Validation Failure

Error:

ValidationError: Validation failed for item 0
- categoryId: Required field missing

Solution:

// Add required fields
await callTool('create_work_items', {
  items: [{
    title: 'My Task',
    categoryId: 'programming'  // ✅ Added required field
  }]
});

Best Practices

1. Always Handle Errors

// ❌ Bad - unhandled errors crash the app
const result = await callTool('get_work_item', { workItemId: 123 });

// ✅ Good - graceful error handling
try {
  const result = await callTool('get_work_item', { workItemId: 123 });
} catch (error) {
  console.error('Error:', error.message);
}

2. Check Validation Before API Calls

// ✅ Validate client-side
function validateWorkItem(item) {
  if (!item.title) throw new Error('title required');
  if (!item.categoryId) throw new Error('categoryId required');
  return true;
}

// Only call API if valid
if (validateWorkItem(myItem)) {
  await callTool('create_work_items', { items: [myItem] });
}

3. Use Error Context

catch (error) {
  // Error often includes helpful context
  if (error.context) {
    console.log('Available options:', error.context);
  }

  if (error.suggestion) {
    console.log('Try:', error.suggestion);
  }
}

4. Log Errors for Debugging

catch (error) {
  // Log full error for debugging
  console.error('Full error:', JSON.stringify(error, null, 2));

  // Show user-friendly message
  alert(`Failed to create task: ${error.message}`);
}

See Also: