Skip to content

Fix: HTTP API client shows cryptic errors instead of server messages #315

@juniorcammel

Description

@juniorcammel

Bug Description

When MCP server HTTP requests fail (404, 500, etc.), the HTTP API client attempts to parse the error response as JSON without first checking response.ok. This causes a cryptic "Unexpected end of JSON input" error instead of showing the actual server error message.

Impact

  • Severity: High - Makes debugging MCP server issues extremely difficult
  • User Experience: Users see misleading "JSON parse error" instead of helpful error messages like "Server not found" or actual backend errors
  • Affected Operations: All MCP server HTTP operations (add, edit, test, delete)

Affected Code

File: apps/ui/src/lib/http-api-client.ts
Lines: 165-251 (all four HTTP methods: post, get, put, httpDelete)

Current Behavior (Incorrect)

private async post<T>(endpoint: string, body?: unknown): Promise<T> {
  const response = await fetch(`${this.serverUrl}${endpoint}`, {
    method: 'POST',
    headers: this.getHeaders(),
    body: body ? JSON.stringify(body) : undefined,
  });
  return response.json(); // ❌ No error checking - fails with cryptic message
}

Example Error:

  • Request: POST /api/mcp/test with invalid server ID
  • Response: 404 Not Found with body {"error": "Server not found"}
  • User Sees: "Unexpected end of JSON input" ❌
  • Should See: "Server not found" ✅

Expected Behavior

All HTTP methods should validate response.ok before attempting to parse JSON, and provide meaningful error messages.

Proposed Solution

Add response.ok validation to all four HTTP methods with proper error handling:

private async post<T>(endpoint: string, body?: unknown): Promise<T> {
  const response = await fetch(`${this.serverUrl}${endpoint}`, {
    method: 'POST',
    headers: this.getHeaders(),
    body: body ? JSON.stringify(body) : undefined,
  });

  // ✅ Check response status before parsing
  if (!response.ok) {
    let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
    try {
      const errorData = await response.json();
      if (errorData.error) {
        errorMessage = errorData.error;
      }
    } catch {
      // If parsing JSON fails, use status text
    }
    throw new Error(errorMessage);
  }

  return response.json();
}

Apply the same pattern to:

  • private async get<T>(endpoint: string): Promise<T>
  • private async put<T>(endpoint: string, body?: unknown): Promise<T>
  • private async httpDelete<T>(endpoint: string): Promise<T>

Testing

Before Fix:

  • Test invalid MCP server → Shows "Unexpected end of JSON input"
  • Test 404 endpoint → Shows "Unexpected end of JSON input"

After Fix:

  • Test invalid MCP server → Shows "Server not found"
  • Test 404 endpoint → Shows "HTTP 404: Not Found"
  • Test 500 error → Shows actual backend error message

Additional Context

  • This is a prerequisite fix for proper error visibility in race condition scenarios (see related issue about MCP server auto-test timing)
  • No breaking changes - only improves error reporting
  • Performance impact: negligible (~1-5ms for error path)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions