Skip to content

Conversation

@semistrict
Copy link
Contributor

@semistrict semistrict commented Jul 21, 2025

Description

This PR implements MCP elicitation support, allowing servers to request additional information from clients during interactions. The implementation follows the same patterns as the existing sampling feature, providing bidirectional communication capabilities for stdio and in-process transports.

Fixes #413

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • MCP spec compatibility implementation
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Code refactoring (no functional changes)
  • Performance improvement
  • Tests only (no functional changes)
  • Other (please describe):

Checklist

  • My code follows the code style of this project
  • I have performed a self-review of my own code
  • I have added tests that prove my fix is effective or that my feature works
  • I have updated the documentation accordingly

MCP Spec Compliance

  • This PR implements a feature defined in the MCP specification
  • Link to relevant spec section: Elicitation Concepts
  • Implementation follows the specification exactly
    -- we do not implement response schema validation in the server

Additional Information

Implementation Details

  • Added core types: ElicitationRequest, ElicitationResult, ElicitationParams, ElicitationResponse, and ElicitationResponseType
  • Server capability: WithElicitation() option and RequestElicitation() method
  • Client capability: ElicitationHandler interface and WithElicitationHandler() option
  • Transport support: Implemented in stdio (bidirectional) and in-process transports
  • Testing: Comprehensive tests following the same patterns as sampling tests
  • Example: Complete example demonstrating elicitation usage patterns

Design Decisions

  • Architecture: Followed existing sampling implementation patterns closely for consistency

Summary by CodeRabbit

  • New Features

    • Added end-to-end elicitation support, enabling tools to request user input (accept/decline/cancel) with schema-based validation.
    • Client and server now advertise elicitation capability and handle request/response over stdio and in-process transports.
    • Included a demo showcasing project creation and data processing flows with interactive prompts.
  • Tests

    • Added unit and in-process tests covering initialization, request routing, and elicitation outcomes.
  • Style

    • Performed minor formatting/whitespace cleanups in HTTP and SSE transports (no behavior changes).

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 21, 2025

Walkthrough

Adds elicitation support across client, server, transports, and types: new MCP method and types, capability advertising, client option and handler, request dispatch/handling, server session APIs and stdio/in-process transport plumbing, unit/integration tests, and an example server demonstrating elicitation flows. Some transport files have formatting-only changes.

Changes

Cohort / File(s) Summary
Client core: elicitation support
client/client.go, client/elicitation.go
Adds ElicitationHandler, client option WithElicitationHandler, capability advertisement in Initialize, routing for mcp.MethodElicitationCreate, and request handling via handleElicitationRequestTransport.
Client tests: unit and E2E
client/elicitation_test.go, client/inprocess_elicitation_test.go
Introduces unit tests for handler wiring and transport dispatch; adds in-process end-to-end test, mock handler/transport; helper NewInProcessClientWithElicitationHandler.
Client transport (in-process)
client/transport/inprocess.go
Adds elicitation handler option and passes it to session creation via NewInProcessSessionWithHandlers; updates Start to register session when sampling or elicitation is present.
Client transports (formatting only)
client/transport/sse.go, client/transport/streamable_http.go
Whitespace/formatting adjustments; no functional changes.
MCP types and capabilities
mcp/types.go
Adds MethodElicitationCreate, client/server capability fields, and types: ElicitationRequest, ElicitationParams, ElicitationResult, ElicitationResponse, and action constants.
Server API: elicitation entrypoint
server/elicitation.go, server/session.go, server/server.go
Adds SessionWithElicitation interface, server option WithElicitation, capability emission, and MCPServer.RequestElicitation with errors for no session/unsupported.
Server sessions: in-process
server/inprocess_session.go
Adds ElicitationHandler, session field, constructor with handlers, and RequestElicitation delegating to handler.
Server stdio plumbing
server/stdio.go
Implements elicitation request/response path: pending map, RequestElicitation, response handling in message processing, and interface assertion.
Server streamable HTTP (formatting only)
server/streamable_http.go
Formatting/whitespace changes only; no behavior changes.
Example: elicitation demo
examples/elicitation/main.go
Adds example server demonstrating elicitation for tool workflows (project creation, data processing) with JSON schema requests and response handling.
Server tests
server/elicitation_test.go
Adds unit tests for RequestElicitation with mock sessions covering accept/decline and unsupported cases.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60–90 minutes

Possibly related PRs

Suggested labels

type: enhancement, area: mcp spec

