Skip to content

Commit dff5fe1

Browse files
refactor: merge ElicitationRequest + ElicitationInvocation into ElicitationContext
Combines the two-argument elicitation handler pattern into a single ElicitationContext type across all three SDKs, matching the existing CommandContext pattern. The context now includes SessionId alongside the request fields (Message, RequestedSchema, Mode, etc.). Changes per language: - .NET: ElicitationContext class, single-arg delegate, Lazy<> cached Ui - Go: ElicitationContext struct, single-arg handler func - Python: ElicitationContext TypedDict, single-arg callable All tests, READMEs, and E2E tests updated.
1 parent 0c4ff33 commit dff5fe1

17 files changed

Lines changed: 116 additions & 116 deletions

dotnet/README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -910,14 +910,15 @@ var session = await client.CreateSessionAsync(new SessionConfig
910910
{
911911
Model = "gpt-5",
912912
OnPermissionRequest = PermissionHandler.ApproveAll,
913-
OnElicitationRequest = async (request, invocation) =>
913+
OnElicitationRequest = async (context) =>
914914
{
915-
// request.Message - Description of what information is needed
916-
// request.RequestedSchema - JSON Schema describing the form fields
917-
// request.Mode - "form" (structured input) or "url" (browser redirect)
918-
// request.ElicitationSource - Origin of the request (e.g. MCP server name)
915+
// context.SessionId - Session that triggered the request
916+
// context.Message - Description of what information is needed
917+
// context.RequestedSchema - JSON Schema describing the form fields
918+
// context.Mode - "form" (structured input) or "url" (browser redirect)
919+
// context.ElicitationSource - Origin of the request (e.g. MCP server name)
919920
920-
Console.WriteLine($"Elicitation from {request.ElicitationSource}: {request.Message}");
921+
Console.WriteLine($"Elicitation from {context.ElicitationSource}: {context.Message}");
921922

922923
// Present UI to the user and collect their response...
923924
return new ElicitationResult

dotnet/src/Session.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -493,8 +493,9 @@ private async Task HandleBroadcastEventAsync(SessionEvent sessionEvent)
493493
: null;
494494

495495
await HandleElicitationRequestAsync(
496-
new ElicitationRequest
496+
new ElicitationContext
497497
{
498+
SessionId = SessionId,
498499
Message = data.Message,
499500
RequestedSchema = schema,
500501
Mode = data.Mode,
@@ -720,14 +721,14 @@ await handler(new CommandContext
720721
/// Dispatches an elicitation.requested event to the registered handler and
721722
/// responds via the ui.handlePendingElicitation RPC. Auto-cancels on handler errors.
722723
/// </summary>
723-
private async Task HandleElicitationRequestAsync(ElicitationRequest request, string requestId)
724+
private async Task HandleElicitationRequestAsync(ElicitationContext context, string requestId)
724725
{
725726
var handler = _elicitationHandler;
726727
if (handler is null) return;
727728

728729
try
729730
{
730-
var result = await handler(request, new ElicitationInvocation { SessionId = SessionId });
731+
var result = await handler(context);
731732
await Rpc.Ui.HandlePendingElicitationAsync(requestId, new SessionUiHandlePendingElicitationRequestResult
732733
{
733734
Action = result.Action,

dotnet/src/Types.cs

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -693,10 +693,14 @@ public interface ISessionUiApi
693693
// ============================================================================
694694

695695
/// <summary>
696-
/// An elicitation request received from the server.
696+
/// Context for an elicitation handler invocation, combining the request data
697+
/// with session context. Mirrors the single-argument pattern of <see cref="CommandContext"/>.
697698
/// </summary>
698-
public class ElicitationRequest
699+
public class ElicitationContext
699700
{
701+
/// <summary>Identifier of the session that triggered the elicitation request.</summary>
702+
public string SessionId { get; set; } = string.Empty;
703+
700704
/// <summary>Message describing what information is needed from the user.</summary>
701705
public string Message { get; set; } = string.Empty;
702706

@@ -713,21 +717,10 @@ public class ElicitationRequest
713717
public string? Url { get; set; }
714718
}
715719

716-
/// <summary>
717-
/// Context for an elicitation handler invocation.
718-
/// </summary>
719-
public class ElicitationInvocation
720-
{
721-
/// <summary>
722-
/// Identifier of the session that triggered the elicitation request.
723-
/// </summary>
724-
public string SessionId { get; set; } = string.Empty;
725-
}
726-
727720
/// <summary>
728721
/// Delegate for handling elicitation requests from the server.
729722
/// </summary>
730-
public delegate Task<ElicitationResult> ElicitationHandler(ElicitationRequest request, ElicitationInvocation invocation);
723+
public delegate Task<ElicitationResult> ElicitationHandler(ElicitationContext context);
731724

732725
// ============================================================================
733726
// Session Capabilities

dotnet/test/ElicitationTests.cs

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public async Task Sends_RequestElicitation_When_Handler_Provided()
7878
var session = await CreateSessionAsync(new SessionConfig
7979
{
8080
OnPermissionRequest = PermissionHandler.ApproveAll,
81-
OnElicitationRequest = (_, _) => Task.FromResult(new ElicitationResult
81+
OnElicitationRequest = _ => Task.FromResult(new ElicitationResult
8282
{
8383
Action = SessionUiElicitationResultAction.Accept,
8484
Content = new Dictionary<string, object>(),
@@ -97,7 +97,7 @@ public async Task Session_With_ElicitationHandler_Reports_Elicitation_Capability
9797
var session = await CreateSessionAsync(new SessionConfig
9898
{
9999
OnPermissionRequest = PermissionHandler.ApproveAll,
100-
OnElicitationRequest = (_, _) => Task.FromResult(new ElicitationResult
100+
OnElicitationRequest = _ => Task.FromResult(new ElicitationResult
101101
{
102102
Action = SessionUiElicitationResultAction.Accept,
103103
Content = new Dictionary<string, object>(),
@@ -231,10 +231,11 @@ public void InputOptions_Has_All_Properties()
231231
}
232232

233233
[Fact]
234-
public void ElicitationRequest_Has_All_Properties()
234+
public void ElicitationContext_Has_All_Properties()
235235
{
236-
var request = new ElicitationRequest
236+
var context = new ElicitationContext
237237
{
238+
SessionId = "session-42",
238239
Message = "Pick a color",
239240
RequestedSchema = new ElicitationSchema
240241
{
@@ -248,28 +249,18 @@ public void ElicitationRequest_Has_All_Properties()
248249
Url = null,
249250
};
250251

251-
Assert.Equal("Pick a color", request.Message);
252-
Assert.NotNull(request.RequestedSchema);
253-
Assert.Equal(ElicitationRequestedDataMode.Form, request.Mode);
254-
Assert.Equal("mcp-server", request.ElicitationSource);
255-
Assert.Null(request.Url);
256-
}
257-
258-
[Fact]
259-
public void ElicitationInvocation_Has_SessionId()
260-
{
261-
var invocation = new ElicitationInvocation
262-
{
263-
SessionId = "session-42"
264-
};
265-
266-
Assert.Equal("session-42", invocation.SessionId);
252+
Assert.Equal("session-42", context.SessionId);
253+
Assert.Equal("Pick a color", context.Message);
254+
Assert.NotNull(context.RequestedSchema);
255+
Assert.Equal(ElicitationRequestedDataMode.Form, context.Mode);
256+
Assert.Equal("mcp-server", context.ElicitationSource);
257+
Assert.Null(context.Url);
267258
}
268259

269260
[Fact]
270261
public async Task Session_Config_OnElicitationRequest_Is_Cloned()
271262
{
272-
ElicitationHandler handler = (_, _) => Task.FromResult(new ElicitationResult
263+
ElicitationHandler handler = _ => Task.FromResult(new ElicitationResult
273264
{
274265
Action = SessionUiElicitationResultAction.Cancel,
275266
});
@@ -288,7 +279,7 @@ public async Task Session_Config_OnElicitationRequest_Is_Cloned()
288279
[Fact]
289280
public void Resume_Config_OnElicitationRequest_Is_Cloned()
290281
{
291-
ElicitationHandler handler = (_, _) => Task.FromResult(new ElicitationResult
282+
ElicitationHandler handler = _ => Task.FromResult(new ElicitationResult
292283
{
293284
Action = SessionUiElicitationResultAction.Cancel,
294285
});
@@ -304,3 +295,4 @@ public void Resume_Config_OnElicitationRequest_Is_Cloned()
304295
Assert.Same(handler, clone.OnElicitationRequest);
305296
}
306297
}
298+

dotnet/test/MultiClientCommandsElicitationTests.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ public async Task Capabilities_Changed_Fires_When_Second_Client_Joins_With_Elici
173173
var session2 = await Client2.ResumeSessionAsync(session1.SessionId, new ResumeSessionConfig
174174
{
175175
OnPermissionRequest = PermissionHandler.ApproveAll,
176-
OnElicitationRequest = (_, _) => Task.FromResult(new ElicitationResult
176+
OnElicitationRequest = _ => Task.FromResult(new ElicitationResult
177177
{
178178
Action = Rpc.SessionUiElicitationResultAction.Accept,
179179
Content = new Dictionary<string, object>(),
@@ -227,7 +227,7 @@ public async Task Capabilities_Changed_Fires_When_Elicitation_Provider_Disconnec
227227
await _client3.ResumeSessionAsync(session1.SessionId, new ResumeSessionConfig
228228
{
229229
OnPermissionRequest = PermissionHandler.ApproveAll,
230-
OnElicitationRequest = (_, _) => Task.FromResult(new ElicitationResult
230+
OnElicitationRequest = _ => Task.FromResult(new ElicitationResult
231231
{
232232
Action = Rpc.SessionUiElicitationResultAction.Accept,
233233
Content = new Dictionary<string, object>(),
@@ -259,3 +259,4 @@ public async Task Capabilities_Changed_Fires_When_Elicitation_Provider_Disconnec
259259
"After elicitation provider disconnects, capability should be removed");
260260
}
261261
}
262+

go/README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -822,12 +822,13 @@ When the server (or an MCP tool) needs to ask the end-user a question, it sends
822822
```go
823823
session, err := client.CreateSession(ctx, &copilot.SessionConfig{
824824
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
825-
OnElicitationRequest: func(req copilot.ElicitationRequest, inv copilot.ElicitationInvocation) (copilot.ElicitationResult, error) {
826-
// req.Message — what's being asked
827-
// req.RequestedSchema — form schema (if mode is "form")
828-
// req.Mode — "form" or "url"
829-
// req.ElicitationSource — e.g. MCP server name
830-
// req.URL — browser URL (if mode is "url")
825+
OnElicitationRequest: func(ctx copilot.ElicitationContext) (copilot.ElicitationResult, error) {
826+
// ctx.SessionID — session that triggered the request
827+
// ctx.Message — what's being asked
828+
// ctx.RequestedSchema — form schema (if mode is "form")
829+
// ctx.Mode — "form" or "url"
830+
// ctx.ElicitationSource — e.g. MCP server name
831+
// ctx.URL — browser URL (if mode is "url")
831832

832833
// Return the user's response
833834
return copilot.ElicitationResult{

go/internal/e2e/commands_and_elicitation_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ func TestUIElicitationCallback(t *testing.T) {
159159

160160
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
161161
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
162-
OnElicitationRequest: func(req copilot.ElicitationRequest, inv copilot.ElicitationInvocation) (copilot.ElicitationResult, error) {
162+
OnElicitationRequest: func(ctx copilot.ElicitationContext) (copilot.ElicitationResult, error) {
163163
return copilot.ElicitationResult{Action: "accept", Content: map[string]any{}}, nil
164164
},
165165
})
@@ -250,7 +250,7 @@ func TestUIElicitationMultiClient(t *testing.T) {
250250
session2, err := client2.ResumeSession(t.Context(), session1.SessionID, &copilot.ResumeSessionConfig{
251251
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
252252
DisableResume: true,
253-
OnElicitationRequest: func(req copilot.ElicitationRequest, inv copilot.ElicitationInvocation) (copilot.ElicitationResult, error) {
253+
OnElicitationRequest: func(ctx copilot.ElicitationContext) (copilot.ElicitationResult, error) {
254254
return copilot.ElicitationResult{Action: "accept", Content: map[string]any{}}, nil
255255
},
256256
})
@@ -311,7 +311,7 @@ func TestUIElicitationMultiClient(t *testing.T) {
311311
_, err = client3.ResumeSession(t.Context(), session1.SessionID, &copilot.ResumeSessionConfig{
312312
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
313313
DisableResume: true,
314-
OnElicitationRequest: func(req copilot.ElicitationRequest, inv copilot.ElicitationInvocation) (copilot.ElicitationResult, error) {
314+
OnElicitationRequest: func(ctx copilot.ElicitationContext) (copilot.ElicitationResult, error) {
315315
return copilot.ElicitationResult{Action: "accept", Content: map[string]any{}}, nil
316316
},
317317
})
@@ -355,3 +355,4 @@ func TestUIElicitationMultiClient(t *testing.T) {
355355
unsubDisabled()
356356
})
357357
}
358+

go/session.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -577,16 +577,15 @@ func (s *Session) getElicitationHandler() ElicitationHandler {
577577

578578
// handleElicitationRequest dispatches an elicitation.requested event to the registered handler
579579
// and sends the result back via the RPC layer. Auto-cancels on error.
580-
func (s *Session) handleElicitationRequest(request ElicitationRequest, requestID string) {
580+
func (s *Session) handleElicitationRequest(elicitCtx ElicitationContext, requestID string) {
581581
handler := s.getElicitationHandler()
582582
if handler == nil {
583583
return
584584
}
585585

586586
ctx := context.Background()
587-
invocation := ElicitationInvocation{SessionID: s.SessionID}
588587

589-
result, err := handler(request, invocation)
588+
result, err := handler(elicitCtx)
590589
if err != nil {
591590
// Handler failed — attempt to cancel so the request doesn't hang.
592591
s.RPC.Ui.HandlePendingElicitation(ctx, &rpc.SessionUIHandlePendingElicitationParams{
@@ -976,7 +975,8 @@ func (s *Session) handleBroadcastEvent(event SessionEvent) {
976975
if event.Data.URL != nil {
977976
url = *event.Data.URL
978977
}
979-
s.handleElicitationRequest(ElicitationRequest{
978+
s.handleElicitationRequest(ElicitationContext{
979+
SessionID: s.SessionID,
980980
Message: message,
981981
RequestedSchema: requestedSchema,
982982
Mode: mode,

go/session_test.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,7 @@ func TestSession_ElicitationHandler(t *testing.T) {
470470
t.Error("Expected nil handler before registration")
471471
}
472472

473-
session.registerElicitationHandler(func(req ElicitationRequest, inv ElicitationInvocation) (ElicitationResult, error) {
473+
session.registerElicitationHandler(func(ctx ElicitationContext) (ElicitationResult, error) {
474474
return ElicitationResult{Action: "accept"}, nil
475475
})
476476

@@ -483,7 +483,7 @@ func TestSession_ElicitationHandler(t *testing.T) {
483483
session, cleanup := newTestSession()
484484
defer cleanup()
485485

486-
session.registerElicitationHandler(func(req ElicitationRequest, inv ElicitationInvocation) (ElicitationResult, error) {
486+
session.registerElicitationHandler(func(ctx ElicitationContext) (ElicitationResult, error) {
487487
return ElicitationResult{}, fmt.Errorf("handler exploded")
488488
})
489489

@@ -493,8 +493,7 @@ func TestSession_ElicitationHandler(t *testing.T) {
493493
}
494494

495495
_, err := handler(
496-
ElicitationRequest{Message: "Pick a color"},
497-
ElicitationInvocation{SessionID: "test-session"},
496+
ElicitationContext{SessionID: "test-session", Message: "Pick a color"},
498497
)
499498
if err == nil {
500499
t.Fatal("Expected error from handler")
@@ -508,7 +507,7 @@ func TestSession_ElicitationHandler(t *testing.T) {
508507
session, cleanup := newTestSession()
509508
defer cleanup()
510509

511-
session.registerElicitationHandler(func(req ElicitationRequest, inv ElicitationInvocation) (ElicitationResult, error) {
510+
session.registerElicitationHandler(func(ctx ElicitationContext) (ElicitationResult, error) {
512511
return ElicitationResult{
513512
Action: "accept",
514513
Content: map[string]any{"color": "blue"},
@@ -517,8 +516,7 @@ func TestSession_ElicitationHandler(t *testing.T) {
517516

518517
handler := session.getElicitationHandler()
519518
result, err := handler(
520-
ElicitationRequest{Message: "Pick a color"},
521-
ElicitationInvocation{SessionID: "test-session"},
519+
ElicitationContext{SessionID: "test-session", Message: "Pick a color"},
522520
)
523521
if err != nil {
524522
t.Fatalf("Expected no error, got %v", err)
@@ -587,3 +585,4 @@ func TestSession_ElicitationRequestSchema(t *testing.T) {
587585
}
588586
})
589587
}
588+

go/types.go

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -586,8 +586,12 @@ type ElicitationResult struct {
586586
Content map[string]any `json:"content,omitempty"`
587587
}
588588

589-
// ElicitationRequest describes an elicitation request from the server.
590-
type ElicitationRequest struct {
589+
// ElicitationContext describes an elicitation request from the server,
590+
// combining the request data with session context. Mirrors the
591+
// single-argument pattern of CommandContext.
592+
type ElicitationContext struct {
593+
// SessionID is the identifier of the session that triggered the request.
594+
SessionID string
591595
// Message describes what information is needed from the user.
592596
Message string
593597
// RequestedSchema is a JSON Schema describing the form fields (form mode only).
@@ -601,14 +605,9 @@ type ElicitationRequest struct {
601605
}
602606

603607
// ElicitationHandler handles elicitation requests from the server (e.g. from MCP tools).
604-
// It receives the request and an ElicitationInvocation for context, and must return
605-
// an ElicitationResult. If the handler returns an error the SDK auto-cancels the request.
606-
type ElicitationHandler func(request ElicitationRequest, invocation ElicitationInvocation) (ElicitationResult, error)
607-
608-
// ElicitationInvocation provides context about an elicitation request.
609-
type ElicitationInvocation struct {
610-
SessionID string
611-
}
608+
// It receives an ElicitationContext and must return an ElicitationResult.
609+
// If the handler returns an error the SDK auto-cancels the request.
610+
type ElicitationHandler func(ctx ElicitationContext) (ElicitationResult, error)
612611

613612
// InputOptions configures a text input field for the Input convenience method.
614613
type InputOptions struct {

0 commit comments

Comments
 (0)