Skip to content

Error Handling

Comprehensive guide to handling errors in the AT Protocol MCP Server.

Error Types

The server implements a hierarchical error system:

1. MCP Protocol Errors

Standard JSON-RPC 2.0 errors:

CodeNameDescription
-32700Parse ErrorInvalid JSON received
-32600Invalid RequestInvalid JSON-RPC request
-32601Method Not FoundMethod does not exist
-32602Invalid ParamsInvalid method parameters
-32603Internal ErrorInternal server error

2. AT Protocol Errors

AT Protocol-specific errors:

TypeDescriptionHTTP Status
AuthenticationErrorAuthentication failed or required401
AuthorizationErrorInsufficient permissions403
NotFoundErrorResource not found404
RateLimitErrorRate limit exceeded429
ValidationErrorInvalid input data400
NetworkErrorNetwork or connection issue503

3. Application Errors

Server-specific errors:

TypeDescription
ToolExecutionErrorTool execution failed
ResourceReadErrorResource read failed
PromptGenerationErrorPrompt generation failed
ConfigurationErrorInvalid configuration

Error Structure

All errors follow a consistent structure:

typescript
{
  error: {
    code: number;           // JSON-RPC error code
    message: string;        // Human-readable message
    data?: {                // Additional error details
      type: string;         // Error type
      details: string;      // Detailed description
      context?: object;     // Error context
      stack?: string;       // Stack trace (dev only)
    }
  }
}

Common Errors

Authentication Required

Error:

json
{
  "error": {
    "code": -32603,
    "message": "Authentication required",
    "data": {
      "type": "AuthenticationError",
      "details": "This operation requires authentication. Please provide ATPROTO_IDENTIFIER and ATPROTO_PASSWORD.",
      "tool": "create_post"
    }
  }
}

Solution:

bash
# Set authentication credentials
export ATPROTO_IDENTIFIER="your-handle.bsky.social"
export ATPROTO_PASSWORD="your-app-password"
atproto-mcp

Invalid Parameters

Error:

json
{
  "error": {
    "code": -32602,
    "message": "Invalid params",
    "data": {
      "type": "ValidationError",
      "details": "Post text cannot exceed 300 characters",
      "field": "text",
      "value": "..."
    }
  }
}

Solution:

  • Validate input before calling tools
  • Check parameter requirements
  • Follow schema constraints

Rate Limit Exceeded

Error:

json
{
  "error": {
    "code": -32603,
    "message": "Rate limit exceeded",
    "data": {
      "type": "RateLimitError",
      "details": "Too many requests. Please wait before retrying.",
      "retryAfter": 60,
      "limit": 100,
      "remaining": 0
    }
  }
}

Solution:

typescript
// Implement exponential backoff
async function retryWithBackoff(fn, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (error.type === 'RateLimitError' && i < maxRetries - 1) {
        const delay = Math.pow(2, i) * 1000; // Exponential backoff
        await new Promise(resolve => setTimeout(resolve, delay));
        continue;
      }
      throw error;
    }
  }
}

Resource Not Found

Error:

json
{
  "error": {
    "code": -32603,
    "message": "Resource not found",
    "data": {
      "type": "NotFoundError",
      "details": "Post not found or has been deleted",
      "uri": "at://did:plc:abc123.../app.bsky.feed.post/xyz789"
    }
  }
}

Solution:

  • Verify resource URIs are correct
  • Check if resource still exists
  • Handle deleted content gracefully

Network Error

Error:

json
{
  "error": {
    "code": -32603,
    "message": "Network error",
    "data": {
      "type": "NetworkError",
      "details": "Failed to connect to AT Protocol service",
      "service": "https://bsky.social"
    }
  }
}

Solution:

  • Check internet connectivity
  • Verify service URL is correct
  • Check for service outages
  • Implement retry logic

Error Handling Patterns

Try-Catch Pattern

typescript
try {
  const result = await tool.execute(params);
  return result;
} catch (error) {
  if (error.type === 'AuthenticationError') {
    // Handle authentication error
    console.error('Please authenticate first');
  } else if (error.type === 'RateLimitError') {
    // Handle rate limit
    console.error('Rate limit exceeded, retrying...');
    await delay(error.retryAfter * 1000);
    return retry();
  } else {
    // Handle other errors
    console.error('Operation failed:', error.message);
  }
}

Graceful Degradation

typescript
async function getUserProfile(actor: string) {
  try {
    // Try authenticated request first
    return await getProfileAuthenticated(actor);
  } catch (error) {
    if (error.type === 'AuthenticationError') {
      // Fall back to public request
      return await getProfilePublic(actor);
    }
    throw error;
  }
}

Retry with Backoff

