packages feed

mcp-server-0.1.0.10: src/MCP/Server/Derive.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell   #-}

module MCP.Server.Derive
  ( -- * Template Haskell Derivation
    derivePromptHandler
  , derivePromptHandlerWithDescription
  , deriveResourceHandler
  , deriveResourceHandlerWithDescription
  , deriveToolHandler
  , deriveToolHandlerWithDescription
  ) where

import qualified Data.Map            as Map
import qualified Data.Text           as T
import           Language.Haskell.TH
import           Text.Read           (readMaybe)
import qualified Data.Char           as Char

import           MCP.Server.Types

-- Helper function to convert PascalCase/camelCase to snake_case
toSnakeCase :: String -> String
toSnakeCase [] = []
toSnakeCase (x:xs) = Char.toLower x : go xs
  where
    go [] = []
    go (c:cs)
      | Char.isUpper c = '_' : Char.toLower c : go cs
      | otherwise = c : go cs

-- Helper function to convert Clause to Match
clauseToMatch :: Clause -> Match
clauseToMatch (Clause ps b ds) = Match (case ps of [p] -> p; _ -> TupP ps) b ds

-- | Derive prompt handlers from a data type with custom descriptions
-- Usage: $(derivePromptHandlerWithDescription ''MyPrompt 'handlePrompt [("Constructor", "Description")])
derivePromptHandlerWithDescription :: Name -> Name -> [(String, String)] -> Q Exp
derivePromptHandlerWithDescription typeName handlerName descriptions = do
  info <- reify typeName
  case info of
    TyConI (DataD _ _ _ _ constructors _) -> do
      -- Generate prompt definitions
      promptDefs <- sequence $ map (mkPromptDefWithDescription descriptions) constructors

      -- Generate list handler
      listHandlerExp <- [| pure $(return $ ListE promptDefs) |]

      -- Generate get handler with cases
      cases <- sequence $ map (mkPromptCase handlerName) constructors
      defaultCase <- [| pure $ Left $ InvalidPromptName $ "Unknown prompt: " <> name |]
      let defaultMatch = Match WildP (NormalB defaultCase) []

      getHandlerExp <- return $ LamE [VarP (mkName "name"), VarP (mkName "args")] $
        CaseE (AppE (VarE 'T.unpack) (VarE (mkName "name")))
          (map clauseToMatch cases ++ [defaultMatch])

      return $ TupE [Just listHandlerExp, Just getHandlerExp]
    _ -> fail $ "derivePromptHandlerWithDescription: " ++ show typeName ++ " is not a data type"

-- | Derive prompt handlers from a data type
-- Usage: $(derivePromptHandler ''MyPrompt 'handlePrompt)
derivePromptHandler :: Name -> Name -> Q Exp
derivePromptHandler typeName handlerName = 
  derivePromptHandlerWithDescription typeName handlerName []

mkPromptDefWithDescription :: [(String, String)] -> Con -> Q Exp
mkPromptDefWithDescription descriptions con = 
  case con of
    NormalC name [] -> do
      let promptName = T.pack . toSnakeCase . nameBase $ name
      let constructorName = nameBase name
      let description = case lookup constructorName descriptions of
            Just desc -> desc
            Nothing   -> "Handle " ++ constructorName
      [| PromptDefinition
          { promptDefinitionName = $(litE $ stringL $ T.unpack promptName)
          , promptDefinitionDescription = $(litE $ stringL description)
          , promptDefinitionArguments = []
          } |]
    RecC name fields -> do
      let promptName = T.pack . toSnakeCase . nameBase $ name
      let constructorName = nameBase name
      let description = case lookup constructorName descriptions of
            Just desc -> desc
            Nothing   -> "Handle " ++ constructorName
      args <- sequence $ map (mkArgDef descriptions) fields
      [| PromptDefinition
          { promptDefinitionName = $(litE $ stringL $ T.unpack promptName)
          , promptDefinitionDescription = $(litE $ stringL description)
          , promptDefinitionArguments = $(return $ ListE args)
          } |]
    NormalC name [(_bang, paramType)] -> do
      -- Handle separate parameter types approach
      let promptName = T.pack . toSnakeCase . nameBase $ name
      let constructorName = nameBase name
      let description = case lookup constructorName descriptions of
            Just desc -> desc
            Nothing   -> "Handle " ++ constructorName
      args <- extractFieldsFromType descriptions paramType
      [| PromptDefinition
          { promptDefinitionName = $(litE $ stringL $ T.unpack promptName)
          , promptDefinitionDescription = $(litE $ stringL description)
          , promptDefinitionArguments = $(return $ ListE args)
          } |]
    _ -> fail "Unsupported constructor type"

-- Extract field definitions from a parameter type recursively
extractFieldsFromType :: [(String, String)] -> Type -> Q [Exp]
extractFieldsFromType descriptions paramType = do
  case paramType of
    ConT typeName -> do
      info <- reify typeName
      case info of
        TyConI (DataD _ _ _ _ [RecC _ fields] _) -> do
          -- Parameter type is a record with fields
          sequence $ map (mkArgDef descriptions) fields
        TyConI (DataD _ _ _ _ [NormalC _ [(_bang, innerType)]] _) -> do
          -- Parameter type has a single parameter - recurse
          extractFieldsFromType descriptions innerType
        _ -> fail $ "Parameter type " ++ show typeName ++ " must be a record type or single-parameter constructor"
    _ -> fail $ "Parameter type must be a concrete type, got: " ++ show paramType

mkArgDef :: [(String, String)] -> (Name, Bang, Type) -> Q Exp
mkArgDef descriptions (fieldName, _, fieldType) = do
  let isOptional = case fieldType of
        AppT (ConT n) _ -> nameBase n == "Maybe"
        _               -> False
  let fieldNameStr = nameBase fieldName
  let description = case lookup fieldNameStr descriptions of
        Just desc -> desc
        Nothing   -> fieldNameStr
  [| ArgumentDefinition
      { argumentDefinitionName = $(litE $ stringL fieldNameStr)
      , argumentDefinitionDescription = $(litE $ stringL description)
      , argumentDefinitionRequired = $(if isOptional then [| False |] else [| True |])
      } |]

mkPromptCase :: Name -> Con -> Q Clause
mkPromptCase handlerName (NormalC name []) = do
  let promptName = T.pack . toSnakeCase . nameBase $ name
  clause [litP $ stringL $ T.unpack promptName]
    (normalB [| do
        content <- $(varE handlerName) $(conE name)
        pure $ Right content |])
    []
mkPromptCase handlerName (RecC name fields) = do
  let promptName = T.pack . toSnakeCase . nameBase $ name
  body <- mkRecordCase name handlerName fields
  clause [litP $ stringL $ T.unpack promptName] (normalB (return body)) []
mkPromptCase handlerName (NormalC name [(_bang, paramType)]) = do
  let promptName = T.pack . toSnakeCase . nameBase $ name
  body <- mkSeparateParamsCase name handlerName paramType
  clause [litP $ stringL $ T.unpack promptName] (normalB (return body)) []
mkPromptCase _ _ = fail "Unsupported constructor type"

mkSeparateParamsCase :: Name -> Name -> Type -> Q Exp
mkSeparateParamsCase conName handlerName paramType = do
  fields <- extractFieldsFromParamType paramType
  buildNestedFieldValidationWithConstructor conName handlerName paramType fields 0
  where
    buildNestedFieldValidationWithConstructor :: Name -> Name -> Type -> [(Name, Bang, Type)] -> Int -> Q Exp
    buildNestedFieldValidationWithConstructor outerConName handlerName' paramType' [] depth = do
      -- Base case: all fields validated, build parameter constructor hierarchy and outer constructor
      let fieldVars = [mkName ("field" ++ show i) | i <- [0..depth-1]]
      paramConstructorApp <- buildParameterConstructor paramType' fieldVars
      let outerConstructorApp = AppE (ConE outerConName) paramConstructorApp
      [| do
          content <- $(varE handlerName') $(return outerConstructorApp)
          pure $ Right content |]
    
    buildNestedFieldValidationWithConstructor outerConName handlerName' paramType' ((fieldName, _, fieldType):remainingFields) depth = do
      let fieldStr = nameBase fieldName
      let (isOptional, innerType) = case fieldType of
            AppT (ConT n) inner | nameBase n == "Maybe" -> (True, inner)
            other                                       -> (False, other)
      let fieldVar = mkName ("field" ++ show depth)

      continuation <- buildNestedFieldValidationWithConstructor outerConName handlerName' paramType' remainingFields (depth + 1)

      -- Generate conversion expression based on type
      let convertExpr rawVar = case innerType of
            ConT n | nameBase n == "Int" ->
              [| case readMaybe (T.unpack $(varE rawVar)) of
                   Just parsed -> parsed
                   Nothing -> error $ "Failed to parse Int from: " <> T.unpack $(varE rawVar) |]
            ConT n | nameBase n == "Integer" ->
              [| case readMaybe (T.unpack $(varE rawVar)) of
                   Just parsed -> parsed
                   Nothing -> error $ "Failed to parse Integer from: " <> T.unpack $(varE rawVar) |]
            ConT n | nameBase n == "Double" ->
              [| case readMaybe (T.unpack $(varE rawVar)) of
                   Just parsed -> parsed
                   Nothing -> error $ "Failed to parse Double from: " <> T.unpack $(varE rawVar) |]
            ConT n | nameBase n == "Float" ->
              [| case readMaybe (T.unpack $(varE rawVar)) of
                   Just parsed -> parsed
                   Nothing -> error $ "Failed to parse Float from: " <> T.unpack $(varE rawVar) |]
            ConT n | nameBase n == "Bool" ->
              [| case T.toLower $(varE rawVar) of
                   "true" -> True
                   "false" -> False
                   _ -> error $ "Failed to parse Bool from: " <> T.unpack $(varE rawVar) |]
            _ -> varE rawVar  -- Text or other types, use as-is

      if isOptional
        then do
          rawFieldVar <- newName ("raw" ++ show depth)
          convertedExpr <- convertExpr rawFieldVar
          [| do
              let $(varP fieldVar) = case Map.lookup $(litE $ stringL fieldStr) (Map.fromList args) of
                    Nothing                  -> Nothing
                    Just $(varP rawFieldVar) -> Just $(return convertedExpr)
              $(return continuation) |]
        else do
          rawFieldVar <- newName ("raw" ++ show depth)
          convertedExpr <- convertExpr rawFieldVar
          [| case Map.lookup $(litE $ stringL fieldStr) (Map.fromList args) of
              Just $(varP rawFieldVar) -> do
                let $(varP fieldVar) = $(return convertedExpr)
                $(return continuation)
              Nothing -> pure $ Left $ MissingRequiredParams $ "field '" <> $(litE $ stringL fieldStr) <> "' is missing" |]

-- Extract field information from parameter type
extractFieldsFromParamType :: Type -> Q [(Name, Bang, Type)]
extractFieldsFromParamType paramType = do
  case paramType of
    ConT typeName -> do
      info <- reify typeName
      case info of
        TyConI (DataD _ _ _ _ [RecC _ fields] _) -> 
          return fields
        TyConI (DataD _ _ _ _ [NormalC _ [(_bang, innerType)]] _) -> 
          extractFieldsFromParamType innerType
        _ -> fail $ "Parameter type " ++ show typeName ++ " must be a record type or single-parameter constructor"
    _ -> fail $ "Parameter type must be a concrete type, got: " ++ show paramType

-- Build the parameter constructor application recursively
buildParameterConstructor :: Type -> [Name] -> Q Exp
buildParameterConstructor paramType fieldVars = do
  case paramType of
    ConT typeName -> do
      info <- reify typeName
      case info of
        TyConI (DataD _ _ _ _ [RecC conName _] _) -> do
          -- Record constructor - apply all field variables
          return $ foldl AppE (ConE conName) (map VarE fieldVars)
        TyConI (DataD _ _ _ _ [NormalC conName [(_bang, innerType)]] _) -> do
          -- Single parameter constructor - recurse and wrap
          innerConstructor <- buildParameterConstructor innerType fieldVars
          return $ AppE (ConE conName) innerConstructor
        _ -> fail $ "Parameter type " ++ show typeName ++ " must be a record type or single-parameter constructor"
    _ -> fail $ "Parameter type must be a concrete type, got: " ++ show paramType

mkRecordCase :: Name -> Name -> [(Name, Bang, Type)] -> Q Exp
mkRecordCase conName handlerName fields = do
  case fields of
    [] -> [| do
        content <- $(varE handlerName) $(conE conName)
        pure $ Right content |]
    _ -> do
      -- Build nested case expressions for field validation
      buildNestedFieldValidation conName handlerName fields 0

-- Build nested case expressions for field validation, supporting any number of fields
buildNestedFieldValidation :: Name -> Name -> [(Name, Bang, Type)] -> Int -> Q Exp
buildNestedFieldValidation conName handlerName [] depth = do
  -- Base case: all fields validated, build constructor application
  let fieldVars = [mkName ("field" ++ show i) | i <- [0..depth-1]]
  let constructorApp = foldl AppE (ConE conName) (map VarE fieldVars)
  [| do
      content <- $(varE handlerName) $(return constructorApp)
      pure $ Right content |]

buildNestedFieldValidation conName handlerName ((fieldName, _, fieldType):remainingFields) depth = do
  let fieldStr = nameBase fieldName
  let (isOptional, innerType) = case fieldType of
        AppT (ConT n) inner | nameBase n == "Maybe" -> (True, inner)
        other                                       -> (False, other)
  let fieldVar = mkName ("field" ++ show depth)

  continuation <- buildNestedFieldValidation conName handlerName remainingFields (depth + 1)

  -- Generate conversion expression based on type
  let convertExpr rawVar = case innerType of
        ConT n | nameBase n == "Int" ->
          [| case readMaybe (T.unpack $(varE rawVar)) of
               Just parsed -> parsed
               Nothing -> error $ "Failed to parse Int from: " <> T.unpack $(varE rawVar) |]
        ConT n | nameBase n == "Integer" ->
          [| case readMaybe (T.unpack $(varE rawVar)) of
               Just parsed -> parsed
               Nothing -> error $ "Failed to parse Integer from: " <> T.unpack $(varE rawVar) |]
        ConT n | nameBase n == "Double" ->
          [| case readMaybe (T.unpack $(varE rawVar)) of
               Just parsed -> parsed
               Nothing -> error $ "Failed to parse Double from: " <> T.unpack $(varE rawVar) |]
        ConT n | nameBase n == "Float" ->
          [| case readMaybe (T.unpack $(varE rawVar)) of
               Just parsed -> parsed
               Nothing -> error $ "Failed to parse Float from: " <> T.unpack $(varE rawVar) |]
        ConT n | nameBase n == "Bool" ->
          [| case T.toLower $(varE rawVar) of
               "true" -> True
               "false" -> False
               _ -> error $ "Failed to parse Bool from: " <> T.unpack $(varE rawVar) |]
        _ -> varE rawVar  -- Text or other types, use as-is

  if isOptional
    then do
      rawFieldVar <- newName ("raw" ++ show depth)
      convertedExpr <- convertExpr rawFieldVar
      [| do
          let $(varP fieldVar) = case Map.lookup $(litE $ stringL fieldStr) (Map.fromList args) of
                Nothing                  -> Nothing
                Just $(varP rawFieldVar) -> Just $(return convertedExpr)
          $(return continuation) |]
    else do
      rawFieldVar <- newName ("raw" ++ show depth)
      convertedExpr <- convertExpr rawFieldVar
      [| case Map.lookup $(litE $ stringL fieldStr) (Map.fromList args) of
          Just $(varP rawFieldVar) -> do
            let $(varP fieldVar) = $(return convertedExpr)
            $(return continuation)
          Nothing -> pure $ Left $ MissingRequiredParams $ "field '" <> $(litE $ stringL fieldStr) <> "' is missing" |]

-- | Derive resource handlers from a data type with custom descriptions
-- Usage: $(deriveResourceHandlerWithDescription ''MyResource 'handleResource [("Constructor", "Description")])
deriveResourceHandlerWithDescription :: Name -> Name -> [(String, String)] -> Q Exp
deriveResourceHandlerWithDescription typeName handlerName descriptions = do
  info <- reify typeName
  case info of
    TyConI (DataD _ _ _ _ constructors _) -> do
      -- Generate resource definitions
      resourceDefs <- sequence $ map (mkResourceDefWithDescription descriptions) constructors

      listHandlerExp <- [| pure $(return $ ListE resourceDefs) |]

      -- Generate read handler with cases
      cases <- sequence $ map (mkResourceCase handlerName) constructors
      defaultCase <- [| pure $ Left $ ResourceNotFound $ "Resource not found: " <> T.pack unknown |]
      let defaultMatch = Match (VarP (mkName "unknown")) (NormalB defaultCase) []

      readHandlerExp <- return $ LamE [VarP (mkName "uri")] $
        CaseE (AppE (VarE 'show) (VarE (mkName "uri")))
          (map clauseToMatch cases ++ [defaultMatch])

      return $ TupE [Just listHandlerExp, Just readHandlerExp]
    _ -> fail $ "deriveResourceHandlerWithDescription: " ++ show typeName ++ " is not a data type"

-- | Derive resource handlers from a data type
-- Usage: $(deriveResourceHandler ''MyResource 'handleResource)
deriveResourceHandler :: Name -> Name -> Q Exp
deriveResourceHandler typeName handlerName = 
  deriveResourceHandlerWithDescription typeName handlerName []

mkResourceDefWithDescription :: [(String, String)] -> Con -> Q Exp
mkResourceDefWithDescription descriptions (NormalC name []) = do
  let resourceName = T.pack . toSnakeCase . nameBase $ name
  let resourceURI = "resource://" <> T.unpack resourceName
  let constructorName = nameBase name
  let description = case lookup constructorName descriptions of
        Just desc -> Just desc
        Nothing   -> Just constructorName
  [| ResourceDefinition
      { resourceDefinitionURI = $(litE $ stringL resourceURI)
      , resourceDefinitionName = $(litE $ stringL $ T.unpack resourceName)
      , resourceDefinitionDescription = $(case description of
          Just desc -> [| Just $(litE $ stringL desc) |]
          Nothing   -> [| Nothing |])
      , resourceDefinitionMimeType = Just "text/plain"
      } |]
mkResourceDefWithDescription _ _ = fail "Unsupported constructor type for resources"


mkResourceCase :: Name -> Con -> Q Clause
mkResourceCase handlerName (NormalC name []) = do
  let resourceName = T.pack . toSnakeCase . nameBase $ name
  let resourceURI = "resource://" <> T.unpack resourceName
  clause [litP $ stringL resourceURI]
    (normalB [| $(varE handlerName) $(varE (mkName "uri")) $(conE name) >>= pure . Right |])
    []
mkResourceCase _ _ = fail "Unsupported constructor type for resources"

-- | Derive tool handlers from a data type with custom descriptions
-- Usage: $(deriveToolHandlerWithDescription ''MyTool 'handleTool [("Constructor", "Description")])
deriveToolHandlerWithDescription :: Name -> Name -> [(String, String)] -> Q Exp
deriveToolHandlerWithDescription typeName handlerName descriptions = do
  info <- reify typeName
  case info of
    TyConI (DataD _ _ _ _ constructors _) -> do
      -- Generate tool definitions
      toolDefs <- sequence $ map (mkToolDefWithDescription descriptions) constructors

      listHandlerExp <- [| pure $(return $ ListE toolDefs) |]

      -- Generate call handler with cases
      cases <- sequence $ map (mkToolCase handlerName) constructors
      defaultCase <- [| pure $ Left $ UnknownTool $ "Unknown tool: " <> name |]
      let defaultMatch = Match WildP (NormalB defaultCase) []

      callHandlerExp <- return $ LamE [VarP (mkName "name"), VarP (mkName "args")] $
        CaseE (AppE (VarE 'T.unpack) (VarE (mkName "name")))
          (map clauseToMatch cases ++ [defaultMatch])

      return $ TupE [Just listHandlerExp, Just callHandlerExp]
    _ -> fail $ "deriveToolHandlerWithDescription: " ++ show typeName ++ " is not a data type"

-- | Derive tool handlers from a data type
-- Usage: $(deriveToolHandler ''MyTool 'handleTool)
deriveToolHandler :: Name -> Name -> Q Exp
deriveToolHandler typeName handlerName = 
  deriveToolHandlerWithDescription typeName handlerName []

mkToolDefWithDescription :: [(String, String)] -> Con -> Q Exp
mkToolDefWithDescription descriptions con = 
  case con of
    NormalC name [] -> do
      let toolName = T.pack . toSnakeCase . nameBase $ name
      let constructorName = nameBase name
      let description = case lookup constructorName descriptions of
            Just desc -> desc
            Nothing   -> constructorName
      [| ToolDefinition
          { toolDefinitionName = $(litE $ stringL $ T.unpack toolName)
          , toolDefinitionDescription = $(litE $ stringL description)
          , toolDefinitionInputSchema = InputSchemaDefinitionObject
              { properties = []
              , required = []
              }
          } |]
    RecC name fields -> do
      let toolName = T.pack . toSnakeCase . nameBase $ name
      let constructorName = nameBase name
      let description = case lookup constructorName descriptions of
            Just desc -> desc
            Nothing   -> constructorName
      props <- sequence $ map (mkProperty descriptions) fields
      requiredFields <- return $ map (\(fieldName, _, fieldType) ->
        let isOptional = case fieldType of
              AppT (ConT n) _ -> nameBase n == "Maybe"
              _               -> False
        in if isOptional then Nothing else Just (nameBase fieldName)
        ) fields
      let required = [f | Just f <- requiredFields]
      [| ToolDefinition
          { toolDefinitionName = $(litE $ stringL $ T.unpack toolName)
          , toolDefinitionDescription = $(litE $ stringL description)
          , toolDefinitionInputSchema = InputSchemaDefinitionObject
              { properties = $(return $ ListE props)
              , required = $(return $ ListE $ map (LitE . StringL) required)
              }
          } |]
    NormalC name [(_bang, paramType)] -> do
      -- Handle separate parameter types approach for tools
      let toolName = T.pack . toSnakeCase . nameBase $ name
      let constructorName = nameBase name
      let description = case lookup constructorName descriptions of
            Just desc -> desc
            Nothing   -> constructorName
      fields <- extractFieldsFromParamType paramType
      props <- sequence $ map (mkProperty descriptions) fields
      requiredFields <- return $ map (\(fieldName, _, fieldType) ->
        let isOptional = case fieldType of
              AppT (ConT n) _ -> nameBase n == "Maybe"
              _               -> False
        in if isOptional then Nothing else Just (nameBase fieldName)
        ) fields
      let required = [f | Just f <- requiredFields]
      [| ToolDefinition
          { toolDefinitionName = $(litE $ stringL $ T.unpack toolName)
          , toolDefinitionDescription = $(litE $ stringL description)
          , toolDefinitionInputSchema = InputSchemaDefinitionObject
              { properties = $(return $ ListE props)
              , required = $(return $ ListE $ map (LitE . StringL) required)
              }
          } |]
    _ -> fail "Unsupported constructor type for tools"


mkProperty :: [(String, String)] -> (Name, Bang, Type) -> Q Exp
mkProperty descriptions (fieldName, _, fieldType) = do
  let fieldStr = nameBase fieldName
  let description = case lookup fieldStr descriptions of
        Just desc -> desc
        Nothing   -> fieldStr
  let jsonType = case fieldType of
        ConT n | nameBase n == "Int" -> "integer"
        ConT n | nameBase n == "Integer" -> "integer"
        ConT n | nameBase n == "Double" -> "number"
        ConT n | nameBase n == "Float" -> "number"
        ConT n | nameBase n == "Bool" -> "boolean"
        AppT (ConT maybeType) innerType | nameBase maybeType == "Maybe" ->
          case innerType of
            ConT n | nameBase n == "Int"     -> "integer"
            ConT n | nameBase n == "Integer" -> "integer"
            ConT n | nameBase n == "Double"  -> "number"
            ConT n | nameBase n == "Float"   -> "number"
            ConT n | nameBase n == "Bool"    -> "boolean"
            _                                -> "string"
        _ -> "string"
  [| ($(litE $ stringL fieldStr), InputSchemaDefinitionProperty
      { propertyType = $(litE $ stringL jsonType)
      , propertyDescription = $(litE $ stringL description)
      }) |]

mkToolCase :: Name -> Con -> Q Clause
mkToolCase handlerName (NormalC name []) = do
  let toolName = T.pack . toSnakeCase . nameBase $ name
  clause [litP $ stringL $ T.unpack toolName]
    (normalB [| do
        content <- $(varE handlerName) $(conE name)
        pure $ Right content |])
    []
mkToolCase handlerName (RecC name fields) = do
  let toolName = T.pack . toSnakeCase . nameBase $ name
  body <- mkRecordCase name handlerName fields
  clause [litP $ stringL $ T.unpack toolName] (normalB (return body)) []
mkToolCase handlerName (NormalC name [(_bang, paramType)]) = do
  let toolName = T.pack . toSnakeCase . nameBase $ name
  body <- mkSeparateParamsCase name handlerName paramType
  clause [litP $ stringL $ T.unpack toolName] (normalB (return body)) []
mkToolCase _ _ = fail "Unsupported constructor type for tools"