packages feed

mcp-0.2.0.0: src/MCP/Protocol.hs

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}

{-# HLINT ignore "Use newtype instead of data" #-}

{- |
Module      : MCP.Protocol
Description : JSON-RPC protocol implementation for MCP
Copyright   : (C) 2025 Matthias Pall Gissurarson
License     : MIT
Maintainer  : mpg@mpg.is
Stability   : experimental
Portability : GHC

This module implements the JSON-RPC 2.0 protocol layer for MCP,
including request/response handling, message parsing and encoding,
and protocol-level error handling.
-}
module MCP.Protocol (
    -- * JSON-RPC Types
    JSONRPCRequest (..),
    JSONRPCResponse (..),
    JSONRPCError (..),
    JSONRPCNotification (..),
    JSONRPCMessage (..),
    JSONRPCErrorInfo (..),

    -- * Client Request Types
    InitializeRequest (..),
    InitializeParams (..),
    PingRequest (..),
    PingParams (..),
    ListResourcesRequest (..),
    ListResourcesParams (..),
    ListResourceTemplatesRequest (..),
    ListResourceTemplatesParams (..),
    ReadResourceRequest (..),
    ReadResourceParams (..),
    SubscribeRequest (..),
    SubscribeParams (..),
    UnsubscribeRequest (..),
    UnsubscribeParams (..),
    ListPromptsRequest (..),
    ListPromptsParams (..),
    GetPromptRequest (..),
    GetPromptParams (..),
    ListToolsRequest (..),
    ListToolsParams (..),
    CallToolRequest (..),
    CallToolParams (..),
    SetLevelRequest (..),
    SetLevelParams (..),
    CompleteRequest (..),
    CompleteParams (..),
    CompletionArgument (..),
    Reference (..),

    -- * Server Request Types
    CreateMessageRequest (..),
    CreateMessageParams (..),
    ListRootsRequest (..),
    ListRootsParams (..),

    -- * Response Types
    InitializeResult (..),
    ListResourcesResult (..),
    ListResourceTemplatesResult (..),
    ReadResourceResult (..),
    ListPromptsResult (..),
    GetPromptResult (..),
    ListToolsResult (..),
    CallToolResult (..),
    CompleteResult (..),
    CompletionResult (..),
    CreateMessageResult (..),
    ListRootsResult (..),

    -- * Notification Types
    CancelledNotification (..),
    CancelledParams (..),
    InitializedNotification (..),
    InitializedParams (..),
    ProgressNotification (..),
    ProgressParams (..),
    ResourceListChangedNotification (..),
    ResourceUpdatedNotification (..),
    ResourceUpdatedParams (..),
    PromptListChangedNotification (..),
    ToolListChangedNotification (..),
    LoggingMessageNotification (..),
    LoggingMessageParams (..),
    RootsListChangedNotification (..),

    -- * Union Types
    ClientRequest (..),
    ServerRequest (..),
    ClientNotification (..),
    ServerNotification (..),
) where

import Control.Applicative ((<|>))
import Data.Aeson
import Data.Aeson.TH
import Data.Map (Map)
import Data.Text (Text)
import GHC.Generics

import MCP.Types

-- * JSON-RPC Types

-- | JSON-RPC error information
data JSONRPCErrorInfo = JSONRPCErrorInfo
    { code :: Int
    , message :: Text
    , errorData :: Maybe Value
    }
    deriving stock (Show, Eq, Generic)

instance ToJSON JSONRPCErrorInfo where
    toJSON (JSONRPCErrorInfo c m d) =
        object $
            [ "code" .= c
            , "message" .= m
            ]
                ++ maybe [] (\ed -> ["data" .= ed]) d

instance FromJSON JSONRPCErrorInfo where
    parseJSON = withObject "JSONRPCErrorInfo" $ \o ->
        JSONRPCErrorInfo <$> o .: "code" <*> o .: "message" <*> o .:? "data"

-- | A JSON-RPC request that expects a response
data JSONRPCRequest = JSONRPCRequest
    { jsonrpc :: Text -- Always "2.0"
    , id :: RequestId
    , method :: Text
    , params :: Maybe Value
    }
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions{omitNothingFields = True} ''JSONRPCRequest)

-- | A successful JSON-RPC response
data JSONRPCResponse = JSONRPCResponse
    { jsonrpc :: Text -- Always "2.0"
    , id :: RequestId
    , result :: Value
    }
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions ''JSONRPCResponse)

-- | A JSON-RPC error response
data JSONRPCError = JSONRPCError
    { jsonrpc :: Text -- Always "2.0"
    , id :: RequestId
    , error :: JSONRPCErrorInfo
    }
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions ''JSONRPCError)

