packages feed

mcp-server 0.1.0.9 → 0.1.0.10

raw patch · 8 files changed

+86/−25 lines, 8 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

+ MCP.Server.Types: ResourceBlob :: URI -> Text -> Text -> ResourceContent
+ MCP.Server.Types: ResourceText :: URI -> Text -> Text -> ResourceContent
+ MCP.Server.Types: [resourceBlob] :: ResourceContent -> Text
+ MCP.Server.Types: [resourceMimeType] :: ResourceContent -> Text
+ MCP.Server.Types: [resourceText] :: ResourceContent -> Text
+ MCP.Server.Types: [resourceUri] :: ResourceContent -> URI
+ MCP.Server.Types: data ResourceContent
+ MCP.Server.Types: data URI
+ MCP.Server.Types: instance Data.Aeson.Types.FromJSON.FromJSON MCP.Server.Types.ResourceContent
+ MCP.Server.Types: instance Data.Aeson.Types.ToJSON.ToJSON MCP.Server.Types.ResourceContent
+ MCP.Server.Types: instance GHC.Classes.Eq MCP.Server.Types.ResourceContent
+ MCP.Server.Types: instance GHC.Internal.Generics.Generic MCP.Server.Types.ResourceContent
+ MCP.Server.Types: instance GHC.Internal.Show.Show MCP.Server.Types.ResourceContent
+ MCP.Server.Types: parseURI :: String -> Maybe URI
- MCP.Server.Protocol: ResourcesReadResponse :: [Content] -> ResourcesReadResponse
+ MCP.Server.Protocol: ResourcesReadResponse :: [ResourceContent] -> ResourcesReadResponse
- MCP.Server.Protocol: [resourcesReadContents] :: ResourcesReadResponse -> [Content]
+ MCP.Server.Protocol: [resourcesReadContents] :: ResourcesReadResponse -> [ResourceContent]
- MCP.Server.Types: type ResourceReadHandler (m :: Type -> Type) = URI -> m Either Error Content
+ MCP.Server.Types: type ResourceReadHandler (m :: Type -> Type) = URI -> m Either Error ResourceContent

Files

