Skip to content

[Client] HttpTransport resumes an Amp-suspended Fiber with a JSON-RPC error #504

Open

Description

@ineersa

Describe the bug

HTTP MCP initialization fails with a type error when the HTTP request uses Amp:

HTTP request failed: Amp\Internal\FutureIterator::consume(): Return value must be of type ?array, Mcp\Schema\JsonRpc\Error returned

The suspected cause is competing ownership of the same Fiber. HttpTransport::processFiber() checks whether the active Fiber is suspended, then resumes it with a JSON-RPC response or timeout error. However, an Amp-backed PSR-18 client can suspend that Fiber during sendRequest(), before the SDK reaches its own response wait. The timeout error then reaches Amp's suspension instead of the SDK's response wait.

To Reproduce

Observed in a CLI application using mcp/sdk v0.7.1, with HTTP client discovery left enabled. Three separate HTTP MCP servers fail during initialization, each after approximately 30 seconds.

Steps to isolate the suspected integration conflict:

  1. Use the SDK HTTP transport with Symfony's Psr18Client, explicitly backed by AmpHttpClient, so backend selection does not depend on the host's cURL capabilities.
  2. Configure an HTTP MCP endpoint and an initialization timeout.
  3. Connect through the SDK client.
  4. Observe whether the SDK resumes the Fiber while Amp is waiting for HTTP I/O, causing the type error above when the initialization timeout expires.

These isolation steps are proposed, not a separately executed minimal reproduction. The application failure and logs below are observed. We have not yet reproduced the failure with an explicitly injected Amp client or rerun it on v0.8.1.

Expected behavior

The HTTP transport must not resume a Fiber suspended by the injected HTTP client. Initialization should complete, or fail with the relevant transport or initialization error, without passing a JSON-RPC object into Amp's internal suspension.

If Fiber-suspending PSR-18 clients are unsupported, that restriction should be documented and enforced rather than failing with an unrelated return-type error.

Logs

Sanitized excerpts from the same application session on 2026-09-09. Each failure was for a different HTTP MCP server:

21:55:26.003694Z MCP server discovery failed
HTTP request failed: Amp\Internal\FutureIterator::consume(): Return value must be of type ?array, Mcp\Schema\JsonRpc\Error returned

21:55:56.000764Z MCP server discovery failed
HTTP request failed: Amp\Internal\FutureIterator::consume(): Return value must be of type ?array, Mcp\Schema\JsonRpc\Error returned

21:56:26.000375Z MCP server discovery failed
HTTP request failed: Amp\Internal\FutureIterator::consume(): Return value must be of type ?array, Mcp\Schema\JsonRpc\Error returned

Additional context

Application dependency lock:

  • mcp/sdk: v0.7.1, commit 785fc3b9b7006ecc8a73322c939d96a4a7154345
  • symfony/http-client: v8.1.5
  • amphp/amp: v3.1.3
  • amphp/http-client: v5.3.6
  • revolt/event-loop: v1.0.9

Relevant source:

  • SDK v0.7.1 HttpTransport: connect() starts an active Fiber. send() calls the PSR-18 client inside it. processFiber() resumes the active Fiber with a JSON-RPC error when a pending request times out.
  • Amp v3.1.3 FutureIterator: consume(): ?array returns the value supplied to its event-loop suspension. Receiving an MCP error object here matches the observed exception.
  • SDK v0.8.1 HttpTransport: source inspection shows the same unconditional resume of a suspended active Fiber. The same logic was also present on main when checked on 2026-09-09. This is source evidence, not a runtime reproduction on those revisions.

Explicitly injecting Symfony's Psr18Client backed by CurlHttpClient is a proposed application workaround. It has not yet been validated for this failure. It would avoid the Amp path but would not fix Fiber ownership in the SDK.

A search of upstream issues and PRs for Amp, amphp, FutureIterator, and fiber did not find a matching report. PR #425 addresses HTTP status handling, not this suspension conflict.

Activity

  1. added
    ClientIssues & PRs related to the Client component
    on Oct 7, 2026
  2. aton-of-data commented on Oct 11, 2026

    @aton-of-data
    Contributor

    Reproduced on main at b35b52d. I used an injected Psr18Client(new AmpHttpClient()) (symfony/http-client 8.1.8, amphp/http-client 5.3.6, amp 3.1.3, revolt 1.0.9, PHP 8.4.26) and also amphp's own PSR-18 adapter on PHP 8.3. Both give the same error:

    HTTP request failed: Amp\Internal\FutureIterator::consume(): Return value must be of type ?array, Mcp\Schema\JsonRpc\Error returned
    

    Slow servers are not the cause. It also fails against the repo's own discovery-calculator example, which answers at once. The request never leaves the process, and php -S logs no connection. Amp only ever gets the timeout Error, never a response.

    • connect() runs the handshake in a Fiber (HttpTransport.php:114-121), and send() calls sendRequest() inside it (HttpTransport.php:184, from Protocol.php:660).
    • Inside a fiber, Revolt does a bare Fiber::suspend() and waits for its event loop. It only runs the loop itself from {main}, and here nothing runs it.
    • processFiber() sees the fiber suspended. After the init timeout it resumes it with Error::forInternalError('Request timed out') (HttpTransport.php:664-667). That value lands in Amp's suspension and triggers the TypeError, which is caught at 185-187. With the default 30s timeout I got it after 29.6s.
    • Moving only sendRequest() out of the fiber is not enough. Reading the Amp body (getContents(), line 219) suspends the same way.

    Controls: Native (8.4) and Curl/discovery (8.3) both connect. Against a server that sleeps 10s they fail with Initialization failed: Request timed out. Discovery itself picks AmpHttpClient when amphp/http-client is installed and ext-curl is missing, and I got the same error that way.

    Fix direction: inside the request fiber, send() suspends with the message, and tick() sends it from {main}, where Revolt runs its own loop. I have this as a patch with a unit test. The test's fake client suspends like Revolt in sendRequest() and in body reads. It fails on main and passes with the patch. Unit suite (1801), phpstan and cs are green. Both Amp clients then connect and list tools. Happy to open a PR.

    Not run: v0.7.1 or v0.8.1, ReactPHP clients, listen: true with Amp. DualEraElicitationTest times out in my env both with and without the patch.

    Repro
    $client = Client::builder()->setInitTimeout(3)->setMaxRetries(0)->build();
    $client->connect(new HttpTransport('http://127.0.0.1:8101', httpClient: new Psr18Client(new AmpHttpClient())));
    // FAILED after 2.14s: ConnectionException: HTTP request failed: ...FutureIterator::consume()...
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

    ClientIssues & PRs related to the Client componentbugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions