Skip to content

feat: add MCP elicitation support (form + URL modes)#190

Draft
datashaman wants to merge 10 commits intolaravel:mainfrom
datashaman:mcp_elicitations
Draft

feat: add MCP elicitation support (form + URL modes)#190
datashaman wants to merge 10 commits intolaravel:mainfrom
datashaman:mcp_elicitations

Conversation

@datashaman
Copy link
Copy Markdown

@datashaman datashaman commented Apr 6, 2026

Summary

  • Implements the MCP 2025-11-25 elicitation specification, allowing servers to request additional information from users mid-tool-execution
  • Adds injectable Elicitation service with form() (structured JSON Schema data collection) and url() (external redirects for OAuth/payments) modes
  • Includes fluent schema builders for all MCP-specified field types (string, number, integer, boolean, enum, multi-select enum), client capability detection, UrlElicitationRequiredException (-32042), completion notifications, and full testing assertions

Usage

// In a Tool's handle() method — type-hint Elicitation to inject it
public function handle(Request $request, Elicitation $elicitation): Response
{
    $result = $elicitation->form('Please provide your details', function (ElicitSchema $schema) {
        return [
            'name'  => $schema->string('Your Name')->required(),
            'email' => $schema->string('Email')->format('email'),
            'plan'  => $schema->enum('Plan', ['free', 'pro', 'enterprise'])->default('free'),
        ];
    });

    if ($result->accepted()) {
        return Response::text('Hello, '.$result->get('name').'!');
    }

    return Response::text('No details provided.');
}
// URL mode for OAuth/payments
$result = $elicitation->url('Please authorize access', 'https://myapp.com/oauth/authorize');

if ($result->accepted()) {
    $elicitation->notifyComplete($result->elicitationId());
}
// Testing
MyServer::elicitation(['action' => 'accept', 'content' => ['name' => 'Taylor']])
    ->tool(MyTool::class)
    ->assertElicited()
    ->assertElicitedForm('Please provide your details')
    ->assertSee('Hello, Taylor!');

Changes

New files (13 source, 5 test):

  • src/Server/Elicitation/Elicitation.php — injectable service with event dispatching
  • src/Server/Elicitation/ElicitationResult.php — response value object
  • src/Server/Elicitation/ElicitSchema.php — fluent schema builder
  • src/Server/Elicitation/UrlElicitationRequiredException.php — error code -32042
  • src/Server/Elicitation/Events/ElicitationSent.php — dispatched on elicitation request
  • src/Server/Elicitation/Events/ElicitationReceived.php — dispatched on response
  • src/Server/Elicitation/Fields/ — ElicitField interface + AbstractElicitField base + 6 field classes
  • tests/Unit/Elicitation/ — 4 unit test files
  • tests/Feature/Testing/Tools/AssertElicitationTest.php — integration tests

Modified files (8):

  • src/Server/Contracts/Transport.php — added sendRequest() method
  • src/Server/Transport/StdioTransport.php — blocking stdin implementation
  • src/Server/Transport/HttpTransport.php — SSE + cache polling implementation
  • src/Server/Transport/FakeTransporter.php — expectation queue + sent tracking
  • src/Server/Transport/JsonRpcResponse.php — added ::request() factory
  • src/Server.php — elicitation capability, response routing, Elicitation container binding
  • src/Server/Testing/PendingTestResponse.phpelicitation() test setup method
  • src/Server/Testing/TestResponse.php — elicitation assertion methods

Test plan

  • All 690 tests pass
  • PHPStan clean, Pint clean, Rector clean
  • Manual: StdioTransport round-trip with a real MCP client
  • Manual: HttpTransport round-trip with cache-based polling

🤖 Generated with Claude Code

Implement the MCP 2025-11-25 elicitation specification, allowing servers
to request additional information from users mid-tool-execution. Adds an
injectable Elicitation service with form mode (structured data via JSON
Schema) and URL mode (external redirects for OAuth/payments), fluent
schema builders, client capability detection, and testing assertions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 6, 2026

Thanks for submitting a PR!

Note that draft PRs are not reviewed. If you would like a review, please mark your pull request as ready for review in the GitHub user interface.

Pull requests that are abandoned in draft may be closed due to inactivity.

datashaman and others added 9 commits April 6, 2026 06:37
…ntract

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ansport routing

- Improve 3 error messages with cause + fix guidance
- Add ElicitationSent and ElicitationReceived Laravel events
- Advertise elicitation capability in server initialize response
- Add clientCapabilities() to Transport interface, remove FakeTransporter coupling
- Extract AbstractElicitField base class, add isRequired() to interface
- Route elicitation responses to cache for HttpTransport polling (P1 fix)
- Add cache cleanup on timeout, cache TTL on writes
- Add ID mismatch test, update field tests for isRequired()

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Apply SafeDeclareStrictTypesRector, ClosureToArrowFunctionRector,
and AddArrowFunctionReturnTypeRector fixes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Cast json_encode returns in FakeTransporter to string
- Remove redundant null coalescing on stream_get_meta_data keys
- Add array cast for nullable titledOptions in foreach loops

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract elicitations() helper in TestResponse, reorder properties in
PendingTestResponse, deduplicate json_decode in FakeTransporter, and
add missing strict_types declarations to test files.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ity key

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…method

Merge withElicitation() and expectsElicitation() into elicitation() on
PendingTestResponse. Use CAPABILITY_ELICITATION constant consistently.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Only FakeTransporter used it meaningfully; real transports returned null.
Capabilities now flow through Server::$clientCapabilities exclusively.
PendingTestResponse injects capabilities via ->call() pattern.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@datashaman datashaman marked this pull request as ready for review April 6, 2026 07:39
@pushpak1300 pushpak1300 self-assigned this Apr 6, 2026
@pushpak1300 pushpak1300 self-requested a review April 6, 2026 11:40
@pushpak1300 pushpak1300 marked this pull request as draft April 6, 2026 11:40
@pushpak1300
Copy link
Copy Markdown
Member

Hi before I start the review, could you confirm you’ve tested this locally and it works? The entire PR looks AI-generated and as this is a very large PR I don’t just want to jump in and review it. Could you please double-check?

@datashaman
Copy link
Copy Markdown
Author

datashaman commented Apr 6, 2026

I've tested that elicitation works with STDIO mode in Claude Code.
I struggled to find clients that implement elicitations in HTTP streaming mode, so that's not tested.

@datashaman
Copy link
Copy Markdown
Author

Hmm, I have not given it a runthrough via MCP Inspector. Please hold off on reviewing this until I get back to you. That will support everything.

@datashaman
Copy link
Copy Markdown
Author

The MCP Inspector does not support HTTP elicitations.
Neither does Claude Code.
:(

@pushpak1300
Copy link
Copy Markdown
Member

Screenshot 2026-04-08 at 8 52 38 PM

Can you point me to relevant issue ? i can see the elicitation support in mcp isnpetor fr http server/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants