diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -706,15 +706,28 @@
 -- | /v1/messages - Anthropic endpoint
 anthropicMessagesHandler :: AppState -> Application
 anthropicMessagesHandler state req respond = do
+  -- Generate trace ID
+  traceId <- generateTraceId
+
   -- Read request body
   body <- strictRequestBody req
 
   case eitherDecode body of
-    Left err -> respond $ responseLBS status400
-      [("Content-Type", "application/json")]
-      (encode $ object ["error" .= ("Invalid Anthropic request: " <> T.pack err :: Text)])
+    Left err -> do
+      logEvent traceId "anthropic_parse_error" $ object
+        [ "error" .= T.pack err
+        , "body_preview" .= T.take 500 (TE.decodeUtf8 (BL.toStrict body))
+        ]
+      respond $ responseLBS status400
+        [("Content-Type", "application/json")]
+        (encode $ object ["error" .= ("Invalid Anthropic request: " <> T.pack err :: Text)])
 
     Right anthropicReq -> do
+      -- Log the full Anthropic request
+      logEvent traceId "anthropic_request" $ object
+        [ "request" .= anthropicReq
+        ]
+
       -- Get first backend (for now - TODO: support backend selection via model name)
       case Map.toList (configBackends $ appConfig state) of
         [] -> respond $ responseLBS status500
@@ -726,8 +739,8 @@
           let isStreaming = getAnthropicStreamFlag anthropicReq
 
           if isStreaming
-            then handleAnthropicStreaming state backendCfg anthropicReq respond
-            else handleAnthropicNonStreaming state backendCfg anthropicReq respond
+            then handleAnthropicStreaming traceId state backendCfg anthropicReq respond
+            else handleAnthropicNonStreaming traceId state backendCfg anthropicReq respond
 
 -- | Get Anthropic stream flag
 getAnthropicStreamFlag :: Value -> Bool
@@ -737,8 +750,8 @@
 getAnthropicStreamFlag _ = False
 
 -- | Handle Anthropic streaming request
-handleAnthropicStreaming :: AppState -> BackendConfig -> Value -> (Response -> IO ResponseReceived) -> IO ResponseReceived
-handleAnthropicStreaming state backendCfg anthropicReq respond = do
+handleAnthropicStreaming :: Text -> AppState -> BackendConfig -> Value -> (Response -> IO ResponseReceived) -> IO ResponseReceived
+handleAnthropicStreaming traceId state backendCfg anthropicReq respond = do
   -- Convert Anthropic request to OpenAI format
   case anthropicToOpenAI anthropicReq of
     Left err -> respond $ responseLBS status400
@@ -769,8 +782,8 @@
       respond sseResponse
 
 -- | Handle Anthropic non-streaming request
-handleAnthropicNonStreaming :: AppState -> BackendConfig -> Value -> (Response -> IO ResponseReceived) -> IO ResponseReceived
-handleAnthropicNonStreaming state backendCfg anthropicReq respond = do
+handleAnthropicNonStreaming :: Text -> AppState -> BackendConfig -> Value -> (Response -> IO ResponseReceived) -> IO ResponseReceived
+handleAnthropicNonStreaming traceId state backendCfg anthropicReq respond = do
   case anthropicToOpenAI anthropicReq of
     Left err -> respond $ responseLBS status400
       [("Content-Type", "application/json")]
diff --git a/louter.cabal b/louter.cabal
--- a/louter.cabal
+++ b/louter.cabal
@@ -1,6 +1,6 @@
 cabal-version: 3.0
 name: louter
-version: 0.1.0.0
+version: 0.1.1.0
 synopsis: Multi-protocol LLM router and client library
 description: Protocol converter library that lets your application connect to any LLM API
              (OpenAI, Gemini, Anthropic) with automatic protocol translation, SSE streaming,
diff --git a/src/Louter/Client.hs b/src/Louter/Client.hs
--- a/src/Louter/Client.hs
+++ b/src/Louter/Client.hs
@@ -267,21 +267,57 @@
     Just (Number n) -> Right (floor n)
     _ -> Right 0
 