typescript
async function executeWithRetry(
  operation: () => Promise<any>,
  maxRetries: number = 3,
  baseDelay: number = 1000
) {
  let lastError;
  
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await operation();
    } catch (error) {
      lastError = error;
      
      // Don't retry on certain errors
      if (error.type === 'ValidationError' || 
          error.type === 'AuthenticationError') {
        throw error;
      }
      
      // Calculate delay with exponential backoff
      const delay = baseDelay * Math.pow(2, attempt);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  
  throw lastError;
}

Circuit Breaker

typescript
class CircuitBreaker {
  private failures = 0;
  private lastFailureTime = 0;
  private readonly threshold = 5;
  private readonly timeout = 60000; // 1 minute
  
  async execute(operation: () => Promise<any>) {
    // Check if circuit is open
    if (this.isOpen()) {
      throw new Error('Circuit breaker is open');
    }
    
    try {
      const result = await operation();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }
  
  private isOpen(): boolean {
    if (this.failures >= this.threshold) {
      const timeSinceLastFailure = Date.now() - this.lastFailureTime;
      return timeSinceLastFailure < this.timeout;
    }
    return false;
  }
  
  private onSuccess() {
    this.failures = 0;
  }
  
  private onFailure() {
    this.failures++;
    this.lastFailureTime = Date.now();
  }
}

Error Logging

Development

bash
# Enable debug logging
LOG_LEVEL=debug atproto-mcp

Output includes:

  • Full error messages
  • Stack traces
  • Request/response details
  • Timing information

Production

bash
# Use info or warn level
LOG_LEVEL=info atproto-mcp

Output includes:

  • Error messages (sanitized)
  • Error types and codes
  • Context information
  • No sensitive data

Structured Logging

typescript
logger.error('Tool execution failed', {
  tool: 'create_post',
  error: error.message,
  type: error.type,
  code: error.code,
  context: {
    user: 'did:plc:...',
    timestamp: new Date().toISOString()
  }
});

Error Recovery

Automatic Recovery

The server implements automatic recovery for:

  • Session expiration: Automatically refreshes sessions
  • Network timeouts: Retries with exponential backoff
  • Temporary failures: Implements circuit breaker pattern

Manual Recovery

For persistent errors:

bash
# Restart the server
atproto-mcp

# Clear cache
rm -rf ~/.cache/atproto-mcp

# Reset configuration
cp .env.example .env

Debugging Errors

Enable Debug Mode

bash
atproto-mcp --log-level debug

Check Logs

bash
# View recent logs
tail -f ~/.local/share/atproto-mcp/logs/server.log

# Search for errors
grep ERROR ~/.local/share/atproto-mcp/logs/server.log

Validate Configuration

bash
# Check environment variables
env | grep ATPROTO

# Test authentication
atproto-mcp --log-level debug

Test Tools

bash
# Test specific tool
echo '{"method":"tools/call","params":{"name":"search_posts","arguments":{"q":"test"}}}' | atproto-mcp

Error Prevention

Input Validation

typescript
// Validate before calling tools
function validatePostText(text: string) {
  if (!text || text.trim().length === 0) {
    throw new ValidationError('Post text cannot be empty');
  }
  if (text.length > 300) {
    throw new ValidationError('Post text cannot exceed 300 characters');
  }
}

Rate Limiting

typescript
// Implement client-side rate limiting
class RateLimiter {
  private requests: number[] = [];
  private readonly limit = 100;
  private readonly window = 60000; // 1 minute
  
  async checkLimit() {
    const now = Date.now();
    this.requests = this.requests.filter(time => now - time < this.window);
    
    if (this.requests.length >= this.limit) {
      throw new RateLimitError('Client rate limit exceeded');
    }
    
    this.requests.push(now);
  }
}

Connection Management

typescript
// Implement connection pooling
const agent = new AtpAgent({
  service: 'https://bsky.social',
  persistSession: true,
  maxRetries: 3,
  timeout: 30000
});

Best Practices

Error Handling

  • ✅ Always handle errors explicitly
  • ✅ Provide meaningful error messages
  • ✅ Log errors with context
  • ✅ Implement retry logic for transient errors
  • ✅ Use circuit breakers for failing services

Error Reporting

  • ✅ Include error type and code
  • ✅ Provide actionable error messages
  • ✅ Sanitize sensitive data
  • ✅ Include relevant context
  • ✅ Log errors for debugging

Error Recovery

  • ✅ Implement automatic recovery when possible
  • ✅ Provide manual recovery options
  • ✅ Document recovery procedures
  • ✅ Test error scenarios
  • ✅ Monitor error rates

Next Steps


Previous: Tools & Resources ← | Next: Troubleshooting

Released under the MIT License.