Skip to content

[Server] Add outgoing (elicitation, sampling) request and client response events - #386

Open
omouren wants to merge 10 commits into
modelcontextprotocol:mainfrom
FrenchFounders:feature/elicit-lifecycle-events
Open

[Server] Add outgoing (elicitation, sampling) request and client response events#386
omouren wants to merge 10 commits into
modelcontextprotocol:mainfrom
FrenchFounders:feature/elicit-lifecycle-events

Conversation

@omouren

@omouren omouren commented Jun 24, 2026

Copy link
Copy Markdown

Introduce ServerRequestEvent and ClientResponseEvent to provide full observability over the server's interaction with the client on handshake-era connections. This includes requests sent by the server (e.g. elicitation/create, sampling/createMessage) and the client's replies to those requests.

Additionally, update ResponseEvent and ErrorEvent to be dispatched when a suspended Fiber completes. This ensures that deferred responses from long-running, asynchronous operations are also observable via the existing event mechanisms.

On the modern protocol revision (2026-07-28), there is no server-initiated JSON-RPC over HTTP, so ServerRequestEvent / ClientResponseEvent are not dispatched there, they would misrepresent the wire. Instead, StatelessProtocol dispatches the same lifecycle events as the handshake path:

  • RequestEvent : including multi-round-trip retries, when the client sends inputResponses, they are already lifted onto the session as InputContext
  • ResponseEvent : an elicitation ask is a successful result (InputRequiredResult, resultType: input_required)
  • ErrorEvent : handler-related failures

Documentation is updated in docs/advanced/events.md.

Motivation and Context

We wanted to be able to log every step of our MCP usage, including elicitation, across both protocol eras.

How Has This Been Tested?

  • Unit tests added/extended in ProtocolTest, StatelessProtocolTest, and MessageFactoryTest
  • Full unit suite passes locally (1596 tests)
  • Manual testing on handshake-era connections (elicitation events) + MRTR

Breaking Changes

  • [BC Break] TransportInterface gains setFiberTerminationHandler(callable $handler): void. Custom transport implementations must implement it, SDK transports (StdioTransport, StreamableHttpTransport) already do via ManagesTransportCallbacks.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Introduce `OutgoingRequestEvent` and `ClientResponseEvent` to provide full
observability over the server's interaction with the client. This includes
requests sent by the server (e.g., elicitation, sampling) and the client's
replies to those requests.

Additionally, update `ResponseEvent` and `ErrorEvent` to be dispatched when
a suspended Fiber completes. This ensures that deferred responses from
long-running, asynchronous operations are also observable via the existing
event mechanisms.

@chr-hertel chr-hertel left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @omouren - looks like a valuable addition to me, left some comments and didn't test yet.

Comment thread src/JsonRpc/MessageFactory.php Outdated
Comment thread src/Event/OutgoingRequestEvent.php Outdated
Comment thread src/Server/Protocol.php Outdated
Comment thread src/Server/Protocol.php Outdated
@chr-hertel chr-hertel changed the title Add outgoing (elicitation, sampling) request and client response events [Server] Add outgoing (elicitation, sampling) request and client response events Jul 5, 2026
@chr-hertel chr-hertel added Server Issues & PRs related to the Server component enhancement Request for a new feature that's not currently supported labels Jul 5, 2026
@chr-hertel chr-hertel added this to the 0.7.0 milestone Jul 5, 2026
@chr-hertel chr-hertel added the needs more work Not ready to be merged yet, needs additional follow-up from the author(s). label Jul 5, 2026
omouren and others added 4 commits July 21, 2026 15:20
Co-authored-by: Christopher Hertel <mail@christopher-hertel.de>
…age factory

The `OutgoingRequestEvent` is renamed to `ServerRequestEvent` to improve clarity, as it is dispatched when the server sends a request to the client.

Additionally, the `MessageFactory` has been refactored. The `createMessage` and `createFromArray` methods are consolidated into a single public `createFromArray`, simplifying message creation and error handling logic.
@omouren
omouren requested a review from chr-hertel July 21, 2026 13:30
@omouren

omouren commented Jul 21, 2026

Copy link
Copy Markdown
Author

Thanks for the review @chr-hertel 👍