-- | A JSON-RPC notification (no response expected)
data JSONRPCNotification = JSONRPCNotification
    { jsonrpc :: Text -- Always "2.0"
    , method :: Text
    , params :: Maybe Value
    }
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions{omitNothingFields = True} ''JSONRPCNotification)

-- | Any JSON-RPC message
data JSONRPCMessage
    = RequestMessage JSONRPCRequest
    | ResponseMessage JSONRPCResponse
    | ErrorMessage JSONRPCError
    | NotificationMessage JSONRPCNotification
    deriving stock (Show, Eq, Generic)

instance ToJSON JSONRPCMessage where
    toJSON (RequestMessage r) = toJSON r
    toJSON (ResponseMessage r) = toJSON r
    toJSON (ErrorMessage e) = toJSON e
    toJSON (NotificationMessage n) = toJSON n

instance FromJSON JSONRPCMessage where
    parseJSON v =
        (RequestMessage <$> parseJSON v)
            <|> (ResponseMessage <$> parseJSON v)
            <|> (ErrorMessage <$> parseJSON v)
            <|> (NotificationMessage <$> parseJSON v)

-- * Client Request Types

-- | Initialize request parameters
data InitializeParams = InitializeParams
    { protocolVersion :: Text
    , capabilities :: ClientCapabilities
    , clientInfo :: Implementation
    }
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions ''InitializeParams)

-- | Initialize request
data InitializeRequest = InitializeRequest
    { method :: Text -- Always "initialize"
    , params :: InitializeParams
    }
    deriving stock (Show, Eq, Generic)

instance ToJSON InitializeRequest where
    toJSON (InitializeRequest _ p) =
        object
            [ "method" .= ("initialize" :: Text)
            , "params" .= p
            ]

instance FromJSON InitializeRequest where
    parseJSON = withObject "InitializeRequest" $ \o -> do
        m <- o .: "method"
        if m == ("initialize" :: Text)
            then InitializeRequest m <$> o .: "params"
            else fail "Expected method 'initialize'"

-- | Ping request parameters
data PingParams where
    PingParams :: {_meta :: Maybe Metadata} -> PingParams
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions{omitNothingFields = True, fieldLabelModifier = \case { "_meta" -> "_meta"; x -> x }} ''PingParams)

-- | Ping request
data PingRequest = PingRequest
    { method :: Text -- Always "ping"
    , params :: Maybe PingParams
    }
    deriving stock (Show, Eq, Generic)

instance ToJSON PingRequest where
    toJSON (PingRequest _ p) =
        object $
            ("method" .= ("ping" :: Text)) : maybe [] (\pr -> ["params" .= pr]) p

instance FromJSON PingRequest where
    parseJSON = withObject "PingRequest" $ \o -> do
        m <- o .: "method"
        if m == ("ping" :: Text)
            then PingRequest m <$> o .:? "params"
            else fail "Expected method 'ping'"

-- | List resources request parameters
data ListResourcesParams where
    ListResourcesParams ::
        {cursor :: Maybe Cursor} ->
        ListResourcesParams
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions{omitNothingFields = True} ''ListResourcesParams)

-- | List resources request
data ListResourcesRequest = ListResourcesRequest
    { method :: Text -- Always "resources/list"
    , params :: Maybe ListResourcesParams
    }
    deriving stock (Show, Eq, Generic)

instance ToJSON ListResourcesRequest where
    toJSON (ListResourcesRequest _ p) =
        object $
            ("method" .= ("resources/list" :: Text)) : maybe [] (\pr -> ["params" .= pr]) p

instance FromJSON ListResourcesRequest where
    parseJSON = withObject "ListResourcesRequest" $ \o -> do
        m <- o .: "method"
        if m == ("resources/list" :: Text)
            then ListResourcesRequest m <$> o .:? "params"
            else fail "Expected method 'resources/list'"

-- | List resource templates request parameters
data ListResourceTemplatesParams where
    ListResourceTemplatesParams ::
        {cursor :: Maybe Cursor} ->
        ListResourceTemplatesParams
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions{omitNothingFields = True} ''ListResourceTemplatesParams)

-- | List resource templates request
data ListResourceTemplatesRequest = ListResourceTemplatesRequest
    { method :: Text -- Always "resources/templates/list"
    , params :: Maybe ListResourceTemplatesParams
    }
    deriving stock (Show, Eq, Generic)

instance ToJSON ListResourceTemplatesRequest where
    toJSON (ListResourceTemplatesRequest _ p) =
        object $
            ("method" .= ("resources/templates/list" :: Text)) : maybe [] (\pr -> ["params" .= pr]) p

instance FromJSON ListResourceTemplatesRequest where
    parseJSON = withObject "ListResourceTemplatesRequest" $ \o -> do
        m <- o .: "method"
        if m == ("resources/templates/list" :: Text)
            then ListResourceTemplatesRequest m <$> o .:? "params"
            else fail "Expected method 'resources/templates/list'"

-- | Read resource request parameters
data ReadResourceParams where
    ReadResourceParams :: {uri :: Text} -> ReadResourceParams
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions ''ReadResourceParams)

-- | Read resource request
data ReadResourceRequest = ReadResourceRequest
    { method :: Text -- Always "resources/read"
    , params :: ReadResourceParams
    }
    deriving stock (Show, Eq, Generic)

instance ToJSON ReadResourceRequest where
    toJSON (ReadResourceRequest _ p) =
        object
            [ "method" .= ("resources/read" :: Text)
            , "params" .= p
            ]

instance FromJSON ReadResourceRequest where
    parseJSON = withObject "ReadResourceRequest" $ \o -> do
        m <- o .: "method"
        if m == ("resources/read" :: Text)
            then ReadResourceRequest m <$> o .: "params"
            else fail "Expected method 'resources/read'"

-- | Subscribe request parameters
data SubscribeParams where
    SubscribeParams :: {uri :: Text} -> SubscribeParams
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions ''SubscribeParams)

-- | Subscribe request
data SubscribeRequest = SubscribeRequest
    { method :: Text -- Always "resources/subscribe"
    , params :: SubscribeParams
    }
    deriving stock (Show, Eq, Generic)

instance ToJSON SubscribeRequest where
    toJSON (SubscribeRequest _ p) =
        object
            [ "method" .= ("resources/subscribe" :: Text)
            , "params" .= p
            ]

instance FromJSON SubscribeRequest where
    parseJSON = withObject "SubscribeRequest" $ \o -> do
        m <- o .: "method"
        if m == ("resources/subscribe" :: Text)
            then SubscribeRequest m <$> o .: "params"
            else fail "Expected method 'resources/subscribe'"

-- | Unsubscribe request parameters
data UnsubscribeParams where
    UnsubscribeParams :: {uri :: Text} -> UnsubscribeParams
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions ''UnsubscribeParams)

-- | Unsubscribe request
data UnsubscribeRequest = UnsubscribeRequest
    { method :: Text -- Always "resources/unsubscribe"
    , params :: UnsubscribeParams
    }
    deriving stock (Show, Eq, Generic)

instance ToJSON UnsubscribeRequest where
    toJSON (UnsubscribeRequest _ p) =
        object
            [ "method" .= ("resources/unsubscribe" :: Text)
            , "params" .= p
            ]

instance FromJSON UnsubscribeRequest where
    parseJSON = withObject "UnsubscribeRequest" $ \o -> do
        m <- o .: "method"
        if m == ("resources/unsubscribe" :: Text)
            then UnsubscribeRequest m <$> o .: "params"
            else fail "Expected method 'resources/unsubscribe'"

-- | List prompts request parameters
data ListPromptsParams where
    ListPromptsParams :: {cursor :: Maybe Cursor} -> ListPromptsParams
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions{omitNothingFields = True} ''ListPromptsParams)

-- | List prompts request
data ListPromptsRequest = ListPromptsRequest
    { method :: Text -- Always "prompts/list"
    , params :: Maybe ListPromptsParams
    }
    deriving stock (Show, Eq, Generic)

instance ToJSON ListPromptsRequest where
    toJSON (ListPromptsRequest _ p) =
        object $
            ("method" .= ("prompts/list" :: Text)) : maybe [] (\pr -> ["params" .= pr]) p

instance FromJSON ListPromptsRequest where
    parseJSON = withObject "ListPromptsRequest" $ \o -> do
        m <- o .: "method"
        if m == ("prompts/list" :: Text)
            then ListPromptsRequest m <$> o .:? "params"
            else fail "Expected method 'prompts/list'"

-- | Get prompt request parameters
data GetPromptParams = GetPromptParams
    { name :: Text
    , arguments :: Maybe (Map Text Text)
    }
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions{omitNothingFields = True} ''GetPromptParams)

-- | Get prompt request
data GetPromptRequest = GetPromptRequest
    { method :: Text -- Always "prompts/get"
    , params :: GetPromptParams
    }
    deriving stock (Show, Eq, Generic)

instance ToJSON GetPromptRequest where
    toJSON (GetPromptRequest _ p) =
        object
            [ "method" .= ("prompts/get" :: Text)
            , "params" .= p
            ]

instance FromJSON GetPromptRequest where
    parseJSON = withObject "GetPromptRequest" $ \o -> do
        m <- o .: "method"
        if m == ("prompts/get" :: Text)
            then GetPromptRequest m <$> o .: "params"
            else fail "Expected method 'prompts/get'"

-- | List tools request parameters
data ListToolsParams where
    ListToolsParams :: {cursor :: Maybe Cursor} -> ListToolsParams
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions{omitNothingFields = True} ''ListToolsParams)

