packages feed

mcp-server 0.1.0.11 → 0.1.0.12

raw patch · 12 files changed

+305/−96 lines, 12 filesdep ~bytestringPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependency ranges changed: bytestring

API changes (from Hackage documentation)

- MCP.Server: runMcpServerStdIn :: McpServerInfo -> McpServerHandlers IO -> IO ()
- MCP.Server: runMcpServerWithTransport :: (MonadIO m, McpTransport t) => t -> McpServerInfo -> McpServerHandlers m -> m TransportResult
- MCP.Server.Transport.Http: HttpTransport :: HttpConfig -> HttpTransport
- MCP.Server.Transport.Http: data HttpTransport
- MCP.Server.Transport.Http: instance GHC.Classes.Eq MCP.Server.Transport.Http.HttpTransport
- MCP.Server.Transport.Http: instance GHC.Internal.Show.Show MCP.Server.Transport.Http.HttpTransport
- MCP.Server.Transport.Http: runMcpServerHttp :: McpServerInfo -> McpServerHandlers IO -> IO ()
- MCP.Server.Transport.Http: runMcpServerHttpWithConfig :: HttpConfig -> McpServerInfo -> McpServerHandlers IO -> IO ()
- MCP.Server.Transport.Stdio: StdioTransport :: StdioTransport
- MCP.Server.Transport.Stdio: data StdioTransport
- MCP.Server.Transport.Stdio: instance GHC.Classes.Eq MCP.Server.Transport.Stdio.StdioTransport
- MCP.Server.Transport.Stdio: instance GHC.Internal.Show.Show MCP.Server.Transport.Stdio.StdioTransport
- MCP.Server.Transport.Stdio: instance MCP.Server.Transport.Types.McpTransport MCP.Server.Transport.Stdio.StdioTransport
- MCP.Server.Transport.Stdio: runMcpServerStdio :: MonadIO m => McpServerInfo -> McpServerHandlers m -> m ()
- MCP.Server.Transport.Types: StdioConfig :: TransportConfig
- MCP.Server.Transport.Types: TransportResult :: Bool -> Maybe String -> TransportResult
- MCP.Server.Transport.Types: [transportError] :: TransportResult -> Maybe String
- MCP.Server.Transport.Types: [transportSuccess] :: TransportResult -> Bool
- MCP.Server.Transport.Types: class McpTransport t
- MCP.Server.Transport.Types: data TransportConfig
- MCP.Server.Transport.Types: data TransportResult
- MCP.Server.Transport.Types: instance GHC.Classes.Eq MCP.Server.Transport.Types.TransportConfig
- MCP.Server.Transport.Types: instance GHC.Classes.Eq MCP.Server.Transport.Types.TransportResult
- MCP.Server.Transport.Types: instance GHC.Internal.Show.Show MCP.Server.Transport.Types.TransportConfig
- MCP.Server.Transport.Types: instance GHC.Internal.Show.Show MCP.Server.Transport.Types.TransportResult
- MCP.Server.Transport.Types: runTransport :: (McpTransport t, MonadIO m) => t -> McpServerInfo -> McpServerHandlers m -> m TransportResult
+ MCP.Server: HttpConfig :: Int -> String -> String -> Bool -> HttpConfig
+ MCP.Server: [httpEndpoint] :: HttpConfig -> String
+ MCP.Server: [httpHost] :: HttpConfig -> String
+ MCP.Server: [httpPort] :: HttpConfig -> Int
+ MCP.Server: [httpVerbose] :: HttpConfig -> Bool
+ MCP.Server: data HttpConfig
+ MCP.Server: runMcpServerHttp :: McpServerInfo -> McpServerHandlers IO -> IO ()
+ MCP.Server: runMcpServerHttpWithConfig :: HttpConfig -> McpServerInfo -> McpServerHandlers IO -> IO ()
+ MCP.Server: runMcpServerStdio :: McpServerInfo -> McpServerHandlers IO -> IO ()
+ MCP.Server.Transport.Http: defaultHttpConfig :: HttpConfig
+ MCP.Server.Transport.Http: transportRunHttp :: HttpConfig -> McpServerInfo -> McpServerHandlers IO -> IO ()
+ MCP.Server.Transport.Stdio: transportRunStdio :: MonadIO m => McpServerInfo -> McpServerHandlers m -> m ()