Suggested reviewers

  • dugenkui03
  • pottekkat
  • rwjblue-glean

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Linked Issues Check ⚠️ Warning Most coding objectives from [#413] are implemented: new MCP types, server APIs (WithElicitation, RequestElicitation), client-side handler and option, transport plumbing for stdio/in-process, tests, and an example demonstrating elicitation flows. However the PR explicitly omits server-side response schema validation and does not clearly document or demonstrate standardized cancellation/decline error semantics, so structured elicitation support is only partially implemented. Because those items are required by the linked issue, the changes do not fully satisfy the issue's objectives. Implement server-side response schema validation (e.g., JSON Schema validation) and add unit tests covering schema mismatch, validation failures, and successful validation; define and document explicit cancellation/decline error semantics with corresponding tests; then update the PR description to list these remaining items and expected behaviors so reviewers can verify compliance.
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title "feat: implement MCP elicitation support (#413)" is concise, accurately describes the primary change (adding elicitation support across MCP, server, client, transports, tests, and examples), and includes the linked issue number for traceability, so it communicates the main intent clearly to reviewers.
Out of Scope Changes Check ✅ Passed All substantive code changes relate to elicitation support and its integration across mcp, server, client, transports, tests, and examples; several transport files show only whitespace/formatting edits which are incidental. I did not find unrelated feature additions or refactors outside the scope of issue [#413].
Description Check ✅ Passed The PR description follows the repository template: it contains a clear summary, references Fixes #413, marks the change type(s), lists checklist items, provides an MCP spec link, and includes implementation details, tests, and an example; it also notes the intentional omission of server-side schema validation. This makes the description sufficiently complete and useful for reviewers.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (2)
server/session.go (1)

51-56: Interface addition LGTM, but clarify blocking behaviour.

RequestElicitation is (implicitly) expected to block until the client responds.
Please document this expectation in the interface comment (or make it explicit in the method name) so implementers know whether they must spawn a goroutine / select on ctx.Done() internally.

-// RequestElicitation sends an elicitation request to the client and waits for response
+// RequestElicitation sends an elicitation request to the client and MUST block
+// until either a response is received or ctx is cancelled.
+// Implementations should respect ctx cancellation to avoid server goroutine leaks.
server/elicitation_test.go (1)

59-82: Use consistent server configuration pattern.

There's an inconsistency in how elicitation capability is enabled. Line 61 directly sets server.capabilities.elicitation = mcp.ToBoolPtr(true), while other tests use the WithElicitation() option (line 85). Consider using the option pattern consistently for better maintainability.

func TestMCPServer_RequestElicitation_NoSession(t *testing.T) {
-	server := NewMCPServer("test", "1.0.0")
-	server.capabilities.elicitation = mcp.ToBoolPtr(true)
+	server := NewMCPServer("test", "1.0.0", WithElicitation())
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between baa7153 and e215c98.

📒 Files selected for processing (18)
  • client/client.go (5 hunks)
  • client/elicitation.go (1 hunks)
  • client/elicitation_test.go (1 hunks)
  • client/inprocess_elicitation_test.go (1 hunks)
  • client/transport/inprocess.go (3 hunks)
  • client/transport/sse_test.go (2 hunks)
  • client/transport/streamable_http_test.go (2 hunks)
  • examples/elicitation/main.go (1 hunks)
  • mcp/tools.go (3 hunks)
  • mcp/types.go (4 hunks)
  • mcptest/mcptest.go (1 hunks)
  • server/elicitation.go (1 hunks)
  • server/elicitation_test.go (1 hunks)
  • server/inprocess_session.go (3 hunks)
  • server/server.go (3 hunks)
  • server/session.go (1 hunks)
  • server/stdio.go (6 hunks)
  • server/streamable_http_test.go (1 hunks)
🧰 Additional context used
🧠 Learnings (14)
📓 Common learnings
Learnt from: ezynda3
PR: mark3labs/mcp-go#461
File: server/sampling.go:22-26
Timestamp: 2025-06-30T07:13:17.052Z
Learning: In the mark3labs/mcp-go project, the MCPServer.capabilities field is a struct value (serverCapabilities), not a pointer, so it cannot be nil and doesn't require nil checking. Only pointer fields within the capabilities struct should be checked for nil.
mcptest/mcptest.go (5)
Learnt from: ezynda3
PR: mark3labs/mcp-go#461
File: server/sampling.go:22-26
Timestamp: 2025-06-30T07:13:17.052Z
Learning: In the mark3labs/mcp-go project, the MCPServer.capabilities field is a struct value (serverCapabilities), not a pointer, so it cannot be nil and doesn't require nil checking. Only pointer fields within the capabilities struct should be checked for nil.
Learnt from: octo
PR: mark3labs/mcp-go#149
File: mcptest/mcptest.go:0-0
Timestamp: 2025-04-21T21:26:32.945Z
Learning: In the mcptest package, prefer returning errors from helper functions rather than calling t.Fatalf() directly, giving callers flexibility in how to handle errors.
Learnt from: robert-jackson-glean
PR: mark3labs/mcp-go#214
File: server/sse.go:0-0
Timestamp: 2025-04-28T00:14:49.263Z
Learning: The SSE server in mcp-go implements path sanitization within the `WithDynamicBasePath` function that ensures the dynamic base path starts with "/" and has no trailing "/" to prevent double slashes in URL construction.
Learnt from: floatingIce91
PR: mark3labs/mcp-go#401
File: server/server.go:1082-1092
Timestamp: 2025-06-23T11:10:42.948Z
Learning: In Go MCP server, ServerTool.Tool field is only used for tool listing and indexing, not for tool execution or middleware. During handleToolCall, only the Handler field is used, so dynamic tools don't need the Tool field populated.
Learnt from: xinwo
PR: mark3labs/mcp-go#35
File: mcp/tools.go:107-137
Timestamp: 2025-03-04T06:59:43.882Z
Learning: Tool responses from the MCP server shouldn't contain RawInputSchema, which is why the UnmarshalJSON method for the Tool struct is implemented to handle only the structured InputSchema format.
server/server.go (3)
Learnt from: ezynda3
PR: mark3labs/mcp-go#461
File: server/sampling.go:22-26
Timestamp: 2025-06-30T07:13:17.052Z
Learning: In the mark3labs/mcp-go project, the MCPServer.capabilities field is a struct value (serverCapabilities), not a pointer, so it cannot be nil and doesn't require nil checking. Only pointer fields within the capabilities struct should be checked for nil.
Learnt from: floatingIce91
PR: mark3labs/mcp-go#401
File: server/server.go:1082-1092
Timestamp: 2025-06-23T11:10:42.948Z
Learning: In Go MCP server, ServerTool.Tool field is only used for tool listing and indexing, not for tool execution or middleware. During handleToolCall, only the Handler field is used, so dynamic tools don't need the Tool field populated.
Learnt from: xinwo
PR: mark3labs/mcp-go#35
File: mcp/tools.go:107-137
Timestamp: 2025-03-04T06:59:43.882Z
Learning: Tool responses from the MCP server shouldn't contain RawInputSchema, which is why the UnmarshalJSON method for the Tool struct is implemented to handle only the structured InputSchema format.
client/transport/sse_test.go (1)
Learnt from: leavez
PR: mark3labs/mcp-go#114
File: client/transport/sse.go:137-179
Timestamp: 2025-04-06T10:07:06.685Z
Learning: The SSE client implementation in the MCP-Go project uses a 30-second timeout for reading SSE events to match the behavior of the original implementation before the transport layer refactoring.
server/streamable_http_test.go (5)
Learnt from: octo
PR: mark3labs/mcp-go#149
File: mcptest/mcptest.go:0-0
Timestamp: 2025-04-21T21:26:32.945Z
Learning: In the mcptest package, prefer returning errors from helper functions rather than calling t.Fatalf() directly, giving callers flexibility in how to handle errors.
Learnt from: xinwo
PR: mark3labs/mcp-go#35
File: mcp/tools.go:107-137
Timestamp: 2025-03-04T06:59:43.882Z
Learning: Tool responses from the MCP server shouldn't contain RawInputSchema, which is why the UnmarshalJSON method for the Tool struct is implemented to handle only the structured InputSchema format.
Learnt from: xinwo
PR: mark3labs/mcp-go#35
File: mcp/tools.go:0-0
Timestamp: 2025-03-04T07:00:57.111Z
Learning: The Tool struct in the mark3labs/mcp-go project should handle both InputSchema and RawInputSchema consistently between MarshalJSON and UnmarshalJSON methods, even though the tools response from MCP server typically doesn't contain rawInputSchema.
Learnt from: ezynda3
PR: mark3labs/mcp-go#461
File: server/sampling.go:22-26
Timestamp: 2025-06-30T07:13:17.052Z
Learning: In the mark3labs/mcp-go project, the MCPServer.capabilities field is a struct value (serverCapabilities), not a pointer, so it cannot be nil and doesn't require nil checking. Only pointer fields within the capabilities struct should be checked for nil.
Learnt from: floatingIce91
PR: mark3labs/mcp-go#401
File: server/server.go:1082-1092
Timestamp: 2025-06-23T11:10:42.948Z
Learning: In Go MCP server, ServerTool.Tool field is only used for tool listing and indexing, not for tool execution or middleware. During handleToolCall, only the Handler field is used, so dynamic tools don't need the Tool field populated.
server/elicitation.go (1)
Learnt from: ezynda3
PR: mark3labs/mcp-go#461
File: server/sampling.go:22-26
Timestamp: 2025-06-30T07:13:17.052Z
Learning: In the mark3labs/mcp-go project, the MCPServer.capabilities field is a struct value (serverCapabilities), not a pointer, so it cannot be nil and doesn't require nil checking. Only pointer fields within the capabilities struct should be checked for nil.
mcp/tools.go (8)
Learnt from: floatingIce91
PR: mark3labs/mcp-go#401
File: server/server.go:1082-1092
Timestamp: 2025-06-23T11:10:42.948Z
Learning: In Go MCP server, ServerTool.Tool field is only used for tool listing and indexing, not for tool execution or middleware. During handleToolCall, only the Handler field is used, so dynamic tools don't need the Tool field populated.
Learnt from: xinwo
PR: mark3labs/mcp-go#35
File: mcp/tools.go:107-137
Timestamp: 2025-03-04T06:59:43.882Z
Learning: Tool responses from the MCP server shouldn't contain RawInputSchema, which is why the UnmarshalJSON method for the Tool struct is implemented to handle only the structured InputSchema format.
Learnt from: xinwo
PR: mark3labs/mcp-go#35
File: mcp/tools.go:0-0
Timestamp: 2025-03-04T07:00:57.111Z
Learning: The Tool struct in the mark3labs/mcp-go project should handle both InputSchema and RawInputSchema consistently between MarshalJSON and UnmarshalJSON methods, even though the tools response from MCP server typically doesn't contain rawInputSchema.
Learnt from: xinwo
PR: mark3labs/mcp-go#35
File: mcp/tools.go:0-0
Timestamp: 2025-03-04T07:00:57.111Z
Learning: The Tool struct in mark3labs/mcp-go handles both InputSchema and RawInputSchema formats. When unmarshaling JSON, it first tries to parse into a structured ToolInputSchema format, and if that fails, it falls back to using the raw schema format, providing symmetry with the MarshalJSON method.
Learnt from: octo
PR: mark3labs/mcp-go#149
File: mcptest/mcptest.go:0-0
Timestamp: 2025-04-21T21:26:32.945Z
Learning: In the mcptest package, prefer returning errors from helper functions rather than calling t.Fatalf() directly, giving callers flexibility in how to handle errors.
Learnt from: ezynda3
PR: mark3labs/mcp-go#461
File: server/sampling.go:22-26
Timestamp: 2025-06-30T07:13:17.052Z
Learning: In the mark3labs/mcp-go project, the MCPServer.capabilities field is a struct value (serverCapabilities), not a pointer, so it cannot be nil and doesn't require nil checking. Only pointer fields within the capabilities struct should be checked for nil.
Learnt from: davidleitw
PR: mark3labs/mcp-go#451
File: mcp/tools.go:1192-1217
Timestamp: 2025-06-26T09:38:18.629Z
Learning: In mcp-go project, the maintainer prefers keeping builder pattern APIs simple without excessive validation for edge cases. The WithOutput* functions are designed to assume correct usage rather than defensive programming, following the principle of API simplicity over comprehensive validation.
Learnt from: lariel-fernandes
PR: mark3labs/mcp-go#428
File: www/docs/pages/servers/prompts.mdx:218-234
Timestamp: 2025-06-20T20:39:51.870Z
Learning: In the mcp-go library, the GetPromptParams.Arguments field is of type map[string]string, not map[string]interface{}, so direct string access without type assertions is safe and correct.
server/elicitation_test.go (2)
Learnt from: octo
PR: mark3labs/mcp-go#149
File: mcptest/mcptest.go:0-0
Timestamp: 2025-04-21T21:26:32.945Z
Learning: In the mcptest package, prefer returning errors from helper functions rather than calling t.Fatalf() directly, giving callers flexibility in how to handle errors.
Learnt from: ezynda3
PR: mark3labs/mcp-go#461
File: server/sampling.go:22-26
Timestamp: 2025-06-30T07:13:17.052Z
Learning: In the mark3labs/mcp-go project, the MCPServer.capabilities field is a struct value (serverCapabilities), not a pointer, so it cannot be nil and doesn't require nil checking. Only pointer fields within the capabilities struct should be checked for nil.
examples/elicitation/main.go (4)
Learnt from: floatingIce91
PR: mark3labs/mcp-go#401
File: server/server.go:1082-1092
Timestamp: 2025-06-23T11:10:42.948Z
Learning: In Go MCP server, ServerTool.Tool field is only used for tool listing and indexing, not for tool execution or middleware. During handleToolCall, only the Handler field is used, so dynamic tools don't need the Tool field populated.
Learnt from: ezynda3
PR: mark3labs/mcp-go#461
File: server/sampling.go:22-26
Timestamp: 2025-06-30T07:13:17.052Z
Learning: In the mark3labs/mcp-go project, the MCPServer.capabilities field is a struct value (serverCapabilities), not a pointer, so it cannot be nil and doesn't require nil checking. Only pointer fields within the capabilities struct should be checked for nil.
Learnt from: xinwo
PR: mark3labs/mcp-go#35
File: mcp/tools.go:107-137
Timestamp: 2025-03-04T06:59:43.882Z
Learning: Tool responses from the MCP server shouldn't contain RawInputSchema, which is why the UnmarshalJSON method for the Tool struct is implemented to handle only the structured InputSchema format.
Learnt from: xinwo
PR: mark3labs/mcp-go#35
File: mcp/tools.go:0-0
Timestamp: 2025-03-04T07:00:57.111Z
Learning: The Tool struct in the mark3labs/mcp-go project should handle both InputSchema and RawInputSchema consistently between MarshalJSON and UnmarshalJSON methods, even though the tools response from MCP server typically doesn't contain rawInputSchema.
client/inprocess_elicitation_test.go (2)
Learnt from: octo
PR: mark3labs/mcp-go#149
File: mcptest/mcptest.go:0-0
Timestamp: 2025-04-21T21:26:32.945Z
Learning: In the mcptest package, prefer returning errors from helper functions rather than calling t.Fatalf() directly, giving callers flexibility in how to handle errors.
Learnt from: floatingIce91
PR: mark3labs/mcp-go#401
File: server/server.go:1082-1092
Timestamp: 2025-06-23T11:10:42.948Z
Learning: In Go MCP server, ServerTool.Tool field is only used for tool listing and indexing, not for tool execution or middleware. During handleToolCall, only the Handler field is used, so dynamic tools don't need the Tool field populated.
mcp/types.go (4)
Learnt from: ezynda3
PR: mark3labs/mcp-go#461
File: server/sampling.go:22-26
Timestamp: 2025-06-30T07:13:17.052Z
Learning: In the mark3labs/mcp-go project, the MCPServer.capabilities field is a struct value (serverCapabilities), not a pointer, so it cannot be nil and doesn't require nil checking. Only pointer fields within the capabilities struct should be checked for nil.
Learnt from: xinwo
PR: mark3labs/mcp-go#35
File: mcp/tools.go:107-137
Timestamp: 2025-03-04T06:59:43.882Z
Learning: Tool responses from the MCP server shouldn't contain RawInputSchema, which is why the UnmarshalJSON method for the Tool struct is implemented to handle only the structured InputSchema format.
Learnt from: floatingIce91
PR: mark3labs/mcp-go#401
File: server/server.go:1082-1092
Timestamp: 2025-06-23T11:10:42.948Z
Learning: In Go MCP server, ServerTool.Tool field is only used for tool listing and indexing, not for tool execution or middleware. During handleToolCall, only the Handler field is used, so dynamic tools don't need the Tool field populated.
Learnt from: xinwo
PR: mark3labs/mcp-go#35
File: mcp/tools.go:0-0
Timestamp: 2025-03-04T07:00:57.111Z
Learning: The Tool struct in the mark3labs/mcp-go project should handle both InputSchema and RawInputSchema consistently between MarshalJSON and UnmarshalJSON methods, even though the tools response from MCP server typically doesn't contain rawInputSchema.
client/elicitation_test.go (1)
Learnt from: octo
PR: mark3labs/mcp-go#149
File: mcptest/mcptest.go:0-0
Timestamp: 2025-04-21T21:26:32.945Z
Learning: In the mcptest package, prefer returning errors from helper functions rather than calling t.Fatalf() directly, giving callers flexibility in how to handle errors.
client/client.go (1)
Learnt from: ezynda3
PR: mark3labs/mcp-go#461
File: server/sampling.go:22-26
Timestamp: 2025-06-30T07:13:17.052Z
Learning: In the mark3labs/mcp-go project, the MCPServer.capabilities field is a struct value (serverCapabilities), not a pointer, so it cannot be nil and doesn't require nil checking. Only pointer fields within the capabilities struct should be checked for nil.
server/stdio.go (1)
Learnt from: xinwo
PR: mark3labs/mcp-go#35
File: mcp/tools.go:107-137
Timestamp: 2025-03-04T06:59:43.882Z
Learning: Tool responses from the MCP server shouldn't contain RawInputSchema, which is why the UnmarshalJSON method for the Tool struct is implemented to handle only the structured InputSchema format.
🧬 Code Graph Analysis (9)
server/server.go (1)
mcp/utils.go (1)
  • ToBoolPtr (817-819)
server/session.go (1)
mcp/types.go (2)
  • ElicitationRequest (805-808)
  • ElicitationResult (819-826)
client/elicitation.go (1)
mcp/types.go (2)
  • ElicitationRequest (805-808)
  • ElicitationResult (819-826)
server/elicitation.go (3)
server/server.go (1)
  • MCPServer (138-163)
mcp/types.go (2)
  • ElicitationRequest (805-808)
  • ElicitationResult (819-826)
server/session.go (2)
  • ClientSessionFromContext (78-83)
  • SessionWithElicitation (52-56)
mcp/tools.go (1)
mcp/types.go (2)
  • Meta (123-135)
  • Content (917-919)
examples/elicitation/main.go (5)
server/server.go (4)
  • MCPServer (138-163)
  • ToolHandlerFunc (41-41)
  • NewMCPServer (300-326)
  • WithElicitation (286-290)
mcp/tools.go (7)
  • CallToolRequest (48-52)
  • CallToolResult (38-45)
  • NewTool (638-660)
  • WithDescription (681-685)
  • WithString (916-934)
  • Required (748-752)
  • Description (740-744)
mcp/types.go (7)
  • ElicitationRequest (805-808)
  • Params (167-167)
  • ElicitationParams (811-816)
  • ElicitationResponseTypeAccept (842-842)
  • Content (917-919)
  • ElicitationResponseTypeDecline (844-844)
  • ElicitationResponseTypeCancel (846-846)
mcp/utils.go (1)
  • NewTextContent (199-204)
server/stdio.go (1)
  • NewStdioServer (271-280)
server/inprocess_session.go (4)
mcp/types.go (3)
  • ElicitationRequest (805-808)
  • ElicitationResult (819-826)
  • JSONRPCNotification (323-326)
client/elicitation.go (1)
  • ElicitationHandler (11-19)
server/session.go (4)
  • ClientSession (11-20)
  • SessionWithLogging (23-29)
  • SessionWithClientInfo (43-49)
  • SessionWithElicitation (52-56)
server/sampling.go (1)
  • SessionWithSampling (39-42)
client/client.go (4)
client/elicitation.go (1)
  • ElicitationHandler (11-19)
server/inprocess_session.go (1)
  • ElicitationHandler (19-21)
mcp/types.go (9)
  • JSONRPCRequest (315-320)
  • JSONRPCResponse (329-333)
  • MethodElicitationCreate (60-60)
  • ElicitationParams (811-816)
  • Params (167-167)
  • ElicitationRequest (805-808)
  • Request (158-161)
  • JSONRPC_VERSION (113-113)
  • Result (236-240)
client/transport/interface.go (2)
  • JSONRPCRequest (50-55)
  • JSONRPCResponse (57-66)
server/stdio.go (3)
mcp/types.go (7)
  • ElicitationResult (819-826)
  • ElicitationRequest (805-808)
  • Params (167-167)
  • ElicitationParams (811-816)
  • JSONRPC_VERSION (113-113)
  • MethodElicitationCreate (60-60)
  • Result (236-240)
server/session.go (2)
  • ClientSession (11-20)
  • SessionWithElicitation (52-56)
server/sampling.go (1)
  • SessionWithSampling (39-42)
🔇 Additional comments (49)
mcptest/mcptest.go (1)

145-145: Cosmetic change approved

The removal of the superfluous blank line is a harmless style cleanup and has no functional impact.

client/transport/sse_test.go (1)

408-459: Whitespace-only change – no action needed.
No functional impact observed.

client/transport/streamable_http_test.go (1)

418-451: Whitespace-only change – no action needed.
The surrounding logic remains untouched.

server/streamable_http_test.go (1)

846-897: Cosmetic re-indentation – looks fine.
Behaviour of the header-passthrough test is unchanged.

mcp/tools.go (1)

470-488: Whitespace-only cleanup – no functional changes.

Also applies to: 498-529

server/server.go (3)

178-178: LGTM! Elicitation capability field follows established pattern.

The addition of the elicitation *bool field to the serverCapabilities struct correctly follows the same pattern as the existing logging capability field, using a pointer to bool for optional capability configuration.


285-290: LGTM! Capability option function follows established pattern.

The WithElicitation() function correctly implements the elicitation capability option following the exact same pattern as WithLogging(), including proper use of mcp.ToBoolPtr(true) to set the capability flag.


578-580: LGTM! Capability advertisement follows established pattern.

The elicitation capability advertisement in handleInitialize correctly follows the same pattern as the logging capability, with proper nil and boolean checks before setting the capability to an empty struct.

server/elicitation.go (1)

13-25: LGTM! Method implementation follows established session patterns.

The RequestElicitation method correctly implements the server-side elicitation request forwarding with proper session validation, interface type assertion, and error handling. The implementation follows the same pattern as other session-dependent server methods.

client/elicitation.go (1)

11-19: LGTM! Well-designed interface with comprehensive documentation.

The ElicitationHandler interface is excellently designed with a clean single-method contract, proper type usage from the MCP protocol, and comprehensive documentation that clearly guides implementers on expected behavior including user interaction patterns.

client/transport/inprocess.go (3)

16-16: LGTM! Field addition follows established pattern.

The elicitationHandler field correctly follows the same pattern as the existing samplingHandler field, using the appropriate server.ElicitationHandler interface type for in-process transport.


32-36: LGTM! Option function follows established pattern.

The WithElicitationHandler option function correctly follows the same pattern as WithSamplingHandler, maintaining consistency in the transport configuration API.


59-60: LGTM! Session creation logic properly handles both handler types.

The updated session creation logic correctly uses OR condition to create sessions when either handler is present and properly passes both handlers to NewInProcessSessionWithHandlers, maintaining backward compatibility while enabling elicitation support.

server/inprocess_session.go (5)

18-21: LGTM! Interface definition follows established patterns.

The ElicitationHandler interface correctly defines the server-side contract for elicitation handling with proper method signature, context support, and appropriate MCP protocol types.


30-30: LGTM! Field addition follows established pattern.

The elicitationHandler field correctly follows the same pattern as the existing samplingHandler field, maintaining consistency in the struct design.


42-49: LGTM! Constructor follows established patterns.

The NewInProcessSessionWithHandlers constructor correctly follows the same initialization pattern as the existing constructor while properly setting both handler fields, maintaining consistency and enabling dual handler support.


105-115: LGTM! Method implementation mirrors established patterns.

The RequestElicitation method correctly follows the exact same pattern as RequestSampling with proper read locking, nil checking, and handler delegation, ensuring thread safety and consistent error handling.


128-128: LGTM! Interface compliance assertion ensures correctness.

The addition of SessionWithElicitation to the interface compliance assertions provides important compile-time verification that InProcessSession correctly implements the elicitation interface, following the established pattern of other interface assertions.

server/elicitation_test.go (4)

12-57: Well-structured mock implementations for testing.

The mock session types correctly implement the expected interfaces and provide good test coverage infrastructure. The mockBasicSession properly represents sessions without elicitation support, while mockElicitationSession extends it with configurable elicitation behavior.


84-112: Good test coverage for unsupported session scenario.

This test correctly validates the error handling when a session doesn't support elicitation. The setup and assertions are appropriate.


114-172: Comprehensive test for successful elicitation flow.

This test effectively validates the happy path scenario with proper response validation and type checking. The mock setup and assertions are thorough.


174-263: fakeSession type validated

The table-driven tests reference fakeSession, which is defined in server/server_test.go, so there’s no missing type or compilation issue. Everything else in TestRequestElicitation is well-structured and provides comprehensive coverage. Approving these changes.

examples/elicitation/main.go (2)

1-13: Clean package setup with appropriate imports.

The import statements are well-organized and all dependencies are necessary for the example functionality.


99-99: Appropriate use of atomic counter for request tracking.

The atomic counter effectively demonstrates thread-safe state management across tool requests.

client/client.go (4)

26-26: Consistent implementation following established patterns.

The new elicitationHandler field and WithElicitationHandler option function correctly follow the same patterns as the existing sampling functionality. The documentation is clear and implementation is straightforward.

Also applies to: 46-52


166-169: Proper capability declaration following existing pattern.

The elicitation capability declaration correctly mirrors the sampling capability pattern, ensuring consistency in the client initialization flow.


443-443: Clean integration with existing request routing.

The elicitation request routing follows the established pattern and integrates seamlessly with the existing request handling infrastructure.

Also applies to: 448-449


503-549: Well-implemented transport handler following established patterns.

The handleElicitationRequestTransport method correctly mirrors the structure and patterns of the existing handleSamplingRequestTransport method. The parameter handling, error checking, and response construction all follow the established conventions.

client/elicitation_test.go (4)

13-24: Well-designed mock for flexible testing scenarios.

The mockElicitationHandler provides good flexibility for testing different response scenarios and error conditions.


26-123: Comprehensive test coverage with good scenario validation.

The table-driven test effectively covers all important scenarios including error conditions and different response types. The assertion logic is thorough and appropriate.


125-135: Simple and focused option test.

The test correctly verifies that the WithElicitationHandler option properly sets the handler on the client.


192-225: Complete and appropriate mock transport implementation.

The mock transport provides all necessary interface methods with configurable behavior, making it suitable for testing various scenarios.

client/inprocess_elicitation_test.go (3)

12-33: Effective mock handler for integration testing.

The mock implementation provides appropriate tracking capabilities and simulates user acceptance, which is perfect for testing the end-to-end elicitation flow.


185-197: Useful helper function for in-process elicitation testing.

The helper function correctly sets up both server and client sides of elicitation handling for in-process testing scenarios.


199-206: Simple and effective adapter pattern implementation.

The wrapper correctly bridges the client and server elicitation handler interfaces, enabling seamless in-process testing.

mcp/types.go (8)

58-60: LGTM! Method constant follows established patterns.

The new elicitation method constant is well-documented and follows the existing naming convention for MCP methods.


455-456: Client capability addition looks good.

The elicitation capability follows the same pattern as other client capabilities like sampling.


485-486: Server capability addition is consistent.

The elicitation capability matches the client-side declaration and follows established patterns.


803-808: Request type structure is well-designed.

The ElicitationRequest follows the established pattern for MCP request types, properly embedding the base Request type.


811-816: Parameters are appropriately typed.

The use of any for RequestedSchema is correct for JSON Schema flexibility, consistent with other schema fields in the codebase.


819-826: Result type is well-documented.

The ElicitationResult structure properly embeds the base Result type and clearly documents all possible response scenarios.


829-835: Response structure handles all cases correctly.

The optional Value field with omitempty tag is appropriate since it's only populated for accepted responses.


838-847: Enum values cover all response scenarios.

The three response types (accept, decline, cancel) appropriately handle all user interaction outcomes.

server/stdio.go (6)

55-64: Session struct changes follow existing patterns.

The pendingElicitations map mirrors the design of pendingRequests for sampling, maintaining consistency in the codebase.


73-77: Response type matches established patterns.

The elicitationResponse struct correctly mirrors the samplingResponse pattern with appropriate types.


185-246: RequestElicitation implementation is solid.

The method correctly implements the request-response pattern with proper cleanup, error handling, and context support, mirroring the sampling implementation.


256-260: Interface implementation check added correctly.

The compile-time assertion for SessionWithElicitation ensures the session properly implements the interface.


263-267: Session instance initialization is complete.

The pendingElicitations map is properly initialized alongside existing maps.


423-426: Response routing added correctly.

The elicitation response check follows the established pattern and is properly integrated into the message processing flow.

@miguelb-gk
Copy link
Contributor

Any idea when this might be getting in? It would be great to have elicitation.

@Sameer-Jain-123
Copy link

hey when we are planning to merge this ?

@JBUinfo
Copy link
Contributor

JBUinfo commented Aug 14, 2025

@dugenkui03 or @ezynda3 can you review when you have a chance? Thanks!

@ezynda3
Copy link
Contributor

ezynda3 commented Aug 14, 2025

This has a bunch of merge conflicts that need to be addressed

@miguelb-gk
Copy link
Contributor

it looks like @JBUinfo resolved the conflicts and this is now mergable.

* Add ElicitationRequest, ElicitationResult, and related types to mcp/types.go
* Implement server-side RequestElicitation method with session support
* Add client-side ElicitationHandler interface and request handling
* Implement elicitation in stdio and in-process transports
* Add comprehensive tests following sampling patterns
* Create elicitation example demonstrating usage patterns
* Use 'Elicitation' prefix for type names to maintain clarity
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
server/elicitation.go (2)

10-12: Clarify doc comment with explicit error contracts.

Documenting the concrete errors helps users handle them correctly (especially if switched to sentinels).

-// RequestElicitation sends an elicitation request to the client.
-// The client must have declared elicitation capability during initialization.
-// The session must implement SessionWithElicitation to support this operation.
+// RequestElicitation sends an elicitation request to the client.
+// Preconditions: client declares elicitation capability during initialization.
+// Returns:
+//   - ErrNoActiveSession if no active session is associated with ctx.
+//   - ErrElicitationNotSupported if the session doesn't implement SessionWithElicitation.

13-13: Optional: keep parameter passing consistent with adjacent APIs.

If other request-style entrypoints in this package accept pointers (to avoid copying larger request structs and to align signatures), consider request *mcp.ElicitationRequest. If peers use values, keep as-is for consistency.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c0b1fd7 and b7a18c5.

📒 Files selected for processing (13)
  • client/client.go (5 hunks)
  • client/elicitation.go (1 hunks)
  • client/elicitation_test.go (1 hunks)
  • client/inprocess_elicitation_test.go (1 hunks)
  • client/transport/inprocess.go (3 hunks)
  • examples/elicitation/main.go (1 hunks)
  • mcp/types.go (4 hunks)
  • server/elicitation.go (1 hunks)
  • server/elicitation_test.go (1 hunks)
  • server/inprocess_session.go (4 hunks)
  • server/server.go (3 hunks)
  • server/session.go (1 hunks)
  • server/stdio.go (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (12)
  • server/session.go
  • server/elicitation_test.go
  • client/elicitation.go
  • server/server.go
  • mcp/types.go
  • client/transport/inprocess.go
  • client/inprocess_elicitation_test.go
  • server/inprocess_session.go
  • client/client.go
  • client/elicitation_test.go
  • examples/elicitation/main.go
  • server/stdio.go
🧰 Additional context used
🧬 Code graph analysis (1)
server/elicitation.go (3)
server/server.go (1)
  • MCPServer (144-169)
mcp/types.go (2)
  • ElicitationRequest (829-832)
  • ElicitationResult (843-850)
server/session.go (2)
  • ClientSessionFromContext (82-87)
  • SessionWithElicitation (56-60)
🔇 Additional comments (1)
server/elicitation.go (1)

13-22: Control flow and delegation look good.

Fetching the session from context, early-returning on absence, and delegating via SessionWithElicitation preserves transport-agnostic behavior and cleanly separates concerns.

Comment on lines 3 to 8
import (
"context"
"fmt"

"github.com/mark3labs/mcp-go/mcp"
)
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Use sentinel errors (errors.New) instead of raw strings; simplify imports.

Returning typed, package-level errors enables callers/tests to use errors.Is for reliable matching and avoids brittle string checks. Also, no formatting is needed—prefer errors.New over fmt.Errorf here.

If you adopt the sentinels, run this to find any string-based assertions that need updating:

Proposed diff:

 import (
  	"context"
-	"fmt"
+	"errors"
 
  	"github.com/mark3labs/mcp-go/mcp"
  )
 
+// Sentinel errors to allow callers to detect conditions via errors.Is.
+var (
+	ErrNoActiveSession         = errors.New("no active session")
+	ErrElicitationNotSupported = errors.New("session does not support elicitation")
+)
@@
 	session := ClientSessionFromContext(ctx)
 	if session == nil {
-		return nil, fmt.Errorf("no active session")
+		return nil, ErrNoActiveSession
 	}
@@
-	return nil, fmt.Errorf("session does not support elicitation")
+	return nil, ErrElicitationNotSupported

Also applies to: 16-17, 24-24


🏁 Script executed:

#!/bin/bash
# Find places asserting on error strings that should switch to errors.Is
rg -n -C2 --type=go '"no active session"|"session does not support elicitation"'

Length of output: 2426


Use sentinel errors and update tests accordingly

Defining package-level sentinel errors and replacing raw fmt.Errorf calls will enable callers and tests to use errors.Is for reliable matching. After making these changes, you’ll need to update all test assertions that compare error strings to use errors.Is(err, <SentinelError>) instead.

Bullet-traced refactor plan:

  • server/elicitation.go
    • Remove fmt import, add errors.
    • Declare at top of file:

    var (
        ErrNoActiveSession         = errors.New("no active session")
        ErrElicitationNotSupported = errors.New("session does not support elicitation")
    )

    • Replace:

    -   return nil, fmt.Errorf("no active session")
    +   return nil, ErrNoActiveSession-   return nil, fmt.Errorf("session does not support elicitation")
    +   return nil, ErrElicitationNotSupported
  • server/sampling.go
    • Remove fmt import, add errors.
    • Declare near top:

    var ErrNoActiveSession = errors.New("no active session")

    • Replace:

    -   return nil, fmt.Errorf("no active session")
    +   return nil, ErrNoActiveSession
  • Tests in server/elicitation_test.go, server/sampling_test.go, server/server_test.go
    • Replace string comparisons such as:

    expectedError := "no active session"
    if err.Error() != expectedError { … }

    with

    if !errors.Is(err, ErrNoActiveSession) {
        t.Errorf("expected ErrNoActiveSession, got %v", err)
    }

    • And similarly for ErrElicitationNotSupported.

To locate any remaining raw-string assertions, run:

rg -n -C2 --type=go '"no active session"|"session does not support elicitation"'

This refactor is mandatory to keep error matching robust and maintainable.

🤖 Prompt for AI Agents
In server/elicitation.go around lines 3-8, replace the current use of raw
fmt.Errorf error strings by removing the fmt import and adding the errors
package, declare package-level sentinel errors (ErrNoActiveSession and
ErrElicitationNotSupported) at the top of the file, and replace all
fmt.Errorf(...) occurrences that produce those exact messages with the
corresponding sentinel error variables; do the same in server/sampling.go
(remove fmt, add errors, declare ErrNoActiveSession and replace raw error
strings with the sentinel); then update tests in server/elicitation_test.go,
server/sampling_test.go, and server/server_test.go to stop comparing error
strings and instead assert with errors.Is(err, ErrNoActiveSession) or
errors.Is(err, ErrElicitationNotSupported) as appropriate; finally run a search
(rg -n ...) to find any remaining raw-string comparisons and convert them to
errors.Is checks.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (9)
client/inprocess_elicitation_test.go (9)

23-33: Align mock response with requested schema and prefer any over interface{}

The elicitation response returns keys (response, accepted) that don’t match the schema you request later (confirm, details). It’s harmless given server-side schema validation isn’t implemented yet, but keeping them consistent prevents confusion and future-proof tests if validation is added. Also, prefer map[string]any for consistency with Go 1.18+.

Apply this diff:

-	return &mcp.ElicitationResult{
-		Response: mcp.ElicitationResponse{
-			Type: mcp.ElicitationResponseTypeAccept,
-			Value: map[string]interface{}{
-				"response": "User provided data",
-				"accepted": true,
-			},
-		},
-	}, nil
+	return &mcp.ElicitationResult{
+		Response: mcp.ElicitationResponse{
+			Type: mcp.ElicitationResponseTypeAccept,
+			Value: map[string]any{
+				"confirm": true,
+				"details": "User provided data",
+			},
+		},
+	}, nil

50-69: Use map[string]any consistently for JSON-shaped maps

Minor style/nit: replace map[string]interface{} with map[string]any throughout the schema literal.

-				RequestedSchema: map[string]interface{}{
+				RequestedSchema: map[string]any{
 					"type": "object",
-					"properties": map[string]interface{}{
-						"confirm": map[string]interface{}{
+					"properties": map[string]any{
+						"confirm": map[string]any{
 							"type":        "boolean",
 							"description": "Confirm the action",
 						},
-						"details": map[string]interface{}{
+						"details": map[string]any{
 							"type":        "string",
 							"description": "Additional details",
 						},
 					},
 					"required": []string{"confirm"},
 				},

85-104: Handle unknown response types and mark cancel as error (optional)

Adding a default branch guards against unexpected values. Also consider surfacing cancellation as an error at the tool layer, which aligns with “proper error handling for cancelled elicitations” in the linked issue.

-		var responseText string
+		var (
+			responseText string
+			isError      bool
+		)
 		switch result.Response.Type {
 		case mcp.ElicitationResponseTypeAccept:
 			responseText = "User accepted and provided data"
 		case mcp.ElicitationResponseTypeDecline:
 			responseText = "User declined to provide information"
 		case mcp.ElicitationResponseTypeCancel:
 			responseText = "User cancelled the request"
+			isError = true
+		default:
+			responseText = "Unknown elicitation response type"
+			isError = true
 		}
 
 		return &mcp.CallToolResult{
 			Content: []mcp.Content{
 				mcp.TextContent{
 					Type: "text",
 					Text: responseText,
 				},
 			},
-		}, nil
+			IsError: isError,
+		}, nil

If your intended semantics are “decline is non-error, cancel is error,” this change reflects that. If you prefer both decline and cancel to be errors, set isError = true in the decline branch as well.


3-10: Add timeouts to prevent hanging tests

Wrap Start/Initialize/CallTool calls with timeouts and import time. This makes the test more robust against transport/server deadlocks.

 import (
 	"context"
 	"testing"
+	"time"
-	// Start the client
-	if err := client.Start(context.Background()); err != nil {
+	// Start the client (with timeout)
+	startCtx, startCancel := context.WithTimeout(context.Background(), 5*time.Second)
+	defer startCancel()
+	if err := client.Start(startCtx); err != nil {
 		t.Fatalf("Failed to start client: %v", err)
 	}
-	// Initialize the client
-	_, err = client.Initialize(context.Background(), mcp.InitializeRequest{
+	// Initialize the client (with timeout)
+	initCtx, initCancel := context.WithTimeout(context.Background(), 5*time.Second)
+	defer initCancel()
+	_, err = client.Initialize(initCtx, mcp.InitializeRequest{
 		Params: mcp.InitializeParams{
 			ProtocolVersion: mcp.LATEST_PROTOCOL_VERSION,
 			ClientInfo: mcp.Implementation{
 				Name:    "test-client",
 				Version: "1.0.0",
 			},
 			Capabilities: mcp.ClientCapabilities{
 				Elicitation: &struct{}{},
 			},
 		},
 	})
-	// Call the tool that triggers elicitation
-	result, err := client.CallTool(context.Background(), mcp.CallToolRequest{
+	// Call the tool that triggers elicitation (with timeout)
+	callCtx, callCancel := context.WithTimeout(context.Background(), 5*time.Second)
+	defer callCancel()
+	result, err := client.CallTool(callCtx, mcp.CallToolRequest{
 		Params: mcp.CallToolParams{
 			Name: "test_elicitation",
 			Arguments: map[string]any{
 				"action": "test-action",
 			},
 		},
 	})

Also applies to: 116-120, 121-133, 138-147


152-155: Assert non-error result for the accept path

Since the mock returns “accept,” ensure the tool result isn’t flagged as an error.

 	// Verify the result
 	if len(result.Content) == 0 {
 		t.Fatal("Expected content in result")
 	}
+	if result.IsError {
+		t.Fatalf("Unexpected error result: %+v", result)
+	}

171-174: Add a minimal schema assertion for stronger end-to-end coverage

Verifying the schema shape reached the handler catches wiring mistakes and future regressions.

 	if mockHandler.LastRequest.Params.Message != "Need additional information for test-action" {
 		t.Errorf("Unexpected elicitation message: %s", mockHandler.LastRequest.Params.Message)
 	}
+	// Verify schema was passed through
+	if schema, ok := mockHandler.LastRequest.Params.RequestedSchema.(map[string]any); ok {
+		if _, hasProps := schema["properties"]; !hasProps {
+			t.Error("requestedSchema missing 'properties'")
+		}
+	} else {
+		t.Fatal("requestedSchema is not a JSON object")
+	}

184-186: Redundant assignment to client.elicitationHandler in in-process setup (confirm intent)

Because the in-process transport is wired with server.ElicitationHandler via the wrapper, the explicit client.elicitationHandler = handler may be unnecessary for this test. If it isn’t required for capability negotiation elsewhere, consider removing it.

 	client := NewClient(inProcessTransport)
-	client.elicitationHandler = handler

176-188: Optional: simplify helper signature by dropping the unused error return

The helper can’t fail as written; simplifying the signature reduces noise.

Change the helper:

-func NewInProcessClientWithElicitationHandler(server *server.MCPServer, handler ElicitationHandler) (*Client, error) {
+func NewInProcessClientWithElicitationHandler(server *server.MCPServer, handler ElicitationHandler) *Client {
@@
-	return client, nil
+	return client
 }

And the call site:

-	client, err := NewInProcessClientWithElicitationHandler(mcpServer, mockHandler)
-	if err != nil {
-		t.Fatalf("Failed to create client: %v", err)
-	}
+	client := NewInProcessClientWithElicitationHandler(mcpServer, mockHandler)

Also applies to: 109-113


35-104: Broaden coverage: add decline and cancel scenarios

The test exercises only the accept path. Consider adding table-driven subtests to validate decline and cancel flows (and the IsError semantics you choose). I can draft a follow-up test if you’d like.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b7a18c5 and 8986c49.

📒 Files selected for processing (13)
  • client/inprocess_elicitation_test.go (1 hunks)
  • client/transport/sse.go (2 hunks)
  • client/transport/streamable_http.go (4 hunks)
  • client/transport/streamable_http_sampling_test.go (15 hunks)
  • examples/elicitation/main.go (1 hunks)
  • examples/sampling_client/main.go (1 hunks)
  • examples/sampling_http_client/main.go (5 hunks)
  • examples/sampling_http_server/main.go (1 hunks)
  • server/sampling.go (1 hunks)
  • server/sampling_test.go (2 hunks)
  • server/stdio.go (6 hunks)
  • server/streamable_http.go (5 hunks)
  • server/streamable_http_sampling_test.go (1 hunks)
✅ Files skipped from review due to trivial changes (10)
  • server/sampling.go
  • client/transport/streamable_http.go
  • examples/sampling_client/main.go
  • examples/sampling_http_server/main.go
  • server/streamable_http_sampling_test.go
  • examples/sampling_http_client/main.go
  • client/transport/streamable_http_sampling_test.go
  • server/sampling_test.go
  • client/transport/sse.go
  • server/streamable_http.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • examples/elicitation/main.go
  • server/stdio.go
🧰 Additional context used
🧬 Code graph analysis (1)
client/inprocess_elicitation_test.go (7)
mcp/types.go (15)
  • ElicitationRequest (829-832)
  • ElicitationResult (843-850)
  • ElicitationResponse (853-859)
  • ElicitationResponseTypeAccept (866-866)
  • Params (181-181)
  • ElicitationParams (835-840)
  • Content (941-943)
  • TextContent (947-954)
  • TextContent (956-956)
  • ElicitationResponseTypeDecline (868-868)
  • ElicitationResponseTypeCancel (870-870)
  • InitializeRequest (418-422)
  • InitializeParams (424-430)
  • Implementation (506-509)
  • ClientCapabilities (459-471)
server/server.go (3)
  • NewMCPServer (307-333)
  • WithElicitation (293-297)
  • MCPServer (144-169)
mcp/tools.go (3)
  • CallToolRequest (54-58)
  • CallToolResult (40-51)
  • CallToolParams (60-64)
client/elicitation.go (1)
  • ElicitationHandler (11-19)
server/inprocess_session.go (1)
  • ElicitationHandler (19-21)
client/client.go (3)
  • Client (17-29)
  • WithElicitationHandler (50-54)
  • NewClient (71-81)
client/transport/inprocess.go (2)
  • NewInProcessTransportWithOptions (44-55)
  • WithElicitationHandler (32-36)
🔇 Additional comments (1)
client/inprocess_elicitation_test.go (1)

39-45: Tool registration pattern matches current server API — looks good

You’ve updated the tool registration to use mcp.NewTool with field options. This aligns with the current MCP server API patterns and resolves the earlier feedback about outdated registration.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
client/inprocess_elicitation_test.go (1)

39-44: Tool registration now matches current server API.

Using mcp.NewTool and option builders is consistent with the repo’s pattern. Thanks for addressing the earlier feedback.

🧹 Nitpick comments (15)
server/elicitation_test.go (6)

60-61: Use public option WithElicitation and consistent testify assertions

  • Prefer the public option WithElicitation over mutating unexported fields.
  • Use require/assert to fail fast and avoid brittle string equality checks.
-	server := NewMCPServer("test", "1.0.0")
-	server.capabilities.elicitation = mcp.ToBoolPtr(true)
+	server := NewMCPServer("test", "1.0.0", WithElicitation())
-	if err == nil {
-		t.Error("expected error when no session available")
-	}
-
-	expectedError := "no active session"
-	if err.Error() != expectedError {
-		t.Errorf("expected error %q, got %q", expectedError, err.Error())
-	}
+	require.Error(t, err)
+	assert.Contains(t, err.Error(), "no active session")

Also applies to: 74-81


104-111: Standardize error assertions with require/assert

Align with the table-driven style and avoid brittle string equality.

-	if err == nil {
-		t.Error("expected error when session doesn't support elicitation")
-	}
-
-	expectedError := "session does not support elicitation"
-	if err.Error() != expectedError {
-		t.Errorf("expected error %q, got %q", expectedError, err.Error())
-	}
+	require.Error(t, err)
+	assert.Contains(t, err.Error(), "session does not support elicitation")

150-172: Tighten success-path assertions

Use require/assert consistently, and verify all expected fields (both projectName and framework). This fails fast and covers more behavior.

-	if err != nil {
-		t.Errorf("unexpected error: %v", err)
-	}
-
-	if result == nil {
-		t.Error("expected result, got nil")
-		return
-	}
-
-	if result.Response.Type != mcp.ElicitationResponseTypeAccept {
-		t.Errorf("expected response type %q, got %q", mcp.ElicitationResponseTypeAccept, result.Response.Type)
-	}
-
-	value, ok := result.Response.Value.(map[string]any)
-	if !ok {
-		t.Error("expected value to be a map")
-		return
-	}
-
-	if value["projectName"] != "my-project" {
-		t.Errorf("expected projectName %q, got %q", "my-project", value["projectName"])
-	}
+	require.NoError(t, err)
+	require.NotNil(t, result)
+	assert.Equal(t, mcp.ElicitationResponseTypeAccept, result.Response.Type)
+	value, ok := result.Response.Value.(map[string]any)
+	require.True(t, ok, "expected value to be a map[string]any")
+	assert.Equal(t, "my-project", value["projectName"])
+	assert.Equal(t, "react", value["framework"])

258-260: Assert response value type for accept cases in table-driven test

Strengthen the check to ensure the accepted response has the expected structure.

-			if tt.expectedType == mcp.ElicitationResponseTypeAccept {
-				assert.NotNil(t, result.Response.Value)
-			}
+			if tt.expectedType == mcp.ElicitationResponseTypeAccept {
+				require.NotNil(t, result.Response.Value)
+				require.IsType(t, map[string]any{}, result.Response.Value)
+			}

59-63: Optional: Run tests in parallel to speed up CI

Subtests are isolated; parallelizing them can reduce overall test time.

 func TestMCPServer_RequestElicitation_NoSession(t *testing.T) {
-	server := NewMCPServer("test", "1.0.0", WithElicitation())
+	t.Parallel()
+	server := NewMCPServer("test", "1.0.0", WithElicitation())
 func TestMCPServer_RequestElicitation_SessionDoesNotSupportElicitation(t *testing.T) {
-	server := NewMCPServer("test", "1.0.0", WithElicitation())
+	t.Parallel()
+	server := NewMCPServer("test", "1.0.0", WithElicitation())
 func TestMCPServer_RequestElicitation_Success(t *testing.T) {
-	server := NewMCPServer("test", "1.0.0", WithElicitation())
+	t.Parallel()
+	server := NewMCPServer("test", "1.0.0", WithElicitation())
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
 			server := NewMCPServer("test", "1.0", WithElicitation())
 			ctx := server.WithContext(context.Background(), tt.session)

Also applies to: 84-86, 114-116, 241-245


3-10: Add coverage for error and cancellation paths

Issue #413 calls out proper handling of cancelled elicitations. Add cases where the session returns context.Canceled and an arbitrary error to ensure propagation and messaging.

Add imports:

 import (
 	"context"
+	"errors"
 	"testing"

Add table cases:

 		{
 			name:    "session does not support elicitation",
 			session: &mockBasicSession{sessionID: "test-3"},
 			request: mcp.ElicitationRequest{
 				Params: mcp.ElicitationParams{
 					Message:         "Need info",
 					RequestedSchema: map[string]any{"type": "object"},
 				},
 			},
 			expectedError: "session does not support elicitation",
 		},
+		{
+			name: "elicitation canceled",
+			session: &mockElicitationSession{
+				sessionID: "test-4",
+				err:       context.Canceled,
+			},
+			request: mcp.ElicitationRequest{
+				Params: mcp.ElicitationParams{
+					Message:         "Please confirm",
+					RequestedSchema: map[string]any{"type": "object"},
+				},
+			},
+			expectedError: "canceled",
+		},
+		{
+			name: "elicitation request error propagated",
+			session: &mockElicitationSession{
+				sessionID: "test-5",
+				err:       errors.New("backend failure"),
+			},
+			request: mcp.ElicitationRequest{
+				Params: mcp.ElicitationParams{
+					Message:         "Provide details",
+					RequestedSchema: map[string]any{"type": "object"},
+				},
+			},
+			expectedError: "backend failure",
+		},

If server maps cancellations to a custom error, adjust expectedError accordingly.

Also applies to: 182-239

client/inprocess_elicitation_test.go (9)

12-17: Make MockElicitationHandler concurrency-safe (future-proofing).

If this handler ever gets reused in parallel tests, reads/writes to CallCount and LastRequest will race. Consider adding a small mutex.

Example change:

 type MockElicitationHandler struct {
-	// Track calls for verification
-	CallCount   int
-	LastRequest mcp.ElicitationRequest
+	// Track calls for verification
+	mu          sync.Mutex
+	CallCount   int
+	LastRequest mcp.ElicitationRequest
 }

and in Elicit:

-	h.CallCount++
-	h.LastRequest = request
+	h.mu.Lock()
+	h.CallCount++
+	h.LastRequest = request
+	h.mu.Unlock()

You’ll also need to import sync.


50-69: Tighten the requested schema for clearer contracts.

Add additionalProperties: false to prevent unexpected fields, and (optionally) a title/description for readability in UIs/logs.

 				RequestedSchema: map[string]any{
-					"type": "object",
+					"type": "object",
+					"title": "TestElicitationResponse",
+					"description": "Response for test_elicitation tool",
 					"properties": map[string]any{
 						"confirm": map[string]any{
 							"type":        "boolean",
 							"description": "Confirm the action",
 						},
 						"details": map[string]any{
 							"type":        "string",
 							"description": "Additional details",
 						},
 					},
 					"required": []string{"confirm"},
+					"additionalProperties": false,
 				},

85-104: Handle unknown response types explicitly.

Future enum extensions could lead to empty responseText. Add a default case and mark the result as error to surface issues quickly.

 		var responseText string
+		isError := false
 		switch result.Response.Type {
 		case mcp.ElicitationResponseTypeAccept:
 			responseText = "User accepted and provided data"
 		case mcp.ElicitationResponseTypeDecline:
 			responseText = "User declined to provide information"
 		case mcp.ElicitationResponseTypeCancel:
 			responseText = "User cancelled the request"
+		default:
+			responseText = "Unknown elicitation response type"
+			isError = true
 		}
 
 		return &mcp.CallToolResult{
 			Content: []mcp.Content{
 				mcp.TextContent{
 					Type: "text",
 					Text: responseText,
 				},
 			},
-		}, nil
+			IsError: isError,
+		}, nil

3-10: Use a test-scoped timeout context to avoid hangs.

If wiring regresses, Start/Initialize/CallTool could block. A short timeout keeps CI green with clear failures.

 import (
 	"context"
+	"time"
 	"testing"
 
 	"github.com/mark3labs/mcp-go/client/transport"
 	"github.com/mark3labs/mcp-go/mcp"
 	"github.com/mark3labs/mcp-go/server"
 )

113-116: Apply the timeout context to Start.

-	// Start the client
-	if err := client.Start(context.Background()); err != nil {
+	// Start the client with a timeout to prevent hangs
+	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+	defer cancel()
+	if err := client.Start(ctx); err != nil {
 		t.Fatalf("Failed to start client: %v", err)
 	}

118-133: Apply the timeout context to Initialize.

-	_, err := client.Initialize(context.Background(), mcp.InitializeRequest{
+	_, err := client.Initialize(ctx, mcp.InitializeRequest{
 		Params: mcp.InitializeParams{
 			ProtocolVersion: mcp.LATEST_PROTOCOL_VERSION,
 			ClientInfo: mcp.Implementation{
 				Name:    "test-client",
 				Version: "1.0.0",
 			},
 			Capabilities: mcp.ClientCapabilities{
 				Elicitation: &struct{}{},
 			},
 		},
 	})

135-147: Apply the timeout context to CallTool.

-	result, err := client.CallTool(context.Background(), mcp.CallToolRequest{
+	result, err := client.CallTool(ctx, mcp.CallToolRequest{
 		Params: mcp.CallToolParams{
 			Name: "test_elicitation",
 			Arguments: map[string]any{
 				"action": "test-action",
 			},
 		},
 	})

173-176: Strengthen assertions: verify requested schema shape.

Validate that the handler received the expected schema, not just the message. This prevents silent drift in schema wiring.

-	if mockHandler.LastRequest.Params.Message != "Need additional information for test-action" {
-		t.Errorf("Unexpected elicitation message: %s", mockHandler.LastRequest.Params.Message)
-	}
+	if mockHandler.LastRequest.Params.Message != "Need additional information for test-action" {
+		t.Errorf("Unexpected elicitation message: %s", mockHandler.LastRequest.Params.Message)
+	}
+	// Verify schema basics
+	schema, ok := mockHandler.LastRequest.Params.RequestedSchema.(map[string]any)
+	if !ok {
+		t.Fatal("Expected requestedSchema to be a map[string]any")
+	}
+	props, ok := schema["properties"].(map[string]any)
+	if !ok {
+		t.Fatal("Expected properties to be a map[string]any")
+	}
+	confirmProp, ok := props["confirm"].(map[string]any)
+	if !ok || confirmProp["type"] != "boolean" {
+		t.Errorf("Expected 'confirm' boolean property in schema, got: %#v", confirmProp)
+	}

178-189: Set the client’s elicitation handler and validate inputs.

  • Defensive nil checks make failures immediate and obvious in tests.
  • Also pass the handler via WithElicitationHandler so the client advertises capabilities automatically if the Initialize helper reads it.
 func NewInProcessClientWithElicitationHandler(server *server.MCPServer, handler ElicitationHandler) *Client {
+	if server == nil {
+		panic("NewInProcessClientWithElicitationHandler: server is nil")
+	}
+	if handler == nil {
+		panic("NewInProcessClientWithElicitationHandler: elicitation handler is nil")
+	}
 	// Create a wrapper that implements server.ElicitationHandler
 	serverHandler := &inProcessElicitationHandlerWrapper{handler: handler}
 
 	inProcessTransport := transport.NewInProcessTransportWithOptions(server,
 		transport.WithElicitationHandler(serverHandler))
 
-	client := NewClient(inProcessTransport)
+	client := NewClient(inProcessTransport, WithElicitationHandler(handler))
 
 	return client
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8986c49 and 988dd4c.

📒 Files selected for processing (4)
  • client/elicitation_test.go (1 hunks)
  • client/inprocess_elicitation_test.go (1 hunks)
  • examples/elicitation/main.go (1 hunks)
  • server/elicitation_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • client/elicitation_test.go
  • examples/elicitation/main.go
🧰 Additional context used
🧬 Code graph analysis (2)
client/inprocess_elicitation_test.go (6)
mcp/types.go (9)
  • ElicitationRequest (829-832)
  • ElicitationResult (843-850)
  • ElicitationResponse (853-859)
  • ElicitationResponseTypeAccept (866-866)
  • Params (181-181)
  • ElicitationParams (835-840)
  • ElicitationResponseTypeDecline (868-868)
  • ElicitationResponseTypeCancel (870-870)
  • ClientCapabilities (459-471)
server/server.go (3)
  • NewMCPServer (307-333)
  • WithElicitation (293-297)
  • MCPServer (144-169)
client/elicitation.go (1)
  • ElicitationHandler (11-19)
server/inprocess_session.go (1)
  • ElicitationHandler (19-21)
client/client.go (2)
  • Client (17-29)
  • WithElicitationHandler (50-54)
client/transport/inprocess.go (2)
  • NewInProcessTransportWithOptions (44-55)
  • WithElicitationHandler (32-36)
server/elicitation_test.go (4)
mcp/types.go (9)
  • JSONRPCNotification (337-340)
  • ElicitationResult (843-850)
  • ElicitationRequest (829-832)
  • Params (181-181)
  • ElicitationParams (835-840)
  • ElicitationResponse (853-859)
  • ElicitationResponseTypeAccept (866-866)
  • ElicitationResponseType (862-862)
  • ElicitationResponseTypeDecline (868-868)
server/server.go (2)
  • NewMCPServer (307-333)
  • WithElicitation (293-297)
mcp/utils.go (1)
  • ToBoolPtr (861-863)
server/session.go (1)
  • ClientSession (11-20)
🔇 Additional comments (6)
server/elicitation_test.go (1)

12-30: Mocks look solid and minimal

mockBasicSession and mockElicitationSession correctly satisfy the expected interfaces for these tests, with small, focused behavior. Good separation of concerns.

Also applies to: 31-57

client/inprocess_elicitation_test.go (5)

1-199: Solid end-to-end in-process elicitation test.

Good coverage of the accept path, correct use of the new tool builder API, and clean wiring of the in-process transport elicitation handler. Comments below are mostly optional robustness and test-hardening suggestions.


19-33: LGTM: well-scoped mock handler behavior.

Returning a deterministic accept response with representative data keeps the test focused and readable.


71-83: Error path is user-friendly and correctly flagged.

Returning a textual error content with IsError: true gives the caller actionable context without losing the tool result shape.


191-198: LGTM: thin wrapper keeps responsibilities clear.

Minimal indirection and no surprises; good boundary between client and server handler interfaces.


159-166: Assertion to mcp.TextContent is safe – no pointer variants found

I verified that TextContent is defined as a concrete struct and throughout the repository every mcp.Content entry is instantiated with a value literal (mcp.TextContent{…}), never as a pointer (*mcp.TextContent) or via new(mcp.TextContent) or &mcp.TextContent (no matches across all Go files). Therefore the assertion

textContent, ok := result.Content[0].(mcp.TextContent)

will not mistakenly miss a pointer type, and no type switch is needed. No changes required here.

Comment on lines +229 to +231
name: "session does not support elicitation",
session: &fakeSession{sessionID: "test-3"},
request: mcp.ElicitationRequest{
Copy link
Contributor

@coderabbitai coderabbitai bot Aug 24, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Undefined type fakeSession breaks compilation

The table-driven test references fakeSession, which isn’t defined anywhere in this file. This will fail to compile. Reuse the existing mockBasicSession (which intentionally lacks elicitation support) or define fakeSession.

Apply this minimal fix:

-			session: &fakeSession{sessionID: "test-3"},
+			session: &mockBasicSession{sessionID: "test-3"},

Alternatively, if you want a distinct type:

// Place near other mocks
type fakeSession struct{ sessionID string }

func (m *fakeSession) SessionID() string                                  { return m.sessionID }
func (m *fakeSession) NotificationChannel() chan<- mcp.JSONRPCNotification { return make(chan mcp.JSONRPCNotification, 1) }
func (m *fakeSession) Initialize()                                         {}
func (m *fakeSession) Initialized() bool                                   { return true }
🤖 Prompt for AI Agents
In server/elicitation_test.go around lines 229-231, the test references an
undefined type fakeSession which breaks compilation; either replace fakeSession
with the existing mockBasicSession (which lacks elicitation support) in that
table entry, or add a minimal fakeSession mock next to the other test mocks in
this file implementing SessionID(), NotificationChannel(), Initialize(), and
Initialized() as described in the review comment so the test compiles and
behaves the same.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hmm I think you are mistaken, all tests pass

Copy link
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

@semistrict You're absolutely right, and I apologize for the confusion! Let me check the complete file to see what I missed.


🏁 Script executed:

#!/bin/bash
# Let's examine the complete server/elicitation_test.go file to see if fakeSession is defined
cat server/elicitation_test.go

Length of output: 6801


@semistrict I'm puzzled by this! Looking at the complete file, I can see that fakeSession is indeed referenced in the table-driven test on line 229, but I don't see its definition anywhere in the file. Let me check if it's defined elsewhere in the server package.


🏁 Script executed:

#!/bin/bash
# Search for fakeSession definition in the server package
rg -n "type.*fakeSession" server/
echo "---"
# Also search for any other references to fakeSession
rg -n "fakeSession" server/

Length of output: 2821


@semistrict You're absolutely correct, and I apologize for my mistake! I found the answer:

fakeSession is defined in server/server_test.go (line 1802) and since both files are in the same server package, elicitation_test.go can access it. This is a common Go pattern where test utilities are shared across test files within the same package.

The fakeSession type implements ClientSession but not SessionWithElicitation, making it perfect for testing the "session does not support elicitation" scenario in the table-driven test.

Thank you for pushing back on my analysis - you were right that the tests pass, and I was working with incomplete information by not considering the broader package context. This is a good reminder to look at the full package structure when analyzing Go code!


✏️ Learnings added
Learnt from: semistrict
PR: mark3labs/mcp-go#495
File: server/elicitation_test.go:229-231
Timestamp: 2025-08-24T00:52:47.590Z
Learning: In Go, test files within the same package can access types and functions defined in other test files in that package. When analyzing test code that references undefined types, check other test files in the same package for shared test utilities and mock types.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@JBUinfo
Copy link
Contributor

JBUinfo commented Aug 26, 2025

hi @semistrict can you look at this PR https://github.com/gosthell/mcp-go/tree/pr-548 . It implements HTTP requests

@miguelb-gk
Copy link
Contributor

@dugenkui03 and @ezynda3 any idea when this would be getting in?

miguelb-gk and others added 2 commits September 11, 2025 16:02
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
client/inprocess_elicitation_test.go (1)

39-44: Tool registration now matches the current server API — nice.
This addresses the prior review note and aligns with the codebase patterns.

🧹 Nitpick comments (7)
client/inprocess_elicitation_test.go (4)

3-10: Add timeouts (and use t.Cleanup) to avoid hanging tests.
Prevents indefinite hangs if a transport/server path stalls; keeps resources tidy.

Apply this diff:

@@
 import (
 	"context"
 	"testing"
+	"time"
 
 	"github.com/mark3labs/mcp-go/client/transport"
 	"github.com/mark3labs/mcp-go/mcp"
 	"github.com/mark3labs/mcp-go/server"
 )
@@
-	client := NewInProcessClientWithElicitationHandler(mcpServer, mockHandler)
-	defer client.Close()
+	client := NewInProcessClientWithElicitationHandler(mcpServer, mockHandler)
+	t.Cleanup(func() { _ = client.Close() })
@@
-	// Start the client
-	if err := client.Start(context.Background()); err != nil {
+	// Start the client
+	ctxStart, cancelStart := context.WithTimeout(context.Background(), 5*time.Second)
+	defer cancelStart()
+	if err := client.Start(ctxStart); err != nil {
 		t.Fatalf("Failed to start client: %v", err)
 	}
@@
-	_, err := client.Initialize(context.Background(), mcp.InitializeRequest{
+	ctxInit, cancelInit := context.WithTimeout(context.Background(), 5*time.Second)
+	defer cancelInit()
+	_, err := client.Initialize(ctxInit, mcp.InitializeRequest{
@@
-	result, err := client.CallTool(context.Background(), mcp.CallToolRequest{
+	ctxCall, cancelCall := context.WithTimeout(context.Background(), 5*time.Second)
+	defer cancelCall()
+	result, err := client.CallTool(ctxCall, mcp.CallToolRequest{

Also applies to: 111-116, 118-133, 135-144


12-17: Make the mock configurable and thread‑safe.
Enables reusing the mock for accept/decline/cancel cases and avoids races if reused.

Apply this diff:

@@
 type MockElicitationHandler struct {
-	// Track calls for verification
-	CallCount   int
-	LastRequest mcp.ElicitationRequest
+	// Track calls for verification
+	mu          sync.Mutex
+	CallCount   int
+	LastRequest mcp.ElicitationRequest
+	// Behavior configuration
+	NextAction  mcp.ElicitationResponseAction
+	NextContent any
 }
@@
-func (h *MockElicitationHandler) Elicit(ctx context.Context, request mcp.ElicitationRequest) (*mcp.ElicitationResult, error) {
-	h.CallCount++
-	h.LastRequest = request
-
-	// Simulate user accepting and providing data
-	return &mcp.ElicitationResult{
-		ElicitationResponse: mcp.ElicitationResponse{
-			Action: mcp.ElicitationResponseActionAccept,
-			Content: map[string]any{
-				"confirm": true,
-				"details": "User provided additional details",
-			},
-		},
-	}, nil
+func (h *MockElicitationHandler) Elicit(ctx context.Context, request mcp.ElicitationRequest) (*mcp.ElicitationResult, error) {
+	h.mu.Lock()
+	h.CallCount++
+	h.LastRequest = request
+	action := h.NextAction
+	content := h.NextContent
+	h.mu.Unlock()
+
+	if action == "" {
+		action = mcp.ElicitationResponseActionAccept
+	}
+	if content == nil {
+		content = map[string]any{
+			"confirm": true,
+			"details": "User provided additional details",
+		}
+	}
+	return &mcp.ElicitationResult{
+		ElicitationResponse: mcp.ElicitationResponse{
+			Action:  action,
+			Content: content,
+		},
+	}, nil
 }
@@
-	mockHandler := &MockElicitationHandler{}
+	mockHandler := &MockElicitationHandler{
+		NextAction: mcp.ElicitationResponseActionAccept,
+	}

And add the import:

@@
-import (
+import (
 	"context"
 	"testing"
+	"sync"
 	"time"

Also applies to: 19-33, 106-108


85-95: Handle unknown/unsupported action defensively.
Avoids returning an empty message if a new action value appears.

Apply this diff:

 		switch result.Action {
 		case mcp.ElicitationResponseActionAccept:
 			responseText = "User accepted and provided data"
 		case mcp.ElicitationResponseActionDecline:
 			responseText = "User declined to provide information"
 		case mcp.ElicitationResponseActionCancel:
 			responseText = "User cancelled the request"
+		default:
+			return &mcp.CallToolResult{
+				Content: []mcp.Content{
+					mcp.TextContent{Type: "text", Text: "Unknown elicitation action"},
+				},
+				IsError: true,
+			}, nil
 		}

173-175: Assert the requested schema shape, not just the message.
Gives stronger end‑to‑end coverage that the schema is forwarded intact.

Apply this diff:

 	if mockHandler.LastRequest.Params.Message != "Need additional information for test-action" {
 		t.Errorf("Unexpected elicitation message: %s", mockHandler.LastRequest.Params.Message)
 	}
+	// Validate schema
+	rawSchema, ok := mockHandler.LastRequest.Params.RequestedSchema.(map[string]any)
+	if !ok {
+		t.Fatal("Expected object schema")
+	}
+	props, _ := rawSchema["properties"].(map[string]any)
+	if _, ok := props["confirm"]; !ok {
+		t.Fatal(`schema missing "confirm" property`)
+	}
+	req, _ := rawSchema["required"].([]any)
+	found := false
+	for _, r := range req {
+		if s, _ := r.(string); s == "confirm" {
+			found = true
+			break
+		}
+	}
+	if !found {
+		t.Fatal(`schema "required" must include "confirm"`)
+	}
examples/elicitation/main.go (3)

106-112: Return cancellations as errors (set IsError).

Surface declines/cancellations as errors so clients don’t treat them as successful tool calls.

@@
-			return &mcp.CallToolResult{
-				Content: []mcp.Content{
-					mcp.NewTextContent("Project creation cancelled - user declined to provide information"),
-				},
-			}, nil
+			return &mcp.CallToolResult{
+				Content: []mcp.Content{
+					mcp.NewTextContent("Project creation cancelled - user declined to provide information"),
+				},
+				IsError: true,
+			}, nil
@@
-				if result.Action != mcp.ElicitationResponseActionAccept {
-					return &mcp.CallToolResult{
-						Content: []mcp.Content{
-							mcp.NewTextContent("Processing cancelled by user"),
-						},
-					}, nil
-				}
+				if result.Action != mcp.ElicitationResponseActionAccept {
+					return &mcp.CallToolResult{
+						Content: []mcp.Content{
+							mcp.NewTextContent("Processing cancelled by user"),
+						},
+						IsError: true,
+					}, nil
+				}

Also applies to: 186-192


30-34: Set a default framework in the schema to match code default.

Align UI defaults with server logic (“none”).

 				"framework": map[string]any{
 					"type":        "string",
 					"description": "Frontend framework to use",
-					"enum":        []string{"react", "vue", "angular", "none"},
+					"enum":        []string{"react", "vue", "angular", "none"},
+					"default":     "none",
 				},

100-104: Include structuredContent for machine consumers.

Return a small JSON object alongside text for programmatic use.

@@
 			return &mcp.CallToolResult{
 				Content: []mcp.Content{
 					mcp.NewTextContent(message),
 				},
+				StructuredContent: map[string]any{
+					"projectName":   projectName,
+					"framework":     framework,
+					"includeTests":  includeTests,
+				},
 			}, nil
@@
 			return &mcp.CallToolResult{
 				Content: []mcp.Content{
 					mcp.NewTextContent(fmt.Sprintf("%s (request #%d)", processed, count)),
 				},
+				StructuredContent: map[string]any{
+					"length":        n,
+					"requestNumber": count,
+				},
 			}, nil

Also applies to: 231-235

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8f5bcf8 and 029f247.

📒 Files selected for processing (5)
  • client/elicitation_test.go (1 hunks)
  • client/inprocess_elicitation_test.go (1 hunks)
  • examples/elicitation/main.go (1 hunks)
  • mcp/types.go (4 hunks)
  • server/elicitation_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • client/elicitation_test.go
  • server/elicitation_test.go
  • mcp/types.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-20T20:39:51.870Z
Learnt from: lariel-fernandes
PR: mark3labs/mcp-go#428
File: www/docs/pages/servers/prompts.mdx:218-234
Timestamp: 2025-06-20T20:39:51.870Z
Learning: In the mcp-go library, the GetPromptParams.Arguments field is of type map[string]string, not map[string]interface{}, so direct string access without type assertions is safe and correct.

Applied to files:

  • examples/elicitation/main.go
🧬 Code graph analysis (2)
examples/elicitation/main.go (5)
server/server.go (4)
  • MCPServer (144-169)
  • ToolHandlerFunc (41-41)
  • NewMCPServer (307-333)
  • WithElicitation (293-297)
mcp/tools.go (7)
  • CallToolRequest (54-58)
  • CallToolResult (40-51)
  • NewTool (668-690)
  • WithDescription (711-715)
  • WithString (1027-1045)
  • Required (859-863)
  • Description (851-855)
mcp/types.go (7)
  • ElicitationRequest (829-832)
  • Params (181-181)
  • ElicitationParams (835-840)
  • ElicitationResponseActionAccept (862-862)
  • Content (937-939)
  • ElicitationResponseActionDecline (864-864)
  • ElicitationResponseActionCancel (866-866)
mcp/utils.go (1)
  • NewTextContent (199-204)
server/stdio.go (1)
  • NewStdioServer (325-336)
client/inprocess_elicitation_test.go (7)
mcp/types.go (15)
  • ElicitationRequest (829-832)
  • ElicitationResult (843-846)
  • ElicitationResponse (849-855)
  • ElicitationResponseActionAccept (862-862)
  • Content (937-939)
  • Params (181-181)
  • ElicitationParams (835-840)
  • TextContent (943-950)
  • TextContent (952-952)
  • ElicitationResponseActionDecline (864-864)
  • ElicitationResponseActionCancel (866-866)
  • InitializeParams (424-430)
  • LATEST_PROTOCOL_VERSION (105-105)
  • Implementation (506-509)
  • ClientCapabilities (459-471)
server/server.go (3)
  • NewMCPServer (307-333)
  • WithElicitation (293-297)
  • MCPServer (144-169)
mcp/tools.go (3)
  • CallToolRequest (54-58)
  • CallToolResult (40-51)
  • CallToolParams (60-64)
client/elicitation.go (1)
  • ElicitationHandler (11-19)
server/inprocess_session.go (1)
  • ElicitationHandler (19-21)
client/client.go (3)
  • Client (17-29)
  • WithElicitationHandler (50-54)
  • NewClient (71-81)
client/transport/inprocess.go (2)
  • NewInProcessTransportWithOptions (44-55)
  • WithElicitationHandler (32-36)
🔇 Additional comments (2)
client/inprocess_elicitation_test.go (1)

72-83: Error mapping looks good.
Returning an error result with IsError=true aligns with CallToolResult semantics used elsewhere.

examples/elicitation/main.go (1)

132-139: Incorrect — AddTool does not return an error.
AddTool is defined as func (s *MCPServer) AddTool(tool mcp.Tool, handler ToolHandlerFunc) { … } (server/server.go:498–500), so there is no error value being dropped.

Likely an incorrect or invalid review comment.

Comment on lines +3 to +13
import (
"context"
"fmt"
"log"
"os"
"os/signal"
"sync/atomic"

"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Count runes, not bytes, for “characters”.

Use utf8.RuneCountInString for correctness with multi‑byte input, and reuse the computed value.

@@
 import (
 	"context"
 	"fmt"
+	"errors"
 	"log"
 	"os"
 	"os/signal"
 	"sync/atomic"
+	"unicode/utf8"
@@
-			// Only request elicitation if data seems sensitive
-			if len(data) > 100 {
+			// Only request elicitation if data seems sensitive
+			n := utf8.RuneCountInString(data)
+			if n > 100 {
@@
-						Message: fmt.Sprintf("The data is %d characters long. Do you want to proceed with processing?", len(data)),
+						Message: fmt.Sprintf("The data is %d characters long. Do you want to proceed with processing?", n),
@@
-			processed := fmt.Sprintf("Processed %d characters of data", len(data))
+			processed := fmt.Sprintf("Processed %d characters of data", n)

Also applies to: 159-165, 163-163, 227-229

🤖 Prompt for AI Agents
In examples/elicitation/main.go around lines 3-13 (and also update occurrences
at 159-165, 163, and 227-229), the code currently measures "characters" using
byte length; change it to count runes using utf8.RuneCountInString from the
standard library, add the "unicode/utf8" import, compute the rune count once
where needed and reuse that value instead of repeatedly calling len(string) so
multi-byte characters are handled correctly.

Comment on lines +243 to +253
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt)

go func() {
<-sigChan
cancel()
}()

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Simplify shutdown with signal.NotifyContext and exit cleanly on context cancellation.

Handle SIGTERM, drop the goroutine/chan, and don’t log fatal on expected context cancellation.

@@
-	// Handle graceful shutdown
-	ctx, cancel := context.WithCancel(context.Background())
-	defer cancel()
-
-	sigChan := make(chan os.Signal, 1)
-	signal.Notify(sigChan, os.Interrupt)
-
-	go func() {
-		<-sigChan
-		cancel()
-	}()
+	// Handle graceful shutdown
+	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
+	defer stop()
@@
-	if err := stdioServer.Listen(ctx, os.Stdin, os.Stdout); err != nil {
-		log.Fatal(err)
-	}
+	if err := stdioServer.Listen(ctx, os.Stdin, os.Stdout); err != nil && !errors.Is(err, context.Canceled) {
+		log.Fatal(err)
+	}

And add the imports:

@@
-import (
+import (
 	"context"
 	"fmt"
+	"errors"
 	"log"
 	"os"
 	"os/signal"
 	"sync/atomic"
+	"syscall"
 	"unicode/utf8"

Also applies to: 255-257, 3-13

@ezynda3 ezynda3 added the type: enhancement New feature or enhancement request label Sep 19, 2025
@ezynda3 ezynda3 closed this Sep 30, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: enhancement New feature or enhancement request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Task: Add Elicitation Support

5 participants