-- | List tools request
data ListToolsRequest = ListToolsRequest
    { method :: Text -- Always "tools/list"
    , params :: Maybe ListToolsParams
    }
    deriving stock (Show, Eq, Generic)

instance ToJSON ListToolsRequest where
    toJSON (ListToolsRequest _ p) =
        object $
            ("method" .= ("tools/list" :: Text)) : maybe [] (\pr -> ["params" .= pr]) p

instance FromJSON ListToolsRequest where
    parseJSON = withObject "ListToolsRequest" $ \o -> do
        m <- o .: "method"
        if m == ("tools/list" :: Text)
            then ListToolsRequest m <$> o .:? "params"
            else fail "Expected method 'tools/list'"

-- | Call tool request parameters
data CallToolParams = CallToolParams
    { name :: Text
    , arguments :: Maybe (Map Text Value)
    }
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions{omitNothingFields = True} ''CallToolParams)

-- | Call tool request
data CallToolRequest = CallToolRequest
    { method :: Text -- Always "tools/call"
    , params :: CallToolParams
    }
    deriving stock (Show, Eq, Generic)

instance ToJSON CallToolRequest where
    toJSON (CallToolRequest _ p) =
        object
            [ "method" .= ("tools/call" :: Text)
            , "params" .= p
            ]

instance FromJSON CallToolRequest where
    parseJSON = withObject "CallToolRequest" $ \o -> do
        m <- o .: "method"
        if m == ("tools/call" :: Text)
            then CallToolRequest m <$> o .: "params"
            else fail "Expected method 'tools/call'"

-- | Set level request parameters
data SetLevelParams where
    SetLevelParams :: {level :: LoggingLevel} -> SetLevelParams
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions ''SetLevelParams)

-- | Set level request
data SetLevelRequest = SetLevelRequest
    { method :: Text -- Always "logging/setLevel"
    , params :: SetLevelParams
    }
    deriving stock (Show, Eq, Generic)

instance ToJSON SetLevelRequest where
    toJSON (SetLevelRequest _ p) =
        object
            [ "method" .= ("logging/setLevel" :: Text)
            , "params" .= p
            ]

instance FromJSON SetLevelRequest where
    parseJSON = withObject "SetLevelRequest" $ \o -> do
        m <- o .: "method"
        if m == ("logging/setLevel" :: Text)
            then SetLevelRequest m <$> o .: "params"
            else fail "Expected method 'logging/setLevel'"

-- | Completion argument
data CompletionArgument = CompletionArgument
    { name :: Text
    , value :: Text
    }
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions ''CompletionArgument)

-- | Reference (prompt or resource)
data Reference
    = PromptRef PromptReference
    | ResourceRef ResourceReference
    deriving stock (Show, Eq, Generic)

instance ToJSON Reference where
    toJSON (PromptRef p) = toJSON p
    toJSON (ResourceRef r) = toJSON r

instance FromJSON Reference where
    parseJSON v =
        (PromptRef <$> parseJSON v)
            <|> (ResourceRef <$> parseJSON v)

-- | Complete request parameters
data CompleteParams = CompleteParams
    { ref :: Reference
    , argument :: CompletionArgument
    }
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions ''CompleteParams)

-- | Complete request
data CompleteRequest = CompleteRequest
    { method :: Text -- Always "completion/complete"
    , params :: CompleteParams
    }
    deriving stock (Show, Eq, Generic)

instance ToJSON CompleteRequest where
    toJSON (CompleteRequest _ p) =
        object
            [ "method" .= ("completion/complete" :: Text)
            , "params" .= p
            ]

instance FromJSON CompleteRequest where
    parseJSON = withObject "CompleteRequest" $ \o -> do
        m <- o .: "method"
        if m == ("completion/complete" :: Text)
            then CompleteRequest m <$> o .: "params"
            else fail "Expected method 'completion/complete'"

-- * Server Request Types

-- | Create message request parameters
data CreateMessageParams = CreateMessageParams
    { maxTokens :: Int
    , messages :: [SamplingMessage]
    , modelPreferences :: Maybe ModelPreferences
    , systemPrompt :: Maybe Text
    , includeContext :: Maybe IncludeContext
    , temperature :: Maybe Double
    , stopSequences :: Maybe [Text]
    , metadata :: Maybe (Map Text Value)
    }
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions{omitNothingFields = True} ''CreateMessageParams)

-- | Create message request
data CreateMessageRequest = CreateMessageRequest
    { method :: Text -- Always "sampling/createMessage"
    , params :: CreateMessageParams
    }
    deriving stock (Show, Eq, Generic)

instance ToJSON CreateMessageRequest where
    toJSON (CreateMessageRequest _ p) =
        object
            [ "method" .= ("sampling/createMessage" :: Text)
            , "params" .= p
            ]

