Skip to content

Conversation

amannirala13
Copy link

Fix ElicitResult Schema to Support null type for content in Cancel/Decline Responses

This PR fixes the ElicitResultSchema validation to properly accept content: null for cancel/decline responses, resolving a type validation bug that violated MCP specification flexibility requirements.

Fixes #966

Motivation and Context

The current ElicitResultSchema incorrectly rejects content: null for cancel/decline responses, causing validation failures in legitimate elicitation workflows. This violates the MCP specification which states that content is "typically omitted" (not forbidden) for these actions.

Problem scenarios:

  • ElicitResultSchema.parse({ action: "cancel", content: null }) throws "Expected object, received null" error
  • Clients using content: null as a sensible default pattern face validation failures
  • The existing schema was too restrictive compared to MCP spec requirements

Root cause:

The schema used a simple interface that didn't distinguish between accept actions (which require content) and cancel/decline actions (which should allow flexible content patterns).

Solution

Replaced the simple interface with a discriminated union that:

  • Accept actions: Require valid content object (maintains type safety)
  • Cancel/decline actions: Allow optional content including null (MCP spec compliant)

Tests

  1. Schema validation tests - Direct testing of all problematic scenarios:
    • { action: "cancel", content: null } - Now passes (was failing)
    • { action: "decline", content: null } - Now passes (was failing)
    • { action: "cancel" } - Still passes (omitted content)
    • { action: "accept", content: {...} } - Still passes (required content)
  2. Integration tests - Server-client elicitation workflows with various response patterns
  3. Regression testing - All existing test patterns continue to work
  4. Build verification - All 756 tests pass, TypeScript compilation successful
  5. Real application scenarios - Tested with example server/client implementations

Breaking Changes

None. This is a backward-compatible fix that:

  • Maintains all existing working patterns
  • Only adds support for previously failing content: null scenarios
  • Preserves type safety for accept actions (content still required)
  • All existing code continues to work without modification

Technical Implementation

// New discriminated union approach
export const ElicitResultSchema = z.union([
  // Accept: content required
  ResultSchema.extend({
    action: z.literal("accept"),
    content: z.record(z.string(), z.unknown())
  }),
  // Cancel/decline: content optional and flexible  
  ResultSchema.extend({
    action: z.enum(["decline", "cancel"]),
    content: z.optional(z.any())
  })
])

Files Changed

  • src/types.ts - Updated ElicitResultSchema with discriminated union
  • src/elicit-result.test.ts - Added comprehensive test suite covering all scenarios
  • fix-spec-types.cjs - Utility script for test compatibility with external spec types
  • package.json - Enhanced spec type compatibility workflow
  • .gitignore - Updated to include development utility script

Types of Changes

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

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally (756/756)
  • I have added appropriate error handling
  • I have added comprehensive test coverage for the fix
  • The fix maintains backward compatibility
  • External spec type compatibility is preserved

This approach provides better type inference while maintaining MCP spec compliance and supporting all reasonable developer patterns, including the common content: null pattern for cancelled elicitation requests.

- Fix ElicitResultSchema to accept content: null for cancel/decline actions
- Replace simple interface with discriminated union for better type safety
- Accept action requires content, decline/cancel allows optional content
- Maintains backward compatibility with existing code patterns
- Adds comprehensive test suite to prevent regression

Fixes modelcontextprotocol#966
Update placeholder XXX to actual issue number modelcontextprotocol#966 in ElicitResult test suite
@felixweinberger felixweinberger force-pushed the main branch 2 times, most recently from 0845a57 to c94ba4b Compare September 25, 2025 14:53
@amannirala13 amannirala13 marked this pull request as ready for review September 25, 2025 23:34
@amannirala13 amannirala13 requested a review from a team as a code owner September 25, 2025 23:34
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.

The validation of ElicitResultSchema is excessively strict and violates the MCP Specifications.
1 participant