diff --git a/mcp/elicitation_test.go b/mcp/elicitation_test.go index d588516..098fce3 100644 --- a/mcp/elicitation_test.go +++ b/mcp/elicitation_test.go @@ -12,6 +12,7 @@ import ( "testing/synctest" "github.com/google/jsonschema-go/jsonschema" + "github.com/modelcontextprotocol/go-sdk/internal/jsonrpc2" "github.com/modelcontextprotocol/go-sdk/jsonrpc" ) @@ -194,9 +195,9 @@ func TestElicitationCompleteNotification(t *testing.T) { } // 2. Server sends elicitation complete notification (simulating out-of-band completion) - err = handleNotify(ctx, notificationElicitationComplete, newServerRequest(ss, &ElicitationCompleteParams{ + err = ss.NotifyElicitationComplete(ctx, &ElicitationCompleteParams{ ElicitationID: elicitID, - })) + }) if err != nil { t.Fatalf("failed to send elicitation complete notification: %v", err) } @@ -209,6 +210,41 @@ func TestElicitationCompleteNotification(t *testing.T) { }) } +func TestNotifyElicitationCompleteInvalidParams(t *testing.T) { + ctx := context.Background() + + ct, st := NewInMemoryTransports() + s := NewServer(testImpl, nil) + ss, err := s.Connect(ctx, st, nil) + if err != nil { + t.Fatal(err) + } + defer ss.Close() + c := NewClient(testImpl, nil) + cs, err := c.Connect(ctx, ct, nil) + if err != nil { + t.Fatal(err) + } + defer cs.Close() + + // An empty elicitation ID cannot be matched to a pending elicitation by the + // client, so it is rejected rather than sent. + for _, tc := range []struct { + name string + params *ElicitationCompleteParams + }{ + {"nil params", nil}, + {"empty ID", &ElicitationCompleteParams{}}, + } { + t.Run(tc.name, func(t *testing.T) { + err := ss.NotifyElicitationComplete(ctx, tc.params) + if !errors.Is(err, jsonrpc2.ErrInvalidParams) { + t.Errorf("NotifyElicitationComplete(%v) = %v, want %v", tc.params, err, jsonrpc2.ErrInvalidParams) + } + }) + } +} + func TestElicitationNoValidationWithoutAccept(t *testing.T) { ctx := context.Background() diff --git a/mcp/error_test.go b/mcp/error_test.go index 898562c..c69fc4b 100644 --- a/mcp/error_test.go +++ b/mcp/error_test.go @@ -300,10 +300,9 @@ func TestURLElicitationRequired(t *testing.T) { // In a real scenario, this would happen out-of-band after the user // completes the form submission. go func() { - err := handleNotify(ctx, notificationElicitationComplete, - newServerRequest(ss, &ElicitationCompleteParams{ - ElicitationID: elicitID, - })) + err := ss.NotifyElicitationComplete(ctx, &ElicitationCompleteParams{ + ElicitationID: elicitID, + }) if err != nil { t.Errorf("failed to send elicitation complete notification: %v", err) } diff --git a/mcp/server.go b/mcp/server.go index 13172a5..f9c8233 100644 --- a/mcp/server.go +++ b/mcp/server.go @@ -1733,6 +1733,18 @@ func (ss *ServerSession) Elicit(ctx context.Context, params *ElicitParams) (*Eli return res, nil } +// NotifyElicitationComplete tells the client that the out-of-band ("url" mode) +// elicitation identified by params.ElicitationID has finished. +func (ss *ServerSession) NotifyElicitationComplete(ctx context.Context, params *ElicitationCompleteParams) error { + if params == nil { + return fmt.Errorf("%w: params cannot be nil", jsonrpc2.ErrInvalidParams) + } + if params.ElicitationID == "" { + return fmt.Errorf("%w: ElicitationID cannot be empty", jsonrpc2.ErrInvalidParams) + } + return handleNotify(ctx, notificationElicitationComplete, newServerRequest(ss, orZero[Params](params))) +} + // Log sends a log message to the client. // // For new-protocol (>= 2026-07-28) requests, the level is taken from the