instance FromJSON CreateMessageRequest where
    parseJSON = withObject "CreateMessageRequest" $ \o -> do
        m <- o .: "method"
        if m == ("sampling/createMessage" :: Text)
            then CreateMessageRequest m <$> o .: "params"
            else fail "Expected method 'sampling/createMessage'"

-- | List roots request parameters
data ListRootsParams where
    ListRootsParams :: {_meta :: Maybe Metadata} -> ListRootsParams
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions{omitNothingFields = True, fieldLabelModifier = \case { "_meta" -> "_meta"; x -> x }} ''ListRootsParams)

-- | List roots request
data ListRootsRequest = ListRootsRequest
    { method :: Text -- Always "roots/list"
    , params :: Maybe ListRootsParams
    }
    deriving stock (Show, Eq, Generic)

instance ToJSON ListRootsRequest where
    toJSON (ListRootsRequest _ p) =
        object $
            ("method" .= ("roots/list" :: Text)) : maybe [] (\pr -> ["params" .= pr]) p

instance FromJSON ListRootsRequest where
    parseJSON = withObject "ListRootsRequest" $ \o -> do
        m <- o .: "method"
        if m == ("roots/list" :: Text)
            then ListRootsRequest m <$> o .:? "params"
            else fail "Expected method 'roots/list'"

-- * Response Types

-- | Initialize result
data InitializeResult = InitializeResult
    { protocolVersion :: Text
    , capabilities :: ServerCapabilities
    , serverInfo :: Implementation
    , instructions :: Maybe Text
    , _meta :: Maybe Metadata
    }
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions{omitNothingFields = True, fieldLabelModifier = \case { "_meta" -> "_meta"; x -> x }} ''InitializeResult)

-- | List resources result
data ListResourcesResult = ListResourcesResult
    { resources :: [Resource]
    , nextCursor :: Maybe Cursor
    , _meta :: Maybe Metadata
    }
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions{omitNothingFields = True, fieldLabelModifier = \case { "_meta" -> "_meta"; x -> x }} ''ListResourcesResult)

-- | List resource templates result
data ListResourceTemplatesResult = ListResourceTemplatesResult
    { resourceTemplates :: [ResourceTemplate]
    , nextCursor :: Maybe Cursor
    , _meta :: Maybe Metadata
    }
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions{omitNothingFields = True, fieldLabelModifier = \case { "_meta" -> "_meta"; x -> x }} ''ListResourceTemplatesResult)

-- | Read resource result
data ReadResourceResult = ReadResourceResult
    { contents :: [ResourceContents]
    , _meta :: Maybe Metadata
    }
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions{omitNothingFields = True, fieldLabelModifier = \case { "_meta" -> "_meta"; x -> x }} ''ReadResourceResult)

-- | List prompts result
data ListPromptsResult = ListPromptsResult
    { prompts :: [Prompt]
    , nextCursor :: Maybe Cursor
    , _meta :: Maybe Metadata
    }
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions{omitNothingFields = True, fieldLabelModifier = \case { "_meta" -> "_meta"; x -> x }} ''ListPromptsResult)

-- | Get prompt result
data GetPromptResult = GetPromptResult
    { description :: Maybe Text
    , messages :: [PromptMessage]
    , _meta :: Maybe Metadata
    }
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions{omitNothingFields = True, fieldLabelModifier = \case { "_meta" -> "_meta"; x -> x }} ''GetPromptResult)

-- | List tools result
data ListToolsResult = ListToolsResult
    { tools :: [Tool]
    , nextCursor :: Maybe Cursor
    , _meta :: Maybe Metadata
    }
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions{omitNothingFields = True, fieldLabelModifier = \case { "_meta" -> "_meta"; x -> x }} ''ListToolsResult)

-- | Call tool result
data CallToolResult = CallToolResult
    { content :: [Content]
    , isError :: Maybe Bool
    , _meta :: Maybe Metadata
    }
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions{omitNothingFields = True, fieldLabelModifier = \case { "_meta" -> "_meta"; x -> x }} ''CallToolResult)

-- | Completion result inner type
data CompletionResult = CompletionResult
    { values :: [Text]
    , total :: Maybe Int
    , hasMore :: Maybe Bool
    }
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions{omitNothingFields = True} ''CompletionResult)

-- | Complete result
data CompleteResult = CompleteResult
    { completion :: CompletionResult
    , _meta :: Maybe Metadata
    }
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions{omitNothingFields = True, fieldLabelModifier = \case { "_meta" -> "_meta"; x -> x }} ''CompleteResult)

