packages feed

mcp-0.1.0.0: src/MCP/Types.hs

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}

-- |
-- Module      : MCP.Types
-- Description : Core types for the Model Context Protocol (MCP)
-- Copyright   : (C) 2025 Matthias Pall Gissurarson
-- License     : MIT
-- Maintainer  : mpg@mpg.is
-- Stability   : experimental
-- Portability : GHC
--
-- This module defines the core types used in the Model Context Protocol (MCP),
-- including JSON-RPC message types, client/server capabilities, resources,
-- tools, prompts, and various request/response types.
module MCP.Types (
    -- * Basic Types
    RequestId (..),
    Role (..),
    Cursor (..),
    ProgressToken (..),
    LoggingLevel (..),

    -- * Content Types
    Annotations (..),
    TextContent (..),
    ImageContent (..),
    AudioContent (..),
    EmbeddedResource (..),
    Content (..),

    -- * Resource Types
    ResourceContents (..),
    TextResourceContents (..),
    BlobResourceContents (..),
    Resource (..),
    ResourceTemplate (..),
    ResourceReference (..),

    -- * Tool Types
    ToolAnnotations (..),
    Tool (..),
    InputSchema (..),

    -- * Prompt Types
    PromptArgument (..),
    Prompt (..),
    PromptMessage (..),
    PromptReference (..),

    -- * Model Types
    ModelHint (..),
    ModelPreferences (..),
    IncludeContext (..),
    SamplingMessage (..),

    -- * Capability Types
    ClientCapabilities (..),
    ServerCapabilities (..),
    RootsCapability (..),
    PromptsCapability (..),
    ResourcesCapability (..),
    ToolsCapability (..),
    CompletionsCapability (..),
    LoggingCapability (..),
    SamplingCapability (..),
    ExperimentalCapability (..),

    -- * Implementation Info
    Implementation (..),

    -- * Roots
    Root (..),

    -- * Result Types
    Result (..),
    Metadata (..),
) where

import Control.Applicative (Alternative ((<|>)))
import Data.Aeson hiding (Error, Result)
import Data.Aeson.TH
import Data.Map (Map)
import Data.Text (Text)
import GHC.Generics

-- | A uniquely identifying ID for a request in JSON-RPC
newtype RequestId = RequestId Value
    deriving stock (Show, Eq)
    deriving newtype (ToJSON, FromJSON)

-- | The sender or recipient of messages and data in a conversation
data Role = User | Assistant
    deriving stock (Show, Eq, Generic)

instance ToJSON Role where
    toJSON User = "user"
    toJSON Assistant = "assistant"

instance FromJSON Role where
    parseJSON = withText "Role" $ \case
        "user" -> pure User
        "assistant" -> pure Assistant
        other -> fail $ "Unknown role: " <> show other

-- | An opaque token used to represent a cursor for pagination
newtype Cursor = Cursor Text
    deriving stock (Show, Eq)
    deriving newtype (ToJSON, FromJSON)

-- | A progress token, used to associate progress notifications with the original request
newtype ProgressToken = ProgressToken Value
    deriving stock (Show, Eq)
    deriving newtype (ToJSON, FromJSON)

-- | The severity of a log message
data LoggingLevel = Alert | Critical | Debug | Emergency | Error | Info | Notice | Warning
    deriving stock (Show, Eq, Generic)

instance ToJSON LoggingLevel where
    toJSON Alert = "alert"
    toJSON Critical = "critical"
    toJSON Debug = "debug"
    toJSON Emergency = "emergency"
    toJSON Error = "error"
    toJSON Info = "info"
    toJSON Notice = "notice"
    toJSON Warning = "warning"

instance FromJSON LoggingLevel where
    parseJSON = withText "LoggingLevel" $ \case
        "alert" -> pure Alert
        "critical" -> pure Critical
        "debug" -> pure Debug
        "emergency" -> pure Emergency
        "error" -> pure Error
        "info" -> pure Info
        "notice" -> pure Notice
        "warning" -> pure Warning
        other -> fail $ "Unknown logging level: " <> show other

-- | Optional annotations for the client
data Annotations = Annotations
    { audience :: Maybe [Role]
    , priority :: Maybe Double -- 0.0 to 1.0
    }
    deriving stock (Show, Eq, Generic)

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

-- | Text provided to or from an LLM
data TextContent = TextContent
    { textType :: Text -- Always "text"
    , text :: Text
    , annotations :: Maybe Annotations
    }
    deriving stock (Show, Eq, Generic)

