packages feed

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

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

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

import qualified Data.Map            as Map
import qualified Data.Text           as T
import           Language.Haskell.TH

import           MCP.Server.Types

-- 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
-- Usage: $(derivePromptHandler ''MyPrompt 'handlePrompt)
derivePromptHandler :: Name -> Name -> Q Exp
derivePromptHandler typeName handlerName = do
  info <- reify typeName
  case info of
    TyConI (DataD _ _ _ _ constructors _) -> do
      -- Generate prompt definitions
      promptDefs <- sequence $ map mkPromptDef constructors
      
      -- Generate list handler
      listHandlerExp <- [| \_cursor -> pure $ PaginatedResult
        { paginatedItems = $(return $ ListE promptDefs)
        , paginatedNextCursor = Nothing
        } |]
      
      -- 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 $ "derivePromptHandler: " ++ show typeName ++ " is not a data type"

mkPromptDef :: Con -> Q Exp
mkPromptDef (NormalC name []) = do
  let promptName = T.toLower . T.pack . nameBase $ name
  [| PromptDefinition
      { promptDefinitionName = $(litE $ stringL $ T.unpack promptName)
      , promptDefinitionDescription = $(litE $ stringL $ "Handle " ++ nameBase name)
      , promptDefinitionArguments = []
      } |]
mkPromptDef (RecC name fields) = do
  let promptName = T.toLower . T.pack . nameBase $ name
  args <- sequence $ map mkArgDef fields
  [| PromptDefinition
      { promptDefinitionName = $(litE $ stringL $ T.unpack promptName)
      , promptDefinitionDescription = $(litE $ stringL $ "Handle " ++ nameBase name)
      , promptDefinitionArguments = $(return $ ListE args)
      } |]
mkPromptDef _ = fail "Unsupported constructor type"

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

mkPromptCase :: Name -> Con -> Q Clause
mkPromptCase handlerName (NormalC name []) = do
  let promptName = T.toLower . T.pack . 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.toLower . T.pack . nameBase $ name
  body <- mkRecordCase name handlerName fields
  clause [litP $ stringL $ T.unpack promptName] (normalB (return body)) []
mkPromptCase _ _ = fail "Unsupported constructor type"

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 = case fieldType of
        AppT (ConT n) _ -> nameBase n == "Maybe"
        _ -> False
  let fieldVar = mkName ("field" ++ show depth)
  
  continuation <- buildNestedFieldValidation conName handlerName remainingFields (depth + 1)
  
  if isOptional
    then [| do
        let $(varP fieldVar) = Map.lookup $(litE $ stringL fieldStr) (Map.fromList args)
        $(return continuation) |]
    else [| case Map.lookup $(litE $ stringL fieldStr) (Map.fromList args) of
        Just $(varP fieldVar) -> $(return continuation)
        Nothing -> pure $ Left $ MissingRequiredParams $ "field '" <> $(litE $ stringL fieldStr) <> "' is missing" |]

-- | Derive resource handlers from a data type
-- Usage: $(deriveResourceHandler ''MyResource 'handleResource)
deriveResourceHandler :: Name -> Name -> Q Exp
deriveResourceHandler typeName handlerName = do
  info <- reify typeName
  case info of
    TyConI (DataD _ _ _ _ constructors _) -> do
      -- Generate resource definitions
      resourceDefs <- sequence $ map mkResourceDef constructors
      
      listHandlerExp <- [| \_cursor -> pure $ PaginatedResult
        { paginatedItems = $(return $ ListE resourceDefs)
        , paginatedNextCursor = Nothing
        } |]

      -- 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 $ "deriveResourceHandler: " ++ show typeName ++ " is not a data type"

mkResourceDef :: Con -> Q Exp
mkResourceDef (NormalC name []) = do
  let resourceName = T.toLower . T.pack . nameBase $ name
  let resourceURI = "resource://" <> T.unpack resourceName
  [| ResourceDefinition
      { resourceDefinitionURI = $(litE $ stringL resourceURI)
      , resourceDefinitionName = $(litE $ stringL $ T.unpack resourceName)
      , resourceDefinitionDescription = Just $(litE $ stringL $ nameBase name)
      , resourceDefinitionMimeType = Just "text/plain"
      } |]
mkResourceDef _ = fail "Unsupported constructor type for resources"

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

-- | Derive tool handlers from a data type
-- Usage: $(deriveToolHandler ''MyTool 'handleTool)
deriveToolHandler :: Name -> Name -> Q Exp
deriveToolHandler typeName handlerName = do
  info <- reify typeName
  case info of
    TyConI (DataD _ _ _ _ constructors _) -> do
      -- Generate tool definitions
      toolDefs <- sequence $ map mkToolDef constructors
      
      listHandlerExp <- [| \_cursor -> pure $ PaginatedResult
        { paginatedItems = $(return $ ListE toolDefs)
        , paginatedNextCursor = Nothing
        } |]

      -- 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 $ "deriveToolHandler: " ++ show typeName ++ " is not a data type"

mkToolDef :: Con -> Q Exp
mkToolDef (NormalC name []) = do
  let toolName = T.toLower . T.pack . nameBase $ name
  [| ToolDefinition
      { toolDefinitionName = $(litE $ stringL $ T.unpack toolName)
      , toolDefinitionDescription = $(litE $ stringL $ nameBase name)
      , toolDefinitionInputSchema = InputSchemaDefinitionObject
          { properties = []
          , required = []
          }
      } |]
mkToolDef (RecC name fields) = do
  let toolName = T.toLower . T.pack . nameBase $ name
  props <- sequence $ map mkProperty 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 $ nameBase name)
      , toolDefinitionInputSchema = InputSchemaDefinitionObject
          { properties = $(return $ ListE props)
          , required = $(return $ ListE $ map (LitE . StringL) required)
          }
      } |]
mkToolDef _ = fail "Unsupported constructor type for tools"

mkProperty :: (Name, Bang, Type) -> Q Exp
mkProperty (fieldName, _, _) = do
  let fieldStr = nameBase fieldName
  [| ($(litE $ stringL fieldStr), InputSchemaDefinitionProperty
      { propertyType = "string"
      , propertyDescription = $(litE $ stringL fieldStr)
      }) |]

mkToolCase :: Name -> Con -> Q Clause
mkToolCase handlerName (NormalC name []) = do
  let toolName = T.toLower . T.pack . 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.toLower . T.pack . nameBase $ name
  body <- mkRecordCase name handlerName fields
  clause [litP $ stringL $ T.unpack toolName] (normalB (return body)) []
mkToolCase _ _ = fail "Unsupported constructor type for tools"