-- | Create message result
data CreateMessageResult = CreateMessageResult
    { role :: Role
    , content :: Content
    , model :: Text
    , stopReason :: Maybe Text
    , _meta :: Maybe Metadata
    }
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions{omitNothingFields = True, fieldLabelModifier = \case { "_meta" -> "_meta"; x -> x }} ''CreateMessageResult)

-- | List roots result
data ListRootsResult = ListRootsResult
    { roots :: [Root]
    , _meta :: Maybe Metadata
    }
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions{omitNothingFields = True, fieldLabelModifier = \case { "_meta" -> "_meta"; x -> x }} ''ListRootsResult)

-- * Notification Types

-- | Cancelled notification parameters
data CancelledParams = CancelledParams
    { requestId :: RequestId
    , reason :: Maybe Text
    }
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions{omitNothingFields = True} ''CancelledParams)

-- | Cancelled notification
data CancelledNotification = CancelledNotification
    { method :: Text -- Always "notifications/cancelled"
    , params :: CancelledParams
    }
    deriving stock (Show, Eq, Generic)

instance ToJSON CancelledNotification where
    toJSON (CancelledNotification _ p) =
        object
            [ "method" .= ("notifications/cancelled" :: Text)
            , "params" .= p
            ]

instance FromJSON CancelledNotification where
    parseJSON = withObject "CancelledNotification" $ \o -> do
        m <- o .: "method"
        if m == ("notifications/cancelled" :: Text)
            then CancelledNotification m <$> o .: "params"
            else fail "Expected method 'notifications/cancelled'"

-- | Initialized notification parameters
data InitializedParams where
    InitializedParams :: {_meta :: Maybe Metadata} -> InitializedParams
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions{omitNothingFields = True, fieldLabelModifier = \case { "_meta" -> "_meta"; x -> x }} ''InitializedParams)

-- | Initialized notification
data InitializedNotification = InitializedNotification
    { method :: Text -- Always "notifications/initialized"
    , params :: Maybe InitializedParams
    }
    deriving stock (Show, Eq, Generic)

instance ToJSON InitializedNotification where
    toJSON (InitializedNotification _ p) =
        object $
            ("method" .= ("notifications/initialized" :: Text)) : maybe [] (\pr -> ["params" .= pr]) p

instance FromJSON InitializedNotification where
    parseJSON = withObject "InitializedNotification" $ \o -> do
        m <- o .: "method"
        if m == ("notifications/initialized" :: Text)
            then InitializedNotification m <$> o .:? "params"
            else fail "Expected method 'notifications/initialized'"

-- | Progress notification parameters
data ProgressParams = ProgressParams
    { progressToken :: ProgressToken
    , progress :: Double
    , total :: Maybe Double
    , message :: Maybe Text
    }
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions{omitNothingFields = True} ''ProgressParams)

-- | Progress notification
data ProgressNotification = ProgressNotification
    { method :: Text -- Always "notifications/progress"
    , params :: ProgressParams
    }
    deriving stock (Show, Eq, Generic)

instance ToJSON ProgressNotification where
    toJSON (ProgressNotification _ p) =
        object
            [ "method" .= ("notifications/progress" :: Text)
            , "params" .= p
            ]

instance FromJSON ProgressNotification where
    parseJSON = withObject "ProgressNotification" $ \o -> do
        m <- o .: "method"
        if m == ("notifications/progress" :: Text)
            then ProgressNotification m <$> o .: "params"
            else fail "Expected method 'notifications/progress'"

-- | Resource list changed notification
data ResourceListChangedNotification = ResourceListChangedNotification
    { method :: Text -- Always "notifications/resources/list_changed"
    , params :: Maybe InitializedParams
    }
    deriving stock (Show, Eq, Generic)

instance ToJSON ResourceListChangedNotification where
    toJSON (ResourceListChangedNotification _ p) =
        object $
            ("method" .= ("notifications/resources/list_changed" :: Text)) : maybe [] (\pr -> ["params" .= pr]) p

instance FromJSON ResourceListChangedNotification where
    parseJSON = withObject "ResourceListChangedNotification" $ \o -> do
        m <- o .: "method"
        if m == ("notifications/resources/list_changed" :: Text)
            then ResourceListChangedNotification m <$> o .:? "params"
            else fail "Expected method 'notifications/resources/list_changed'"

-- | Resource updated notification parameters
data ResourceUpdatedParams = ResourceUpdatedParams
    { uri :: Text
    }
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions ''ResourceUpdatedParams)

-- | Resource updated notification
data ResourceUpdatedNotification = ResourceUpdatedNotification
    { method :: Text -- Always "notifications/resources/updated"
    , params :: ResourceUpdatedParams
    }
    deriving stock (Show, Eq, Generic)

instance ToJSON ResourceUpdatedNotification where
    toJSON (ResourceUpdatedNotification _ p) =
        object
            [ "method" .= ("notifications/resources/updated" :: Text)
            , "params" .= p
            ]