examples/Complete/Main.hs view
@@ -15,10 +15,13 @@ handlePrompt (Shopping description) =     pure $ ContentText $ "Shopping prompt for " <> description <> ": Create a detailed shopping list..." -handleResource :: MyResource -> IO Content-handleResource ProductCategories = pure $ ContentText "Fresh Produce, Dairy, Bakery, Meat & Seafood, Frozen Foods"-handleResource SaleItems = pure $ ContentText "Organic Apples $2.99/lb, Free Range Eggs $4.50/dozen, Artisan Bread $3.25/loaf"-handleResource HeadlineBannerAd = pure $ ContentText "🛒 Weekly Special: 20% off all organic produce! 🥕🥬🍎"+handleResource :: URI -> MyResource -> IO ResourceContent+handleResource uri ProductCategories =+    pure $ ResourceText uri "text/plain" "Fresh Produce, Dairy, Bakery, Meat & Seafood, Frozen Foods"+handleResource uri SaleItems =+    pure $ ResourceText uri "text/plain" "Organic Apples $2.99/lb, Free Range Eggs $4.50/dozen, Artisan Bread $3.25/loaf"+handleResource uri HeadlineBannerAd =+    pure $ ResourceText uri "text/plain" "🛒 Weekly Special: 20% off all organic produce! 🥕🥬🍎"  handleTool :: MyTool -> IO Content handleTool (SearchForProduct q category) =
mcp-server.cabal view
@@ -15,7 +15,7 @@ -- PVP summary:     +-+------- breaking API changes --                  | | +----- non-breaking API additions --                  | | | +--- code changes with no API change-version: 0.1.0.9+version: 0.1.0.10 -- A short (one-line) description of the package. synopsis: Library for building Model Context Protocol (MCP) servers -- A longer description of the package.
src/MCP/Server.hs view
@@ -279,9 +279,9 @@                   , errorMessage = errorMessageFromMcpError err                   , errorData = Nothing                   }-                Right content -> do+                Right resourceContent -> do                   let response = ResourcesReadResponse-                        { resourcesReadContents = [content]+                        { resourcesReadContents = [resourceContent]                         }                   return $ makeSuccessResponse (requestId req) (toJSON response) 
src/MCP/Server/Derive.hs view
@@ -378,7 +378,7 @@   let resourceName = T.pack . toSnakeCase . nameBase $ name   let resourceURI = "resource://" <> T.unpack resourceName   clause [litP $ stringL resourceURI]-    (normalB [| Right <$> $(varE handlerName) $(conE name) |])+    (normalB [| $(varE handlerName) $(varE (mkName "uri")) $(conE name) >>= pure . Right |])     [] mkResourceCase _ _ = fail "Unsupported constructor type for resources" 
src/MCP/Server/Protocol.hs view
@@ -39,7 +39,7 @@ import Data.Text (Text) import Data.Aeson import Data.Map (Map)-import Network.URI (URI, parseURI)+import Network.URI (URI) import GHC.Generics (Generic) import MCP.Server.Types @@ -189,7 +189,7 @@  -- | Resources read response data ResourcesReadResponse = ResourcesReadResponse-  { resourcesReadContents :: [Content]+  { resourcesReadContents :: [ResourceContent]   } deriving (Show, Eq, Generic)  instance ToJSON ResourcesReadResponse where
src/MCP/Server/Types.hs view
@@ -6,7 +6,12 @@     Content(..)   , ContentImageData(..)   , ContentResourceData(..)+  , ResourceContent(..) +    -- * URI Utilities  +  , parseURI+  , URI+     -- * Error Types   , Error(..) @@ -49,7 +54,7 @@ import           Data.Text        (Text) import qualified Data.Text        as T import           GHC.Generics     (Generic)-import           Network.URI      (URI)+import           Network.URI      (URI, parseURI)  type PromptName = Text type ToolName = Text@@ -107,6 +112,47 @@   , contentResourceMimeType :: Maybe Text   } deriving (Show, Eq, Generic) +-- | Resource content compliant with MCP specification+-- Must include uri and mimeType, with either text or blob data+data ResourceContent+  = ResourceText+      { resourceUri :: URI+      , resourceMimeType :: Text+      , resourceText :: Text+      }+  | ResourceBlob+      { resourceUri :: URI+      , resourceMimeType :: Text+      , resourceBlob :: Text  -- base64 encoded+      }+  deriving (Show, Eq, Generic)++instance ToJSON ResourceContent where+  toJSON (ResourceText uri mimeType text) = object+    [ "uri" .= show uri+    , "mimeType" .= mimeType+    , "text" .= text+    ]+  toJSON (ResourceBlob uri mimeType blob) = object+    [ "uri" .= show uri+    , "mimeType" .= mimeType+    , "blob" .= blob+    ]++instance FromJSON ResourceContent where+  parseJSON = withObject "ResourceContent" $ \o -> do+    uriText <- o .: "uri"+    mimeType <- o .: "mimeType"+    case parseURI uriText of+      Nothing -> fail "Invalid URI"+      Just uri -> do+        maybeText <- o .:? "text"+        maybeBlob <- o .:? "blob"+        case (maybeText, maybeBlob) of+          (Just text, Nothing) -> return $ ResourceText uri mimeType text+          (Nothing, Just blob) -> return $ ResourceBlob uri mimeType blob+          _ -> fail "ResourceContent must have either 'text' or 'blob' field"+ -- | MCP protocol errors data Error   = InvalidPromptName Text@@ -293,7 +339,7 @@ type PromptGetHandler m = PromptName -> [(ArgumentName, ArgumentValue)] -> m (Either Error Content)  type ResourceListHandler m = m [ResourceDefinition]-type ResourceReadHandler m = URI -> m (Either Error Content)+type ResourceReadHandler m = URI -> m (Either Error ResourceContent)  type ToolListHandler m = m [ToolDefinition] type ToolCallHandler m = ToolName -> [(ArgumentName, ArgumentValue)] -> m (Either Error Content)
test/Spec/BasicDerivation.hs view
@@ -46,7 +46,12 @@ testResourceCall :: ResourceReadHandler IO -> String -> Text -> IO () testResourceCall handler uriString expected = do   case parseURI uriString of-    Just uri -> shouldReturnContentText (handler uri) expected+    Just uri -> do+      result <- handler uri+      case result of+        Right (ResourceText _ _ content) -> content `shouldBe` expected+        Right (ResourceBlob _ _ _) -> expectationFailure "Expected ResourceText but got ResourceBlob"+        Left err -> expectationFailure $ "Expected success but got error: " ++ show err     Nothing -> expectationFailure $ "Failed to parse URI: " ++ uriString  testToolCall :: ToolCallHandler IO -> Text -> [(Text, Text)] -> Text -> IO ()@@ -68,12 +73,18 @@          forM_ resourceTestCases $ \testCase ->       it (T.unpack $ resourceTestDescription testCase) $ do-        case parseURI (T.unpack $ resourceUri testCase) of+        case parseURI (T.unpack $ TestData.resourceUri testCase) of           Just uri ->              if useSubstringMatch testCase-              then shouldContainText (readHandler uri) (resourceExpectedContent testCase)-              else testResourceCall readHandler (T.unpack $ resourceUri testCase) (resourceExpectedContent testCase)-          Nothing -> expectationFailure $ "Failed to parse URI: " ++ T.unpack (resourceUri testCase)+              then do+                result <- readHandler uri+                case result of+                  Right (ResourceText _ _ content) -> +                    T.isInfixOf (resourceExpectedContent testCase) content `shouldBe` True+                  Right (ResourceBlob _ _ _) -> expectationFailure "Expected ResourceText but got ResourceBlob"+                  Left err -> expectationFailure $ "Expected success but got error: " ++ show err+              else testResourceCall readHandler (T.unpack $ TestData.resourceUri testCase) (resourceExpectedContent testCase)+          Nothing -> expectationFailure $ "Failed to parse URI: " ++ T.unpack (TestData.resourceUri testCase)    describe "Tool derivation" $ do     let (_, callHandler) = testToolHandlers
test/TestTypes.hs view
@@ -4,7 +4,8 @@  import           Data.Text  (Text) import qualified Data.Text  as T-import           MCP.Server (Content(..))+import           MCP.Server (Content(..), ResourceContent(..), parseURI)+import           Network.URI (URI)  -- Test data types for end-to-end testing data TestPrompt@@ -54,13 +55,13 @@ handleTestPrompt (OptionalPrompt req opt) =      pure $ ContentText $ "Optional prompt: " <> req <> maybe "" ((" optional=" <>) . T.pack . show) opt -handleTestResource :: TestResource -> IO Content-handleTestResource ConfigFile = -    pure $ ContentText "Config file contents: debug=true, timeout=30"-handleTestResource DatabaseConnection = -    pure $ ContentText "Database at localhost:5432"-handleTestResource UserProfile = -    pure $ ContentText "User profile for ID 123"+handleTestResource :: URI -> TestResource -> IO ResourceContent+handleTestResource uri ConfigFile = +    pure $ ResourceText uri "text/plain" "Config file contents: debug=true, timeout=30"+handleTestResource uri DatabaseConnection = +    pure $ ResourceText uri "text/plain" "Database at localhost:5432"+handleTestResource uri UserProfile = +    pure $ ResourceText uri "text/plain" "User profile for ID 123"  handleTestTool :: TestTool -> IO Content handleTestTool (Echo text) =