instance ToJSON TextContent where
    toJSON (TextContent _ txt anns) =
        object $
            [ "type" .= ("text" :: Text)
            , "text" .= txt
            ]
                ++ maybe [] (\a -> ["annotations" .= a]) anns

instance FromJSON TextContent where
    parseJSON = withObject "TextContent" $ \o -> do
        ty <- o .: "type"
        if ty == ("text" :: Text)
            then TextContent ty <$> o .: "text" <*> o .:? "annotations"
            else fail "Expected type 'text'"

-- | An image provided to or from an LLM
data ImageContent = ImageContent
    { imageType :: Text -- Always "image"
    , data' :: Text -- base64-encoded image data
    , mimeType :: Text
    , annotations :: Maybe Annotations
    }
    deriving stock (Show, Eq, Generic)

instance ToJSON ImageContent where
    toJSON (ImageContent _ dat mime anns) =
        object $
            [ "type" .= ("image" :: Text)
            , "data" .= dat
            , "mimeType" .= mime
            ]
                ++ maybe [] (\a -> ["annotations" .= a]) anns

instance FromJSON ImageContent where
    parseJSON = withObject "ImageContent" $ \o -> do
        ty <- o .: "type"
        if ty == ("image" :: Text)
            then ImageContent ty <$> o .: "data" <*> o .: "mimeType" <*> o .:? "annotations"
            else fail "Expected type 'image'"

-- | Audio provided to or from an LLM
data AudioContent = AudioContent
    { audioType :: Text -- Always "audio"
    , data' :: Text -- base64-encoded audio data
    , mimeType :: Text
    , annotations :: Maybe Annotations
    }
    deriving stock (Show, Eq, Generic)

instance ToJSON AudioContent where
    toJSON (AudioContent _ dat mime anns) =
        object $
            [ "type" .= ("audio" :: Text)
            , "data" .= dat
            , "mimeType" .= mime
            ]
                ++ maybe [] (\a -> ["annotations" .= a]) anns

instance FromJSON AudioContent where
    parseJSON = withObject "AudioContent" $ \o -> do
        ty <- o .: "type"
        if ty == ("audio" :: Text)
            then AudioContent ty <$> o .: "data" <*> o .: "mimeType" <*> o .:? "annotations"
            else fail "Expected type 'audio'"

-- | Text resource contents
data TextResourceContents = TextResourceContents
    { uri :: Text
    , text :: Text
    , mimeType :: Maybe Text
    }
    deriving stock (Show, Eq, Generic)

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

-- | Blob resource contents
data BlobResourceContents = BlobResourceContents
    { uri :: Text
    , blob :: Text -- base64-encoded
    , mimeType :: Maybe Text
    }
    deriving stock (Show, Eq, Generic)

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

-- | Resource contents (text or blob)
data ResourceContents
    = TextResource TextResourceContents
    | BlobResource BlobResourceContents
    deriving stock (Show, Eq, Generic)

instance ToJSON ResourceContents where
    toJSON (TextResource t) = toJSON t
    toJSON (BlobResource b) = toJSON b

instance FromJSON ResourceContents where
    parseJSON v =
        (TextResource <$> parseJSON v)
            <|> (BlobResource <$> parseJSON v)

-- | The contents of a resource, embedded into a prompt or tool call result
data EmbeddedResource = EmbeddedResource
    { resourceType :: Text -- Always "resource"
    , resource :: ResourceContents
    , annotations :: Maybe Annotations
    }
    deriving stock (Show, Eq, Generic)

instance ToJSON EmbeddedResource where
    toJSON (EmbeddedResource _ res anns) =
        object $
            [ "type" .= ("resource" :: Text)
            , "resource" .= res
            ]
                ++ maybe [] (\a -> ["annotations" .= a]) anns

instance FromJSON EmbeddedResource where
    parseJSON = withObject "EmbeddedResource" $ \o -> do
        ty <- o .: "type"
        if ty == ("resource" :: Text)
            then EmbeddedResource ty <$> o .: "resource" <*> o .:? "annotations"
            else fail "Expected type 'resource'"

-- | Content that can be text, image, audio, or embedded resource
data Content
    = TextContentType TextContent
    | ImageContentType ImageContent
    | AudioContentType AudioContent
    | EmbeddedResourceType EmbeddedResource
    deriving stock (Show, Eq, Generic)

instance ToJSON Content where
    toJSON (TextContentType c) = toJSON c
    toJSON (ImageContentType c) = toJSON c
    toJSON (AudioContentType c) = toJSON c
    toJSON (EmbeddedResourceType c) = toJSON c

instance FromJSON Content where
    parseJSON v =
        (TextContentType <$> parseJSON v)
            <|> (ImageContentType <$> parseJSON v)
            <|> (AudioContentType <$> parseJSON v)
            <|> (EmbeddedResourceType <$> parseJSON v)

-- | A known resource that the server is capable of reading
data Resource = Resource
    { uri :: Text
    , name :: Text
    , description :: Maybe Text
    , mimeType :: Maybe Text
    , size :: Maybe Int
    , annotations :: Maybe Annotations
    }
    deriving stock (Show, Eq, Generic)

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

-- | A template description for resources available on the server
data ResourceTemplate = ResourceTemplate
    { name :: Text
    , uriTemplate :: Text
    , description :: Maybe Text
    , mimeType :: Maybe Text
    , annotations :: Maybe Annotations
    }
    deriving stock (Show, Eq, Generic)

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

-- | A reference to a resource or resource template definition
data ResourceReference = ResourceReference
    { refType :: Text -- Always "ref/resource"
    , uri :: Text
    }
    deriving stock (Show, Eq, Generic)

instance ToJSON ResourceReference where
    toJSON (ResourceReference _ u) =
        object
            [ "type" .= ("ref/resource" :: Text)
            , "uri" .= u
            ]

instance FromJSON ResourceReference where
    parseJSON = withObject "ResourceReference" $ \o -> do
        ty <- o .: "type"
        if ty == ("ref/resource" :: Text)
            then ResourceReference ty <$> o .: "uri"
            else fail "Expected type 'ref/resource'"

-- | Additional properties describing a Tool to clients
data ToolAnnotations = ToolAnnotations
    { title :: Maybe Text
    , readOnlyHint :: Maybe Bool
    , destructiveHint :: Maybe Bool
    , idempotentHint :: Maybe Bool
    , openWorldHint :: Maybe Bool
    }
    deriving stock (Show, Eq, Generic)

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

-- | Input schema for a tool
data InputSchema = InputSchema
    { schemaType :: Text -- Always "object"
    , properties :: Maybe (Map Text Value)
    , required :: Maybe [Text]
    }
    deriving stock (Show, Eq, Generic)

instance ToJSON InputSchema where
    toJSON (InputSchema _ props req) =
        object $
            [ "type" .= ("object" :: Text)
            ]
                ++ maybe [] (\p -> ["properties" .= p]) props
                ++ maybe [] (\r -> ["required" .= r]) req

instance FromJSON InputSchema where
    parseJSON = withObject "InputSchema" $ \o -> do
        ty <- o .: "type"
        if ty == ("object" :: Text)
            then InputSchema ty <$> o .:? "properties" <*> o .:? "required"
            else fail "Expected type 'object'"

-- | Definition for a tool the client can call
data Tool = Tool
    { name :: Text
    , description :: Maybe Text
    , inputSchema :: InputSchema
    , annotations :: Maybe ToolAnnotations
    }
    deriving stock (Show, Eq, Generic)

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

-- | Describes an argument that a prompt can accept
data PromptArgument = PromptArgument
    { name :: Text
    , description :: Maybe Text
    , required :: Maybe Bool
    }
    deriving stock (Show, Eq, Generic)

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

-- | A prompt or prompt template that the server offers
data Prompt = Prompt
    { name :: Text
    , description :: Maybe Text
    , arguments :: Maybe [PromptArgument]
    }
    deriving stock (Show, Eq, Generic)

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

-- | Describes a message returned as part of a prompt
data PromptMessage = PromptMessage
    { role :: Role
    , content :: Content
    }
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions ''PromptMessage)

-- | Identifies a prompt
data PromptReference = PromptReference
    { refType :: Text -- Always "ref/prompt"
    , name :: Text
    }
    deriving stock (Show, Eq, Generic)

instance ToJSON PromptReference where
    toJSON (PromptReference _ n) =
        object
            [ "type" .= ("ref/prompt" :: Text)
            , "name" .= n
            ]

instance FromJSON PromptReference where
    parseJSON = withObject "PromptReference" $ \o -> do
        ty <- o .: "type"
        if ty == ("ref/prompt" :: Text)
            then PromptReference ty <$> o .: "name"
            else fail "Expected type 'ref/prompt'"

-- | Hints to use for model selection
data ModelHint = ModelHint
    { name :: Maybe Text
    }
    deriving stock (Show, Eq, Generic)

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

-- | The server's preferences for model selection
data ModelPreferences = ModelPreferences
    { hints :: Maybe [ModelHint]
    , costPriority :: Maybe Double -- 0.0 to 1.0
    , speedPriority :: Maybe Double -- 0.0 to 1.0
    , intelligencePriority :: Maybe Double -- 0.0 to 1.0
    }
    deriving stock (Show, Eq, Generic)

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

-- | Include context options for sampling
data IncludeContext = AllServers | None | ThisServer
    deriving stock (Show, Eq, Generic)

instance ToJSON IncludeContext where
    toJSON AllServers = "allServers"
    toJSON None = "none"
    toJSON ThisServer = "thisServer"

instance FromJSON IncludeContext where
    parseJSON = withText "IncludeContext" $ \case
        "allServers" -> pure AllServers
        "none" -> pure None
        "thisServer" -> pure ThisServer
        other -> fail $ "Unknown include context: " <> show other

-- | Describes a message issued to or received from an LLM API
data SamplingMessage = SamplingMessage
    { role :: Role
    , content :: Content
    }
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions ''SamplingMessage)

-- | Roots capability
data RootsCapability = RootsCapability
    { listChanged :: Maybe Bool
    }
    deriving stock (Show, Eq, Generic)

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

-- | Prompts capability
data PromptsCapability = PromptsCapability
    { listChanged :: Maybe Bool
    }
    deriving stock (Show, Eq, Generic)

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

-- | Resources capability
data ResourcesCapability = ResourcesCapability
    { listChanged :: Maybe Bool
    , subscribe :: Maybe Bool
    }
    deriving stock (Show, Eq, Generic)

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

-- | Tools capability
data ToolsCapability = ToolsCapability
    { listChanged :: Maybe Bool
    }
    deriving stock (Show, Eq, Generic)

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

-- | Completions capability
data CompletionsCapability = CompletionsCapability
    deriving stock (Show, Eq, Generic)

instance ToJSON CompletionsCapability where
    toJSON _ = object []

instance FromJSON CompletionsCapability where
    parseJSON = withObject "CompletionsCapability" $ \_ -> pure CompletionsCapability

-- | Logging capability
data LoggingCapability = LoggingCapability
    deriving stock (Show, Eq, Generic)

instance ToJSON LoggingCapability where
    toJSON _ = object []

instance FromJSON LoggingCapability where
    parseJSON = withObject "LoggingCapability" $ \_ -> pure LoggingCapability

-- | Sampling capability
data SamplingCapability = SamplingCapability
    deriving stock (Show, Eq, Generic)

instance ToJSON SamplingCapability where
    toJSON _ = object []

instance FromJSON SamplingCapability where
    parseJSON = withObject "SamplingCapability" $ \_ -> pure SamplingCapability

-- | Experimental capability
newtype ExperimentalCapability = ExperimentalCapability (Map Text Value)
    deriving stock (Show, Eq, Generic)
    deriving newtype (ToJSON, FromJSON)

-- | Capabilities a client may support
data ClientCapabilities = ClientCapabilities
    { roots :: Maybe RootsCapability
    , sampling :: Maybe SamplingCapability
    , experimental :: Maybe ExperimentalCapability
    }
    deriving stock (Show, Eq, Generic)

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

-- | Capabilities that a server may support
data ServerCapabilities = ServerCapabilities
    { logging :: Maybe LoggingCapability
    , prompts :: Maybe PromptsCapability
    , resources :: Maybe ResourcesCapability
    , tools :: Maybe ToolsCapability
    , completions :: Maybe CompletionsCapability
    , experimental :: Maybe ExperimentalCapability
    }
    deriving stock (Show, Eq, Generic)

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

-- | Describes the name and version of an MCP implementation
data Implementation = Implementation
    { name :: Text
    , version :: Text
    }
    deriving stock (Show, Eq, Generic)

$(deriveJSON defaultOptions ''Implementation)

-- | Represents a root directory or file that the server can operate on
data Root = Root
    { uri :: Text
    , name :: Maybe Text
    }
    deriving stock (Show, Eq, Generic)

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

-- | Metadata for results
newtype Metadata = Metadata (Map Text Value)
    deriving stock (Show, Eq, Generic)
    deriving newtype (ToJSON, FromJSON)

-- | Base result type
data Result = Result
    { _meta :: Maybe Metadata
    }
    deriving stock (Show, Eq, Generic)

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