instance FromJSON ResourceUpdatedNotification where
    parseJSON = withObject "ResourceUpdatedNotification" $ \o -> do
        m <- o .: "method"
        if m == ("notifications/resources/updated" :: Text)
            then ResourceUpdatedNotification m <$> o .: "params"
            else fail "Expected method 'notifications/resources/updated'"

-- | Prompt list changed notification
data PromptListChangedNotification = PromptListChangedNotification
    { method :: Text -- Always "notifications/prompts/list_changed"
    , params :: Maybe InitializedParams
    }
    deriving stock (Show, Eq, Generic)

instance ToJSON PromptListChangedNotification where
    toJSON (PromptListChangedNotification _ p) =
        object $
            ("method" .= ("notifications/prompts/list_changed" :: Text)) : maybe [] (\pr -> ["params" .= pr]) p

instance FromJSON PromptListChangedNotification where
    parseJSON = withObject "PromptListChangedNotification" $ \o -> do
        m <- o .: "method"
        if m == ("notifications/prompts/list_changed" :: Text)
            then PromptListChangedNotification m <$> o .:? "params"
            else fail "Expected method 'notifications/prompts/list_changed'"

-- | Tool list changed notification
data ToolListChangedNotification = ToolListChangedNotification
    { method :: Text -- Always "notifications/tools/list_changed"
    , params :: Maybe InitializedParams
    }
    deriving stock (Show, Eq, Generic)

instance ToJSON ToolListChangedNotification where
    toJSON (ToolListChangedNotification _ p) =
        object $
            ("method" .= ("notifications/tools/list_changed" :: Text)) : maybe [] (\pr -> ["params" .= pr]) p

instance FromJSON ToolListChangedNotification where
    parseJSON = withObject "ToolListChangedNotification" $ \o -> do
        m <- o .: "method"
        if m == ("notifications/tools/list_changed" :: Text)
            then ToolListChangedNotification m <$> o .:? "params"
            else fail "Expected method 'notifications/tools/list_changed'"

-- | Logging message notification parameters
data LoggingMessageParams = LoggingMessageParams
    { level :: LoggingLevel
    , data' :: Value -- Can be any JSON value
    , logger :: Maybe Text
    }
    deriving stock (Show, Eq, Generic)

instance ToJSON LoggingMessageParams where
    toJSON (LoggingMessageParams lvl d lgr) =
        object $
            [ "level" .= lvl
            , "data" .= d
            ]
                ++ maybe [] (\l -> ["logger" .= l]) lgr

instance FromJSON LoggingMessageParams where
    parseJSON = withObject "LoggingMessageParams" $ \o ->
        LoggingMessageParams <$> o .: "level" <*> o .: "data" <*> o .:? "logger"

-- | Logging message notification
data LoggingMessageNotification = LoggingMessageNotification
    { method :: Text -- Always "notifications/message"
    , params :: LoggingMessageParams
    }
    deriving stock (Show, Eq, Generic)

instance ToJSON LoggingMessageNotification where
    toJSON (LoggingMessageNotification _ p) =
        object
            [ "method" .= ("notifications/message" :: Text)
            , "params" .= p
            ]

instance FromJSON LoggingMessageNotification where
    parseJSON = withObject "LoggingMessageNotification" $ \o -> do
        m <- o .: "method"
        if m == ("notifications/message" :: Text)
            then LoggingMessageNotification m <$> o .: "params"
            else fail "Expected method 'notifications/message'"

-- | Roots list changed notification
data RootsListChangedNotification = RootsListChangedNotification
    { method :: Text -- Always "notifications/roots/list_changed"
    , params :: Maybe InitializedParams
    }
    deriving stock (Show, Eq, Generic)

instance ToJSON RootsListChangedNotification where
    toJSON (RootsListChangedNotification _ p) =
        object $
            ("method" .= ("notifications/roots/list_changed" :: Text)) : maybe [] (\pr -> ["params" .= pr]) p

instance FromJSON RootsListChangedNotification where
    parseJSON = withObject "RootsListChangedNotification" $ \o -> do
        m <- o .: "method"
        if m == ("notifications/roots/list_changed" :: Text)
            then RootsListChangedNotification m <$> o .:? "params"
            else fail "Expected method 'notifications/roots/list_changed'"

-- * Union Types

-- | Any client request
data ClientRequest
    = InitializeReq InitializeRequest
    | PingReq PingRequest
    | ListResourcesReq ListResourcesRequest
    | ListResourceTemplatesReq ListResourceTemplatesRequest
    | ReadResourceReq ReadResourceRequest
    | SubscribeReq SubscribeRequest
    | UnsubscribeReq UnsubscribeRequest
    | ListPromptsReq ListPromptsRequest
    | GetPromptReq GetPromptRequest
    | ListToolsReq ListToolsRequest
    | CallToolReq CallToolRequest
    | SetLevelReq SetLevelRequest
    | CompleteReq CompleteRequest
    deriving stock (Show, Eq, Generic)

