Skip to content

TypeScript Compatibility: protect() method forces type conversion causing integration errors #7

@ryanRfox

Description

@ryanRfox

GitHub Issue #7: TypeScript Compatibility: protect() method forces type conversion causing integration errors

Issue Type

🐛 Bug Report

Summary

The SDK's protect() method has a rigid type signature that forces all handlers to the MCPHandler type, causing TypeScript errors when integrating with the MCP TypeScript SDK and other frameworks that have specific handler type signatures.

Current Behavior

The current method signature forces type conversion:

protect(tokenId: number | number[], handler: MCPHandler): MCPHandler

When developers use handlers with specific type signatures from their MCP framework:

// MCP TypeScript SDK handler with specific types
type SpecificMCPHandler = (
  request: MCPRequest, 
  extra?: { session?: string }
) => Promise<MCPResponse>;

const myHandler: SpecificMCPHandler = async (request, extra) => {
  const sessionId = extra?.session; // TypeScript knows this is string | undefined
  return { content: [{ type: 'text', text: 'Result' }] };
};

// This causes TypeScript error
const protectedHandler = radius.protect(101, myHandler);
// ❌ Error: Type 'MCPHandler' is not assignable to type 'SpecificMCPHandler'

// Developers must use type assertion as workaround
const protectedHandler = radius.protect(101, myHandler) as SpecificMCPHandler;

This also affects FastMCP and other framework integrations:

// FastMCP handler
type FastMCPHandler = (args: any) => Promise<{ result: string }>;

const fastHandler: FastMCPHandler = async (args) => {
  return { result: 'success' };
};

// ❌ Type error: loses FastMCP-specific return type
const protected = radius.protect(102, fastHandler);

Expected Behavior

The protect() method should preserve the original handler's type signature, allowing seamless integration without type errors or requiring type assertions. The protected handler should maintain the exact same type as the input handler.

Steps to Reproduce

  1. Install Radius MCP SDK in a TypeScript project with strict mode
  2. Create a handler with specific type signature from MCP TypeScript SDK
  3. Apply protect() to the handler
  4. Observe TypeScript error about type incompatibility
  5. Note that type assertion is required as workaround

Developer Experience Impact

Current Developer Experience

  1. TypeScript Errors on Integration: Every protected handler causes type errors with framework-specific handlers
  2. Type Assertion Workarounds: Developers must add as TypeOfHandler after every protect() call
  3. Lost Type Safety: Type assertions bypass TypeScript's type checking, potentially hiding real issues
  4. Poor IntelliSense: IDE autocomplete breaks due to forced type conversion
  5. Integration Friction: What should be a simple one-line protection requires additional type management

Expected Developer Experience

  1. Seamless Type Preservation: Protected handlers maintain their original type signatures
  2. No Type Assertions Needed: Clean, readable code without workarounds
  3. Full Type Safety: TypeScript validates the actual handler types throughout
  4. Perfect IntelliSense: IDE autocomplete works correctly with protected handlers
  5. Zero-Friction Integration: Works out-of-the-box with any MCP framework

Impact

  • Critical: Blocks adoption for teams using TypeScript strict mode
  • Affects: All TypeScript users integrating with MCP TypeScript SDK, FastMCP, or custom typed handlers
  • Workaround: Type assertions work but compromise type safety

Environment

  • SDK Version: 1.0.0
  • TypeScript Version: 5.x with strict mode
  • Node.js: 16.0.0+
  • Affected Frameworks: MCP TypeScript SDK, FastMCP, custom implementations

Proposed Solution

Make the protect() method generic to preserve the original handler type:

// Current implementation (forces type conversion)
protect(tokenId: number | number[], handler: MCPHandler): MCPHandler

// Proposed implementation (preserves original type)
protect<T extends (...args: any[]) => any>(
  tokenId: number | number[], 
  handler: T
): T

This change would:

  • Preserve the exact type of the input handler
  • Eliminate all TypeScript errors on integration
  • Remove the need for type assertion workarounds
  • Maintain full backward compatibility (existing code continues to work)
  • Enable seamless integration with any MCP framework

Additional Context

This issue was reported by multiple developers integrating the SDK with the official MCP TypeScript SDK. The TypeScript team's recommendation for wrapper functions is to use generics to preserve type information, which is considered a best practice for SDK design.

The proposed solution has been validated to:

  • Fix all type errors
  • Maintain backward compatibility
  • Pass all existing tests
  • Work with MCP SDK, FastMCP, and generic handlers

Acceptance Criteria

  • protect() method uses generic type parameter to preserve handler type
  • No TypeScript errors when using with MCP TypeScript SDK handlers
  • No TypeScript errors when using with FastMCP handlers
  • Type assertions are no longer needed
  • All existing tests continue to pass
  • IntelliSense/autocomplete works correctly with protected handlers
  • No breaking changes to existing code
  • Documentation updated with typed examples

Labels: bug, typescript, developer-experience, compatibility
Priority: High
Milestone: Next Release

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions