mcp-server-0.1.0.0: 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
-- | 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
-- For now, return a basic working implementation
listHandlerExp <- [| \_cursor -> pure $ PaginatedResult
{ paginatedItems =
[ PromptDefinition
{ promptDefinitionName = "recipe"
, promptDefinitionDescription = "Generate a detailed recipe"
, promptDefinitionArguments =
[ ArgumentDefinition
{ argumentDefinitionName = "idea"
, argumentDefinitionDescription = "Recipe idea"
, argumentDefinitionRequired = True
}
]
}
, PromptDefinition
{ promptDefinitionName = "shopping"
, promptDefinitionDescription = "Create a shopping list"
, promptDefinitionArguments =
[ ArgumentDefinition
{ argumentDefinitionName = "description"
, argumentDefinitionDescription = "Shopping description"
, argumentDefinitionRequired = True
}
]
}
]
, paginatedNextCursor = Nothing
} |]
getHandlerExp <- [| \name args -> case T.unpack name of
"recipe" -> case Map.lookup "idea" (Map.fromList args) of
Nothing -> pure $ Left $ MissingRequiredParams "field 'idea' is missing"
Just idea -> do
content <- $(varE handlerName) (Recipe idea)
pure $ Right content
"shopping" -> case Map.lookup "description" (Map.fromList args) of
Nothing -> pure $ Left $ MissingRequiredParams "field 'description' is missing"
Just desc -> do
content <- $(varE handlerName) (Shopping desc)
pure $ Right content
_ -> pure $ Left $ InvalidPromptName $ "Unknown prompt: " <> name |]
return $ TupE [Just listHandlerExp, Just getHandlerExp]
_ -> fail $ "derivePromptHandler: " ++ 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 = do
info <- reify typeName
case info of
TyConI (DataD _ _ _ _ _constructors _) -> do
listHandlerExp <- [| \_cursor -> pure $ PaginatedResult
{ paginatedItems =
[ ResourceDefinition
{ resourceDefinitionURI = "resource://productcategories"
, resourceDefinitionName = "productcategories"
, resourceDefinitionDescription = Just "Product categories"
, resourceDefinitionMimeType = Just "text/plain"
}
, ResourceDefinition
{ resourceDefinitionURI = "resource://saleitems"
, resourceDefinitionName = "saleitems"
, resourceDefinitionDescription = Just "Sale items"
, resourceDefinitionMimeType = Just "text/plain"
}
, ResourceDefinition
{ resourceDefinitionURI = "resource://headlinebannerad"
, resourceDefinitionName = "headlinebannerad"
, resourceDefinitionDescription = Just "Headline banner ad"
, resourceDefinitionMimeType = Just "text/plain"
}
]
, paginatedNextCursor = Nothing
} |]
readHandlerExp <- [| \uri -> case show uri of
"resource://productcategories" -> Right <$> $(varE handlerName) ProductCategories
"resource://saleitems" -> Right <$> $(varE handlerName) SaleItems
"resource://headlinebannerad" -> Right <$> $(varE handlerName) HeadlineBannerAd
unknown -> pure $ Left $ ResourceNotFound $ "Resource not found: " <> T.pack unknown |]
return $ TupE [Just listHandlerExp, Just readHandlerExp]
_ -> fail $ "deriveResourceHandler: " ++ 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 = do
info <- reify typeName
case info of
TyConI (DataD _ _ _ _ _constructors _) -> do
listHandlerExp <- [| \_cursor -> pure $ PaginatedResult
{ paginatedItems =
[ ToolDefinition
{ toolDefinitionName = "searchforproduct"
, toolDefinitionDescription = "Search for products"
, toolDefinitionInputSchema = InputSchemaDefinitionObject
{ properties =
[ ("q", InputSchemaDefinitionProperty
{ propertyType = "string"
, propertyDescription = "Search query"
})
, ("category", InputSchemaDefinitionProperty
{ propertyType = "string"
, propertyDescription = "Category filter"
})
]
, required = ["q"]
}
}
, ToolDefinition
{ toolDefinitionName = "addtocart"
, toolDefinitionDescription = "Add item to cart"
, toolDefinitionInputSchema = InputSchemaDefinitionObject
{ properties =
[ ("sku", InputSchemaDefinitionProperty
{ propertyType = "string"
, propertyDescription = "Product SKU"
})
]
, required = ["sku"]
}
}
, ToolDefinition
{ toolDefinitionName = "checkout"
, toolDefinitionDescription = "Complete checkout"
, toolDefinitionInputSchema = InputSchemaDefinitionObject
{ properties = []
, required = []
}
}
]
, paginatedNextCursor = Nothing
} |]
callHandlerExp <- [| \name args -> case T.unpack name of
"searchforproduct" -> case Map.lookup "q" (Map.fromList args) of
Nothing -> pure $ Left $ MissingRequiredParams "field 'q' is missing"
Just q -> do
let category = Map.lookup "category" (Map.fromList args)
content <- $(varE handlerName) (SearchForProduct q category)
pure $ Right content
"addtocart" -> case Map.lookup "sku" (Map.fromList args) of
Nothing -> pure $ Left $ MissingRequiredParams "field 'sku' is missing"
Just sku -> do
content <- $(varE handlerName) (AddToCart sku)
pure $ Right content
"checkout" -> do
content <- $(varE handlerName) Checkout
pure $ Right content
_ -> pure $ Left $ UnknownTool $ "Unknown tool: " <> name |]
return $ TupE [Just listHandlerExp, Just callHandlerExp]
_ -> fail $ "deriveToolHandler: " ++ show typeName ++ " is not a data type"