mcp-server-0.1.0.19: test/Spec/ToolCallParsing.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
module Spec.ToolCallParsing (spec) where
import Data.Text (Text)
import qualified Data.Text as T
import MCP.Server
import MCP.Server.Derive
import Test.Hspec
import TestTypes
allTypesHandlers :: (ToolListHandler IO, ToolCallHandler IO)
allTypesHandlers = $(deriveToolHandler ''AllTypesTool 'handleAllTypesTool)
callTool :: Text -> [(Text, Text)] -> IO (Either Error Content)
callTool = snd allTypesHandlers
shouldBeRight :: IO (Either Error Content) -> Text -> IO ()
shouldBeRight action expected = do
result <- action
case result of
Right (ContentText content) -> content `shouldBe` expected
other -> expectationFailure $ "Expected ContentText '" ++ T.unpack expected ++ "' but got: " ++ show other
shouldBeInvalidParams :: IO (Either Error Content) -> Text -> IO ()
shouldBeInvalidParams action expectedSubstring = do
result <- action
case result of
Left (InvalidParams msg) ->
T.isInfixOf expectedSubstring msg `shouldBe` True
other -> expectationFailure $ "Expected InvalidParams containing '" ++ T.unpack expectedSubstring ++ "' but got: " ++ show other
shouldBeMissingParams :: IO (Either Error Content) -> Text -> IO ()
shouldBeMissingParams action expectedSubstring = do
result <- action
case result of
Left (MissingRequiredParams msg) ->
T.isInfixOf expectedSubstring msg `shouldBe` True
other -> expectationFailure $ "Expected MissingRequiredParams containing '" ++ T.unpack expectedSubstring ++ "' but got: " ++ show other
spec :: Spec
spec = describe "Tool call parsing" $ do
describe "Required fields — successful parse" $ do
it "parses all required field types" $
shouldBeRight
(callTool "required_fields"
[ ("rfText", "hello")
, ("rfInt", "42")
, ("rfInteger", "100")
, ("rfDouble", "3.14")
, ("rfFloat", "2.5")
, ("rfBool", "true")
])
"text=hello, int=42, integer=100, double=3.14, float=2.5, bool=True"
describe "Optional fields — present, successful parse" $ do
it "parses all optional field types when present" $
shouldBeRight
(callTool "optional_fields"
[ ("ofText", "world")
, ("ofInt", "7")
, ("ofInteger", "999")
, ("ofDouble", "1.5")
, ("ofFloat", "0.5")
, ("ofBool", "false")
])
"text=world, int=7, integer=999, double=1.5, float=0.5, bool=False"
describe "Optional fields — missing" $ do
it "handles all optional fields omitted" $
shouldBeRight
(callTool "optional_fields" [])
"text=Nothing, int=Nothing, integer=Nothing, double=Nothing, float=Nothing, bool=Nothing"
describe "Required fields — parse failure" $ do
it "fails to parse Int from non-numeric string" $
shouldBeInvalidParams
(callTool "required_fields"
[ ("rfText", "hello"), ("rfInt", "not_a_number"), ("rfInteger", "1")
, ("rfDouble", "1.0"), ("rfFloat", "1.0"), ("rfBool", "true")
])
"field 'rfInt': Failed to parse Int from: not_a_number"
it "fails to parse Integer from non-numeric string" $
shouldBeInvalidParams
(callTool "required_fields"
[ ("rfText", "hello"), ("rfInt", "1"), ("rfInteger", "nope")
, ("rfDouble", "1.0"), ("rfFloat", "1.0"), ("rfBool", "true")
])
"field 'rfInteger': Failed to parse Integer from: nope"
it "fails to parse Double from non-numeric string" $
shouldBeInvalidParams
(callTool "required_fields"
[ ("rfText", "hello"), ("rfInt", "1"), ("rfInteger", "1")
, ("rfDouble", "abc"), ("rfFloat", "1.0"), ("rfBool", "true")
])
"field 'rfDouble': Failed to parse Double from: abc"
it "fails to parse Float from non-numeric string" $
shouldBeInvalidParams
(callTool "required_fields"
[ ("rfText", "hello"), ("rfInt", "1"), ("rfInteger", "1")
, ("rfDouble", "1.0"), ("rfFloat", "xyz"), ("rfBool", "true")
])
"field 'rfFloat': Failed to parse Float from: xyz"
it "fails to parse Bool from invalid string" $
shouldBeInvalidParams
(callTool "required_fields"
[ ("rfText", "hello"), ("rfInt", "1"), ("rfInteger", "1")
, ("rfDouble", "1.0"), ("rfFloat", "1.0"), ("rfBool", "maybe")
])
"field 'rfBool': Failed to parse Bool from: maybe"
describe "Optional fields — parse failure" $ do
it "fails to parse optional Int from invalid value" $
shouldBeInvalidParams
(callTool "optional_fields" [("ofInt", "bad")])
"field 'ofInt': Failed to parse Int from: bad"
it "fails to parse optional Integer from invalid value" $
shouldBeInvalidParams
(callTool "optional_fields" [("ofInteger", "bad")])
"field 'ofInteger': Failed to parse Integer from: bad"
it "fails to parse optional Double from invalid value" $
shouldBeInvalidParams
(callTool "optional_fields" [("ofDouble", "bad")])
"field 'ofDouble': Failed to parse Double from: bad"
it "fails to parse optional Float from invalid value" $
shouldBeInvalidParams
(callTool "optional_fields" [("ofFloat", "bad")])
"field 'ofFloat': Failed to parse Float from: bad"
it "fails to parse optional Bool from invalid value" $
shouldBeInvalidParams
(callTool "optional_fields" [("ofBool", "bad")])
"field 'ofBool': Failed to parse Bool from: bad"
describe "Missing required field" $ do
it "reports missing required field" $
shouldBeMissingParams
(callTool "required_fields" [("rfText", "hello")])
"is missing"