mcp 0.3.0.1 → 0.3.1.0
raw patch · 8 files changed
+623/−211 lines, 8 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
- MCP.Server.Common: instance GHC.Base.Functor MCP.Server.Common.ProcessResult
- 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.Common: ($dmfromJSONRPCNotification) :: forall (c0 :: Meta) (i0 :: Meta) (s1 :: Meta) i30. (IsJSONRPCNotification a, Generic a, Rep a ~ D1 c0 (C1 i0 (S1 s1 (K1 i30 (NotificationParams a) :: Type -> Type)))) => JSONRPCNotification -> Either String a
+ MCP.Server.Common: ($dmfromJSONRPCRequest) :: forall (c0 :: Meta) (i0 :: Meta) (s0 :: Meta) i1 (s1 :: Meta) i30. (IsJSONRPCRequest a, Generic a, Rep a ~ D1 c0 (C1 i0 (S1 s0 (K1 i1 RequestId :: Type -> Type) :*: S1 s1 (K1 i30 (RequestParams a) :: Type -> Type)))) => JSONRPCRequest -> Either String a
+ MCP.Server.Common: -- | The type of the <tt>params</tt> field.
+ MCP.Server.Common: Alert :: LoggingLevel
+ MCP.Server.Common: AllServers :: IncludeContext
+ MCP.Server.Common: Annotations :: Maybe [Role] -> Maybe Double -> Maybe Text -> Annotations
+ MCP.Server.Common: Assistant :: Role
+ MCP.Server.Common: AudioBlock :: AudioContent -> ContentBlock
+ MCP.Server.Common: AudioContent :: Text -> Text -> Text -> Maybe Annotations -> Maybe Metadata -> AudioContent
+ MCP.Server.Common: AudioContentType :: AudioContent -> Content
+ MCP.Server.Common: BaseMetadata :: Text -> Maybe Text -> BaseMetadata
+ MCP.Server.Common: BlobResource :: BlobResourceContents -> ResourceContents
+ MCP.Server.Common: BlobResourceContents :: Text -> Text -> Maybe Text -> Maybe Metadata -> BlobResourceContents
+ MCP.Server.Common: BooleanSchema :: Text -> Maybe Text -> Maybe Text -> Maybe Bool -> PrimitiveSchemaDefinition
+ MCP.Server.Common: CallToolParams :: Text -> Maybe (Map Text Value) -> CallToolParams
+ MCP.Server.Common: CallToolReq :: CallToolRequest -> ClientRequest
+ MCP.Server.Common: CallToolRequest :: RequestId -> CallToolParams -> CallToolRequest
+ MCP.Server.Common: CallToolResult :: [ContentBlock] -> Maybe (Map Text Value) -> Maybe Bool -> Maybe Metadata -> CallToolResult
+ MCP.Server.Common: CancelledNotif :: CancelledNotification -> ClientNotification
+ MCP.Server.Common: CancelledNotification :: CancelledParams -> CancelledNotification
+ MCP.Server.Common: CancelledParams :: RequestId -> Maybe Text -> CancelledParams
+ MCP.Server.Common: CancelledServerNotif :: CancelledNotification -> ServerNotification
+ MCP.Server.Common: ClientCapabilities :: Maybe RootsCapability -> Maybe SamplingCapability -> Maybe ElicitationCapability -> Maybe ExperimentalCapability -> ClientCapabilities
+ MCP.Server.Common: CompleteParams :: Reference -> CompletionArgument -> Maybe CompletionContext -> CompleteParams
+ MCP.Server.Common: CompleteReq :: CompleteRequest -> ClientRequest
+ MCP.Server.Common: CompleteRequest :: RequestId -> CompleteParams -> CompleteRequest
+ MCP.Server.Common: CompleteResult :: CompletionResult -> Maybe Metadata -> CompleteResult
+ MCP.Server.Common: CompletionArgument :: Text -> Text -> CompletionArgument
+ MCP.Server.Common: CompletionContext :: Maybe (Map Text Text) -> CompletionContext
+ MCP.Server.Common: CompletionResult :: [Text] -> Maybe Int -> Maybe Bool -> CompletionResult
+ MCP.Server.Common: CompletionsCapability :: CompletionsCapability
+ MCP.Server.Common: CreateMessageParams :: Int -> [SamplingMessage] -> Maybe ModelPreferences -> Maybe Text -> Maybe IncludeContext -> Maybe Double -> Maybe [Text] -> Maybe (Map Text Value) -> CreateMessageParams
+ MCP.Server.Common: CreateMessageReq :: CreateMessageRequest -> ServerRequest
+ MCP.Server.Common: CreateMessageRequest :: RequestId -> CreateMessageParams -> CreateMessageRequest
+ MCP.Server.Common: CreateMessageResult :: Role -> Content -> Text -> Maybe Text -> Maybe Metadata -> CreateMessageResult
+ MCP.Server.Common: Critical :: LoggingLevel
+ MCP.Server.Common: Cursor :: Text -> Cursor
+ MCP.Server.Common: Debug :: LoggingLevel
+ MCP.Server.Common: ElicitParams :: Text -> Map Text PrimitiveSchemaDefinition -> Maybe [Text] -> ElicitParams
+ MCP.Server.Common: ElicitReq :: ElicitRequest -> ServerRequest
+ MCP.Server.Common: ElicitRequest :: RequestId -> ElicitParams -> ElicitRequest
+ MCP.Server.Common: ElicitResult :: Text -> Maybe (Map Text Value) -> Maybe Metadata -> ElicitResult
+ MCP.Server.Common: ElicitationCapability :: ElicitationCapability
+ MCP.Server.Common: EmbeddedResource :: Text -> ResourceContents -> Maybe Annotations -> Maybe Metadata -> EmbeddedResource
+ MCP.Server.Common: EmbeddedResourceBlock :: EmbeddedResource -> ContentBlock
+ MCP.Server.Common: EmbeddedResourceType :: EmbeddedResource -> Content
+ MCP.Server.Common: Emergency :: LoggingLevel
+ MCP.Server.Common: EnumSchema :: Text -> Maybe Text -> Maybe Text -> [Text] -> Maybe [Text] -> PrimitiveSchemaDefinition
+ MCP.Server.Common: Error :: LoggingLevel
+ MCP.Server.Common: ErrorMessage :: JSONRPCError -> JSONRPCMessage
+ MCP.Server.Common: ExperimentalCapability :: Map Text Value -> ExperimentalCapability
+ MCP.Server.Common: GetPromptParams :: Text -> Maybe (Map Text Text) -> GetPromptParams
+ MCP.Server.Common: GetPromptReq :: GetPromptRequest -> ClientRequest
+ MCP.Server.Common: GetPromptRequest :: RequestId -> GetPromptParams -> GetPromptRequest
+ MCP.Server.Common: GetPromptResult :: Maybe Text -> [PromptMessage] -> Maybe Metadata -> GetPromptResult
+ MCP.Server.Common: ImageBlock :: ImageContent -> ContentBlock
+ MCP.Server.Common: ImageContent :: Text -> Text -> Text -> Maybe Annotations -> Maybe Metadata -> ImageContent
+ MCP.Server.Common: ImageContentType :: ImageContent -> Content
+ MCP.Server.Common: Implementation :: Text -> Text -> Maybe Text -> Implementation
+ MCP.Server.Common: Info :: LoggingLevel
+ MCP.Server.Common: InitializeParams :: Text -> ClientCapabilities -> Implementation -> InitializeParams
+ MCP.Server.Common: InitializeReq :: InitializeRequest -> ClientRequest
+ MCP.Server.Common: InitializeRequest :: RequestId -> InitializeParams -> InitializeRequest
+ MCP.Server.Common: InitializeResult :: Text -> ServerCapabilities -> Implementation -> Maybe Text -> Maybe Metadata -> InitializeResult
+ MCP.Server.Common: InitializedNotif :: InitializedNotification -> ClientNotification
+ MCP.Server.Common: InitializedNotification :: Maybe EmptyParams -> InitializedNotification
+ MCP.Server.Common: InputSchema :: Text -> Maybe (Map Text Value) -> Maybe [Text] -> InputSchema
+ MCP.Server.Common: JSONRPCError :: Text -> RequestId -> JSONRPCErrorInfo -> JSONRPCError
+ MCP.Server.Common: JSONRPCErrorInfo :: Int -> Text -> Maybe Value -> JSONRPCErrorInfo
+ MCP.Server.Common: JSONRPCNotification :: Text -> Text -> Value -> JSONRPCNotification
+ MCP.Server.Common: JSONRPCRequest :: Text -> RequestId -> Text -> Value -> JSONRPCRequest
+ MCP.Server.Common: JSONRPCResponse :: Text -> RequestId -> Value -> JSONRPCResponse
+ MCP.Server.Common: ListPromptsReq :: ListPromptsRequest -> ClientRequest
+ MCP.Server.Common: ListPromptsRequest :: RequestId -> Maybe ListPromptsParams -> ListPromptsRequest
+ MCP.Server.Common: ListPromptsResult :: [Prompt] -> Maybe Cursor -> Maybe Metadata -> ListPromptsResult
+ MCP.Server.Common: ListResourceTemplatesReq :: ListResourceTemplatesRequest -> ClientRequest
+ MCP.Server.Common: ListResourceTemplatesRequest :: RequestId -> Maybe ListResourceTemplatesParams -> ListResourceTemplatesRequest
+ MCP.Server.Common: ListResourceTemplatesResult :: [ResourceTemplate] -> Maybe Cursor -> Maybe Metadata -> ListResourceTemplatesResult
+ MCP.Server.Common: ListResourcesReq :: ListResourcesRequest -> ClientRequest
+ MCP.Server.Common: ListResourcesRequest :: RequestId -> Maybe ListResourcesParams -> ListResourcesRequest
+ MCP.Server.Common: ListResourcesResult :: [Resource] -> Maybe Cursor -> Maybe Metadata -> ListResourcesResult
+ MCP.Server.Common: ListRootsReq :: ListRootsRequest -> ServerRequest
+ MCP.Server.Common: ListRootsRequest :: RequestId -> Maybe ListRootsParams -> ListRootsRequest
+ MCP.Server.Common: ListRootsResult :: [Root] -> Maybe Metadata -> ListRootsResult
+ MCP.Server.Common: ListToolsReq :: ListToolsRequest -> ClientRequest
+ MCP.Server.Common: ListToolsRequest :: RequestId -> Maybe ListToolsParams -> ListToolsRequest
+ MCP.Server.Common: ListToolsResult :: [Tool] -> Maybe Cursor -> Maybe Metadata -> ListToolsResult
+ MCP.Server.Common: LoggingCapability :: LoggingCapability
+ MCP.Server.Common: LoggingMessageNotif :: LoggingMessageNotification -> ServerNotification
+ MCP.Server.Common: LoggingMessageNotification :: LoggingMessageParams -> LoggingMessageNotification
+ MCP.Server.Common: LoggingMessageParams :: LoggingLevel -> Value -> Maybe Text -> LoggingMessageParams
+ MCP.Server.Common: Metadata :: Map Text Value -> Metadata
+ MCP.Server.Common: ModelPreferences :: Maybe [ModelHint] -> Maybe Double -> Maybe Double -> Maybe Double -> ModelPreferences
+ MCP.Server.Common: None :: IncludeContext
+ MCP.Server.Common: Notice :: LoggingLevel
+ MCP.Server.Common: NotificationMessage :: JSONRPCNotification -> JSONRPCMessage
+ MCP.Server.Common: NumberSchema :: Text -> Maybe Text -> Maybe Text -> Maybe Double -> Maybe Double -> PrimitiveSchemaDefinition
+ MCP.Server.Common: PingReq :: PingRequest -> ClientRequest
+ MCP.Server.Common: PingRequest :: RequestId -> Maybe PingParams -> PingRequest
+ MCP.Server.Common: PingServerReq :: PingRequest -> ServerRequest
+ MCP.Server.Common: ProgressNotif :: ProgressNotification -> ClientNotification
+ MCP.Server.Common: ProgressNotification :: ProgressParams -> ProgressNotification
+ MCP.Server.Common: ProgressParams :: ProgressToken -> Double -> Maybe Double -> Maybe Text -> ProgressParams
+ MCP.Server.Common: ProgressServerNotif :: ProgressNotification -> ServerNotification
+ MCP.Server.Common: ProgressToken :: Value -> ProgressToken
+ MCP.Server.Common: Prompt :: Text -> Maybe Text -> Maybe Text -> Maybe [PromptArgument] -> Maybe Metadata -> Prompt
+ MCP.Server.Common: PromptArgument :: Text -> Maybe Text -> Maybe Text -> Maybe Bool -> PromptArgument
+ MCP.Server.Common: PromptListChangedNotif :: PromptListChangedNotification -> ServerNotification
+ MCP.Server.Common: PromptListChangedNotification :: Maybe EmptyParams -> PromptListChangedNotification
+ MCP.Server.Common: PromptMessage :: Role -> ContentBlock -> PromptMessage
+ MCP.Server.Common: PromptRef :: PromptReference -> Reference
+ MCP.Server.Common: PromptReference :: Text -> Text -> Maybe Text -> PromptReference
+ MCP.Server.Common: ReadResourceReq :: ReadResourceRequest -> ClientRequest
+ MCP.Server.Common: ReadResourceRequest :: RequestId -> ReadResourceParams -> ReadResourceRequest
+ MCP.Server.Common: ReadResourceResult :: [ResourceContents] -> Maybe Metadata -> ReadResourceResult
+ MCP.Server.Common: RequestId :: Value -> RequestId
+ MCP.Server.Common: RequestMessage :: JSONRPCRequest -> JSONRPCMessage
+ MCP.Server.Common: Resource :: Text -> Text -> Maybe Text -> Maybe Text -> Maybe Text -> Maybe Int -> Maybe Annotations -> Maybe Metadata -> Resource
+ MCP.Server.Common: ResourceLink :: Text -> Text -> Text -> Maybe Text -> Maybe Text -> Maybe Text -> Maybe Int -> Maybe Annotations -> Maybe Metadata -> ResourceLink
+ MCP.Server.Common: ResourceLinkBlock :: ResourceLink -> ContentBlock
+ MCP.Server.Common: ResourceListChangedNotif :: ResourceListChangedNotification -> ServerNotification
+ MCP.Server.Common: ResourceListChangedNotification :: Maybe EmptyParams -> ResourceListChangedNotification
+ MCP.Server.Common: ResourceReference :: Text -> Text -> ResourceReference
+ MCP.Server.Common: ResourceTemplate :: Text -> Maybe Text -> Text -> Maybe Text -> Maybe Text -> Maybe Annotations -> Maybe Metadata -> ResourceTemplate
+ MCP.Server.Common: ResourceTemplateRef :: ResourceTemplateReference -> Reference
+ MCP.Server.Common: ResourceUpdatedNotif :: ResourceUpdatedNotification -> ServerNotification
+ MCP.Server.Common: ResourceUpdatedNotification :: ResourceUpdatedParams -> ResourceUpdatedNotification
+ MCP.Server.Common: ResourceUpdatedParams :: Text -> ResourceUpdatedParams
+ MCP.Server.Common: ResourcesCapability :: Maybe Bool -> Maybe Bool -> ResourcesCapability
+ MCP.Server.Common: ResponseMessage :: JSONRPCResponse -> JSONRPCMessage
+ MCP.Server.Common: Root :: Text -> Maybe Text -> Maybe Metadata -> Root
+ MCP.Server.Common: RootsListChangedNotif :: RootsListChangedNotification -> ClientNotification
+ MCP.Server.Common: RootsListChangedNotification :: Maybe EmptyParams -> RootsListChangedNotification
+ MCP.Server.Common: SamplingCapability :: SamplingCapability
+ MCP.Server.Common: SamplingMessage :: Role -> Content -> SamplingMessage
+ MCP.Server.Common: ServerCapabilities :: Maybe LoggingCapability -> Maybe PromptsCapability -> Maybe ResourcesCapability -> Maybe ToolsCapability -> Maybe CompletionsCapability -> Maybe ExperimentalCapability -> ServerCapabilities
+ MCP.Server.Common: SetLevelReq :: SetLevelRequest -> ClientRequest
+ MCP.Server.Common: SetLevelRequest :: RequestId -> SetLevelParams -> SetLevelRequest
+ MCP.Server.Common: StringSchema :: Text -> Maybe Text -> Maybe Text -> Maybe Int -> Maybe Int -> Maybe Text -> PrimitiveSchemaDefinition
+ MCP.Server.Common: SubscribeReq :: SubscribeRequest -> ClientRequest
+ MCP.Server.Common: SubscribeRequest :: RequestId -> SubscribeParams -> SubscribeRequest
+ MCP.Server.Common: TextBlock :: TextContent -> ContentBlock
+ MCP.Server.Common: TextContent :: Text -> Text -> Maybe Annotations -> Maybe Metadata -> TextContent
+ MCP.Server.Common: TextContentType :: TextContent -> Content
+ MCP.Server.Common: TextResource :: TextResourceContents -> ResourceContents
+ MCP.Server.Common: TextResourceContents :: Text -> Text -> Maybe Text -> Maybe Metadata -> TextResourceContents
+ MCP.Server.Common: ThisServer :: IncludeContext
+ MCP.Server.Common: Tool :: Text -> Maybe Text -> Maybe Text -> InputSchema -> Maybe InputSchema -> Maybe ToolAnnotations -> Maybe Metadata -> Tool
+ MCP.Server.Common: ToolAnnotations :: Maybe Text -> Maybe Bool -> Maybe Bool -> Maybe Bool -> Maybe Bool -> ToolAnnotations
+ MCP.Server.Common: ToolListChangedNotif :: ToolListChangedNotification -> ServerNotification
+ MCP.Server.Common: ToolListChangedNotification :: Maybe EmptyParams -> ToolListChangedNotification
+ MCP.Server.Common: UnsubscribeReq :: UnsubscribeRequest -> ClientRequest
+ MCP.Server.Common: UnsubscribeRequest :: RequestId -> UnsubscribeParams -> UnsubscribeRequest
+ MCP.Server.Common: User :: Role
+ MCP.Server.Common: ViaJSONRPCNotification :: a -> ViaJSONRPCNotification a
+ MCP.Server.Common: ViaJSONRPCRequest :: a -> ViaJSONRPCRequest a
+ MCP.Server.Common: Warning :: LoggingLevel
+ MCP.Server.Common: [InitializedParams] :: Maybe Metadata -> InitializedParams
+ MCP.Server.Common: [ListPromptsParams] :: Maybe Cursor -> ListPromptsParams
+ MCP.Server.Common: [ListResourceTemplatesParams] :: Maybe Cursor -> ListResourceTemplatesParams
+ MCP.Server.Common: [ListResourcesParams] :: Maybe Cursor -> ListResourcesParams
+ MCP.Server.Common: [ListRootsParams] :: Maybe Metadata -> ListRootsParams
+ MCP.Server.Common: [ListToolsParams] :: Maybe Cursor -> ListToolsParams
+ MCP.Server.Common: [ModelHint] :: Maybe Text -> ModelHint
+ MCP.Server.Common: [PingParams] :: Maybe Metadata -> PingParams
+ MCP.Server.Common: [PromptsCapability] :: Maybe Bool -> PromptsCapability
+ MCP.Server.Common: [ReadResourceParams] :: Text -> ReadResourceParams
+ MCP.Server.Common: [Result] :: Maybe Metadata -> Result
+ MCP.Server.Common: [RootsCapability] :: Maybe Bool -> RootsCapability
+ MCP.Server.Common: [SetLevelParams] :: LoggingLevel -> SetLevelParams
+ MCP.Server.Common: [SubscribeParams] :: Text -> SubscribeParams
+ MCP.Server.Common: [ToolsCapability] :: Maybe Bool -> ToolsCapability
+ MCP.Server.Common: [UnsubscribeParams] :: Text -> UnsubscribeParams
+ MCP.Server.Common: [_meta] :: Tool -> Maybe Metadata
+ MCP.Server.Common: [action] :: ElicitResult -> Text
+ MCP.Server.Common: [annotations] :: Tool -> Maybe ToolAnnotations
+ MCP.Server.Common: [argument] :: CompleteParams -> CompletionArgument
+ MCP.Server.Common: [arguments] :: Prompt -> Maybe [PromptArgument]
+ MCP.Server.Common: [audience] :: Annotations -> Maybe [Role]
+ MCP.Server.Common: [audioType] :: AudioContent -> Text
+ MCP.Server.Common: [blob] :: BlobResourceContents -> Text
+ MCP.Server.Common: [capabilities] :: InitializeResult -> ServerCapabilities
+ MCP.Server.Common: [clientInfo] :: InitializeParams -> Implementation
+ MCP.Server.Common: [code] :: JSONRPCErrorInfo -> Int
+ MCP.Server.Common: [completion] :: CompleteResult -> CompletionResult
+ MCP.Server.Common: [completions] :: ServerCapabilities -> Maybe CompletionsCapability
+ MCP.Server.Common: [content] :: SamplingMessage -> Content
+ MCP.Server.Common: [contents] :: ReadResourceResult -> [ResourceContents]
+ MCP.Server.Common: [context] :: CompleteParams -> Maybe CompletionContext
+ MCP.Server.Common: [costPriority] :: ModelPreferences -> Maybe Double
+ MCP.Server.Common: [data'] :: ImageContent -> Text
+ MCP.Server.Common: [defaultValue] :: PrimitiveSchemaDefinition -> Maybe Bool
+ MCP.Server.Common: [description] :: Tool -> Maybe Text
+ MCP.Server.Common: [destructiveHint] :: ToolAnnotations -> Maybe Bool
+ MCP.Server.Common: [elicitation] :: ClientCapabilities -> Maybe ElicitationCapability
+ MCP.Server.Common: [enumNames] :: PrimitiveSchemaDefinition -> Maybe [Text]
+ MCP.Server.Common: [enum] :: PrimitiveSchemaDefinition -> [Text]
+ MCP.Server.Common: [errorData] :: JSONRPCErrorInfo -> Maybe Value
+ MCP.Server.Common: [error] :: JSONRPCError -> JSONRPCErrorInfo
+ MCP.Server.Common: [experimental] :: ServerCapabilities -> Maybe ExperimentalCapability
+ MCP.Server.Common: [format] :: PrimitiveSchemaDefinition -> Maybe Text
+ MCP.Server.Common: [hasMore] :: CompletionResult -> Maybe Bool
+ MCP.Server.Common: [hints] :: ModelPreferences -> Maybe [ModelHint]
+ MCP.Server.Common: [id] :: UnsubscribeRequest -> RequestId
+ MCP.Server.Common: [idempotentHint] :: ToolAnnotations -> Maybe Bool
+ MCP.Server.Common: [imageType] :: ImageContent -> Text
+ MCP.Server.Common: [includeContext] :: CreateMessageParams -> Maybe IncludeContext
+ MCP.Server.Common: [inputSchema] :: Tool -> InputSchema
+ MCP.Server.Common: [instructions] :: InitializeResult -> Maybe Text
+ MCP.Server.Common: [intelligencePriority] :: ModelPreferences -> Maybe Double
+ MCP.Server.Common: [isError] :: CallToolResult -> Maybe Bool
+ MCP.Server.Common: [jsonrpc] :: JSONRPCResponse -> Text
+ MCP.Server.Common: [lastModified] :: Annotations -> Maybe Text
+ MCP.Server.Common: [level] :: LoggingMessageParams -> LoggingLevel
+ MCP.Server.Common: [linkType] :: ResourceLink -> Text
+ MCP.Server.Common: [listChanged] :: ResourcesCapability -> Maybe Bool
+ MCP.Server.Common: [logger] :: LoggingMessageParams -> Maybe Text
+ MCP.Server.Common: [logging] :: ServerCapabilities -> Maybe LoggingCapability
+ MCP.Server.Common: [maxLength] :: PrimitiveSchemaDefinition -> Maybe Int
+ MCP.Server.Common: [maxTokens] :: CreateMessageParams -> Int
+ MCP.Server.Common: [maximum] :: PrimitiveSchemaDefinition -> Maybe Double
+ MCP.Server.Common: [message] :: ProgressParams -> Maybe Text
+ MCP.Server.Common: [messages] :: GetPromptResult -> [PromptMessage]
+ MCP.Server.Common: [metadata] :: CreateMessageParams -> Maybe (Map Text Value)
+ MCP.Server.Common: [method] :: JSONRPCRequest -> Text
+ MCP.Server.Common: [mimeType] :: TextResourceContents -> Maybe Text
+ MCP.Server.Common: [minLength] :: PrimitiveSchemaDefinition -> Maybe Int
+ MCP.Server.Common: [minimum] :: PrimitiveSchemaDefinition -> Maybe Double
+ MCP.Server.Common: [modelPreferences] :: CreateMessageParams -> Maybe ModelPreferences
+ MCP.Server.Common: [model] :: CreateMessageResult -> Text
+ MCP.Server.Common: [name] :: Tool -> Text
+ MCP.Server.Common: [nextCursor] :: ListToolsResult -> Maybe Cursor
+ MCP.Server.Common: [openWorldHint] :: ToolAnnotations -> Maybe Bool
+ MCP.Server.Common: [outputSchema] :: Tool -> Maybe InputSchema
+ MCP.Server.Common: [params] :: UnsubscribeRequest -> UnsubscribeParams
+ MCP.Server.Common: [priority] :: Annotations -> Maybe Double
+ MCP.Server.Common: [progressToken] :: ProgressParams -> ProgressToken
+ MCP.Server.Common: [progress] :: ProgressParams -> Double
+ MCP.Server.Common: [prompts] :: ServerCapabilities -> Maybe PromptsCapability
+ MCP.Server.Common: [properties] :: InputSchema -> Maybe (Map Text Value)
+ MCP.Server.Common: [protocolVersion] :: InitializeResult -> Text
+ MCP.Server.Common: [readOnlyHint] :: ToolAnnotations -> Maybe Bool
+ MCP.Server.Common: [reason] :: CancelledParams -> Maybe Text
+ MCP.Server.Common: [refType] :: ResourceReference -> Text
+ MCP.Server.Common: [ref] :: CompleteParams -> Reference
+ MCP.Server.Common: [requestId] :: CancelledParams -> RequestId
+ MCP.Server.Common: [requestedSchema] :: ElicitParams -> Map Text PrimitiveSchemaDefinition
+ MCP.Server.Common: [requiredFields] :: ElicitParams -> Maybe [Text]
+ MCP.Server.Common: [required] :: PromptArgument -> Maybe Bool
+ MCP.Server.Common: [resourceTemplates] :: ListResourceTemplatesResult -> [ResourceTemplate]
+ MCP.Server.Common: [resourceType] :: EmbeddedResource -> Text
+ MCP.Server.Common: [resource] :: EmbeddedResource -> ResourceContents
+ MCP.Server.Common: [resources] :: ServerCapabilities -> Maybe ResourcesCapability
+ MCP.Server.Common: [result] :: JSONRPCResponse -> Value
+ MCP.Server.Common: [role] :: SamplingMessage -> Role
+ MCP.Server.Common: [roots] :: ClientCapabilities -> Maybe RootsCapability
+ MCP.Server.Common: [sampling] :: ClientCapabilities -> Maybe SamplingCapability
+ MCP.Server.Common: [schemaType] :: InputSchema -> Text
+ MCP.Server.Common: [serverInfo] :: InitializeResult -> Implementation
+ MCP.Server.Common: [size] :: ResourceLink -> Maybe Int
+ MCP.Server.Common: [speedPriority] :: ModelPreferences -> Maybe Double
+ MCP.Server.Common: [stopReason] :: CreateMessageResult -> Maybe Text
+ MCP.Server.Common: [stopSequences] :: CreateMessageParams -> Maybe [Text]
+ MCP.Server.Common: [structuredContent] :: CallToolResult -> Maybe (Map Text Value)
+ MCP.Server.Common: [subscribe] :: ResourcesCapability -> Maybe Bool
+ MCP.Server.Common: [systemPrompt] :: CreateMessageParams -> Maybe Text
+ MCP.Server.Common: [temperature] :: CreateMessageParams -> Maybe Double
+ MCP.Server.Common: [textType] :: TextContent -> Text
+ MCP.Server.Common: [text] :: TextResourceContents -> Text
+ MCP.Server.Common: [title] :: ToolAnnotations -> Maybe Text
+ MCP.Server.Common: [tools] :: ServerCapabilities -> Maybe ToolsCapability
+ MCP.Server.Common: [total] :: ProgressParams -> Maybe Double
+ MCP.Server.Common: [unViaJSONRPCNotification] :: ViaJSONRPCNotification a -> a
+ MCP.Server.Common: [unViaJSONRPCRequest] :: ViaJSONRPCRequest a -> a
+ MCP.Server.Common: [uriTemplate] :: ResourceTemplate -> Text
+ MCP.Server.Common: [uri] :: TextResourceContents -> Text
+ MCP.Server.Common: [value] :: CompletionArgument -> Text
+ MCP.Server.Common: [values] :: CompletionResult -> [Text]
+ MCP.Server.Common: [version] :: Implementation -> Text
+ MCP.Server.Common: class (HasField "params" a NotificationParams a, Typeable a, ToJSON NotificationParams a, FromJSON NotificationParams a) => IsJSONRPCNotification a where {
+ MCP.Server.Common: class (HasField "id" a RequestId, HasField "params" a RequestParams a, Typeable a, ToJSON RequestParams a, FromJSON RequestParams a) => IsJSONRPCRequest a where {
+ MCP.Server.Common: data Annotations
+ MCP.Server.Common: data AudioContent
+ MCP.Server.Common: data BaseMetadata
+ MCP.Server.Common: data BlobResourceContents
+ MCP.Server.Common: data CallToolParams
+ MCP.Server.Common: data CallToolRequest
+ MCP.Server.Common: data CallToolResult
+ MCP.Server.Common: data CancelledNotification
+ MCP.Server.Common: data CancelledParams
+ MCP.Server.Common: data ClientCapabilities
+ MCP.Server.Common: data ClientNotification
+ MCP.Server.Common: data ClientRequest
+ MCP.Server.Common: data CompleteParams
+ MCP.Server.Common: data CompleteRequest
+ MCP.Server.Common: data CompleteResult
+ MCP.Server.Common: data CompletionArgument
+ MCP.Server.Common: data CompletionContext
+ MCP.Server.Common: data CompletionResult
+ MCP.Server.Common: data CompletionsCapability
+ MCP.Server.Common: data Content
+ MCP.Server.Common: data ContentBlock
+ MCP.Server.Common: data CreateMessageParams
+ MCP.Server.Common: data CreateMessageRequest
+ MCP.Server.Common: data CreateMessageResult
+ MCP.Server.Common: data ElicitParams
+ MCP.Server.Common: data ElicitRequest
+ MCP.Server.Common: data ElicitResult
+ MCP.Server.Common: data ElicitationCapability
+ MCP.Server.Common: data EmbeddedResource
+ MCP.Server.Common: data EmptyParams
+ MCP.Server.Common: data GetPromptParams
+ MCP.Server.Common: data GetPromptRequest
+ MCP.Server.Common: data GetPromptResult
+ MCP.Server.Common: data ImageContent
+ MCP.Server.Common: data Implementation
+ MCP.Server.Common: data IncludeContext
+ MCP.Server.Common: data InitializeParams
+ MCP.Server.Common: data InitializeRequest
+ MCP.Server.Common: data InitializeResult
+ MCP.Server.Common: data InitializedNotification
+ MCP.Server.Common: data InitializedParams
+ MCP.Server.Common: data InputSchema
+ MCP.Server.Common: data JSONRPCError
+ MCP.Server.Common: data JSONRPCErrorInfo
+ MCP.Server.Common: data JSONRPCMessage
+ MCP.Server.Common: data JSONRPCNotification
+ MCP.Server.Common: data JSONRPCRequest
+ MCP.Server.Common: data JSONRPCResponse
+ MCP.Server.Common: data ListPromptsParams
+ MCP.Server.Common: data ListPromptsRequest
+ MCP.Server.Common: data ListPromptsResult
+ MCP.Server.Common: data ListResourceTemplatesParams
+ MCP.Server.Common: data ListResourceTemplatesRequest
+ MCP.Server.Common: data ListResourceTemplatesResult
+ MCP.Server.Common: data ListResourcesParams
+ MCP.Server.Common: data ListResourcesRequest
+ MCP.Server.Common: data ListResourcesResult
+ MCP.Server.Common: data ListRootsParams
+ MCP.Server.Common: data ListRootsRequest
+ MCP.Server.Common: data ListRootsResult
+ MCP.Server.Common: data ListToolsParams
+ MCP.Server.Common: data ListToolsRequest
+ MCP.Server.Common: data ListToolsResult
+ MCP.Server.Common: data LoggingCapability
+ MCP.Server.Common: data LoggingLevel
+ MCP.Server.Common: data LoggingMessageNotification
+ MCP.Server.Common: data LoggingMessageParams
+ MCP.Server.Common: data ModelHint
+ MCP.Server.Common: data ModelPreferences
+ MCP.Server.Common: data PingParams
+ MCP.Server.Common: data PingRequest
+ MCP.Server.Common: data PrimitiveSchemaDefinition
+ MCP.Server.Common: data ProgressNotification
+ MCP.Server.Common: data ProgressParams
+ MCP.Server.Common: data Prompt
+ MCP.Server.Common: data PromptArgument
+ MCP.Server.Common: data PromptListChangedNotification
+ MCP.Server.Common: data PromptMessage
+ MCP.Server.Common: data PromptReference
+ MCP.Server.Common: data PromptsCapability
+ MCP.Server.Common: data ReadResourceParams
+ MCP.Server.Common: data ReadResourceRequest
+ MCP.Server.Common: data ReadResourceResult
+ MCP.Server.Common: data Reference
+ MCP.Server.Common: data Resource
+ MCP.Server.Common: data ResourceContents
+ MCP.Server.Common: data ResourceLink
+ MCP.Server.Common: data ResourceListChangedNotification
+ MCP.Server.Common: data ResourceReference
+ MCP.Server.Common: data ResourceTemplate
+ MCP.Server.Common: data ResourceUpdatedNotification
+ MCP.Server.Common: data ResourceUpdatedParams
+ MCP.Server.Common: data ResourcesCapability
+ MCP.Server.Common: data Result
+ MCP.Server.Common: data Role
+ MCP.Server.Common: data Root
+ MCP.Server.Common: data RootsCapability
+ MCP.Server.Common: data RootsListChangedNotification
+ MCP.Server.Common: data SamplingCapability
+ MCP.Server.Common: data SamplingMessage
+ MCP.Server.Common: data ServerCapabilities
+ MCP.Server.Common: data ServerNotification
+ MCP.Server.Common: data ServerRequest
+ MCP.Server.Common: data SetLevelParams
+ MCP.Server.Common: data SetLevelRequest
+ MCP.Server.Common: data SubscribeParams
+ MCP.Server.Common: data SubscribeRequest
+ MCP.Server.Common: data TextContent
+ MCP.Server.Common: data TextResourceContents
+ MCP.Server.Common: data Tool
+ MCP.Server.Common: data ToolAnnotations
+ MCP.Server.Common: data ToolListChangedNotification
+ MCP.Server.Common: data ToolsCapability
+ MCP.Server.Common: data UnsubscribeParams
+ MCP.Server.Common: data UnsubscribeRequest
+ MCP.Server.Common: fromJSONRPCNotification :: IsJSONRPCNotification a => JSONRPCNotification -> Either String a
+ MCP.Server.Common: fromJSONRPCRequest :: IsJSONRPCRequest a => JSONRPCRequest -> Either String a
+ MCP.Server.Common: iNTERNAL_ERROR :: Int
+ MCP.Server.Common: iNVALID_PARAMS :: Int
+ MCP.Server.Common: iNVALID_REQUEST :: Int
+ MCP.Server.Common: instance GHC.Internal.Base.Functor MCP.Server.Common.ProcessResult
+ MCP.Server.Common: mETHOD_NOT_FOUND :: Int
+ MCP.Server.Common: newtype Cursor
+ MCP.Server.Common: newtype ExperimentalCapability
+ MCP.Server.Common: newtype Metadata
+ MCP.Server.Common: newtype ProgressToken
+ MCP.Server.Common: newtype RequestId
+ MCP.Server.Common: newtype ViaJSONRPCNotification a
+ MCP.Server.Common: newtype ViaJSONRPCRequest a
+ MCP.Server.Common: notificationsMethod :: IsJSONRPCNotification a => Proxy a -> Text
+ MCP.Server.Common: pARSE_ERROR :: Int
+ MCP.Server.Common: pROTOCOL_VERSION :: Text
+ MCP.Server.Common: rPC_VERSION :: Text
+ MCP.Server.Common: requestMethod :: IsJSONRPCRequest a => Proxy a -> Text
+ MCP.Server.Common: sERVER_NOT_INITIALIZED :: Int
+ MCP.Server.Common: toJSONRPCNotification :: IsJSONRPCNotification a => a -> JSONRPCNotification
+ MCP.Server.Common: toJSONRPCRequest :: IsJSONRPCRequest a => a -> JSONRPCRequest
+ MCP.Server.Common: type NotificationParams a = NotificationParamType Rep a;
+ MCP.Server.Common: type RequestParams a = RequestParamType Rep a;
+ MCP.Server.Common: type ResourceTemplateReference = ResourceReference
+ MCP.Server.Common: }
+ MCP.Server.HTTP: simpleHttpApp :: MVar MCPServerState -> Application
+ MCP.Server.HTTP: type SimpleHTTPAPI = "mcp" :> ReqBody '[JSON] JSONRPCMessage :> StreamPost JSONRPCFrame JSONRPCEvent SourceIO JSONRPCMessage :<|> "mcp" :> StreamGet JSONRPCFrame JSONRPCEvent SourceIO JSONRPCMessage
+ MCP.Server.HTTP.Internal: data JSONRPCEvent
+ MCP.Server.HTTP.Internal: data JSONRPCFrame
+ MCP.Server.HTTP.Internal: handleMCPRequestCore :: MVar MCPServerState -> Maybe MCPHandlerUser -> JSONRPCMessage -> Handler (SourceIO JSONRPCMessage)
+ MCP.Server.HTTP.Internal: instance Servant.API.ContentTypes.Accept MCP.Server.HTTP.Internal.JSONRPCEvent
+ MCP.Server.HTTP.Internal: instance Servant.API.ContentTypes.MimeRender MCP.Server.HTTP.Internal.JSONRPCEvent JSONRPC.JSONRPCMessage
+ MCP.Server.HTTP.Internal: instance Servant.API.Stream.FramingRender MCP.Server.HTTP.Internal.JSONRPCFrame
- MCP.Server.Common: type family MCPHandlerUser
+ MCP.Server.Common: type family RequestParams a
Files
- CHANGELOG.md +10/−0
- README.md +9/−5
- mcp.cabal +13/−7
- src/MCP/Server.hs +3/−3
- src/MCP/Server/HTTP.hs +73/−196
- src/MCP/Server/HTTP/Internal.hs +233/−0
- test/MCP/SimpleHTTPIntegration.hs +280/−0
- test/Main.hs +2/−0
CHANGELOG.md view
@@ -1,5 +1,15 @@ # Changelog +## 0.3.1.0++### Added+- Simple unauthenticated HTTP transport (`SimpleHTTPAPI` / `simpleHttpApp`) in+ `MCP.Server.HTTP`, for local development or use behind a reverse proxy.+- `MCP.Server.HTTP.Internal`: shared handler core factored out of the HTTP+ transport, eliminating duplication between the JWT and simple transports.+- `--simple-http` flag in the example server.+- Integration tests for the simple HTTP transport.+ ## 0.3.0.1 ### Added
README.md view
@@ -4,7 +4,7 @@ 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`.+built on Servant. 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.@@ -14,8 +14,11 @@ - **`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.HTTP`** — Servant-based HTTP transports with streaming SSE+ responses at the `/mcp` endpoint. Provides both a JWT-authenticated API+ (`MCPAPI` / `mcpAPI`) and a simple unauthenticated API+ (`SimpleHTTPAPI` / `simpleHttpApp`) for local development or use behind a+ reverse proxy. - **`MCP.Server.Stdio`** — Stdio transport reading/writing JSON-RPC messages line-by-line, suitable for subprocess-based integrations. @@ -76,8 +79,9 @@ ## Features -- **Two transports**: HTTP (Servant + SSE) and stdio-- **JWT authentication** via `servant-auth-server` (HTTP transport)+- **Three transports**: HTTP with JWT auth, simple unauthenticated HTTP, and stdio+- **JWT authentication** via `servant-auth-server` (authenticated HTTP transport)+- **Simple HTTP** for local development or behind a reverse proxy (`simpleHttpApp`) - **Extensible handler framework**: `ProcessHandlers` record with optional handlers for each MCP method - **Tool helpers**: `ToolHandler`, `toolHandler`, `withToolHandlers`,
mcp.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: mcp-version: 0.3.0.1+version: 0.3.1.0 license: MPL-2.0 license-file: LICENSE copyright: (c) 2025 DPella AB@@ -17,18 +17,21 @@ 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.+ Protocol (MCP) for Haskell, built on Servant. 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.+ Three transports are provided: HTTP with JWT authentication via+ @servant-auth-server@, a simple unauthenticated HTTP transport for local+ development or use behind a reverse proxy, and a stdio transport for+ subprocess-based integrations. All HTTP transports use 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/DPella/mcp bug-reports: https://github.com/DPella/mcp/issues@@ -62,6 +65,7 @@ MCP.Server MCP.Server.Common MCP.Server.HTTP+ MCP.Server.HTTP.Internal MCP.Server.Stdio build-depends:@@ -77,6 +81,7 @@ servant-server >=0.19 && <0.21, text >=2.0 && <2.2, time >=1.12 && <1.15,+ wai >=3.2 && <3.3, test-suite test-mcp type: exitcode-stdio-1.0@@ -103,6 +108,7 @@ other-modules: MCP.Integration+ MCP.SimpleHTTPIntegration MCP.StdioIntegration MCP.TestServer MCP.TestUtils
src/MCP/Server.hs view
@@ -8,9 +8,9 @@ This module re-exports everything from the transport-specific modules for backwards compatibility. For finer-grained imports use: -* "MCP.Server.Common" — types, state, request routing, tool helpers-* "MCP.Server.HTTP" — Servant-based HTTP transport with JWT auth-* "MCP.Server.Stdio" — stdio transport+* "MCP.Server.Common" — types, state, request routing, tool helpers+* "MCP.Server.HTTP" — HTTP transports (JWT-authenticated and unauthenticated)+* "MCP.Server.Stdio" — stdio transport -} module MCP.Server ( module MCP.Server.Common,
src/MCP/Server/HTTP.hs view
@@ -1,8 +1,5 @@ {-# LANGUAGE DataKinds #-}-{-# LANGUAGE DuplicateRecordFields #-}-{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TypeOperators #-} {- |@@ -10,57 +7,51 @@ License: MPL-2.0 Maintainer: <matti@dpella.io>, <lobo@dpella.io> -Servant-based HTTP transport for the MCP server.+Servant-based HTTP transports 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.+This module provides two HTTP transports:++* 'MCPAPI' / 'mcpAPI' — a JWT-authenticated transport using @servant-auth@.+ Use this when your server is exposed to the network and you need+ per-request identity.++* 'SimpleHTTPAPI' / 'simpleHttpApp' — an unauthenticated transport that+ accepts all requests unconditionally. This should only be used on+ @localhost@ for local development, or behind a reverse proxy (e.g.+ Nginx, Envoy, Cloudflare Access) that handles authentication before+ requests reach the application. Do __not__ expose it directly to the+ public internet.++Both transports accept JSON-RPC requests via POST and return responses as+SSE streams at the @\/mcp@ endpoint. -} module MCP.Server.HTTP (- -- * Servant API+ -- * JWT-authenticated API MCPAPI, mcpAPI, handleMCPRequest, handleMCPEvents,++ -- * Simple (unauthenticated) API+ SimpleHTTPAPI,+ simpleHttpApp, ) 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.Aeson (encode, object, (.=)) 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 MCP.Server.HTTP.Internal 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")+-- ---------------------------------------------------------------------------+-- JWT-authenticated API+-- --------------------------------------------------------------------------- -{- | Servant API type for the MCP endpoint.+{- | Servant API type for the JWT-authenticated MCP endpoint. Accepts JSON-RPC requests with JWT authentication via servant-auth. All MCP methods are multiplexed through this single endpoint.@@ -96,16 +87,8 @@ {- | 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.+Validates JWT authentication, then delegates to the shared+'handleMCPRequestCore' from "MCP.Server.HTTP.Internal". -} handleMCPRequest :: MVar MCPServerState ->@@ -113,161 +96,55 @@ 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 auth_result of+ AuthServer.NoSuchUser -> mcpAuthError "Invalid authentication credentials"+ AuthServer.BadPassword -> mcpAuthError "Authentication failed"+ AuthServer.Indefinite -> mcpAuthError "Authentication error"+ AuthServer.Authenticated auth_user ->+ handleMCPRequestCore state_var (Just auth_user) request_value+ where+ mcpAuthError :: Text -> Handler (SourceIO JSONRPCMessage)+ mcpAuthError err = throwError err401{errBody = encode $ object ["error" .= err]} - 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 <> "'"}+-- ---------------------------------------------------------------------------+-- Simple (unauthenticated) API+-- --------------------------------------------------------------------------- - -- Validate request ID- when (not $ isValidRequestId req_id) $- throwError err400{errBody = "Invalid request ID, must be string, number, or null"}+{- | Servant API type for the unauthenticated MCP endpoint. - -- 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'}+Unlike 'MCPAPI', this does not use @servant-auth@ and performs no+authentication. See the module documentation for guidance on when it is+safe to use this transport.+-}+type SimpleHTTPAPI =+ "mcp"+ :> ReqBody '[JSON] JSONRPCMessage+ :> StreamPost JSONRPCFrame JSONRPCEvent (SourceIO JSONRPCMessage)+ :<|> "mcp"+ :> StreamGet JSONRPCFrame JSONRPCEvent (SourceIO JSONRPCMessage) - -- Process the request routing to the appropriate handler- res <- liftIO $ modifyMVar state_var $ fmap swap <$> runStateT (processMethod server_initialized method params)+{- | Build a WAI 'Application' for the unauthenticated HTTP transport. - -- 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 ()+Every request is accepted without authentication. This should only be+used on @localhost@ or behind an authenticating reverse proxy.+-}+simpleHttpApp :: MVar MCPServerState -> Application+simpleHttpApp state_var =+ serve (Proxy @SimpleHTTPAPI) $+ handleSimpleHTTPRequest state_var+ :<|> handleSimpleHTTPEvents - -- Get the final result to return- final_result <- liftIO $ handleProcessResult req_id res+-- | Handle GET \/mcp requests (unauthenticated).+handleSimpleHTTPEvents :: Handler (SourceIO JSONRPCMessage)+handleSimpleHTTPEvents = return $ fromStepT Stop - -- 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)+{- | Handle incoming MCP JSON-RPC requests (unauthenticated). - -- Helper to throw unauthorized errors- mcpAuthError :: Text -> Handler (StepT IO JSONRPCMessage)- mcpAuthError err = throwError err401{errBody = encode $ object ["error" .= err]}+Delegates directly to 'handleMCPRequestCore'. No user type is available,+so @mcp_handler_init@ is not called.+-}+handleSimpleHTTPRequest ::+ MVar MCPServerState ->+ JSONRPCMessage ->+ Handler (SourceIO JSONRPCMessage)+handleSimpleHTTPRequest state_var = handleMCPRequestCore state_var Nothing
+ src/MCP/Server/HTTP/Internal.hs view
@@ -0,0 +1,233 @@+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++{- |+Module: MCP.Server.HTTP.Internal+License: MPL-2.0+Maintainer: <matti@dpella.io>, <lobo@dpella.io>++Shared internals for HTTP-based MCP transports.++This module contains the SSE framing types and the core request handler+used by both the JWT-authenticated and simple (unauthenticated) HTTP+transports in 'MCP.Server.HTTP'. Each transport handles authentication+(or skips it) and then delegates to 'handleMCPRequestCore'.+-}+module MCP.Server.HTTP.Internal (+ -- * SSE framing+ JSONRPCFrame,+ JSONRPCEvent,++ -- * Core request handler+ handleMCPRequestCore,+) 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 qualified as T+import Data.Text.Encoding (encodeUtf8)+import Data.Tuple (swap)+import MCP.Server.Common+import Network.HTTP.Media ((//))+import Servant+import Servant.Types.SourceT+import Servant.Types.SourceT qualified as Source++-- ---------------------------------------------------------------------------+-- SSE framing types+-- ---------------------------------------------------------------------------++-- | Frame for JSON-RPC messages — appends a newline after each chunk.+data JSONRPCFrame++instance FramingRender JSONRPCFrame where+ framingRender _ f = fmap (\x -> f x <> "\n")++-- | Content-type for Server-Sent Events.+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")++-- ---------------------------------------------------------------------------+-- Core request handler+-- ---------------------------------------------------------------------------++{- | Transport-agnostic HTTP request handler.++Both 'MCP.Server.HTTP.handleMCPRequest' and+'MCP.Server.SimpleHTTP.handleSimpleHTTPRequest' delegate here after+performing their own authentication checks.++When @mb_user@ is @'Just' user@, the server calls @mcp_handler_init user@+on the first @initialize@ request (JWT\/HTTP transport). When @'Nothing'@,+the init hook is skipped (SimpleHTTP \/ Stdio convention).+-}+handleMCPRequestCore ::+ MVar MCPServerState ->+ -- | Authenticated user, if available (for @mcp_handler_init@)+ Maybe MCPHandlerUser ->+ JSONRPCMessage ->+ Handler (SourceIO JSONRPCMessage)+handleMCPRequestCore state_var mb_user request_value =+ fromStepT <$> do+ MCPServerState+ { mcp_server_initialized = server_initialized+ , mcp_log_level = log_level+ } <-+ liftIO $ readMVar state_var++ -- Log the request if debug level+ 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}+ -- 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+ -- (only when an authenticated user is available)+ case mb_user of+ Just auth_user ->+ 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'}+ Nothing -> return ()++ -- 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+ handler_st' <- handler_finalize handler_st+ return st{mcp_handler_state = handler_st'}+ return final_result+ where+ 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+ ci_resp <- liftIO $ takeMVar mvar+ 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)
+ test/MCP/SimpleHTTPIntegration.hs view
@@ -0,0 +1,280 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}++{- |+Module: MCP.SimpleHTTPIntegration+License: MPL-2.0+Maintainer: <matti@dpella.io>, <lobo@dpella.io>++Integration tests for the SimpleHTTP transport.+-}+module MCP.SimpleHTTPIntegration where++import Control.Concurrent.MVar (MVar, newMVar)+import Control.Monad.IO.Class (liftIO)+import Data.Aeson (FromJSON, 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.IntMap qualified as IM+import Data.Map qualified as Map+import MCP.Protocol+import MCP.Server.Common+import MCP.Server.HTTP (simpleHttpApp)+import MCP.TestServer (+ availablePrompts,+ availableResourceTemplates,+ availableResources,+ availableTools,+ initializeTestState,+ mb_handler_finalize,+ processHandlers,+ )+import MCP.TestUtils+import Network.HTTP.Types qualified as HTTP+import Network.Wai (Application)+import Network.Wai.Test (SResponse (..))+import Test.Hspec+import Test.Hspec.Wai (+ WaiSession,+ get,+ shouldRespondWith,+ withState,+ )++-- ---------------------------------------------------------------------------+-- Test application setup+-- ---------------------------------------------------------------------------++-- | Create a test application using SimpleHTTP (no auth)+createTestApp :: IO ((), Application)+createTestApp = do+ state_var <- createSimpleHTTPTestState+ let app = simpleHttpApp state_var+ return ((), app)++-- | Create test server state for SimpleHTTP (no handler_init, same as Stdio)+createSimpleHTTPTestState :: IO (MVar MCPServerState)+createSimpleHTTPTestState = 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 = Nothing -- no user type in SimpleHTTP+ , mcp_handler_finalize = mb_handler_finalize+ , mcp_client_capabilities = Nothing+ , mcp_log_level = Just Info+ , mcp_pending_responses = IM.empty+ , mcp_pending_responses_next = 1+ , mcp_server_capabilities = server_caps+ , mcp_implementation = impl+ , mcp_instructions = Nothing+ , mcp_process_handlers = processHandlers+ }++-- ---------------------------------------------------------------------------+-- Test specifications+-- ---------------------------------------------------------------------------++-- | Complete SimpleHTTP integration test suite+simpleHTTPIntegrationSpec :: Spec+simpleHTTPIntegrationSpec = describe "SimpleHTTP Integration Tests" $ do+ withState createTestApp $ do+ protocolFlowSpec+ endpointsSpec+ preInitializationSpec++-- ---------------------------------------------------------------------------+-- Protocol flow tests+-- ---------------------------------------------------------------------------++protocolFlowSpec :: SpecWith ((), Application)+protocolFlowSpec = describe "Protocol Flow" $ do+ it "initialization handshake works" $ do+ let init_req = toJSON createInitializeRequest+ resp_init <- mcpPostRequest headers init_req+ withValidSimpleHTTPResponse resp_init 1 validateInitializationResponse++ let notify_request = toJSON createInitializedNotification+ mcpPostRequestOk headers notify_request++ it "rejects request with wrong jsonrpc version" $ do+ let invalid_request =+ toJSON $+ createJSONRPCRequest (Just "1.0") (1 :: Int) "ping" Aeson.Null+ mcpPostRequestExpects headers invalid_request 400++ it "accepts GET /mcp" $+ get "/mcp" `shouldRespondWith` 200++-- ---------------------------------------------------------------------------+-- Endpoint tests+-- ---------------------------------------------------------------------------++endpointsSpec :: SpecWith ((), Application)+endpointsSpec = describe "Endpoints" $ do+ it "handles tools/list request" $+ withInitializedSimpleHTTP $ do+ let req = toJSON $ createListToolsRequest 2+ resp <- mcpPostRequest headers req+ withValidSimpleHTTPResponse resp 2 $ \(ListToolsResult{tools = ls_tools}) ->+ length ls_tools `shouldBe` length availableTools++ it "handles tools/call request" $+ withInitializedSimpleHTTP $ do+ let req = toJSON $ createCallToolRequest 3 "addition-tool" [("arg1", toJSON (5 :: Int)), ("arg2", toJSON (7 :: Int))]+ resp <- mcpPostRequest headers req+ withValidSimpleHTTPResponse resp 3 $ \(CallToolResult{structuredContent = structured}) ->+ case structured of+ Just map_results ->+ Map.lookup "result" map_results `shouldBe` Just (toJSON (12 :: Int))+ Nothing -> expectationFailure "Expected structured content"++ it "handles resources/list request" $+ withInitializedSimpleHTTP $ do+ let req = toJSON $ createListResourcesRequest 4+ resp <- mcpPostRequest headers req+ withValidSimpleHTTPResponse resp 4 $ \(ListResourcesResult{resources = ls_resources}) ->+ length ls_resources `shouldBe` length availableResources++ it "handles prompts/list request" $+ withInitializedSimpleHTTP $ do+ let req = toJSON $ createPromptListRequest 5+ resp <- mcpPostRequest headers req+ withValidSimpleHTTPResponse resp 5 $ \(ListPromptsResult{prompts = ls_prompts}) ->+ length ls_prompts `shouldBe` length availablePrompts++ it "handles resources/templates/list request" $+ withInitializedSimpleHTTP $ do+ let req = toJSON $ createListResourceTemplatesRequest 6+ resp <- mcpPostRequest headers req+ withValidSimpleHTTPResponse resp 6 $ \(ListResourceTemplatesResult{resourceTemplates = templates}) ->+ length templates `shouldBe` length availableResourceTemplates++ it "handles ping after initialization" $+ withInitializedSimpleHTTP $ do+ let req = toJSON createPingRequest+ mcpPostRequestOk headers req++-- ---------------------------------------------------------------------------+-- Pre-initialization tests+-- ---------------------------------------------------------------------------++preInitializationSpec :: SpecWith ((), Application)+preInitializationSpec = describe "Pre-Initialization Enforcement" $ do+ it "rejects tools/list before initialization" $ do+ let req = toJSON $ createListToolsRequest 1+ resp <- mcpPostRequest headers req+ withValidSimpleHTTPErrorResponse resp 1 $ \err_info ->+ code err_info `shouldBe` sERVER_NOT_INITIALIZED++ it "allows ping before initialization" $ do+ let req = toJSON createPingRequest+ mcpPostRequestOk headers req++-- ---------------------------------------------------------------------------+-- Helpers+-- ---------------------------------------------------------------------------++-- | Standard headers for SimpleHTTP requests (no auth needed)+headers :: [(HTTP.HeaderName, BS.ByteString)]+headers = [("Content-Type", "application/json")]++-- | Initialize server then run action+withInitializedSimpleHTTP :: WaiSession () a -> WaiSession () a+withInitializedSimpleHTTP f = do+ let init_request = toJSON createInitializeRequest+ mcpPostRequestOk headers init_request+ let notify_request = toJSON createInitializedNotification+ mcpPostRequestOk headers notify_request+ f++-- | Parse and validate a SimpleHTTP JSON-RPC response+withValidSimpleHTTPResponse ::+ (FromJSON a) =>+ SResponse ->+ Int ->+ (a -> Expectation) ->+ WaiSession () ()+withValidSimpleHTTPResponse 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++-- | Parse and validate a SimpleHTTP JSON-RPC error response+withValidSimpleHTTPErrorResponse ::+ SResponse ->+ Int ->+ (JSONRPCErrorInfo -> Expectation) ->+ WaiSession () ()+withValidSimpleHTTPErrorResponse 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++-- | Validates initialization response+validateInitializationResponse :: InitializeResult -> Expectation+validateInitializationResponse InitializeResult{protocolVersion = init_protocol_version} = do+ init_protocol_version `shouldBe` pROTOCOL_VERSION++-- ** SSE parsing (same as Integration.hs)++-- | 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 SSE format+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/Main.hs view
@@ -11,6 +11,7 @@ module Main where import MCP.Integration qualified as Integration+import MCP.SimpleHTTPIntegration qualified as SimpleHTTPIntegration import MCP.StdioIntegration qualified as StdioIntegration import Test.Hspec (describe, hspec) @@ -19,4 +20,5 @@ main = hspec $ do describe "MCP Library" $ do Integration.integrationSpec+ SimpleHTTPIntegration.simpleHTTPIntegrationSpec StdioIntegration.stdioIntegrationSpec