Files

README.md view
@@ -56,7 +56,7 @@  -- Derive handlers automatically main :: IO ()-main = runMcpServerStdIn serverInfo handlers+main = runMcpServerStdio serverInfo handlers   where     serverInfo = McpServerInfo       { serverName = "My MCP Server"@@ -132,7 +132,7 @@ -- ... implement your custom logic  main :: IO ()-main = runMcpServerStdIn serverInfo handlers+main = runMcpServerStdio serverInfo handlers   where     handlers = McpServerHandlers       { prompts = Just (promptListHandler, promptGetHandler)
app/Main.hs view
@@ -2,5 +2,12 @@  module Main where +import System.IO (hSetEncoding, stderr, stdout, utf8)+ main :: IO ()-main = putStrLn "Hello, World!"+main = do+    -- Set UTF-8 encoding to handle Unicode characters properly+    hSetEncoding stdout utf8+    hSetEncoding stderr utf8+    +    putStrLn "Hello, World!"
examples/Complete/Main.hs view
@@ -5,6 +5,7 @@  import           MCP.Server import           MCP.Server.Derive+import           System.IO (hSetEncoding, stderr, stdout, utf8) import           Types  -- High-level handler functions@@ -37,11 +38,14 @@  main :: IO () main = do+    -- Set UTF-8 encoding to handle Unicode characters properly+    hSetEncoding stdout utf8+    hSetEncoding stderr utf8     -- Derive the handlers using Template Haskell     let prompts = $(derivePromptHandler ''MyPrompt 'handlePrompt)         resources = $(deriveResourceHandler ''MyResource 'handleResource)         tools = $(deriveToolHandler ''MyTool 'handleTool)-     in runMcpServerStdIn+     in runMcpServerStdio         McpServerInfo             { serverName = "Complete Example MCP Server"             , serverVersion = "0.3.0"
examples/HttpSimple/Main.hs view
@@ -6,11 +6,15 @@ import           Data.IORef import           MCP.Server import           MCP.Server.Derive-import           System.IO         (hPutStrLn, stderr)+import           System.IO         (hPutStrLn, hSetEncoding, stderr, stdout, utf8) import           Types  main :: IO () main = do+    -- Set UTF-8 encoding to handle Unicode characters properly+    hSetEncoding stdout utf8+    hSetEncoding stderr utf8+         hPutStrLn stderr "Starting HTTP Simple MCP Server..."      -- Create a simple in-memory store
examples/Simple/Main.hs view
@@ -6,11 +6,15 @@ import           Data.IORef import           MCP.Server import           MCP.Server.Derive-import           System.IO         (hPutStrLn, stderr)+import           System.IO         (hPutStrLn, hSetEncoding, stderr, stdout, utf8) import           Types  main :: IO () main = do+    -- Set UTF-8 encoding to handle Unicode characters properly+    hSetEncoding stdout utf8+    hSetEncoding stderr utf8+         hPutStrLn stderr "Starting Simple MCP Server..."      -- Create a simple in-memory store@@ -30,7 +34,7 @@      -- Derive the tool handlers using Template Haskell with descriptions     let tools = $(deriveToolHandlerWithDescription ''SimpleTool 'handleTool simpleDescriptions)-     in runMcpServerStdIn+     in runMcpServerStdio         McpServerInfo             { serverName = "Simple Key-Value MCP Server"             , serverVersion = "1.0.0"
mcp-server.cabal view
@@ -15,7 +15,7 @@ -- PVP summary:     +-+------- breaking API changes --                  | | +----- non-breaking API additions --                  | | | +--- code changes with no API change-version: 0.1.0.11+version: 0.1.0.12 -- A short (one-line) description of the package. synopsis: Library for building Model Context Protocol (MCP) servers -- A longer description of the package.@@ -67,7 +67,6 @@     MCP.Server.JsonRpc     MCP.Server.Protocol     MCP.Server.Types-    MCP.Server.Transport.Types     MCP.Server.Transport.Stdio     MCP.Server.Transport.Http @@ -187,6 +186,7 @@     Spec.BasicDerivation     Spec.JSONConversion     Spec.SchemaValidation+    Spec.UnicodeHandling     TestData     TestTypes @@ -203,6 +203,7 @@     QuickCheck,     aeson,     base,+    bytestring,     containers,     hspec,     mcp-server,
src/MCP/Server.hs view
@@ -3,13 +3,12 @@  module MCP.Server   ( -- * Server Runtime-    runMcpServerStdIn-  , runMcpServerWithTransport+    runMcpServerStdio+  , runMcpServerHttp+  , runMcpServerHttpWithConfig -    -- * Transport Support-  , module MCP.Server.Transport.Types-  , module MCP.Server.Transport.Stdio-  , module MCP.Server.Transport.Http+    -- * Transport Configuration+  , HttpConfig(..)      -- * Utility Functions   , jsonValueToText@@ -23,9 +22,8 @@ import           Data.Text              (Text) import qualified Data.Text              as T -import           MCP.Server.Transport.Types-import           MCP.Server.Transport.Stdio-import           MCP.Server.Transport.Http+import           MCP.Server.Transport.Stdio (transportRunStdio)+import           MCP.Server.Transport.Http (HttpConfig(..), transportRunHttp, defaultHttpConfig) import           MCP.Server.Types  -- | Convert JSON Value to Text representation suitable for handlers@@ -41,19 +39,15 @@ jsonValueToText Null = "" jsonValueToText v = T.pack $ show v --- | Run an MCP server with a specific transport-runMcpServerWithTransport :: (MonadIO m, McpTransport t) -                         => t                      -- ^ Transport configuration  -                         -> McpServerInfo          -- ^ Server information-                         -> McpServerHandlers m    -- ^ Message handlers-                         -> m TransportResult      -- ^ Result of transport operation-runMcpServerWithTransport transport serverInfo handlers = -  runTransport transport serverInfo handlers+-- | Run an MCP server using STDIO transport+runMcpServerStdio :: McpServerInfo -> McpServerHandlers IO -> IO ()+runMcpServerStdio serverInfo handlers = transportRunStdio serverInfo handlers +-- | Run an MCP server using HTTP transport with default configuration+runMcpServerHttp :: McpServerInfo -> McpServerHandlers IO -> IO ()+runMcpServerHttp serverInfo handlers = transportRunHttp defaultHttpConfig serverInfo handlers --- | Run an MCP server using stdin/stdout-runMcpServerStdIn :: McpServerInfo -> McpServerHandlers IO -> IO ()-runMcpServerStdIn serverInfo handlers = do-  _ <- runMcpServerWithTransport StdioTransport serverInfo handlers-  return ()+-- | Run an MCP server using HTTP transport with custom configuration+runMcpServerHttpWithConfig :: HttpConfig -> McpServerInfo -> McpServerHandlers IO -> IO ()+runMcpServerHttpWithConfig config serverInfo handlers = transportRunHttp config serverInfo handlers 
src/MCP/Server/Transport/Http.hs view
@@ -3,10 +3,9 @@  module MCP.Server.Transport.Http   ( -- * HTTP Transport-    HttpTransport(..)-  , HttpConfig(..)-  , runMcpServerHttp-  , runMcpServerHttpWithConfig+    HttpConfig(..)+  , transportRunHttp+  , defaultHttpConfig   ) where  import           Control.Monad            (when)@@ -43,24 +42,14 @@   , httpVerbose = False   } --- | HTTP transport following MCP 2025-03-26 Streamable HTTP specification-data HttpTransport = HttpTransport HttpConfig-  deriving (Show, Eq)---- Note: HTTP transport is constrained to IO only due to Warp requirements--- For the generic interface, we'll use a more specific function- -- | Helper for conditional logging logVerbose :: HttpConfig -> String -> IO () logVerbose config msg = when (httpVerbose config) $ hPutStrLn stderr msg --- | Run an MCP server using HTTP transport with default config-runMcpServerHttp :: McpServerInfo -> McpServerHandlers IO -> IO ()-runMcpServerHttp = runMcpServerHttpWithConfig defaultHttpConfig --- | Run an MCP server using HTTP transport with custom config-runMcpServerHttpWithConfig :: HttpConfig -> McpServerInfo -> McpServerHandlers IO -> IO ()-runMcpServerHttpWithConfig config serverInfo handlers = do+-- | Transport-specific implementation for HTTP+transportRunHttp :: HttpConfig -> McpServerInfo -> McpServerHandlers IO -> IO ()+transportRunHttp config serverInfo handlers = do   let settings = Warp.setHost (fromString $ httpHost config) $                  Warp.setPort (httpPort config) $                  Warp.defaultSettings
src/MCP/Server/Transport/Stdio.hs view
@@ -3,8 +3,7 @@  module MCP.Server.Transport.Stdio   ( -- * STDIO Transport-    StdioTransport(..)-  , runMcpServerStdio+    transportRunStdio   ) where  import           Control.Monad          (when)@@ -18,22 +17,12 @@  import           MCP.Server.Handlers import           MCP.Server.JsonRpc-import           MCP.Server.Transport.Types import           MCP.Server.Types --- | STDIO transport configuration-data StdioTransport = StdioTransport-  deriving (Show, Eq) -instance McpTransport StdioTransport where-  runTransport StdioTransport serverInfo handlers = do-    runMcpServerStdio serverInfo handlers-    return $ TransportResult True Nothing----- | Run an MCP server using STDIO transport-runMcpServerStdio :: (MonadIO m) => McpServerInfo -> McpServerHandlers m -> m ()-runMcpServerStdio serverInfo handlers = do+-- | Transport-specific implementation for STDIO+transportRunStdio :: (MonadIO m) => McpServerInfo -> McpServerHandlers m -> m ()+transportRunStdio serverInfo handlers = do   loop   where     loop = do@@ -56,4 +45,5 @@                     liftIO $ hFlush stdout                   Nothing -> liftIO $ hPutStrLn stderr $ "No response needed for: " ++ show (getMessageSummary message)         loop+ 
− src/MCP/Server/Transport/Types.hs
@@ -1,33 +0,0 @@-{-# LANGUAGE RankNTypes #-}--module MCP.Server.Transport.Types-  ( -- * Transport Class-    McpTransport(..)-  , TransportConfig(..)-  -    -- * Transport Result-  , TransportResult(..)-  ) where--import           Control.Monad.IO.Class (MonadIO)-import           MCP.Server.Types---- | Result of transport operations-data TransportResult = TransportResult-  { transportSuccess :: Bool-  , transportError   :: Maybe String-  } deriving (Show, Eq)---- | Configuration for different transport types-data TransportConfig-  = StdioConfig-  deriving (Show, Eq)---- | Transport abstraction class-class McpTransport t where-  -- | Run the MCP server with this transport-  runTransport :: (MonadIO m) -               => t                      -- ^ Transport configuration-               -> McpServerInfo          -- ^ Server information-               -> McpServerHandlers m    -- ^ Message handlers-               -> m TransportResult      -- ^ Result of transport operation
test/HspecMain.hs view
@@ -7,6 +7,7 @@ import qualified Spec.BasicDerivation import qualified Spec.SchemaValidation import qualified Spec.AdvancedDerivation+import qualified Spec.UnicodeHandling  main :: IO () main = hspec $ do@@ -15,3 +16,4 @@     Spec.BasicDerivation.spec     Spec.SchemaValidation.spec     Spec.AdvancedDerivation.spec+    Spec.UnicodeHandling.spec
+ test/Spec/UnicodeHandling.hs view
@@ -0,0 +1,247 @@+{-# LANGUAGE OverloadedStrings #-}++module Spec.UnicodeHandling (spec) where++import           Data.Aeson+import qualified Data.Aeson.KeyMap      as KM+import qualified Data.Text              as T+import qualified Data.Text.Encoding     as TE+import qualified Data.ByteString.Lazy   as BSL+import           MCP.Server.Protocol+import           MCP.Server.Types+import           System.IO              (hSetEncoding, stderr, stdout, utf8)+import           Test.Hspec++spec :: Spec  +spec = describe "Unicode Handling" $ do+  describe "Unicode in Content Types" $ do+    it "handles Unicode in ContentText" $ do+      -- Set UTF-8 encoding for this test+      hSetEncoding stdout utf8+      hSetEncoding stderr utf8+      let content = ContentText "Test with Unicode: √π∞≈∑"+      let json = toJSON content+      case json of+        Object obj -> case KM.lookup "text" obj of+          Just (String txt) -> "√π∞≈∑" `T.isInfixOf` txt `shouldBe` True+          _ -> expectationFailure "Expected String in text field"+        _ -> expectationFailure "Expected Object"++    it "handles Unicode in ResourceContent" $ do+      uri <- case parseURI "test://unicode" of+        Just u -> return u+        Nothing -> fail "Invalid URI"+      let resource = ResourceText uri "text/plain" "Unicode content: ∀x∈ℝ: √x²=|x|"+      let json = toJSON resource+      case json of+        Object obj -> case KM.lookup "text" obj of+          Just (String txt) -> "∀x∈ℝ: √x²=|x|" `T.isInfixOf` txt `shouldBe` True+          _ -> expectationFailure "Expected String in text field"+        _ -> expectationFailure "Expected Object"++    it "handles square root symbol specifically" $ do+      let content = ContentText "Square root: √16 = 4"+      let json = toJSON content+      case json of+        Object obj -> case KM.lookup "text" obj of+          Just (String txt) -> txt `shouldBe` "Square root: √16 = 4"+          _ -> expectationFailure "Expected String in text field"+        _ -> expectationFailure "Expected Object"++  describe "JSON Encoding/Decoding with Unicode" $ do+    it "properly encodes and decodes Unicode content" $ do+      let originalContent = ContentText "Mathematical symbols: ∀∃∈∉∪∩⊆⊇√∑∏∞≤≥≠≈π∅Ω∆∇"+      let encoded = encode originalContent+      let decoded = eitherDecode encoded+      case decoded of+        Right decodedContent -> decodedContent `shouldBe` originalContent+        Left err -> expectationFailure $ "Failed to decode: " ++ err++    it "handles Unicode in JSON-RPC messages" $ do+      let response = ToolsCallResponse +            { toolsCallContent = [ContentText "Result: √16 = 4, π ≈ 3.14159"]+            , toolsCallIsError = Nothing+            }+      let encoded = encode response+      -- Just verify the JSON can be encoded and contains Unicode+      BSL.length encoded `shouldSatisfy` (> 0)++  describe "UTF-8 Text Encoding" $ do+    it "properly handles UTF-8 byte sequences" $ do+      let unicodeText = "Unicode: √∞≈π"+      let utf8Bytes = TE.encodeUtf8 unicodeText+      let decodedText = TE.decodeUtf8 utf8Bytes+      decodedText `shouldBe` unicodeText++    it "handles multibyte Unicode characters" $ do+      let characters = ["√", "∞", "π", "∑", "∏", "∀", "∃", "∈", "∉"]+      mapM_ (\char -> do+        let encoded = TE.encodeUtf8 char+        let decoded = TE.decodeUtf8 encoded+        decoded `shouldBe` char+        ) characters++  describe "Protocol Messages with Unicode" $ do+    it "handles Unicode in tool responses" $ do+      let response = ToolsCallResponse +            { toolsCallContent = [ContentText "Mathematical result: √(π²+e²) ≈ 4.53"]+            , toolsCallIsError = Nothing+            }+      let json = toJSON response+      -- Verify the JSON can be encoded and contains Unicode+      let encoded = encode json+      BSL.length encoded `shouldSatisfy` (> 0)++    it "handles Unicode in prompt responses" $ do+      let message = PromptMessage RoleUser (ContentText "Formula: E=mc², √(x²+y²)")+      let response = PromptsGetResponse+            { promptsGetDescription = Just "Mathematical formulas with symbols: ∀∃"+            , promptsGetMessages = [message]+            }+      let json = toJSON response+      let encoded = encode json+      BSL.length encoded `shouldSatisfy` (> 0)++    it "handles Unicode in error messages" $ do+      let mcpError = InvalidParams "Invalid formula: expected √x not ∛x"+      let json = toJSON mcpError+      case json of+        Object obj -> case KM.lookup "message" obj of+          Just (String msg) -> "√" `T.isInfixOf` msg `shouldBe` True+          _ -> expectationFailure "Expected message field"+        _ -> expectationFailure "Expected Object"++  describe "Manual Handler Tests with Unicode" $ do+    it "tests Unicode content through manual prompt handler" $ do+      let promptHandler :: PromptName -> [(ArgumentName, ArgumentValue)] -> IO (Either Error Content)+          promptHandler "unicode_test" args = do+            case lookup "formula" args of+              Just formula -> return $ Right $ ContentText $ "Formula result: " <> formula <> " → √solution"+              Nothing -> return $ Left $ MissingRequiredParams "formula"+          promptHandler name _ = return $ Left $ InvalidPromptName name++      result <- promptHandler "unicode_test" [("formula", "x² + y² = z²")]+      case result of+        Right (ContentText txt) -> do+          txt `shouldSatisfy` T.isInfixOf "√"+          txt `shouldSatisfy` T.isInfixOf "²"+        _ -> expectationFailure "Expected successful ContentText with Unicode"++    it "tests Unicode content through manual tool handler" $ do+      let toolHandler :: ToolName -> [(ArgumentName, ArgumentValue)] -> IO (Either Error Content)+          toolHandler "calculate" args = do+            case lookup "expression" args of+              Just expr -> return $ Right $ ContentText $ "Calculation: " <> expr <> " = √result"+              Nothing -> return $ Left $ MissingRequiredParams "expression"+          toolHandler name _ = return $ Left $ UnknownTool name++      result <- toolHandler "calculate" [("expression", "∫₀^∞ e^(-x²) dx")]+      case result of+        Right (ContentText txt) -> do+          txt `shouldSatisfy` T.isInfixOf "√"+          txt `shouldSatisfy` T.isInfixOf "∫"+        _ -> expectationFailure "Expected successful ContentText with Unicode"++    it "tests Unicode content through manual resource handler" $ do+      let resourceHandler :: URI -> IO (Either Error ResourceContent)+          resourceHandler uri = do+            let uriStr = show uri+            if "unicode" `T.isInfixOf` T.pack uriStr+              then return $ Right $ ResourceText uri "text/plain" "Unicode symbols: ∀∃∈∉√∑∏∞≤≥≠≈π"+              else return $ Left $ ResourceNotFound $ T.pack uriStr++      uri <- case parseURI "resource://unicode_test" of+        Just u -> return u+        Nothing -> fail "Invalid URI"+      +      result <- resourceHandler uri+      case result of+        Right (ResourceText _ _ txt) -> do+          txt `shouldSatisfy` T.isInfixOf "∀∃∈∉"+          txt `shouldSatisfy` T.isInfixOf "√∑∏∞"+        _ -> expectationFailure "Expected successful ResourceText with Unicode"++  describe "Integration test simulating MCP workflow" $ do+    it "handles complete Unicode workflow without Template Haskell" $ do+      -- Create manual handlers with Unicode content+      let promptListHandler = return [+            PromptDefinition +              { promptDefinitionName = "math_formula"+              , promptDefinitionDescription = "Generate mathematical formulas with Unicode: ∀∃∈√"+              , promptDefinitionArguments = [ArgumentDefinition "formula" "Mathematical expression" True]+              }+            ]+      +      let promptGetHandler name args = case name of+            "math_formula" -> case lookup "formula" args of+              Just formula -> return $ Right $ ContentText $ "Formula: " <> formula <> " → √(solution)"+              Nothing -> return $ Left $ MissingRequiredParams "formula"+            _ -> return $ Left $ InvalidPromptName name++      let resourceListHandler = return [+            ResourceDefinition+              { resourceDefinitionURI = "resource://unicode_symbols"+              , resourceDefinitionName = "unicode_symbols"+              , resourceDefinitionDescription = Just "Unicode mathematical symbols: ∀∃∈∉√∑"+              , resourceDefinitionMimeType = Just "text/plain"+              }+            ]++      let resourceReadHandler uri = +            if show uri == "resource://unicode_symbols"+              then return $ Right $ ResourceText uri "text/plain" "Symbols: ∀∃∈∉∪∩⊆⊇√∑∏∞≤≥≠≈π∅"+              else return $ Left $ ResourceNotFound $ T.pack $ show uri++      let toolListHandler = return [+            ToolDefinition+              { toolDefinitionName = "calculate"+              , toolDefinitionDescription = "Calculate with Unicode symbols: √∑∏"+              , toolDefinitionInputSchema = InputSchemaDefinitionObject+                  { properties = [("expression", InputSchemaDefinitionProperty "string" "Mathematical expression")]+                  , required = ["expression"]+                  }+              }+            ]++      let toolCallHandler name args = case name of+            "calculate" -> case lookup "expression" args of+              Just expr -> return $ Right $ ContentText $ "Result: " <> expr <> " → √answer"+              Nothing -> return $ Left $ MissingRequiredParams "expression"+            _ -> return $ Left $ UnknownTool name++      let handlers = McpServerHandlers+            { prompts = Just (promptListHandler, promptGetHandler)+            , resources = Just (resourceListHandler, resourceReadHandler)+            , tools = Just (toolListHandler, toolCallHandler)+            }++      -- Test that all handlers work with Unicode+      promptList <- fst $ case prompts handlers of Just h -> h; Nothing -> error "No prompts"+      promptList `shouldSatisfy` (not . null)++      resourceList <- fst $ case resources handlers of Just h -> h; Nothing -> error "No resources"+      resourceList `shouldSatisfy` (not . null)++      toolList <- fst $ case tools handlers of Just h -> h; Nothing -> error "No tools"+      toolList `shouldSatisfy` (not . null)++      -- Test actual Unicode handling+      promptResult <- snd (case prompts handlers of Just h -> h; Nothing -> error "No prompts") "math_formula" [("formula", "√(x²+y²)")]+      case promptResult of+        Right (ContentText txt) -> txt `shouldSatisfy` T.isInfixOf "√"+        _ -> expectationFailure "Expected successful prompt result"++      uri <- case parseURI "resource://unicode_symbols" of+        Just u -> return u+        Nothing -> fail "Invalid URI"+      resourceResult <- snd (case resources handlers of Just h -> h; Nothing -> error "No resources") uri+      case resourceResult of+        Right (ResourceText _ _ txt) -> txt `shouldSatisfy` T.isInfixOf "∀∃∈∉"+        _ -> expectationFailure "Expected successful resource result"++      toolResult <- snd (case tools handlers of Just h -> h; Nothing -> error "No tools") "calculate" [("expression", "∫₀^∞ e^(-x²) dx = √π/2")]+      case toolResult of+        Right (ContentText txt) -> do+          txt `shouldSatisfy` T.isInfixOf "√"+          txt `shouldSatisfy` T.isInfixOf "∫"+        _ -> expectationFailure "Expected successful tool result"