Understanding and handling errors when using the HacknPlan MCP server.
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 itemsInput 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 missingBest Practice: Validate inputs client-side before calling tools.
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.
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 insteadSolution: Be more specific or use numeric ID.
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 configSolution: Check environment variables and server configuration.
All HTTP requests automatically retry on transient failures:
Behavior:
- Exponential backoff: 1s → 2s → 4s (max 10s)
- Max 3 retries
- Honors
Retry-Afterheader 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 errorUser Experience: Transparent - you don't see the retries.
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);
}Behavior:
- DNS failures, timeouts, connection refused
- Automatic retry with backoff
- Max 3 retries
Timeout: 60 seconds per request (configurable)
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);
}
}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);
}
}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)');
}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;
}
}
}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}`);
});
}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 hacknplanError:
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
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);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
}]
});// ❌ 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);
}// ✅ 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] });
}catch (error) {
// Error often includes helpful context
if (error.context) {
console.log('Available options:', error.context);
}
if (error.suggestion) {
console.log('Try:', error.suggestion);
}
}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:
- Best Practices - Error prevention tips
- Common Patterns - Error handling in workflows
- API Reference - Full error documentation