-  message <- case HM.lookup "message" choice of
-    Just (Object msg) -> case HM.lookup "content" msg of
-      Just (String txt) -> Right txt
-      _ -> Right ""
-    _ -> Right ""
+  (message, toolCalls) <- case HM.lookup "message" choice of
+    Just (Object msg) -> do
+      let content = case HM.lookup "content" msg of
+            Just (String txt) -> txt
+            Just Null -> ""
+            _ -> ""
 
+      tools <- case HM.lookup "tool_calls" msg of
+        Just (Array arr) -> mapM parseToolCall (V.toList arr)
+        _ -> Right []
+
+      Right (content, tools)
+    _ -> Right ("", [])
+
   let finishReason = case HM.lookup "finish_reason" choice of
         Just (String "stop") -> Just FinishStop
         Just (String "length") -> Just FinishLength
         Just (String "tool_calls") -> Just FinishToolCalls
         _ -> Nothing
 
-  pure $ Choice index message finishReason
+  pure $ Choice index message toolCalls finishReason
 
 parseOpenAIChoice _ = Left "Expected choice object"
+
+-- | Parse a tool call from OpenAI format
+parseToolCall :: Value -> Either String ResponseToolCall
+parseToolCall (Object obj) = do
+  tcId <- case HM.lookup "id" obj of
+    Just (String i) -> Right i
+    _ -> Left "Missing tool call id"
+
+  tcType <- case HM.lookup "type" obj of
+    Just (String t) -> Right t
+    _ -> Right "function"
+
+  tcFunction <- case HM.lookup "function" obj of
+    Just (Object func) -> do
+      name <- case HM.lookup "name" func of
+        Just (String n) -> Right n
+        _ -> Left "Missing function name"
+
+      args <- case HM.lookup "arguments" func of
+        Just (String a) -> Right a
+        _ -> Right ""
+
+      Right $ FunctionCall name args
+    _ -> Left "Missing function object"
+
+  pure $ ResponseToolCall tcId tcType tcFunction
+
+parseToolCall _ = Left "Expected tool call object"
 
 -- | Parse Anthropic format response (uses converter)
 parseAnthropicResponse :: BL.ByteString -> Either String ChatResponse
diff --git a/src/Louter/Types/Response.hs b/src/Louter/Types/Response.hs
--- a/src/Louter/Types/Response.hs
+++ b/src/Louter/Types/Response.hs
@@ -8,9 +8,11 @@
   , Choice(..)
   , FinishReason(..)
   , Usage(..)
+  , ResponseToolCall(..)
+  , FunctionCall(..)
   ) where
 
-import Data.Aeson (FromJSON, ToJSON)
+import Data.Aeson (FromJSON, ToJSON, Value)
 import Data.Text (Text)
 import GHC.Generics (Generic)
 
@@ -29,11 +31,31 @@
 data Choice = Choice
   { choiceIndex :: !Int
   , choiceMessage :: !Text          -- ^ Response text (or empty if tool call)
+  , choiceToolCalls :: ![ResponseToolCall]  -- ^ Tool/function calls
   , choiceFinishReason :: !(Maybe FinishReason)
   } deriving (Show, Eq, Generic)
 
 instance FromJSON Choice
 instance ToJSON Choice
+
+-- | Tool/function call from the model (for non-streaming responses)
+data ResponseToolCall = ResponseToolCall
+  { rtcId :: !Text
+  , rtcType :: !Text          -- ^ Usually "function"
+  , rtcFunction :: !FunctionCall
+  } deriving (Show, Eq, Generic)
+
+instance FromJSON ResponseToolCall
+instance ToJSON ResponseToolCall
+
+-- | Function call details
+data FunctionCall = FunctionCall
+  { functionName :: !Text
+  , functionArguments :: !Text     -- ^ JSON string of arguments
+  } deriving (Show, Eq, Generic)
+
+instance FromJSON FunctionCall
+instance ToJSON FunctionCall
 
 -- | Why the model stopped generating
 data FinishReason