@chr-hertel chr-hertel modified the milestones: 0.7.0, 0.8.0 Jul 27, 2026
@chr-hertel chr-hertel added needs review PR needs code review by maintainer and removed needs more work Not ready to be merged yet, needs additional follow-up from the author(s). labels Aug 14, 2026
@chr-hertel

Copy link
Copy Markdown
Member

Hey @omouren this needs a rebase - and more importantly, please double-check the handling in context of spec version 2026-07-28 with StatelessProtocol

@chr-hertel chr-hertel modified the milestones: 0.8.0, 0.9.0 Aug 23, 2026
@omouren
omouren force-pushed the feature/elicit-lifecycle-events branch from dbf4c37 to a626b04 Compare August 25, 2026 13:35
@omouren

omouren commented Aug 25, 2026

Copy link
Copy Markdown
Author

Hello @chr-hertel
I’ve rebased onto main and resolved the merge conflict.

I haven’t looked at spec revision 2026-07-28 / StatelessProtocol yet, and I’m not entirely sure how to test the new spec for now. I retested with the previous spec and the events still work as expected there.

@omouren

omouren commented Aug 31, 2026

Copy link
Copy Markdown
Author

Hello @chr-hertel

Follow-up on 2026-07-28 / StatelessProtocol.

I did not map elicitation onto ServerRequestEvent / ClientResponseEvent as there is no server-initiated JSON-RPC on that revision, so those events would lie about the wire. They stay for the handshake-era.

I updated the StatelessProtocol to dispatch the same lifecycle events as the handshake path:

  • RequestEvent (after InputContext is lifted)
  • ResponseEvent / ErrorEvent from finalize()
  • An ask is a ResponseEvent + InputRequiredResult
  • The answer is the next RequestEvent + InputContext

Introduces `RequestEvent`, `ResponseEvent`, and `ErrorEvent` for the stateless 2026-07-28 protocol revision. This provides extensibility points to observe and modify server operations during request processing.

`RequestEvent` now includes `InputContext` for multi-round trip retries, and `ResponseEvent` dispatches `InputRequiredResult` for elicitation on the modern era. `ErrorEvent` is dispatched for all handler-related exceptions.

Documentation is updated to clarify event behavior across protocol eras.
@omouren
omouren force-pushed the feature/elicit-lifecycle-events branch from a0e32c9 to 7aa5c7c Compare August 31, 2026 13:51
Comment thread src/Event/ClientResponseEvent.php Outdated
Comment thread docs/advanced/events.md Outdated
Comment thread src/Server/Protocol.php
}
}

$session->save();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be moved into the if (null !== $parentRequest), no?

or maybe just invert that to keep the if small

if (null === $parentRequest) {
    return $finalResult;
}

@omouren omouren Sep 2, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chr-hertel
save() has to stay even when $parentRequest is null. pull() already forgets SESSION_FIBER_PARENT_REQUEST. Without a save that deletion never hits the session store, so the next createWithId() would see the parent request again.

@chr-hertel chr-hertel added the breaking change Breaking the Backwards Compatibility Promise label Sep 1, 2026
Comment thread src/Server/Transport/TransportInterface.php
Comment thread docs/advanced/events.md Outdated
…ling

Introduce `ServerRequestEvent` and `ClientResponseEvent` to provide observability for handshake-era server-to-client requests like `elicitation/create` and `sampling/createMessage`, and their corresponding client replies.

Dispatch `ResponseEvent` and `ErrorEvent` when a suspended Fiber completes, ensuring a consistent event lifecycle for multi-round trip operations. This also applies to modern protocol elicitation.

Update `ClientResponseEvent::getId()` to correctly return `null` for error responses that lack an ID. Enhance fiber termination to always persist session state, preventing data loss even when a parent request is absent.

This includes a BC break requiring custom `TransportInterface` implementations to include `setFiberTerminationHandler()`.
@omouren

omouren commented Sep 2, 2026

Copy link
Copy Markdown
Author

Thanks for the review @chr-hertel, all done, except for the null === $parentRequest, you can see my comment above.

@omouren
omouren requested a review from chr-hertel September 7, 2026 08:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking change Breaking the Backwards Compatibility Promise enhancement Request for a new feature that's not currently supported needs review PR needs code review by maintainer Server Issues & PRs related to the Server component

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants