packages feed

mcp-server-0.2.0.0: src/MCP/Server/Protocol.hs

{-# LANGUAGE DeriveGeneric     #-}
{-# LANGUAGE OverloadedStrings #-}

module MCP.Server.Protocol
  ( -- * MCP Protocol Messages
    InitializeRequest(..)
  , InitializeResponse(..)
  , InitializedNotification(..)
  , PingRequest(..)
  , PongResponse(..)

    -- * Prompts Protocol
  , PromptsListRequest(..)
  , PromptsListResponse(..)
  , PromptsGetRequest(..)
  , PromptsGetResponse(..)
  , PromptMessage(..)
  , MessageRole(..)

    -- * Resources Protocol
  , ResourcesListRequest(..)
  , ResourcesListResponse(..)
  , ResourcesReadRequest(..)
  , ResourcesReadResponse(..)
  , ResourcesTemplatesListResponse(..)

    -- * Completion Protocol
  , CompleteRequest(..)
  , CompleteResponse(..)

    -- * Tools Protocol
  , ToolsListRequest(..)
  , ToolsListResponse(..)
  , ToolsCallRequest(..)
  , ToolsCallResponse(..)

    -- * Common Types
  , ListChangedNotification(..)

    -- * Protocol Functions
  , protocolVersion
  , supportedVersions
  , modernVersions
  , allVersions
  ) where

import           Data.Aeson
import           Data.Aeson.Types (Parser)
import           Data.Map         (Map)
import           Data.Text        (Text)
import qualified Data.Text        as T
import           GHC.Generics     (Generic)
import           MCP.Server.Types

-- | The newest /legacy/ (initialize-handshake) revision the server
-- advertises by default. Used as the fallback when an initializing client
-- proposes a version this library does not recognise — deliberately capped
-- at the newest legacy revision: a client that sends @initialize@ is legacy
-- by definition and must not be promised the stateless 2026-07-28 semantics.
protocolVersion :: Text
protocolVersion = "2025-11-25"

-- | Legacy (initialize-handshake) revisions whose wire format for the basic
-- tool/resource/prompt operations this library implements is identical.
-- The server echoes back any of these a client proposes (see
-- 'MCP.Server.Handlers.validateProtocolVersion'), satisfying the spec
-- requirement that a supported version be answered with the same version.
--
-- Ordered newest-first.
supportedVersions :: [Text]
supportedVersions =
  [ "2025-11-25"
  , "2025-06-18"
  , "2025-03-26"
  , "2024-11-05"
  ]

-- | Modern (stateless, per-request @_meta@) revisions this library
-- implements. Requests declaring one of these are served with modern
-- semantics: @resultType@, cacheability fields, and @serverInfo@ in result
-- @_meta@.
modernVersions :: [Text]
modernVersions =
  [ "2026-07-28"
  ]

-- | Every revision this library implements, newest first — what
-- @server\/discover@ and 'UnsupportedProtocolVersionError' report.
allVersions :: [Text]
allVersions = modernVersions ++ supportedVersions


-- | Initialize request
data InitializeRequest = InitializeRequest
  { initProtocolVersion :: Text
  , initCapabilities    :: Value
  , initClientInfo      :: Value
  } deriving (Show, Eq, Generic)

instance FromJSON InitializeRequest where
  parseJSON = withObject "InitializeRequest" $ \o -> InitializeRequest
    <$> o .: "protocolVersion"
    <*> o .: "capabilities"
    <*> o .: "clientInfo"

-- | Initialize response
data InitializeResponse = InitializeResponse
  { initRespProtocolVersion :: Text
  , initRespCapabilities    :: ServerCapabilities
  , initRespServerInfo      :: McpServerInfo
  } deriving (Show, Eq, Generic)

instance ToJSON InitializeResponse where
  toJSON resp = object
    [ "protocolVersion" .= initRespProtocolVersion resp
    , "capabilities" .= initRespCapabilities resp
    , "serverInfo" .= object
        [ "name" .= serverName (initRespServerInfo resp)
        , "version" .= serverVersion (initRespServerInfo resp)
        , "instructions" .= serverInstructions (initRespServerInfo resp)
        ]
    ]

-- | Initialized notification (no parameters)
data InitializedNotification = InitializedNotification
  deriving (Show, Eq, Generic)

instance FromJSON InitializedNotification where
  parseJSON _ = return InitializedNotification

-- | Ping request (no parameters)
data PingRequest = PingRequest
  deriving (Show, Eq, Generic)

instance FromJSON PingRequest where
  parseJSON _ = return PingRequest

-- | Pong response (empty object)
data PongResponse = PongResponse
  deriving (Show, Eq, Generic)

instance ToJSON PongResponse where
  toJSON PongResponse = object []

-- | Prompts list request
data PromptsListRequest = PromptsListRequest
  deriving (Show, Eq, Generic)

instance FromJSON PromptsListRequest where
  parseJSON _ = return PromptsListRequest

-- | Prompts list response
data PromptsListResponse = PromptsListResponse
  { promptsListPrompts :: [PromptDefinition]
  } deriving (Show, Eq, Generic)

instance ToJSON PromptsListResponse where
  toJSON resp = object
    [ "prompts" .= promptsListPrompts resp
    ]

-- | Prompts get request
data PromptsGetRequest = PromptsGetRequest
  { promptsGetName      :: Text
  , promptsGetArguments :: Maybe (Map Text Value)
  } deriving (Show, Eq, Generic)

instance FromJSON PromptsGetRequest where
  parseJSON = withObject "PromptsGetRequest" $ \o -> PromptsGetRequest
    <$> o .: "name"
    <*> o .:? "arguments"

-- | Prompts get response (2025-06-18 enhanced)
data PromptsGetResponse = PromptsGetResponse
  { promptsGetDescription :: Maybe Text
  , promptsGetMessages    :: [PromptMessage]
  , promptsGetMeta        :: Maybe Value  -- New _meta field for additional metadata
  } deriving (Show, Eq, Generic)

instance ToJSON PromptsGetResponse where
  toJSON resp = object $
    [ "messages" .= promptsGetMessages resp
    ] ++ maybe [] (\d -> ["description" .= d]) (promptsGetDescription resp)
      ++ maybe [] (\m -> ["_meta" .= m]) (promptsGetMeta resp)

-- | Resources list request
data ResourcesListRequest = ResourcesListRequest
  deriving (Show, Eq, Generic)

instance FromJSON ResourcesListRequest where
  parseJSON _ = return ResourcesListRequest

-- | Resources list response
data ResourcesListResponse = ResourcesListResponse
  { resourcesListResources :: [ResourceDefinition]
  } deriving (Show, Eq, Generic)

instance ToJSON ResourcesListResponse where
  toJSON resp = object
    [ "resources" .= resourcesListResources resp
    ]

-- | Resources read request
data ResourcesReadRequest = ResourcesReadRequest
  { resourcesReadUri :: URI
  } deriving (Show, Eq, Generic)

instance FromJSON ResourcesReadRequest where
  parseJSON = withObject "ResourcesReadRequest" $ \o -> do
    uriText <- o .: "uri"
    case parseURI uriText of
      Just uri -> return $ ResourcesReadRequest uri
      Nothing  -> fail "Invalid URI"

-- | Resources read response
data ResourcesReadResponse = ResourcesReadResponse
  { resourcesReadContents :: [ResourceContent]
  } deriving (Show, Eq, Generic)

instance ToJSON ResourcesReadResponse where
  toJSON resp = object
    [ "contents" .= resourcesReadContents resp
    ]

-- | Resource templates list response
data ResourcesTemplatesListResponse = ResourcesTemplatesListResponse
  { resourcesTemplatesList :: [ResourceTemplateDefinition]
  } deriving (Show, Eq, Generic)

instance ToJSON ResourcesTemplatesListResponse where
  toJSON resp = object
    [ "resourceTemplates" .= resourcesTemplatesList resp
    ]

-- | Completion request (completion/complete)
data CompleteRequest = CompleteRequest
  { completeRef             :: CompletionRef
  , completeArgumentName    :: Text
  , completeArgumentValue   :: Text
  , completeContextArgs     :: Map Text Text
  } deriving (Show, Eq, Generic)

instance FromJSON CompleteRequest where
  parseJSON = withObject "CompleteRequest" $ \o -> do
    refObj <- o .: "ref"
    ref <- withObject "CompletionRef"
      (\r -> do
        refType <- r .: "type" :: Parser Text
        case refType of
          "ref/prompt"   -> CompletionRefPrompt <$> r .: "name"
          "ref/resource" -> CompletionRefResource <$> r .: "uri"
          _              -> fail $ "Unknown completion ref type: " ++ T.unpack refType)
      refObj
    argument <- o .: "argument"
    (name, value) <- withObject "argument"
      (\a -> (,) <$> a .: "name" <*> a .: "value")
      argument
    ctx <- o .:? "context"
    ctxArgs <- case ctx of
      Nothing -> pure mempty
      Just c  -> withObject "context" (\co -> co .:? "arguments" .!= mempty) c
    pure $ CompleteRequest ref name value ctxArgs

-- | Completion response
data CompleteResponse = CompleteResponse
  { completeResult :: CompletionResult
  } deriving (Show, Eq, Generic)

instance ToJSON CompleteResponse where
  toJSON (CompleteResponse res) = object
    [ "completion" .= object
        ([ "values" .= take 100 (completionValues res) ]
          ++ maybe [] (\t -> ["total" .= t]) (completionTotal res)
          ++ maybe [] (\h -> ["hasMore" .= h]) (completionHasMore res))
    ]

-- | Tools list request
data ToolsListRequest = ToolsListRequest
  deriving (Show, Eq, Generic)

instance FromJSON ToolsListRequest where
  parseJSON _ = return ToolsListRequest

-- | Tools list response
data ToolsListResponse = ToolsListResponse
  { toolsListTools :: [ToolDefinition]
  } deriving (Show, Eq, Generic)

instance ToJSON ToolsListResponse where
  toJSON resp = object
    [ "tools" .= toolsListTools resp
    ]

-- | Tools call request
data ToolsCallRequest = ToolsCallRequest
  { toolsCallName      :: Text
  , toolsCallArguments :: Maybe (Map Text Value)
  } deriving (Show, Eq, Generic)

instance FromJSON ToolsCallRequest where
  parseJSON = withObject "ToolsCallRequest" $ \o -> ToolsCallRequest
    <$> o .: "name"
    <*> o .:? "arguments"

-- | Tools call response (2025-06-18 enhanced)
data ToolsCallResponse = ToolsCallResponse
  { toolsCallContent           :: [Content]
  , toolsCallIsError           :: Maybe Bool
  , toolsCallStructuredContent :: Maybe Value  -- ^ structuredContent (2025-06-18+)
  , toolsCallMeta              :: Maybe Value
  } deriving (Show, Eq, Generic)

instance ToJSON ToolsCallResponse where
  toJSON resp = object $
    [ "content" .= toolsCallContent resp
    ] ++ maybe [] (\e -> ["isError" .= e]) (toolsCallIsError resp)
      ++ maybe [] (\s -> ["structuredContent" .= s]) (toolsCallStructuredContent resp)
      ++ maybe [] (\m -> ["_meta" .= m]) (toolsCallMeta resp)

-- | List changed notification
data ListChangedNotification = ListChangedNotification
  deriving (Show, Eq, Generic)

instance ToJSON ListChangedNotification where
  toJSON ListChangedNotification = object []