mcp-server-0.2.0.1: src/MCP/Server/Types.hs
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveLift #-}
{-# LANGUAGE OverloadedStrings #-}
module MCP.Server.Types
( -- * Content Types
Content(..)
, ContentImageData(..)
, ContentAudioData(..)
, ResourceContent(..)
-- * Metadata Types
, Annotations(..)
, defaultAnnotations
, ToolAnnotations(..)
, defaultToolAnnotations
, Icon(..)
, icon
, LogLevel(..)
-- * Handler Result Types
, ToolResult(..)
, toolResult
, toolError
, ToToolResult(..)
, ToolOutput(..)
, PromptResult(..)
, ToPromptResult(..)
, PromptMessage(..)
, MessageRole(..)
-- * URI Utilities
, parseURI
, URI
-- * Error Types
, Error(..)
-- * Schema Types
, Schema(..)
, SchemaType(..)
, schema
, describedSchema
-- * Definition Types
, PromptDefinition(..)
, mkPromptDefinition
, ResourceDefinition(..)
, mkResourceDefinition
, ResourceTemplateDefinition(..)
, mkResourceTemplateDefinition
, ToolDefinition(..)
, mkToolDefinition
, ArgumentDefinition(..)
-- * Completion Types
, CompletionRef(..)
, CompletionResult(..)
, completionResult
-- * Server Types
, McpServerInfo(..)
, McpServerHandlers(..)
, noHandlers
, ClientContext(..)
, anonymousContext
, CacheHints(..)
, defaultCacheHints
, NotificationSupport(..)
, noNotificationSupport
, ServerCapabilities(..)
, PromptCapabilities(..)
, ResourceCapabilities(..)
, ToolCapabilities(..)
, CompletionCapabilities(..)
, LoggingCapabilities(..)
-- * Handler Types
, PromptListHandler
, PromptGetHandler
, ResourceListHandler
, ResourceReadHandler
, ResourceTemplateListHandler
, ToolListHandler
, ToolCallHandler
, CompletionHandler
-- * Basic Types
, PromptName
, ToolName
, ArgumentName
, ArgumentValue
) where
import Data.Aeson
import Data.Aeson.Key (fromText)
import qualified Data.Aeson.KeyMap as KM
import Data.Aeson.Types (Pair, Parser)
import Data.Map (Map)
import Data.Maybe (catMaybes, listToMaybe)
import Data.Text (Text)
import qualified Data.Text as T
import GHC.Generics (Generic)
import Language.Haskell.TH.Syntax (Lift)
import Network.URI (URI, parseURI)
type PromptName = Text
type ToolName = Text
type ArgumentName = Text
type ArgumentValue = Text
-- | Content that can be returned by prompts, resources, and tools
data Content
= ContentText Text
| ContentImage ContentImageData
| ContentAudio ContentAudioData
| ContentEmbeddedResource ResourceContent
-- ^ A resource embedded into the result, carrying its full contents
| ContentResourceLink ResourceDefinition
-- ^ A reference to a resource the client can read separately
| ContentAnnotated Annotations Content
-- ^ A content block carrying 'Annotations'; the annotations are merged
-- into the inner block's JSON object. Do not nest.
deriving (Show, Eq, Generic)
instance ToJSON Content where
toJSON (ContentText text) = object
[ "type" .= ("text" :: Text)
, "text" .= text
]
toJSON (ContentImage img) = object
[ "type" .= ("image" :: Text)
, "data" .= contentImageData img
, "mimeType" .= contentImageMimeType img
]
toJSON (ContentAudio audio) = object
[ "type" .= ("audio" :: Text)
, "data" .= contentAudioData audio
, "mimeType" .= contentAudioMimeType audio
]
toJSON (ContentEmbeddedResource res) = object
[ "type" .= ("resource" :: Text)
, "resource" .= res
]
toJSON (ContentResourceLink def) =
case toJSON def of
Object o -> Object (KM.insert "type" (String "resource_link") o)
other -> other
toJSON (ContentAnnotated anns inner) =
case toJSON inner of
Object o -> Object (KM.insert "annotations" (toJSON anns) o)
other -> other
instance FromJSON Content where
parseJSON = withObject "Content" $ \o -> do
inner <- parseInner o
case KM.lookup "annotations" o of
Just anns -> ContentAnnotated <$> parseJSON anns <*> pure inner
Nothing -> pure inner
where
parseInner o = do
contentType <- o .: "type" :: Parser Text
case contentType of
"text" -> ContentText <$> o .: "text"
"image" -> do
imgData <- o .: "data"
mimeType <- o .: "mimeType"
return $ ContentImage $ ContentImageData imgData mimeType
"audio" -> do
audioData <- o .: "data"
mimeType <- o .: "mimeType"
return $ ContentAudio $ ContentAudioData audioData mimeType
"resource" -> ContentEmbeddedResource <$> o .: "resource"
"resource_link" -> ContentResourceLink <$> parseJSON (Object o)
_ -> fail $ "Unknown content type: " ++ T.unpack contentType
-- | Optional hints on content blocks: who the content is for, how important
-- it is, and when it last changed (2025-03-26+).
data Annotations = Annotations
{ annotationsAudience :: [MessageRole] -- ^ Intended audience(s); omitted when empty
, annotationsPriority :: Maybe Double -- ^ 0.0 (optional) … 1.0 (most important)
, annotationsLastModified :: Maybe Text -- ^ ISO 8601 timestamp
} deriving (Show, Eq, Generic)
-- | No annotations set; record-update the ones you need.
defaultAnnotations :: Annotations
defaultAnnotations = Annotations
{ annotationsAudience = []
, annotationsPriority = Nothing
, annotationsLastModified = Nothing
}
instance ToJSON Annotations where
toJSON anns = object $
(if null (annotationsAudience anns) then [] else ["audience" .= annotationsAudience anns])
++ maybe [] (\p -> ["priority" .= p]) (annotationsPriority anns)
++ maybe [] (\lm -> ["lastModified" .= lm]) (annotationsLastModified anns)
instance FromJSON Annotations where
parseJSON = withObject "Annotations" $ \o -> Annotations
<$> o .:? "audience" .!= []
<*> o .:? "priority"
<*> o .:? "lastModified"
data ContentImageData = ContentImageData
{ contentImageData :: Text -- ^ base64-encoded image data
, contentImageMimeType :: Text
} deriving (Show, Eq, Generic)
data ContentAudioData = ContentAudioData
{ contentAudioData :: Text -- ^ base64-encoded audio data
, contentAudioMimeType :: Text
} deriving (Show, Eq, Generic)
-- | Resource content compliant with MCP specification
-- Must include uri and mimeType, with either text or blob data
data ResourceContent
= ResourceText
{ resourceUri :: URI
, resourceMimeType :: Text
, resourceText :: Text
}
| ResourceBlob
{ resourceUri :: URI
, resourceMimeType :: Text
, resourceBlob :: Text -- base64 encoded
}
deriving (Show, Eq, Generic)
instance ToJSON ResourceContent where
toJSON (ResourceText uri mimeType text) = object
[ "uri" .= show uri
, "mimeType" .= mimeType
, "text" .= text
]
toJSON (ResourceBlob uri mimeType blob) = object
[ "uri" .= show uri
, "mimeType" .= mimeType
, "blob" .= blob
]
instance FromJSON ResourceContent where
parseJSON = withObject "ResourceContent" $ \o -> do
uriText <- o .: "uri"
mimeType <- o .: "mimeType"
case parseURI uriText of
Nothing -> fail "Invalid URI"
Just uri -> do
maybeText <- o .:? "text"
maybeBlob <- o .:? "blob"
case (maybeText, maybeBlob) of
(Just text, Nothing) -> return $ ResourceText uri mimeType text
(Nothing, Just blob) -> return $ ResourceBlob uri mimeType blob
_ -> fail "ResourceContent must have either 'text' or 'blob' field"
-- | Message role for prompts
data MessageRole = RoleUser | RoleAssistant
deriving (Show, Eq, Generic)
instance ToJSON MessageRole where
toJSON RoleUser = "user"
toJSON RoleAssistant = "assistant"
instance FromJSON MessageRole where
parseJSON = withText "MessageRole" $ \t -> case t of
"user" -> pure RoleUser
"assistant" -> pure RoleAssistant
_ -> fail $ "Unknown role: " ++ T.unpack t
-- | Behavioral hints on a tool (2025-03-26+): clients use these for
-- permission UX (e.g. auto-approving read-only tools). All hints are
-- advisory and default to unset.
data ToolAnnotations = ToolAnnotations
{ toolAnnotationsTitle :: Maybe Text
, toolReadOnlyHint :: Maybe Bool -- ^ The tool does not modify its environment
, toolDestructiveHint :: Maybe Bool -- ^ The tool may perform destructive updates
, toolIdempotentHint :: Maybe Bool -- ^ Repeated calls with the same arguments have no additional effect
, toolOpenWorldHint :: Maybe Bool -- ^ The tool interacts with an open world of external entities
} deriving (Show, Eq, Generic, Lift)
-- | No hints set; record-update the ones you need.
defaultToolAnnotations :: ToolAnnotations
defaultToolAnnotations = ToolAnnotations
{ toolAnnotationsTitle = Nothing
, toolReadOnlyHint = Nothing
, toolDestructiveHint = Nothing
, toolIdempotentHint = Nothing
, toolOpenWorldHint = Nothing
}
instance ToJSON ToolAnnotations where
toJSON anns = object $ concat
[ maybe [] (\t -> ["title" .= t]) (toolAnnotationsTitle anns)
, maybe [] (\b -> ["readOnlyHint" .= b]) (toolReadOnlyHint anns)
, maybe [] (\b -> ["destructiveHint" .= b]) (toolDestructiveHint anns)
, maybe [] (\b -> ["idempotentHint" .= b]) (toolIdempotentHint anns)
, maybe [] (\b -> ["openWorldHint" .= b]) (toolOpenWorldHint anns)
]
-- | An icon a client may display for a tool, prompt or resource
-- (2025-11-25+).
data Icon = Icon
{ iconSrc :: Text -- ^ URI of the icon
, iconMimeType :: Maybe Text
, iconSizes :: [Text] -- ^ e.g. @[\"48x48\"]@; omitted when empty
} deriving (Show, Eq, Generic, Lift)
-- | An icon with just a source URI.
icon :: Text -> Icon
icon src = Icon { iconSrc = src, iconMimeType = Nothing, iconSizes = [] }
-- | RFC 5424 log severities, least to most severe; the 'Ord' instance
-- follows severity, so @level >= threshold@ is the filtering test.
data LogLevel
= LogDebug
| LogInfo
| LogNotice
| LogWarning
| LogError
| LogCritical
| LogAlert
| LogEmergency
deriving (Show, Eq, Ord, Enum, Bounded, Generic)
logLevelText :: LogLevel -> Text
logLevelText l = case l of
LogDebug -> "debug"
LogInfo -> "info"
LogNotice -> "notice"
LogWarning -> "warning"
LogError -> "error"
LogCritical -> "critical"
LogAlert -> "alert"
LogEmergency -> "emergency"
instance ToJSON LogLevel where
toJSON = String . logLevelText
instance FromJSON LogLevel where
parseJSON = withText "LogLevel" $ \t ->
case lookup t [(logLevelText l, l) | l <- [minBound .. maxBound]] of
Just l -> pure l
Nothing -> fail $ "Unknown log level: " ++ T.unpack t
instance ToJSON Icon where
toJSON i = object $
[ "src" .= iconSrc i ]
++ maybe [] (\m -> ["mimeType" .= m]) (iconMimeType i)
++ (if null (iconSizes i) then [] else ["sizes" .= iconSizes i])
instance FromJSON Icon where
parseJSON = withObject "Icon" $ \o -> Icon
<$> o .: "src"
<*> o .:? "mimeType"
<*> o .:? "sizes" .!= []
-- | Prompt message
data PromptMessage = PromptMessage
{ promptMessageRole :: MessageRole
, promptMessageContent :: Content
} deriving (Show, Eq, Generic)
instance ToJSON PromptMessage where
toJSON msg = object
[ "role" .= promptMessageRole msg
, "content" .= promptMessageContent msg
]
-- | The full result of a tool call.
--
-- Tool /execution/ failures belong in the result with 'toolResultIsError'
-- set (so the model can see what went wrong and react); JSON-RPC errors are
-- reserved for protocol-level failures such as unknown tools or malformed
-- arguments.
data ToolResult = ToolResult
{ toolResultContent :: [Content]
, toolResultStructured :: Maybe Value -- ^ structuredContent (2025-06-18+)
, toolResultIsError :: Bool
, toolResultMeta :: Maybe Value
} deriving (Show, Eq, Generic)
-- | A successful tool result with the given content blocks.
toolResult :: [Content] -> ToolResult
toolResult content = ToolResult
{ toolResultContent = content
, toolResultStructured = Nothing
, toolResultIsError = False
, toolResultMeta = Nothing
}
-- | A failed tool execution: the message is reported to the model with
-- @isError: true@ rather than as a JSON-RPC error.
toolError :: Text -> ToolResult
toolError msg = (toolResult [ContentText msg]) { toolResultIsError = True }
-- | Types a tool handler may return. Returning plain 'Content' (or 'Text')
-- keeps simple handlers simple; return a full 'ToolResult' for multiple
-- content blocks, structured content, or execution errors.
class ToToolResult a where
toToolResult :: a -> ToolResult
instance ToToolResult ToolResult where
toToolResult = id
instance ToToolResult Content where
toToolResult c = toolResult [c]
-- | Concatenates content; a merged result is an error if any element is
-- ('toolResultIsError' is OR-ed), and the first present 'structuredContent'
-- and @_meta@ are kept.
instance ToToolResult a => ToToolResult [a] where
toToolResult xs = ToolResult
{ toolResultContent = concatMap toolResultContent rs
, toolResultStructured = firstJust (map toolResultStructured rs)
, toolResultIsError = any toolResultIsError rs
, toolResultMeta = firstJust (map toolResultMeta rs)
}
where rs = map toToolResult xs
instance ToToolResult Text where
toToolResult = toToolResult . ContentText
firstJust :: [Maybe a] -> Maybe a
firstJust = listToMaybe . catMaybes
-- | What a tool handler derived with
-- 'MCP.Server.Derive.deriveToolHandlerWithOutput' returns: a typed value
-- that the derivation serializes into @structuredContent@ (matching the
-- derived @outputSchema@), or one of the escape hatches.
data ToolOutput o
= ToolOutput o
-- ^ A structured value. Per the spec's recommendation, the serialized
-- JSON is also returned as a text content block for clients that
-- predate structured output.
| ToolOutputWith [Content] o
-- ^ A structured value with caller-supplied content blocks (no
-- automatic text block).
| ToolOutputError Text
-- ^ An execution failure, reported with @isError@ (see 'toolError').
| ToolOutputRaw ToolResult
-- ^ Full control: return this 'ToolResult' as-is, with no structured
-- content implied.
deriving (Show, Eq)
-- | The full result of a prompts/get request: an optional description and
-- a conversation of one or more messages.
data PromptResult = PromptResult
{ promptResultDescription :: Maybe Text
, promptResultMessages :: [PromptMessage]
} deriving (Show, Eq, Generic)
-- | Types a prompt handler may return. Plain 'Content' (or 'Text') becomes a
-- single user message; return 'PromptResult' or @['PromptMessage']@ for
-- multi-message conversations or assistant roles.
class ToPromptResult a where
toPromptResult :: a -> PromptResult
instance ToPromptResult PromptResult where
toPromptResult = id
instance ToPromptResult PromptMessage where
toPromptResult m = PromptResult Nothing [m]
-- | Concatenates messages and keeps the first present description.
instance ToPromptResult a => ToPromptResult [a] where
toPromptResult xs = PromptResult
(firstJust (map promptResultDescription rs))
(concatMap promptResultMessages rs)
where rs = map toPromptResult xs
instance ToPromptResult Content where
toPromptResult c = toPromptResult (PromptMessage RoleUser c)
instance ToPromptResult Text where
toPromptResult = toPromptResult . ContentText
-- | MCP protocol errors
data Error
= InvalidPromptName Text
| MissingRequiredParams Text
| ResourceNotFound Text
| InternalError Text
| UnknownTool Text
| InvalidRequest Text
| MethodNotFound Text
| InvalidParams Text
deriving (Show, Eq, Generic)
instance ToJSON Error where
toJSON err = object
[ "code" .= errorCode err
, "message" .= errorMessage err
]
where
errorCode :: Error -> Int
errorCode (InvalidPromptName _) = -32602
errorCode (MissingRequiredParams _) = -32602
errorCode (ResourceNotFound _) = -32602
errorCode (InternalError _) = -32603
errorCode (UnknownTool _) = -32602
errorCode (InvalidRequest _) = -32600
errorCode (MethodNotFound _) = -32601
errorCode (InvalidParams _) = -32602
errorMessage :: Error -> Text
errorMessage (InvalidPromptName msg) = "Invalid prompt name: " <> msg
errorMessage (MissingRequiredParams msg) = "Missing required parameters: " <> msg
errorMessage (ResourceNotFound msg) = "Resource not found: " <> msg
errorMessage (InternalError msg) = "Internal error: " <> msg
errorMessage (UnknownTool msg) = "Unknown tool: " <> msg
errorMessage (InvalidRequest msg) = "Invalid request: " <> msg
errorMessage (MethodNotFound msg) = "Method not found: " <> msg
errorMessage (InvalidParams msg) = "Invalid parameters: " <> msg
-- | A JSON Schema fragment: a shape plus an optional description.
data Schema = Schema
{ schemaDescription :: Maybe Text
, schemaShape :: SchemaType
} deriving (Show, Eq, Generic)
-- | The shape of a JSON Schema fragment.
data SchemaType
= SchemaString (Maybe [Text])
-- ^ A string, optionally restricted to an enum of allowed values
| SchemaInteger
| SchemaNumber
| SchemaBoolean
| SchemaArray Schema
| SchemaObject [(Text, Schema)] [Text]
-- ^ Properties and the names of the required ones
deriving (Show, Eq, Generic)
-- | A schema with no description.
schema :: SchemaType -> Schema
schema = Schema Nothing
-- | A schema with a description.
describedSchema :: Text -> SchemaType -> Schema
describedSchema desc = Schema (Just desc)
instance ToJSON Schema where
toJSON (Schema desc shape) = object $
typeFields shape ++ maybe [] (\d -> ["description" .= d]) desc
where
typeFields :: SchemaType -> [Pair]
typeFields (SchemaString enumVals) =
[ "type" .= ("string" :: Text) ]
++ maybe [] (\vs -> ["enum" .= vs]) enumVals
typeFields SchemaInteger = [ "type" .= ("integer" :: Text) ]
typeFields SchemaNumber = [ "type" .= ("number" :: Text) ]
typeFields SchemaBoolean = [ "type" .= ("boolean" :: Text) ]
typeFields (SchemaArray items) =
[ "type" .= ("array" :: Text)
, "items" .= items
]
typeFields (SchemaObject props req) =
[ "type" .= ("object" :: Text)
, "properties" .= object (map (\(k, v) -> fromText k .= v) props)
, "required" .= req
]
-- | Prompt definition (2025-06-18 enhanced)
data PromptDefinition = PromptDefinition
{ promptDefinitionName :: Text
, promptDefinitionDescription :: Text
, promptDefinitionArguments :: [ArgumentDefinition]
, promptDefinitionTitle :: Maybe Text -- New title field for human-friendly display
, promptDefinitionIcons :: [Icon] -- ^ omitted when empty (2025-11-25+)
} deriving (Show, Eq, Generic)
-- | A prompt definition with only the required fields set; record-update
-- the optional ones (constructing 'PromptDefinition' directly breaks when
-- fields are added).
mkPromptDefinition :: Text -> Text -> [ArgumentDefinition] -> PromptDefinition
mkPromptDefinition name description args = PromptDefinition
{ promptDefinitionName = name
, promptDefinitionDescription = description
, promptDefinitionArguments = args
, promptDefinitionTitle = Nothing
, promptDefinitionIcons = []
}
instance ToJSON PromptDefinition where
toJSON def = object $
[ "name" .= promptDefinitionName def
, "description" .= promptDefinitionDescription def
, "arguments" .= promptDefinitionArguments def
] ++ maybe [] (\t -> ["title" .= t]) (promptDefinitionTitle def)
++ (if null (promptDefinitionIcons def) then [] else ["icons" .= promptDefinitionIcons def])
-- | Resource definition (2025-06-18 enhanced)
data ResourceDefinition = ResourceDefinition
{ resourceDefinitionURI :: Text
, resourceDefinitionName :: Text
, resourceDefinitionDescription :: Maybe Text
, resourceDefinitionMimeType :: Maybe Text
, resourceDefinitionTitle :: Maybe Text -- New title field for human-friendly display
, resourceDefinitionIcons :: [Icon] -- ^ omitted when empty (2025-11-25+)
} deriving (Show, Eq, Generic)
-- | A resource definition with only the required fields set; record-update
-- the optional ones.
mkResourceDefinition :: Text -> Text -> ResourceDefinition
mkResourceDefinition uri name = ResourceDefinition
{ resourceDefinitionURI = uri
, resourceDefinitionName = name
, resourceDefinitionDescription = Nothing
, resourceDefinitionMimeType = Nothing
, resourceDefinitionTitle = Nothing
, resourceDefinitionIcons = []
}
instance ToJSON ResourceDefinition where
toJSON def = object $
[ "uri" .= resourceDefinitionURI def
, "name" .= resourceDefinitionName def
] ++
maybe [] (\d -> ["description" .= d]) (resourceDefinitionDescription def) ++
maybe [] (\m -> ["mimeType" .= m]) (resourceDefinitionMimeType def) ++
maybe [] (\t -> ["title" .= t]) (resourceDefinitionTitle def) ++
(if null (resourceDefinitionIcons def) then [] else ["icons" .= resourceDefinitionIcons def])
instance FromJSON ResourceDefinition where
parseJSON = withObject "ResourceDefinition" $ \o -> ResourceDefinition
<$> o .: "uri"
<*> o .: "name"
<*> o .:? "description"
<*> o .:? "mimeType"
<*> o .:? "title"
<*> o .:? "icons" .!= []
-- | Resource template definition: a parameterized resource identified by an
-- RFC 6570 URI template.
data ResourceTemplateDefinition = ResourceTemplateDefinition
{ resourceTemplateURITemplate :: Text
, resourceTemplateName :: Text
, resourceTemplateDescription :: Maybe Text
, resourceTemplateMimeType :: Maybe Text
, resourceTemplateTitle :: Maybe Text
, resourceTemplateIcons :: [Icon] -- ^ omitted when empty (2025-11-25+)
} deriving (Show, Eq, Generic)
-- | A template definition with only the required fields set; record-update
-- the optional ones.
mkResourceTemplateDefinition :: Text -> Text -> ResourceTemplateDefinition
mkResourceTemplateDefinition uriTemplate name = ResourceTemplateDefinition
{ resourceTemplateURITemplate = uriTemplate
, resourceTemplateName = name
, resourceTemplateDescription = Nothing
, resourceTemplateMimeType = Nothing
, resourceTemplateTitle = Nothing
, resourceTemplateIcons = []
}
instance ToJSON ResourceTemplateDefinition where
toJSON def = object $
[ "uriTemplate" .= resourceTemplateURITemplate def
, "name" .= resourceTemplateName def
] ++
maybe [] (\d -> ["description" .= d]) (resourceTemplateDescription def) ++
maybe [] (\m -> ["mimeType" .= m]) (resourceTemplateMimeType def) ++
maybe [] (\t -> ["title" .= t]) (resourceTemplateTitle def) ++
(if null (resourceTemplateIcons def) then [] else ["icons" .= resourceTemplateIcons def])
-- | What a completion request is completing an argument for.
data CompletionRef
= CompletionRefPrompt Text -- ^ @ref/prompt@: a prompt, by name
| CompletionRefResource Text -- ^ @ref/resource@: a resource URI or URI template
deriving (Show, Eq, Generic)
-- | Completion suggestions for an argument value.
data CompletionResult = CompletionResult
{ completionValues :: [Text] -- ^ Suggestions ranked by relevance (max 100 are sent)
, completionTotal :: Maybe Int -- ^ Optional total number of matches
, completionHasMore :: Maybe Bool -- ^ Whether more results exist beyond 'completionValues'
} deriving (Show, Eq, Generic)
-- | A completion result with just the given suggestions.
completionResult :: [Text] -> CompletionResult
completionResult vals = CompletionResult
{ completionValues = vals
, completionTotal = Nothing
, completionHasMore = Nothing
}
-- | Tool definition (2025-06-18 enhanced)
data ToolDefinition = ToolDefinition
{ toolDefinitionName :: Text
, toolDefinitionDescription :: Text
, toolDefinitionInputSchema :: Schema
, toolDefinitionOutputSchema :: Maybe Schema
, toolDefinitionTitle :: Maybe Text -- New title field for human-friendly display
, toolDefinitionAnnotations :: Maybe ToolAnnotations -- ^ behavioral hints (2025-03-26+)
, toolDefinitionIcons :: [Icon] -- ^ omitted when empty (2025-11-25+)
} deriving (Show, Eq, Generic)
-- | A tool definition with only the required fields set; record-update the
-- optional ones.
mkToolDefinition :: Text -> Text -> Schema -> ToolDefinition
mkToolDefinition name description inputSchema = ToolDefinition
{ toolDefinitionName = name
, toolDefinitionDescription = description
, toolDefinitionInputSchema = inputSchema
, toolDefinitionOutputSchema = Nothing
, toolDefinitionTitle = Nothing
, toolDefinitionAnnotations = Nothing
, toolDefinitionIcons = []
}
instance ToJSON ToolDefinition where
toJSON def = object $
[ "name" .= toolDefinitionName def
, "description" .= toolDefinitionDescription def
, "inputSchema" .= toolDefinitionInputSchema def
] ++ maybe [] (\s -> ["outputSchema" .= s]) (toolDefinitionOutputSchema def)
++ maybe [] (\t -> ["title" .= t]) (toolDefinitionTitle def)
++ maybe [] (\a -> ["annotations" .= a]) (toolDefinitionAnnotations def)
++ (if null (toolDefinitionIcons def) then [] else ["icons" .= toolDefinitionIcons def])
-- | Argument definition for prompts
data ArgumentDefinition = ArgumentDefinition
{ argumentDefinitionName :: Text
, argumentDefinitionDescription :: Text
, argumentDefinitionRequired :: Bool
} deriving (Show, Eq, Generic)
instance ToJSON ArgumentDefinition where
toJSON def = object
[ "name" .= argumentDefinitionName def
, "description" .= argumentDefinitionDescription def
, "required" .= argumentDefinitionRequired def
]
-- | Server information
data McpServerInfo = McpServerInfo
{ serverName :: Text
, serverVersion :: Text
, serverInstructions :: Text
} deriving (Show, Eq, Generic)
-- | Individual capability objects
data PromptCapabilities = PromptCapabilities
{ promptListChanged :: Maybe Bool
} deriving (Show, Eq, Generic)
instance ToJSON PromptCapabilities where
toJSON caps = object $ catMaybes
[ fmap ("listChanged" .=) (promptListChanged caps)
]
data ResourceCapabilities = ResourceCapabilities
{ resourceSubscribe :: Maybe Bool
, resourceListChanged :: Maybe Bool
} deriving (Show, Eq, Generic)
instance ToJSON ResourceCapabilities where
toJSON caps = object $ catMaybes
[ fmap ("subscribe" .=) (resourceSubscribe caps)
, fmap ("listChanged" .=) (resourceListChanged caps)
]
data ToolCapabilities = ToolCapabilities
{ toolListChanged :: Maybe Bool
} deriving (Show, Eq, Generic)
instance ToJSON ToolCapabilities where
toJSON caps = object $ catMaybes
[ fmap ("listChanged" .=) (toolListChanged caps)
]
data CompletionCapabilities = CompletionCapabilities
{ -- No sub-capabilities defined
} deriving (Show, Eq, Generic)
instance ToJSON CompletionCapabilities where
toJSON _ = object []
data LoggingCapabilities = LoggingCapabilities
{ -- No specific sub-capabilities for logging yet
} deriving (Show, Eq, Generic)
instance ToJSON LoggingCapabilities where
toJSON _ = object []
-- | Server capabilities
data ServerCapabilities = ServerCapabilities
{ capabilityPrompts :: Maybe PromptCapabilities
, capabilityResources :: Maybe ResourceCapabilities
, capabilityTools :: Maybe ToolCapabilities
, capabilityCompletions :: Maybe CompletionCapabilities
, capabilityLogging :: Maybe LoggingCapabilities
} deriving (Show, Eq, Generic)
instance ToJSON ServerCapabilities where
toJSON caps = object $ catMaybes
[ fmap ("prompts" .=) (capabilityPrompts caps)
, fmap ("resources" .=) (capabilityResources caps)
, fmap ("tools" .=) (capabilityTools caps)
, fmap ("completions" .=) (capabilityCompletions caps)
, fmap ("logging" .=) (capabilityLogging caps)
]
-- | Per-request context passed to every handler, so handlers can behave
-- differently depending on who is calling.
data ClientContext = ClientContext
{ clientToken :: Maybe Text -- ^ Authenticated bearer token, if any.
, clientPrincipal :: Maybe Value -- ^ Application-defined principal returned by
-- the transport's authorization callback
-- (e.g. a role). 'Nothing' when
-- authentication is disabled.
, clientProtocolVersion :: Maybe Text
-- ^ The protocol revision the request declared in its @_meta@
-- (modern, 2026-07-28+ clients). 'Nothing' for legacy clients that
-- negotiated via @initialize@.
, clientInfo :: Maybe Value
-- ^ The client's self-reported identity from request @_meta@
-- (modern clients), for display/logging only.
, clientCapabilities :: Maybe Value
-- ^ The client's declared capabilities from request @_meta@
-- (modern clients).
, reportProgress :: Double -> Maybe Double -> Maybe Text -> IO ()
-- ^ Report progress for this request: current progress (which must
-- increase with each call), an optional total, and an optional
-- human-readable message. A no-op when the request carried no
-- @progressToken@, so handlers can call it unconditionally. Stop
-- reporting once the handler returns; avoid flooding.
, logToClient :: LogLevel -> Value -> IO ()
-- ^ Send a log message to this request's client
-- (@notifications\/message@). A no-op unless the request declared
-- @io.modelcontextprotocol\/logLevel@ (the spec forbids emitting
-- otherwise); messages below the declared level are dropped.
}
-- | A context carrying no transport- or request-level information: what
-- handlers see for legacy stdio requests.
anonymousContext :: ClientContext
anonymousContext = ClientContext
{ clientToken = Nothing
, clientPrincipal = Nothing
, clientProtocolVersion = Nothing
, clientInfo = Nothing
, clientCapabilities = Nothing
, reportProgress = \_ _ _ -> pure ()
, logToClient = \_ _ -> pure ()
}
-- | Cacheability hints stamped onto modern (2026-07-28+) list/read results,
-- which require @ttlMs@ and @cacheScope@ fields.
data CacheHints = CacheHints
{ cacheTtlMs :: Int -- ^ Freshness hint in milliseconds.
, cacheScopePublic :: Bool -- ^ 'True' allows shared intermediaries to
-- cache the response (@\"public\"@);
-- 'False' is @\"private\"@.
} deriving (Show, Eq)
-- | Conservative defaults: no caching (@ttlMs = 0@), private scope.
defaultCacheHints :: CacheHints
defaultCacheHints = CacheHints
{ cacheTtlMs = 0
, cacheScopePublic = False
}
-- | What change-notification delivery the serving transport offers, which
-- decides the @listChanged@\/@subscribe@ capabilities each era advertises.
data NotificationSupport = NotificationSupport
{ supportsLegacyPush :: Bool
-- ^ The transport can push unsolicited notifications to legacy
-- (initialize-handshake) clients — true for stdio with a configured
-- notification source; never for HTTP (this library dropped the
-- deprecated GET SSE stream legacy delivery relied on).
, supportsListen :: Bool
-- ^ @subscriptions\/listen@ (2026-07-28) is served.
} deriving (Show, Eq)
-- | No notification delivery at all: nothing extra is advertised.
noNotificationSupport :: NotificationSupport
noNotificationSupport = NotificationSupport
{ supportsLegacyPush = False
, supportsListen = False
}
-- | Handler type definitions. Every handler receives the request's
-- 'ClientContext' as its first argument.
--
-- Prompt arguments are string-valued per the MCP specification; tool
-- arguments are full JSON values.
type PromptListHandler = ClientContext -> IO [PromptDefinition]
type PromptGetHandler = ClientContext -> PromptName -> Map Text Text -> IO (Either Error PromptResult)
type ResourceListHandler = ClientContext -> IO [ResourceDefinition]
type ResourceReadHandler = ClientContext -> URI -> IO (Either Error ResourceContent)
type ResourceTemplateListHandler = ClientContext -> IO [ResourceTemplateDefinition]
type ToolListHandler = ClientContext -> IO [ToolDefinition]
type ToolCallHandler = ClientContext -> ToolName -> Map Text Value -> IO (Either Error ToolResult)
-- | Completion handler: given what is being completed ('CompletionRef'), the
-- argument name, the partial value typed so far, and any already-resolved
-- sibling arguments, produce ranked suggestions.
type CompletionHandler = ClientContext -> CompletionRef -> ArgumentName -> Text -> Map Text Text -> IO (Either Error CompletionResult)
-- | Server handlers
data McpServerHandlers = McpServerHandlers
{ prompts :: Maybe (PromptListHandler, PromptGetHandler)
, resources :: Maybe (ResourceListHandler, ResourceReadHandler)
, resourceTemplates :: Maybe ResourceTemplateListHandler
-- ^ Parameterized resources (@resources\/templates\/list@). Template
-- URIs are read through the 'ResourceReadHandler' in 'resources' —
-- configure both, or template reads have nothing to serve them
-- (a templates-only configuration still answers @resources\/list@
-- with an empty list so the advertised capability stays honest).
, tools :: Maybe (ToolListHandler, ToolCallHandler)
, completions :: Maybe CompletionHandler
-- ^ Argument autocompletion (@completion\/complete@) for prompts and
-- resource templates.
}
-- | Handlers for a server that supports nothing — record-update the features
-- you provide, so adding new handler slots to the library does not break
-- your construction.
noHandlers :: McpServerHandlers
noHandlers = McpServerHandlers
{ prompts = Nothing
, resources = Nothing
, resourceTemplates = Nothing
, tools = Nothing
, completions = Nothing
}