Skip to content

Conversation

ryanRfox
Copy link

@ryanRfox ryanRfox commented Sep 8, 2025

Make protect() method generic to preserve handler types

Fixes #7

🎯 Core Implementation

Changes the protect() method signature from rigid type enforcement to generic type preservation:

// Before: Forces type conversion
protect(tokenId: number | number[], handler: MCPHandler): MCPHandler

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

🚀 Key Benefits

  • Zero Type Errors: Eliminates TypeScript errors when integrating with MCP TypeScript SDK
  • Type Preservation: Protected handlers maintain their exact original type signatures
  • No Type Assertions: Removes the need for as TypeOfHandler workarounds
  • Perfect IntelliSense: IDE autocomplete works correctly with protected handlers
  • 100% Backward Compatible: Existing code continues to work unchanged

📋 Usage Examples

MCP TypeScript SDK Integration

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

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

// Before: ❌ Type error
const protected = radius.protect(101, handler); // Type 'MCPHandler' not assignable

// After: ✅ Type preserved
const protected = radius.protect(101, handler); // Still SpecificMCPHandler

FastMCP Integration

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

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

// Type is preserved perfectly
const protected: FastMCPHandler = radius.protect(102, fastHandler);

🔧 Technical Implementation

  • Generic Type Parameter: Uses <T extends (...args: any[]) => any> to accept any function shape
  • Type Assertion in Implementation: Casts wrapped handler back to T to preserve type
  • No Runtime Changes: Only TypeScript signatures changed, runtime behavior identical
  • Removed Unused Import: Cleaned up unused MCPHandler import

Changes Made

  1. Updated protect() method signature to use generic type parameter
  2. Fixed RadiusErrorCode to be const object instead of type union (allows use as both type and value)
  3. Removed unused MCPHandler import from radius-mcp-sdk.ts

🧪 Testing

All tests pass with the new implementation:

  • 37/37 tests passing including all existing test cases
  • TypeScript compilation successful with strict mode enabled
  • No runtime behavior changes - only type signatures improved
  • Backward compatibility verified - existing code works unchanged
pnpm test
# ✓ src/__tests__/radius-mcp-sdk.test.ts (29 tests)
# ✓ src/__tests__/radius-mcp-server-integration.test.ts (8 tests)
# Test Files  2 passed (2)
# Tests      37 passed (37)

pnpm run typecheck
# No errors

📁 Files Changed

  • src/radius-mcp-sdk.ts: Made protect() generic, removed unused import
  • src/types/errors.ts: Fixed RadiusErrorCode to be const object

✔️ Checklist

… strict mode

- Changed RadiusErrorCode from a union type to a const object with type inference
- Maintains type safety while allowing use as both type and value
- Fixes TypeScript compilation errors in strict mode
- All tests pass (37/37)
- Changed protect() to use generic type T extending function signature
- Preserves original handler type instead of forcing MCPHandler type
- Enables seamless integration with any MCP framework (MCP SDK, FastMCP, etc.)
- Fixes TypeScript compatibility issues reported by users
- No breaking changes - existing code continues to work

This change makes the SDK more flexible and developer-friendly by allowing
handlers to maintain their original type signatures through the protection
wrapper.
@ryanRfox ryanRfox requested a review from Matt-Dionis September 8, 2025 13:46
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.

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

1 participant