instance ToJSON ClientRequest where
    toJSON (InitializeReq r) = toJSON r
    toJSON (PingReq r) = toJSON r
    toJSON (ListResourcesReq r) = toJSON r
    toJSON (ListResourceTemplatesReq r) = toJSON r
    toJSON (ReadResourceReq r) = toJSON r
    toJSON (SubscribeReq r) = toJSON r
    toJSON (UnsubscribeReq r) = toJSON r
    toJSON (ListPromptsReq r) = toJSON r
    toJSON (GetPromptReq r) = toJSON r
    toJSON (ListToolsReq r) = toJSON r
    toJSON (CallToolReq r) = toJSON r
    toJSON (SetLevelReq r) = toJSON r
    toJSON (CompleteReq r) = toJSON r

instance FromJSON ClientRequest where
    parseJSON v =
        (InitializeReq <$> parseJSON v)
            <|> (PingReq <$> parseJSON v)
            <|> (ListResourcesReq <$> parseJSON v)
            <|> (ListResourceTemplatesReq <$> parseJSON v)
            <|> (ReadResourceReq <$> parseJSON v)
            <|> (SubscribeReq <$> parseJSON v)
            <|> (UnsubscribeReq <$> parseJSON v)
            <|> (ListPromptsReq <$> parseJSON v)
            <|> (GetPromptReq <$> parseJSON v)
            <|> (ListToolsReq <$> parseJSON v)
            <|> (CallToolReq <$> parseJSON v)
            <|> (SetLevelReq <$> parseJSON v)
            <|> (CompleteReq <$> parseJSON v)

-- | Any server request
data ServerRequest
    = PingServerReq PingRequest
    | CreateMessageReq CreateMessageRequest
    | ListRootsReq ListRootsRequest
    deriving stock (Show, Eq, Generic)

instance ToJSON ServerRequest where
    toJSON (PingServerReq r) = toJSON r
    toJSON (CreateMessageReq r) = toJSON r
    toJSON (ListRootsReq r) = toJSON r

instance FromJSON ServerRequest where
    parseJSON v =
        (PingServerReq <$> parseJSON v)
            <|> (CreateMessageReq <$> parseJSON v)
            <|> (ListRootsReq <$> parseJSON v)

-- | Any client notification
data ClientNotification
    = CancelledNotif CancelledNotification
    | InitializedNotif InitializedNotification
    | ProgressNotif ProgressNotification
    | RootsListChangedNotif RootsListChangedNotification
    deriving stock (Show, Eq, Generic)

instance ToJSON ClientNotification where
    toJSON (CancelledNotif n) = toJSON n
    toJSON (InitializedNotif n) = toJSON n
    toJSON (ProgressNotif n) = toJSON n
    toJSON (RootsListChangedNotif n) = toJSON n

instance FromJSON ClientNotification where
    parseJSON v =
        (CancelledNotif <$> parseJSON v)
            <|> (InitializedNotif <$> parseJSON v)
            <|> (ProgressNotif <$> parseJSON v)
            <|> (RootsListChangedNotif <$> parseJSON v)

-- | Any server notification
data ServerNotification
    = CancelledServerNotif CancelledNotification
    | ProgressServerNotif ProgressNotification
    | ResourceListChangedNotif ResourceListChangedNotification
    | ResourceUpdatedNotif ResourceUpdatedNotification
    | PromptListChangedNotif PromptListChangedNotification
    | ToolListChangedNotif ToolListChangedNotification
    | LoggingMessageNotif LoggingMessageNotification
    deriving stock (Show, Eq, Generic)

instance ToJSON ServerNotification where
    toJSON (CancelledServerNotif n) = toJSON n
    toJSON (ProgressServerNotif n) = toJSON n
    toJSON (ResourceListChangedNotif n) = toJSON n
    toJSON (ResourceUpdatedNotif n) = toJSON n
    toJSON (PromptListChangedNotif n) = toJSON n
    toJSON (ToolListChangedNotif n) = toJSON n
    toJSON (LoggingMessageNotif n) = toJSON n

instance FromJSON ServerNotification where
    parseJSON v =
        (CancelledServerNotif <$> parseJSON v)
            <|> (ProgressServerNotif <$> parseJSON v)
            <|> (ResourceListChangedNotif <$> parseJSON v)
            <|> (ResourceUpdatedNotif <$> parseJSON v)
            <|> (PromptListChangedNotif <$> parseJSON v)
            <|> (ToolListChangedNotif <$> parseJSON v)
            <|> (LoggingMessageNotif <$> parseJSON v)