Skip to content

Client configuration

Everything ClientBuilder takes before build().

Client info

Required before build(). Stamped into every request's _meta.

->setClientInfo(
    name: 'my-client',
    version: '1.0.0',
    title: 'My Friendly Client',
    description: 'A short description carried in every request.',
    websiteUrl: 'https://example.com',
)

Client capabilities

Optional. Defaults to an empty ClientCapabilities. Stamped into every request's _meta, so the server can read what the client supports.

use Nexus\Mcp\Core\Schema\ClientCapabilities;

->setClientCapabilities(new ClientCapabilities(elicitation: []))

Logger

Optional. Defaults to Psr\Log\NullLogger. Transport errors and uncaught notification-handler exceptions are logged here.

->setLogger($psrLogger)

In-flight dispatch cap

On by default at ClientBuilder::DEFAULT_MAX_IN_FLIGHT (1024). It bounds what the server can make this client do. Without a cap, a server that emits notifications faster than your listeners return accumulates one coroutine per message until the process runs out of memory.

->setMaxInFlightDispatches(64)   // tighter
->setMaxInFlightDispatches(null) // uncapped, at that risk

Shedding

Past the cap, a server-to-client request is answered -32000 (SdkErrorCode::Overloaded), and a notification is dropped without a reply. A client cannot answer a notification, so dropping is the only backpressure available to it. The first drop logs a warning that names the method. The log is throttled, so it cannot become the flood.

A subscription delivery is the exception. Shedding one ends its stream, and await() throws SubscriptionDeliveryDroppedException. Call listen() again to resubscribe.

Sizing the cap

Size it by inbound messages, not by operations. One tools/call that reports progress spends a slot per progress notification, so a handful of concurrent calls can occupy far more than a handful of slots.

notifications/cancelled is exempt only when it frees work. The first one that names a request in flight is admitted past the cap and cancelled on admission. Any other cancellation meets the cap.

Request-ID and progress-token factories

Optional. Both default to a monotonically-incrementing factory: 1, 2, … for request IDs, and progress-1, progress-2, … for progress tokens. Override either when you need a different ID scheme, for example UUIDs.

->setRequestIdFactory(static fn(): string => Uuid::v4()->toRfc4122())
->setProgressTokenFactory(static fn(): string => Uuid::v4()->toRfc4122())

Each factory is a \Closure(): (int|non-empty-string). It must return a value unique among the requests in flight at the same time.

Extra _meta keys

Optional. setMetaExtrasFactory() takes a \Closure(): array<non-empty-string, mixed> the client calls once per outbound request. The keys it returns join the request's _meta beside the lifecycle fields. Use it to propagate the W3C trace context the spec reserves for OpenTelemetry (traceparent, tracestate, baggage):

->setMetaExtrasFactory(static fn(): array => ['traceparent' => $propagator->currentTraceparent()])

The factory runs per request, so a value that changes between calls, such as the current span, stays correct. A lifecycle key it returns, for example progressToken, is ignored.