RemoteAgent.run rejects successful API responses whose body omits the `error` field (`result.error !== null` trap) · Issue #2637 · mcp-use/mcp-use · GitHub
Skip to content

RemoteAgent.run rejects successful API responses whose body omits the error field (result.error !== null trap) #2637

Closed

Description

@BlueX888

Describe the bug

RemoteAgent.run (the hosted/remote-agent execution path of @mcp-use/agent) treats a successful HTTP 200 response as a failure whenever the JSON body does not contain an explicit error: null key. The guard at libraries/typescript/packages/agent/src/agents/remote.ts:347 reads:

if (result.status === "error" || result.error !== null) {

For a success body like {"result": "The answer"} the error key is simply absent, so result.error is undefined, and undefined !== null is true — the success response is treated as a failure and run() throws Remote agent execution failed: [object Object] (the message comes from the interpolation at remote.ts:348, result.error ?? String(result)). Only bodies that literally contain error: null pass through as successes.

To Reproduce

Steps to reproduce the behavior:

  1. Save the test below as libraries/typescript/packages/agent/tests/repro-remote-error-field.test.ts (fetch is stubbed — no network is contacted).
  2. Run cd libraries/typescript/packages/agent && npx vitest run tests/repro-remote-error-field.test.ts.
  3. See cases A and B — plain success bodies — being rejected.
import { describe, it } from "vitest";
import { RemoteAgent } from "../src/agents/remote.js";

/** Install a fetch stub that replays the given JSON bodies with HTTP 200. */
function stubFetch(bodies: unknown[]) {
  let i = 0;
  (globalThis as any).fetch = async () => {
    const body = bodies[Math.min(i, bodies.length - 1)];
    i++;
    return {
      ok: true,
      status: 200,
      json: async () => body,
      text: async () => JSON.stringify(body),
    };
  };
}

function makeAgent(): RemoteAgent {
  return new RemoteAgent({
    agentId: "agent-1",
    apiKey: "test-key",
    baseUrl: "http://127.0.0.1:1", // never contacted; fetch is stubbed
  });
}

describe("RemoteAgent handling of successful responses", () => {
  it("response body {result: string} (no error field)", async () => {
    stubFetch([{ id: "chat-1" }, { result: "The answer" }]);
    const outcome = await makeAgent().run({ prompt: "hi" }).then(
      (v) => `RESOLVED with ${JSON.stringify(v)}`,
      (e) => `REJECTED with ${e.constructor.name}: ${e.message}`
    );
    console.log(`[case A] body {result:"The answer"} -> ${outcome}`);
  });

  it("response body {status: success, result: string}", async () => {
    stubFetch([{ id: "chat-1" }, { status: "success", result: "The answer" }]);
    const outcome = await makeAgent().run({ prompt: "hi" }).then(
      (v) => `RESOLVED with ${JSON.stringify(v)}`,
      (e) => `REJECTED with ${e.constructor.name}: ${e.message}`
    );
    console.log(`[case B] body {status:"success",result:..} -> ${outcome}`);
  });

  it("response body {result: string, error: null} (control)", async () => {
    stubFetch([{ id: "chat-1" }, { result: "The answer", error: null }]);
    const outcome = await makeAgent().run({ prompt: "hi" }).then(
      (v) => `RESOLVED with ${JSON.stringify(v)}`,
      (e) => `REJECTED with ${e.constructor.name}: ${e.message}`
    );
    console.log(`[case C] body {result:..,error:null} (ctrl) -> ${outcome}`);
  });
});

Expected behavior

A 200 response whose body is {"result": "The answer"} (no error key) should resolve with "The answer". The code's own intent says as much:

  • The comment directly above the guard (remote.ts:346) states: // Check for actual error conditions (not just presence of error field) — an absent field is not an error condition.
  • Ten lines below, the same function treats { result: ... } as the success shape (remote.ts:374-375): if (typeof result === "object" && result !== null && "result" in result) { return result.result as T; }.
  • parseStructuredResponse (remote.ts:107-125) likewise accepts { result: ... } bodies.

The result.error !== null check rejects exactly those success bodies.

Screenshots

N/A — plain text output only.

Desktop (please complete the following information:)

  • OS: macOS (darwin 25.6.0, arm64)
  • Browser: N/A (Node library)
  • Version: @mcp-use/agent 2.0.16 at commit dcaa0b8df5721f11d4d2aefae1f85df2a1903ba5 (repo main), Node v22.23.2, vitest 4.1.10

Smartphone (please complete the following information:)

N/A

Additional context

Actual output (verbatim, from the run above; reproduced identically by three independent runs):

[case A] body {result:"The answer"}        -> REJECTED with Error: Remote agent execution failed: Remote agent execution failed: [object Object]
[case B] body {status:"success",result:..} -> REJECTED with Error: Remote agent execution failed: Remote agent execution failed: [object Object]
[case C] body {result:..,error:null} (ctrl) -> RESOLVED with "The answer"

Root cause: libraries/typescript/packages/agent/src/agents/remote.ts:347 — result.error !== null is true when the error key is absent (undefined !== null), so every 200 body without an explicit error key is classified as an error. Two adjacent details make genuine failures harder to diagnose too: remote.ts:348 interpolates result.error ?? String(result), which renders an object error value as [object Object], and the String(result).includes("failed to initialize") check at remote.ts:354 is dead for object bodies for the same reason (String({}) is "[object Object]").

Reachability: RemoteAgent is exported from the package root (src/index.ts:56), and both MCPAgent variants constructed with agentId delegate run() to it (src/agents/mcp_agent.ts:505-507, src/agents/mcp_agent_langchain.ts:1203-1205). Any consumer of the hosted-agent API whose backend returns a 200 body without an explicit error: null key hits this on every call.

A small fix would be to only fire on a real error value — e.g. if (result.status === "error" || result.error != null) (loose equality also covers undefined) — and interpolate JSON.stringify(result.error) instead of result.error ?? String(result) so genuine error messages stay readable. Happy to open a PR with this approach if wanted.

Related: I searched the issue tracker (RemoteAgent, remote agent execution failed, remote error null) and found no existing report covering this — #2413 concerns local-mode maxSteps, #1581 the browser entry bundling.

Disclosure: found and verified by an AI bug-hunting agent; the reproduction above was run locally against the repository at the commit stated, with no network access.

Activity

  1. BlueX888 commented on Sep 23, 2026

    @BlueX888
    ContributorAuthor

    Reproduced this. Root cause: libraries/typescript/packages/agent/src/agents/remote.ts:347 — result.error !== null is true when the error key is absent (undefined !== null), so every 200 body without an explicit error key is classified as a failure.

    Minimal reproduction:

    cd libraries/typescript/packages/agent && npx vitest run tests/repro-remote-error-field.test.ts
    

    Observed output:

    [case A] body {result:"The answer"}         -> REJECTED with Error: Remote agent execution failed: Remote agent execution failed: [object Object]
    [case B] body {status:"success",result:..} -> REJECTED with Error: Remote agent execution failed: Remote agent execution failed: [object Object]
    [case C] body {result:..,error:null} (ctrl) -> RESOLVED with "The answer"
    

    Happy to open a PR with this fix, or to be assigned if you prefer to take it.

  2. khandrew1 commented on Oct 19, 2026

    @khandrew1
    Contributor

    Fixed by #2651 and released in @mcp-use/agent 2.0.25 (mcp-use 2.11.0). Closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions