mcp 0.2.0.1 → 0.3.0.1
raw patch · 22 files changed
+3321/−4185 lines, 22 filesdep +attoparsecdep +hspecdep +hspec-waidep −asyncdep −base64-bytestringdep −cryptonitedep ~basedep ~bytestringdep ~containersPVP ok
version bump matches the API change (PVP)
Dependencies added: attoparsec, hspec, hspec-wai, http-media, mcp-types, process, wai-test
Dependencies removed: async, base64-bytestring, cryptonite, http-conduit, memory, optparse-applicative, random, scientific, servant-auth, stm, transformers, unordered-containers, uuid, warp
Dependency ranges changed: base, bytestring, containers, mtl, text, time
API changes (from Hackage documentation)
- MCP.Protocol: CallToolParams :: Text -> Maybe (Map Text Value) -> CallToolParams
- MCP.Protocol: CallToolReq :: CallToolRequest -> ClientRequest
- MCP.Protocol: CallToolRequest :: Text -> CallToolParams -> CallToolRequest
- MCP.Protocol: CallToolResult :: [Content] -> Maybe Bool -> Maybe Metadata -> CallToolResult
- MCP.Protocol: CancelledNotif :: CancelledNotification -> ClientNotification
- MCP.Protocol: CancelledNotification :: Text -> CancelledParams -> CancelledNotification
- MCP.Protocol: CancelledParams :: RequestId -> Maybe Text -> CancelledParams
- MCP.Protocol: CancelledServerNotif :: CancelledNotification -> ServerNotification
- MCP.Protocol: CompleteParams :: Reference -> CompletionArgument -> CompleteParams
- MCP.Protocol: CompleteReq :: CompleteRequest -> ClientRequest
- MCP.Protocol: CompleteRequest :: Text -> CompleteParams -> CompleteRequest
- MCP.Protocol: CompleteResult :: CompletionResult -> Maybe Metadata -> CompleteResult
- MCP.Protocol: CompletionArgument :: Text -> Text -> CompletionArgument
- MCP.Protocol: CompletionResult :: [Text] -> Maybe Int -> Maybe Bool -> CompletionResult
- MCP.Protocol: CreateMessageParams :: Int -> [SamplingMessage] -> Maybe ModelPreferences -> Maybe Text -> Maybe IncludeContext -> Maybe Double -> Maybe [Text] -> Maybe (Map Text Value) -> CreateMessageParams
- MCP.Protocol: CreateMessageReq :: CreateMessageRequest -> ServerRequest
- MCP.Protocol: CreateMessageRequest :: Text -> CreateMessageParams -> CreateMessageRequest
- MCP.Protocol: CreateMessageResult :: Role -> Content -> Text -> Maybe Text -> Maybe Metadata -> CreateMessageResult
- MCP.Protocol: ErrorMessage :: JSONRPCError -> JSONRPCMessage
- MCP.Protocol: GetPromptParams :: Text -> Maybe (Map Text Text) -> GetPromptParams
- MCP.Protocol: GetPromptReq :: GetPromptRequest -> ClientRequest
- MCP.Protocol: GetPromptRequest :: Text -> GetPromptParams -> GetPromptRequest
- MCP.Protocol: GetPromptResult :: Maybe Text -> [PromptMessage] -> Maybe Metadata -> GetPromptResult
- MCP.Protocol: InitializeParams :: Text -> ClientCapabilities -> Implementation -> InitializeParams
- MCP.Protocol: InitializeReq :: InitializeRequest -> ClientRequest
- MCP.Protocol: InitializeRequest :: Text -> InitializeParams -> InitializeRequest
- MCP.Protocol: InitializeResult :: Text -> ServerCapabilities -> Implementation -> Maybe Text -> Maybe Metadata -> InitializeResult
- MCP.Protocol: InitializedNotif :: InitializedNotification -> ClientNotification
- MCP.Protocol: InitializedNotification :: Text -> Maybe InitializedParams -> InitializedNotification
- MCP.Protocol: JSONRPCError :: Text -> RequestId -> JSONRPCErrorInfo -> JSONRPCError
- MCP.Protocol: JSONRPCErrorInfo :: Int -> Text -> Maybe Value -> JSONRPCErrorInfo
- MCP.Protocol: JSONRPCNotification :: Text -> Text -> Maybe Value -> JSONRPCNotification
- MCP.Protocol: JSONRPCRequest :: Text -> RequestId -> Text -> Maybe Value -> JSONRPCRequest
- MCP.Protocol: JSONRPCResponse :: Text -> RequestId -> Value -> JSONRPCResponse
- MCP.Protocol: ListPromptsReq :: ListPromptsRequest -> ClientRequest
- MCP.Protocol: ListPromptsRequest :: Text -> Maybe ListPromptsParams -> ListPromptsRequest
- MCP.Protocol: ListPromptsResult :: [Prompt] -> Maybe Cursor -> Maybe Metadata -> ListPromptsResult
- MCP.Protocol: ListResourceTemplatesReq :: ListResourceTemplatesRequest -> ClientRequest
- MCP.Protocol: ListResourceTemplatesRequest :: Text -> Maybe ListResourceTemplatesParams -> ListResourceTemplatesRequest
- MCP.Protocol: ListResourceTemplatesResult :: [ResourceTemplate] -> Maybe Cursor -> Maybe Metadata -> ListResourceTemplatesResult
- MCP.Protocol: ListResourcesReq :: ListResourcesRequest -> ClientRequest
- MCP.Protocol: ListResourcesRequest :: Text -> Maybe ListResourcesParams -> ListResourcesRequest
- MCP.Protocol: ListResourcesResult :: [Resource] -> Maybe Cursor -> Maybe Metadata -> ListResourcesResult
- MCP.Protocol: ListRootsReq :: ListRootsRequest -> ServerRequest
- MCP.Protocol: ListRootsRequest :: Text -> Maybe ListRootsParams -> ListRootsRequest
- MCP.Protocol: ListRootsResult :: [Root] -> Maybe Metadata -> ListRootsResult
- MCP.Protocol: ListToolsReq :: ListToolsRequest -> ClientRequest
- MCP.Protocol: ListToolsRequest :: Text -> Maybe ListToolsParams -> ListToolsRequest
- MCP.Protocol: ListToolsResult :: [Tool] -> Maybe Cursor -> Maybe Metadata -> ListToolsResult
- MCP.Protocol: LoggingMessageNotif :: LoggingMessageNotification -> ServerNotification
- MCP.Protocol: LoggingMessageNotification :: Text -> LoggingMessageParams -> LoggingMessageNotification
- MCP.Protocol: LoggingMessageParams :: LoggingLevel -> Value -> Maybe Text -> LoggingMessageParams
- MCP.Protocol: NotificationMessage :: JSONRPCNotification -> JSONRPCMessage
- MCP.Protocol: PingReq :: PingRequest -> ClientRequest
- MCP.Protocol: PingRequest :: Text -> Maybe PingParams -> PingRequest
- MCP.Protocol: PingServerReq :: PingRequest -> ServerRequest
- MCP.Protocol: ProgressNotif :: ProgressNotification -> ClientNotification
- MCP.Protocol: ProgressNotification :: Text -> ProgressParams -> ProgressNotification
- MCP.Protocol: ProgressParams :: ProgressToken -> Double -> Maybe Double -> Maybe Text -> ProgressParams
- MCP.Protocol: ProgressServerNotif :: ProgressNotification -> ServerNotification
- MCP.Protocol: PromptListChangedNotif :: PromptListChangedNotification -> ServerNotification
- MCP.Protocol: PromptListChangedNotification :: Text -> Maybe InitializedParams -> PromptListChangedNotification
- MCP.Protocol: PromptRef :: PromptReference -> Reference
- MCP.Protocol: ReadResourceReq :: ReadResourceRequest -> ClientRequest
- MCP.Protocol: ReadResourceRequest :: Text -> ReadResourceParams -> ReadResourceRequest
- MCP.Protocol: ReadResourceResult :: [ResourceContents] -> Maybe Metadata -> ReadResourceResult
- MCP.Protocol: RequestMessage :: JSONRPCRequest -> JSONRPCMessage
- MCP.Protocol: ResourceListChangedNotif :: ResourceListChangedNotification -> ServerNotification
- MCP.Protocol: ResourceListChangedNotification :: Text -> Maybe InitializedParams -> ResourceListChangedNotification
- MCP.Protocol: ResourceRef :: ResourceReference -> Reference
- MCP.Protocol: ResourceUpdatedNotif :: ResourceUpdatedNotification -> ServerNotification
- MCP.Protocol: ResourceUpdatedNotification :: Text -> ResourceUpdatedParams -> ResourceUpdatedNotification
- MCP.Protocol: ResourceUpdatedParams :: Text -> ResourceUpdatedParams
- MCP.Protocol: ResponseMessage :: JSONRPCResponse -> JSONRPCMessage
- MCP.Protocol: RootsListChangedNotif :: RootsListChangedNotification -> ClientNotification
- MCP.Protocol: RootsListChangedNotification :: Text -> Maybe InitializedParams -> RootsListChangedNotification
- MCP.Protocol: SetLevelReq :: SetLevelRequest -> ClientRequest
- MCP.Protocol: SetLevelRequest :: Text -> SetLevelParams -> SetLevelRequest
- MCP.Protocol: SubscribeReq :: SubscribeRequest -> ClientRequest
- MCP.Protocol: SubscribeRequest :: Text -> SubscribeParams -> SubscribeRequest
- MCP.Protocol: ToolListChangedNotif :: ToolListChangedNotification -> ServerNotification
- MCP.Protocol: ToolListChangedNotification :: Text -> Maybe InitializedParams -> ToolListChangedNotification
- MCP.Protocol: UnsubscribeReq :: UnsubscribeRequest -> ClientRequest
- MCP.Protocol: UnsubscribeRequest :: Text -> UnsubscribeParams -> UnsubscribeRequest
- MCP.Protocol: [$sel:_meta:CallToolResult] :: CallToolResult -> Maybe Metadata
- MCP.Protocol: [$sel:_meta:CompleteResult] :: CompleteResult -> Maybe Metadata
- MCP.Protocol: [$sel:_meta:CreateMessageResult] :: CreateMessageResult -> Maybe Metadata
- MCP.Protocol: [$sel:_meta:GetPromptResult] :: GetPromptResult -> Maybe Metadata
- MCP.Protocol: [$sel:_meta:InitializeResult] :: InitializeResult -> Maybe Metadata
- MCP.Protocol: [$sel:_meta:ListPromptsResult] :: ListPromptsResult -> Maybe Metadata
- MCP.Protocol: [$sel:_meta:ListResourceTemplatesResult] :: ListResourceTemplatesResult -> Maybe Metadata
- MCP.Protocol: [$sel:_meta:ListResourcesResult] :: ListResourcesResult -> Maybe Metadata
- MCP.Protocol: [$sel:_meta:ListRootsResult] :: ListRootsResult -> Maybe Metadata
- MCP.Protocol: [$sel:_meta:ListToolsResult] :: ListToolsResult -> Maybe Metadata
- MCP.Protocol: [$sel:_meta:ReadResourceResult] :: ReadResourceResult -> Maybe Metadata
- MCP.Protocol: [$sel:argument:CompleteParams] :: CompleteParams -> CompletionArgument
- MCP.Protocol: [$sel:arguments:CallToolParams] :: CallToolParams -> Maybe (Map Text Value)
- MCP.Protocol: [$sel:arguments:GetPromptParams] :: GetPromptParams -> Maybe (Map Text Text)
- MCP.Protocol: [$sel:capabilities:InitializeParams] :: InitializeParams -> ClientCapabilities
- MCP.Protocol: [$sel:capabilities:InitializeResult] :: InitializeResult -> ServerCapabilities
- MCP.Protocol: [$sel:clientInfo:InitializeParams] :: InitializeParams -> Implementation
- MCP.Protocol: [$sel:code:JSONRPCErrorInfo] :: JSONRPCErrorInfo -> Int
- MCP.Protocol: [$sel:completion:CompleteResult] :: CompleteResult -> CompletionResult
- MCP.Protocol: [$sel:content:CallToolResult] :: CallToolResult -> [Content]
- MCP.Protocol: [$sel:content:CreateMessageResult] :: CreateMessageResult -> Content
- MCP.Protocol: [$sel:contents:ReadResourceResult] :: ReadResourceResult -> [ResourceContents]
- MCP.Protocol: [$sel:data':LoggingMessageParams] :: LoggingMessageParams -> Value
- MCP.Protocol: [$sel:description:GetPromptResult] :: GetPromptResult -> Maybe Text
- MCP.Protocol: [$sel:error:JSONRPCError] :: JSONRPCError -> JSONRPCErrorInfo
- MCP.Protocol: [$sel:errorData:JSONRPCErrorInfo] :: JSONRPCErrorInfo -> Maybe Value
- MCP.Protocol: [$sel:hasMore:CompletionResult] :: CompletionResult -> Maybe Bool
- MCP.Protocol: [$sel:id:JSONRPCError] :: JSONRPCError -> RequestId
- MCP.Protocol: [$sel:id:JSONRPCRequest] :: JSONRPCRequest -> RequestId
- MCP.Protocol: [$sel:id:JSONRPCResponse] :: JSONRPCResponse -> RequestId
- MCP.Protocol: [$sel:includeContext:CreateMessageParams] :: CreateMessageParams -> Maybe IncludeContext
- MCP.Protocol: [$sel:instructions:InitializeResult] :: InitializeResult -> Maybe Text
- MCP.Protocol: [$sel:isError:CallToolResult] :: CallToolResult -> Maybe Bool
- MCP.Protocol: [$sel:jsonrpc:JSONRPCError] :: JSONRPCError -> Text
- MCP.Protocol: [$sel:jsonrpc:JSONRPCNotification] :: JSONRPCNotification -> Text
- MCP.Protocol: [$sel:jsonrpc:JSONRPCRequest] :: JSONRPCRequest -> Text
- MCP.Protocol: [$sel:jsonrpc:JSONRPCResponse] :: JSONRPCResponse -> Text
- MCP.Protocol: [$sel:level:LoggingMessageParams] :: LoggingMessageParams -> LoggingLevel
- MCP.Protocol: [$sel:logger:LoggingMessageParams] :: LoggingMessageParams -> Maybe Text
- MCP.Protocol: [$sel:maxTokens:CreateMessageParams] :: CreateMessageParams -> Int
- MCP.Protocol: [$sel:message:JSONRPCErrorInfo] :: JSONRPCErrorInfo -> Text
- MCP.Protocol: [$sel:message:ProgressParams] :: ProgressParams -> Maybe Text
- MCP.Protocol: [$sel:messages:CreateMessageParams] :: CreateMessageParams -> [SamplingMessage]
- MCP.Protocol: [$sel:messages:GetPromptResult] :: GetPromptResult -> [PromptMessage]
- MCP.Protocol: [$sel:metadata:CreateMessageParams] :: CreateMessageParams -> Maybe (Map Text Value)
- MCP.Protocol: [$sel:method:CallToolRequest] :: CallToolRequest -> Text
- MCP.Protocol: [$sel:method:CancelledNotification] :: CancelledNotification -> Text
- MCP.Protocol: [$sel:method:CompleteRequest] :: CompleteRequest -> Text
- MCP.Protocol: [$sel:method:CreateMessageRequest] :: CreateMessageRequest -> Text
- MCP.Protocol: [$sel:method:GetPromptRequest] :: GetPromptRequest -> Text
- MCP.Protocol: [$sel:method:InitializeRequest] :: InitializeRequest -> Text
- MCP.Protocol: [$sel:method:InitializedNotification] :: InitializedNotification -> Text
- MCP.Protocol: [$sel:method:JSONRPCNotification] :: JSONRPCNotification -> Text
- MCP.Protocol: [$sel:method:JSONRPCRequest] :: JSONRPCRequest -> Text
- MCP.Protocol: [$sel:method:ListPromptsRequest] :: ListPromptsRequest -> Text
- MCP.Protocol: [$sel:method:ListResourceTemplatesRequest] :: ListResourceTemplatesRequest -> Text
- MCP.Protocol: [$sel:method:ListResourcesRequest] :: ListResourcesRequest -> Text
- MCP.Protocol: [$sel:method:ListRootsRequest] :: ListRootsRequest -> Text
- MCP.Protocol: [$sel:method:ListToolsRequest] :: ListToolsRequest -> Text
- MCP.Protocol: [$sel:method:LoggingMessageNotification] :: LoggingMessageNotification -> Text
- MCP.Protocol: [$sel:method:PingRequest] :: PingRequest -> Text
- MCP.Protocol: [$sel:method:ProgressNotification] :: ProgressNotification -> Text
- MCP.Protocol: [$sel:method:PromptListChangedNotification] :: PromptListChangedNotification -> Text
- MCP.Protocol: [$sel:method:ReadResourceRequest] :: ReadResourceRequest -> Text
- MCP.Protocol: [$sel:method:ResourceListChangedNotification] :: ResourceListChangedNotification -> Text
- MCP.Protocol: [$sel:method:ResourceUpdatedNotification] :: ResourceUpdatedNotification -> Text
- MCP.Protocol: [$sel:method:RootsListChangedNotification] :: RootsListChangedNotification -> Text
- MCP.Protocol: [$sel:method:SetLevelRequest] :: SetLevelRequest -> Text
- MCP.Protocol: [$sel:method:SubscribeRequest] :: SubscribeRequest -> Text
- MCP.Protocol: [$sel:method:ToolListChangedNotification] :: ToolListChangedNotification -> Text
- MCP.Protocol: [$sel:method:UnsubscribeRequest] :: UnsubscribeRequest -> Text
- MCP.Protocol: [$sel:model:CreateMessageResult] :: CreateMessageResult -> Text
- MCP.Protocol: [$sel:modelPreferences:CreateMessageParams] :: CreateMessageParams -> Maybe ModelPreferences
- MCP.Protocol: [$sel:name:CallToolParams] :: CallToolParams -> Text
- MCP.Protocol: [$sel:name:CompletionArgument] :: CompletionArgument -> Text
- MCP.Protocol: [$sel:name:GetPromptParams] :: GetPromptParams -> Text
- MCP.Protocol: [$sel:nextCursor:ListPromptsResult] :: ListPromptsResult -> Maybe Cursor
- MCP.Protocol: [$sel:nextCursor:ListResourceTemplatesResult] :: ListResourceTemplatesResult -> Maybe Cursor
- MCP.Protocol: [$sel:nextCursor:ListResourcesResult] :: ListResourcesResult -> Maybe Cursor
- MCP.Protocol: [$sel:nextCursor:ListToolsResult] :: ListToolsResult -> Maybe Cursor
- MCP.Protocol: [$sel:params:CallToolRequest] :: CallToolRequest -> CallToolParams
- MCP.Protocol: [$sel:params:CancelledNotification] :: CancelledNotification -> CancelledParams
- MCP.Protocol: [$sel:params:CompleteRequest] :: CompleteRequest -> CompleteParams
- MCP.Protocol: [$sel:params:CreateMessageRequest] :: CreateMessageRequest -> CreateMessageParams
- MCP.Protocol: [$sel:params:GetPromptRequest] :: GetPromptRequest -> GetPromptParams
- MCP.Protocol: [$sel:params:InitializeRequest] :: InitializeRequest -> InitializeParams
- MCP.Protocol: [$sel:params:InitializedNotification] :: InitializedNotification -> Maybe InitializedParams
- MCP.Protocol: [$sel:params:JSONRPCNotification] :: JSONRPCNotification -> Maybe Value
- MCP.Protocol: [$sel:params:JSONRPCRequest] :: JSONRPCRequest -> Maybe Value
- MCP.Protocol: [$sel:params:ListPromptsRequest] :: ListPromptsRequest -> Maybe ListPromptsParams
- MCP.Protocol: [$sel:params:ListResourceTemplatesRequest] :: ListResourceTemplatesRequest -> Maybe ListResourceTemplatesParams
- MCP.Protocol: [$sel:params:ListResourcesRequest] :: ListResourcesRequest -> Maybe ListResourcesParams
- MCP.Protocol: [$sel:params:ListRootsRequest] :: ListRootsRequest -> Maybe ListRootsParams
- MCP.Protocol: [$sel:params:ListToolsRequest] :: ListToolsRequest -> Maybe ListToolsParams
- MCP.Protocol: [$sel:params:LoggingMessageNotification] :: LoggingMessageNotification -> LoggingMessageParams
- MCP.Protocol: [$sel:params:PingRequest] :: PingRequest -> Maybe PingParams
- MCP.Protocol: [$sel:params:ProgressNotification] :: ProgressNotification -> ProgressParams
- MCP.Protocol: [$sel:params:PromptListChangedNotification] :: PromptListChangedNotification -> Maybe InitializedParams
- MCP.Protocol: [$sel:params:ReadResourceRequest] :: ReadResourceRequest -> ReadResourceParams
- MCP.Protocol: [$sel:params:ResourceListChangedNotification] :: ResourceListChangedNotification -> Maybe InitializedParams
- MCP.Protocol: [$sel:params:ResourceUpdatedNotification] :: ResourceUpdatedNotification -> ResourceUpdatedParams
- MCP.Protocol: [$sel:params:RootsListChangedNotification] :: RootsListChangedNotification -> Maybe InitializedParams
- MCP.Protocol: [$sel:params:SetLevelRequest] :: SetLevelRequest -> SetLevelParams
- MCP.Protocol: [$sel:params:SubscribeRequest] :: SubscribeRequest -> SubscribeParams
- MCP.Protocol: [$sel:params:ToolListChangedNotification] :: ToolListChangedNotification -> Maybe InitializedParams
- MCP.Protocol: [$sel:params:UnsubscribeRequest] :: UnsubscribeRequest -> UnsubscribeParams
- MCP.Protocol: [$sel:progress:ProgressParams] :: ProgressParams -> Double
- MCP.Protocol: [$sel:progressToken:ProgressParams] :: ProgressParams -> ProgressToken
- MCP.Protocol: [$sel:prompts:ListPromptsResult] :: ListPromptsResult -> [Prompt]
- MCP.Protocol: [$sel:protocolVersion:InitializeParams] :: InitializeParams -> Text
- MCP.Protocol: [$sel:protocolVersion:InitializeResult] :: InitializeResult -> Text
- MCP.Protocol: [$sel:reason:CancelledParams] :: CancelledParams -> Maybe Text
- MCP.Protocol: [$sel:ref:CompleteParams] :: CompleteParams -> Reference
- MCP.Protocol: [$sel:requestId:CancelledParams] :: CancelledParams -> RequestId
- MCP.Protocol: [$sel:resourceTemplates:ListResourceTemplatesResult] :: ListResourceTemplatesResult -> [ResourceTemplate]
- MCP.Protocol: [$sel:resources:ListResourcesResult] :: ListResourcesResult -> [Resource]
- MCP.Protocol: [$sel:result:JSONRPCResponse] :: JSONRPCResponse -> Value
- MCP.Protocol: [$sel:role:CreateMessageResult] :: CreateMessageResult -> Role
- MCP.Protocol: [$sel:roots:ListRootsResult] :: ListRootsResult -> [Root]
- MCP.Protocol: [$sel:serverInfo:InitializeResult] :: InitializeResult -> Implementation
- MCP.Protocol: [$sel:stopReason:CreateMessageResult] :: CreateMessageResult -> Maybe Text
- MCP.Protocol: [$sel:stopSequences:CreateMessageParams] :: CreateMessageParams -> Maybe [Text]
- MCP.Protocol: [$sel:systemPrompt:CreateMessageParams] :: CreateMessageParams -> Maybe Text
- MCP.Protocol: [$sel:temperature:CreateMessageParams] :: CreateMessageParams -> Maybe Double
- MCP.Protocol: [$sel:tools:ListToolsResult] :: ListToolsResult -> [Tool]
- MCP.Protocol: [$sel:total:CompletionResult] :: CompletionResult -> Maybe Int
- MCP.Protocol: [$sel:total:ProgressParams] :: ProgressParams -> Maybe Double
- MCP.Protocol: [$sel:uri:ResourceUpdatedParams] :: ResourceUpdatedParams -> Text
- MCP.Protocol: [$sel:value:CompletionArgument] :: CompletionArgument -> Text
- MCP.Protocol: [$sel:values:CompletionResult] :: CompletionResult -> [Text]
- MCP.Protocol: [InitializedParams] :: Maybe Metadata -> InitializedParams
- MCP.Protocol: [ListPromptsParams] :: Maybe Cursor -> ListPromptsParams
- MCP.Protocol: [ListResourceTemplatesParams] :: Maybe Cursor -> ListResourceTemplatesParams
- MCP.Protocol: [ListResourcesParams] :: Maybe Cursor -> ListResourcesParams
- MCP.Protocol: [ListRootsParams] :: Maybe Metadata -> ListRootsParams
- MCP.Protocol: [ListToolsParams] :: Maybe Cursor -> ListToolsParams
- MCP.Protocol: [PingParams] :: Maybe Metadata -> PingParams
- MCP.Protocol: [ReadResourceParams] :: Text -> ReadResourceParams
- MCP.Protocol: [SetLevelParams] :: LoggingLevel -> SetLevelParams
- MCP.Protocol: [SubscribeParams] :: Text -> SubscribeParams
- MCP.Protocol: [UnsubscribeParams] :: Text -> UnsubscribeParams
- MCP.Protocol: data CallToolParams
- MCP.Protocol: data CallToolRequest
- MCP.Protocol: data CallToolResult
- MCP.Protocol: data CancelledNotification
- MCP.Protocol: data CancelledParams
- MCP.Protocol: data ClientNotification
- MCP.Protocol: data ClientRequest
- MCP.Protocol: data CompleteParams
- MCP.Protocol: data CompleteRequest
- MCP.Protocol: data CompleteResult
- MCP.Protocol: data CompletionArgument
- MCP.Protocol: data CompletionResult
- MCP.Protocol: data CreateMessageParams
- MCP.Protocol: data CreateMessageRequest
- MCP.Protocol: data CreateMessageResult
- MCP.Protocol: data GetPromptParams
- MCP.Protocol: data GetPromptRequest
- MCP.Protocol: data GetPromptResult
- MCP.Protocol: data InitializeParams
- MCP.Protocol: data InitializeRequest
- MCP.Protocol: data InitializeResult
- MCP.Protocol: data InitializedNotification
- MCP.Protocol: data InitializedParams
- MCP.Protocol: data JSONRPCError
- MCP.Protocol: data JSONRPCErrorInfo
- MCP.Protocol: data JSONRPCMessage
- MCP.Protocol: data JSONRPCNotification
- MCP.Protocol: data JSONRPCRequest
- MCP.Protocol: data JSONRPCResponse
- MCP.Protocol: data ListPromptsParams
- MCP.Protocol: data ListPromptsRequest
- MCP.Protocol: data ListPromptsResult
- MCP.Protocol: data ListResourceTemplatesParams
- MCP.Protocol: data ListResourceTemplatesRequest
- MCP.Protocol: data ListResourceTemplatesResult
- MCP.Protocol: data ListResourcesParams
- MCP.Protocol: data ListResourcesRequest
- MCP.Protocol: data ListResourcesResult
- MCP.Protocol: data ListRootsParams
- MCP.Protocol: data ListRootsRequest
- MCP.Protocol: data ListRootsResult
- MCP.Protocol: data ListToolsParams
- MCP.Protocol: data ListToolsRequest
- MCP.Protocol: data ListToolsResult
- MCP.Protocol: data LoggingMessageNotification
- MCP.Protocol: data LoggingMessageParams
- MCP.Protocol: data PingParams
- MCP.Protocol: data PingRequest
- MCP.Protocol: data ProgressNotification
- MCP.Protocol: data ProgressParams
- MCP.Protocol: data PromptListChangedNotification
- MCP.Protocol: data ReadResourceParams
- MCP.Protocol: data ReadResourceRequest
- MCP.Protocol: data ReadResourceResult
- MCP.Protocol: data Reference
- MCP.Protocol: data ResourceListChangedNotification
- MCP.Protocol: data ResourceUpdatedNotification
- MCP.Protocol: data ResourceUpdatedParams
- MCP.Protocol: data RootsListChangedNotification
- MCP.Protocol: data ServerNotification
- MCP.Protocol: data ServerRequest
- MCP.Protocol: data SetLevelParams
- MCP.Protocol: data SetLevelRequest
- MCP.Protocol: data SubscribeParams
- MCP.Protocol: data SubscribeRequest
- MCP.Protocol: data ToolListChangedNotification
- MCP.Protocol: data UnsubscribeParams
- MCP.Protocol: data UnsubscribeRequest
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.CallToolParams
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.CallToolRequest
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.CallToolResult
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.CancelledNotification
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.CancelledParams
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.ClientNotification
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.ClientRequest
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.CompleteParams
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.CompleteRequest
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.CompleteResult
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.CompletionArgument
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.CompletionResult
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.CreateMessageParams
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.CreateMessageRequest
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.CreateMessageResult
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.GetPromptParams
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.GetPromptRequest
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.GetPromptResult
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.InitializeParams
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.InitializeRequest
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.InitializeResult
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.InitializedNotification
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.InitializedParams
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.JSONRPCError
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.JSONRPCErrorInfo
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.JSONRPCMessage
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.JSONRPCNotification
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.JSONRPCRequest
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.JSONRPCResponse
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.ListPromptsParams
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.ListPromptsRequest
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.ListPromptsResult
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.ListResourceTemplatesParams
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.ListResourceTemplatesRequest
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.ListResourceTemplatesResult
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.ListResourcesParams
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.ListResourcesRequest
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.ListResourcesResult
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.ListRootsParams
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.ListRootsRequest
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.ListRootsResult
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.ListToolsParams
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.ListToolsRequest
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.ListToolsResult
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.LoggingMessageNotification
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.LoggingMessageParams
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.PingParams
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.PingRequest
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.ProgressNotification
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.ProgressParams
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.PromptListChangedNotification
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.ReadResourceParams
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.ReadResourceRequest
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.ReadResourceResult
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.Reference
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.ResourceListChangedNotification
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.ResourceUpdatedNotification
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.ResourceUpdatedParams
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.RootsListChangedNotification
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.ServerNotification
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.ServerRequest
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.SetLevelParams
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.SetLevelRequest
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.SubscribeParams
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.SubscribeRequest
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.ToolListChangedNotification
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.UnsubscribeParams
- MCP.Protocol: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Protocol.UnsubscribeRequest
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.CallToolParams
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.CallToolRequest
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.CallToolResult
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.CancelledNotification
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.CancelledParams
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.ClientNotification
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.ClientRequest
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.CompleteParams
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.CompleteRequest
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.CompleteResult
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.CompletionArgument
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.CompletionResult
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.CreateMessageParams
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.CreateMessageRequest
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.CreateMessageResult
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.GetPromptParams
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.GetPromptRequest
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.GetPromptResult
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.InitializeParams
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.InitializeRequest
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.InitializeResult
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.InitializedNotification
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.InitializedParams
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.JSONRPCError
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.JSONRPCErrorInfo
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.JSONRPCMessage
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.JSONRPCNotification
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.JSONRPCRequest
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.JSONRPCResponse
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.ListPromptsParams
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.ListPromptsRequest
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.ListPromptsResult
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.ListResourceTemplatesParams
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.ListResourceTemplatesRequest
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.ListResourceTemplatesResult
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.ListResourcesParams
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.ListResourcesRequest
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.ListResourcesResult
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.ListRootsParams
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.ListRootsRequest
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.ListRootsResult
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.ListToolsParams
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.ListToolsRequest
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.ListToolsResult
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.LoggingMessageNotification
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.LoggingMessageParams
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.PingParams
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.PingRequest
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.ProgressNotification
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.ProgressParams
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.PromptListChangedNotification
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.ReadResourceParams
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.ReadResourceRequest
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.ReadResourceResult
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.Reference
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.ResourceListChangedNotification
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.ResourceUpdatedNotification
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.ResourceUpdatedParams
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.RootsListChangedNotification
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.ServerNotification
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.ServerRequest
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.SetLevelParams
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.SetLevelRequest
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.SubscribeParams
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.SubscribeRequest
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.ToolListChangedNotification
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.UnsubscribeParams
- MCP.Protocol: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Protocol.UnsubscribeRequest
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.CallToolParams
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.CallToolRequest
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.CallToolResult
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.CancelledNotification
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.CancelledParams
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.ClientNotification
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.ClientRequest
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.CompleteParams
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.CompleteRequest
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.CompleteResult
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.CompletionArgument
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.CompletionResult
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.CreateMessageParams
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.CreateMessageRequest
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.CreateMessageResult
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.GetPromptParams
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.GetPromptRequest
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.GetPromptResult
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.InitializeParams
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.InitializeRequest
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.InitializeResult
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.InitializedNotification
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.InitializedParams
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.JSONRPCError
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.JSONRPCErrorInfo
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.JSONRPCMessage
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.JSONRPCNotification
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.JSONRPCRequest
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.JSONRPCResponse
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.ListPromptsParams
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.ListPromptsRequest
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.ListPromptsResult
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.ListResourceTemplatesParams
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.ListResourceTemplatesRequest
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.ListResourceTemplatesResult
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.ListResourcesParams
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.ListResourcesRequest
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.ListResourcesResult
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.ListRootsParams
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.ListRootsRequest
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.ListRootsResult
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.ListToolsParams
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.ListToolsRequest
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.ListToolsResult
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.LoggingMessageNotification
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.LoggingMessageParams
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.PingParams
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.PingRequest
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.ProgressNotification
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.ProgressParams
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.PromptListChangedNotification
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.ReadResourceParams
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.ReadResourceRequest
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.ReadResourceResult
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.Reference
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.ResourceListChangedNotification
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.ResourceUpdatedNotification
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.ResourceUpdatedParams
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.RootsListChangedNotification
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.ServerNotification
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.ServerRequest
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.SetLevelParams
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.SetLevelRequest
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.SubscribeParams
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.SubscribeRequest
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.ToolListChangedNotification
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.UnsubscribeParams
- MCP.Protocol: instance GHC.Classes.Eq MCP.Protocol.UnsubscribeRequest
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.CallToolParams
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.CallToolRequest
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.CallToolResult
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.CancelledNotification
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.CancelledParams
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.ClientNotification
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.ClientRequest
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.CompleteParams
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.CompleteRequest
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.CompleteResult
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.CompletionArgument
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.CompletionResult
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.CreateMessageParams
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.CreateMessageRequest
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.CreateMessageResult
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.GetPromptParams
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.GetPromptRequest
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.GetPromptResult
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.InitializeParams
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.InitializeRequest
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.InitializeResult
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.InitializedNotification
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.InitializedParams
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.JSONRPCError
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.JSONRPCErrorInfo
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.JSONRPCMessage
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.JSONRPCNotification
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.JSONRPCRequest
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.JSONRPCResponse
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.ListPromptsParams
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.ListPromptsRequest
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.ListPromptsResult
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.ListResourceTemplatesParams
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.ListResourceTemplatesRequest
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.ListResourceTemplatesResult
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.ListResourcesParams
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.ListResourcesRequest
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.ListResourcesResult
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.ListRootsParams
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.ListRootsRequest
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.ListRootsResult
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.ListToolsParams
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.ListToolsRequest
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.ListToolsResult
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.LoggingMessageNotification
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.LoggingMessageParams
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.PingParams
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.PingRequest
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.ProgressNotification
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.ProgressParams
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.PromptListChangedNotification
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.ReadResourceParams
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.ReadResourceRequest
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.ReadResourceResult
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.Reference
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.ResourceListChangedNotification
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.ResourceUpdatedNotification
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.ResourceUpdatedParams
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.RootsListChangedNotification
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.ServerNotification
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.ServerRequest
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.SetLevelParams
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.SetLevelRequest
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.SubscribeParams
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.SubscribeRequest
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.ToolListChangedNotification
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.UnsubscribeParams
- MCP.Protocol: instance GHC.Generics.Generic MCP.Protocol.UnsubscribeRequest
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.CallToolParams
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.CallToolRequest
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.CallToolResult
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.CancelledNotification
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.CancelledParams
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.ClientNotification
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.ClientRequest
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.CompleteParams
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.CompleteRequest
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.CompleteResult
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.CompletionArgument
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.CompletionResult
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.CreateMessageParams
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.CreateMessageRequest
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.CreateMessageResult
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.GetPromptParams
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.GetPromptRequest
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.GetPromptResult
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.InitializeParams
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.InitializeRequest
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.InitializeResult
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.InitializedNotification
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.InitializedParams
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.JSONRPCError
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.JSONRPCErrorInfo
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.JSONRPCMessage
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.JSONRPCNotification
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.JSONRPCRequest
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.JSONRPCResponse
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.ListPromptsParams
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.ListPromptsRequest
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.ListPromptsResult
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.ListResourceTemplatesParams
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.ListResourceTemplatesRequest
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.ListResourceTemplatesResult
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.ListResourcesParams
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.ListResourcesRequest
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.ListResourcesResult
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.ListRootsParams
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.ListRootsRequest
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.ListRootsResult
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.ListToolsParams
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.ListToolsRequest
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.ListToolsResult
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.LoggingMessageNotification
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.LoggingMessageParams
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.PingParams
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.PingRequest
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.ProgressNotification
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.ProgressParams
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.PromptListChangedNotification
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.ReadResourceParams
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.ReadResourceRequest
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.ReadResourceResult
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.Reference
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.ResourceListChangedNotification
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.ResourceUpdatedNotification
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.ResourceUpdatedParams
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.RootsListChangedNotification
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.ServerNotification
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.ServerRequest
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.SetLevelParams
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.SetLevelRequest
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.SubscribeParams
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.SubscribeRequest
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.ToolListChangedNotification
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.UnsubscribeParams
- MCP.Protocol: instance GHC.Show.Show MCP.Protocol.UnsubscribeRequest
- MCP.Server: ServerConfig :: Handle -> Handle -> Implementation -> ServerCapabilities -> ServerConfig
- MCP.Server: ServerState :: Bool -> ServerCapabilities -> Maybe ClientCapabilities -> Maybe Implementation -> Map Text () -> ServerState
- MCP.Server: [$sel:clientCapabilities:ServerState] :: ServerState -> Maybe ClientCapabilities
- MCP.Server: [$sel:configCapabilities:ServerConfig] :: ServerConfig -> ServerCapabilities
- MCP.Server: [$sel:configInput:ServerConfig] :: ServerConfig -> Handle
- MCP.Server: [$sel:configOutput:ServerConfig] :: ServerConfig -> Handle
- MCP.Server: [$sel:configServerInfo:ServerConfig] :: ServerConfig -> Implementation
- MCP.Server: [$sel:serverCapabilities:ServerState] :: ServerState -> ServerCapabilities
- MCP.Server: [$sel:serverInfo:ServerState] :: ServerState -> Maybe Implementation
- MCP.Server: [$sel:serverInitialized:ServerState] :: ServerState -> Bool
- MCP.Server: [$sel:subscriptions:ServerState] :: ServerState -> Map Text ()
- MCP.Server: class (Monad m) => MCPServer m
- MCP.Server: data ServerConfig
- MCP.Server: data ServerState
- MCP.Server: handleCallTool :: MCPServer m => CallToolParams -> m CallToolResult
- MCP.Server: handleComplete :: MCPServer m => CompleteParams -> m CompleteResult
- MCP.Server: handleGetPrompt :: MCPServer m => GetPromptParams -> m GetPromptResult
- MCP.Server: handleListPrompts :: MCPServer m => ListPromptsParams -> m ListPromptsResult
- MCP.Server: handleListResourceTemplates :: MCPServer m => ListResourceTemplatesParams -> m ListResourceTemplatesResult
- MCP.Server: handleListResources :: MCPServer m => ListResourcesParams -> m ListResourcesResult
- MCP.Server: handleListTools :: MCPServer m => ListToolsParams -> m ListToolsResult
- MCP.Server: handleReadResource :: MCPServer m => ReadResourceParams -> m ReadResourceResult
- MCP.Server: handleSetLevel :: MCPServer m => SetLevelParams -> m ()
- MCP.Server: initialServerState :: ServerCapabilities -> ServerState
- MCP.Server: instance GHC.Show.Show MCP.Server.ServerConfig
- MCP.Server: instance GHC.Show.Show MCP.Server.ServerState
- MCP.Server: runMCPServer :: ServerConfig -> ServerState -> MCPServerM a -> IO (Either Text (a, ServerState))
- MCP.Server: sendError :: MonadIO m => Handle -> RequestId -> JSONRPCErrorInfo -> m ()
- MCP.Server: sendNotification :: (MonadIO m, ToJSON a) => Handle -> Text -> a -> m ()
- MCP.Server: sendResponse :: (MonadIO m, ToJSON a) => Handle -> RequestId -> a -> m ()
- MCP.Server: type MCPServerM = ReaderT ServerConfig (StateT ServerState (ExceptT Text IO))
- MCP.Server.Auth: AuthorizationCode :: OAuthGrantType
- MCP.Server.Auth: ClientCredentials :: OAuthGrantType
- MCP.Server.Auth: OAuthConfig :: Bool -> [OAuthProvider] -> Maybe Text -> Bool -> Int -> Int -> [Text] -> [Text] -> [Text] -> [Text] -> [Text] -> Bool -> Maybe Text -> Text -> Text -> Maybe Text -> Text -> Text -> Text -> Maybe Text -> OAuthConfig
- MCP.Server.Auth: OAuthMetadata :: Text -> Text -> Text -> Maybe Text -> Maybe Text -> Maybe Text -> Maybe [Text] -> [Text] -> Maybe [Text] -> Maybe [Text] -> Maybe [Text] -> OAuthMetadata
- MCP.Server.Auth: OAuthProvider :: Text -> Text -> Maybe Text -> Text -> Text -> Maybe Text -> [Text] -> [OAuthGrantType] -> Bool -> Maybe Text -> OAuthProvider
- MCP.Server.Auth: PKCEChallenge :: Text -> Text -> Text -> PKCEChallenge
- MCP.Server.Auth: TokenInfo :: Bool -> Maybe Text -> Maybe Text -> Maybe Text -> Maybe Text -> Maybe Integer -> Maybe Integer -> Maybe Integer -> Maybe Text -> Maybe [Text] -> Maybe Text -> TokenInfo
- MCP.Server.Auth: [$sel:accessTokenExpirySeconds:OAuthConfig] :: OAuthConfig -> Int
- MCP.Server.Auth: [$sel:active:TokenInfo] :: TokenInfo -> Bool
- MCP.Server.Auth: [$sel:aud:TokenInfo] :: TokenInfo -> Maybe [Text]
- MCP.Server.Auth: [$sel:authCodeExpirySeconds:OAuthConfig] :: OAuthConfig -> Int
- MCP.Server.Auth: [$sel:authCodePrefix:OAuthConfig] :: OAuthConfig -> Text
- MCP.Server.Auth: [$sel:authorizationEndpoint:OAuthMetadata] :: OAuthMetadata -> Text
- MCP.Server.Auth: [$sel:authorizationEndpoint:OAuthProvider] :: OAuthProvider -> Text
- MCP.Server.Auth: [$sel:authorizationSuccessTemplate:OAuthConfig] :: OAuthConfig -> Maybe Text
- MCP.Server.Auth: [$sel:autoApproveAuth:OAuthConfig] :: OAuthConfig -> Bool
- MCP.Server.Auth: [$sel:challengeMethod:PKCEChallenge] :: PKCEChallenge -> Text
- MCP.Server.Auth: [$sel:clientId:OAuthProvider] :: OAuthProvider -> Text
- MCP.Server.Auth: [$sel:clientId:TokenInfo] :: TokenInfo -> Maybe Text
- MCP.Server.Auth: [$sel:clientIdPrefix:OAuthConfig] :: OAuthConfig -> Text
- MCP.Server.Auth: [$sel:clientSecret:OAuthProvider] :: OAuthProvider -> Maybe Text
- MCP.Server.Auth: [$sel:codeChallenge:PKCEChallenge] :: PKCEChallenge -> Text
- MCP.Server.Auth: [$sel:codeChallengeMethodsSupported:OAuthMetadata] :: OAuthMetadata -> Maybe [Text]
- MCP.Server.Auth: [$sel:codeVerifier:PKCEChallenge] :: PKCEChallenge -> Text
- MCP.Server.Auth: [$sel:demoEmailDomain:OAuthConfig] :: OAuthConfig -> Text
- MCP.Server.Auth: [$sel:demoUserIdTemplate:OAuthConfig] :: OAuthConfig -> Maybe Text
- MCP.Server.Auth: [$sel:demoUserName:OAuthConfig] :: OAuthConfig -> Text
- MCP.Server.Auth: [$sel:exp:TokenInfo] :: TokenInfo -> Maybe Integer
- MCP.Server.Auth: [$sel:grantTypes:OAuthProvider] :: OAuthProvider -> [OAuthGrantType]
- MCP.Server.Auth: [$sel:grantTypesSupported:OAuthMetadata] :: OAuthMetadata -> Maybe [Text]
- MCP.Server.Auth: [$sel:iat:TokenInfo] :: TokenInfo -> Maybe Integer
- MCP.Server.Auth: [$sel:iss:TokenInfo] :: TokenInfo -> Maybe Text
- MCP.Server.Auth: [$sel:issuer:OAuthMetadata] :: OAuthMetadata -> Text
- MCP.Server.Auth: [$sel:jwksUri:OAuthMetadata] :: OAuthMetadata -> Maybe Text
- MCP.Server.Auth: [$sel:metadataEndpoint:OAuthProvider] :: OAuthProvider -> Maybe Text
- MCP.Server.Auth: [$sel:nbf:TokenInfo] :: TokenInfo -> Maybe Integer
- MCP.Server.Auth: [$sel:oauthEnabled:OAuthConfig] :: OAuthConfig -> Bool
- MCP.Server.Auth: [$sel:oauthProviders:OAuthConfig] :: OAuthConfig -> [OAuthProvider]
- MCP.Server.Auth: [$sel:providerName:OAuthProvider] :: OAuthProvider -> Text
- MCP.Server.Auth: [$sel:publicClientSecret:OAuthConfig] :: OAuthConfig -> Maybe Text
- MCP.Server.Auth: [$sel:refreshTokenPrefix:OAuthConfig] :: OAuthConfig -> Text
- MCP.Server.Auth: [$sel:registrationEndpoint:OAuthMetadata] :: OAuthMetadata -> Maybe Text
- MCP.Server.Auth: [$sel:requireHTTPS:OAuthConfig] :: OAuthConfig -> Bool
- MCP.Server.Auth: [$sel:requiresPKCE:OAuthProvider] :: OAuthProvider -> Bool
- MCP.Server.Auth: [$sel:responseTypesSupported:OAuthMetadata] :: OAuthMetadata -> [Text]
- MCP.Server.Auth: [$sel:scope:TokenInfo] :: TokenInfo -> Maybe Text
- MCP.Server.Auth: [$sel:scopes:OAuthProvider] :: OAuthProvider -> [Text]
- MCP.Server.Auth: [$sel:scopesSupported:OAuthMetadata] :: OAuthMetadata -> Maybe [Text]
- MCP.Server.Auth: [$sel:sub:TokenInfo] :: TokenInfo -> Maybe Text
- MCP.Server.Auth: [$sel:supportedAuthMethods:OAuthConfig] :: OAuthConfig -> [Text]
- MCP.Server.Auth: [$sel:supportedCodeChallengeMethods:OAuthConfig] :: OAuthConfig -> [Text]
- MCP.Server.Auth: [$sel:supportedGrantTypes:OAuthConfig] :: OAuthConfig -> [Text]
- MCP.Server.Auth: [$sel:supportedResponseTypes:OAuthConfig] :: OAuthConfig -> [Text]
- MCP.Server.Auth: [$sel:supportedScopes:OAuthConfig] :: OAuthConfig -> [Text]
- MCP.Server.Auth: [$sel:tokenEndpoint:OAuthMetadata] :: OAuthMetadata -> Text
- MCP.Server.Auth: [$sel:tokenEndpoint:OAuthProvider] :: OAuthProvider -> Text
- MCP.Server.Auth: [$sel:tokenEndpointAuthMethodsSupported:OAuthMetadata] :: OAuthMetadata -> Maybe [Text]
- MCP.Server.Auth: [$sel:tokenType:TokenInfo] :: TokenInfo -> Maybe Text
- MCP.Server.Auth: [$sel:tokenValidationEndpoint:OAuthConfig] :: OAuthConfig -> Maybe Text
- MCP.Server.Auth: [$sel:userInfoEndpoint:OAuthMetadata] :: OAuthMetadata -> Maybe Text
- MCP.Server.Auth: [$sel:userInfoEndpoint:OAuthProvider] :: OAuthProvider -> Maybe Text
- MCP.Server.Auth: [$sel:username:TokenInfo] :: TokenInfo -> Maybe Text
- MCP.Server.Auth: data OAuthConfig
- MCP.Server.Auth: data OAuthGrantType
- MCP.Server.Auth: data OAuthMetadata
- MCP.Server.Auth: data OAuthProvider
- MCP.Server.Auth: data PKCEChallenge
- MCP.Server.Auth: data TokenInfo
- MCP.Server.Auth: discoverOAuthMetadata :: MonadIO m => Text -> m (Either String OAuthMetadata)
- MCP.Server.Auth: extractBearerToken :: Text -> Maybe Text
- MCP.Server.Auth: generateCodeChallenge :: Text -> Text
- MCP.Server.Auth: generateCodeVerifier :: IO Text
- MCP.Server.Auth: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Server.Auth.OAuthMetadata
- MCP.Server.Auth: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Server.Auth.TokenInfo
- MCP.Server.Auth: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Server.Auth.OAuthMetadata
- MCP.Server.Auth: instance GHC.Classes.Eq MCP.Server.Auth.OAuthGrantType
- MCP.Server.Auth: instance GHC.Generics.Generic MCP.Server.Auth.OAuthConfig
- MCP.Server.Auth: instance GHC.Generics.Generic MCP.Server.Auth.OAuthGrantType
- MCP.Server.Auth: instance GHC.Generics.Generic MCP.Server.Auth.OAuthMetadata
- MCP.Server.Auth: instance GHC.Generics.Generic MCP.Server.Auth.OAuthProvider
- MCP.Server.Auth: instance GHC.Generics.Generic MCP.Server.Auth.PKCEChallenge
- MCP.Server.Auth: instance GHC.Generics.Generic MCP.Server.Auth.TokenInfo
- MCP.Server.Auth: instance GHC.Show.Show MCP.Server.Auth.OAuthConfig
- MCP.Server.Auth: instance GHC.Show.Show MCP.Server.Auth.OAuthGrantType
- MCP.Server.Auth: instance GHC.Show.Show MCP.Server.Auth.OAuthMetadata
- MCP.Server.Auth: instance GHC.Show.Show MCP.Server.Auth.OAuthProvider
- MCP.Server.Auth: instance GHC.Show.Show MCP.Server.Auth.PKCEChallenge
- MCP.Server.Auth: instance GHC.Show.Show MCP.Server.Auth.TokenInfo
- MCP.Server.Auth: validateBearerToken :: MonadIO m => OAuthConfig -> Text -> m (Either Text TokenInfo)
- MCP.Server.Auth: validateCodeVerifier :: Text -> Text -> Bool
- MCP.Server.HTTP: HTTPServerConfig :: Port -> Text -> Implementation -> ServerCapabilities -> Bool -> Maybe OAuthConfig -> Maybe JWK -> Text -> HTTPServerConfig
- MCP.Server.HTTP: [$sel:httpBaseUrl:HTTPServerConfig] :: HTTPServerConfig -> Text
- MCP.Server.HTTP: [$sel:httpCapabilities:HTTPServerConfig] :: HTTPServerConfig -> ServerCapabilities
- MCP.Server.HTTP: [$sel:httpEnableLogging:HTTPServerConfig] :: HTTPServerConfig -> Bool
- MCP.Server.HTTP: [$sel:httpJWK:HTTPServerConfig] :: HTTPServerConfig -> Maybe JWK
- MCP.Server.HTTP: [$sel:httpOAuthConfig:HTTPServerConfig] :: HTTPServerConfig -> Maybe OAuthConfig
- MCP.Server.HTTP: [$sel:httpPort:HTTPServerConfig] :: HTTPServerConfig -> Port
- MCP.Server.HTTP: [$sel:httpProtocolVersion:HTTPServerConfig] :: HTTPServerConfig -> Text
- MCP.Server.HTTP: [$sel:httpServerInfo:HTTPServerConfig] :: HTTPServerConfig -> Implementation
- MCP.Server.HTTP: data HTTPServerConfig
- MCP.Server.HTTP: defaultDemoOAuthConfig :: OAuthConfig
- MCP.Server.HTTP: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Server.HTTP.AuthUser
- MCP.Server.HTTP: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Server.HTTP.ClientRegistrationRequest
- MCP.Server.HTTP: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Server.HTTP.AuthUser
- MCP.Server.HTTP: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Server.HTTP.ClientRegistrationResponse
- MCP.Server.HTTP: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Server.HTTP.TokenResponse
- MCP.Server.HTTP: instance GHC.Generics.Generic MCP.Server.HTTP.AuthUser
- MCP.Server.HTTP: instance GHC.Generics.Generic MCP.Server.HTTP.AuthorizationCode
- MCP.Server.HTTP: instance GHC.Generics.Generic MCP.Server.HTTP.ClientInfo
- MCP.Server.HTTP: instance GHC.Generics.Generic MCP.Server.HTTP.ClientRegistrationRequest
- MCP.Server.HTTP: instance GHC.Generics.Generic MCP.Server.HTTP.ClientRegistrationResponse
- MCP.Server.HTTP: instance GHC.Generics.Generic MCP.Server.HTTP.OAuthState
- MCP.Server.HTTP: instance GHC.Generics.Generic MCP.Server.HTTP.TokenResponse
- MCP.Server.HTTP: instance GHC.Show.Show MCP.Server.HTTP.AuthUser
- MCP.Server.HTTP: instance GHC.Show.Show MCP.Server.HTTP.AuthorizationCode
- MCP.Server.HTTP: instance GHC.Show.Show MCP.Server.HTTP.ClientInfo
- MCP.Server.HTTP: instance GHC.Show.Show MCP.Server.HTTP.ClientRegistrationRequest
- MCP.Server.HTTP: instance GHC.Show.Show MCP.Server.HTTP.ClientRegistrationResponse
- MCP.Server.HTTP: instance GHC.Show.Show MCP.Server.HTTP.HTTPServerConfig
- MCP.Server.HTTP: instance GHC.Show.Show MCP.Server.HTTP.OAuthState
- MCP.Server.HTTP: instance GHC.Show.Show MCP.Server.HTTP.TokenResponse
- MCP.Server.HTTP: instance Servant.Auth.JWT.FromJWT MCP.Server.HTTP.AuthUser
- MCP.Server.HTTP: instance Servant.Auth.JWT.ToJWT MCP.Server.HTTP.AuthUser
- MCP.Server.HTTP: runServerHTTP :: MCPServer MCPServerM => HTTPServerConfig -> IO ()
- MCP.Server.StdIO: ServerConfig :: Handle -> Handle -> Implementation -> ServerCapabilities -> ServerConfig
- MCP.Server.StdIO: [$sel:configCapabilities:ServerConfig] :: ServerConfig -> ServerCapabilities
- MCP.Server.StdIO: [$sel:configInput:ServerConfig] :: ServerConfig -> Handle
- MCP.Server.StdIO: [$sel:configOutput:ServerConfig] :: ServerConfig -> Handle
- MCP.Server.StdIO: [$sel:configServerInfo:ServerConfig] :: ServerConfig -> Implementation
- MCP.Server.StdIO: data ServerConfig
- MCP.Server.StdIO: runServer :: MCPServer MCPServerM => ServerConfig -> IO ()
- MCP.Types: Alert :: LoggingLevel
- MCP.Types: AllServers :: IncludeContext
- MCP.Types: Annotations :: Maybe [Role] -> Maybe Double -> Annotations
- MCP.Types: Assistant :: Role
- MCP.Types: AudioContent :: Text -> Text -> Text -> Maybe Annotations -> AudioContent
- MCP.Types: AudioContentType :: AudioContent -> Content
- MCP.Types: BlobResource :: BlobResourceContents -> ResourceContents
- MCP.Types: BlobResourceContents :: Text -> Text -> Maybe Text -> BlobResourceContents
- MCP.Types: ClientCapabilities :: Maybe RootsCapability -> Maybe SamplingCapability -> Maybe ExperimentalCapability -> ClientCapabilities
- MCP.Types: CompletionsCapability :: CompletionsCapability
- MCP.Types: Critical :: LoggingLevel
- MCP.Types: Cursor :: Text -> Cursor
- MCP.Types: Debug :: LoggingLevel
- MCP.Types: EmbeddedResource :: Text -> ResourceContents -> Maybe Annotations -> EmbeddedResource
- MCP.Types: EmbeddedResourceType :: EmbeddedResource -> Content
- MCP.Types: Emergency :: LoggingLevel
- MCP.Types: Error :: LoggingLevel
- MCP.Types: ExperimentalCapability :: Map Text Value -> ExperimentalCapability
- MCP.Types: ImageContent :: Text -> Text -> Text -> Maybe Annotations -> ImageContent
- MCP.Types: ImageContentType :: ImageContent -> Content
- MCP.Types: Implementation :: Text -> Text -> Implementation
- MCP.Types: Info :: LoggingLevel
- MCP.Types: InputSchema :: Text -> Maybe (Map Text Value) -> Maybe [Text] -> InputSchema
- MCP.Types: LoggingCapability :: LoggingCapability
- MCP.Types: Metadata :: Map Text Value -> Metadata
- MCP.Types: ModelPreferences :: Maybe [ModelHint] -> Maybe Double -> Maybe Double -> Maybe Double -> ModelPreferences
- MCP.Types: None :: IncludeContext
- MCP.Types: Notice :: LoggingLevel
- MCP.Types: ProgressToken :: Value -> ProgressToken
- MCP.Types: Prompt :: Text -> Maybe Text -> Maybe [PromptArgument] -> Prompt
- MCP.Types: PromptArgument :: Text -> Maybe Text -> Maybe Bool -> PromptArgument
- MCP.Types: PromptMessage :: Role -> Content -> PromptMessage
- MCP.Types: PromptReference :: Text -> Text -> PromptReference
- MCP.Types: RequestId :: Value -> RequestId
- MCP.Types: Resource :: Text -> Text -> Maybe Text -> Maybe Text -> Maybe Int -> Maybe Annotations -> Resource
- MCP.Types: ResourceReference :: Text -> Text -> ResourceReference
- MCP.Types: ResourceTemplate :: Text -> Text -> Maybe Text -> Maybe Text -> Maybe Annotations -> ResourceTemplate
- MCP.Types: ResourcesCapability :: Maybe Bool -> Maybe Bool -> ResourcesCapability
- MCP.Types: Root :: Text -> Maybe Text -> Root
- MCP.Types: SamplingCapability :: SamplingCapability
- MCP.Types: SamplingMessage :: Role -> Content -> SamplingMessage
- MCP.Types: ServerCapabilities :: Maybe LoggingCapability -> Maybe PromptsCapability -> Maybe ResourcesCapability -> Maybe ToolsCapability -> Maybe CompletionsCapability -> Maybe ExperimentalCapability -> ServerCapabilities
- MCP.Types: TextContent :: Text -> Text -> Maybe Annotations -> TextContent
- MCP.Types: TextContentType :: TextContent -> Content
- MCP.Types: TextResource :: TextResourceContents -> ResourceContents
- MCP.Types: TextResourceContents :: Text -> Text -> Maybe Text -> TextResourceContents
- MCP.Types: ThisServer :: IncludeContext
- MCP.Types: Tool :: Text -> Maybe Text -> InputSchema -> Maybe ToolAnnotations -> Tool
- MCP.Types: ToolAnnotations :: Maybe Text -> Maybe Bool -> Maybe Bool -> Maybe Bool -> Maybe Bool -> ToolAnnotations
- MCP.Types: User :: Role
- MCP.Types: Warning :: LoggingLevel
- MCP.Types: [$sel:annotations:AudioContent] :: AudioContent -> Maybe Annotations
- MCP.Types: [$sel:annotations:EmbeddedResource] :: EmbeddedResource -> Maybe Annotations
- MCP.Types: [$sel:annotations:ImageContent] :: ImageContent -> Maybe Annotations
- MCP.Types: [$sel:annotations:ResourceTemplate] :: ResourceTemplate -> Maybe Annotations
- MCP.Types: [$sel:annotations:Resource] :: Resource -> Maybe Annotations
- MCP.Types: [$sel:annotations:TextContent] :: TextContent -> Maybe Annotations
- MCP.Types: [$sel:annotations:Tool] :: Tool -> Maybe ToolAnnotations
- MCP.Types: [$sel:arguments:Prompt] :: Prompt -> Maybe [PromptArgument]
- MCP.Types: [$sel:audience:Annotations] :: Annotations -> Maybe [Role]
- MCP.Types: [$sel:audioType:AudioContent] :: AudioContent -> Text
- MCP.Types: [$sel:blob:BlobResourceContents] :: BlobResourceContents -> Text
- MCP.Types: [$sel:completions:ServerCapabilities] :: ServerCapabilities -> Maybe CompletionsCapability
- MCP.Types: [$sel:content:PromptMessage] :: PromptMessage -> Content
- MCP.Types: [$sel:content:SamplingMessage] :: SamplingMessage -> Content
- MCP.Types: [$sel:costPriority:ModelPreferences] :: ModelPreferences -> Maybe Double
- MCP.Types: [$sel:data':AudioContent] :: AudioContent -> Text
- MCP.Types: [$sel:data':ImageContent] :: ImageContent -> Text
- MCP.Types: [$sel:description:PromptArgument] :: PromptArgument -> Maybe Text
- MCP.Types: [$sel:description:Prompt] :: Prompt -> Maybe Text
- MCP.Types: [$sel:description:ResourceTemplate] :: ResourceTemplate -> Maybe Text
- MCP.Types: [$sel:description:Resource] :: Resource -> Maybe Text
- MCP.Types: [$sel:description:Tool] :: Tool -> Maybe Text
- MCP.Types: [$sel:destructiveHint:ToolAnnotations] :: ToolAnnotations -> Maybe Bool
- MCP.Types: [$sel:experimental:ClientCapabilities] :: ClientCapabilities -> Maybe ExperimentalCapability
- MCP.Types: [$sel:experimental:ServerCapabilities] :: ServerCapabilities -> Maybe ExperimentalCapability
- MCP.Types: [$sel:hints:ModelPreferences] :: ModelPreferences -> Maybe [ModelHint]
- MCP.Types: [$sel:idempotentHint:ToolAnnotations] :: ToolAnnotations -> Maybe Bool
- MCP.Types: [$sel:imageType:ImageContent] :: ImageContent -> Text
- MCP.Types: [$sel:inputSchema:Tool] :: Tool -> InputSchema
- MCP.Types: [$sel:intelligencePriority:ModelPreferences] :: ModelPreferences -> Maybe Double
- MCP.Types: [$sel:listChanged:ResourcesCapability] :: ResourcesCapability -> Maybe Bool
- MCP.Types: [$sel:logging:ServerCapabilities] :: ServerCapabilities -> Maybe LoggingCapability
- MCP.Types: [$sel:mimeType:AudioContent] :: AudioContent -> Text
- MCP.Types: [$sel:mimeType:BlobResourceContents] :: BlobResourceContents -> Maybe Text
- MCP.Types: [$sel:mimeType:ImageContent] :: ImageContent -> Text
- MCP.Types: [$sel:mimeType:ResourceTemplate] :: ResourceTemplate -> Maybe Text
- MCP.Types: [$sel:mimeType:Resource] :: Resource -> Maybe Text
- MCP.Types: [$sel:mimeType:TextResourceContents] :: TextResourceContents -> Maybe Text
- MCP.Types: [$sel:name:Implementation] :: Implementation -> Text
- MCP.Types: [$sel:name:PromptArgument] :: PromptArgument -> Text
- MCP.Types: [$sel:name:PromptReference] :: PromptReference -> Text
- MCP.Types: [$sel:name:Prompt] :: Prompt -> Text
- MCP.Types: [$sel:name:ResourceTemplate] :: ResourceTemplate -> Text
- MCP.Types: [$sel:name:Resource] :: Resource -> Text
- MCP.Types: [$sel:name:Root] :: Root -> Maybe Text
- MCP.Types: [$sel:name:Tool] :: Tool -> Text
- MCP.Types: [$sel:openWorldHint:ToolAnnotations] :: ToolAnnotations -> Maybe Bool
- MCP.Types: [$sel:priority:Annotations] :: Annotations -> Maybe Double
- MCP.Types: [$sel:prompts:ServerCapabilities] :: ServerCapabilities -> Maybe PromptsCapability
- MCP.Types: [$sel:properties:InputSchema] :: InputSchema -> Maybe (Map Text Value)
- MCP.Types: [$sel:readOnlyHint:ToolAnnotations] :: ToolAnnotations -> Maybe Bool
- MCP.Types: [$sel:refType:PromptReference] :: PromptReference -> Text
- MCP.Types: [$sel:refType:ResourceReference] :: ResourceReference -> Text
- MCP.Types: [$sel:required:InputSchema] :: InputSchema -> Maybe [Text]
- MCP.Types: [$sel:required:PromptArgument] :: PromptArgument -> Maybe Bool
- MCP.Types: [$sel:resource:EmbeddedResource] :: EmbeddedResource -> ResourceContents
- MCP.Types: [$sel:resourceType:EmbeddedResource] :: EmbeddedResource -> Text
- MCP.Types: [$sel:resources:ServerCapabilities] :: ServerCapabilities -> Maybe ResourcesCapability
- MCP.Types: [$sel:role:PromptMessage] :: PromptMessage -> Role
- MCP.Types: [$sel:role:SamplingMessage] :: SamplingMessage -> Role
- MCP.Types: [$sel:roots:ClientCapabilities] :: ClientCapabilities -> Maybe RootsCapability
- MCP.Types: [$sel:sampling:ClientCapabilities] :: ClientCapabilities -> Maybe SamplingCapability
- MCP.Types: [$sel:schemaType:InputSchema] :: InputSchema -> Text
- MCP.Types: [$sel:size:Resource] :: Resource -> Maybe Int
- MCP.Types: [$sel:speedPriority:ModelPreferences] :: ModelPreferences -> Maybe Double
- MCP.Types: [$sel:subscribe:ResourcesCapability] :: ResourcesCapability -> Maybe Bool
- MCP.Types: [$sel:text:TextContent] :: TextContent -> Text
- MCP.Types: [$sel:text:TextResourceContents] :: TextResourceContents -> Text
- MCP.Types: [$sel:textType:TextContent] :: TextContent -> Text
- MCP.Types: [$sel:title:ToolAnnotations] :: ToolAnnotations -> Maybe Text
- MCP.Types: [$sel:tools:ServerCapabilities] :: ServerCapabilities -> Maybe ToolsCapability
- MCP.Types: [$sel:uri:BlobResourceContents] :: BlobResourceContents -> Text
- MCP.Types: [$sel:uri:ResourceReference] :: ResourceReference -> Text
- MCP.Types: [$sel:uri:Resource] :: Resource -> Text
- MCP.Types: [$sel:uri:Root] :: Root -> Text
- MCP.Types: [$sel:uri:TextResourceContents] :: TextResourceContents -> Text
- MCP.Types: [$sel:uriTemplate:ResourceTemplate] :: ResourceTemplate -> Text
- MCP.Types: [$sel:version:Implementation] :: Implementation -> Text
- MCP.Types: [ModelHint] :: Maybe Text -> ModelHint
- MCP.Types: [PromptsCapability] :: Maybe Bool -> PromptsCapability
- MCP.Types: [Result] :: Maybe Metadata -> Result
- MCP.Types: [RootsCapability] :: Maybe Bool -> RootsCapability
- MCP.Types: [ToolsCapability] :: Maybe Bool -> ToolsCapability
- MCP.Types: data Annotations
- MCP.Types: data AudioContent
- MCP.Types: data BlobResourceContents
- MCP.Types: data ClientCapabilities
- MCP.Types: data CompletionsCapability
- MCP.Types: data Content
- MCP.Types: data EmbeddedResource
- MCP.Types: data ImageContent
- MCP.Types: data Implementation
- MCP.Types: data IncludeContext
- MCP.Types: data InputSchema
- MCP.Types: data LoggingCapability
- MCP.Types: data LoggingLevel
- MCP.Types: data ModelHint
- MCP.Types: data ModelPreferences
- MCP.Types: data Prompt
- MCP.Types: data PromptArgument
- MCP.Types: data PromptMessage
- MCP.Types: data PromptReference
- MCP.Types: data PromptsCapability
- MCP.Types: data Resource
- MCP.Types: data ResourceContents
- MCP.Types: data ResourceReference
- MCP.Types: data ResourceTemplate
- MCP.Types: data ResourcesCapability
- MCP.Types: data Result
- MCP.Types: data Role
- MCP.Types: data Root
- MCP.Types: data RootsCapability
- MCP.Types: data SamplingCapability
- MCP.Types: data SamplingMessage
- MCP.Types: data ServerCapabilities
- MCP.Types: data TextContent
- MCP.Types: data TextResourceContents
- MCP.Types: data Tool
- MCP.Types: data ToolAnnotations
- MCP.Types: data ToolsCapability
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.Annotations
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.AudioContent
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.BlobResourceContents
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.ClientCapabilities
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.CompletionsCapability
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.Content
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.Cursor
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.EmbeddedResource
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.ExperimentalCapability
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.ImageContent
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.Implementation
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.IncludeContext
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.InputSchema
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.LoggingCapability
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.LoggingLevel
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.Metadata
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.ModelHint
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.ModelPreferences
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.ProgressToken
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.Prompt
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.PromptArgument
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.PromptMessage
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.PromptReference
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.PromptsCapability
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.RequestId
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.Resource
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.ResourceContents
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.ResourceReference
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.ResourceTemplate
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.ResourcesCapability
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.Result
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.Role
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.Root
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.RootsCapability
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.SamplingCapability
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.SamplingMessage
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.ServerCapabilities
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.TextContent
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.TextResourceContents
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.Tool
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.ToolAnnotations
- MCP.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Types.ToolsCapability
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.Annotations
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.AudioContent
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.BlobResourceContents
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.ClientCapabilities
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.CompletionsCapability
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.Content
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.Cursor
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.EmbeddedResource
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.ExperimentalCapability
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.ImageContent
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.Implementation
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.IncludeContext
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.InputSchema
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.LoggingCapability
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.LoggingLevel
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.Metadata
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.ModelHint
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.ModelPreferences
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.ProgressToken
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.Prompt
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.PromptArgument
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.PromptMessage
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.PromptReference
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.PromptsCapability
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.RequestId
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.Resource
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.ResourceContents
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.ResourceReference
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.ResourceTemplate
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.ResourcesCapability
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.Result
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.Role
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.Root
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.RootsCapability
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.SamplingCapability
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.SamplingMessage
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.ServerCapabilities
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.TextContent
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.TextResourceContents
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.Tool
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.ToolAnnotations
- MCP.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Types.ToolsCapability
- MCP.Types: instance GHC.Classes.Eq MCP.Types.Annotations
- MCP.Types: instance GHC.Classes.Eq MCP.Types.AudioContent
- MCP.Types: instance GHC.Classes.Eq MCP.Types.BlobResourceContents
- MCP.Types: instance GHC.Classes.Eq MCP.Types.ClientCapabilities
- MCP.Types: instance GHC.Classes.Eq MCP.Types.CompletionsCapability
- MCP.Types: instance GHC.Classes.Eq MCP.Types.Content
- MCP.Types: instance GHC.Classes.Eq MCP.Types.Cursor
- MCP.Types: instance GHC.Classes.Eq MCP.Types.EmbeddedResource
- MCP.Types: instance GHC.Classes.Eq MCP.Types.ExperimentalCapability
- MCP.Types: instance GHC.Classes.Eq MCP.Types.ImageContent
- MCP.Types: instance GHC.Classes.Eq MCP.Types.Implementation
- MCP.Types: instance GHC.Classes.Eq MCP.Types.IncludeContext
- MCP.Types: instance GHC.Classes.Eq MCP.Types.InputSchema
- MCP.Types: instance GHC.Classes.Eq MCP.Types.LoggingCapability
- MCP.Types: instance GHC.Classes.Eq MCP.Types.LoggingLevel
- MCP.Types: instance GHC.Classes.Eq MCP.Types.Metadata
- MCP.Types: instance GHC.Classes.Eq MCP.Types.ModelHint
- MCP.Types: instance GHC.Classes.Eq MCP.Types.ModelPreferences
- MCP.Types: instance GHC.Classes.Eq MCP.Types.ProgressToken
- MCP.Types: instance GHC.Classes.Eq MCP.Types.Prompt
- MCP.Types: instance GHC.Classes.Eq MCP.Types.PromptArgument
- MCP.Types: instance GHC.Classes.Eq MCP.Types.PromptMessage
- MCP.Types: instance GHC.Classes.Eq MCP.Types.PromptReference
- MCP.Types: instance GHC.Classes.Eq MCP.Types.PromptsCapability
- MCP.Types: instance GHC.Classes.Eq MCP.Types.RequestId
- MCP.Types: instance GHC.Classes.Eq MCP.Types.Resource
- MCP.Types: instance GHC.Classes.Eq MCP.Types.ResourceContents
- MCP.Types: instance GHC.Classes.Eq MCP.Types.ResourceReference
- MCP.Types: instance GHC.Classes.Eq MCP.Types.ResourceTemplate
- MCP.Types: instance GHC.Classes.Eq MCP.Types.ResourcesCapability
- MCP.Types: instance GHC.Classes.Eq MCP.Types.Result
- MCP.Types: instance GHC.Classes.Eq MCP.Types.Role
- MCP.Types: instance GHC.Classes.Eq MCP.Types.Root
- MCP.Types: instance GHC.Classes.Eq MCP.Types.RootsCapability
- MCP.Types: instance GHC.Classes.Eq MCP.Types.SamplingCapability
- MCP.Types: instance GHC.Classes.Eq MCP.Types.SamplingMessage
- MCP.Types: instance GHC.Classes.Eq MCP.Types.ServerCapabilities
- MCP.Types: instance GHC.Classes.Eq MCP.Types.TextContent
- MCP.Types: instance GHC.Classes.Eq MCP.Types.TextResourceContents
- MCP.Types: instance GHC.Classes.Eq MCP.Types.Tool
- MCP.Types: instance GHC.Classes.Eq MCP.Types.ToolAnnotations
- MCP.Types: instance GHC.Classes.Eq MCP.Types.ToolsCapability
- MCP.Types: instance GHC.Generics.Generic MCP.Types.Annotations
- MCP.Types: instance GHC.Generics.Generic MCP.Types.AudioContent
- MCP.Types: instance GHC.Generics.Generic MCP.Types.BlobResourceContents
- MCP.Types: instance GHC.Generics.Generic MCP.Types.ClientCapabilities
- MCP.Types: instance GHC.Generics.Generic MCP.Types.CompletionsCapability
- MCP.Types: instance GHC.Generics.Generic MCP.Types.Content
- MCP.Types: instance GHC.Generics.Generic MCP.Types.EmbeddedResource
- MCP.Types: instance GHC.Generics.Generic MCP.Types.ExperimentalCapability
- MCP.Types: instance GHC.Generics.Generic MCP.Types.ImageContent
- MCP.Types: instance GHC.Generics.Generic MCP.Types.Implementation
- MCP.Types: instance GHC.Generics.Generic MCP.Types.IncludeContext
- MCP.Types: instance GHC.Generics.Generic MCP.Types.InputSchema
- MCP.Types: instance GHC.Generics.Generic MCP.Types.LoggingCapability
- MCP.Types: instance GHC.Generics.Generic MCP.Types.LoggingLevel
- MCP.Types: instance GHC.Generics.Generic MCP.Types.Metadata
- MCP.Types: instance GHC.Generics.Generic MCP.Types.ModelHint
- MCP.Types: instance GHC.Generics.Generic MCP.Types.ModelPreferences
- MCP.Types: instance GHC.Generics.Generic MCP.Types.Prompt
- MCP.Types: instance GHC.Generics.Generic MCP.Types.PromptArgument
- MCP.Types: instance GHC.Generics.Generic MCP.Types.PromptMessage
- MCP.Types: instance GHC.Generics.Generic MCP.Types.PromptReference
- MCP.Types: instance GHC.Generics.Generic MCP.Types.PromptsCapability
- MCP.Types: instance GHC.Generics.Generic MCP.Types.Resource
- MCP.Types: instance GHC.Generics.Generic MCP.Types.ResourceContents
- MCP.Types: instance GHC.Generics.Generic MCP.Types.ResourceReference
- MCP.Types: instance GHC.Generics.Generic MCP.Types.ResourceTemplate
- MCP.Types: instance GHC.Generics.Generic MCP.Types.ResourcesCapability
- MCP.Types: instance GHC.Generics.Generic MCP.Types.Result
- MCP.Types: instance GHC.Generics.Generic MCP.Types.Role
- MCP.Types: instance GHC.Generics.Generic MCP.Types.Root
- MCP.Types: instance GHC.Generics.Generic MCP.Types.RootsCapability
- MCP.Types: instance GHC.Generics.Generic MCP.Types.SamplingCapability
- MCP.Types: instance GHC.Generics.Generic MCP.Types.SamplingMessage
- MCP.Types: instance GHC.Generics.Generic MCP.Types.ServerCapabilities
- MCP.Types: instance GHC.Generics.Generic MCP.Types.TextContent
- MCP.Types: instance GHC.Generics.Generic MCP.Types.TextResourceContents
- MCP.Types: instance GHC.Generics.Generic MCP.Types.Tool
- MCP.Types: instance GHC.Generics.Generic MCP.Types.ToolAnnotations
- MCP.Types: instance GHC.Generics.Generic MCP.Types.ToolsCapability
- MCP.Types: instance GHC.Show.Show MCP.Types.Annotations
- MCP.Types: instance GHC.Show.Show MCP.Types.AudioContent
- MCP.Types: instance GHC.Show.Show MCP.Types.BlobResourceContents
- MCP.Types: instance GHC.Show.Show MCP.Types.ClientCapabilities
- MCP.Types: instance GHC.Show.Show MCP.Types.CompletionsCapability
- MCP.Types: instance GHC.Show.Show MCP.Types.Content
- MCP.Types: instance GHC.Show.Show MCP.Types.Cursor
- MCP.Types: instance GHC.Show.Show MCP.Types.EmbeddedResource
- MCP.Types: instance GHC.Show.Show MCP.Types.ExperimentalCapability
- MCP.Types: instance GHC.Show.Show MCP.Types.ImageContent
- MCP.Types: instance GHC.Show.Show MCP.Types.Implementation
- MCP.Types: instance GHC.Show.Show MCP.Types.IncludeContext
- MCP.Types: instance GHC.Show.Show MCP.Types.InputSchema
- MCP.Types: instance GHC.Show.Show MCP.Types.LoggingCapability
- MCP.Types: instance GHC.Show.Show MCP.Types.LoggingLevel
- MCP.Types: instance GHC.Show.Show MCP.Types.Metadata
- MCP.Types: instance GHC.Show.Show MCP.Types.ModelHint
- MCP.Types: instance GHC.Show.Show MCP.Types.ModelPreferences
- MCP.Types: instance GHC.Show.Show MCP.Types.ProgressToken
- MCP.Types: instance GHC.Show.Show MCP.Types.Prompt
- MCP.Types: instance GHC.Show.Show MCP.Types.PromptArgument
- MCP.Types: instance GHC.Show.Show MCP.Types.PromptMessage
- MCP.Types: instance GHC.Show.Show MCP.Types.PromptReference
- MCP.Types: instance GHC.Show.Show MCP.Types.PromptsCapability
- MCP.Types: instance GHC.Show.Show MCP.Types.RequestId
- MCP.Types: instance GHC.Show.Show MCP.Types.Resource
- MCP.Types: instance GHC.Show.Show MCP.Types.ResourceContents
- MCP.Types: instance GHC.Show.Show MCP.Types.ResourceReference
- MCP.Types: instance GHC.Show.Show MCP.Types.ResourceTemplate
- MCP.Types: instance GHC.Show.Show MCP.Types.ResourcesCapability
- MCP.Types: instance GHC.Show.Show MCP.Types.Result
- MCP.Types: instance GHC.Show.Show MCP.Types.Role
- MCP.Types: instance GHC.Show.Show MCP.Types.Root
- MCP.Types: instance GHC.Show.Show MCP.Types.RootsCapability
- MCP.Types: instance GHC.Show.Show MCP.Types.SamplingCapability
- MCP.Types: instance GHC.Show.Show MCP.Types.SamplingMessage
- MCP.Types: instance GHC.Show.Show MCP.Types.ServerCapabilities
- MCP.Types: instance GHC.Show.Show MCP.Types.TextContent
- MCP.Types: instance GHC.Show.Show MCP.Types.TextResourceContents
- MCP.Types: instance GHC.Show.Show MCP.Types.Tool
- MCP.Types: instance GHC.Show.Show MCP.Types.ToolAnnotations
- MCP.Types: instance GHC.Show.Show MCP.Types.ToolsCapability
- MCP.Types: newtype Cursor
- MCP.Types: newtype ExperimentalCapability
- MCP.Types: newtype Metadata
- MCP.Types: newtype ProgressToken
- MCP.Types: newtype RequestId
+ MCP.Server.Common: MCPServerState :: Bool -> MCPHandlerState -> Maybe (MCPHandlerUser -> MCPHandlerState -> IO MCPHandlerState) -> Maybe (MCPHandlerState -> IO MCPHandlerState) -> Maybe ClientCapabilities -> Maybe LoggingLevel -> IntMap (MVar Value) -> Int -> ServerCapabilities -> Implementation -> Maybe Text -> ProcessHandlers -> MCPServerState
+ MCP.Server.Common: ProcessHandlers :: Maybe (ListResourcesParams -> MCPServerT (ProcessResult ListResourcesResult)) -> Maybe (ReadResourceParams -> MCPServerT (ProcessResult ReadResourceResult)) -> Maybe (ListToolsParams -> MCPServerT (ProcessResult ListToolsResult)) -> Maybe (CallToolParams -> MCPServerT (ProcessResult CallToolResult)) -> Maybe (ListPromptsParams -> MCPServerT (ProcessResult ListPromptsResult)) -> Maybe (GetPromptParams -> MCPServerT (ProcessResult GetPromptResult)) -> Maybe (ListResourceTemplatesParams -> MCPServerT (ProcessResult ListResourceTemplatesResult)) -> Maybe (CompleteParams -> MCPServerT (ProcessResult CompleteResult)) -> Maybe (SubscribeParams -> MCPServerT (ProcessResult Result)) -> Maybe (UnsubscribeParams -> MCPServerT (ProcessResult Result)) -> ProcessHandlers
+ MCP.Server.Common: ToolHandler :: Text -> Maybe Text -> Maybe Text -> InputSchema -> Maybe InputSchema -> Maybe ToolAnnotations -> Maybe Metadata -> (Maybe (Map Text Value) -> MCPServerT (ProcessResult CallToolResult)) -> ToolHandler
+ MCP.Server.Common: [ProcessClientInput] :: forall a. Text -> Value -> (Value -> MCPServerT (ProcessResult a)) -> ProcessResult a
+ MCP.Server.Common: [ProcessRPCError] :: forall a. Int -> Text -> ProcessResult a
+ MCP.Server.Common: [ProcessServerError] :: forall a. Text -> ProcessResult a
+ MCP.Server.Common: [ProcessSuccess] :: forall a. a -> ProcessResult a
+ MCP.Server.Common: [callToolHandler] :: ProcessHandlers -> Maybe (CallToolParams -> MCPServerT (ProcessResult CallToolResult))
+ MCP.Server.Common: [completeHandler] :: ProcessHandlers -> Maybe (CompleteParams -> MCPServerT (ProcessResult CompleteResult))
+ MCP.Server.Common: [getPromptHandler] :: ProcessHandlers -> Maybe (GetPromptParams -> MCPServerT (ProcessResult GetPromptResult))
+ MCP.Server.Common: [listPromptsHandler] :: ProcessHandlers -> Maybe (ListPromptsParams -> MCPServerT (ProcessResult ListPromptsResult))
+ MCP.Server.Common: [listResourceTemplatesHandler] :: ProcessHandlers -> Maybe (ListResourceTemplatesParams -> MCPServerT (ProcessResult ListResourceTemplatesResult))
+ MCP.Server.Common: [listResourcesHandler] :: ProcessHandlers -> Maybe (ListResourcesParams -> MCPServerT (ProcessResult ListResourcesResult))
+ MCP.Server.Common: [listToolsHandler] :: ProcessHandlers -> Maybe (ListToolsParams -> MCPServerT (ProcessResult ListToolsResult))
+ MCP.Server.Common: [mcp_client_capabilities] :: MCPServerState -> Maybe ClientCapabilities
+ MCP.Server.Common: [mcp_handler_finalize] :: MCPServerState -> Maybe (MCPHandlerState -> IO MCPHandlerState)
+ MCP.Server.Common: [mcp_handler_init] :: MCPServerState -> Maybe (MCPHandlerUser -> MCPHandlerState -> IO MCPHandlerState)
+ MCP.Server.Common: [mcp_handler_state] :: MCPServerState -> MCPHandlerState
+ MCP.Server.Common: [mcp_implementation] :: MCPServerState -> Implementation
+ MCP.Server.Common: [mcp_instructions] :: MCPServerState -> Maybe Text
+ MCP.Server.Common: [mcp_log_level] :: MCPServerState -> Maybe LoggingLevel
+ MCP.Server.Common: [mcp_pending_responses] :: MCPServerState -> IntMap (MVar Value)
+ MCP.Server.Common: [mcp_pending_responses_next] :: MCPServerState -> Int
+ MCP.Server.Common: [mcp_process_handlers] :: MCPServerState -> ProcessHandlers
+ MCP.Server.Common: [mcp_server_capabilities] :: MCPServerState -> ServerCapabilities
+ MCP.Server.Common: [mcp_server_initialized] :: MCPServerState -> Bool
+ MCP.Server.Common: [readResourceHandler] :: ProcessHandlers -> Maybe (ReadResourceParams -> MCPServerT (ProcessResult ReadResourceResult))
+ MCP.Server.Common: [subscribeHandler] :: ProcessHandlers -> Maybe (SubscribeParams -> MCPServerT (ProcessResult Result))
+ MCP.Server.Common: [tool_annotations] :: ToolHandler -> Maybe ToolAnnotations
+ MCP.Server.Common: [tool_description] :: ToolHandler -> Maybe Text
+ MCP.Server.Common: [tool_handler] :: ToolHandler -> Maybe (Map Text Value) -> MCPServerT (ProcessResult CallToolResult)
+ MCP.Server.Common: [tool_input] :: ToolHandler -> InputSchema
+ MCP.Server.Common: [tool_meta] :: ToolHandler -> Maybe Metadata
+ MCP.Server.Common: [tool_name] :: ToolHandler -> Text
+ MCP.Server.Common: [tool_output] :: ToolHandler -> Maybe InputSchema
+ MCP.Server.Common: [tool_title] :: ToolHandler -> Maybe Text
+ MCP.Server.Common: [unsubscribeHandler] :: ProcessHandlers -> Maybe (UnsubscribeParams -> MCPServerT (ProcessResult Result))
+ MCP.Server.Common: data MCPServerState
+ MCP.Server.Common: data ProcessHandlers
+ MCP.Server.Common: data ProcessResult a
+ MCP.Server.Common: data ToolHandler
+ MCP.Server.Common: defaultProcessHandlers :: ProcessHandlers
+ MCP.Server.Common: initMCPServerState :: MCPHandlerState -> Maybe (MCPHandlerUser -> MCPHandlerState -> IO MCPHandlerState) -> Maybe (MCPHandlerState -> IO MCPHandlerState) -> ServerCapabilities -> Implementation -> Maybe Text -> ProcessHandlers -> MCPServerState
+ MCP.Server.Common: instance GHC.Base.Functor MCP.Server.Common.ProcessResult
+ MCP.Server.Common: isValidRequestId :: RequestId -> Bool
+ MCP.Server.Common: processMethod :: Bool -> Text -> Value -> StateT MCPServerState IO (ProcessResult Value)
+ MCP.Server.Common: recurReplaceMeta :: Value -> Value
+ MCP.Server.Common: toolHandler :: Text -> Maybe Text -> InputSchema -> (Maybe (Map Text Value) -> MCPServerT (ProcessResult CallToolResult)) -> ToolHandler
+ MCP.Server.Common: toolTextError :: Text -> CallToolResult
+ MCP.Server.Common: toolTextResult :: [Text] -> CallToolResult
+ MCP.Server.Common: type MCPServerT a = ExceptT Text StateT MCPServerState IO a
+ MCP.Server.Common: type family MCPHandlerUser
+ MCP.Server.Common: withToolHandlers :: [ToolHandler] -> ProcessHandlers -> ProcessHandlers
+ MCP.Server.HTTP: handleMCPEvents :: AuthResult MCPHandlerUser -> Handler (SourceIO JSONRPCMessage)
+ MCP.Server.HTTP: handleMCPRequest :: MVar MCPServerState -> AuthResult MCPHandlerUser -> JSONRPCMessage -> Handler (SourceIO JSONRPCMessage)
+ MCP.Server.HTTP: instance Servant.API.ContentTypes.Accept MCP.Server.HTTP.JSONRPCEvent
+ MCP.Server.HTTP: instance Servant.API.ContentTypes.MimeRender MCP.Server.HTTP.JSONRPCEvent JSONRPC.JSONRPCMessage
+ MCP.Server.HTTP: instance Servant.API.Stream.FramingRender MCP.Server.HTTP.JSONRPCFrame
+ MCP.Server.HTTP: mcpAPI :: MVar MCPServerState -> Server MCPAPI
+ MCP.Server.HTTP: type MCPAPI = "mcp" :> Auth '[JWT] MCPHandlerUser :> ReqBody '[JSON] JSONRPCMessage :> StreamPost JSONRPCFrame JSONRPCEvent SourceIO JSONRPCMessage :<|> "mcp" :> Auth '[JWT] MCPHandlerUser :> StreamGet JSONRPCFrame JSONRPCEvent SourceIO JSONRPCMessage
+ MCP.Server.Stdio: serveStdio :: Handle -> Handle -> MCPServerState -> IO ()
Files
- CHANGELOG.md +13/−46
- LICENSE +370/−17
- README.md +91/−0
- app/Main.hs +0/−112
- examples/README.md +0/−253
- examples/claude-desktop-config.json +0/−24
- examples/full-config-example.json +0/−34
- examples/http-server.hs +0/−244
- mcp.cabal +114/−203
- src/MCP/Protocol.hs +0/−1132
- src/MCP/Server.hs +17/−112
- src/MCP/Server/Auth.hs +0/−307
- src/MCP/Server/Common.hs +471/−0
- src/MCP/Server/HTTP.hs +272/−807
- src/MCP/Server/StdIO.hs +0/−280
- src/MCP/Server/Stdio.hs +187/−0
- src/MCP/Types.hs +0/−612
- test/MCP/Integration.hs +651/−0
- test/MCP/StdioIntegration.hs +349/−0
- test/MCP/TestServer.hs +481/−0
- test/MCP/TestUtils.hs +285/−0
- test/Main.hs +20/−2
CHANGELOG.md view
@@ -1,49 +1,16 @@-# Revision history for mcp--## 0.2.0.0 -- 2025-01-18+# Changelog -* Add HTTP transport support following MCP specification-* Implement full OAuth 2.0 authorization flow with mandatory PKCE- - OAuth metadata discovery at /.well-known/oauth-authorization-server- - Dynamic client registration at /register endpoint- - /authorize endpoint for authorization requests with PKCE validation- - /token endpoint for authorization code and refresh token grants- - In-memory storage for authorization codes, tokens, and registered clients- - Automatic user approval for demo mode- - Support for public clients (no client secret required)-* Add OAuth 2.1 authentication module (MCP.Server.Auth)- - JWT token validation and introspection- - PKCE code verifier/challenge generation and validation (S256 method)- - OAuth metadata discovery with ToJSON/FromJSON instances- - Support for multiple OAuth providers (Google, GitHub, custom)- - Token expiration and not-before time validation-* OAuth implementation features:- - Returns empty string for client_secret (public clients)- - Validates redirect URIs and client IDs- - 10-minute authorization code expiry- - 1-hour access token validity- - Refresh token support with rotation- - Proper JWT token generation using servant-auth-server- - Fixed authentication loop issue by replacing UUID tokens with JWT-* Add example OAuth resources:- - OAuth demo client script (examples/oauth-client-demo.sh)- - OAuth metadata test script (test-oauth-metadata.sh)- - Updated HTTP server example with --oauth flag-* Fix all compiler warnings with -Wall enabled-* Clean up unused imports and parameters-* Add uuid dependency for token generation-* **BREAKING CHANGE**: Refactor HTTP server to be production-ready:- - Extract hardcoded demo values into configurable parameters- - Add `httpBaseUrl` and `httpProtocolVersion` to HTTPServerConfig- - Expand OAuthConfig with timing, OAuth parameters, and demo settings- - Move demo-specific configuration to examples/http-server.hs- - Add `defaultDemoOAuthConfig` helper for testing- - Make server library more robust and configurable for production use-* Package metadata cleanup and code formatting improvements-* Remove unused MyLib module-* Add MCP configuration and examples-* Published to Hackage+## 0.3.0.1 -## 0.1.0.0 -- YYYY-mm-dd+### Added+- Protocol types extracted into `mcp-types`; server remains as `mcp`.+- `MCP.Server.Common`: transport-agnostic types, state, request routing, and tool helpers.+- `MCP.Server.HTTP`: Servant-based HTTP transport with JWT authentication and SSE responses.+- `MCP.Server.Stdio`: stdio transport reading/writing JSON-RPC messages line-by-line.+- `MCP.Server`: re-exports all three submodules for backwards compatibility.+- Re-exports `MCP.Protocol` and `MCP.Types` from `mcp-types` for convenience.+- Integration test suite covering both HTTP and stdio transports. -* First version. Released on an unsuspecting world.+### Migration+- Change `import MCP` to `import MCP.Server`.+- If you only need types, depend on `mcp-types` instead of `mcp`.
LICENSE view
@@ -1,20 +1,373 @@-Copyright (c) 2025 Matthias Pall Gissurarson+Mozilla Public License Version 2.0+================================== -Permission is hereby granted, free of charge, to any person obtaining-a copy of this software and associated documentation files (the-"Software"), to deal in the Software without restriction, including-without limitation the rights to use, copy, modify, merge, publish,-distribute, sublicense, and/or sell copies of the Software, and to-permit persons to whom the Software is furnished to do so, subject to-the following conditions:+1. Definitions+-------------- -The above copyright notice and this permission notice shall be included-in all copies or substantial portions of the Software.+1.1. "Contributor"+ means each individual or legal entity that creates, contributes to+ the creation of, or owns Covered Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.-IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY-CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,-TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE-SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.+1.2. "Contributor Version"+ means the combination of the Contributions of others (if any) used+ by a Contributor and that particular Contributor's Contribution.++1.3. "Contribution"+ means Covered Software of a particular Contributor.++1.4. "Covered Software"+ means Source Code Form to which the initial Contributor has attached+ the notice in Exhibit A, the Executable Form of such Source Code+ Form, and Modifications of such Source Code Form, in each case+ including portions thereof.++1.5. "Incompatible With Secondary Licenses"+ means++ (a) that the initial Contributor has attached the notice described+ in Exhibit B to the Covered Software; or++ (b) that the Covered Software was made available under the terms of+ version 1.1 or earlier of the License, but not also under the+ terms of a Secondary License.++1.6. "Executable Form"+ means any form of the work other than Source Code Form.++1.7. "Larger Work"+ means a work that combines Covered Software with other material, in+ a separate file or files, that is not Covered Software.++1.8. "License"+ means this document.++1.9. "Licensable"+ means having the right to grant, to the maximum extent possible,+ whether at the time of the initial grant or subsequently, any and+ all of the rights conveyed by this License.++1.10. "Modifications"+ means any of the following:++ (a) any file in Source Code Form that results from an addition to,+ deletion from, or modification of the contents of Covered+ Software; or++ (b) any new file in Source Code Form that contains any Covered+ Software.++1.11. "Patent Claims" of a Contributor+ means any patent claim(s), including without limitation, method,+ process, and apparatus claims, in any patent Licensable by such+ Contributor that would be infringed, but for the grant of the+ License, by the making, using, selling, offering for sale, having+ made, import, or transfer of either its Contributions or its+ Contributor Version.++1.12. "Secondary License"+ means either the GNU General Public License, Version 2.0, the GNU+ Lesser General Public License, Version 2.1, the GNU Affero General+ Public License, Version 3.0, or any later versions of those+ licenses.++1.13. "Source Code Form"+ means the form of the work preferred for making modifications.++1.14. "You" (or "Your")+ means an individual or a legal entity exercising rights under this+ License. For legal entities, "You" includes any entity that+ controls, is controlled by, or is under common control with You. For+ purposes of this definition, "control" means (a) the power, direct+ or indirect, to cause the direction or management of such entity,+ whether by contract or otherwise, or (b) ownership of more than+ fifty percent (50%) of the outstanding shares or beneficial+ ownership of such entity.++2. License Grants and Conditions+--------------------------------++2.1. Grants++Each Contributor hereby grants You a world-wide, royalty-free,+non-exclusive license:++(a) under intellectual property rights (other than patent or trademark)+ Licensable by such Contributor to use, reproduce, make available,+ modify, display, perform, distribute, and otherwise exploit its+ Contributions, either on an unmodified basis, with Modifications, or+ as part of a Larger Work; and++(b) under Patent Claims of such Contributor to make, use, sell, offer+ for sale, have made, import, and otherwise transfer either its+ Contributions or its Contributor Version.++2.2. Effective Date++The licenses granted in Section 2.1 with respect to any Contribution+become effective for each Contribution on the date the Contributor first+distributes such Contribution.++2.3. Limitations on Grant Scope++The licenses granted in this Section 2 are the only rights granted under+this License. No additional rights or licenses will be implied from the+distribution or licensing of Covered Software under this License.+Notwithstanding Section 2.1(b) above, no patent license is granted by a+Contributor:++(a) for any code that a Contributor has removed from Covered Software;+ or++(b) for infringements caused by: (i) Your and any other third party's+ modifications of Covered Software, or (ii) the combination of its+ Contributions with other software (except as part of its Contributor+ Version); or++(c) under Patent Claims infringed by Covered Software in the absence of+ its Contributions.++This License does not grant any rights in the trademarks, service marks,+or logos of any Contributor (except as may be necessary to comply with+the notice requirements in Section 3.4).++2.4. Subsequent Licenses++No Contributor makes additional grants as a result of Your choice to+distribute the Covered Software under a subsequent version of this+License (see Section 10.2) or under the terms of a Secondary License (if+permitted under the terms of Section 3.3).++2.5. Representation++Each Contributor represents that the Contributor believes its+Contributions are its original creation(s) or it has sufficient rights+to grant the rights to its Contributions conveyed by this License.++2.6. Fair Use++This License is not intended to limit any rights You have under+applicable copyright doctrines of fair use, fair dealing, or other+equivalents.++2.7. Conditions++Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted+in Section 2.1.++3. Responsibilities+-------------------++3.1. Distribution of Source Form++All distribution of Covered Software in Source Code Form, including any+Modifications that You create or to which You contribute, must be under+the terms of this License. You must inform recipients that the Source+Code Form of the Covered Software is governed by the terms of this+License, and how they can obtain a copy of this License. You may not+attempt to alter or restrict the recipients' rights in the Source Code+Form.++3.2. Distribution of Executable Form++If You distribute Covered Software in Executable Form then:++(a) such Covered Software must also be made available in Source Code+ Form, as described in Section 3.1, and You must inform recipients of+ the Executable Form how they can obtain a copy of such Source Code+ Form by reasonable means in a timely manner, at a charge no more+ than the cost of distribution to the recipient; and++(b) You may distribute such Executable Form under the terms of this+ License, or sublicense it under different terms, provided that the+ license for the Executable Form does not attempt to limit or alter+ the recipients' rights in the Source Code Form under this License.++3.3. Distribution of a Larger Work++You may create and distribute a Larger Work under terms of Your choice,+provided that You also comply with the requirements of this License for+the Covered Software. If the Larger Work is a combination of Covered+Software with a work governed by one or more Secondary Licenses, and the+Covered Software is not Incompatible With Secondary Licenses, this+License permits You to additionally distribute such Covered Software+under the terms of such Secondary License(s), so that the recipient of+the Larger Work may, at their option, further distribute the Covered+Software under the terms of either this License or such Secondary+License(s).++3.4. Notices++You may not remove or alter the substance of any license notices+(including copyright notices, patent notices, disclaimers of warranty,+or limitations of liability) contained within the Source Code Form of+the Covered Software, except that You may alter any license notices to+the extent required to remedy known factual inaccuracies.++3.5. Application of Additional Terms++You may choose to offer, and to charge a fee for, warranty, support,+indemnity or liability obligations to one or more recipients of Covered+Software. However, You may do so only on Your own behalf, and not on+behalf of any Contributor. You must make it absolutely clear that any+such warranty, support, indemnity, or liability obligation is offered by+You alone, and You hereby agree to indemnify every Contributor for any+liability incurred by such Contributor as a result of warranty, support,+indemnity or liability terms You offer. You may include additional+disclaimers of warranty and limitations of liability specific to any+jurisdiction.++4. Inability to Comply Due to Statute or Regulation+---------------------------------------------------++If it is impossible for You to comply with any of the terms of this+License with respect to some or all of the Covered Software due to+statute, judicial order, or regulation then You must: (a) comply with+the terms of this License to the maximum extent possible; and (b)+describe the limitations and the code they affect. Such description must+be placed in a text file included with all distributions of the Covered+Software under this License. Except to the extent prohibited by statute+or regulation, such description must be sufficiently detailed for a+recipient of ordinary skill to be able to understand it.++5. Termination+--------------++5.1. The rights granted under this License will terminate automatically+if You fail to comply with any of its terms. However, if You become+compliant, then the rights granted under this License from a particular+Contributor are reinstated (a) provisionally, unless and until such+Contributor explicitly and finally terminates Your grants, and (b) on an+ongoing basis, if such Contributor fails to notify You of the+non-compliance by some reasonable means prior to 60 days after You have+come back into compliance. Moreover, Your grants from a particular+Contributor are reinstated on an ongoing basis if such Contributor+notifies You of the non-compliance by some reasonable means, this is the+first time You have received notice of non-compliance with this License+from such Contributor, and You become compliant prior to 30 days after+Your receipt of the notice.++5.2. If You initiate litigation against any entity by asserting a patent+infringement claim (excluding declaratory judgment actions,+counter-claims, and cross-claims) alleging that a Contributor Version+directly or indirectly infringes any patent, then the rights granted to+You by any and all Contributors for the Covered Software under Section+2.1 of this License shall terminate.++5.3. In the event of termination under Sections 5.1 or 5.2 above, all+end user license agreements (excluding distributors and resellers) which+have been validly granted by You or Your distributors under this License+prior to termination shall survive termination.++************************************************************************+* *+* 6. Disclaimer of Warranty *+* ------------------------- *+* *+* Covered Software is provided under this License on an "as is" *+* basis, without warranty of any kind, either expressed, implied, or *+* statutory, including, without limitation, warranties that the *+* Covered Software is free of defects, merchantable, fit for a *+* particular purpose or non-infringing. The entire risk as to the *+* quality and performance of the Covered Software is with You. *+* Should any Covered Software prove defective in any respect, You *+* (not any Contributor) assume the cost of any necessary servicing, *+* repair, or correction. This disclaimer of warranty constitutes an *+* essential part of this License. No use of any Covered Software is *+* authorized under this License except under this disclaimer. *+* *+************************************************************************++************************************************************************+* *+* 7. Limitation of Liability *+* -------------------------- *+* *+* Under no circumstances and under no legal theory, whether tort *+* (including negligence), contract, or otherwise, shall any *+* Contributor, or anyone who distributes Covered Software as *+* permitted above, be liable to You for any direct, indirect, *+* special, incidental, or consequential damages of any character *+* including, without limitation, damages for lost profits, loss of *+* goodwill, work stoppage, computer failure or malfunction, or any *+* and all other commercial damages or losses, even if such party *+* shall have been informed of the possibility of such damages. This *+* limitation of liability shall not apply to liability for death or *+* personal injury resulting from such party's negligence to the *+* extent applicable law prohibits such limitation. Some *+* jurisdictions do not allow the exclusion or limitation of *+* incidental or consequential damages, so this exclusion and *+* limitation may not apply to You. *+* *+************************************************************************++8. Litigation+-------------++Any litigation relating to this License may be brought only in the+courts of a jurisdiction where the defendant maintains its principal+place of business and such litigation shall be governed by laws of that+jurisdiction, without reference to its conflict-of-law provisions.+Nothing in this Section shall prevent a party's ability to bring+cross-claims or counter-claims.++9. Miscellaneous+----------------++This License represents the complete agreement concerning the subject+matter hereof. If any provision of this License is held to be+unenforceable, such provision shall be reformed only to the extent+necessary to make it enforceable. Any law or regulation which provides+that the language of a contract shall be construed against the drafter+shall not be used to construe this License against a Contributor.++10. Versions of the License+---------------------------++10.1. New Versions++Mozilla Foundation is the license steward. Except as provided in Section+10.3, no one other than the license steward has the right to modify or+publish new versions of this License. Each version will be given a+distinguishing version number.++10.2. Effect of New Versions++You may distribute the Covered Software under the terms of the version+of the License under which You originally received the Covered Software,+or under the terms of any subsequent version published by the license+steward.++10.3. Modified Versions++If you create software not governed by this License, and you want to+create a new license for such software, you may create and use a+modified version of this License if you rename the license and remove+any references to the name of the license steward (except to note that+such modified license differs from this License).++10.4. Distributing Source Code Form that is Incompatible With Secondary+Licenses++If You choose to distribute Source Code Form that is Incompatible With+Secondary Licenses under the terms of this version of the License, the+notice described in Exhibit B of this License must be attached.++Exhibit A - Source Code Form License Notice+-------------------------------------------++ This Source Code Form is subject to the terms of the Mozilla Public+ License, v. 2.0. If a copy of the MPL was not distributed with this+ file, You can obtain one at https://mozilla.org/MPL/2.0/.++If it is not possible or desirable to put the notice in a particular+file, then You may include the notice in a location (such as a LICENSE+file in a relevant directory) where a recipient would be likely to look+for such a notice.++You may add additional accurate notices of copyright ownership.++Exhibit B - "Incompatible With Secondary Licenses" Notice+---------------------------------------------------------++ This Source Code Form is "Incompatible With Secondary Licenses", as+ defined by the Mozilla Public License, v. 2.0.
+ README.md view
@@ -0,0 +1,91 @@+# mcp — MCP server for Haskell++[](https://hackage.haskell.org/package/mcp)++A complete server implementation of the+[Model Context Protocol (MCP)](https://modelcontextprotocol.io/) for Haskell,+built on Servant with JWT authentication via `servant-auth-server`.++Implements MCP protocol version **2025-06-18**. Re-exports the core protocol types from+[`mcp-types`](https://hackage.haskell.org/package/mcp-types) for convenience.++## Modules++- **`MCP.Server`** — Re-exports everything below for convenience.+- **`MCP.Server.Common`** — Transport-agnostic core: types, state management,+ request routing, `ProcessHandlers`, `ToolHandler` framework.+- **`MCP.Server.HTTP`** — Servant-based HTTP transport with JWT authentication+ and streaming SSE responses at the `/mcp` endpoint.+- **`MCP.Server.Stdio`** — Stdio transport reading/writing JSON-RPC messages+ line-by-line, suitable for subprocess-based integrations.++## Install++```cabal+build-depends:+ base+ , servant+ , servant-server+ , servant-auth-server+ , aeson+ , mcp+```++If you only need the protocol types (e.g. for a client), depend on+[`mcp-types`](https://hackage.haskell.org/package/mcp-types) instead.++## Quick Start++```haskell+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeFamilies #-}++import Control.Concurrent.MVar (newMVar)+import MCP.Server++-- Define your handler state and user types+type instance MCPHandlerState = ()+type instance MCPHandlerUser = MyUser++-- Create server state with capabilities+mkServerState :: IO (MVar MCPServerState)+mkServerState = do+ let impl = Implementation "my-server" "1.0.0" Nothing+ caps = ServerCapabilities+ { logging = Nothing+ , prompts = Nothing+ , resources = Nothing+ , tools = Just (ToolsCapability { listChanged = Just True })+ , completions = Nothing+ , experimental = Nothing+ }+ handlers = withToolHandlers myTools defaultProcessHandlers+ newMVar $ initMCPServerState () Nothing Nothing caps impl Nothing handlers++-- Define tools using the ToolHandler framework+myTools :: [ToolHandler]+myTools =+ [ toolHandler "greet" (Just "Say hello") greetSchema $ \_args ->+ return $ ProcessSuccess $ toolTextResult ["Hello!"]+ ]+```++A fully documented example server lives in+[`mcp-server/example/`](https://github.com/DPella/mcp/tree/main/mcp-server/example).++## Features++- **Two transports**: HTTP (Servant + SSE) and stdio+- **JWT authentication** via `servant-auth-server` (HTTP transport)+- **Extensible handler framework**: `ProcessHandlers` record with optional+ handlers for each MCP method+- **Tool helpers**: `ToolHandler`, `toolHandler`, `withToolHandlers`,+ `toolTextResult`, `toolTextError`+- **Server-to-client requests**: `ProcessClientInput` for sampling,+ elicitation, and other client callbacks on both transports+- **MCP 2025-06-18**: Full protocol version support++## License++MPL-2.0
− app/Main.hs
@@ -1,112 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--module Main where--import Control.Monad.IO.Class (liftIO)-import Data.Text qualified as T-import Data.Time (defaultTimeLocale, formatTime, getCurrentTime)-import System.IO (stdin, stdout)--import MCP.Protocol-import MCP.Server-import MCP.Server.StdIO-import MCP.Types---- | Minimal MCP Server implementation-instance MCPServer MCPServerM where- handleListResources _params = do- return $ ListResourcesResult{resources = [], nextCursor = Nothing, _meta = Nothing}-- handleReadResource _params = do- let textContent = TextResourceContents{uri = "example://hello", text = "Hello from MCP Haskell server!", mimeType = Just "text/plain"}- let content = TextResource textContent- return $ ReadResourceResult{contents = [content], _meta = Nothing}-- handleListResourceTemplates _params = do- return $ ListResourceTemplatesResult{resourceTemplates = [], nextCursor = Nothing, _meta = Nothing}-- handleListPrompts _params = do- return $ ListPromptsResult{prompts = [], nextCursor = Nothing, _meta = Nothing}-- handleGetPrompt _params = do- let textContent = TextContent{text = "Hello prompt!", textType = "text", annotations = Nothing}- let content = TextContentType textContent- let message = PromptMessage{role = User, content = content}- return $ GetPromptResult{messages = [message], description = Nothing, _meta = Nothing}-- handleListTools _params = do- let getCurrentDateTool =- Tool- { name = "getCurrentDate"- , description = Just "Get the current date and time"- , inputSchema = InputSchema "object" Nothing Nothing- , annotations = Nothing- }- return $ ListToolsResult{tools = [getCurrentDateTool], nextCursor = Nothing, _meta = Nothing}-- handleCallTool CallToolParams{name = toolName} = do- case toolName of- "getCurrentDate" -> do- currentTime <- liftIO getCurrentTime- let dateStr = formatTime defaultTimeLocale "%Y-%m-%d %H:%M:%S UTC" currentTime- let textContent = TextContent{text = T.pack dateStr, textType = "text", annotations = Nothing}- let content = TextContentType textContent- return $ CallToolResult{content = [content], isError = Nothing, _meta = Nothing}- _ -> do- let textContent = TextContent{text = "Tool not found", textType = "text", annotations = Nothing}- let content = TextContentType textContent- return $ CallToolResult{content = [content], isError = Just True, _meta = Nothing}-- handleComplete _params = do- let completionResult = CompletionResult{values = [], total = Nothing, hasMore = Just True}- return $ CompleteResult{completion = completionResult, _meta = Nothing}-- handleSetLevel _params = do- liftIO $ putStrLn "Log level set"--main :: IO ()-main = do- putStrLn "Starting MCP Haskell Server..."-- let serverInfo =- Implementation- { name = "mcp-haskell-example"- , version = "0.1.0"- }-- let resourcesCap =- ResourcesCapability- { subscribe = Just False- , listChanged = Just False- }- let promptsCap =- PromptsCapability- { listChanged = Just False- }- let toolsCap =- ToolsCapability- { listChanged = Just False- }-- let capabilities =- ServerCapabilities- { resources = Just resourcesCap- , prompts = Just promptsCap- , tools = Just toolsCap- , completions = Nothing- , logging = Nothing- , experimental = Nothing- }-- let config =- MCP.Server.StdIO.ServerConfig- { configInput = stdin- , configOutput = stdout- , configServerInfo = serverInfo- , configCapabilities = capabilities- }-- putStrLn "Server configured, starting message loop..."- MCP.Server.StdIO.runServer config
− examples/README.md
@@ -1,253 +0,0 @@-# MCP Configuration Examples and Usage--This directory contains example configuration files for connecting MCP clients to the Haskell MCP server, as well as example implementations demonstrating different transport methods.--## Claude Desktop Configuration--Claude Desktop reads its MCP server configuration from a JSON file. The location depends on your operating system:--### Configuration File Locations--- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`-- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`-- **Linux**: `~/.config/claude/claude_desktop_config.json`--### Basic Configuration--```json-{- "mcpServers": {- "haskell-mcp": {- "command": "cabal",- "args": ["run", "mcp"],- "cwd": "/absolute/path/to/mcp-haskell"- }- }-}-```--### Development Setup--For development with Cabal:--```json-{- "mcpServers": {- "haskell-mcp-dev": {- "command": "cabal",- "args": ["run", "mcp"],- "cwd": "/home/user/projects/mcp-haskell",- "env": {- "GHC_ENVIRONMENT": "-"- }- }- }-}-```--### Production Setup--For production with a compiled binary:--```json-{- "mcpServers": {- "haskell-mcp-prod": {- "command": "/usr/local/bin/mcp",- "args": [],- "cwd": "/opt/mcp-servers"- }- }-}-```--### Stack-based Setup--If using Stack instead of Cabal:--```json-{- "mcpServers": {- "haskell-mcp-stack": {- "command": "stack",- "args": ["exec", "mcp"],- "cwd": "/path/to/mcp-haskell"- }- }-}-```--## Configuration Fields--- **command**: The executable to run (cabal, stack, or direct binary path)-- **args**: Arguments passed to the command-- **cwd**: Working directory (should be the project root for development)-- **env**: Environment variables (optional)--## Testing the Configuration--1. Save the configuration to the appropriate location for your OS-2. Restart Claude Desktop-3. Start a new conversation-4. The Haskell MCP server should appear in the available tools/context--## Troubleshooting--### Common Issues--1. **Path Problems**: Ensure `cwd` points to the correct project directory-2. **Permission Issues**: Make sure the command is executable-3. **Build Issues**: Run `cabal build` first to ensure the project compiles-4. **Port Conflicts**: Each server needs a unique name in the configuration--### Debugging--To test the server manually:--```bash-cd /path/to/mcp-haskell-cabal run mcp-```--The server should start and wait for JSON-RPC messages on stdin.--### Logs--Claude Desktop logs can help debug connection issues:--- **macOS**: `~/Library/Logs/Claude/`-- **Windows**: `%LOCALAPPDATA%\Claude\logs\`-- **Linux**: `~/.local/share/claude/logs/`--## Example Usage--Once configured, you can:--1. **List Resources**: Ask Claude to show available resources-2. **Read Content**: Request specific resource content-3. **Use Tools**: Execute tools provided by the server-4. **Get Prompts**: Use pre-defined prompt templates--The Haskell MCP server provides basic examples of each capability that you can extend for your specific use case.-------## HTTP Server Example--**File:** `http-server.hs`--Demonstrates how to run an MCP server using HTTP transport instead of StdIO.--### Building and Running--**Using Cabal (recommended):**-```bash-# From the project root-cabal build mcp-http-cabal run mcp-http-```--**Manual compilation:**-```bash-# From the project root-ghc -package-env=. -o examples/http-server examples/http-server.hs-cd examples-./http-server-```--**Using cabal exec:**-```bash-cabal exec ghc -- -package-env=. -o examples/http-server examples/http-server.hs-cd examples -./http-server-```--### Testing the HTTP Server--Once running, the server exposes the MCP API at `POST http://localhost:8080/mcp`.--**Test with curl:**--```bash-# Ping test-curl -X POST http://localhost:8080/mcp \- -H "Content-Type: application/json" \- -d '{"jsonrpc":"2.0","id":1,"method":"ping"}'--# Initialize the server-curl -X POST http://localhost:8080/mcp \- -H "Content-Type: application/json" \- -d '{- "jsonrpc":"2.0",- "id":1,- "method":"initialize",- "params":{- "protocolVersion":"2024-11-05",- "capabilities":{},- "clientInfo":{"name":"test-client","version":"1.0.0"}- }- }'--# List available tools-curl -X POST http://localhost:8080/mcp \- -H "Content-Type: application/json" \- -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'--# Call the getCurrentDate tool-curl -X POST http://localhost:8080/mcp \- -H "Content-Type: application/json" \- -d '{- "jsonrpc":"2.0",- "id":3,- "method":"tools/call",- "params":{"name":"getCurrentDate"}- }'-```--**Test with HTTPie:**--```bash-# Ping test-http POST localhost:8080/mcp jsonrpc=2.0 id:=1 method=ping--# Initialize-http POST localhost:8080/mcp \- jsonrpc=2.0 id:=1 method=initialize \- params:='{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test-client","version":"1.0.0"}}'--# List tools-http POST localhost:8080/mcp jsonrpc=2.0 id:=2 method=tools/list--# Call tool-http POST localhost:8080/mcp \- jsonrpc=2.0 id:=3 method=tools/call \- params:='{"name":"getCurrentDate"}'-```--## Key Differences: StdIO vs HTTP--| Aspect | StdIO Transport | HTTP Transport |-|--------|----------------|----------------|-| **Client Integration** | Process-based (stdin/stdout) | HTTP clients, web apps |-| **Message Format** | Line-delimited JSON-RPC | HTTP POST with JSON body |-| **Server Lifecycle** | Managed by client process | Independent HTTP service |-| **Debugging** | Log to stderr | HTTP access logs |-| **Scalability** | One client per process | Multiple concurrent clients |-| **Network** | Local only | Network accessible |--## Implementation Notes--- Both transports use the same `MCPServer` typeclass implementation-- Server logic is identical between StdIO and HTTP modes-- HTTP transport follows the MCP specification for streamable HTTP-- Future versions will support Server-Sent Events (SSE) for bidirectional communication--## Error Handling--The HTTP server returns appropriate HTTP status codes:--- **200 OK**: Successful JSON-RPC response-- **400 Bad Request**: Invalid JSON or malformed JSON-RPC-- **500 Internal Server Error**: Server-side processing errors--JSON-RPC errors are returned within the 200 response body following the JSON-RPC 2.0 specification.
− examples/claude-desktop-config.json
@@ -1,24 +0,0 @@-{- "mcpServers": {- "haskell-mcp-development": {- "command": "cabal",- "args": ["run", "mcp"],- "cwd": "/absolute/path/to/mcp-haskell",- "env": {- "GHC_ENVIRONMENT": "-"- }- },- "haskell-mcp-production": {- "command": "/usr/local/bin/mcp-haskell",- "args": [],- "cwd": "/opt/mcp-servers",- "env": {}- },- "haskell-mcp-stack": {- "command": "stack",- "args": ["exec", "mcp"],- "cwd": "/path/to/mcp-haskell",- "env": {}- }- }-}
− examples/full-config-example.json
@@ -1,34 +0,0 @@-{- "$schema": "https://schemas.claudeai.com/claude_desktop_config.json",- "mcpServers": {- "haskell-mcp-example": {- "command": "cabal",- "args": ["run", "mcp"],- "cwd": "/Users/username/projects/mcp-haskell",- "env": {- "GHC_ENVIRONMENT": "-",- "CABAL_CONFIG": ""- }- },- "haskell-mcp-with-logging": {- "command": "cabal", - "args": ["run", "mcp", "--", "--verbose"],- "cwd": "/Users/username/projects/mcp-haskell",- "env": {- "MCP_LOG_LEVEL": "debug"- }- },- "haskell-mcp-custom-tools": {- "command": "/usr/local/bin/mcp-haskell-tools",- "args": ["--config", "/etc/mcp/tools.conf"],- "cwd": "/var/lib/mcp",- "env": {- "MCP_TOOLS_PATH": "/usr/local/share/mcp-tools",- "MCP_RESOURCES_PATH": "/var/lib/mcp/resources"- }- }- },- "globalShortcuts": {- "mcp": "Cmd+Shift+M"- }-}
− examples/http-server.hs
@@ -1,244 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--{- |-Example HTTP MCP Server--This example demonstrates how to run the MCP server over HTTP transport.-The server will expose the MCP API at POST /mcp--To test:-1. Compile: cabal build mcp-http-2. Run: cabal run mcp-http-3. Send JSON-RPC requests to: http://localhost:<port>/mcp--Example request:-curl -X POST http://localhost:8080/mcp \- -H "Content-Type: application/json" \- -d '{"jsonrpc":"2.0","id":1,"method":"ping"}'--Command line options:-cabal run mcp-http -- --port 8080 --log--}-module Main where--import Control.Monad (when)-import Control.Monad.IO.Class (liftIO)-import Data.Text qualified as T-import Data.Time (defaultTimeLocale, formatTime, getCurrentTime)-import Options.Applicative--import MCP.Protocol hiding (CompletionResult)-import MCP.Protocol qualified as Protocol-import MCP.Server-import MCP.Server.Auth-import MCP.Server.HTTP-import MCP.Types---- | Command line options-data Options = Options- { optPort :: Int- , optEnableLogging :: Bool- , optEnableOAuth :: Bool- }- deriving (Show)---- | Parser for command line options-optionsParser :: Parser Options-optionsParser =- Options- <$> option- auto- ( long "port"- <> short 'p'- <> metavar "PORT"- <> Options.Applicative.value 8080- <> help "Port to run the HTTP server on (default: 8080)"- )- <*> switch- ( long "log"- <> short 'l'- <> help "Enable request/response logging"- )- <*> switch- ( long "oauth"- <> short 'o'- <> help "Enable OAuth authentication (demo mode)"- )---- | Full parser with help-opts :: ParserInfo Options-opts =- info- (optionsParser <**> helper)- ( fullDesc- <> progDesc "Run an MCP server over HTTP transport"- <> header "mcp-http - HTTP MCP Server Example"- )---- | Example MCP Server implementation (copied from Main.hs)-instance MCPServer MCPServerM where- handleListResources _params = do- return $ ListResourcesResult{resources = [], nextCursor = Nothing, _meta = Nothing}-- handleReadResource _params = do- let textContent = TextResourceContents{uri = "example://hello", text = "Hello from MCP Haskell HTTP server!", mimeType = Just "text/plain"}- let content = TextResource textContent- return $ ReadResourceResult{contents = [content], _meta = Nothing}-- handleListResourceTemplates _params = do- return $ ListResourceTemplatesResult{resourceTemplates = [], nextCursor = Nothing, _meta = Nothing}-- handleListPrompts _params = do- return $ ListPromptsResult{prompts = [], nextCursor = Nothing, _meta = Nothing}-- handleGetPrompt _params = do- let textContent = TextContent{text = "Hello HTTP prompt!", textType = "text", annotations = Nothing}- let content = TextContentType textContent- let message = PromptMessage{role = User, content = content}- return $ GetPromptResult{messages = [message], description = Nothing, _meta = Nothing}-- handleListTools _params = do- let getCurrentDateTool =- Tool- { name = "getCurrentDate"- , description = Just "Get the current date and time via HTTP"- , inputSchema = InputSchema "object" Nothing Nothing- , annotations = Nothing- }- return $ ListToolsResult{tools = [getCurrentDateTool], nextCursor = Nothing, _meta = Nothing}-- handleCallTool CallToolParams{name = toolName} = do- case toolName of- "getCurrentDate" -> do- currentTime <- liftIO getCurrentTime- let dateStr = formatTime defaultTimeLocale "%Y-%m-%d %H:%M:%S UTC (via HTTP)" currentTime- let textContent = TextContent{text = T.pack dateStr, textType = "text", annotations = Nothing}- let content = TextContentType textContent- return $ CallToolResult{content = [content], isError = Nothing, _meta = Nothing}- _ -> do- let textContent = TextContent{text = "Tool not found", textType = "text", annotations = Nothing}- let content = TextContentType textContent- return $ CallToolResult{content = [content], isError = Just True, _meta = Nothing}-- handleComplete _params = do- let completionResult = Protocol.CompletionResult{values = [], total = Nothing, hasMore = Just True}- return $ CompleteResult{completion = completionResult, _meta = Nothing}-- handleSetLevel _params = do- liftIO $ putStrLn "Log level set via HTTP"--main :: IO ()-main = do- Options{..} <- execParser opts-- putStrLn "Starting MCP Haskell HTTP Server..."- putStrLn $ "Port: " ++ show optPort- when optEnableLogging $ putStrLn "Request/Response logging: enabled"-- let serverInfo =- Implementation- { name = "mcp-haskell-http-example"- , version = "0.1.0"- }-- let resourcesCap =- ResourcesCapability- { subscribe = Just False- , listChanged = Just False- }- let promptsCap =- PromptsCapability- { listChanged = Just False- }- let toolsCap =- ToolsCapability- { listChanged = Just False- }-- let capabilities =- ServerCapabilities- { resources = Just resourcesCap- , prompts = Just promptsCap- , tools = Just toolsCap- , completions = Nothing- , logging = Nothing- , experimental = Nothing- }-- let baseUrl = T.pack $ "http://localhost:" ++ show optPort- oauthConfig =- if optEnableOAuth- then- Just $- defaultDemoOAuthConfig- { oauthProviders =- [ OAuthProvider- { providerName = "demo"- , clientId = "demo-client"- , clientSecret = Just "demo-secret"- , authorizationEndpoint = baseUrl <> "/authorize"- , tokenEndpoint = baseUrl <> "/token"- , userInfoEndpoint = Nothing- , scopes = ["mcp:read", "mcp:write"]- , grantTypes = [AuthorizationCode]- , requiresPKCE = True -- MCP requires PKCE- , metadataEndpoint = Nothing- }- ]- , -- Override demo defaults for example- authCodeExpirySeconds = 600 -- 10 minutes- , accessTokenExpirySeconds = 3600 -- 1 hour- , demoUserIdTemplate = Just "demo-user-{clientId}"- , demoEmailDomain = "demo.example.com"- , demoUserName = "Demo User"- , authorizationSuccessTemplate =- Just $- "Demo Authorization Successful!\n\n"- <> "Redirect to: {redirectUri}?code={code}{state}\n\n"- <> "This is a demo server. In production, this would redirect automatically."- }- else Nothing-- let config =- HTTPServerConfig- { httpPort = optPort- , httpBaseUrl = baseUrl -- Configurable base URL- , httpServerInfo = serverInfo- , httpCapabilities = capabilities- , httpEnableLogging = optEnableLogging- , httpOAuthConfig = oauthConfig- , httpJWK = Nothing -- Will be auto-generated- , httpProtocolVersion = "2024-11-05" -- Configurable protocol version- }-- putStrLn $ "HTTP server configured, starting on port " ++ show optPort ++ "..."- putStrLn $ "MCP endpoint available at: POST " ++ T.unpack baseUrl ++ "/mcp"-- if optEnableOAuth- then do- putStrLn ""- putStrLn "OAuth Demo Flow:"- putStrLn "1. Generate PKCE code verifier and challenge"- putStrLn "2. Open authorization URL in browser:"- putStrLn $ " " ++ T.unpack baseUrl ++ "/authorize?response_type=code&client_id=demo-client&redirect_uri=http://localhost:3000/callback&code_challenge=YOUR_CHALLENGE&code_challenge_method=S256&scope=mcp:read%20mcp:write"- putStrLn "3. Exchange authorization code for token:"- putStrLn $ " curl -X POST " ++ T.unpack baseUrl ++ "/token \\"- putStrLn " -H \"Content-Type: application/x-www-form-urlencoded\" \\"- putStrLn " -d \"grant_type=authorization_code&code=AUTH_CODE&code_verifier=YOUR_VERIFIER\""- putStrLn "4. Use access token for MCP requests:"- putStrLn $ " curl -X POST " ++ T.unpack baseUrl ++ "/mcp \\"- putStrLn " -H \"Authorization: Bearer ACCESS_TOKEN\" \\"- putStrLn " -H \"Content-Type: application/json\" \\"- putStrLn " -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"ping\"}'"- else do- putStrLn ""- putStrLn "Example test command:"- putStrLn $ "curl -X POST " ++ T.unpack baseUrl ++ "/mcp \\"- putStrLn " -H \"Content-Type: application/json\" \\"- putStrLn " -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"ping\"}'"-- putStrLn ""-- runServerHTTP config
mcp.cabal view
@@ -1,220 +1,131 @@-cabal-version: 3.4--- The cabal-version field refers to the version of the .cabal specification,--- and can be different from the cabal-install (the tool) version and the--- Cabal (the library) version you are using. As such, the Cabal (the library)--- version used must be equal or greater than the version stated in this field.--- Starting from the specification version 2.2, the cabal-version field must be--- the first thing in the cabal file.---- Initial package description 'mcp' generated by--- 'cabal init'. For further documentation, see:--- http://haskell.org/cabal/users-guide/------ The name of the package.-name: mcp---- The package version.--- See the Haskell package versioning policy (PVP) for standards--- guiding when and how versions should be incremented.--- https://pvp.haskell.org--- PVP summary: +-+------- breaking API changes--- | | +----- non-breaking API additions--- | | | +--- code changes with no API change-version: 0.2.0.1---- A short (one-line) description of the package.-synopsis: A Haskell implementation of the Model Context Protocol (MCP)---- A longer description of the package.-description: - This library provides a complete implementation of the Model Context Protocol (MCP)- for Haskell. MCP is a protocol that enables seamless communication between AI models- and external tools, resources, and services. This implementation includes support for- resources, tools, prompts, and all standard MCP message types. It provides both a- server framework and type definitions for building MCP-compliant applications.- .- Features dual transport support:- .- * StdIO transport for process-based clients (e.g., Claude Desktop)- * HTTP transport following the official MCP specification for web-based integration,- and supports OAuth authentication.- .- Both transports use the same MCPServer typeclass, allowing seamless switching between- communication methods while maintaining identical server logic.- Supports version 2025-03-26 of the MCP protocol.---- The license under which the package is released.-license: MIT---- The file containing the license text.-license-file: LICENSE---- The package author(s).-author: Matthias Pall Gissurarson---- An email address to which users can send suggestions, bug reports, and patches.-maintainer: mpg@mpg.is---- A copyright notice.--- copyright:-category: Network-build-type: Simple+cabal-version: 2.2+name: mcp+version: 0.3.0.1+license: MPL-2.0+license-file: LICENSE+copyright: (c) 2025 DPella AB+author: DPella AB+tested-with: ghc ==9.12.2+extra-doc-files:+ CHANGELOG.md+ README.md --- Extra doc files to be distributed with the package, such as a CHANGELOG or a README.-extra-doc-files: CHANGELOG.md+maintainer:+ matti@dpella.io,+ lobo@dpella.io --- Extra source files to be distributed with the package, such as examples, or a tutorial module.-extra-source-files:- examples/README.md- examples/http-server.hs- examples/claude-desktop-config.json- examples/full-config-example.json+synopsis: A Servant-based Model Context Protocol (MCP) server for Haskell+description:+ This library provides a complete server implementation of the Model Context+ Protocol (MCP) for Haskell, built on Servant with JWT authentication via+ @servant-auth-server@. It re-exports the core protocol types from+ @mcp-types@ for convenience.+ .+ MCP is a protocol that enables seamless communication between AI models and+ external tools, resources, and services. This implementation supports MCP+ protocol version 2025-06-18 with full compatibility for resources, tools,+ prompts, completions, elicitation, and all standard MCP message types.+ .+ The server uses HTTP transport with streaming SSE responses at the @/mcp@+ endpoint. An extensible handler framework (@ProcessHandlers@, @ToolHandler@)+ allows implementing custom MCP servers with minimal boilerplate. -homepage: https://github.com/Tritlo/mcp+homepage: https://github.com/DPella/mcp+bug-reports: https://github.com/DPella/mcp/issues+category: Web, AI source-repository head- type: git- location: https://github.com/Tritlo/mcp.git--common warnings- ghc-options: -Wall+ type: git+ location: https://github.com/DPella/mcp.git library- -- Import common warning flags.- import: warnings-- -- Modules exported by the library.- exposed-modules: - MCP.Types- MCP.Protocol - MCP.Server- MCP.Server.StdIO- MCP.Server.HTTP- MCP.Server.Auth-- -- Modules included in this library but not exported.- -- other-modules:-- -- LANGUAGE extensions used by modules in this package.- -- other-extensions:-- -- Other library packages from which modules are imported.- build-depends: - base ^>=4.18.2.1,- aeson >= 2.1 && < 2.3,- text >= 2.0 && < 2.1,- containers >= 0.6 && < 0.7,- bytestring >= 0.11 && < 0.12,- unordered-containers >= 0.2 && < 0.3,- stm >= 2.5 && < 2.6,- async >= 2.2 && < 2.3,- mtl >= 2.3 && < 2.4,- transformers >= 0.6 && < 0.7,- warp >= 3.3 && < 3.4,- wai >= 3.2 && < 3.3,- wai-extra >= 3.1 && < 3.2,- servant-server >= 0.19 && < 0.21,- servant >= 0.19 && < 0.21,- http-types >= 0.12 && < 0.13,- servant-auth >= 0.4 && < 0.5,- servant-auth-server >= 0.4 && < 0.5,- jose >= 0.10 && < 0.12,- cryptonite >= 0.30 && < 0.31,- memory >= 0.18 && < 0.19,- base64-bytestring >= 1.2 && < 1.3,- http-conduit >= 2.3 && < 2.4,- random >= 1.2 && < 1.3,- time >= 1.12 && < 1.13,- uuid >= 1.3 && < 1.4-- -- Directories containing source files.- hs-source-dirs: src-- -- Base language which the package is written in.- default-language: GHC2021--executable mcp- -- Import common warning flags.- import: warnings-- -- .hs or .lhs file containing the Main module.- main-is: Main.hs-- -- Modules included in this executable, other than Main.- -- other-modules:-- -- LANGUAGE extensions used by modules in this package.- -- other-extensions:-- -- Other library packages from which modules are imported.- build-depends:- base ^>=4.18.2.1,- mcp,- aeson >= 2.1 && < 2.3,- text >= 2.0 && < 2.1,- containers >= 0.6 && < 0.7,- scientific >= 0.3 && < 0.4,- time >= 1.12 && < 1.13-- -- Directories containing source files.- hs-source-dirs: app-- -- Base language which the package is written in.- default-language: GHC2021--executable mcp-http- -- Import common warning flags.- import: warnings-- -- .hs or .lhs file containing the Main module.- main-is: http-server.hs-- -- Modules included in this executable, other than Main.- -- other-modules:-- -- LANGUAGE extensions used by modules in this package.- -- other-extensions:-- -- Other library packages from which modules are imported.- build-depends:- base ^>=4.18.2.1,- mcp,- aeson >= 2.1 && < 2.3,- text >= 2.0 && < 2.1,- containers >= 0.6 && < 0.7,- scientific >= 0.3 && < 0.4,- time >= 1.12 && < 1.13,- optparse-applicative >= 0.17 && < 0.19+ default-language:+ Haskell2010 - -- Directories containing source files.- hs-source-dirs: examples+ default-extensions:+ DataKinds+ FlexibleInstances+ GADTs+ GeneralizedNewtypeDeriving+ ImportQualifiedPost+ LambdaCase+ OverloadedStrings+ PolyKinds+ RankNTypes+ TypeApplications+ TypeFamilies - -- Base language which the package is written in.- default-language: GHC2021+ hs-source-dirs:+ src -test-suite mcp-test- -- Import common warning flags.- import: warnings+ exposed-modules:+ MCP.Server+ MCP.Server.Common+ MCP.Server.HTTP+ MCP.Server.Stdio - -- Base language which the package is written in.- default-language: GHC2021+ build-depends:+ aeson >=2.1 && <2.3,+ base >=4.18 && <4.22,+ bytestring >=0.11 && <0.13,+ containers >=0.6 && <0.8,+ http-media >=0.8 && <0.9,+ mcp-types >=0.1.0 && <0.2,+ mtl >=2.2 && <2.4,+ servant >=0.19 && <0.21,+ servant-auth-server >=0.4 && <0.5,+ servant-server >=0.19 && <0.21,+ text >=2.0 && <2.2,+ time >=1.12 && <1.15, - -- Modules included in this executable, other than Main.- -- other-modules:+test-suite test-mcp+ type: exitcode-stdio-1.0+ default-language:+ Haskell2010 - -- LANGUAGE extensions used by modules in this package.- -- other-extensions:+ default-extensions:+ DataKinds+ FlexibleInstances+ GADTs+ GeneralizedNewtypeDeriving+ ImportQualifiedPost+ LambdaCase+ OverloadedStrings+ PolyKinds+ RankNTypes+ TypeApplications - -- The interface type and version of the test suite.- type: exitcode-stdio-1.0+ hs-source-dirs:+ test - -- Directories containing source files.- hs-source-dirs: test+ main-is:+ Main.hs - -- The entrypoint to the test suite.- main-is: Main.hs+ other-modules:+ MCP.Integration+ MCP.StdioIntegration+ MCP.TestServer+ MCP.TestUtils - -- Test dependencies.- build-depends:- base ^>=4.18.2.1,- mcp+ build-depends:+ aeson >=2.1 && <2.3,+ attoparsec >=0.14 && <0.15,+ base >=4.18 && <4.22,+ bytestring >=0.11 && <0.13,+ containers >=0.6 && <0.8,+ hspec >=2.10 && <2.12,+ hspec-wai >=0.11 && <0.12,+ http-types >=0.12 && <0.13,+ jose >=0.10 && <0.12,+ mcp,+ mcp-types >=0.1.0 && <0.2,+ mtl >=2.2 && <2.4,+ process >=1.6 && <1.8,+ servant >=0.19 && <0.21,+ servant-auth-server >=0.4 && <0.5,+ servant-server >=0.19 && <0.21,+ text >=2.0 && <2.2,+ time >=1.12 && <1.15,+ wai >=3.2 && <3.3,+ wai-extra >=3.1 && <3.2,+ wai-test >=3.0 && <3.1,
− src/MCP/Protocol.hs
@@ -1,1132 +0,0 @@-{-# 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)
src/MCP/Server.hs view
@@ -1,118 +1,23 @@-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE DuplicateRecordFields #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TypeFamilies #-}- {- |-Module : MCP.Server-Description : MCP server core types and interface-Copyright : (C) 2025 Matthias Pall Gissurarson-License : MIT-Maintainer : mpg@mpg.is-Stability : experimental-Portability : GHC--This module provides the core types and interface for MCP server implementations.--}-module MCP.Server (- -- * Server Interface- MCPServer (..),- ServerState (..),- ServerConfig (..),- MCPServerM,- runMCPServer,- initialServerState,-- -- * Utilities- sendResponse,- sendNotification,- sendError,-) where--import Control.Monad.Except (ExceptT, runExceptT)-import Control.Monad.IO.Class (MonadIO, liftIO)-import Control.Monad.Reader (ReaderT, runReaderT)-import Control.Monad.State.Strict (StateT, runStateT)-import Data.Aeson (ToJSON, encode, toJSON)-import Data.ByteString.Lazy.Char8 qualified as LBSC-import Data.Map.Strict (Map)-import Data.Map.Strict qualified as Map-import Data.Text (Text)-import System.IO (Handle, hFlush)--import MCP.Protocol-import MCP.Types---- | Server state tracking initialization, capabilities, and subscriptions-data ServerState = ServerState- { serverInitialized :: Bool- , serverCapabilities :: ServerCapabilities- , clientCapabilities :: Maybe ClientCapabilities- , serverInfo :: Maybe Implementation- , subscriptions :: Map Text ()- }- deriving (Show)---- | Configuration for running an MCP server-data ServerConfig = ServerConfig- { configInput :: Handle- , configOutput :: Handle- , configServerInfo :: Implementation- , configCapabilities :: ServerCapabilities- }- deriving (Show)+Module: MCP.Server+License: MPL-2.0+Maintainer: <matti@dpella.io>, <lobo@dpella.io> --- | The monad stack for MCP server operations-type MCPServerM = ReaderT ServerConfig (StateT ServerState (ExceptT Text IO))+Model Context Protocol (MCP) server implementation. --- | Run an MCPServerM computation with the given config and initial state-runMCPServer :: ServerConfig -> ServerState -> MCPServerM a -> IO (Either Text (a, ServerState))-runMCPServer config state action = runExceptT $ runStateT (runReaderT action config) state+This module re-exports everything from the transport-specific modules+for backwards compatibility. For finer-grained imports use: -{- | Create the initial server state with the given capabilities-The server starts uninitialized and must receive an 'initialize' request-before it can handle other requests.+* "MCP.Server.Common" — types, state, request routing, tool helpers+* "MCP.Server.HTTP" — Servant-based HTTP transport with JWT auth+* "MCP.Server.Stdio" — stdio transport -}-initialServerState :: ServerCapabilities -> ServerState-initialServerState caps =- ServerState- { serverInitialized = False- , serverCapabilities = caps- , clientCapabilities = Nothing- , serverInfo = Nothing- , subscriptions = Map.empty- }---- | Type class for implementing MCP server handlers-class (Monad m) => MCPServer m where- handleListResources :: ListResourcesParams -> m ListResourcesResult- handleReadResource :: ReadResourceParams -> m ReadResourceResult- handleListResourceTemplates :: ListResourceTemplatesParams -> m ListResourceTemplatesResult- handleListPrompts :: ListPromptsParams -> m ListPromptsResult- handleGetPrompt :: GetPromptParams -> m GetPromptResult- handleListTools :: ListToolsParams -> m ListToolsResult- handleCallTool :: CallToolParams -> m CallToolResult- handleComplete :: CompleteParams -> m CompleteResult- handleSetLevel :: SetLevelParams -> m ()---- | Send a JSON-RPC response-sendResponse :: (MonadIO m, ToJSON a) => Handle -> RequestId -> a -> m ()-sendResponse handle reqId result = liftIO $ do- let response = JSONRPCResponse "2.0" reqId (toJSON result)- LBSC.hPutStrLn handle (encode response)- hFlush handle---- | Send a JSON-RPC error response-sendError :: (MonadIO m) => Handle -> RequestId -> JSONRPCErrorInfo -> m ()-sendError handle reqId errorInfo = liftIO $ do- let response = JSONRPCError "2.0" reqId errorInfo- LBSC.hPutStrLn handle (encode response)- hFlush handle+module MCP.Server (+ module MCP.Server.Common,+ module MCP.Server.HTTP,+ module MCP.Server.Stdio,+) where --- | Send a JSON-RPC notification-sendNotification :: (MonadIO m, ToJSON a) => Handle -> Text -> a -> m ()-sendNotification handle method params = liftIO $ do- let notification = JSONRPCNotification "2.0" method (Just (toJSON params))- LBSC.hPutStrLn handle (encode notification)- hFlush handle+import MCP.Server.Common+import MCP.Server.HTTP+import MCP.Server.Stdio
− src/MCP/Server/Auth.hs
@@ -1,307 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE DuplicateRecordFields #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}--{- |-Module : MCP.Server.Auth-Description : MCP-compliant OAuth 2.1 authentication-Copyright : (C) 2025 Matthias Pall Gissurarson-License : MIT-Maintainer : mpg@mpg.is-Stability : experimental-Portability : GHC--This module provides MCP-compliant OAuth 2.1 authentication with PKCE support.--}-module MCP.Server.Auth (- -- * OAuth Configuration- OAuthConfig (..),- OAuthProvider (..),- OAuthGrantType (..),-- -- * Token Validation- TokenInfo (..),- validateBearerToken,- extractBearerToken,-- -- * PKCE Support- PKCEChallenge (..),- generateCodeVerifier,- generateCodeChallenge,- validateCodeVerifier,-- -- * Metadata Discovery- OAuthMetadata (..),- discoverOAuthMetadata,-) where--import Control.Monad.IO.Class (MonadIO, liftIO)-import Crypto.Hash (hashWith)-import Crypto.Hash.Algorithms (SHA256 (..))-import Data.Aeson (FromJSON, ToJSON)-import Data.Aeson qualified as Aeson-import Data.ByteArray (convert)-import Data.ByteString (ByteString)-import Data.ByteString.Base64.URL qualified as B64URL-import Data.ByteString.Lazy qualified as LBS-import Data.Text (Text)-import Data.Text qualified as T-import Data.Text.Encoding qualified as TE-import Data.Time.Clock (UTCTime, getCurrentTime)-import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds)-import GHC.Generics (Generic)-import Network.HTTP.Simple (addRequestHeader, getResponseBody, httpJSON, parseRequest, setRequestBodyJSON, setRequestMethod)-import System.Random (newStdGen, randomRs)---- | OAuth grant types supported by MCP-data OAuthGrantType- = AuthorizationCode -- For user-based scenarios- | ClientCredentials -- For application-to-application- deriving (Show, Eq, Generic)---- | OAuth provider configuration (MCP-compliant)-data OAuthProvider = OAuthProvider- { providerName :: Text- , clientId :: Text- , clientSecret :: Maybe Text -- Optional for public clients- , authorizationEndpoint :: Text- , tokenEndpoint :: Text- , userInfoEndpoint :: Maybe Text- , scopes :: [Text]- , grantTypes :: [OAuthGrantType]- , requiresPKCE :: Bool -- MCP requires PKCE for all clients- , metadataEndpoint :: Maybe Text -- For OAuth metadata discovery- }- deriving (Show, Generic)---- | OAuth configuration for the MCP server-data OAuthConfig = OAuthConfig- { oauthEnabled :: Bool- , oauthProviders :: [OAuthProvider]- , tokenValidationEndpoint :: Maybe Text -- For validating tokens- , requireHTTPS :: Bool -- MCP requires HTTPS for OAuth- -- Configurable timing parameters- , authCodeExpirySeconds :: Int- , accessTokenExpirySeconds :: Int- , -- Configurable OAuth parameters- supportedScopes :: [Text]- , supportedResponseTypes :: [Text]- , supportedGrantTypes :: [Text]- , supportedAuthMethods :: [Text]- , supportedCodeChallengeMethods :: [Text]- , -- Demo mode settings- autoApproveAuth :: Bool- , demoUserIdTemplate :: Maybe Text -- Nothing means no demo mode- , demoEmailDomain :: Text- , demoUserName :: Text- , publicClientSecret :: Maybe Text- , -- Token prefixes- authCodePrefix :: Text- , refreshTokenPrefix :: Text- , clientIdPrefix :: Text- , -- Response templates- authorizationSuccessTemplate :: Maybe Text- }- deriving (Show, Generic)---- | PKCE challenge data-data PKCEChallenge = PKCEChallenge- { codeVerifier :: Text- , codeChallenge :: Text- , challengeMethod :: Text -- Always "S256" for MCP- }- deriving (Show, Generic)---- | OAuth metadata (from discovery endpoint)-data OAuthMetadata = OAuthMetadata- { issuer :: Text- , authorizationEndpoint :: Text- , tokenEndpoint :: Text- , registrationEndpoint :: Maybe Text- , userInfoEndpoint :: Maybe Text- , jwksUri :: Maybe Text- , scopesSupported :: Maybe [Text]- , responseTypesSupported :: [Text]- , grantTypesSupported :: Maybe [Text]- , tokenEndpointAuthMethodsSupported :: Maybe [Text]- , codeChallengeMethodsSupported :: Maybe [Text]- }- deriving (Show, Generic)--instance FromJSON OAuthMetadata where- parseJSON = Aeson.withObject "OAuthMetadata" $ \v ->- OAuthMetadata- <$> v Aeson..: "issuer"- <*> v Aeson..: "authorization_endpoint"- <*> v Aeson..: "token_endpoint"- <*> v Aeson..:? "registration_endpoint"- <*> v Aeson..:? "userinfo_endpoint"- <*> v Aeson..:? "jwks_uri"- <*> v Aeson..:? "scopes_supported"- <*> v Aeson..: "response_types_supported"- <*> v Aeson..:? "grant_types_supported"- <*> v Aeson..:? "token_endpoint_auth_methods_supported"- <*> v Aeson..:? "code_challenge_methods_supported"--instance ToJSON OAuthMetadata where- toJSON OAuthMetadata{..} =- Aeson.object $- [ "issuer" Aeson..= issuer- , "authorization_endpoint" Aeson..= authorizationEndpoint- , "token_endpoint" Aeson..= tokenEndpoint- , "response_types_supported" Aeson..= responseTypesSupported- ]- ++ maybe [] (\x -> ["registration_endpoint" Aeson..= x]) registrationEndpoint- ++ maybe [] (\x -> ["userinfo_endpoint" Aeson..= x]) userInfoEndpoint- ++ maybe [] (\x -> ["jwks_uri" Aeson..= x]) jwksUri- ++ maybe [] (\x -> ["scopes_supported" Aeson..= x]) scopesSupported- ++ maybe [] (\x -> ["grant_types_supported" Aeson..= x]) grantTypesSupported- ++ maybe [] (\x -> ["token_endpoint_auth_methods_supported" Aeson..= x]) tokenEndpointAuthMethodsSupported- ++ maybe [] (\x -> ["code_challenge_methods_supported" Aeson..= x]) codeChallengeMethodsSupported---- | Token introspection response-data TokenInfo = TokenInfo- { active :: Bool- , scope :: Maybe Text- , clientId :: Maybe Text- , username :: Maybe Text- , tokenType :: Maybe Text- , exp :: Maybe Integer -- Expiration time (Unix timestamp)- , iat :: Maybe Integer -- Issued at time (Unix timestamp)- , nbf :: Maybe Integer -- Not before time (Unix timestamp)- , sub :: Maybe Text -- Subject- , aud :: Maybe [Text] -- Audience- , iss :: Maybe Text -- Issuer- }- deriving (Show, Generic)--instance FromJSON TokenInfo where- parseJSON = Aeson.withObject "TokenInfo" $ \v ->- TokenInfo- <$> v Aeson..: "active"- <*> v Aeson..:? "scope"- <*> v Aeson..:? "client_id"- <*> v Aeson..:? "username"- <*> v Aeson..:? "token_type"- <*> v Aeson..:? "exp"- <*> v Aeson..:? "iat"- <*> v Aeson..:? "nbf"- <*> v Aeson..:? "sub"- <*> v Aeson..:? "aud"- <*> v Aeson..:? "iss"---- | Extract Bearer token from Authorization header-extractBearerToken :: Text -> Maybe Text-extractBearerToken authHeader =- case T.words authHeader of- ["Bearer", token] -> Just token- _ -> Nothing---- | Validate a bearer token-validateBearerToken :: (MonadIO m) => OAuthConfig -> Text -> m (Either Text TokenInfo)-validateBearerToken config token = do- -- Basic validation- if T.null token- then return $ Left "Empty token"- else case tokenValidationEndpoint config of- Just endpoint -> introspectToken endpoint token- Nothing -> do- -- Without an introspection endpoint, perform basic JWT validation- -- In production, this should:- -- 1. Verify JWT signature using JWK from jwks_uri- -- 2. Check expiration time- -- 3. Validate issuer and audience- -- 4. Check token type is "Bearer"-- -- For now, decode JWT payload (middle part) for basic validation- case T.splitOn "." token of- [_header, payload, _signature] -> do- currentTime <- liftIO getCurrentTime- case decodeJWTPayload payload of- Right tokenInfo ->- case validateTokenClaims tokenInfo currentTime of- Right _ -> return $ Right tokenInfo- Left err -> return $ Left err- Left err -> return $ Left $ "Invalid JWT format: " <> err- _ -> return $ Left "Invalid JWT structure"---- | Introspect token using OAuth introspection endpoint-introspectToken :: (MonadIO m) => Text -> Text -> m (Either Text TokenInfo)-introspectToken endpoint token = liftIO $ do- let url = T.unpack endpoint- request <- parseRequest url- let requestWithBody =- setRequestMethod "POST" $- setRequestBodyJSON (Aeson.object [("token", Aeson.String token)]) $- addRequestHeader "Content-Type" "application/json" request-- response <- httpJSON requestWithBody- let tokenInfo = getResponseBody response-- if active tokenInfo- then return $ Right tokenInfo- else return $ Left "Token is not active"---- | Decode JWT payload (base64url encoded JSON)-decodeJWTPayload :: Text -> Either Text TokenInfo-decodeJWTPayload payload =- case B64URL.decodeUnpadded (TE.encodeUtf8 payload) of- Right decodedBytes ->- case Aeson.decode' (LBS.fromStrict decodedBytes) of- Just info -> Right info{active = True} -- JWT is implicitly active- Nothing -> Left "Failed to parse JWT payload"- Left _ -> Left "Invalid base64url encoding"---- | Validate token claims (expiration, not-before, etc.)-validateTokenClaims :: TokenInfo -> UTCTime -> Either Text ()-validateTokenClaims tokenInfo currentTime = do- let currentTimestamp = floor (realToFrac (utcTimeToPOSIXSeconds currentTime) :: Double) :: Integer-- -- Check expiration- case MCP.Server.Auth.exp tokenInfo of- Just expTime ->- if currentTimestamp > expTime- then Left "Token has expired"- else Right ()- Nothing -> Right ()-- -- Check not-before- case MCP.Server.Auth.nbf tokenInfo of- Just nbfTime ->- if currentTimestamp < nbfTime- then Left "Token not yet valid"- else Right ()- Nothing -> Right ()-- return ()---- | Generate a cryptographically secure code verifier for PKCE-generateCodeVerifier :: IO Text-generateCodeVerifier = do- gen <- newStdGen- let chars = ['A' .. 'Z'] ++ ['a' .. 'z'] ++ ['0' .. '9'] ++ "-._~"- let verifier = take 128 $ randomRs (0, length chars - 1) gen- return $ T.pack $ map (chars !!) verifier---- | Generate code challenge from verifier using SHA256 (S256 method)-generateCodeChallenge :: Text -> Text-generateCodeChallenge verifier =- let verifierBytes = TE.encodeUtf8 verifier- challengeHash = hashWith SHA256 verifierBytes- challengeBytes = convert challengeHash :: ByteString- in TE.decodeUtf8 $ B64URL.encodeUnpadded challengeBytes---- | Validate PKCE code verifier against challenge-validateCodeVerifier :: Text -> Text -> Bool-validateCodeVerifier verifier challenge =- generateCodeChallenge verifier == challenge---- | Discover OAuth metadata from a well-known endpoint-discoverOAuthMetadata :: (MonadIO m) => Text -> m (Either String OAuthMetadata)-discoverOAuthMetadata issuerUrl = liftIO $ do- let wellKnownUrl = T.unpack issuerUrl <> "/.well-known/openid-configuration"- request <- parseRequest wellKnownUrl- response <- httpJSON request- return $ Right (getResponseBody response)
+ src/MCP/Server/Common.hs view
@@ -0,0 +1,471 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}++{- |+Module: MCP.Server.Common+License: MPL-2.0+Maintainer: <matti@dpella.io>, <lobo@dpella.io>++Transport-agnostic core of the MCP server.++This module provides the shared types, state management, request routing,+and tool framework used by both the HTTP and stdio transports.+-}+module MCP.Server.Common (+ -- * Processing results+ ProcessResult (..),++ -- * Server monad+ MCPServerT,++ -- * Handlers+ ProcessHandlers (..),+ defaultProcessHandlers,++ -- * Tool helpers+ ToolHandler (..),+ toolHandler,+ withToolHandlers,+ toolTextResult,+ toolTextError,++ -- * Server state+ MCPServerState (..),+ initMCPServerState,++ -- * Type families+ MCPHandlerState,+ MCPHandlerUser,++ -- * Core processing (transport-agnostic)+ processMethod,+ recurReplaceMeta,+ isValidRequestId,++ -- * Re-exports+ module MCP.Protocol,+ module MCP.Types,+) where++import Control.Concurrent.MVar (MVar)+import Control.Monad.Except+import Control.Monad.State.Lazy+import Data.Aeson (Value, fromJSON, object, toJSON)+import Data.Aeson qualified as Aeson+import Data.Aeson.KeyMap qualified as KM+import Data.IntMap (IntMap)+import Data.IntMap qualified as IM+import Data.Map (Map)+import Data.Map.Strict qualified as Map+import Data.Set (Set)+import Data.Set qualified as Set+import Data.Text (Text)+import Data.Text qualified as T+import MCP.Protocol+import MCP.Types++{- | Result of processing an MCP request.++Distinguishes between server errors, RPC protocol errors,+and successful responses.+-}+data ProcessResult a where+ -- | Internal server error+ ProcessServerError :: Text -> ProcessResult a+ -- | JSON-RPC error with code and message+ ProcessRPCError :: Int -> Text -> ProcessResult a+ -- | Successful result+ ProcessSuccess :: a -> ProcessResult a+ {- | Request additional information from the client before completing.++ This allows a handler to send a JSON-RPC request back to the client+ (e.g. @sampling\/createMessage@ or @elicitation\/create@) and resume+ processing once the client responds.++ Supported on both transports:++ * __HTTP__: the request is yielded as an SSE event; the server blocks+ (via 'MVar') until the client POSTs a matching response.+ * __Stdio__: the request is written to stdout and the response is read+ synchronously from stdin (blocks until the client responds).+ -}+ ProcessClientInput ::+ -- | JSON-RPC method name for the client request+ Text ->+ -- | Parameters for the client request+ Aeson.Value ->+ -- | Continuation that receives the client's response+ (Aeson.Value -> MCPServerT (ProcessResult a)) ->+ ProcessResult a++instance Functor ProcessResult where+ fmap f (ProcessSuccess a) = ProcessSuccess (f a)+ fmap _ (ProcessServerError err) = ProcessServerError err+ fmap _ (ProcessRPCError code msg) = ProcessRPCError code msg+ fmap f (ProcessClientInput mthd param cont) =+ ProcessClientInput mthd param $ \res -> do+ result <- cont res+ return $ fmap f result++{- | Monad transformer for MCP server operations.++Provides access to the MCP server state and IO operations,+with the state available to the handlers.+-}+type MCPServerT a = ExceptT Text (StateT MCPServerState IO) a++{- | Handlers for MCP protocol methods.++Each handler is optional, allowing servers to implement only+the capabilities they need. Handlers run in the MCPServerT monad+with access to server state and IO.+-}+data ProcessHandlers+ = ProcessHandlers+ { listResourcesHandler :: Maybe (ListResourcesParams -> MCPServerT (ProcessResult ListResourcesResult))+ -- ^ List available resources+ , readResourceHandler :: Maybe (ReadResourceParams -> MCPServerT (ProcessResult ReadResourceResult))+ -- ^ Read a specific resource+ , listToolsHandler :: Maybe (ListToolsParams -> MCPServerT (ProcessResult ListToolsResult))+ -- ^ List available tools+ , callToolHandler :: Maybe (CallToolParams -> MCPServerT (ProcessResult CallToolResult))+ -- ^ Execute a tool+ , listPromptsHandler :: Maybe (ListPromptsParams -> MCPServerT (ProcessResult ListPromptsResult))+ -- ^ List prompt templates+ , getPromptHandler :: Maybe (GetPromptParams -> MCPServerT (ProcessResult GetPromptResult))+ -- ^ Get a specific prompt+ , listResourceTemplatesHandler ::+ Maybe (ListResourceTemplatesParams -> MCPServerT (ProcessResult ListResourceTemplatesResult))+ -- ^ List resource templates+ , completeHandler :: Maybe (CompleteParams -> MCPServerT (ProcessResult CompleteResult))+ -- ^ Provide completions+ , subscribeHandler :: Maybe (SubscribeParams -> MCPServerT (ProcessResult Result))+ -- ^ Subscribe to resource updates+ , unsubscribeHandler :: Maybe (UnsubscribeParams -> MCPServerT (ProcessResult Result))+ -- ^ Unsubscribe from resource updates+ }++-- | A ToolHandler, including the name, description, and input schema.+data ToolHandler = ToolHandler+ { tool_name :: Text+ , tool_description :: Maybe Text+ , tool_title :: Maybe Text+ , tool_input :: InputSchema+ , tool_output :: Maybe InputSchema+ , tool_annotations :: Maybe ToolAnnotations+ , tool_meta :: Maybe Metadata+ , tool_handler :: Maybe (Map Text Value) -> MCPServerT (ProcessResult CallToolResult)+ }++-- | Construct a tool handler with the required fields+toolHandler ::+ Text ->+ Maybe Text ->+ InputSchema ->+ (Maybe (Map Text Value) -> MCPServerT (ProcessResult CallToolResult)) ->+ ToolHandler+toolHandler name desc input handler =+ ToolHandler+ { tool_name = name+ , tool_description = desc+ , tool_title = Nothing+ , tool_input = input+ , tool_output = Nothing+ , tool_annotations = Nothing+ , tool_meta = Nothing+ , tool_handler = handler+ }++-- | Create the raw process handlers from a list of tool handlers.+withToolHandlers :: [ToolHandler] -> ProcessHandlers -> ProcessHandlers+withToolHandlers ths ph =+ ph+ { listToolsHandler = Just list_handler+ , callToolHandler = Just call_handler+ }+ where+ tool_handler_to_tool :: ToolHandler -> Tool+ tool_handler_to_tool ToolHandler{..} =+ Tool+ { name = tool_name+ , description = tool_description+ , title = tool_title+ , inputSchema = tool_input+ , outputSchema = tool_output+ , annotations = tool_annotations+ , _meta = tool_meta+ }+ list_handler = const $ do+ return $+ ProcessSuccess $+ ListToolsResult+ { tools = fmap tool_handler_to_tool ths+ , nextCursor = Nothing+ , _meta = Nothing+ }+ tool_map :: Map Text ToolHandler+ tool_map = Map.fromList $ fmap (\th -> (tool_name th, th)) ths++ missing_required :: Set Text -> MCPServerT (ProcessResult CallToolResult)+ missing_required reqs = return $ ProcessRPCError iNVALID_PARAMS $ "Missing required arguments: " <> T.intercalate ", " (Set.toList reqs)++ call_handler :: CallToolParams -> MCPServerT (ProcessResult CallToolResult)+ call_handler CallToolParams{name = t_name, arguments = args} =+ case Map.lookup t_name tool_map of+ Nothing -> return $ ProcessRPCError mETHOD_NOT_FOUND ("Tool not found: " <> t_name)+ Just ToolHandler{tool_input = InputSchema{required = required}, tool_handler = handler} ->+ -- Handle missing arguments+ case (args, required) of+ (_, Nothing) -> handler args+ (Nothing, Just req) | not $ null req -> missing_required (Set.fromList req)+ (Nothing, Just _) -> handler args+ (Just provided, Just req)+ | p_keys <- Map.keysSet provided+ , missing <- Set.fromList req Set.\\ p_keys+ , not (Set.null missing) ->+ missing_required missing+ (Just _provided, _) -> handler args++-- | A tool result containing text content.+toolTextResult :: [Text] -> CallToolResult+toolTextResult res =+ CallToolResult+ { content = map toTContent res+ , isError = Just False+ , _meta = Nothing+ , structuredContent = Nothing+ }+ where+ toTContent :: Text -> ContentBlock+ toTContent =+ TextBlock . \t -> TextContent{textType = "text", text = t, annotations = Nothing, _meta = Nothing}++-- | A tool result containing text content.+toolTextError :: Text -> CallToolResult+toolTextError err =+ CallToolResult+ { content = [TextBlock $ TextContent{textType = "text", text = err, annotations = Nothing, _meta = Nothing}]+ , isError = Just True+ , _meta = Nothing+ , structuredContent = Nothing+ }++{- | Default process handlers with no implementations.++Use this as a starting point and override specific handlers+as needed for your MCP server implementation.+-}+defaultProcessHandlers :: ProcessHandlers+defaultProcessHandlers =+ ProcessHandlers+ Nothing+ Nothing+ Nothing+ Nothing+ Nothing+ Nothing+ Nothing+ Nothing+ Nothing+ Nothing++{- | Initialize MCP server state with given options and capabilities.++Creates a new server state with:+* The initial handler state+* Server capabilities declaration+* Implementation metadata+* Optional instructions for clients+* Custom process handlers+-}+initMCPServerState ::+ -- | Initial handler state+ MCPHandlerState ->+ -- | Initialize handler state when the client sends @initialize@+ Maybe (MCPHandlerUser -> MCPHandlerState -> IO MCPHandlerState) ->+ -- | Finalize handler state after each request+ Maybe (MCPHandlerState -> IO MCPHandlerState) ->+ -- | Server capabilities to advertise to clients+ ServerCapabilities ->+ -- | Server name and version metadata+ Implementation ->+ -- | Optional instructions for clients+ Maybe Text ->+ -- | Handlers for MCP methods+ ProcessHandlers ->+ MCPServerState+initMCPServerState init_state handler_init handler_finalize =+ MCPServerState False init_state handler_init handler_finalize Nothing (Just Warning) IM.empty 0++{- | Type family used to configure the handler state threaded through 'MCPServerT'.+Users must provide a type instance before using the MCP server, e.g.+@type instance MCPHandlerState = MyState@.+-}+type family MCPHandlerState++{- | Type family used to configure the user type for JWT authentication.+Users must provide a type instance before using the MCP server, e.g.+@type instance MCPHandlerUser = MyUser@.+-}+type family MCPHandlerUser++{- | MCP server state maintained across requests.++Contains all information needed to process MCP requests including+initialization status, handler state, and capability declarations.+-}+data MCPServerState = MCPServerState+ { mcp_server_initialized :: Bool+ -- ^ Whether initialize has been called+ , mcp_handler_state :: MCPHandlerState+ -- ^ Current handler state for this session+ , mcp_handler_init :: Maybe (MCPHandlerUser -> MCPHandlerState -> IO MCPHandlerState)+ -- ^ Initialize the handler state on server initialization+ , mcp_handler_finalize :: Maybe (MCPHandlerState -> IO MCPHandlerState)+ -- ^ Finalize the handler state after each request+ , mcp_client_capabilities :: Maybe ClientCapabilities+ -- ^ Client's declared capabilities+ , mcp_log_level :: Maybe LoggingLevel+ -- ^ Log level+ , mcp_pending_responses :: IntMap (MVar Aeson.Value)+ -- ^ Pending responses from the client+ , mcp_pending_responses_next :: Int+ -- ^ Next pending response ID+ , mcp_server_capabilities :: ServerCapabilities+ -- ^ Server's declared capabilities+ , mcp_implementation :: Implementation+ -- ^ Server implementation metadata+ , mcp_instructions :: Maybe Text+ -- ^ Optional instructions for clients+ , mcp_process_handlers :: ProcessHandlers+ -- ^ Handlers for MCP methods+ }++{- | Normalize @_meta@ fields in JSON-RPC responses.+Promotes the contents of @_meta@ objects up one level and removes the @_meta@ key.+-}+recurReplaceMeta :: Aeson.Value -> Aeson.Value+recurReplaceMeta (Aeson.Array arr) =+ Aeson.Array $ fmap recurReplaceMeta arr+recurReplaceMeta o@(Aeson.Object _)+ | Aeson.Object obj' <- replaceMeta o =+ Aeson.Object $ fmap recurReplaceMeta obj'+recurReplaceMeta v = v++-- | Replace @_meta@ at the top level of an object+replaceMeta :: Aeson.Value -> Aeson.Value+replaceMeta (Aeson.Object obj)+ | Just (Aeson.Object meta) <- KM.lookup "_meta" obj =+ Aeson.Object $ KM.delete "_meta" obj `KM.union` meta+replaceMeta v = v++-- | Check if a RequestId is valid according to JSON-RPC 2.0 spec.+isValidRequestId :: RequestId -> Bool+isValidRequestId (RequestId v) =+ case v of+ Aeson.Number _ -> True+ Aeson.String _ -> True+ Aeson.Null -> True+ _ -> False++-- | Methods that can be called without initialization+allowedWithoutInitialization :: Set Text+allowedWithoutInitialization = Set.fromList ["ping", "initialize"]++-- | Methods that are allowed without parameters (null params -> empty object)+allowedWithoutParams :: Set Text+allowedWithoutParams =+ Set.fromList+ [ "resources/list"+ , "resources/templates/list"+ , "tools/list"+ , "prompts/list"+ ]++{- | Route an incoming JSON-RPC method to the appropriate handler.++This is the transport-agnostic core of the MCP server, used by both+the HTTP and stdio transports. It enforces initialization order+(only @\"ping\"@ and @\"initialize\"@ are accepted before the server+has been initialized) and parses method parameters before delegating+to the 'ProcessHandlers'.+-}+processMethod :: Bool -> Text -> Aeson.Value -> StateT MCPServerState IO (ProcessResult Aeson.Value)+processMethod False mthd _ | not $ mthd `Set.member` allowedWithoutInitialization = server_not_initialized+processMethod _ mthd Aeson.Null | mthd `Set.member` allowedWithoutParams = processMethod True mthd (object [])+processMethod _ "resources/list" mb_arg = runProcessHandler listResourcesHandler mb_arg+processMethod _ "resources/read" mb_arg = runProcessHandler readResourceHandler mb_arg+processMethod _ "resources/subscribe" mb_arg = runProcessHandler subscribeHandler mb_arg+processMethod _ "resources/unsubscribe" mb_arg = runProcessHandler unsubscribeHandler mb_arg+processMethod _ "tools/list" mb_arg = runProcessHandler listToolsHandler mb_arg+processMethod _ "tools/call" mb_arg = runProcessHandler callToolHandler mb_arg+processMethod _ "prompts/list" mb_arg = runProcessHandler listPromptsHandler mb_arg+processMethod _ "prompts/get" mb_arg = runProcessHandler getPromptHandler mb_arg+processMethod _ "resources/templates/list" mb_arg = runProcessHandler listResourceTemplatesHandler mb_arg+processMethod _ "completion/complete" mb_arg = runProcessHandler completeHandler mb_arg+processMethod _ "ping" _ = return $ ProcessSuccess (toJSON $ object [])+processMethod _ "logging/setLevel" p+ | Aeson.Error e <- fromJSON @SetLevelParams p = invalid_params e+processMethod _ "logging/setLevel" p+ | Aeson.Success (SetLevelParams{level = level}) <- fromJSON @SetLevelParams p = do+ modify $ \s -> s{mcp_log_level = Just level}+ return $ ProcessSuccess Aeson.Null+processMethod _ "initialize" p+ | Aeson.Error e <- fromJSON @InitializeParams p = invalid_params e+processMethod _ "initialize" p+ | Aeson.Success (InitializeParams{capabilities = capabilities}) <- fromJSON @InitializeParams p = do+ MCPServerState{..} <- get+ modify $ \s ->+ s+ { mcp_server_initialized = True+ , mcp_client_capabilities = Just capabilities+ }+ let result =+ InitializeResult+ { protocolVersion = pROTOCOL_VERSION+ , capabilities = mcp_server_capabilities+ , serverInfo = mcp_implementation+ , instructions = mcp_instructions+ , _meta = Nothing+ }+ return $ ProcessSuccess $ toJSON result+processMethod _ _ _ = method_not_found++-- | Standard JSON-RPC error responses+missing_params :: StateT MCPServerState IO (ProcessResult Aeson.Value)+missing_params = return $ ProcessRPCError iNVALID_PARAMS "Missing params"++invalid_params :: String -> StateT MCPServerState IO (ProcessResult Aeson.Value)+invalid_params = return . ProcessRPCError iNVALID_PARAMS . ("Invalid params: " <>) . T.pack++server_not_initialized :: StateT MCPServerState IO (ProcessResult Aeson.Value)+server_not_initialized = return $ ProcessRPCError sERVER_NOT_INITIALIZED "Server not initialized"++method_not_found :: StateT MCPServerState IO (ProcessResult Aeson.Value)+method_not_found = return $ ProcessRPCError mETHOD_NOT_FOUND "Method not found"++-- | Helper to run a process handler with argument parsing+runProcessHandler ::+ forall a b.+ (Aeson.FromJSON a, Aeson.ToJSON b) =>+ (ProcessHandlers -> Maybe (a -> MCPServerT (ProcessResult b))) ->+ Aeson.Value ->+ StateT MCPServerState IO (ProcessResult Aeson.Value)+runProcessHandler _ Aeson.Null = missing_params+runProcessHandler handler arg_value =+ case fromJSON @a arg_value of+ Aeson.Error err -> invalid_params err+ Aeson.Success arg -> do+ handlerImpl <- gets (handler . mcp_process_handlers)+ case handlerImpl of+ Just impl -> do+ impl_res <- runExceptT $ impl arg+ case impl_res of+ Left err -> return $ ProcessServerError err+ Right res -> return $ fmap toJSON res+ Nothing -> method_not_found
src/MCP/Server/HTTP.hs view
@@ -1,808 +1,273 @@ {-# LANGUAGE DataKinds #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE DuplicateRecordFields #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeOperators #-}--{- |-Module : MCP.Server.HTTP-Description : MCP server implementation for HTTP communication-Copyright : (C) 2025 Matthias Pall Gissurarson-License : MIT-Maintainer : mpg@mpg.is-Stability : experimental-Portability : GHC--This module provides MCP server implementation for HTTP communication.--}-module MCP.Server.HTTP (- -- * Server Runner- runServerHTTP,- HTTPServerConfig (..),-- -- * Demo Configuration Helpers- defaultDemoOAuthConfig,-) where--import Control.Concurrent.STM (TVar, atomically, modifyTVar', newTVarIO, readTVar, readTVarIO, writeTVar)-import Control.Monad.IO.Class (liftIO)-import Control.Monad.Reader (ask)-import Control.Monad.State.Strict (get, put)-import Crypto.JOSE (JWK)-import Data.Aeson (encode, fromJSON, object, toJSON, (.=))-import Data.Aeson qualified as Aeson-import Data.ByteString.Lazy qualified as LBS-import Data.Map.Strict (Map)-import Data.Map.Strict qualified as Map-import Data.Text (Text)-import Data.Text qualified as T-import Data.Text.Encoding qualified as TE-import Data.Time.Clock (UTCTime, addUTCTime, getCurrentTime)-import Data.UUID qualified as UUID-import Data.UUID.V4 qualified as UUID-import GHC.Generics (Generic)-import Network.Wai (Application)-import Network.Wai.Handler.Warp (Port, run)-import Network.Wai.Middleware.RequestLogger (logStdoutDev)-import Servant (Context (..), Handler, Proxy (..), Server, serve, serveWithContext, throwError)-import Servant.API (FormUrlEncoded, Get, JSON, PlainText, Post, QueryParam, QueryParam', ReqBody, Required, (:<|>) (..), (:>))-import Servant.Auth.Server (Auth, AuthResult (..), FromJWT, JWT, JWTSettings, ToJWT, defaultCookieSettings, defaultJWTSettings, generateKey, makeJWT)-import Servant.Server (err400, err401, err500, errBody)--import Control.Monad (unless, when)-import MCP.Protocol-import MCP.Server (MCPServer (..), MCPServerM, ServerConfig (..), ServerState (..), initialServerState, runMCPServer)-import MCP.Server.Auth (OAuthConfig (..), OAuthMetadata (..), OAuthProvider (..), validateCodeVerifier)-import MCP.Types---- | Configuration for running an MCP HTTP server-data HTTPServerConfig = HTTPServerConfig- { httpPort :: Port- , httpBaseUrl :: Text -- Base URL for OAuth endpoints (e.g., "https://api.example.com")- , httpServerInfo :: Implementation- , httpCapabilities :: ServerCapabilities- , httpEnableLogging :: Bool- , httpOAuthConfig :: Maybe OAuthConfig- , httpJWK :: Maybe JWK -- JWT signing key- , httpProtocolVersion :: Text -- MCP protocol version- }- deriving (Show)---- | User information from OAuth-data AuthUser = AuthUser- { userId :: Text- , userEmail :: Maybe Text- , userName :: Maybe Text- }- deriving (Show, Generic)---- | Authorization code with PKCE-data AuthorizationCode = AuthorizationCode- { authCode :: Text- , authClientId :: Text- , authRedirectUri :: Text- , authCodeChallenge :: Text- , authCodeChallengeMethod :: Text- , authScopes :: [Text]- , authUserId :: Text- , authExpiry :: UTCTime- }- deriving (Show, Generic)---- | OAuth server state-data OAuthState = OAuthState- { authCodes :: Map Text AuthorizationCode -- code -> AuthorizationCode- , accessTokens :: Map Text AuthUser -- token -> user- , refreshTokens :: Map Text (Text, AuthUser) -- refresh_token -> (access_token, user)- , registeredClients :: Map Text ClientInfo -- client_id -> ClientInfo- }- deriving (Show, Generic)---- | Client registration request-data ClientRegistrationRequest = ClientRegistrationRequest- { client_name :: Text- , redirect_uris :: [Text]- , grant_types :: [Text]- , response_types :: [Text]- , token_endpoint_auth_method :: Text- }- deriving (Show, Generic)--instance Aeson.FromJSON ClientRegistrationRequest where- parseJSON = Aeson.genericParseJSON Aeson.defaultOptions---- | Client registration response-data ClientRegistrationResponse = ClientRegistrationResponse- { client_id :: Text- , client_secret :: Text -- Empty string for public clients- , client_name :: Text- , redirect_uris :: [Text]- , grant_types :: [Text]- , response_types :: [Text]- , token_endpoint_auth_method :: Text- }- deriving (Show, Generic)--instance Aeson.ToJSON ClientRegistrationResponse where- toJSON = Aeson.genericToJSON Aeson.defaultOptions---- | Client info stored in server-data ClientInfo = ClientInfo- { clientName :: Text- , clientRedirectUris :: [Text]- , clientGrantTypes :: [Text]- , clientResponseTypes :: [Text]- , clientAuthMethod :: Text- }- deriving (Show, Generic)---- | Token response-data TokenResponse = TokenResponse- { access_token :: Text- , token_type :: Text- , expires_in :: Maybe Int- , refresh_token :: Maybe Text- , scope :: Maybe Text- }- deriving (Show, Generic)--instance Aeson.ToJSON TokenResponse where- toJSON TokenResponse{..} =- object $- [ "access_token" .= access_token- , "token_type" .= token_type- ]- ++ maybe [] (\e -> ["expires_in" .= e]) expires_in- ++ maybe [] (\r -> ["refresh_token" .= r]) refresh_token- ++ maybe [] (\s -> ["scope" .= s]) scope--instance Aeson.FromJSON AuthUser where- parseJSON = Aeson.withObject "AuthUser" $ \v ->- AuthUser- <$> v Aeson..: "sub"- <*> v Aeson..:? "email"- <*> v Aeson..:? "name"--instance Aeson.ToJSON AuthUser where- toJSON AuthUser{..} =- object- [ "sub" .= userId- , "email" .= userEmail- , "name" .= userName- ]---- Instances for JWT-instance ToJWT AuthUser-instance FromJWT AuthUser---- | MCP API definition for HTTP server (following the MCP transport spec)-type MCPAPI auths = Auth auths AuthUser :> "mcp" :> ReqBody '[JSON] Aeson.Value :> Post '[JSON] Aeson.Value---- | Unprotected MCP API (for backward compatibility)-type UnprotectedMCPAPI = "mcp" :> ReqBody '[JSON] Aeson.Value :> Post '[JSON] Aeson.Value---- | OAuth endpoints-type OAuthAPI =- ".well-known" :> "oauth-authorization-server" :> Get '[JSON] OAuthMetadata- :<|> "register"- :> ReqBody '[JSON] ClientRegistrationRequest- :> Post '[JSON] ClientRegistrationResponse- :<|> "authorize"- :> QueryParam' '[Required] "response_type" Text- :> QueryParam' '[Required] "client_id" Text- :> QueryParam' '[Required] "redirect_uri" Text- :> QueryParam' '[Required] "code_challenge" Text- :> QueryParam' '[Required] "code_challenge_method" Text- :> QueryParam "scope" Text- :> QueryParam "state" Text- :> Get '[PlainText] Text- :<|> "token"- :> ReqBody '[FormUrlEncoded] [(Text, Text)]- :> Post '[JSON] TokenResponse---- | Complete API with OAuth-type CompleteAPI auths = OAuthAPI :<|> MCPAPI auths---- | Create a WAI Application for the MCP HTTP server-mcpApp :: (MCPServer MCPServerM) => HTTPServerConfig -> TVar ServerState -> TVar OAuthState -> JWTSettings -> Application-mcpApp config stateVar oauthStateVar jwtSettings =- let cookieSettings = defaultCookieSettings- authContext = cookieSettings :. jwtSettings :. EmptyContext- baseApp = case httpOAuthConfig config of- Just oauthCfg- | oauthEnabled oauthCfg ->- serveWithContext- (Proxy :: Proxy (CompleteAPI '[JWT]))- authContext- (oauthServer config oauthStateVar :<|> mcpServerAuth config stateVar)- _ ->- serve (Proxy :: Proxy UnprotectedMCPAPI) (mcpServerNoAuth config stateVar)- in if httpEnableLogging config- then logStdoutDev baseApp- else baseApp- where- oauthServer :: HTTPServerConfig -> TVar OAuthState -> Server OAuthAPI- oauthServer cfg oauthState =- handleMetadata cfg- :<|> handleRegister cfg oauthState- :<|> handleAuthorize cfg oauthState- :<|> handleToken jwtSettings cfg oauthState-- mcpServerAuth :: HTTPServerConfig -> TVar ServerState -> AuthResult AuthUser -> Aeson.Value -> Handler Aeson.Value- mcpServerAuth httpConfig stateTVar authResult requestValue =- case authResult of- Authenticated user -> handleHTTPRequest httpConfig stateTVar (Just user) requestValue- NoSuchUser -> throwError err401{errBody = encode $ object ["error" .= ("Invalid token" :: Text)]}- BadPassword -> throwError err401{errBody = encode $ object ["error" .= ("Invalid token" :: Text)]}- Indefinite -> throwError err401{errBody = encode $ object ["error" .= ("Authentication required" :: Text)]}-- mcpServerNoAuth :: HTTPServerConfig -> TVar ServerState -> Aeson.Value -> Handler Aeson.Value- mcpServerNoAuth httpConfig stateTVar = handleHTTPRequest httpConfig stateTVar Nothing---- | Handle HTTP MCP requests following the MCP transport protocol-handleHTTPRequest :: (MCPServer MCPServerM) => HTTPServerConfig -> TVar ServerState -> Maybe AuthUser -> Aeson.Value -> Handler Aeson.Value-handleHTTPRequest httpConfig stateVar _mAuthUser requestValue = do- -- Parse the incoming JSON-RPC message- case fromJSON requestValue of- Aeson.Success (msg :: JSONRPCMessage) -> do- case msg of- RequestMessage req -> do- -- Process the JSON-RPC request- result <- liftIO $ processHTTPRequest httpConfig stateVar req- case result of- Left err -> throwError err500{errBody = encode $ object ["error" .= err]}- Right response -> return response- NotificationMessage notif -> do- -- Process notifications (no response expected)- _ <- liftIO $ processHTTPNotification httpConfig stateVar notif- return $ object [] -- Empty response for notifications- _ -> throwError err400{errBody = encode $ object ["error" .= ("Invalid JSON-RPC message type" :: Text)]}- Aeson.Error e -> throwError err400{errBody = encode $ object ["error" .= ("Invalid JSON-RPC message" :: Text), "error_description" .= T.pack e]}---- | Process an HTTP MCP notification-processHTTPNotification :: (MCPServer MCPServerM) => HTTPServerConfig -> TVar ServerState -> JSONRPCNotification -> IO ()-processHTTPNotification _ _ _ = do- -- For now, just ignore notifications since they don't need responses- -- In a more complete implementation, this would handle logging/setLevel notifications- return ()---- | Process an HTTP MCP request-processHTTPRequest :: (MCPServer MCPServerM) => HTTPServerConfig -> TVar ServerState -> JSONRPCRequest -> IO (Either Text Aeson.Value)-processHTTPRequest httpConfig stateVar req = do- -- Read the current state- currentState <- readTVarIO stateVar- let dummyConfig =- ServerConfig- { configInput = undefined -- Not used in HTTP mode- , configOutput = undefined -- Not used in HTTP mode- , configServerInfo = httpServerInfo httpConfig- , configCapabilities = httpCapabilities httpConfig- }-- result <- runMCPServer dummyConfig currentState (handleHTTPRequestInner (httpProtocolVersion httpConfig) req)- case result of- Left err -> return $ Left err- Right (response, newState) -> do- -- Update the state atomically- atomically $ writeTVar stateVar newState- return $ Right response---- | Handle HTTP request within the MCP monad, returning proper JSON-RPC responses-handleHTTPRequestInner :: (MCPServer MCPServerM) => Text -> JSONRPCRequest -> MCPServerM Aeson.Value-handleHTTPRequestInner protocolVersion (JSONRPCRequest _ reqId method params) = do- config <- ask- state <- get-- case method of- "initialize" -> case params of- Just p -> case fromJSON p of- Aeson.Success initParams -> do- handleInitializeHTTP reqId initParams- let result =- InitializeResult- { protocolVersion = protocolVersion- , capabilities = configCapabilities config- , serverInfo = configServerInfo config- , instructions = Nothing- , _meta = Nothing- }- return $ toJSON $ JSONRPCResponse "2.0" reqId (toJSON result)- Aeson.Error e ->- return $- toJSON $- JSONRPCError "2.0" reqId $- JSONRPCErrorInfo (-32602) ("Invalid params: " <> T.pack e) Nothing- Nothing ->- return $- toJSON $- JSONRPCError "2.0" reqId $- JSONRPCErrorInfo (-32602) "Missing params" Nothing- "ping" -> return $ toJSON $ JSONRPCResponse "2.0" reqId (object [])- "resources/list" -> do- if not (serverInitialized state)- then- return $- toJSON $- JSONRPCError "2.0" reqId $- JSONRPCErrorInfo (-32002) "Server not initialized" Nothing- else case params of- Just p -> case fromJSON p of- Aeson.Success listParams -> do- result <- handleListResources listParams- return $ toJSON $ JSONRPCResponse "2.0" reqId (toJSON result)- Aeson.Error e ->- return $- toJSON $- JSONRPCError "2.0" reqId $- JSONRPCErrorInfo (-32602) ("Invalid params: " <> T.pack e) Nothing- Nothing -> do- result <- handleListResources (ListResourcesParams Nothing)- return $ toJSON $ JSONRPCResponse "2.0" reqId (toJSON result)- "resources/read" -> do- if not (serverInitialized state)- then- return $- toJSON $- JSONRPCError "2.0" reqId $- JSONRPCErrorInfo (-32002) "Server not initialized" Nothing- else case params of- Just p -> case fromJSON p of- Aeson.Success readParams -> do- result <- handleReadResource readParams- return $ toJSON $ JSONRPCResponse "2.0" reqId (toJSON result)- Aeson.Error e ->- return $- toJSON $- JSONRPCError "2.0" reqId $- JSONRPCErrorInfo (-32602) ("Invalid params: " <> T.pack e) Nothing- Nothing ->- return $- toJSON $- JSONRPCError "2.0" reqId $- JSONRPCErrorInfo (-32602) "Missing params" Nothing- "tools/list" -> do- if not (serverInitialized state)- then- return $- toJSON $- JSONRPCError "2.0" reqId $- JSONRPCErrorInfo (-32002) "Server not initialized" Nothing- else case params of- Just p -> case fromJSON p of- Aeson.Success listParams -> do- result <- handleListTools listParams- return $ toJSON $ JSONRPCResponse "2.0" reqId (toJSON result)- Aeson.Error e ->- return $- toJSON $- JSONRPCError "2.0" reqId $- JSONRPCErrorInfo (-32602) ("Invalid params: " <> T.pack e) Nothing- Nothing -> do- result <- handleListTools (ListToolsParams Nothing)- return $ toJSON $ JSONRPCResponse "2.0" reqId (toJSON result)- "tools/call" -> do- if not (serverInitialized state)- then- return $- toJSON $- JSONRPCError "2.0" reqId $- JSONRPCErrorInfo (-32002) "Server not initialized" Nothing- else case params of- Just p -> case fromJSON p of- Aeson.Success callParams -> do- result <- handleCallTool callParams- return $ toJSON $ JSONRPCResponse "2.0" reqId (toJSON result)- Aeson.Error e ->- return $- toJSON $- JSONRPCError "2.0" reqId $- JSONRPCErrorInfo (-32602) ("Invalid params: " <> T.pack e) Nothing- Nothing ->- return $- toJSON $- JSONRPCError "2.0" reqId $- JSONRPCErrorInfo (-32602) "Missing params" Nothing- "prompts/list" -> do- if not (serverInitialized state)- then- return $- toJSON $- JSONRPCError "2.0" reqId $- JSONRPCErrorInfo (-32002) "Server not initialized" Nothing- else case params of- Just p -> case fromJSON p of- Aeson.Success listParams -> do- result <- handleListPrompts listParams- return $ toJSON $ JSONRPCResponse "2.0" reqId (toJSON result)- Aeson.Error e ->- return $- toJSON $- JSONRPCError "2.0" reqId $- JSONRPCErrorInfo (-32602) ("Invalid params: " <> T.pack e) Nothing- Nothing -> do- result <- handleListPrompts (ListPromptsParams Nothing)- return $ toJSON $ JSONRPCResponse "2.0" reqId (toJSON result)- "prompts/get" -> do- if not (serverInitialized state)- then- return $- toJSON $- JSONRPCError "2.0" reqId $- JSONRPCErrorInfo (-32002) "Server not initialized" Nothing- else case params of- Just p -> case fromJSON p of- Aeson.Success getParams -> do- result <- handleGetPrompt getParams- return $ toJSON $ JSONRPCResponse "2.0" reqId (toJSON result)- Aeson.Error e ->- return $- toJSON $- JSONRPCError "2.0" reqId $- JSONRPCErrorInfo (-32602) ("Invalid params: " <> T.pack e) Nothing- Nothing ->- return $- toJSON $- JSONRPCError "2.0" reqId $- JSONRPCErrorInfo (-32602) "Missing params" Nothing- "completion/complete" -> do- if not (serverInitialized state)- then- return $- toJSON $- JSONRPCError "2.0" reqId $- JSONRPCErrorInfo (-32002) "Server not initialized" Nothing- else case params of- Just p -> case fromJSON p of- Aeson.Success completeParams -> do- result <- handleComplete completeParams- return $ toJSON $ JSONRPCResponse "2.0" reqId (toJSON result)- Aeson.Error e ->- return $- toJSON $- JSONRPCError "2.0" reqId $- JSONRPCErrorInfo (-32602) ("Invalid params: " <> T.pack e) Nothing- Nothing ->- return $- toJSON $- JSONRPCError "2.0" reqId $- JSONRPCErrorInfo (-32602) "Missing params" Nothing- "logging/setLevel" -> case params of- Just p -> case fromJSON p of- Aeson.Success setLevelParams -> do- _ <- handleSetLevel setLevelParams- return $ toJSON $ JSONRPCResponse "2.0" reqId (object [])- Aeson.Error e ->- return $- toJSON $- JSONRPCError "2.0" reqId $- JSONRPCErrorInfo (-32602) ("Invalid params: " <> T.pack e) Nothing- Nothing ->- return $- toJSON $- JSONRPCError "2.0" reqId $- JSONRPCErrorInfo (-32602) "Missing params" Nothing- _ ->- return $- toJSON $- JSONRPCError "2.0" reqId $- JSONRPCErrorInfo (-32601) "Method not found" Nothing---- | Handle HTTP initialize request-handleInitializeHTTP :: RequestId -> InitializeParams -> MCPServerM ()-handleInitializeHTTP _ params = do- config <- ask- state <- get-- let InitializeParams{capabilities = clientCaps} = params-- put- state- { serverInitialized = True- , clientCapabilities = Just clientCaps- , serverInfo = Just (configServerInfo config)- }---- | Handle OAuth metadata discovery endpoint-handleMetadata :: HTTPServerConfig -> Handler OAuthMetadata-handleMetadata config = do- let baseUrl = httpBaseUrl config- oauthCfg = httpOAuthConfig config- return- OAuthMetadata- { issuer = baseUrl- , authorizationEndpoint = baseUrl <> "/authorize"- , tokenEndpoint = baseUrl <> "/token"- , registrationEndpoint = Just (baseUrl <> "/register")- , userInfoEndpoint = Nothing- , jwksUri = Nothing- , scopesSupported = fmap supportedScopes oauthCfg- , responseTypesSupported = maybe ["code"] supportedResponseTypes oauthCfg- , grantTypesSupported = fmap supportedGrantTypes oauthCfg- , tokenEndpointAuthMethodsSupported = fmap supportedAuthMethods oauthCfg- , codeChallengeMethodsSupported = fmap supportedCodeChallengeMethods oauthCfg- }---- | Handle dynamic client registration-handleRegister :: HTTPServerConfig -> TVar OAuthState -> ClientRegistrationRequest -> Handler ClientRegistrationResponse-handleRegister config oauthStateVar (ClientRegistrationRequest reqName reqRedirects reqGrants reqResponses reqAuth) = do- -- Generate client ID- let prefix = maybe "client_" clientIdPrefix (httpOAuthConfig config)- clientId <- liftIO $ (prefix <>) <$> generateAuthCode-- -- Store client info- let clientInfo =- ClientInfo- { clientName = reqName- , clientRedirectUris = reqRedirects- , clientGrantTypes = reqGrants- , clientResponseTypes = reqResponses- , clientAuthMethod = reqAuth- }-- liftIO $ atomically $ modifyTVar' oauthStateVar $ \s ->- s{registeredClients = Map.insert clientId clientInfo (registeredClients s)}-- return- ClientRegistrationResponse- { client_id = clientId- , client_secret = "" -- Empty string for public clients- , client_name = reqName- , redirect_uris = reqRedirects- , grant_types = reqGrants- , response_types = reqResponses- , token_endpoint_auth_method = reqAuth- }---- | Handle OAuth authorize endpoint-handleAuthorize :: HTTPServerConfig -> TVar OAuthState -> Text -> Text -> Text -> Text -> Text -> Maybe Text -> Maybe Text -> Handler Text-handleAuthorize config oauthStateVar responseType clientId redirectUri codeChallenge codeChallengeMethod mScope mState = do- -- Validate parameters according to MCP spec- when (responseType /= "code") $- throwError err400{errBody = encode $ object ["error" .= ("unsupported_response_type" :: Text), "error_description" .= ("Only 'code' response type is supported" :: Text)]}-- when (codeChallengeMethod /= "S256") $- throwError err400{errBody = encode $ object ["error" .= ("invalid_request" :: Text), "error_description" .= ("Only 'S256' code challenge method is supported" :: Text)]}-- -- Generate authorization code- code <- liftIO $ generateAuthCodeWithConfig config- currentTime <- liftIO getCurrentTime- let expirySeconds = maybe 600 (fromIntegral . authCodeExpirySeconds) (httpOAuthConfig config)- expiry = addUTCTime expirySeconds currentTime-- -- Generate user ID based on configuration- let oauthCfg = httpOAuthConfig config- userId = case demoUserIdTemplate =<< oauthCfg of- Just template -> T.replace "{clientId}" clientId template- Nothing -> "user-" <> clientId -- Fallback if no demo mode- authCode =- AuthorizationCode- { authCode = code- , authClientId = clientId- , authRedirectUri = redirectUri- , authCodeChallenge = codeChallenge- , authCodeChallengeMethod = codeChallengeMethod- , authScopes = maybe [] (T.splitOn " ") mScope- , authUserId = userId- , authExpiry = expiry- }-- -- Store authorization code- liftIO $ atomically $ modifyTVar' oauthStateVar $ \s ->- s{authCodes = Map.insert code authCode (authCodes s)}-- -- Return the callback URL with auth code- let stateParam = maybe "" ("&state=" <>) mState- defaultTemplate =- "Authorization successful!\n\n"- <> "Redirect to: "- <> redirectUri- <> "?code="- <> code- <> stateParam- <> "\n\n"- <> "Use this authorization code to exchange for an access token."- template =- maybe- defaultTemplate- ( T.replace "{redirectUri}" redirectUri . T.replace "{code}" code . T.replace "{state}" stateParam- )- (authorizationSuccessTemplate =<< httpOAuthConfig config)- return template---- | Handle OAuth token endpoint-handleToken :: JWTSettings -> HTTPServerConfig -> TVar OAuthState -> [(Text, Text)] -> Handler TokenResponse-handleToken jwtSettings config oauthStateVar params = do- let paramMap = Map.fromList params- case Map.lookup "grant_type" paramMap of- Just "authorization_code" -> handleAuthCodeGrant jwtSettings config oauthStateVar paramMap- Just "refresh_token" -> handleRefreshTokenGrant jwtSettings config oauthStateVar paramMap- Just _other -> throwError err400{errBody = encode $ object ["error" .= ("unsupported_grant_type" :: Text)]}- Nothing -> throwError err400{errBody = encode $ object ["error" .= ("invalid_request" :: Text), "error_description" .= ("Missing grant_type" :: Text)]}---- | Handle authorization code grant-handleAuthCodeGrant :: JWTSettings -> HTTPServerConfig -> TVar OAuthState -> Map Text Text -> Handler TokenResponse-handleAuthCodeGrant jwtSettings config oauthStateVar params = do- code <- case Map.lookup "code" params of- Just c -> return c- Nothing -> throwError err400{errBody = encode $ object ["error" .= ("invalid_request" :: Text)]}-- codeVerifier <- case Map.lookup "code_verifier" params of- Just v -> return v- Nothing -> throwError err400{errBody = encode $ object ["error" .= ("invalid_request" :: Text)]}-- -- Look up authorization code- oauthState <- liftIO $ readTVarIO oauthStateVar- authCode <- case Map.lookup code (authCodes oauthState) of- Just ac -> return ac- Nothing -> throwError err400{errBody = encode $ object ["error" .= ("invalid_grant" :: Text)]}-- -- Verify code hasn't expired- currentTime <- liftIO getCurrentTime- when (currentTime > authExpiry authCode) $- throwError err400{errBody = encode $ object ["error" .= ("invalid_grant" :: Text), "error_description" .= ("Authorization code expired" :: Text)]}-- -- Verify PKCE- unless (validateCodeVerifier codeVerifier (authCodeChallenge authCode)) $- throwError err400{errBody = encode $ object ["error" .= ("invalid_grant" :: Text), "error_description" .= ("Invalid code verifier" :: Text)]}-- -- Create user for JWT- let oauthCfg = httpOAuthConfig config- emailDomain = maybe "example.com" demoEmailDomain oauthCfg- userName = maybe "User" demoUserName oauthCfg- user =- AuthUser- { userId = authUserId authCode- , userEmail = Just $ authUserId authCode <> "@" <> emailDomain- , userName = Just userName- }-- -- Generate tokens- accessTokenText <- generateJWTAccessToken user jwtSettings- refreshToken <- liftIO $ generateRefreshTokenWithConfig config-- -- Store tokens- liftIO $ atomically $ modifyTVar' oauthStateVar $ \s ->- s- { authCodes = Map.delete code (authCodes s)- , accessTokens = Map.insert accessTokenText user (accessTokens s)- , refreshTokens = Map.insert refreshToken (accessTokenText, user) (refreshTokens s)- }-- return- TokenResponse- { access_token = accessTokenText- , token_type = "Bearer"- , expires_in = Just $ maybe 3600 accessTokenExpirySeconds (httpOAuthConfig config)- , refresh_token = Just refreshToken- , scope = if null (authScopes authCode) then Nothing else Just (T.intercalate " " (authScopes authCode))- }---- | Handle refresh token grant-handleRefreshTokenGrant :: JWTSettings -> HTTPServerConfig -> TVar OAuthState -> Map Text Text -> Handler TokenResponse-handleRefreshTokenGrant jwtSettings config oauthStateVar params = do- refreshToken <- case Map.lookup "refresh_token" params of- Just t -> return t- Nothing -> throwError err400{errBody = encode $ object ["error" .= ("invalid_request" :: Text)]}-- -- Look up refresh token- oauthState <- liftIO $ readTVarIO oauthStateVar- (oldAccessToken, user) <- case Map.lookup refreshToken (refreshTokens oauthState) of- Just info -> return info- Nothing -> throwError err400{errBody = encode $ object ["error" .= ("invalid_grant" :: Text)]}-- -- Generate new JWT access token- newAccessTokenText <- generateJWTAccessToken user jwtSettings-- -- Update tokens- liftIO $ atomically $ modifyTVar' oauthStateVar $ \s ->- s- { accessTokens = Map.insert newAccessTokenText user $ Map.delete oldAccessToken (accessTokens s)- , refreshTokens = Map.insert refreshToken (newAccessTokenText, user) (refreshTokens s)- }-- return- TokenResponse- { access_token = newAccessTokenText- , token_type = "Bearer"- , expires_in = Just $ maybe 3600 accessTokenExpirySeconds (httpOAuthConfig config)- , refresh_token = Just refreshToken- , scope = Nothing- }---- | Generate random authorization code-generateAuthCode :: IO Text-generateAuthCode = do- uuid <- UUID.nextRandom- return $ "code_" <> UUID.toText uuid---- | Generate authorization code with configurable prefix-generateAuthCodeWithConfig :: HTTPServerConfig -> IO Text-generateAuthCodeWithConfig config = do- uuid <- UUID.nextRandom- let prefix = maybe "code_" authCodePrefix (httpOAuthConfig config)- return $ prefix <> UUID.toText uuid---- | Generate JWT access token for user-generateJWTAccessToken :: AuthUser -> JWTSettings -> Handler Text-generateJWTAccessToken user jwtSettings = do- accessTokenResult <- liftIO $ makeJWT user jwtSettings Nothing- case accessTokenResult of- Left _err -> throwError err500{errBody = encode $ object ["error" .= ("Token generation failed" :: Text)]}- Right accessToken -> return $ TE.decodeUtf8 $ LBS.toStrict accessToken---- | Generate refresh token with configurable prefix-generateRefreshTokenWithConfig :: HTTPServerConfig -> IO Text-generateRefreshTokenWithConfig config = do- uuid <- UUID.nextRandom- let prefix = maybe "rt_" refreshTokenPrefix (httpOAuthConfig config)- return $ prefix <> UUID.toText uuid---- | Default demo OAuth configuration for testing purposes-defaultDemoOAuthConfig :: OAuthConfig-defaultDemoOAuthConfig =- OAuthConfig- { oauthEnabled = True- , oauthProviders = []- , tokenValidationEndpoint = Nothing- , requireHTTPS = False -- For demo only- -- Default timing parameters- , authCodeExpirySeconds = 600 -- 10 minutes- , accessTokenExpirySeconds = 3600 -- 1 hour- -- Default OAuth parameters- , supportedScopes = ["mcp:read", "mcp:write"]- , supportedResponseTypes = ["code"]- , supportedGrantTypes = ["authorization_code", "refresh_token"]- , supportedAuthMethods = ["none"]- , supportedCodeChallengeMethods = ["S256"]- , -- Demo mode settings- autoApproveAuth = True- , demoUserIdTemplate = Just "test-user-{clientId}"- , demoEmailDomain = "example.com"- , demoUserName = "Test User"- , publicClientSecret = Just ""- , -- Default token prefixes- authCodePrefix = "code_"- , refreshTokenPrefix = "rt_"- , clientIdPrefix = "client_"- , -- Default response template- authorizationSuccessTemplate = Nothing- }---- | Run the MCP server as an HTTP server-runServerHTTP :: (MCPServer MCPServerM) => HTTPServerConfig -> IO ()-runServerHTTP config = do- -- Initialize JWT settings if OAuth is enabled- jwtSettings <- case httpJWK config of- Just jwk -> return $ defaultJWTSettings jwk- Nothing -> defaultJWTSettings <$> generateKey-- -- Initialize the server state- stateVar <- newTVarIO $ initialServerState (httpCapabilities config)-- -- Initialize OAuth state- oauthStateVar <-- newTVarIO $- OAuthState- { authCodes = Map.empty- , accessTokens = Map.empty- , refreshTokens = Map.empty- , registeredClients = Map.empty- }-- putStrLn $ "Starting MCP HTTP Server on port " ++ show (httpPort config) ++ "..."-- when (maybe False oauthEnabled (httpOAuthConfig config)) $ do- putStrLn "OAuth authentication enabled"- putStrLn $ "Authorization endpoint: " ++ T.unpack (httpBaseUrl config) ++ "/authorize"- putStrLn $ "Token endpoint: " ++ T.unpack (httpBaseUrl config) ++ "/token"- case httpOAuthConfig config >>= \cfg -> if null (oauthProviders cfg) then Nothing else Just (oauthProviders cfg) of- Just providers -> do- putStrLn $ "OAuth providers: " ++ T.unpack (T.intercalate ", " (map providerName providers))- when (any requiresPKCE providers) $ putStrLn "PKCE enabled (required by MCP spec)"- Nothing -> return ()-- run (httpPort config) (mcpApp config stateVar oauthStateVar jwtSettings)+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeOperators #-}++{- |+Module: MCP.Server.HTTP+License: MPL-2.0+Maintainer: <matti@dpella.io>, <lobo@dpella.io>++Servant-based HTTP transport for the MCP server.++This module provides a JWT-authenticated Servant API that accepts+JSON-RPC requests via POST and returns responses as SSE streams.+-}+module MCP.Server.HTTP (+ -- * Servant API+ MCPAPI,+ mcpAPI,+ handleMCPRequest,+ handleMCPEvents,+) where++import Control.Concurrent.MVar+import Control.Monad (when)+import Control.Monad.Except+import Control.Monad.State.Lazy+import Data.Aeson (encode, object, toJSON, (.=))+import Data.Aeson qualified as Aeson+import Data.ByteString.Lazy.Char8 qualified as BSL+import Data.IntMap qualified as IM+import Data.Text (Text)+import Data.Text qualified as T+import Data.Text.Encoding (encodeUtf8)+import Data.Tuple (swap)+import MCP.Server.Common+import Network.HTTP.Media ((//))+import Servant+import Servant.Auth.Server (Auth, AuthResult (..), JWT)+import Servant.Auth.Server qualified as AuthServer+import Servant.Types.SourceT+import Servant.Types.SourceT qualified as Source++-- | Frame for JSON-RPC messages.+data JSONRPCFrame++instance FramingRender JSONRPCFrame where+ framingRender _ f = fmap (\x -> f x <> "\n")++-- | Event for JSON-RPC messages.+data JSONRPCEvent++instance Accept JSONRPCEvent where+ contentType _ = "text" // "event-stream"++instance MimeRender JSONRPCEvent JSONRPCMessage where+ mimeRender _ val =+ ("event: message\n" <>) $+ ("data:" <> encode (toJSON val) <> "\n")++{- | Servant API type for the MCP endpoint.++Accepts JSON-RPC requests with JWT authentication via servant-auth.+All MCP methods are multiplexed through this single endpoint.+-}+type MCPAPI =+ "mcp"+ :> Auth '[JWT] MCPHandlerUser+ -- \^ JWT authentication using servant-auth+ :> ReqBody '[JSON] JSONRPCMessage+ -- \^ JSON-RPC request+ :> StreamPost JSONRPCFrame JSONRPCEvent (SourceIO JSONRPCMessage)+ -- \^ Stream of JSON-RPC responses+ :<|> "mcp"+ :> Auth '[JWT] MCPHandlerUser+ :> StreamGet JSONRPCFrame JSONRPCEvent (SourceIO JSONRPCMessage)++-- | Type of the MCP API+mcpAPI :: MVar MCPServerState -> Server MCPAPI+mcpAPI state_var =+ handleMCPRequest state_var+ :<|> handleMCPEvents++-- | This handles the Get /mcp requests. Does nothing for now except authenticate.+handleMCPEvents ::+ AuthResult MCPHandlerUser ->+ Handler (SourceIO JSONRPCMessage)+handleMCPEvents auth_result = do+ case auth_result of+ AuthServer.NoSuchUser -> throwError err401{errBody = "Invalid authentication credentials"}+ AuthServer.BadPassword -> throwError err401{errBody = "Authentication failed"}+ AuthServer.Indefinite -> throwError err401{errBody = "Authentication error"}+ AuthServer.Authenticated _ -> return $ fromStepT $ Stop++{- | Handle incoming MCP JSON-RPC requests.++This is the main entry point for MCP requests. It:++1. Validates authentication via servant-auth (automatically handled)+2. Parses the JSON-RPC request+3. Routes to the appropriate handler based on method name+4. Manages server state updates+5. Returns properly formatted JSON-RPC responses++The server enforces initialization before accepting most methods.+Only "ping" and "initialize" can be called before initialization.+-}+handleMCPRequest ::+ MVar MCPServerState ->+ AuthResult MCPHandlerUser ->+ JSONRPCMessage ->+ Handler (SourceIO JSONRPCMessage)+handleMCPRequest state_var auth_result request_value =+ fromStepT <$> do+ case auth_result of+ AuthServer.NoSuchUser -> mcpAuthError "Invalid authentication credentials"+ AuthServer.BadPassword -> mcpAuthError "Authentication failed"+ AuthServer.Indefinite -> mcpAuthError "Authentication error"+ AuthServer.Authenticated auth_user -> do+ MCPServerState+ { mcp_server_initialized = server_initialized+ , mcp_log_level = log_level+ } <-+ liftIO $ readMVar state_var+ case log_level of+ Nothing -> return ()+ Just level ->+ when (level >= Debug) $+ liftIO $+ BSL.putStrLn $+ "[request] " <> encode request_value++ case request_value of+ NotificationMessage _ ->+ return $+ flip Yield Stop $+ NotificationMessage $+ JSONRPCNotification rPC_VERSION "ok" Aeson.Null+ ErrorMessage err -> do+ throwError err400{errBody = encode err}+ -- Here we fill in the MVar for any pending responses from the client+ ResponseMessage (JSONRPCResponse _ (RequestId req_id) result) -> do+ cur_st <- liftIO $ readMVar state_var+ case Aeson.fromJSON req_id of+ Aeson.Error err -> return (Source.Error $ "Invalid request ID: " <> err)+ Aeson.Success req_index ->+ case IM.lookup req_index (mcp_pending_responses cur_st) of+ Nothing ->+ return $ Source.Error "Server received unexpected response"+ Just mvar -> liftIO $ do+ modifyMVar_ state_var $ \cur_st' ->+ return cur_st'{mcp_pending_responses = IM.delete req_index (mcp_pending_responses cur_st')}+ putMVar mvar result+ return Stop+ RequestMessage (JSONRPCRequest jsonrpc req_id method params) -> do+ -- Validate JSON-RPC version+ let bs_rpc_version = BSL.fromStrict (encodeUtf8 rPC_VERSION)+ when (jsonrpc /= rPC_VERSION) $+ throwError err400{errBody = "Invalid jsonrpc version, must be '" <> bs_rpc_version <> "'"}++ -- Validate request ID+ when (not $ isValidRequestId req_id) $+ throwError err400{errBody = "Invalid request ID, must be string, number, or null"}++ -- Initialize handler state on first initialize request+ when (not server_initialized && method == "initialize") $+ liftIO $+ modifyMVar_ state_var $+ \cur_st@MCPServerState+ { mcp_handler_state = handler_st+ , mcp_handler_init = mb_handler_init+ } -> case mb_handler_init of+ Nothing -> return cur_st+ Just handler_init -> do+ h_st' <- handler_init auth_user handler_st+ return cur_st{mcp_handler_state = h_st'}++ -- Process the request routing to the appropriate handler+ res <- liftIO $ modifyMVar state_var $ fmap swap <$> runStateT (processMethod server_initialized method params)++ -- Log the response if debug level+ cur_log_level <- liftIO $ mcp_log_level <$> readMVar state_var+ case cur_log_level of+ Just level | level >= Debug -> liftIO $ do+ let prefix = "[response] " <> BSL.fromStrict (encodeUtf8 method) <> " -> "+ let body = case res of+ ProcessSuccess val -> encode val+ ProcessRPCError code msg -> encode $ object ["error" .= msg, "code" .= code]+ ProcessServerError err -> encode $ object ["serverError" .= err]+ ProcessClientInput{} -> "client-input"+ BSL.putStrLn $ prefix <> body+ _ -> return ()++ -- Get the final result to return+ final_result <- liftIO $ handleProcessResult req_id res++ -- Finalize handler state after use+ liftIO $+ modifyMVar_ state_var $+ \st@MCPServerState+ { mcp_handler_state = handler_st+ , mcp_handler_finalize = mb_finalizer+ } -> do+ case mb_finalizer of+ Nothing -> return st+ Just handler_finalize -> do+ -- Call the finalizer to clean up the handler state+ handler_st' <- handler_finalize handler_st+ -- Update the state with the finalized handler state+ let st' = st{mcp_handler_state = handler_st'}+ -- Return the updated state+ return st'+ return final_result+ where+ -- Handle the result of processing a request+ handleProcessResult ::+ (Aeson.ToJSON a) =>+ RequestId ->+ ProcessResult a ->+ IO (StepT IO JSONRPCMessage)+ handleProcessResult req_id =+ \case+ ProcessServerError err ->+ return (Source.Error $ T.unpack err)+ ProcessRPCError code msg ->+ return $+ flip Yield Stop $+ ErrorMessage $+ JSONRPCError rPC_VERSION req_id $+ JSONRPCErrorInfo code msg Nothing+ ProcessClientInput ci_mthd ci_params ci_cont -> do+ mvar <- liftIO newEmptyMVar+ r_id <- liftIO $ modifyMVar state_var $ \cur_st -> do+ let pr = mcp_pending_responses cur_st+ let pr_id = mcp_pending_responses_next cur_st+ let pr' = IM.insert pr_id mvar pr+ let pr_id' = pr_id + 1+ return+ ( cur_st{mcp_pending_responses = pr', mcp_pending_responses_next = pr_id'}+ , pr_id+ )+ let msg =+ RequestMessage $+ JSONRPCRequest rPC_VERSION (RequestId $ Aeson.Number $ fromIntegral r_id) ci_mthd ci_params+ return $+ Yield msg $+ Effect $ do+ -- Wait for the response+ ci_resp <- liftIO $ takeMVar mvar+ -- Make sure to use the current updated state+ cur_st <- liftIO $ readMVar state_var+ (result, cur_st') <-+ liftIO $+ flip runStateT cur_st $+ runExceptT $+ ci_cont ci_resp+ liftIO $ modifyMVar_ state_var $ \_ -> return cur_st'+ case result of+ Left err -> do+ return $ Source.Error $ T.unpack err+ Right response ->+ handleProcessResult req_id response+ ProcessSuccess response -> do+ return $+ flip Yield Stop $+ ResponseMessage $+ JSONRPCResponse rPC_VERSION req_id (recurReplaceMeta $ toJSON response)++ -- Helper to throw unauthorized errors+ mcpAuthError :: Text -> Handler (StepT IO JSONRPCMessage)+ mcpAuthError err = throwError err401{errBody = encode $ object ["error" .= err]}
− src/MCP/Server/StdIO.hs
@@ -1,280 +0,0 @@-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE DuplicateRecordFields #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TypeFamilies #-}--{- |-Module : MCP.Server.StdIO-Description : MCP server implementation for stdin/stdout communication-Copyright : (C) 2025 Matthias Pall Gissurarson-License : MIT-Maintainer : mpg@mpg.is-Stability : experimental-Portability : GHC--This module provides MCP server implementation for stdin/stdout streams.--}-module MCP.Server.StdIO (- -- * Server Runner- runServer,- ServerConfig (..),-) where--import Control.Exception (catch, throwIO)-import Control.Monad.IO.Class (liftIO)-import Control.Monad.Reader (ask)-import Control.Monad.State.Strict (get, put)-import Data.Aeson (decode, fromJSON, object, toJSON)-import Data.Aeson qualified as Aeson-import Data.ByteString.Char8 qualified as BSC-import Data.ByteString.Lazy qualified as LBS-import Data.Text qualified as T-import System.IO.Error (isEOFError)--import MCP.Protocol-import MCP.Server (MCPServer (..), MCPServerM, ServerConfig (..), ServerState (..), initialServerState, runMCPServer, sendError, sendResponse)-import MCP.Types---- | Handle an incoming JSON-RPC message-handleMessage :: (MCPServer MCPServerM) => BSC.ByteString -> MCPServerM (Maybe ())-handleMessage input = do- case decode (LBS.fromStrict input) :: Maybe JSONRPCMessage of- Nothing -> do- config <- ask- sendError (configOutput config) (RequestId (toJSON ("unknown" :: T.Text))) $- JSONRPCErrorInfo (-32700) "Parse error" Nothing- return Nothing- Just msg -> case msg of- RequestMessage req -> do- handleRequest req- return (Just ())- NotificationMessage notif -> do- handleNotification notif- return (Just ())- _ -> do- config <- ask- sendError (configOutput config) (RequestId (toJSON ("unknown" :: T.Text))) $- JSONRPCErrorInfo (-32600) "Invalid Request" Nothing- return Nothing---- | Handle a JSON-RPC request-handleRequest :: (MCPServer MCPServerM) => JSONRPCRequest -> MCPServerM ()-handleRequest (JSONRPCRequest _ reqId method params) = do- config <- ask- state <- get-- case method of- "initialize" -> case params of- Just p -> case fromJSON p of- Aeson.Success initParams -> handleInitialize reqId initParams- Aeson.Error e ->- sendError (configOutput config) reqId $- JSONRPCErrorInfo (-32602) ("Invalid params: " <> T.pack e) Nothing- Nothing ->- sendError (configOutput config) reqId $- JSONRPCErrorInfo (-32602) "Missing params" Nothing- "ping" -> handlePing reqId- "resources/list" -> do- if not (serverInitialized state)- then- sendError (configOutput config) reqId $- JSONRPCErrorInfo (-32002) "Server not initialized" Nothing- else case params of- Just p -> case fromJSON p of- Aeson.Success listParams -> do- result <- handleListResources listParams- sendResponse (configOutput config) reqId result- Aeson.Error e ->- sendError (configOutput config) reqId $- JSONRPCErrorInfo (-32602) ("Invalid params: " <> T.pack e) Nothing- Nothing -> do- result <- handleListResources (ListResourcesParams Nothing)- sendResponse (configOutput config) reqId result- "resources/read" -> do- if not (serverInitialized state)- then- sendError (configOutput config) reqId $- JSONRPCErrorInfo (-32002) "Server not initialized" Nothing- else case params of- Just p -> case fromJSON p of- Aeson.Success readParams -> do- result <- handleReadResource readParams- sendResponse (configOutput config) reqId result- Aeson.Error e ->- sendError (configOutput config) reqId $- JSONRPCErrorInfo (-32602) ("Invalid params: " <> T.pack e) Nothing- Nothing ->- sendError (configOutput config) reqId $- JSONRPCErrorInfo (-32602) "Missing params" Nothing- "resources/templates/list" -> do- if not (serverInitialized state)- then- sendError (configOutput config) reqId $- JSONRPCErrorInfo (-32002) "Server not initialized" Nothing- else case params of- Just p -> case fromJSON p of- Aeson.Success listParams -> do- result <- handleListResourceTemplates listParams- sendResponse (configOutput config) reqId result- Aeson.Error e ->- sendError (configOutput config) reqId $- JSONRPCErrorInfo (-32602) ("Invalid params: " <> T.pack e) Nothing- Nothing -> do- result <- handleListResourceTemplates (ListResourceTemplatesParams Nothing)- sendResponse (configOutput config) reqId result- "prompts/list" -> do- if not (serverInitialized state)- then- sendError (configOutput config) reqId $- JSONRPCErrorInfo (-32002) "Server not initialized" Nothing- else case params of- Just p -> case fromJSON p of- Aeson.Success listParams -> do- result <- handleListPrompts listParams- sendResponse (configOutput config) reqId result- Aeson.Error e ->- sendError (configOutput config) reqId $- JSONRPCErrorInfo (-32602) ("Invalid params: " <> T.pack e) Nothing- Nothing -> do- result <- handleListPrompts (ListPromptsParams Nothing)- sendResponse (configOutput config) reqId result- "prompts/get" -> do- if not (serverInitialized state)- then- sendError (configOutput config) reqId $- JSONRPCErrorInfo (-32002) "Server not initialized" Nothing- else case params of- Just p -> case fromJSON p of- Aeson.Success getParams -> do- result <- handleGetPrompt getParams- sendResponse (configOutput config) reqId result- Aeson.Error e ->- sendError (configOutput config) reqId $- JSONRPCErrorInfo (-32602) ("Invalid params: " <> T.pack e) Nothing- Nothing ->- sendError (configOutput config) reqId $- JSONRPCErrorInfo (-32602) "Missing params" Nothing- "tools/list" -> do- if not (serverInitialized state)- then- sendError (configOutput config) reqId $- JSONRPCErrorInfo (-32002) "Server not initialized" Nothing- else case params of- Just p -> case fromJSON p of- Aeson.Success listParams -> do- result <- handleListTools listParams- sendResponse (configOutput config) reqId result- Aeson.Error e ->- sendError (configOutput config) reqId $- JSONRPCErrorInfo (-32602) ("Invalid params: " <> T.pack e) Nothing- Nothing -> do- result <- handleListTools (ListToolsParams Nothing)- sendResponse (configOutput config) reqId result- "tools/call" -> do- if not (serverInitialized state)- then- sendError (configOutput config) reqId $- JSONRPCErrorInfo (-32002) "Server not initialized" Nothing- else case params of- Just p -> case fromJSON p of- Aeson.Success callParams -> do- result <- handleCallTool callParams- sendResponse (configOutput config) reqId result- Aeson.Error e ->- sendError (configOutput config) reqId $- JSONRPCErrorInfo (-32602) ("Invalid params: " <> T.pack e) Nothing- Nothing ->- sendError (configOutput config) reqId $- JSONRPCErrorInfo (-32602) "Missing params" Nothing- "completion/complete" -> do- if not (serverInitialized state)- then- sendError (configOutput config) reqId $- JSONRPCErrorInfo (-32002) "Server not initialized" Nothing- else case params of- Just p -> case fromJSON p of- Aeson.Success completeParams -> do- result <- handleComplete completeParams- sendResponse (configOutput config) reqId result- Aeson.Error e ->- sendError (configOutput config) reqId $- JSONRPCErrorInfo (-32602) ("Invalid params: " <> T.pack e) Nothing- Nothing ->- sendError (configOutput config) reqId $- JSONRPCErrorInfo (-32602) "Missing params" Nothing- "logging/setLevel" -> case params of- Just p -> case fromJSON p of- Aeson.Success setLevelParams -> do- _ <- handleSetLevel setLevelParams- -- SetLevel response is just an empty object- sendResponse (configOutput config) reqId (object [])- Aeson.Error e ->- sendError (configOutput config) reqId $- JSONRPCErrorInfo (-32602) ("Invalid params: " <> T.pack e) Nothing- Nothing ->- sendError (configOutput config) reqId $- JSONRPCErrorInfo (-32602) "Missing params" Nothing- _ ->- sendError (configOutput config) reqId $- JSONRPCErrorInfo (-32601) "Method not found" Nothing--handleInitialize :: RequestId -> InitializeParams -> MCPServerM ()-handleInitialize reqId params = do- config <- ask- state <- get-- let InitializeParams{capabilities = clientCaps} = params-- put- state- { serverInitialized = True- , clientCapabilities = Just clientCaps- , serverInfo = Just (configServerInfo config)- }-- let result =- InitializeResult- { protocolVersion = "2024-11-05"- , capabilities = serverCapabilities state- , serverInfo = configServerInfo config- , instructions = Nothing- , _meta = Nothing- }-- sendResponse (configOutput config) reqId result--handlePing :: RequestId -> MCPServerM ()-handlePing reqId = do- config <- ask- -- Ping response is just an empty object in MCP- sendResponse (configOutput config) reqId (object [])---- | Handle a JSON-RPC notification-handleNotification :: JSONRPCNotification -> MCPServerM ()-handleNotification _ = do- return ()---- | Run the MCP server with the given configuration-runServer :: (MCPServer MCPServerM) => ServerConfig -> IO ()-runServer config = do- let initialState = initialServerState (configCapabilities config)-- let loop = do- eofOrLine <-- liftIO $- catch- (Right <$> BSC.hGetLine (configInput config))- (\e -> if isEOFError e then return (Left ()) else throwIO e)- case eofOrLine of- Left () -> return () -- EOF reached, exit gracefully- Right line -> do- result <- handleMessage line- case result of- Just () -> loop- Nothing -> return ()-- result <- runMCPServer config initialState loop- case result of- Left err -> putStrLn $ "Server error: " ++ T.unpack err- Right _ -> return () -- Don't print "Server terminated" for clean EOF
+ src/MCP/Server/Stdio.hs view
@@ -0,0 +1,187 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeApplications #-}++{- |+Module: MCP.Server.Stdio+License: MPL-2.0+Maintainer: <matti@dpella.io>, <lobo@dpella.io>++Stdio transport for the MCP server.++Reads JSON-RPC messages line-by-line from an input handle and writes+responses to an output handle. This transport does not use JWT+authentication; it is assumed that the process boundary provides+authentication.+-}+module MCP.Server.Stdio (+ serveStdio,+) where++import Control.Monad.Except+import Control.Monad.State.Lazy+import Data.Aeson (encode)+import Data.Aeson qualified as Aeson+import Data.ByteString.Char8 qualified as BS+import Data.ByteString.Lazy.Char8 qualified as BSL+import Data.IORef+import Data.Text qualified as T+import MCP.Server.Common+import System.IO (BufferMode (..), Handle, hFlush, hIsEOF, hSetBuffering)++{- | Run the MCP server over stdio transport.++Reads JSON-RPC messages line-by-line from the input handle,+processes them through the standard MCP message handler, and writes+JSON-RPC responses line-by-line to the output handle.++Stdio transport does not use JWT authentication; it is assumed that+the process boundary provides authentication.++The server runs until EOF is reached on the input handle.++__Note:__ When a handler returns 'ProcessClientInput', the server writes a+request to the client and blocks on the input handle until a response+arrives. There is no timeout — if the client never responds, the server+will block indefinitely.+-}+serveStdio ::+ -- | Input handle (typically stdin)+ Handle ->+ -- | Output handle (typically stdout)+ Handle ->+ -- | Initial server state+ MCPServerState ->+ IO ()+serveStdio h_in h_out initial_state = do+ hSetBuffering h_in LineBuffering+ hSetBuffering h_out LineBuffering+ state_ref <- newIORef initial_state+ loop state_ref+ where+ loop :: IORef MCPServerState -> IO ()+ loop state_ref = do+ eof <- hIsEOF h_in+ if eof+ then return ()+ else do+ line <- BS.hGetLine h_in+ case Aeson.eitherDecodeStrict' line of+ Left err -> do+ -- JSON parse error — write error response with null id+ writeMsg $+ ErrorMessage $+ JSONRPCError rPC_VERSION (RequestId Aeson.Null) $+ JSONRPCErrorInfo pARSE_ERROR (T.pack err) Nothing+ loop state_ref+ Right msg -> do+ processStdioMessage state_ref msg+ loop state_ref++ processStdioMessage :: IORef MCPServerState -> JSONRPCMessage -> IO ()+ processStdioMessage state_ref = \case+ NotificationMessage _ ->+ -- Notifications don't produce a response+ return ()+ ErrorMessage _ ->+ -- Client sent an error — nothing to respond to+ return ()+ ResponseMessage _ ->+ -- Unexpected response from client outside of ProcessClientInput+ return ()+ RequestMessage (JSONRPCRequest jsonrpc req_id method params) -> do+ -- Validate JSON-RPC version+ if jsonrpc /= rPC_VERSION+ then+ writeMsg $+ ErrorMessage $+ JSONRPCError rPC_VERSION req_id $+ JSONRPCErrorInfo iNVALID_REQUEST "Invalid jsonrpc version" Nothing+ else do+ -- Validate request ID+ if not (isValidRequestId req_id)+ then+ writeMsg $+ ErrorMessage $+ JSONRPCError rPC_VERSION req_id $+ JSONRPCErrorInfo iNVALID_REQUEST "Invalid request ID" Nothing+ else do+ cur_st <- readIORef state_ref+ let initialized = mcp_server_initialized cur_st++ -- Process the request+ (res, new_st) <- runStateT (processMethod initialized method params) cur_st+ writeIORef state_ref new_st++ -- Handle ProcessClientInput by synchronous read/write+ final_res <- resolveClientInput state_ref res++ -- Convert result to response message+ case final_res of+ ProcessServerError err ->+ writeMsg $+ ErrorMessage $+ JSONRPCError rPC_VERSION req_id $+ JSONRPCErrorInfo iNTERNAL_ERROR err Nothing+ ProcessRPCError rpc_code rpc_msg ->+ writeMsg $+ ErrorMessage $+ JSONRPCError rPC_VERSION req_id $+ JSONRPCErrorInfo rpc_code rpc_msg Nothing+ ProcessSuccess response ->+ writeMsg $+ ResponseMessage $+ JSONRPCResponse rPC_VERSION req_id (recurReplaceMeta response)+ ProcessClientInput{} ->+ -- Should not happen after resolveClientInput+ writeMsg $+ ErrorMessage $+ JSONRPCError rPC_VERSION req_id $+ JSONRPCErrorInfo iNTERNAL_ERROR "Unresolved client input" Nothing++ -- Finalize handler state+ st <- readIORef state_ref+ case mcp_handler_finalize st of+ Nothing -> return ()+ Just finalizer -> do+ h_st' <- finalizer (mcp_handler_state st)+ writeIORef state_ref st{mcp_handler_state = h_st'}++ -- \| Resolve ProcessClientInput by writing a request to the client and+ -- reading the response synchronously from stdin.+ resolveClientInput :: IORef MCPServerState -> ProcessResult Aeson.Value -> IO (ProcessResult Aeson.Value)+ resolveClientInput state_ref = \case+ ProcessClientInput ci_mthd ci_params ci_cont -> do+ -- Assign a request ID+ st <- readIORef state_ref+ let r_id = mcp_pending_responses_next st+ writeIORef state_ref st{mcp_pending_responses_next = r_id + 1}++ -- Write the server-to-client request+ writeMsg $+ RequestMessage $+ JSONRPCRequest rPC_VERSION (RequestId $ Aeson.Number $ fromIntegral r_id) ci_mthd ci_params++ -- Read the client's response synchronously+ resp_line <- BS.hGetLine h_in+ case Aeson.eitherDecodeStrict' @JSONRPCMessage resp_line of+ Right (ResponseMessage (JSONRPCResponse _ _ result)) -> do+ -- Run the continuation with the client's response+ cur_st <- readIORef state_ref+ (cont_result, new_st) <- runStateT (runExceptT $ ci_cont result) cur_st+ writeIORef state_ref new_st+ case cont_result of+ Left err -> return $ ProcessServerError err+ Right next_res -> resolveClientInput state_ref next_res+ Right (ErrorMessage (JSONRPCError _ _ (JSONRPCErrorInfo _ err_msg _))) ->+ return $ ProcessServerError err_msg+ _ ->+ return $ ProcessServerError "Expected response to client input request"+ other -> return other++ writeMsg :: JSONRPCMessage -> IO ()+ writeMsg msg = do+ BSL.hPut h_out (encode msg)+ BSL.hPut h_out "\n"+ hFlush h_out
− src/MCP/Types.hs
@@ -1,612 +0,0 @@-{-# 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 where- ModelHint :: {name :: Maybe Text} -> ModelHint- 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 where- RootsCapability :: {listChanged :: Maybe Bool} -> RootsCapability- deriving stock (Show, Eq, Generic)--$(deriveJSON defaultOptions{omitNothingFields = True} ''RootsCapability)---- | Prompts capability-data PromptsCapability where- PromptsCapability ::- {listChanged :: Maybe Bool} ->- PromptsCapability- 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 where- ToolsCapability :: {listChanged :: Maybe Bool} -> ToolsCapability- 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 where- Result :: {_meta :: Maybe Metadata} -> Result- deriving stock (Show, Eq, Generic)--$(deriveJSON defaultOptions{omitNothingFields = True, fieldLabelModifier = \case { "_meta" -> "_meta"; x -> x }} ''Result)
+ test/MCP/Integration.hs view
@@ -0,0 +1,651 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}++{- |+Module: MCP.Integration+License: MPL-2.0+Maintainer: <matti@dpella.io>, <lobo@dpella.io>++Integration tests for Model Context Protocol (MCP) server implementation+following the Servant testing cookbook approach+-}+module MCP.Integration where++import Control.Monad.IO.Class (liftIO)+import Data.Aeson (FromJSON, object, toJSON, (.=))+import Data.Aeson qualified as Aeson+import Data.Attoparsec.ByteString.Char8 as C+import Data.Attoparsec.ByteString.Lazy as P+import Data.ByteString qualified as BS+import Data.ByteString.Lazy qualified as LBS+import Data.Map qualified as Map+import Data.Text (Text)+import MCP.Protocol+import MCP.TestServer (+ availablePrompts,+ availableResourceTemplates,+ availableResources,+ availableTools,+ completionValues,+ createTestAppWithJWT,+ csvTestData,+ imageTestData,+ jsonTestData,+ )+import MCP.TestUtils+import MCP.Types+import Network.HTTP.Types qualified as HTTP+import Network.Wai.Test (SResponse (..))+import Servant+import Servant.Auth.Server qualified as AuthServer+import Test.Hspec+import Test.Hspec.Wai (+ WaiSession,+ get,+ post,+ request,+ shouldRespondWith,+ withState,+ )++-- * Integration Test Specifications++-- | Complete integration test suite using hspec-wai+integrationSpec :: Spec+integrationSpec = describe "MCP Integration Tests" $ do+ withState createTestAppWithJWT $ do+ serverLifecycleSpec+ authenticationSpec+ protocolFlowSpec+ endpointsSpec+ errorHandlingSpec+ preInitializationSpec+ protocolMethodsSpec+ toolErrorSpec+ resourceErrorSpec+ promptErrorSpec+ resourceTemplateSpec+ completionSpec+ unimplementedHandlerSpec+ nullParamsSpec++-- | Test server startup and basic HTTP behavior+serverLifecycleSpec :: SpecWith (AuthServer.JWTSettings, Application)+serverLifecycleSpec = describe "Server Lifecycle" $ do+ let ping_request =+ toJSON $+ createJSONRPCRequest Nothing (1 :: Int) "ping" Aeson.Null++ it "responds with 401 for unauthenticated GET requests" $+ get "/mcp" `shouldRespondWith` 401++ it "responds with 401 for unauthenticated POST requests" $ do+ let headers = [("Content-Type", "application/json")]+ mcpPostRequestExpects headers ping_request 401++ it "initialization handshake works with proper JWT authentication" $+ withAuthenticatedRequest $ \headers -> do+ -- the client sends the initialize request+ let init_req = toJSON createInitializeRequest+ resp_init <- mcpPostRequest headers init_req+ withValidJSONRPCResponse resp_init 1 validateInitializationResponse++ -- after successful initialization, the client sends notification to+ -- indicate readiness+ let notify_request = toJSON createInitializedNotification+ mcpPostRequestOk headers notify_request++-- | Test JWT authentication scenarios+authenticationSpec :: SpecWith (AuthServer.JWTSettings, Application)+authenticationSpec = describe "Authentication" $ do+ let ping_request = toJSON createPingRequest+ it "rejects requests without JWT token" $ do+ let headers = [("Content-Type", "application/json")]+ mcpPostRequestExpects headers ping_request 401++ it "rejects requests with invalid JWT token" $ do+ let headers =+ [ ("Content-Type", "application/json")+ , ("Authorization", "Bearer invalid-token")+ ]+ mcpPostRequestExpects headers ping_request 401++ it "rejects requests with malformed Authorization header" $ do+ let headers =+ [ ("Content-Type", "application/json")+ , ("Authorization", "NotBearer token")+ ]+ mcpPostRequestExpects headers ping_request 401++-- | Test MCP protocol flows (with proper JWT authentication)+protocolFlowSpec :: SpecWith (AuthServer.JWTSettings, Application)+protocolFlowSpec = describe "Protocol Flow" $ do+ it "rejects authenticated requests with missing jsonrpc field" $ do+ withAuthenticatedRequest $ \headers -> do+ let invalid_request = object ["id" .= (1 :: Int), "method" .= ("ping" :: Text)]+ mcpPostRequestExpects headers invalid_request 400++ it "rejects authenticated request with wrong jsonrpc version" $ do+ withAuthenticatedRequest $ \headers -> do+ let invalid_request =+ toJSON $+ createJSONRPCRequest (Just "1.0") (1 :: Int) "ping" Aeson.Null+ mcpPostRequestExpects headers invalid_request 400++ it "rejects authenticated request with wrong id" $ do+ withAuthenticatedRequest $ \headers -> do+ let invalid_id = object ["unexpected" .= ("value" :: Text)]+ let invalid_request =+ toJSON $+ createJSONRPCRequest Nothing invalid_id "ping" Aeson.Null+ mcpPostRequestExpects headers invalid_request 400++-- | Test various MCP endpoints to ensure they respond correctly+endpointsSpec :: SpecWith (AuthServer.JWTSettings, Application)+endpointsSpec = describe "Endpoint Health Check" $ do+ it "handles list/tools request successfully" $ do+ withInitializedServer $ \headers -> do+ let req_id = 2+ let expected_tools = ["addition-tool", "constant-msg-tool"]+ let list_tool_req = toJSON $ createListToolsRequest req_id+ -- the client sends the list tools request+ resp_list_tool <- mcpPostRequest headers list_tool_req+ -- verify response's id and listed tools+ withValidJSONRPCResponse resp_list_tool req_id $+ validateToolsListResponse expected_tools++ it "handles tool/call requests successfully" $ do+ withInitializedServer $ \headers -> do+ -- the client sends a tool/call request to the addition tool+ let req_id1 = 3+ let call_req1 =+ toJSON $+ createCallToolRequest req_id1 "addition-tool" [("arg1", toJSON (5 :: Int)), ("arg2", toJSON (7 :: Int))]+ resp_call <- mcpPostRequest headers call_req1+ withValidJSONRPCResponse resp_call req_id1 $+ validateToolCallResponse (toJSON (12 :: Int))++ -- the client sends a tool/call request to the constant message tool+ let req_id2 = 4+ let call_req2 =+ toJSON $+ createCallToolRequest req_id2 "constant-msg-tool" []+ resp_call2 <- mcpPostRequest headers call_req2+ withValidJSONRPCResponse resp_call2 req_id2 $+ validateToolCallResponse (toJSON ("Hello, World!" :: Text))++ it "handles prompt/list requests successfully" $ do+ withInitializedServer $ \headers -> do+ let req_id = 5+ let expected_prompts = ["code-review"]+ let list_prompt_req = toJSON $ createPromptListRequest req_id+ -- the client sends the list prompts request+ resp_list_prompt <- mcpPostRequest headers list_prompt_req+ -- verify response's id and listed prompts+ withValidJSONRPCResponse resp_list_prompt req_id $+ validatePromptListResponse expected_prompts++ it "handles get/prompt requests successfully" $ do+ withInitializedServer $ \headers -> do+ -- the client sends a get/prompt request for the "code-review" prompt+ let req_id = 6+ let args = Map.fromList [("code", "def foo():\n return 42")]+ let get_prompt_req = toJSON $ createGetPromptRequest req_id "code-review" (Just args)+ resp_get_prompt <- mcpPostRequest headers get_prompt_req+ -- verify response's id and retrieved prompt+ withValidJSONRPCResponse resp_get_prompt req_id $+ validateGetPromptResponse "A prompt to request a code review from the LLM" [User, Assistant]++ it "handles list/resources requests successfully" $ do+ withInitializedServer $ \headers -> do+ let req_id = 7+ let list_resources_req = toJSON $ createListResourcesRequest req_id+ -- the client sends the list resources request+ resp_list_resources <- mcpPostRequest headers list_resources_req+ -- verify response's id and listed resources+ withValidJSONRPCResponse resp_list_resources req_id $+ validateListResourcesResponse+ ["resource://example/document", "resource://example/data", "resource://example/image"]++ it "handles read/resource requests successfully" $ do+ withInitializedServer $ \headers -> do+ let requests =+ [ (8, "resource://example/document", csvTestData)+ , (9, "resource://example/data", jsonTestData)+ , (10, "resource://example/image", imageTestData)+ ]++ mapM_+ ( \(req_id, req_uri, expected_content) -> do+ let read_resource = toJSON $ createReadResourceRequest req_id req_uri+ -- the client sends the read resource request+ resp_read_resource <- mcpPostRequest headers read_resource+ -- verify response's id and read resource contents+ withValidJSONRPCResponse resp_read_resource req_id $+ validateReadResourceResponse req_uri expected_content+ )+ requests++-- | Test error handling scenarios+errorHandlingSpec :: SpecWith (AuthServer.JWTSettings, Application)+errorHandlingSpec = describe "Error Handling" $ do+ it "handles malformed JSON requests" $ do+ let headers = [("Content-Type", "application/json")]+ mcpPostRequestExpects headers "invalid json" 400++ it "handles requests with wrong content type" $+ post "/mcp" "some text" `shouldRespondWith` 415++ it "handles empty requests" $ do+ let headers = [("Content-Type", "application/json")]+ -- should auth fail first (401)?+ mcpPostRequestExpects headers "" 400++ it "handles large requests appropriately" $ do+ withAuthenticatedRequest $ \headers -> do+ let large_value = toJSON $ replicate 1000 ('x' :: Char)+ let large_json =+ toJSON $+ createJSONRPCRequest Nothing (1 :: Int) "ping" large_value+ mcpPostRequestOk headers large_json++-- | Test that methods are rejected before initialization+preInitializationSpec :: SpecWith (AuthServer.JWTSettings, Application)+preInitializationSpec = describe "Pre-Initialization Enforcement" $ do+ it "rejects tools/list before initialization" $+ withAuthenticatedRequest $ \headers -> do+ let req = toJSON $ createListToolsRequest 1+ resp <- mcpPostRequest headers req+ withValidJSONRPCErrorResponse resp 1 $ \err_info ->+ code err_info `shouldBe` sERVER_NOT_INITIALIZED++ it "rejects tools/call before initialization" $+ withAuthenticatedRequest $ \headers -> do+ let req = toJSON $ createCallToolRequest 1 "addition-tool" [("arg1", toJSON (1 :: Int)), ("arg2", toJSON (2 :: Int))]+ resp <- mcpPostRequest headers req+ withValidJSONRPCErrorResponse resp 1 $ \err_info ->+ code err_info `shouldBe` sERVER_NOT_INITIALIZED++ it "rejects resources/list before initialization" $+ withAuthenticatedRequest $ \headers -> do+ let req = toJSON $ createListResourcesRequest 1+ resp <- mcpPostRequest headers req+ withValidJSONRPCErrorResponse resp 1 $ \err_info ->+ code err_info `shouldBe` sERVER_NOT_INITIALIZED++ it "rejects prompts/list before initialization" $+ withAuthenticatedRequest $ \headers -> do+ let req = toJSON $ createPromptListRequest 1+ resp <- mcpPostRequest headers req+ withValidJSONRPCErrorResponse resp 1 $ \err_info ->+ code err_info `shouldBe` sERVER_NOT_INITIALIZED++ it "rejects resources/templates/list before initialization" $+ withAuthenticatedRequest $ \headers -> do+ let req = toJSON $ createListResourceTemplatesRequest 1+ resp <- mcpPostRequest headers req+ withValidJSONRPCErrorResponse resp 1 $ \err_info ->+ code err_info `shouldBe` sERVER_NOT_INITIALIZED++ it "rejects completion/complete before initialization" $+ withAuthenticatedRequest $ \headers -> do+ let req = toJSON $ createCompleteRequest 1 "code-review" "code" ""+ resp <- mcpPostRequest headers req+ withValidJSONRPCErrorResponse resp 1 $ \err_info ->+ code err_info `shouldBe` sERVER_NOT_INITIALIZED++ it "allows ping before initialization" $+ withAuthenticatedRequest $ \headers -> do+ let req = toJSON createPingRequest+ mcpPostRequestOk headers req++ it "allows initialize before initialization" $+ withAuthenticatedRequest $ \headers -> do+ let req = toJSON createInitializeRequest+ resp <- mcpPostRequest headers req+ withValidJSONRPCResponse resp 1 validateInitializationResponse++-- | Test various protocol-level methods+protocolMethodsSpec :: SpecWith (AuthServer.JWTSettings, Application)+protocolMethodsSpec = describe "Protocol Methods" $ do+ it "handles ping after initialization" $+ withInitializedServer $ \headers -> do+ let req = toJSON createPingRequest+ mcpPostRequestOk headers req++ it "handles logging/setLevel" $+ withInitializedServer $ \headers -> do+ let req = toJSON $ createSetLevelRequest 2 Debug+ mcpPostRequestOk headers req++ it "returns method_not_found for unknown methods" $+ withInitializedServer $ \headers -> do+ let req =+ toJSON $+ createJSONRPCRequest Nothing (2 :: Int) "unknown/method" (object [])+ resp <- mcpPostRequest headers req+ withValidJSONRPCErrorResponse resp 2 $ \err_info ->+ code err_info `shouldBe` mETHOD_NOT_FOUND++ it "handles GET /mcp with valid authentication" $+ withAuthenticatedRequest $ \headers -> do+ let get_headers = filter (\(h, _) -> h /= "Content-Type") headers+ let auth_header = filter (\(h, _) -> h == "Authorization") get_headers+ resp <- getWithHeaders "/mcp" auth_header+ liftIO $ HTTP.statusCode (simpleStatus resp) `shouldBe` 200++-- | Test tool call error scenarios+toolErrorSpec :: SpecWith (AuthServer.JWTSettings, Application)+toolErrorSpec = describe "Tool Call Errors" $ do+ it "returns error for non-existent tool" $+ withInitializedServer $ \headers -> do+ let req = toJSON $ createCallToolRequest 2 "nonexistent-tool" []+ resp <- mcpPostRequest headers req+ withValidJSONRPCErrorResponse resp 2 $ \err_info ->+ code err_info `shouldBe` 404++ it "returns error for missing required arguments" $+ withInitializedServer $ \headers -> do+ -- addition-tool requires arg1 and arg2, call with no args+ let req = toJSON $ createCallToolRequest 2 "addition-tool" []+ resp <- mcpPostRequest headers req+ withValidJSONRPCErrorResponse resp 2 $ \err_info ->+ code err_info `shouldBe` 400++ it "returns error for partially missing required arguments" $+ withInitializedServer $ \headers -> do+ -- addition-tool requires arg1 and arg2, call with only arg1+ let req = toJSON $ createCallToolRequest 2 "addition-tool" [("arg1", toJSON (5 :: Int))]+ resp <- mcpPostRequest headers req+ withValidJSONRPCErrorResponse resp 2 $ \err_info ->+ code err_info `shouldBe` 400++ it "returns error for invalid argument types" $+ withInitializedServer $ \headers -> do+ -- addition-tool expects numbers, pass strings+ let req = toJSON $ createCallToolRequest 2 "addition-tool" [("arg1", toJSON ("not-a-number" :: Text)), ("arg2", toJSON (5 :: Int))]+ resp <- mcpPostRequest headers req+ withValidJSONRPCErrorResponse resp 2 $ \err_info ->+ code err_info `shouldBe` 400++-- | Test resource error scenarios+resourceErrorSpec :: SpecWith (AuthServer.JWTSettings, Application)+resourceErrorSpec = describe "Resource Errors" $ do+ it "returns error for non-existent resource" $+ withInitializedServer $ \headers -> do+ let req = toJSON $ createReadResourceRequest 2 "resource://nonexistent/thing"+ resp <- mcpPostRequest headers req+ withValidJSONRPCErrorResponse resp 2 $ \err_info ->+ code err_info `shouldBe` 404++-- | Test prompt error scenarios+promptErrorSpec :: SpecWith (AuthServer.JWTSettings, Application)+promptErrorSpec = describe "Prompt Errors" $ do+ it "returns error for non-existent prompt" $+ withInitializedServer $ \headers -> do+ let req = toJSON $ createGetPromptRequest 2 "nonexistent-prompt" Nothing+ resp <- mcpPostRequest headers req+ withValidJSONRPCErrorResponse resp 2 $ \err_info ->+ code err_info `shouldBe` 404++-- | Test resource template operations+resourceTemplateSpec :: SpecWith (AuthServer.JWTSettings, Application)+resourceTemplateSpec = describe "Resource Templates" $ do+ it "handles resources/templates/list successfully" $+ withInitializedServer $ \headers -> do+ let req = toJSON $ createListResourceTemplatesRequest 2+ resp <- mcpPostRequest headers req+ withValidJSONRPCResponse resp 2 $ \(ListResourceTemplatesResult{resourceTemplates = templates}) -> do+ let template_names = fmap (\ResourceTemplate{name = n} -> n) templates+ template_names `shouldMatchList` ["user-profile", "log-file"]++ it "handles resources/templates/list with null params" $+ withInitializedServer $ \headers -> do+ let req =+ toJSON $+ createJSONRPCRequest Nothing (2 :: Int) "resources/templates/list" Aeson.Null+ resp <- mcpPostRequest headers req+ withValidJSONRPCResponse resp 2 $ \(ListResourceTemplatesResult{resourceTemplates = templates}) ->+ length templates `shouldBe` length availableResourceTemplates++-- | Test completion operations+completionSpec :: SpecWith (AuthServer.JWTSettings, Application)+completionSpec = describe "Completions" $ do+ it "handles completion/complete for known prompt argument" $+ withInitializedServer $ \headers -> do+ let req = toJSON $ createCompleteRequest 2 "code-review" "code" ""+ resp <- mcpPostRequest headers req+ withValidJSONRPCResponse resp 2 $ \(CompleteResult{completion = CompletionResult{values = vals}}) ->+ vals `shouldBe` completionValues++ it "handles completion/complete with prefix filter" $+ withInitializedServer $ \headers -> do+ let req = toJSON $ createCompleteRequest 2 "code-review" "code" "def"+ resp <- mcpPostRequest headers req+ withValidJSONRPCResponse resp 2 $ \(CompleteResult{completion = CompletionResult{values = vals}}) ->+ vals `shouldBe` ["def foo():"]++ it "returns empty completions for unknown prompt" $+ withInitializedServer $ \headers -> do+ let req = toJSON $ createCompleteRequest 2 "nonexistent" "arg" ""+ resp <- mcpPostRequest headers req+ withValidJSONRPCResponse resp 2 $ \(CompleteResult{completion = CompletionResult{values = vals}}) ->+ vals `shouldBe` []++ it "returns empty completions for unknown argument" $+ withInitializedServer $ \headers -> do+ let req = toJSON $ createCompleteRequest 2 "code-review" "nonexistent-arg" ""+ resp <- mcpPostRequest headers req+ withValidJSONRPCResponse resp 2 $ \(CompleteResult{completion = CompletionResult{values = vals}}) ->+ vals `shouldBe` []++-- | Test methods whose handlers are not configured+unimplementedHandlerSpec :: SpecWith (AuthServer.JWTSettings, Application)+unimplementedHandlerSpec = describe "Unimplemented Handler Methods" $ do+ it "returns method_not_found for resources/subscribe" $+ withInitializedServer $ \headers -> do+ let req = toJSON $ createSubscribeRequest 2 "resource://example/document"+ resp <- mcpPostRequest headers req+ withValidJSONRPCErrorResponse resp 2 $ \err_info ->+ code err_info `shouldBe` mETHOD_NOT_FOUND++-- | Test that null params work for list methods+nullParamsSpec :: SpecWith (AuthServer.JWTSettings, Application)+nullParamsSpec = describe "Null Params Handling" $ do+ it "handles tools/list with null params" $+ withInitializedServer $ \headers -> do+ let req =+ toJSON $+ createJSONRPCRequest Nothing (2 :: Int) "tools/list" Aeson.Null+ resp <- mcpPostRequest headers req+ withValidJSONRPCResponse resp 2 $ \(ListToolsResult{tools = ls_tools}) ->+ length ls_tools `shouldBe` length availableTools++ it "handles resources/list with null params" $+ withInitializedServer $ \headers -> do+ let req =+ toJSON $+ createJSONRPCRequest Nothing (2 :: Int) "resources/list" Aeson.Null+ resp <- mcpPostRequest headers req+ withValidJSONRPCResponse resp 2 $ \(ListResourcesResult{resources = ls_resources}) ->+ length ls_resources `shouldBe` length availableResources++ it "handles prompts/list with null params" $+ withInitializedServer $ \headers -> do+ let req =+ toJSON $+ createJSONRPCRequest Nothing (2 :: Int) "prompts/list" Aeson.Null+ resp <- mcpPostRequest headers req+ withValidJSONRPCResponse resp 2 $ \(ListPromptsResult{prompts = ls_prompts}) ->+ length ls_prompts `shouldBe` length availablePrompts++ it "handles resources/templates/list with null params" $+ withInitializedServer $ \headers -> do+ let req =+ toJSON $+ createJSONRPCRequest Nothing (2 :: Int) "resources/templates/list" Aeson.Null+ resp <- mcpPostRequest headers req+ withValidJSONRPCResponse resp 2 $ \(ListResourceTemplatesResult{resourceTemplates = templates}) ->+ length templates `shouldBe` length availableResourceTemplates++ it "rejects resources/read with null params" $+ withInitializedServer $ \headers -> do+ let req =+ toJSON $+ createJSONRPCRequest Nothing (2 :: Int) "resources/read" Aeson.Null+ resp <- mcpPostRequest headers req+ withValidJSONRPCErrorResponse resp 2 $ \err_info ->+ code err_info `shouldBe` iNVALID_PARAMS++-- * Helper Functions++-- ** Validation++{- | Validates that the JSON-RPC response can be parsed and applies the given+properties to the parsed result+-}+withValidJSONRPCResponse ::+ (FromJSON a) =>+ SResponse ->+ Int ->+ (a -> Expectation) ->+ WaiSession AuthServer.JWTSettings ()+withValidJSONRPCResponse resp expected_id props = do+ liftIO $+ case parseJSONRPCResponse resp of+ Right (JSONRPCResponse rpc_vrs req_id json_result) -> do+ case Aeson.fromJSON json_result of+ Aeson.Error err -> expectationFailure $ "Failed to parse result: " <> err+ Aeson.Success val -> do+ rpc_vrs `shouldBe` rPC_VERSION+ req_id `shouldBe` toRequestId expected_id+ props val+ Left err_msg -> expectationFailure err_msg++-- | Validates that the initialization response conforms to the protocol+validateInitializationResponse :: InitializeResult -> Expectation+validateInitializationResponse InitializeResult{protocolVersion = init_protocol_version} = do+ init_protocol_version `shouldBe` pROTOCOL_VERSION++-- | Validates that the tools list response contains the expected tools+validateToolsListResponse :: [Text] -> ListToolsResult -> Expectation+validateToolsListResponse expected_tools = \case+ (ListToolsResult{tools = ls_tools}) -> do+ let tools_names = fmap (\(Tool{name = t_name}) -> t_name) ls_tools+ tools_names `shouldMatchList` expected_tools++-- | Validates that the tool call response contains the expected result+validateToolCallResponse :: Aeson.Value -> CallToolResult -> Expectation+validateToolCallResponse expected_result = \case+ (CallToolResult{structuredContent = structured}) -> do+ case structured of+ Just map_results ->+ Map.lookup "result" map_results `shouldBe` Just expected_result+ Nothing -> expectationFailure "Expected structured content in tool call result"++-- | Validates that the prompt list response contains the expected prompts+validatePromptListResponse :: [Text] -> ListPromptsResult -> Expectation+validatePromptListResponse expected_prompts = \case+ (ListPromptsResult{prompts = ls_prompts}) -> do+ let prompt_names = fmap (\(Prompt{name = p_name}) -> p_name) ls_prompts+ prompt_names `shouldMatchList` expected_prompts++{- | Validates that the get prompt response contains the expected description+and messages with correct roles+-}+validateGetPromptResponse :: Text -> [Role] -> GetPromptResult -> Expectation+validateGetPromptResponse expected_description expected_roles = \case+ (GetPromptResult{description = p_description, messages = p_messages}) -> do+ p_description `shouldBe` Just expected_description+ length p_messages `shouldBe` length expected_roles+ fmap (\(PromptMessage{role = m_role}) -> m_role) p_messages+ `shouldSatisfy` all (`elem` expected_roles)++-- | Validates that the list resources response is as expected+validateListResourcesResponse :: [Text] -> ListResourcesResult -> Expectation+validateListResourcesResponse expected_resources = \case+ (ListResourcesResult{resources = ls_resources}) -> do+ let resource_uris = fmap (\(Resource{uri = r_uri}) -> r_uri) ls_resources+ resource_uris `shouldMatchList` expected_resources++-- | Validates that reading a resource response is as expected+validateReadResourceResponse :: Text -> Text -> ReadResourceResult -> Expectation+validateReadResourceResponse expected_uri expected_content = \case+ (ReadResourceResult{contents = r_contents}) -> do+ let uris_contents =+ fmap+ ( \case+ TextResource (TextResourceContents{uri = r_uri, text = r_content}) -> (r_uri, r_content)+ BlobResource (BlobResourceContents{uri = r_uri, blob = r_content}) -> (r_uri, r_content)+ )+ r_contents+ case uris_contents of+ [(res_uri, res_content)] -> do+ res_uri `shouldBe` expected_uri+ res_content `shouldBe` expected_content+ _ -> expectationFailure $ "Expected exactly 1 resource, got " <> show (length uris_contents)++{- | Validates that the JSON-RPC error response can be parsed and applies+the given properties to the parsed error info+-}+withValidJSONRPCErrorResponse ::+ SResponse ->+ Int ->+ (JSONRPCErrorInfo -> Expectation) ->+ WaiSession AuthServer.JWTSettings ()+withValidJSONRPCErrorResponse resp expected_id props = do+ liftIO $+ case parseJSONRPCErrorResponse resp of+ Right (JSONRPCError rpc_vrs req_id err_info) -> do+ rpc_vrs `shouldBe` rPC_VERSION+ req_id `shouldBe` toRequestId expected_id+ props err_info+ Left err_msg -> expectationFailure err_msg++-- | Helper for making GET requests with specific headers+getWithHeaders ::+ BS.ByteString ->+ [(HTTP.HeaderName, BS.ByteString)] ->+ WaiSession st SResponse+getWithHeaders path headers =+ Test.Hspec.Wai.request HTTP.methodGet path headers ""++-- ** Parsing++-- | Parse JSON-RPC response from SSE response body+parseJSONRPCResponse :: SResponse -> Either String JSONRPCResponse+parseJSONRPCResponse resp =+ case extractSSEData (simpleBody resp) of+ Just bs_response ->+ case Aeson.decodeStrict bs_response of+ Just json_resp -> Right json_resp+ Nothing -> Left $ "Failed to decode JSON-RPC response from: " <> show bs_response+ Nothing -> Left $ "Failed to extract JSON data from SSE response: " <> show (simpleBody resp)++-- | Parse JSON-RPC error response from SSE response body+parseJSONRPCErrorResponse :: SResponse -> Either String JSONRPCError+parseJSONRPCErrorResponse resp =+ case extractSSEData (simpleBody resp) of+ Just bs_response ->+ case Aeson.decodeStrict bs_response of+ Just json_err -> Right json_err+ Nothing -> Left $ "Failed to decode JSON-RPC error from: " <> show bs_response+ Nothing -> Left $ "Failed to extract JSON data from SSE response: " <> show (simpleBody resp)++{- | Extract JSON data from Server-Sent Events (SSE) format+SSE format: "event: message\ndata:{json}\n\n"+-}+extractSSEData :: LBS.ByteString -> Maybe BS.ByteString+extractSSEData sseBody = snd <$> P.maybeResult (P.parse parseEvent sseBody)+ where+ parseEvent = do+ _ <- P.string "event:"+ msg_type <- P.takeWhileIncluding (not . C.isEndOfLine)+ _ <- C.string "data:"+ json_data <- P.takeWhile (not . C.isEndOfLine)+ return (msg_type, json_data)
+ test/MCP/StdioIntegration.hs view
@@ -0,0 +1,349 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}++{- |+Module: MCP.StdioIntegration+License: MPL-2.0+Maintainer: <matti@dpella.io>, <lobo@dpella.io>++Integration tests for the MCP stdio transport.+Tests serveStdio by creating in-process pipes and exercising+the full protocol over them.+-}+module MCP.StdioIntegration where++import Control.Concurrent (forkIO, killThread, threadDelay)+import Control.Exception (bracket)+import Data.Aeson (FromJSON, toJSON)+import Data.Aeson qualified as Aeson+import Data.ByteString.Char8 qualified as BS+import Data.ByteString.Lazy qualified as BSL+import Data.Map qualified as Map+import Data.Text (Text)+import MCP.Protocol+import MCP.Server (+ MCPServerState (..),+ serveStdio,+ )+import MCP.TestServer (+ availableResourceTemplates,+ completionValues,+ csvTestData,+ initializeTestState,+ mb_handler_finalize,+ processHandlers,+ )+import MCP.TestUtils+import MCP.Types+import System.IO (BufferMode (..), Handle, hClose, hFlush, hSetBuffering)+import System.Process (createPipe)+import Test.Hspec++-- * Stdio Integration Tests++-- | Complete stdio integration test suite+stdioIntegrationSpec :: Spec+stdioIntegrationSpec = describe "Stdio Transport" $ do+ initializationSpec+ pingSpec+ toolSpec+ resourceSpec+ resourceTemplateStdioSpec+ promptSpec+ completionStdioSpec+ loggingSpec+ errorSpec++-- ** Test Helpers++-- | Create initial server state for stdio tests (no JWT/user)+createStdioServerState :: MCPServerState+createStdioServerState =+ MCPServerState+ { mcp_server_initialized = False+ , mcp_handler_state = initializeTestState+ , mcp_handler_init = Nothing -- No user in stdio mode+ , mcp_handler_finalize = mb_handler_finalize+ , mcp_client_capabilities = Nothing+ , mcp_log_level = Nothing+ , mcp_pending_responses = mempty+ , mcp_pending_responses_next = 1+ , mcp_server_capabilities =+ ServerCapabilities+ { logging = Just LoggingCapability+ , prompts = Just (PromptsCapability{listChanged = Nothing})+ , resources = Just (ResourcesCapability{listChanged = Nothing, subscribe = Nothing})+ , tools = Just (ToolsCapability{listChanged = Just True})+ , completions = Just CompletionsCapability+ , experimental = Nothing+ }+ , mcp_implementation = Implementation "test-server-stdio" "1.0.0" Nothing+ , mcp_instructions = Nothing+ , mcp_process_handlers = processHandlers+ }++{- | Bracket that creates pipes, starts the stdio server in a background+thread, and yields the client-side handles for reading/writing.+-}+withStdioServer ::+ ((Handle, Handle) -> IO a) ->+ IO a+withStdioServer f = do+ -- Pipe for server reading (client writes here)+ (client_to_server_read, client_to_server_write) <- createPipe+ -- Pipe for server writing (client reads here)+ (server_to_client_read, server_to_client_write) <- createPipe++ hSetBuffering client_to_server_write LineBuffering+ hSetBuffering server_to_client_read LineBuffering++ bracket+ ( do+ tid <- forkIO $ serveStdio client_to_server_read server_to_client_write createStdioServerState+ -- Small delay to let the server start its loop+ threadDelay 10000+ return tid+ )+ ( \tid -> do+ killThread tid+ hClose client_to_server_write+ hClose client_to_server_read+ hClose server_to_client_write+ hClose server_to_client_read+ )+ (\_ -> f (server_to_client_read, client_to_server_write))++-- | Send a JSON-RPC message to the server via the write handle+sendMsg :: (Aeson.ToJSON a) => Handle -> a -> IO ()+sendMsg h msg = do+ BSL.hPut h (Aeson.encode msg)+ BSL.hPut h "\n"+ hFlush h++-- | Read a JSON-RPC response from the server via the read handle+recvResponse :: Handle -> IO JSONRPCResponse+recvResponse h = do+ line <- BS.hGetLine h+ case Aeson.eitherDecodeStrict' line of+ Right resp -> return resp+ Left err -> fail $ "Failed to parse response: " <> err++-- | Read a JSON-RPC error from the server via the read handle+recvError :: Handle -> IO JSONRPCError+recvError h = do+ line <- BS.hGetLine h+ case Aeson.eitherDecodeStrict' line of+ Right err -> return err+ Left parse_err -> fail $ "Failed to parse error response: " <> parse_err++-- | Read a JSON-RPC response and extract a typed result+recvTypedResponse :: (FromJSON a) => Handle -> IO (RequestId, a)+recvTypedResponse h = do+ JSONRPCResponse _rpc req_id res_val <- recvResponse h+ case Aeson.fromJSON res_val of+ Aeson.Success val -> return (req_id, val)+ Aeson.Error err -> fail $ "Failed to parse typed result: " <> err++-- | Initialize the server (send initialize + initialized notification)+initializeServer :: Handle -> Handle -> IO ()+initializeServer h_read h_write = do+ -- Send initialize request+ sendMsg h_write (toJSON createInitializeRequest)+ (_, InitializeResult{protocolVersion = pv}) <- recvTypedResponse h_read+ pv `shouldBe` pROTOCOL_VERSION++ -- Send initialized notification+ sendMsg h_write (toJSON createInitializedNotification)++-- ** Test Specs++initializationSpec :: Spec+initializationSpec = describe "Initialization" $ do+ it "completes the initialization handshake" $ do+ withStdioServer $ \(h_read, h_write) -> do+ sendMsg h_write (toJSON createInitializeRequest)+ (req_id, InitializeResult{protocolVersion = pv}) <- recvTypedResponse h_read+ req_id `shouldBe` toRequestId (1 :: Int)+ pv `shouldBe` pROTOCOL_VERSION++ -- Send initialized notification (no response expected)+ sendMsg h_write (toJSON createInitializedNotification)++pingSpec :: Spec+pingSpec = describe "Ping" $ do+ it "handles ping after initialization" $ do+ withStdioServer $ \(h_read, h_write) -> do+ initializeServer h_read h_write+ sendMsg h_write (toJSON createPingRequest)+ resp <- recvResponse h_read+ let JSONRPCResponse _ _ res_val = resp+ res_val `shouldBe` Aeson.Object mempty++toolSpec :: Spec+toolSpec = describe "Tools" $ do+ it "lists available tools" $ do+ withStdioServer $ \(h_read, h_write) -> do+ initializeServer h_read h_write+ sendMsg h_write (toJSON $ createListToolsRequest 2)+ (_, ListToolsResult{tools = ts}) <- recvTypedResponse h_read+ let tool_names = fmap (\Tool{name = n} -> n) ts+ tool_names `shouldMatchList` ["addition-tool", "constant-msg-tool"]++ it "calls the addition tool" $ do+ withStdioServer $ \(h_read, h_write) -> do+ initializeServer h_read h_write+ sendMsg h_write (toJSON $ createCallToolRequest 2 "addition-tool" [("arg1", toJSON (5 :: Int)), ("arg2", toJSON (7 :: Int))])+ (_, CallToolResult{structuredContent = structured}) <- recvTypedResponse h_read+ case structured of+ Just m -> Map.lookup "result" m `shouldBe` Just (toJSON (12 :: Int))+ Nothing -> expectationFailure "Expected structured content"++ it "calls the constant message tool" $ do+ withStdioServer $ \(h_read, h_write) -> do+ initializeServer h_read h_write+ sendMsg h_write (toJSON $ createCallToolRequest 2 "constant-msg-tool" [])+ (_, CallToolResult{structuredContent = structured}) <- recvTypedResponse h_read+ case structured of+ Just m -> Map.lookup "result" m `shouldBe` Just (toJSON ("Hello, World!" :: Text))+ Nothing -> expectationFailure "Expected structured content"++ it "returns error for non-existent tool" $ do+ withStdioServer $ \(h_read, h_write) -> do+ initializeServer h_read h_write+ sendMsg h_write (toJSON $ createCallToolRequest 2 "nonexistent-tool" [])+ JSONRPCError _ _ err_info <- recvError h_read+ code err_info `shouldBe` 404++resourceSpec :: Spec+resourceSpec = describe "Resources" $ do+ it "lists available resources" $ do+ withStdioServer $ \(h_read, h_write) -> do+ initializeServer h_read h_write+ sendMsg h_write (toJSON $ createListResourcesRequest 2)+ (_, ListResourcesResult{resources = rs}) <- recvTypedResponse h_read+ let resource_uris = fmap (\Resource{uri = u} -> u) rs+ resource_uris `shouldMatchList` ["resource://example/document", "resource://example/data", "resource://example/image"]++ it "reads a text resource" $ do+ withStdioServer $ \(h_read, h_write) -> do+ initializeServer h_read h_write+ sendMsg h_write (toJSON $ createReadResourceRequest 2 "resource://example/document")+ (_, ReadResourceResult{contents = cs}) <- recvTypedResponse h_read+ case cs of+ [TextResource (TextResourceContents{text = t})] -> t `shouldBe` csvTestData+ _ -> expectationFailure "Expected single text resource"++ it "returns error for non-existent resource" $ do+ withStdioServer $ \(h_read, h_write) -> do+ initializeServer h_read h_write+ sendMsg h_write (toJSON $ createReadResourceRequest 2 "resource://nonexistent")+ JSONRPCError _ _ err_info <- recvError h_read+ code err_info `shouldBe` 404++resourceTemplateStdioSpec :: Spec+resourceTemplateStdioSpec = describe "Resource Templates" $ do+ it "lists available resource templates" $ do+ withStdioServer $ \(h_read, h_write) -> do+ initializeServer h_read h_write+ sendMsg h_write (toJSON $ createListResourceTemplatesRequest 2)+ (_, ListResourceTemplatesResult{resourceTemplates = ts}) <- recvTypedResponse h_read+ length ts `shouldBe` length availableResourceTemplates++promptSpec :: Spec+promptSpec = describe "Prompts" $ do+ it "lists available prompts" $ do+ withStdioServer $ \(h_read, h_write) -> do+ initializeServer h_read h_write+ sendMsg h_write (toJSON $ createPromptListRequest 2)+ (_, ListPromptsResult{prompts = ps}) <- recvTypedResponse h_read+ let prompt_names = fmap (\Prompt{name = n} -> n) ps+ prompt_names `shouldMatchList` ["code-review"]++ it "gets a prompt with arguments" $ do+ withStdioServer $ \(h_read, h_write) -> do+ initializeServer h_read h_write+ let args = Map.fromList [("code", "def foo():\n return 42")]+ sendMsg h_write (toJSON $ createGetPromptRequest 2 "code-review" (Just args))+ (_, GetPromptResult{description = desc, messages = msgs}) <- recvTypedResponse h_read+ desc `shouldBe` Just "A prompt to request a code review from the LLM"+ length msgs `shouldBe` 2+ fmap (\PromptMessage{role = r} -> r) msgs `shouldBe` [User, Assistant]++ it "returns error for non-existent prompt" $ do+ withStdioServer $ \(h_read, h_write) -> do+ initializeServer h_read h_write+ sendMsg h_write (toJSON $ createGetPromptRequest 2 "nonexistent" Nothing)+ JSONRPCError _ _ err_info <- recvError h_read+ code err_info `shouldBe` 404++completionStdioSpec :: Spec+completionStdioSpec = describe "Completions" $ do+ it "returns completions for known prompt argument" $ do+ withStdioServer $ \(h_read, h_write) -> do+ initializeServer h_read h_write+ sendMsg h_write (toJSON $ createCompleteRequest 2 "code-review" "code" "")+ (_, CompleteResult{completion = CompletionResult{values = vals}}) <- recvTypedResponse h_read+ vals `shouldBe` completionValues++ it "returns filtered completions with prefix" $ do+ withStdioServer $ \(h_read, h_write) -> do+ initializeServer h_read h_write+ sendMsg h_write (toJSON $ createCompleteRequest 2 "code-review" "code" "def")+ (_, CompleteResult{completion = CompletionResult{values = vals}}) <- recvTypedResponse h_read+ vals `shouldBe` ["def foo():"]++ it "returns empty completions for unknown prompt" $ do+ withStdioServer $ \(h_read, h_write) -> do+ initializeServer h_read h_write+ sendMsg h_write (toJSON $ createCompleteRequest 2 "nonexistent" "arg" "")+ (_, CompleteResult{completion = CompletionResult{values = vals}}) <- recvTypedResponse h_read+ vals `shouldBe` []++loggingSpec :: Spec+loggingSpec = describe "Logging" $ do+ it "handles logging/setLevel" $ do+ withStdioServer $ \(h_read, h_write) -> do+ initializeServer h_read h_write+ sendMsg h_write (toJSON $ createSetLevelRequest 2 Debug)+ resp <- recvResponse h_read+ let JSONRPCResponse _ _ res_val = resp+ res_val `shouldBe` Aeson.Null++errorSpec :: Spec+errorSpec = describe "Error Handling" $ do+ it "rejects tools/list before initialization" $ do+ withStdioServer $ \(h_read, h_write) -> do+ sendMsg h_write (toJSON $ createListToolsRequest 1)+ JSONRPCError _ _ err_info <- recvError h_read+ code err_info `shouldBe` sERVER_NOT_INITIALIZED++ it "returns method_not_found for unknown methods" $ do+ withStdioServer $ \(h_read, h_write) -> do+ initializeServer h_read h_write+ sendMsg h_write (toJSON $ createJSONRPCRequest Nothing (2 :: Int) "unknown/method" (Aeson.object []))+ JSONRPCError _ _ err_info <- recvError h_read+ code err_info `shouldBe` mETHOD_NOT_FOUND++ it "returns parse error for malformed JSON" $ do+ withStdioServer $ \(h_read, h_write) -> do+ BS.hPut h_write "this is not json\n"+ hFlush h_write+ JSONRPCError _ _ err_info <- recvError h_read+ code err_info `shouldBe` pARSE_ERROR++ it "handles EOF gracefully" $ do+ withStdioServer $ \(h_read, h_write) -> do+ initializeServer h_read h_write+ sendMsg h_write (toJSON createPingRequest)+ _ <- recvResponse h_read+ -- Closing the write handle signals EOF to the server+ hClose h_write+ -- Server should exit cleanly (no exception)+ -- Wait a bit for the server to process EOF+ threadDelay 50000+ return ()
+ test/MCP/TestServer.hs view
@@ -0,0 +1,481 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+{-# OPTIONS_GHC -Wno-orphans #-}++{- |+Module: MCP.TestServer+License: MPL-2.0+Maintainer: <matti@dpella.io>, <lobo@dpella.io>++This module provides server configuration utilities for testing the MCP server+implementation.+-}+module MCP.TestServer where++import Control.Concurrent.MVar (MVar, newMVar)+import Data.Aeson (FromJSON, ToJSON, fromJSON, object, toJSON, (.=))+import Data.Aeson qualified as Aeson+import Data.Map qualified as Map+import Data.Text (Text)+import Data.Text qualified as T+import GHC.Generics (Generic)+import MCP.Server+import Network.Wai (Application)+import Servant (Context (..), Proxy (..), serveWithContext)+import Servant.Auth.Server qualified as AuthServer++-- * Server Configuration Types and Functions++-- ** Test user++-- | A simple test user for MCP tests+data TestUser = TestUser+ { userId :: Text+ , userName :: Text+ , userEmail :: Text+ }+ deriving (Eq, Show, Generic)++instance ToJSON TestUser++instance FromJSON TestUser++instance AuthServer.ToJWT TestUser++instance AuthServer.FromJWT TestUser++type instance MCPHandlerState = TestStateHandler++type instance MCPHandlerUser = TestUser++-- ** MCP Handler State++-- | A simple state handler for MCP tests+newtype TestStateHandler = TestStateHandler {handle_curr_user :: Maybe Text}++-- | Initialize a fresh test state handler+initializeTestState :: TestStateHandler+initializeTestState = TestStateHandler{handle_curr_user = Nothing}++-- | Initialization of the handler with the current user+mb_handler_init :: Maybe (TestUser -> TestStateHandler -> IO TestStateHandler)+mb_handler_init =+ Just $ \user state -> do+ let usr_id = userId user+ return state{handle_curr_user = Just usr_id}++-- | Finalization of the handler, clearing the current user+mb_handler_finalize :: Maybe (TestStateHandler -> IO TestStateHandler)+mb_handler_finalize =+ Just $ \state -> do+ return state{handle_curr_user = Nothing}++-- ** Process Handlers++-- | Process handlers to be used in tests+processHandlers :: ProcessHandlers+processHandlers =+ defaultProcessHandlers+ { listResourcesHandler = Just processListResourcesHandler+ , readResourceHandler = Just processReadResourceHandler+ , listToolsHandler = Just processListToolsHandler+ , callToolHandler = Just processCallToolHandler+ , listPromptsHandler = Just processListPromptsHandler+ , getPromptHandler = Just processGetPromptHandler+ , listResourceTemplatesHandler = Just processListResourceTemplatesHandler+ , completeHandler = Just processCompleteHandler+ }+ where+ -- Example process handler for listing resources+ processListResourcesHandler :: ListResourcesParams -> MCPServerT (ProcessResult ListResourcesResult)+ processListResourcesHandler =+ return $+ return $+ ProcessSuccess $+ ListResourcesResult+ { resources = availableResources+ , nextCursor = Nothing+ , _meta = Nothing+ }++ -- Example process handler for reading a resource+ processReadResourceHandler :: ReadResourceParams -> MCPServerT (ProcessResult ReadResourceResult)+ processReadResourceHandler (ReadResourceParams{uri = req_uri}) = do+ let rsc_content =+ case req_uri of+ "resource://example/document" ->+ Just $+ TextResource $+ TextResourceContents+ { uri = req_uri+ , text = csvTestData+ , mimeType = Just "text/plain"+ , _meta = Nothing+ }+ "resource://example/data" ->+ Just $+ TextResource $+ TextResourceContents+ { uri = req_uri+ , text = jsonTestData+ , mimeType = Just "application/json"+ , _meta = Nothing+ }+ "resource://example/image" ->+ Just $+ BlobResource $+ BlobResourceContents+ { uri = req_uri+ , blob = imageTestData+ , mimeType = Just "image/png"+ , _meta = Nothing+ }+ _ -> Nothing+ case rsc_content of+ Just rsc_content' ->+ return $ ProcessSuccess (ReadResourceResult{contents = [rsc_content'], _meta = Nothing})+ Nothing ->+ return $ ProcessRPCError 404 "Resource not found"++ -- Example process handler for listing tools+ processListToolsHandler :: ListToolsParams -> MCPServerT (ProcessResult ListToolsResult)+ processListToolsHandler =+ return $+ return $+ ProcessSuccess $+ ListToolsResult+ { tools = availableTools+ , nextCursor = Nothing+ , _meta = Nothing+ }++ -- Example process handler for calling tools+ processCallToolHandler :: CallToolParams -> MCPServerT (ProcessResult CallToolResult)+ processCallToolHandler (CallToolParams t_name t_args) = do+ case t_name of+ "addition-tool" -> case t_args of+ Just args_map -> do+ let parseInt v =+ case fromJSON @Int v of+ Aeson.Success i -> Just i+ Aeson.Error _ -> Nothing+ let mb_arg1 = Map.lookup "arg1" args_map >>= parseInt+ let mb_arg2 = Map.lookup "arg2" args_map >>= parseInt+ case (mb_arg1, mb_arg2) of+ (Just arg1, Just arg2) -> do+ let t_result = arg1 + arg2+ let result_value = Map.fromList [("result", toJSON t_result)]+ let ctx_result =+ TextBlock $ TextContent "text" ("The result is: " <> T.pack (show t_result)) Nothing Nothing+ return $ ProcessSuccess (CallToolResult [ctx_result] (Just result_value) Nothing Nothing)+ _ -> return $ ProcessRPCError 400 "addition-tool: Missing or invalid arguments"+ Nothing -> return $ ProcessRPCError 400 "addition-tool: Missing arguments"+ "constant-msg-tool" -> do+ let t_result = "Hello, World!" :: Text+ let result_value = Map.fromList [("result", toJSON t_result)]+ let ctx_result =+ TextBlock $ TextContent "text" ("This is a constant message: " <> t_result) Nothing Nothing+ return $ ProcessSuccess (CallToolResult [ctx_result] (Just result_value) Nothing Nothing)+ _ -> return $ ProcessRPCError 404 "Tool not found"++ -- Example list prompts handler+ processListPromptsHandler :: ListPromptsParams -> MCPServerT (ProcessResult ListPromptsResult)+ processListPromptsHandler =+ return $+ return $+ ProcessSuccess $+ ListPromptsResult+ { prompts = availablePrompts+ , nextCursor = Nothing+ , _meta = Nothing+ }++ -- Example get prompt handler+ processGetPromptHandler :: GetPromptParams -> MCPServerT (ProcessResult GetPromptResult)+ processGetPromptHandler = \case+ GetPromptParams{name = "code-review", arguments = Just map_args} -> do+ let msgs =+ [+ ( User+ , T.unlines $+ [ "Help me revise the syntax of the following python code snippet:"+ , "```python"+ , Map.findWithDefault "" "code" map_args+ , "```"+ ]+ )+ , (Assistant, "Yes, I can help with that. I will make sure to check common issues and keep practices in mind.")+ ]+ let toMsg msg_role msg_content =+ PromptMessage+ { role = msg_role+ , content =+ TextBlock $+ TextContent+ { textType = "text"+ , text = msg_content+ , annotations = Nothing+ , _meta = Nothing+ }+ }+ return $+ ProcessSuccess $+ GetPromptResult+ { description = Just "A prompt to request a code review from the LLM"+ , messages = fmap (uncurry toMsg) msgs+ , _meta = Nothing+ }+ _ -> return $ ProcessRPCError 404 "Prompt not found"++ -- Resource templates handler+ processListResourceTemplatesHandler :: ListResourceTemplatesParams -> MCPServerT (ProcessResult ListResourceTemplatesResult)+ processListResourceTemplatesHandler =+ return $+ return $+ ProcessSuccess $+ ListResourceTemplatesResult+ { resourceTemplates = availableResourceTemplates+ , nextCursor = Nothing+ , _meta = Nothing+ }++ -- Completion handler: prefix-based completions for the code-review prompt's "code" argument+ processCompleteHandler :: CompleteParams -> MCPServerT (ProcessResult CompleteResult)+ processCompleteHandler (CompleteParams cref (CompletionArgument arg_name arg_value) _ctx) =+ case cref of+ PromptRef (PromptReference _ "code-review" _) ->+ case arg_name of+ "code" ->+ let suggestions = filter (T.isPrefixOf arg_value) completionValues+ in return $+ ProcessSuccess $+ CompleteResult+ { completion =+ CompletionResult+ { values = suggestions+ , total = Just (length suggestions)+ , hasMore = Just False+ }+ , _meta = Nothing+ }+ _ -> emptyCompletion+ _ -> emptyCompletion+ where+ emptyCompletion =+ return $+ ProcessSuccess $+ CompleteResult+ { completion = CompletionResult{values = [], total = Just 0, hasMore = Just False}+ , _meta = Nothing+ }++-- | Completion suggestion values for the code-review prompt+completionValues :: [Text]+completionValues = ["def foo():", "class MyClass:", "import os"]++-- ** Available Tools++-- | Tools available in the test server+availableTools :: [Tool]+availableTools =+ [ Tool+ { name = "addition-tool"+ , title = Just "Addition Tool"+ , description = Just "An example tool that adds two numbers"+ , inputSchema =+ InputSchema+ { schemaType = "object"+ , properties =+ Just $+ Map.fromList+ [ ("arg1", object ["type" .= ("number" :: Text)])+ , ("arg2", object ["type" .= ("number" :: Text)])+ ]+ , required = Just ["arg1", "arg2"]+ }+ , outputSchema =+ Just $+ InputSchema+ { schemaType = "object"+ , properties = Just $ Map.fromList [("result", object ["type" .= ("number" :: Text)])]+ , required = Just ["result"]+ }+ , annotations = Nothing+ , _meta = Nothing+ }+ , Tool+ { name = "constant-msg-tool"+ , title = Just "Constant Message Tool"+ , description = Just "Another example tool"+ , inputSchema =+ InputSchema+ { schemaType = "object"+ , properties = Nothing+ , required = Nothing+ }+ , outputSchema =+ Just $+ InputSchema+ { schemaType = "object"+ , properties = Just (Map.fromList [("message", object ["type" .= ("string" :: Text)])])+ , required = Just ["message"]+ }+ , annotations = Nothing+ , _meta = Nothing+ }+ ]++-- ** Available Prompts++-- | Prompts available in the test server+availablePrompts :: [Prompt]+availablePrompts =+ [ Prompt+ { name = "code-review"+ , title = Just "Code Review Prompt"+ , description = Just "Asks the LLM to analyze code quality and suggest improvements"+ , arguments =+ Just+ [ PromptArgument+ { name = "code"+ , title = Just "Code Snippet"+ , description = Just "The code snippet to review"+ , required = Just True+ }+ ]+ , _meta = Nothing+ }+ ]++-- ** Available Resources++-- | Resources available in the test server+availableResources :: [Resource]+availableResources =+ [ Resource+ { uri = "resource://example/document"+ , name = "text-document"+ , title = Just "An example resource for testing"+ , description = Just "This is a sample text document."+ , mimeType = Just "text/plain"+ , size = Nothing+ , annotations = Nothing+ , _meta = Nothing+ }+ , Resource+ { uri = "resource://example/data"+ , name = "json-data"+ , title = Just "Example Resource 2"+ , description = Just "Another example resource for testing"+ , mimeType = Just "application/json"+ , size = Nothing+ , annotations = Nothing+ , _meta = Nothing+ }+ , Resource+ { uri = "resource://example/image"+ , name = "image-resource"+ , title = Just "An inspiring image"+ , description = Just "Image of a sunset"+ , mimeType = Just "image/png"+ , size = Nothing+ , annotations = Nothing+ , _meta = Nothing+ }+ ]++-- ** Available Resource Templates++-- | Resource templates available in the test server+availableResourceTemplates :: [ResourceTemplate]+availableResourceTemplates =+ [ ResourceTemplate+ { name = "user-profile"+ , title = Just "User Profile Template"+ , uriTemplate = "resource://example/users/{userId}"+ , description = Just "Returns a user profile by ID"+ , mimeType = Just "application/json"+ , annotations = Nothing+ , _meta = Nothing+ }+ , ResourceTemplate+ { name = "log-file"+ , title = Just "Log File Template"+ , uriTemplate = "resource://example/logs/{date}"+ , description = Just "Returns logs for a specific date"+ , mimeType = Just "text/plain"+ , annotations = Nothing+ , _meta = Nothing+ }+ ]++-- *** Sample Resource Contents++-- | Sample CSV data for testing+csvTestData :: Text+csvTestData = "name,age,city\nJohn,25,New York\nJane,30,London\nBob,35,Paris"++-- | Sample JSON data for testing+jsonTestData :: Text+jsonTestData =+ T.pack $+ show $+ object ["key1" .= ("value1" :: Text), "key2" .= (42 :: Int), "key3" .= (True :: Bool)]++-- | Sample image data for testing (base64 encoded)+imageTestData :: Text+imageTestData = "<base64-encoded-image-data>"++-- ** Server State Creation++-- | Create test server state for MCP tests+createTestServerState :: IO (MVar MCPServerState)+createTestServerState = do+ let impl = Implementation "test-server" "1.0.0" Nothing+ let server_caps =+ ServerCapabilities+ { logging = Just LoggingCapability+ , prompts = Just (PromptsCapability{listChanged = Nothing})+ , resources =+ Just+ (ResourcesCapability{listChanged = Nothing, subscribe = Nothing})+ , tools = Just (ToolsCapability{listChanged = Just True})+ , completions = Just CompletionsCapability+ , experimental = Nothing+ }+ newMVar+ MCPServerState+ { mcp_server_initialized = False+ , mcp_handler_state = initializeTestState+ , mcp_handler_init = mb_handler_init+ , mcp_handler_finalize = mb_handler_finalize+ , mcp_client_capabilities = Nothing+ , mcp_log_level = Just Info -- Set to Debug if you want to see json requests+ , mcp_pending_responses = mempty+ , mcp_pending_responses_next = 1+ , mcp_server_capabilities = server_caps+ , mcp_implementation = impl+ , mcp_instructions = Nothing+ , mcp_process_handlers = processHandlers+ }++-- ** Application Setup++-- | Create test application with access to JWT configuration+createTestAppWithJWT :: IO (AuthServer.JWTSettings, Application)+createTestAppWithJWT = do+ state_var <- createTestServerState+ key <- AuthServer.generateKey+ let jwt_cfg = AuthServer.defaultJWTSettings key+ let cookie_cfg = AuthServer.defaultCookieSettings+ let auth_cfg = cookie_cfg :. jwt_cfg :. EmptyContext+ let app = serveWithContext (Proxy @MCPAPI) auth_cfg (mcpAPI state_var)+ return (jwt_cfg, app)
+ test/MCP/TestUtils.hs view
@@ -0,0 +1,285 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}++{- |+Module: MCP.TestUtils+License: MPL-2.0+Maintainer: <matti@dpella.io>, <lobo@dpella.io>++This module provides utility functions and types for testing the MCP server+implementation.+-}+module MCP.TestUtils where++import Crypto.JOSE.Error as Crypto+import Data.Aeson (ToJSON, Value (..), encode, toJSON)+import Data.ByteString qualified as BS+import Data.ByteString.Lazy qualified as BSL+import Data.Map qualified as Map+import Data.Maybe (fromMaybe)+import Data.Text (Text)+import MCP.Protocol qualified as MCP+import MCP.TestServer (TestUser (..))+import MCP.Types qualified as MCP+import Network.HTTP.Types qualified as HTTP+import Network.Wai.Test (SResponse)+import Servant.Auth.Server qualified as AuthServer+import Test.Hspec.Wai (+ ResponseMatcher,+ WaiExpectation,+ WaiSession,+ getState,+ liftIO,+ request,+ shouldRespondWith,+ )++-- * Utility Functions for MCP testing++-- ** Test Combinators++-- | Create a post request with the provided headers+postWith ::+ BS.ByteString ->+ [(HTTP.HeaderName, BS.ByteString)] ->+ BSL.ByteString ->+ WaiSession st SResponse+postWith = request HTTP.methodPost++-- | Helper function for making MCP requests with authentication+mcpPostRequest ::+ [(HTTP.HeaderName, BS.ByteString)] -> Value -> WaiSession st SResponse+mcpPostRequest headers request_object =+ postWith "/mcp" headers (encode request_object)++{- | Helper function for making MCP requests with authentication and expecting a+specific response+-}+mcpPostRequestExpects ::+ [(HTTP.HeaderName, BS.ByteString)] ->+ Value ->+ ResponseMatcher ->+ WaiExpectation st+mcpPostRequestExpects headers request_object expected_response =+ mcpPostRequest headers request_object `shouldRespondWith` expected_response++{- | Helper function for making MCP requests with authentication and expecting+200+-}+mcpPostRequestOk :: [(HTTP.HeaderName, BS.ByteString)] -> Value -> WaiExpectation st+mcpPostRequestOk headers request_object =+ mcpPostRequestExpects headers request_object 200++-- | Create an authenticated request with JWT token+withAuthenticatedRequest ::+ ([(HTTP.HeaderName, BS.ByteString)] -> WaiSession AuthServer.JWTSettings a) ->+ WaiSession AuthServer.JWTSettings a+withAuthenticatedRequest = withAuthenticatedRequestForSession "test-user-id"++-- | Create an authenticated request with JWT token+withAuthenticatedRequestForSession ::+ Text -> -- Session ID to include in JWT+ ([(HTTP.HeaderName, BS.ByteString)] -> WaiSession AuthServer.JWTSettings a) ->+ WaiSession AuthServer.JWTSettings a+withAuthenticatedRequestForSession session_id action = do+ jwt_cfg <- getState+ let test_user = TestUser session_id "Test User" "test@example.com"+ eitherToken <- liftIO $ generateTestJWT test_user jwt_cfg+ case eitherToken of+ Left _ -> error "Failed to generate test JWT token"+ Right token -> do+ let tokenStr = BSL.toStrict token+ headers =+ [ ("Content-Type", "application/json")+ , ("Authorization", "Bearer " <> tokenStr)+ ]+ action headers+ where+ -- generate a JWT token for the given user using the provided JWT settings+ generateTestJWT ::+ (AuthServer.ToJWT a) =>+ a ->+ AuthServer.JWTSettings ->+ IO (Either Crypto.Error BSL.ByteString)+ generateTestJWT user jwt_cfg' = AuthServer.makeJWT user jwt_cfg' Nothing++{- | Computes an action after initializing the server (authenticate, initialize,+and send initialized notification)+-}+withInitializedServer ::+ ([(HTTP.HeaderName, BS.ByteString)] -> WaiSession AuthServer.JWTSettings a) ->+ WaiSession AuthServer.JWTSettings a+withInitializedServer action =+ withAuthenticatedRequest $ \headers -> do+ let init_request = toJSON createInitializeRequest+ mcpPostRequestOk headers init_request+ let notify_request = toJSON createInitializedNotification+ mcpPostRequestOk headers notify_request+ action headers++-- ** Request builders++-- | Create a JSON-RPC request object.+createJSONRPCRequest ::+ (ToJSON a) =>+ Maybe Text ->+ a ->+ Text ->+ Value ->+ MCP.JSONRPCRequest+createJSONRPCRequest mb_jsonrpc req_id method params =+ MCP.JSONRPCRequest+ { jsonrpc = fromMaybe "2.0" mb_jsonrpc+ , id = toRequestId req_id+ , method = method+ , params = params+ }++-- | Create a JSON-RPC request ID from an integer+toRequestId :: (ToJSON a) => a -> MCP.RequestId+toRequestId = MCP.RequestId . toJSON++-- | Create a JSON-RPC initialize request+createInitializeRequest :: MCP.InitializeRequest+createInitializeRequest =+ MCP.InitializeRequest+ { id = toRequestId (1 :: Int)+ , params = init_params+ }+ where+ init_params =+ MCP.InitializeParams+ { protocolVersion = MCP.pROTOCOL_VERSION+ , capabilities = client_caps+ , clientInfo = client_info+ }++ client_caps :: MCP.ClientCapabilities+ client_caps =+ MCP.ClientCapabilities+ { roots = Just $ MCP.RootsCapability (Just True)+ , sampling = Nothing+ , elicitation = Just MCP.ElicitationCapability+ , experimental = Nothing+ }++ client_info :: MCP.Implementation+ client_info =+ MCP.Implementation+ { name = "test-client"+ , version = "0.1.0"+ , title = Nothing+ }++-- | Create a JSON-RPC ping request+createPingRequest :: MCP.PingRequest+createPingRequest =+ MCP.PingRequest+ { id = toRequestId (1 :: Int)+ , params = Nothing+ }++-- | Create a JSON-RPC call tool request+createCallToolRequest :: Int -> Text -> [(Text, Value)] -> MCP.CallToolRequest+createCallToolRequest req_id t_name map_args =+ MCP.CallToolRequest+ { id = toRequestId req_id+ , params = tool_call_params+ }+ where+ tool_call_params :: MCP.CallToolParams+ tool_call_params =+ MCP.CallToolParams+ { name = t_name+ , arguments = case map_args of+ [] -> Nothing+ _ -> Just $ Map.fromList map_args+ }++-- | Create a JSON-RPC tools list request+createListToolsRequest :: Int -> MCP.ListToolsRequest+createListToolsRequest int_id =+ MCP.ListToolsRequest+ { id = toRequestId int_id+ , params = Just $ MCP.ListToolsParams Nothing+ }++-- | Create a JSON-RPC resources list request+createListResourcesRequest :: Int -> MCP.ListResourcesRequest+createListResourcesRequest req_id =+ MCP.ListResourcesRequest+ { id = toRequestId req_id+ , params = Just $ MCP.ListResourcesParams Nothing+ }++-- | Create a JSON-RPC resources read request+createReadResourceRequest :: Int -> Text -> MCP.ReadResourceRequest+createReadResourceRequest req_id txt_uri =+ MCP.ReadResourceRequest+ { id = toRequestId req_id+ , params = MCP.ReadResourceParams txt_uri+ }++-- | Create a JSON-RPC resources subscribe request+createSubscribeRequest :: Int -> Text -> MCP.SubscribeRequest+createSubscribeRequest req_id txt_uri =+ MCP.SubscribeRequest+ { id = toRequestId req_id+ , params = MCP.SubscribeParams txt_uri+ }++-- | Create a JSON-RPC list prompts request+createPromptListRequest :: Int -> MCP.ListPromptsRequest+createPromptListRequest req_id =+ MCP.ListPromptsRequest+ { id = toRequestId req_id+ , params = Just $ MCP.ListPromptsParams Nothing+ }++-- | Create a JSON-RPC get prompt request+createGetPromptRequest :: Int -> Text -> Maybe (Map.Map Text Text) -> MCP.GetPromptRequest+createGetPromptRequest req_id p_name args =+ MCP.GetPromptRequest+ { id = toRequestId req_id+ , params = MCP.GetPromptParams p_name args+ }++-- ** Notification helpers++-- | Create a JSON-RPC initialized notification+createInitializedNotification :: MCP.InitializedNotification+createInitializedNotification =+ MCP.InitializedNotification{params = Nothing}++-- | Create a JSON-RPC logging/setLevel request+createSetLevelRequest :: Int -> MCP.LoggingLevel -> MCP.SetLevelRequest+createSetLevelRequest req_id lvl =+ MCP.SetLevelRequest+ { id = toRequestId req_id+ , params = MCP.SetLevelParams lvl+ }++-- | Create a JSON-RPC request for a resource template list+createListResourceTemplatesRequest :: Int -> MCP.ListResourceTemplatesRequest+createListResourceTemplatesRequest req_id =+ MCP.ListResourceTemplatesRequest+ { id = toRequestId req_id+ , params = Just $ MCP.ListResourceTemplatesParams Nothing+ }++-- | Create a JSON-RPC request for completion/complete+createCompleteRequest :: Int -> Text -> Text -> Text -> MCP.CompleteRequest+createCompleteRequest req_id ref_name arg_name arg_value =+ MCP.CompleteRequest+ { id = toRequestId req_id+ , params =+ MCP.CompleteParams+ { ref = MCP.PromptRef $ MCP.PromptReference "ref/prompt" ref_name Nothing+ , argument = MCP.CompletionArgument arg_name arg_value+ , context = Nothing+ }+ }
test/Main.hs view
@@ -1,4 +1,22 @@-module Main (main) where+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE OverloadedStrings #-} +{- |+Module: Main+License: MPL-2.0+Maintainer: <matti@dpella.io>, <lobo@dpella.io>++Main test suite for the MCP library+-}+module Main where++import MCP.Integration qualified as Integration+import MCP.StdioIntegration qualified as StdioIntegration+import Test.Hspec (describe, hspec)++-- | Main function to run all tests main :: IO ()-main = putStrLn "Test suite not yet implemented."+main = hspec $ do+ describe "MCP Library" $ do+ Integration.integrationSpec+ StdioIntegration.stdioIntegrationSpec