Skip to content

mcporter collapses error payloads to {} when a JSON result includes data plus other fields #105

@AielloChan

Description

@AielloChan

Summary

mcporter currently unwraps any object that contains a data field and returns data directly during call-result parsing. This breaks tool responses whose payload shape includes both data and other meaningful fields such as status, error, summary, message, or meta.

In practice, an error payload like this:

{
  "status": "error",
  "summary": "Failed to create base: name is required",
  "error": {
    "type": "USER_ERROR",
    "message": "Base name is required and cannot be empty or only whitespace",
    "retryable": false,
    "code": "INVALID_NAME"
  },
  "data": {},
  "meta": {},
  "trace_id": "trace-123"
}

is rendered by mcporter as:

{}

That output is misleading because the actual error fields are dropped before rendering.

Impact

  • Users see {} instead of the real error payload.
  • Structured error fields become inaccessible from createCallResult().json().
  • Tool failures can look like empty success-ish payloads, which makes debugging much harder.
  • The issue affects multiple result paths, not just one rendering mode.

Affected Paths

The problem happens in the shared call-result parsing logic in src/result-utils.ts.

Current behavior:

  • Objects with a json field are unwrapped to json.
  • Objects with a data field are also unwrapped to data.
  • The data branch does not check whether data is the only field.

That means these kinds of payloads are incorrectly collapsed:

  • structuredContent objects that include data plus error metadata
  • parsed JSON from text blocks
  • parsed JSON from JSON content blocks

Root Cause

tryParseJson() treats any object containing data as a wrapper envelope and returns only record.data.

The code path effectively behaves like:

if ('data' in record) {
  return record.data ?? null;
}

That assumption is too broad. data is not always a transport envelope. In many APIs it is just one field among several top-level fields.

Expected Behavior

mcporter should only unwrap data when data is the only key in the object.

Expected rules:

  • If an object has json, unwrap json.
  • If an object has only data, unwrap data.
  • If an object has data and any other keys, preserve the full object.

Reproduction

One minimal shape that reproduces the problem:

{
  "content": [
    {
      "type": "text",
      "text": "{\"status\":\"error\",\"summary\":\"Failed\",\"data\":{},\"meta\":{}}"
    }
  ]
}

Observed behavior:

  • createCallResult(response).json() returns {}.

Expected behavior:

  • createCallResult(response).json() returns the full parsed object with status, summary, data, and meta.

Proposed Fix

Refine envelope unwrapping so data is only treated as an envelope when it is the sole field on the object.

Suggested logic:

if ('json' in record) {
  return record.json ?? null;
}
if ('data' in record) {
  return Object.keys(record).length === 1 ? (record.data ?? null) : record;
}

Validation

Add regression coverage for both of these cases:

  1. structuredContent with data plus error fields should preserve the full object.
  2. Text or JSON content that parses into an object with data plus error fields should also preserve the full object.

Why This Matters

This bug hides the most important part of failed tool results: the actual error payload. For users debugging MCP integrations, seeing {} instead of the real error structure creates the false impression that the server returned nothing useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions