packages feed

ollama-haskell 0.3.0.1 → 0.4.0.0

raw patch · 15 files changed

+903/−103 lines, 15 filesdep +mcp-serverdep ~textPVP ok

version bump matches the API change (PVP)

Dependencies added: mcp-server

Dependency ranges changed: text

API changes (from Hackage documentation)

+ Ollama: class ToJsonType a
+ Ollama: class ToSchema a
+ Ollama: formatFor :: forall a. ToSchema a => Format
+ Ollama: schemaFor :: forall a. ToSchema a => Schema
+ Ollama: toJsonType :: (ToJsonType a, ToSchema a) => JsonType
+ Ollama: toSchema :: (ToSchema a, GToSchema (Rep a)) => Schema
+ Ollama.MCP: ArgumentDefinition :: Text -> Text -> Bool -> ArgumentDefinition
+ Ollama.MCP: ContentAnnotated :: Annotations -> Content -> Content
+ Ollama.MCP: ContentAudio :: ContentAudioData -> Content
+ Ollama.MCP: ContentAudioData :: Text -> Text -> ContentAudioData
+ Ollama.MCP: ContentEmbeddedResource :: ResourceContent -> Content
+ Ollama.MCP: ContentImage :: ContentImageData -> Content
+ Ollama.MCP: ContentImageData :: Text -> Text -> ContentImageData
+ Ollama.MCP: ContentResourceLink :: ResourceDefinition -> Content
+ Ollama.MCP: ContentText :: Text -> Content
+ Ollama.MCP: HttpConfig :: Int -> String -> String -> Bool -> Maybe (Maybe Text -> IO (Maybe Value)) -> Maybe [Text] -> CacheHints -> Maybe NotificationSource -> HttpConfig
+ Ollama.MCP: LoggingCapabilities :: LoggingCapabilities
+ Ollama.MCP: McpServerHandlers :: Maybe (PromptListHandler, PromptGetHandler) -> Maybe (ResourceListHandler, ResourceReadHandler) -> Maybe ResourceTemplateListHandler -> Maybe (ToolListHandler, ToolCallHandler) -> Maybe CompletionHandler -> McpServerHandlers
+ Ollama.MCP: McpServerInfo :: Text -> Text -> Text -> McpServerInfo
+ Ollama.MCP: PromptCapabilities :: Maybe Bool -> PromptCapabilities
+ Ollama.MCP: PromptDefinition :: Text -> Text -> [ArgumentDefinition] -> Maybe Text -> [Icon] -> PromptDefinition
+ Ollama.MCP: ResourceBlob :: URI -> Text -> Text -> ResourceContent
+ Ollama.MCP: ResourceCapabilities :: Maybe Bool -> Maybe Bool -> ResourceCapabilities
+ Ollama.MCP: ResourceDefinition :: Text -> Text -> Maybe Text -> Maybe Text -> Maybe Text -> [Icon] -> ResourceDefinition
+ Ollama.MCP: ResourceText :: URI -> Text -> Text -> ResourceContent
+ Ollama.MCP: SchemaArray :: Schema -> SchemaType
+ Ollama.MCP: SchemaBoolean :: SchemaType
+ Ollama.MCP: SchemaInteger :: SchemaType
+ Ollama.MCP: SchemaNumber :: SchemaType
+ Ollama.MCP: SchemaObject :: [(Text, Schema)] -> [Text] -> SchemaType
+ Ollama.MCP: SchemaString :: Maybe [Text] -> SchemaType
+ Ollama.MCP: ServerCapabilities :: Maybe PromptCapabilities -> Maybe ResourceCapabilities -> Maybe ToolCapabilities -> Maybe CompletionCapabilities -> Maybe LoggingCapabilities -> ServerCapabilities
+ Ollama.MCP: ToolCapabilities :: Maybe Bool -> ToolCapabilities
+ Ollama.MCP: ToolDefinition :: Text -> Text -> Schema -> Maybe Schema -> Maybe Text -> Maybe ToolAnnotations -> [Icon] -> ToolDefinition
+ Ollama.MCP: [argumentDefinitionDescription] :: ArgumentDefinition -> Text
+ Ollama.MCP: [argumentDefinitionName] :: ArgumentDefinition -> Text
+ Ollama.MCP: [argumentDefinitionRequired] :: ArgumentDefinition -> Bool
+ Ollama.MCP: [capabilityCompletions] :: ServerCapabilities -> Maybe CompletionCapabilities
+ Ollama.MCP: [capabilityLogging] :: ServerCapabilities -> Maybe LoggingCapabilities
+ Ollama.MCP: [capabilityPrompts] :: ServerCapabilities -> Maybe PromptCapabilities
+ Ollama.MCP: [capabilityResources] :: ServerCapabilities -> Maybe ResourceCapabilities
+ Ollama.MCP: [capabilityTools] :: ServerCapabilities -> Maybe ToolCapabilities
+ Ollama.MCP: [completions] :: McpServerHandlers -> Maybe CompletionHandler
+ Ollama.MCP: [contentAudioData] :: ContentAudioData -> Text
+ Ollama.MCP: [contentAudioMimeType] :: ContentAudioData -> Text
+ Ollama.MCP: [contentImageData] :: ContentImageData -> Text
+ Ollama.MCP: [contentImageMimeType] :: ContentImageData -> Text
+ Ollama.MCP: [httpAllowedOrigins] :: HttpConfig -> Maybe [Text]
+ Ollama.MCP: [httpAuthorize] :: HttpConfig -> Maybe (Maybe Text -> IO (Maybe Value))
+ Ollama.MCP: [httpCacheHints] :: HttpConfig -> CacheHints
+ Ollama.MCP: [httpEndpoint] :: HttpConfig -> String
+ Ollama.MCP: [httpHost] :: HttpConfig -> String
+ Ollama.MCP: [httpNotifications] :: HttpConfig -> Maybe NotificationSource
+ Ollama.MCP: [httpPort] :: HttpConfig -> Int
+ Ollama.MCP: [httpVerbose] :: HttpConfig -> Bool
+ Ollama.MCP: [promptDefinitionArguments] :: PromptDefinition -> [ArgumentDefinition]
+ Ollama.MCP: [promptDefinitionDescription] :: PromptDefinition -> Text
+ Ollama.MCP: [promptDefinitionIcons] :: PromptDefinition -> [Icon]
+ Ollama.MCP: [promptDefinitionName] :: PromptDefinition -> Text
+ Ollama.MCP: [promptDefinitionTitle] :: PromptDefinition -> Maybe Text
+ Ollama.MCP: [promptListChanged] :: PromptCapabilities -> Maybe Bool
+ Ollama.MCP: [prompts] :: McpServerHandlers -> Maybe (PromptListHandler, PromptGetHandler)
+ Ollama.MCP: [resourceBlob] :: ResourceContent -> Text
+ Ollama.MCP: [resourceDefinitionDescription] :: ResourceDefinition -> Maybe Text
+ Ollama.MCP: [resourceDefinitionIcons] :: ResourceDefinition -> [Icon]
+ Ollama.MCP: [resourceDefinitionMimeType] :: ResourceDefinition -> Maybe Text
+ Ollama.MCP: [resourceDefinitionName] :: ResourceDefinition -> Text
+ Ollama.MCP: [resourceDefinitionTitle] :: ResourceDefinition -> Maybe Text
+ Ollama.MCP: [resourceDefinitionURI] :: ResourceDefinition -> Text
+ Ollama.MCP: [resourceListChanged] :: ResourceCapabilities -> Maybe Bool
+ Ollama.MCP: [resourceMimeType] :: ResourceContent -> Text
+ Ollama.MCP: [resourceSubscribe] :: ResourceCapabilities -> Maybe Bool
+ Ollama.MCP: [resourceTemplates] :: McpServerHandlers -> Maybe ResourceTemplateListHandler
+ Ollama.MCP: [resourceText] :: ResourceContent -> Text
+ Ollama.MCP: [resourceUri] :: ResourceContent -> URI
+ Ollama.MCP: [resources] :: McpServerHandlers -> Maybe (ResourceListHandler, ResourceReadHandler)
+ Ollama.MCP: [serverInstructions] :: McpServerInfo -> Text
+ Ollama.MCP: [serverName] :: McpServerInfo -> Text
+ Ollama.MCP: [serverVersion] :: McpServerInfo -> Text
+ Ollama.MCP: [toolDefinitionAnnotations] :: ToolDefinition -> Maybe ToolAnnotations
+ Ollama.MCP: [toolDefinitionDescription] :: ToolDefinition -> Text
+ Ollama.MCP: [toolDefinitionIcons] :: ToolDefinition -> [Icon]
+ Ollama.MCP: [toolDefinitionInputSchema] :: ToolDefinition -> Schema
+ Ollama.MCP: [toolDefinitionName] :: ToolDefinition -> Text
+ Ollama.MCP: [toolDefinitionOutputSchema] :: ToolDefinition -> Maybe Schema
+ Ollama.MCP: [toolDefinitionTitle] :: ToolDefinition -> Maybe Text
+ Ollama.MCP: [toolListChanged] :: ToolCapabilities -> Maybe Bool
+ Ollama.MCP: [tools] :: McpServerHandlers -> Maybe (ToolListHandler, ToolCallHandler)
+ Ollama.MCP: data () => ArgumentDefinition
+ Ollama.MCP: data () => Content
+ Ollama.MCP: data () => ContentAudioData
+ Ollama.MCP: data () => ContentImageData
+ Ollama.MCP: data () => HttpConfig
+ Ollama.MCP: data () => LoggingCapabilities
+ Ollama.MCP: data () => McpServerHandlers
+ Ollama.MCP: data () => McpServerInfo
+ Ollama.MCP: data () => PromptCapabilities
+ Ollama.MCP: data () => PromptDefinition
+ Ollama.MCP: data () => ResourceCapabilities
+ Ollama.MCP: data () => ResourceContent
+ Ollama.MCP: data () => ResourceDefinition
+ Ollama.MCP: data () => SchemaType
+ Ollama.MCP: data () => ServerCapabilities
+ Ollama.MCP: data () => ToolCapabilities
+ Ollama.MCP: data () => ToolDefinition
+ Ollama.MCP: data () => URI
+ Ollama.MCP: describedSchema :: Text -> SchemaType -> Schema
+ Ollama.MCP: jsonValueToText :: Value -> Text
+ Ollama.MCP: mcpContentToToolOutput :: Content -> Text
+ Ollama.MCP: mcpDefinitionToTool :: ToolDefinition -> Tool
+ Ollama.MCP: mkPromptDefinition :: Text -> Text -> [ArgumentDefinition] -> PromptDefinition
+ Ollama.MCP: mkResourceDefinition :: Text -> Text -> ResourceDefinition
+ Ollama.MCP: mkToolDefinition :: Text -> Text -> Schema -> ToolDefinition
+ Ollama.MCP: parseURI :: String -> Maybe URI
+ Ollama.MCP: pattern McpSchema :: Maybe Text -> SchemaType -> McpSchema
+ Ollama.MCP: runMcpServerHttp :: McpServerInfo -> McpServerHandlers -> IO ()
+ Ollama.MCP: runMcpServerHttpWithConfig :: HttpConfig -> McpServerInfo -> McpServerHandlers -> IO ()
+ Ollama.MCP: runMcpServerStdio :: McpServerInfo -> McpServerHandlers -> IO ()
+ Ollama.MCP: schema :: SchemaType -> Schema
+ Ollama.MCP: schemaDescription :: Schema -> Maybe Text
+ Ollama.MCP: schemaShape :: Schema -> SchemaType
+ Ollama.MCP: toolCallToMcpArgs :: ToolCall -> (Text, [(Text, Text)])
+ Ollama.MCP: toolToMcpDefinition :: Tool -> ToolDefinition
+ Ollama.MCP: type ArgumentName = Text
+ Ollama.MCP: type ArgumentValue = Text
+ Ollama.MCP: type McpProtocolError = Error
+ Ollama.MCP: type McpSchema = Schema
+ Ollama.MCP: type PromptGetHandler = ClientContext -> PromptName -> Map Text Text -> IO Either Error PromptResult
+ Ollama.MCP: type PromptListHandler = ClientContext -> IO [PromptDefinition]
+ Ollama.MCP: type PromptName = Text
+ Ollama.MCP: type ResourceListHandler = ClientContext -> IO [ResourceDefinition]
+ Ollama.MCP: type ResourceReadHandler = ClientContext -> URI -> IO Either Error ResourceContent
+ Ollama.MCP: type ToolCallHandler = ClientContext -> ToolName -> Map Text Value -> IO Either Error ToolResult
+ Ollama.MCP: type ToolListHandler = ClientContext -> IO [ToolDefinition]
+ Ollama.MCP: type ToolName = Text
+ Ollama.Types.Format: formatFor :: forall a. ToSchema a => Format
+ Ollama.Types.Format.SchemaDerive: class GCollectFields (f :: Type -> Type)
+ Ollama.Types.Format.SchemaDerive: class GEnumConstructors (f :: Type -> Type)
+ Ollama.Types.Format.SchemaDerive: class GToSchema (f :: Type -> Type)
+ Ollama.Types.Format.SchemaDerive: class GToSchemaDispatch (f :: Type -> Type)
+ Ollama.Types.Format.SchemaDerive: class ToJsonType a
+ Ollama.Types.Format.SchemaDerive: class ToSchema a
+ Ollama.Types.Format.SchemaDerive: gCollectFields :: GCollectFields f => ([(Text, Property)], [Text])
+ Ollama.Types.Format.SchemaDerive: gEnumConstructors :: GEnumConstructors f => [Text]
+ Ollama.Types.Format.SchemaDerive: gToSchema :: GToSchema f => Schema
+ Ollama.Types.Format.SchemaDerive: gToSchemaDispatch :: GToSchemaDispatch f => Schema
+ Ollama.Types.Format.SchemaDerive: instance (GHC.TypeLits.KnownSymbol name, Ollama.Types.Format.SchemaDerive.ToJsonType a) => Ollama.Types.Format.SchemaDerive.GCollectFields (GHC.Generics.M1 GHC.Generics.S ('GHC.Generics.MetaSel ('GHC.Maybe.Just name) su ss ds) (GHC.Generics.K1 GHC.Generics.R (GHC.Maybe.Maybe a)))
+ Ollama.Types.Format.SchemaDerive: instance (GHC.TypeLits.KnownSymbol name, Ollama.Types.Format.SchemaDerive.ToJsonType a) => Ollama.Types.Format.SchemaDerive.GCollectFields (GHC.Generics.M1 GHC.Generics.S ('GHC.Generics.MetaSel ('GHC.Maybe.Just name) su ss ds) (GHC.Generics.K1 GHC.Generics.R a))
+ Ollama.Types.Format.SchemaDerive: instance (Ollama.Types.Format.SchemaDerive.GCollectFields f, Ollama.Types.Format.SchemaDerive.GCollectFields g) => Ollama.Types.Format.SchemaDerive.GCollectFields (f GHC.Generics.:*: g)
+ Ollama.Types.Format.SchemaDerive: instance (Ollama.Types.Format.SchemaDerive.GEnumConstructors f, Ollama.Types.Format.SchemaDerive.GEnumConstructors g) => Ollama.Types.Format.SchemaDerive.GEnumConstructors (f GHC.Generics.:+: g)
+ Ollama.Types.Format.SchemaDerive: instance (Ollama.Types.Format.SchemaDerive.GEnumConstructors f, Ollama.Types.Format.SchemaDerive.GEnumConstructors g) => Ollama.Types.Format.SchemaDerive.GToSchemaDispatch (f GHC.Generics.:+: g)
+ Ollama.Types.Format.SchemaDerive: instance GHC.TypeLits.KnownSymbol name => Ollama.Types.Format.SchemaDerive.GEnumConstructors (GHC.Generics.M1 GHC.Generics.C ('GHC.Generics.MetaCons name fx 'GHC.Types.False) GHC.Generics.U1)
+ Ollama.Types.Format.SchemaDerive: instance Ollama.Types.Format.SchemaDerive.GCollectFields GHC.Generics.U1
+ Ollama.Types.Format.SchemaDerive: instance Ollama.Types.Format.SchemaDerive.GCollectFields f => Ollama.Types.Format.SchemaDerive.GCollectFields (GHC.Generics.M1 GHC.Generics.C meta f)
+ Ollama.Types.Format.SchemaDerive: instance Ollama.Types.Format.SchemaDerive.GCollectFields f => Ollama.Types.Format.SchemaDerive.GToSchemaDispatch (GHC.Generics.M1 GHC.Generics.C meta f)
+ Ollama.Types.Format.SchemaDerive: instance Ollama.Types.Format.SchemaDerive.GToSchemaDispatch f => Ollama.Types.Format.SchemaDerive.GToSchema (GHC.Generics.M1 GHC.Generics.D meta f)
+ Ollama.Types.Format.SchemaDerive: instance Ollama.Types.Format.SchemaDerive.ToJsonType Data.Text.Internal.Text
+ Ollama.Types.Format.SchemaDerive: instance Ollama.Types.Format.SchemaDerive.ToJsonType GHC.Base.String
+ Ollama.Types.Format.SchemaDerive: instance Ollama.Types.Format.SchemaDerive.ToJsonType GHC.Int.Int16
+ Ollama.Types.Format.SchemaDerive: instance Ollama.Types.Format.SchemaDerive.ToJsonType GHC.Int.Int32
+ Ollama.Types.Format.SchemaDerive: instance Ollama.Types.Format.SchemaDerive.ToJsonType GHC.Int.Int64
+ Ollama.Types.Format.SchemaDerive: instance Ollama.Types.Format.SchemaDerive.ToJsonType GHC.Int.Int8
+ Ollama.Types.Format.SchemaDerive: instance Ollama.Types.Format.SchemaDerive.ToJsonType GHC.Num.Integer.Integer
+ Ollama.Types.Format.SchemaDerive: instance Ollama.Types.Format.SchemaDerive.ToJsonType GHC.Types.Bool
+ Ollama.Types.Format.SchemaDerive: instance Ollama.Types.Format.SchemaDerive.ToJsonType GHC.Types.Double
+ Ollama.Types.Format.SchemaDerive: instance Ollama.Types.Format.SchemaDerive.ToJsonType GHC.Types.Float
+ Ollama.Types.Format.SchemaDerive: instance Ollama.Types.Format.SchemaDerive.ToJsonType GHC.Types.Int
+ Ollama.Types.Format.SchemaDerive: instance Ollama.Types.Format.SchemaDerive.ToJsonType GHC.Types.Word
+ Ollama.Types.Format.SchemaDerive: instance Ollama.Types.Format.SchemaDerive.ToJsonType GHC.Word.Word16
+ Ollama.Types.Format.SchemaDerive: instance Ollama.Types.Format.SchemaDerive.ToJsonType GHC.Word.Word32
+ Ollama.Types.Format.SchemaDerive: instance Ollama.Types.Format.SchemaDerive.ToJsonType GHC.Word.Word64
+ Ollama.Types.Format.SchemaDerive: instance Ollama.Types.Format.SchemaDerive.ToJsonType GHC.Word.Word8
+ Ollama.Types.Format.SchemaDerive: instance Ollama.Types.Format.SchemaDerive.ToJsonType a => Ollama.Types.Format.SchemaDerive.ToJsonType (GHC.Maybe.Maybe a)
+ Ollama.Types.Format.SchemaDerive: instance Ollama.Types.Format.SchemaDerive.ToJsonType a => Ollama.Types.Format.SchemaDerive.ToJsonType [a]
+ Ollama.Types.Format.SchemaDerive: schemaFor :: forall a. ToSchema a => Schema
+ Ollama.Types.Format.SchemaDerive: toJsonType :: (ToJsonType a, ToSchema a) => JsonType
+ Ollama.Types.Format.SchemaDerive: toSchema :: (ToSchema a, GToSchema (Rep a)) => Schema
- Ollama: chatStream :: forall (m :: Type -> Type). MonadUnliftIO m => OllamaClient -> ChatRequest -> ConduitT () ChatResponse m ()
+ Ollama: chatStream :: MonadUnliftIO m => OllamaClient -> ChatRequest -> ConduitT () ChatResponse m ()
- Ollama: class Monad m => ConversationStore (m :: Type -> Type)
+ Ollama: class (Monad m) => ConversationStore m
- Ollama: createModelStream :: forall (m :: Type -> Type). MonadUnliftIO m => OllamaClient -> CreateRequest -> ConduitT () CreateResponse m ()
+ Ollama: createModelStream :: MonadUnliftIO m => OllamaClient -> CreateRequest -> ConduitT () CreateResponse m ()
- Ollama: generateStream :: forall (m :: Type -> Type). MonadUnliftIO m => OllamaClient -> GenerateRequest -> ConduitT () GenerateResponse m ()
+ Ollama: generateStream :: MonadUnliftIO m => OllamaClient -> GenerateRequest -> ConduitT () GenerateResponse m ()
- Ollama: pullStream :: forall (m :: Type -> Type). MonadUnliftIO m => OllamaClient -> ModelName -> ConduitT () PullResponse m ()
+ Ollama: pullStream :: MonadUnliftIO m => OllamaClient -> ModelName -> ConduitT () PullResponse m ()
- Ollama: pushStream :: forall (m :: Type -> Type). MonadUnliftIO m => OllamaClient -> ModelName -> ConduitT () PushResponse m ()
+ Ollama: pushStream :: MonadUnliftIO m => OllamaClient -> ModelName -> ConduitT () PushResponse m ()
- Ollama.API.Chat: chatStream :: forall (m :: Type -> Type). MonadUnliftIO m => OllamaClient -> ChatRequest -> ConduitT () ChatResponse m ()
+ Ollama.API.Chat: chatStream :: MonadUnliftIO m => OllamaClient -> ChatRequest -> ConduitT () ChatResponse m ()
- Ollama.API.Generate: generateStream :: forall (m :: Type -> Type). MonadUnliftIO m => OllamaClient -> GenerateRequest -> ConduitT () GenerateResponse m ()
+ Ollama.API.Generate: generateStream :: MonadUnliftIO m => OllamaClient -> GenerateRequest -> ConduitT () GenerateResponse m ()
- Ollama.API.Models.Create: createModelStream :: forall (m :: Type -> Type). MonadUnliftIO m => OllamaClient -> CreateRequest -> ConduitT () CreateResponse m ()
+ Ollama.API.Models.Create: createModelStream :: MonadUnliftIO m => OllamaClient -> CreateRequest -> ConduitT () CreateResponse m ()
- Ollama.API.Models.Pull: pullStream :: forall (m :: Type -> Type). MonadUnliftIO m => OllamaClient -> ModelName -> ConduitT () PullResponse m ()
+ Ollama.API.Models.Pull: pullStream :: MonadUnliftIO m => OllamaClient -> ModelName -> ConduitT () PullResponse m ()
- Ollama.API.Models.Push: pushStream :: forall (m :: Type -> Type). MonadUnliftIO m => OllamaClient -> ModelName -> ConduitT () PushResponse m ()
+ Ollama.API.Models.Push: pushStream :: MonadUnliftIO m => OllamaClient -> ModelName -> ConduitT () PushResponse m ()
- Ollama.Conversation: class Monad m => ConversationStore (m :: Type -> Type)
+ Ollama.Conversation: class (Monad m) => ConversationStore m

Files

CHANGELOG.md view
@@ -6,6 +6,30 @@  --- +## [0.4.0.0] - 2026-08-20++### Added+- **Model Context Protocol (MCP) Integration (`Ollama.MCP`)**:+  - Full bidirectional integration with the Hackage `mcp-server` package (`mcp-server >= 0.2 && < 0.3`).+  - Seamless conversion between Ollama function calling definitions (`Tool`, `ToolCall`) and MCP definitions (`ToolDefinition`, `ArgumentDefinition`, `Content`, `McpSchema`).+  - Bridge functions: `toolToMcpDefinition`, `mcpDefinitionToTool`, `toolCallToMcpArgs`, `mcpContentToToolOutput`.+  - Re-exported MCP server runners (`runMcpServerStdio`, `runMcpServerHttp`, `runMcpServerHttpWithConfig`).+  - Dedicated unit test suite in `Test.Ollama.Unit.MCP`.+- **Automatic JSON Schema Derivation (`Ollama.Types.Format.SchemaDerive`)**:+  - Typeclasses `ToSchema` and `ToJsonType` enabling generic derivation of JSON schemas directly from Haskell record types via `GHC.Generics`.+  - Smart handling of optional fields (`Maybe a` omitted from `required`), nested records (`JObject`), lists (`JArray`), and simple sum enums (`string` enum).+  - Helper functions `schemaFor` and `formatFor` for effortless integration with `chat` / `generate` structured outputs.+  - Dedicated unit test suite in `Test.Ollama.Unit.SchemaDerive`.+- **Configurable Client Timeout**:+  - Support for custom request timeout intervals in `OllamaClientConfig` (`configTimeout`).++### Changed+- **PVP Compliance & Upper Bounds**:+  - Added strict upper bounds for `network-uri` (`>= 2.6 && < 2.8`) and `mcp-server` (`>= 0.2 && < 0.3`).+  - Upgraded Stack resolvers and snapshot dependencies (`lts-21.25`, `lts-22.44`, `lts-23.28`, `lts-24.52`, `nightly`).++---+ ## [0.3.0.0] - 2026-08-04  ### Added
README.md view
@@ -9,12 +9,14 @@  - **Client-Centric Architecture**: Thread-safe `OllamaClient` handle with connection pooling and resource management (`newClient`, `defaultClient`, `clientFromEnv`, `withClient`). - **First-Class Streaming**: `conduit`-based response streaming (`chatStream`, `generateStream`, `pullStream`, `pushStream`, `createModelStream`).+- **Model Context Protocol (MCP) Bridge**: Bidirectional integration with `mcp-server` for converting between Ollama tools and MCP tools, running MCP servers via stdio or HTTP (`Ollama.MCP`).+- **Generic JSON Schema Derivation**: Automatically derive JSON schemas from Haskell data types via `GHC.Generics` with `ToSchema` and `formatFor`. - **Complete API Surface**: Text generation, chat completions, vector embeddings, model management (list, show, copy, delete, pull, push, create), and system endpoints.-- **Structured Outputs**: Powerful `SchemaBuilder` DSL (`|+`, `|++`, `|!`, `|!!`) for type-safe JSON Schema structured responses.+- **Structured Outputs DSL**: Powerful `SchemaBuilder` DSL (`|+`, `|++`, `|!`, `|!!`) for type-safe JSON Schema structured responses. - **Function / Tool Calling**: Full support for tool definitions (`Tool`), tool calls (`ToolCall`), and execution results (`toolResultMessage`). - **Thinking Models Support**: Native support for reasoning models (`qwen3.5`, `deepseek-r1`) with `Think` / `ThinkingLevel` types. - **Environment & Auth Integration**: Robust URL normalization for `OLLAMA_HOST` and bearer token support for `OLLAMA_API_KEY`.-- **Configurable Resilience**: Flexible retry policies (`NoRetry`, `ConstantRetry`, `ExponentialRetry`), lifecycle callbacks, and structured logging.+- **Configurable Resilience**: Flexible retry policies (`NoRetry`, `ConstantRetry`, `ExponentialRetry`), custom timeouts, lifecycle callbacks, and structured logging. - **Conversation Store**: Transactional STM-backed `InMemoryStore` and `ConversationStore` typeclass for managing multi-turn chat sessions. - **SDK Comparison Matrix**: Detailed feature comparison against Python, JS/TS, and Go SDKs in [doc/COMPARISON.md](doc/COMPARISON.md). @@ -27,14 +29,14 @@ ```cabal build-depends:     base >= 4.17 && < 5-  , ollama-haskell >= 0.3.0.0+  , ollama-haskell >= 0.4.0.0 ```  Or using Stack in `package.yaml`:  ```yaml dependencies:-  - ollama-haskell >= 0.3.0.0+  - ollama-haskell >= 0.4.0.0 ```  ---@@ -169,7 +171,6 @@ ## Documentation & SDK Comparison  - [doc/COMPARISON.md](doc/COMPARISON.md) — SDK Feature Matrix comparing `ollama-haskell` with Python, JS/TS, and Go SDKs.-- [ARCHITECTURE.md](ARCHITECTURE.md) — Detailed internal module design and extension guide. - [CONTRIBUTING.md](CONTRIBUTING.md) — Development setup, testing guidelines, and code style. - [CHANGELOG.md](CHANGELOG.md) — Release notes and changelog. - [Hackage Documentation](https://hackage.haskell.org/package/ollama-haskell) — Full Haddock reference.
− doc/COMPARISON.md
@@ -1,66 +0,0 @@-# SDK Feature & Architecture Comparison--This document provides a comprehensive technical comparison between **`ollama-haskell` (v0.3.0.0)**, official SDKs in other ecosystems (**Python**, **JavaScript/TypeScript**, **Go**), and existing Haskell LLM libraries.-------## 1. Feature Support Matrix Across Language SDKs--| Feature / Endpoint | `ollama-haskell` (v0.3.0.0) | `ollama-python` | `ollama-js` | `ollama/ollama/api` (Go) |-| :--- | :---: | :---: | :---: | :---: |-| **Language Paradigm** | Pure Functional / Typed | Dynamic / Async | Dynamic / Promises | Imperative / Structs |-| **Response Streaming** | `conduit` Pipelines | Iterator / AsyncGenerator | AsyncIterable | Channel / Callback |-| **Chat Completions (`/api/chat`)** | ✅ | ✅ | ✅ | ✅ |-| **Text Generation (`/api/generate`)** | ✅ | ✅ | ✅ | ✅ |-| **Thinking / Reasoning Models** | ✅ `Think` ADT (`qwen3.5`, `deepseek-r1`) | ⚠️ Raw JSON parameter | ⚠️ Raw JSON parameter | ⚠️ Raw JSON parameter |-| **Structured Output Schema** | ✅ Type-safe `SchemaBuilder` DSL | ⚠️ Pydantic / Raw Dict | ⚠️ Zod / Raw Schema | ⚠️ Struct tag / Raw Schema |-| **Function / Tool Calling** | ✅ Strongly-typed `Tool` ADT | ✅ Dict / Callables | ✅ JSON schema objects | ✅ Go Structs |-| **Vector Embeddings (`/api/embed`)** | ✅ | ✅ | ✅ | ✅ |-| **Model Lifecycle (`copy`, `delete`, `show`, `ps`)** | ✅ | ✅ | ✅ | ✅ |-| **Blob Management (`checkBlob`, `pushBlob`)** | ✅ | ✅ | ✅ | ✅ |-| **Automatic Retry Policy** | ✅ `NoRetry`, `Constant`, `Exponential` | ❌ (User implementation) | ❌ (User implementation) | ❌ (User implementation) |-| **Connection Lifecycles** | ✅ Automatic GC & Bracket | ⚠️ Manual session | ⚠️ Fetch client | ⚠️ Manual `http.Client` |-| **STM Session Management** | ✅ `InMemoryStore` & `ConversationStore` | ❌ | ❌ | ❌ |-| **Mock Testing Harness** | ✅ `Ollama.Testing` (`withMockClient`) | ❌ | ❌ | ❌ |-------## 2. Comparison with Other Haskell LLM Libraries--| Feature | `ollama-haskell` | `openai-hs` | `langchain-hs` |-| :--- | :--- | :--- | :--- |-| **Target Provider** | Local Ollama Engine | OpenAI Cloud API | Multi-provider Framework |-| **Streaming Abstraction** | First-class `conduit` streams | Lazy ByteString / SSE | Custom Stream types |-| **Retry & Resilience** | Integrated `retry` policy backoff | None | Basic |-| **Mocking & Testing** | Built-in `Ollama.Testing` module | None | Mock handlers |-| **Memory Persistence** | STM-backed `InMemoryStore` | Manual | Vector store abstractions |-------## 3. Key Architectural Advantages of `ollama-haskell`--1. **Type Safety & Smart Constructors**:-   - String parameters are wrapped in domain-specific newtypes (`ModelName`, `Digest`, `Base64Image`, `Duration`, `Version`).-   - `mkModelName` validates model name invariants at runtime.--2. **First-Class Streaming via `conduit`**:-   - `chatStream` and `generateStream` stream line-delimited JSON objects over constant memory pipelines without buffering entire responses in memory.-   - Stream combinators `collectStream` and `foldStream` simplify consumption.--3. **Domain-Specific Schema Builder (`SchemaBuilder`)**:-   - Construct complex JSON Schemas using readable infix operators:-     ```haskell-     userSchema = buildSchema $-       emptyObject-         |+ ("name", JString)-         |+ ("age", JInteger)-         |! "name"-     ```--4. **Built-In Resilient Transport**:-   - Exponential backoff retry strategies (`ExponentialRetry 3 1.0`).-   - Lifecycle callbacks (`configOnStart`, `configOnSuccess`, `configOnError`).-   - Customizable logger thresholds (`Debug`, `Info`, `Warn`, `Error`).--5. **Mock Infrastructure (`Ollama.Testing`)**:-   - Test application code deterministically offline using `withMockClient` without needing a running Ollama server during CI unit testing.
examples/StructuredOutput.hs view
@@ -1,16 +1,20 @@ module Main (main) where +import Data.Aeson (FromJSON, eitherDecode)+import Data.Text (Text) import Data.Text.IO qualified as TIO+import Data.Text.Lazy qualified as TL+import Data.Text.Lazy.Encoding qualified as TLE+import GHC.Generics (Generic) import Ollama-import Ollama.Types.Format.SchemaBuilder -personSchema :: Schema-personSchema =-  buildSchema $-    emptyObject-      |+ ("name", JString)-      |+ ("age", JInteger)-      |! "name"+-- | Define your type and derive 'ToSchema'. That's it — no manual schema needed.+data Person = Person+  { name :: Text+  , age :: Int+  }+  deriving stock (Generic, Show)+  deriving anyclass (FromJSON, ToSchema)  main :: IO () main = do@@ -18,11 +22,16 @@   let opts = Just (defaultOptions {optNumPredict = Just 20})       req =         (generateRequest "qwen3.5:2b" "Generate a person profile.")-          { genFormat = Just (SchemaFormat personSchema)+          { genFormat = Just (formatFor @Person)           , genOptions = opts           , genThink = Just ThinkDisabled           }   res <- generate client req   case res of     Left err -> putStrLn $ "Error: " <> show err-    Right resp -> TIO.putStrLn $ "Structured Response:\n" <> grResponse resp+    Right resp -> do+      TIO.putStrLn $ "Raw response:\n" <> grResponse resp+      -- Decode into our typed Person+      case eitherDecode (TLE.encodeUtf8 . TL.fromStrict $ grResponse resp) of+        Left decErr -> putStrLn $ "Decode error: " <> decErr+        Right person -> putStrLn $ "Parsed: " <> show (person :: Person)
ollama-haskell.cabal view
@@ -1,12 +1,13 @@ cabal-version: 3.0 name:          ollama-haskell-version:       0.3.0.1-synopsis:      Industry-grade Haskell client for the Ollama API+version:       0.4.0.0+synopsis:      Industry-grade Haskell client for Ollama local LLMs description:-  A type-safe, well-tested Haskell client for interacting with+  A type-safe Haskell client for interacting with   locally-running LLMs via the Ollama HTTP API. Supports chat,   text generation, embeddings, model management, streaming via-  conduit, structured outputs, tool calling, and more.+  conduit, structured outputs, tool calling, Model Context Protocol (MCP),+  and generic JSON schema derivation. category:      Web, AI, Network license:       MIT license-file:  LICENSE@@ -20,7 +21,6 @@ extra-doc-files:   README.md   CHANGELOG.md-  doc/COMPARISON.md  source-repository head   type: git@@ -42,6 +42,7 @@     -Wmissing-home-modules     -Wpartial-fields     -Wredundant-constraints+    -Wunused-packages  common lang   default-language: GHC2021@@ -85,11 +86,13 @@     Ollama.Types.Options     Ollama.Types.Format     Ollama.Types.Format.SchemaBuilder+    Ollama.Types.Format.SchemaDerive     Ollama.Types.Common     Ollama.Error     Ollama.Streaming     Ollama.Testing     Ollama.Conversation+    Ollama.MCP   other-modules:     Ollama.Client.Internal   build-depends:@@ -111,6 +114,7 @@     , text             >= 2.0 && < 3     , time             >= 1.11 && < 2     , unliftio-core    >= 0.2 && < 0.3+    , mcp-server       >= 0.2 && < 0.3  test-suite ollama-haskell-test   import: warnings, lang@@ -122,6 +126,7 @@     Test.Ollama.Unit.Error     Test.Ollama.Unit.Config     Test.Ollama.Unit.SchemaBuilder+    Test.Ollama.Unit.SchemaDerive     Test.Ollama.Unit.Testing     Test.Ollama.Property.Arbitrary     Test.Ollama.Property.Roundtrip@@ -129,12 +134,14 @@     Test.Ollama.Golden.Generate     Test.Ollama.Golden.Embed     Test.Ollama.Golden.Models+    Test.Ollama.Unit.MCP   build-depends:       base     , ollama-haskell     , aeson     , bytestring     , containers+    , mcp-server     , QuickCheck       >= 2.14     , tasty          >= 1.5     , tasty-golden   >= 2.3@@ -199,14 +206,14 @@   build-depends:       base     , ollama-haskell-    , text  executable ollama-example-structured-output   import: warnings, lang   main-is: StructuredOutput.hs   hs-source-dirs: examples   build-depends:-      base+      aeson+    , base     , ollama-haskell     , text @@ -217,7 +224,6 @@   build-depends:       base     , ollama-haskell-    , text  executable ollama-example-model-management   import: warnings, lang@@ -226,7 +232,6 @@   build-depends:       base     , ollama-haskell-    , text  executable ollama-example-all-features   import: warnings, lang@@ -235,7 +240,6 @@   build-depends:       base     , ollama-haskell-    , aeson     , text     , time 
src/Ollama.hs view
@@ -140,6 +140,10 @@   ModelOptions (..),   defaultOptions,   Format (..),+  ToSchema (..),+  ToJsonType (..),+  schemaFor,+  formatFor,    -- * Error Handling   OllamaError (..),@@ -168,6 +172,9 @@   loadConversationInMemory,   listConversationsInMemory,   deleteConversationInMemory,++  -- * Model Context Protocol (MCP) Integration+  module Ollama.MCP, ) where  import Ollama.API.Blobs@@ -184,6 +191,7 @@ import Ollama.Client.Config import Ollama.Conversation import Ollama.Error+import Ollama.MCP import Ollama.Streaming import Ollama.Testing import Ollama.Types
src/Ollama/Client/Config.hs view
@@ -8,7 +8,7 @@  Client configuration settings, retry policies, and logging thresholds. -@since 3.0.0.0+@since 0.3.0.0 -} module Ollama.Client.Config (   OllamaClientConfig (..),@@ -27,14 +27,14 @@  {- | Logging levels for structured client events. -@since 3.0.0.0+@since 0.3.0.0 -} data LogLevel = Debug | Info | Warn | Error   deriving stock (Eq, Ord, Show, Bounded, Enum)  {- | Configurable retry strategy for recoverable network errors. -@since 3.0.0.0+@since 0.3.0.0 -} data RetryPolicy   = -- | Disable all retries@@ -47,28 +47,28 @@  {- | Helper constructor for 'NoRetry'. -@since 3.0.0.0+@since 0.3.0.0 -} noRetry :: RetryPolicy noRetry = NoRetry  {- | Helper constructor for 'ConstantRetry'. -@since 3.0.0.0+@since 0.3.0.0 -} constantRetry :: Int -> Int -> RetryPolicy constantRetry = ConstantRetry  {- | Helper constructor for 'ExponentialRetry'. -@since 3.0.0.0+@since 0.3.0.0 -} exponentialRetry :: Int -> Int -> RetryPolicy exponentialRetry = ExponentialRetry -{- | Configuration settings for an 'OllamaClient'.+{- | Configuration settings for an 'Ollama.Client.OllamaClient'. -@since 3.0.0.0+@since 0.3.0.0 -} data OllamaClientConfig = OllamaClientConfig   { configBaseUrl :: !Text@@ -83,15 +83,15 @@   , configOnError :: !(Maybe (IO ()))   } -{- | Default configuration connecting to @http://127.0.0.1:11434@ with 90s timeout and 'NoRetry'.+{- | Default configuration connecting to @http://127.0.0.1:11434@ with 300s timeout and 'NoRetry'. -@since 3.0.0.0+@since 0.3.0.0 -} defaultConfig :: OllamaClientConfig defaultConfig =   OllamaClientConfig     { configBaseUrl = "http://127.0.0.1:11434"-    , configTimeout = 90+    , configTimeout = 300     , configRetry = NoRetry     , configManager = Nothing     , configHeaders = []
src/Ollama/Error.hs view
@@ -55,6 +55,7 @@ isRetryable :: OllamaError -> Bool isRetryable (HttpError _) = True isRetryable TimeoutError = True+isRetryable (ApiError status _) | status >= 500 = True isRetryable _ = False  {- | Helper to throw an 'OllamaError' as an exception.
+ src/Ollama/MCP.hs view
@@ -0,0 +1,260 @@+{-# LANGUAGE ExplicitNamespaces #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE RecordWildCards #-}++{- |+Module      : Ollama.MCP+Description : Model Context Protocol (MCP) tool conversion and server bridging for Ollama using mcp-server.+Copyright   : (c) 2024-2026 Tushar Adhatrao+License     : MIT+Maintainer  : tusharadhatrao@gmail.com+Stability   : stable+Portability : portable++Provides direct bidirectional conversion between Ollama function calling definitions ('Tool')+and Model Context Protocol ('ToolDefinition', 'Content', etc.) directly using the official+Hackage @mcp-server@ package.++@since 0.4.0.0+-}+module Ollama.MCP (+  -- * Re-exported MCP.Server Types & Functions+  Content (..),+  ContentImageData (..),+  ContentAudioData (..),+  ResourceContent (..),+  PromptDefinition (..),+  ResourceDefinition (..),+  ToolDefinition (..),+  ArgumentDefinition (..),+  type McpSchema,+  pattern McpSchema,+  SchemaType (..),+  schemaDescription,+  schemaShape,+  schema,+  describedSchema,+  mkToolDefinition,+  mkPromptDefinition,+  mkResourceDefinition,+  McpServerInfo (..),+  McpServerHandlers (..),+  ServerCapabilities (..),+  PromptCapabilities (..),+  ResourceCapabilities (..),+  ToolCapabilities (..),+  LoggingCapabilities (..),+  PromptListHandler,+  PromptGetHandler,+  ResourceListHandler,+  ResourceReadHandler,+  ToolListHandler,+  ToolCallHandler,+  PromptName,+  ToolName,+  ArgumentName,+  ArgumentValue,+  URI,+  parseURI,+  runMcpServerStdio,+  runMcpServerHttp,+  runMcpServerHttpWithConfig,+  HttpConfig (..),+  jsonValueToText,+  McpProtocolError,++  -- * Conversion between Ollama and mcp-server Types+  toolToMcpDefinition,+  mcpDefinitionToTool,+  toolCallToMcpArgs,+  mcpContentToToolOutput,+) where++import Data.Aeson (Value (..), encode)+import Data.Map.Strict qualified as Map+import Data.Maybe (fromMaybe)+import Data.Text (Text)+import Data.Text qualified as T+import Data.Text.Lazy qualified as TL+import Data.Text.Lazy.Encoding qualified as TEncoding+import MCP.Server (+  HttpConfig (..),+  runMcpServerHttp,+  runMcpServerHttpWithConfig,+  runMcpServerStdio,+ )+import MCP.Server.Types as ServerTypes (+  ArgumentDefinition (..),+  ArgumentName,+  ArgumentValue,+  Content (..),+  ContentAudioData (..),+  ContentImageData (..),+  Error (..),+  LoggingCapabilities (..),+  McpServerHandlers (..),+  McpServerInfo (..),+  PromptCapabilities (..),+  PromptDefinition (..),+  PromptGetHandler,+  PromptListHandler,+  PromptName,+  ResourceCapabilities (..),+  ResourceContent (..),+  ResourceDefinition (..),+  ResourceListHandler,+  ResourceReadHandler,+  Schema (..),+  SchemaType (..),+  ServerCapabilities (..),+  ToolCallHandler,+  ToolCapabilities (..),+  ToolDefinition (..),+  ToolListHandler,+  ToolName,+  URI,+  describedSchema,+  mkPromptDefinition,+  mkResourceDefinition,+  mkToolDefinition,+  parseURI,+  schema,+ )+import Ollama.Types.Tool (+  FunctionDef (..),+  FunctionParameters (..),+  Tool (..),+  ToolCall (..),+  ToolCallFunction (..),+ )++-- | Alias for 'MCP.Server.Types.Error' to prevent collisions with 'Ollama.Error'.+type McpProtocolError = ServerTypes.Error++-- | Alias for 'MCP.Server.Types.Schema' to avoid name collision with 'Ollama.Types.Format.SchemaBuilder.Schema'.+type McpSchema = ServerTypes.Schema++-- | Pattern synonym for matching or constructing an 'McpSchema' ('ServerTypes.Schema').+pattern McpSchema :: Maybe Text -> SchemaType -> McpSchema+pattern McpSchema desc shape = ServerTypes.Schema desc shape++{-# COMPLETE McpSchema #-}++-- | Convert an Aeson 'Value' to 'Text'. Strings are returned unquoted, while other JSON values are serialized.+jsonValueToText :: Value -> Text+jsonValueToText (String t) = t+jsonValueToText v = TL.toStrict (TEncoding.decodeUtf8 (encode v))++-- | Convert an Ollama 'Tool' into an MCP 'ToolDefinition' from @mcp-server@.+toolToMcpDefinition :: Tool -> ToolDefinition+toolToMcpDefinition Tool {toolFunction = FunctionDef {..}} =+  let desc = fromMaybe "" fnDescription+      convertParam :: FunctionParameters -> ServerTypes.Schema+      convertParam p =+        let pDesc = fpDescription p+            shape = case fpType p of+              "string" -> SchemaString (fpEnum p)+              "integer" -> SchemaInteger+              "number" -> SchemaNumber+              "boolean" -> SchemaBoolean+              "array" -> SchemaArray (ServerTypes.Schema Nothing (SchemaString Nothing))+              "object" ->+                let props = maybe [] (\pm -> [(k, convertParam v) | (k, v) <- Map.toList pm]) (fpProperties p)+                    req = fromMaybe [] (fpRequired p)+                 in SchemaObject props req+              _ -> SchemaString Nothing+         in ServerTypes.Schema pDesc shape+      inputSchema = case fnParameters of+        Just p -> convertParam p+        Nothing -> ServerTypes.Schema Nothing (SchemaObject [] [])+   in mkToolDefinition fnName desc inputSchema++-- | Convert an MCP 'ToolDefinition' from @mcp-server@ into an Ollama 'Tool'.+mcpDefinitionToTool :: ToolDefinition -> Tool+mcpDefinitionToTool ToolDefinition {..} =+  let convertSchema :: ServerTypes.Schema -> FunctionParameters+      convertSchema (ServerTypes.Schema mDesc shape) =+        case shape of+          SchemaString mEnum ->+            FunctionParameters+              { fpType = "string"+              , fpProperties = Nothing+              , fpRequired = Nothing+              , fpAdditionalProperties = Nothing+              , fpDescription = mDesc+              , fpEnum = mEnum+              }+          SchemaInteger ->+            FunctionParameters+              { fpType = "integer"+              , fpProperties = Nothing+              , fpRequired = Nothing+              , fpAdditionalProperties = Nothing+              , fpDescription = mDesc+              , fpEnum = Nothing+              }+          SchemaNumber ->+            FunctionParameters+              { fpType = "number"+              , fpProperties = Nothing+              , fpRequired = Nothing+              , fpAdditionalProperties = Nothing+              , fpDescription = mDesc+              , fpEnum = Nothing+              }+          SchemaBoolean ->+            FunctionParameters+              { fpType = "boolean"+              , fpProperties = Nothing+              , fpRequired = Nothing+              , fpAdditionalProperties = Nothing+              , fpDescription = mDesc+              , fpEnum = Nothing+              }+          SchemaArray _itemSchema ->+            FunctionParameters+              { fpType = "array"+              , fpProperties = Nothing+              , fpRequired = Nothing+              , fpAdditionalProperties = Nothing+              , fpDescription = mDesc+              , fpEnum = Nothing+              }+          SchemaObject props req ->+            let propMap = Map.fromList [(k, convertSchema s) | (k, s) <- props]+             in FunctionParameters+                  { fpType = "object"+                  , fpProperties = if Map.null propMap then Nothing else Just propMap+                  , fpRequired = if null req then Nothing else Just req+                  , fpAdditionalProperties = Nothing+                  , fpDescription = mDesc+                  , fpEnum = Nothing+                  }+      params = convertSchema toolDefinitionInputSchema+   in Tool+        { toolType = "function"+        , toolFunction =+            FunctionDef+              { fnName = toolDefinitionName+              , fnDescription = if T.null toolDefinitionDescription then Nothing else Just toolDefinitionDescription+              , fnParameters = Just params+              , fnStrict = Nothing+              }+        }++-- | Convert an Ollama 'ToolCall' into an MCP tool name and arguments list for @mcp-server@.+toolCallToMcpArgs :: ToolCall -> (Text, [(Text, Text)])+toolCallToMcpArgs (ToolCall (ToolCallFunction name args)) =+  (name, [(k, jsonValueToText v) | (k, v) <- Map.toList args])++-- | Extract textual output from an MCP 'Content' (from @mcp-server@).+mcpContentToToolOutput :: Content -> Text+mcpContentToToolOutput (ContentText t) = t+mcpContentToToolOutput (ContentImage (ContentImageData _ mime)) = "[Image: " <> mime <> "]"+mcpContentToToolOutput (ContentAudio (ContentAudioData _ mime)) = "[Audio: " <> mime <> "]"+mcpContentToToolOutput (ContentEmbeddedResource res) =+  "[Resource: " <> T.pack (show (resourceUri res)) <> "]"+mcpContentToToolOutput (ContentResourceLink resDef) =+  "[Resource: " <> resourceDefinitionURI resDef <> "]"+mcpContentToToolOutput (ContentAnnotated _ inner) = mcpContentToToolOutput inner
src/Ollama/Types/Format.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+ {- | Module      : Ollama.Types.Format Copyright   : (c) 2024-2026 Tushar Adhatrao@@ -12,11 +14,14 @@ -} module Ollama.Types.Format (   Format (..),+  formatFor,   module Ollama.Types.Format.SchemaBuilder,+  module Ollama.Types.Format.SchemaDerive, ) where  import Data.Aeson import Ollama.Types.Format.SchemaBuilder+import Ollama.Types.Format.SchemaDerive  {- | Response output format hint. @@ -36,3 +41,16 @@ instance FromJSON Format where   parseJSON (String "json") = pure JsonFormat   parseJSON v = SchemaFormat <$> parseJSON v++{- | Produce a 'Format' value suitable for the @genFormat@ \/ @chatFormat@+request fields.++@+req = (generateRequest model prompt)+        { genFormat = Just ('formatFor' \@Person) }+@++@since 0.4.0.0+-}+formatFor :: forall a. (ToSchema a) => Format+formatFor = SchemaFormat (schemaFor @a)
+ src/Ollama/Types/Format/SchemaDerive.hs view
@@ -0,0 +1,267 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE UndecidableInstances #-}++{- |+Module      : Ollama.Types.Format.SchemaDerive+Copyright   : (c) 2024-2026 Tushar Adhatrao+License     : MIT+Maintainer  : tusharadhatrao@gmail.com+Stability   : experimental+Portability : portable++Generic derivation of JSON 'Schema' from Haskell record types.++This module provides the 'ToSchema' typeclass, which can automatically+derive a JSON Schema ('Schema') from any Haskell data type that has a+'GHC.Generics.Generic' instance.  This eliminates the need to manually+construct schemas using the 'Ollama.Types.Format.SchemaBuilder.SchemaBuilder' DSL when working with+Ollama's structured output API.++== Usage++Simply derive 'Generic' and declare a 'ToSchema' instance:++@+data Person = Person+  { name :: Text+  , age  :: Int+  } deriving stock ('GHC.Generics.Generic', Show)+    deriving anyclass ('ToSchema')++\-\- Use it:+\-\- >>> 'schemaFor' \@Person+\-\- >>> 'Ollama.Types.Format.formatFor' \@Person+@++== Supported types++* __Record types__: All fields become properties; non‑'Maybe' fields+  are marked as required.+* __'Maybe' fields__: Present in the schema properties but excluded+  from the @required@ array.+* __Nested records__: Recursively derived as nested @object@ schemas.+* __Lists__: Mapped to @array@ schemas.+* __Simple enums__: Sum types with all nullary constructors are+  represented as @{\"type\": \"string\", \"enum\": [...]}@.++@since 0.4.0.0+-}+module Ollama.Types.Format.SchemaDerive (+  -- * Typeclass+  ToSchema (..),+  ToJsonType (..),++  -- * Convenience functions+  schemaFor,++  -- * Generic Machinery (internal)+  GToSchema (..),+  GCollectFields (..),+  GEnumConstructors (..),+  GToSchemaDispatch (..),+) where++import Data.Int (Int16, Int32, Int64, Int8)+import Data.Kind (Type)+import Data.Map.Strict qualified as Map+import Data.Proxy (Proxy (..))+import Data.Text (Text)+import Data.Text qualified as T+import Data.Word (Word16, Word32, Word64, Word8)+import GHC.Generics+import GHC.TypeLits (KnownSymbol, symbolVal)+import Ollama.Types.Format.SchemaBuilder (JsonType (..), Property (..), Schema (..))++-- ---------------------------------------------------------------------------+-- Public API+-- ---------------------------------------------------------------------------++{- | Typeclass for types whose structure can be represented as a JSON 'Schema'.++A default implementation is provided via @GHC.Generics@, so you can derive+it for any record type that has a 'Generic' instance:++@+data MyType = MyType { field1 :: Text, field2 :: Int }+  deriving stock ('Generic')+  deriving anyclass ('ToSchema')+@++@since 0.4.0.0+-}+class ToSchema a where+  {- | Produce the JSON 'Schema' for type @a@.++  @since 0.4.0.0+  -}+  toSchema :: Schema+  default toSchema :: (GToSchema (Rep a)) => Schema+  toSchema = gToSchema @(Rep a)++{- | Convenience alias for @'toSchema' \@a@.++@since 0.4.0.0+-}+schemaFor :: forall a. (ToSchema a) => Schema+schemaFor = toSchema @a++{- | Map a Haskell type to its JSON Schema 'JsonType'.++Instances are provided for common primitive types ('Text', 'Int', 'Bool',+'Double', etc.), 'Maybe', lists, and any type with a 'ToSchema' instance+(which maps to a nested @object@ schema).++For nested record types, you don't need to write an instance manually —+simply ensure the nested type has a 'ToSchema' instance and the default+method will handle it:++@+data Address = Address { city :: Text, zip :: Text }+  deriving stock ('Generic')+  deriving anyclass ('ToSchema', 'ToJsonType')+@++@since 0.4.0.0+-}+class ToJsonType a where+  toJsonType :: JsonType+  default toJsonType :: (ToSchema a) => JsonType+  toJsonType = JObject (toSchema @a)++-- ---------------------------------------------------------------------------+-- GHC.Generics machinery (internal)+-- ---------------------------------------------------------------------------++-- | Walk a generic representation to produce a 'Schema'.+class GToSchema (f :: Type -> Type) where+  gToSchema :: Schema++-- | Collect fields (properties + required list) from a generic representation.+class GCollectFields (f :: Type -> Type) where+  gCollectFields :: ([(Text, Property)], [Text])++-- | Collect constructor names from a sum type for enum schemas.+class GEnumConstructors (f :: Type -> Type) where+  gEnumConstructors :: [Text]++-- ---------------------------------------------------------------------------+-- Datatype / Constructor / Sum dispatch+-- ---------------------------------------------------------------------------++-- | Strip datatype metadata wrapper.+instance (GToSchemaDispatch f) => GToSchema (M1 D meta f) where+  gToSchema = gToSchemaDispatch @f++-- | Dispatch: determine if this is a single-constructor record or a sum type.+class GToSchemaDispatch (f :: Type -> Type) where+  gToSchemaDispatch :: Schema++-- | Single constructor → collect fields into an object schema.+instance (GCollectFields f) => GToSchemaDispatch (M1 C meta f) where+  gToSchemaDispatch =+    let (props, req) = gCollectFields @f+     in Schema (Map.fromList props) req++{- | Sum type → try to build an enum schema.+We treat all-nullary sum types as string enums.+-}+instance (GEnumConstructors f, GEnumConstructors g) => GToSchemaDispatch (f :+: g) where+  gToSchemaDispatch =+    let constructors = gEnumConstructors @f <> gEnumConstructors @g+     in Schema Map.empty constructors++-- ---------------------------------------------------------------------------+-- Constructor / field collection+-- ---------------------------------------------------------------------------++-- | Strip constructor metadata wrapper.+instance (GCollectFields f) => GCollectFields (M1 C meta f) where+  gCollectFields = gCollectFields @f++-- | Product of fields: combine both sides.+instance (GCollectFields f, GCollectFields g) => GCollectFields (f :*: g) where+  gCollectFields =+    let (ps1, rs1) = gCollectFields @f+        (ps2, rs2) = gCollectFields @g+     in (ps1 <> ps2, rs1 <> rs2)++-- | Unit constructor (no fields).+instance GCollectFields U1 where+  gCollectFields = ([], [])++-- | Record field with a 'Maybe' type (optional — not required).+instance+  {-# OVERLAPPING #-}+  (KnownSymbol name, ToJsonType a) =>+  GCollectFields (M1 S ('MetaSel ('Just name) su ss ds) (K1 R (Maybe a)))+  where+  gCollectFields =+    let fieldName = T.pack $ symbolVal (Proxy @name)+        prop = Property (toJsonType @a)+     in ([(fieldName, prop)], [])++-- | Record field with a non-'Maybe' type (required).+instance+  {-# OVERLAPPABLE #-}+  (KnownSymbol name, ToJsonType a) =>+  GCollectFields (M1 S ('MetaSel ('Just name) su ss ds) (K1 R a))+  where+  gCollectFields =+    let fieldName = T.pack $ symbolVal (Proxy @name)+        prop = Property (toJsonType @a)+     in ([(fieldName, prop)], [fieldName])++-- ---------------------------------------------------------------------------+-- ToJsonType instances+-- ---------------------------------------------------------------------------++-- Strings+instance ToJsonType Text where toJsonType = JString+instance ToJsonType String where toJsonType = JString++-- Integers+instance ToJsonType Int where toJsonType = JInteger+instance ToJsonType Int8 where toJsonType = JInteger+instance ToJsonType Int16 where toJsonType = JInteger+instance ToJsonType Int32 where toJsonType = JInteger+instance ToJsonType Int64 where toJsonType = JInteger+instance ToJsonType Integer where toJsonType = JInteger+instance ToJsonType Word where toJsonType = JInteger+instance ToJsonType Word8 where toJsonType = JInteger+instance ToJsonType Word16 where toJsonType = JInteger+instance ToJsonType Word32 where toJsonType = JInteger+instance ToJsonType Word64 where toJsonType = JInteger++-- Floating point+instance ToJsonType Double where toJsonType = JNumber+instance ToJsonType Float where toJsonType = JNumber++-- Boolean+instance ToJsonType Bool where toJsonType = JBoolean++-- Maybe: unwrap to the inner type+instance (ToJsonType a) => ToJsonType (Maybe a) where+  toJsonType = toJsonType @a++-- Lists / arrays+instance (ToJsonType a) => ToJsonType [a] where+  toJsonType = JArray (toJsonType @a)++-- ---------------------------------------------------------------------------+-- Enum constructors+-- ---------------------------------------------------------------------------++-- | Sum of two branches.+instance (GEnumConstructors f, GEnumConstructors g) => GEnumConstructors (f :+: g) where+  gEnumConstructors = gEnumConstructors @f <> gEnumConstructors @g++-- | A nullary constructor (unit): extract constructor name.+instance (KnownSymbol name) => GEnumConstructors (M1 C ('MetaCons name fx 'False) U1) where+  gEnumConstructors = [T.toLower . T.pack $ symbolVal (Proxy @name)]++{- | A constructor with fields — not a simple enum. Produce a type error+at the instance level by not providing an instance (compile-time failure).+Users will get "No instance for GEnumConstructors ..." which is clear enough.+-}
test/Main.hs view
@@ -7,7 +7,9 @@ import Test.Ollama.Property.Roundtrip qualified as PropertyRoundtrip import Test.Ollama.Unit.Config qualified as UnitConfig import Test.Ollama.Unit.Error qualified as UnitError+import Test.Ollama.Unit.MCP qualified as UnitMCP import Test.Ollama.Unit.SchemaBuilder qualified as UnitSchemaBuilder+import Test.Ollama.Unit.SchemaDerive qualified as UnitSchemaDerive import Test.Ollama.Unit.Testing qualified as UnitTesting import Test.Ollama.Unit.Types qualified as UnitTypes import Test.Tasty@@ -20,7 +22,9 @@     , UnitError.tests     , UnitConfig.tests     , UnitSchemaBuilder.tests+    , UnitSchemaDerive.tests     , UnitTesting.testingTests+    , UnitMCP.tests     , PropertyRoundtrip.tests     , GoldenChat.tests     , GoldenGenerate.tests
test/Test/Ollama/Unit/Config.hs view
@@ -10,7 +10,7 @@     "Unit Config Tests"     [ testCase "defaultConfig settings" $ do         assertEqual "Default base URL" "http://127.0.0.1:11434" (configBaseUrl defaultConfig)-        assertEqual "Default timeout" 90 (configTimeout defaultConfig)+        assertEqual "Default timeout" 300 (configTimeout defaultConfig)         assertEqual "Default retry policy" NoRetry (configRetry defaultConfig)     , testCase "RetryPolicy smart constructors" $ do         assertEqual "noRetry" NoRetry noRetry
+ test/Test/Ollama/Unit/MCP.hs view
@@ -0,0 +1,100 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++module Test.Ollama.Unit.MCP (tests) where++import Data.Map.Strict qualified as Map+import MCP.Server.Types (+  Content (..),+  ContentImageData (..),+  ResourceContent (..),+  ToolDefinition (..),+  describedSchema,+  mkToolDefinition,+  parseURI,+  schema,+ )+import Ollama.MCP (+  SchemaType (..),+  mcpContentToToolOutput,+  mcpDefinitionToTool,+  schemaDescription,+  schemaShape,+  toolCallToMcpArgs,+  toolToMcpDefinition,+ )+import Ollama.Types.Tool (+  FunctionDef (..),+  FunctionParameters (..),+  Tool (..),+  ToolCall (..),+  ToolCallFunction (..),+ )+import Test.Tasty+import Test.Tasty.HUnit++tests :: TestTree+tests =+  testGroup+    "Unit MCP Integration Tests (mcp-server)"+    [ testCase "toolToMcpDefinition converts Ollama Tool to mcp-server ToolDefinition" $ do+        let propMap =+              Map.fromList+                [ ("query", FunctionParameters "string" Nothing Nothing Nothing (Just "Search query") Nothing)+                , ("limit", FunctionParameters "integer" Nothing Nothing Nothing (Just "Max results") Nothing)+                ]+            params = FunctionParameters "object" (Just propMap) (Just ["query"]) Nothing Nothing Nothing+            ollamaTool = Tool "function" $ FunctionDef "search" (Just "Search codebase") (Just params) Nothing+            mcpDef = toolToMcpDefinition ollamaTool++        toolDefinitionName mcpDef @?= "search"+        toolDefinitionDescription mcpDef @?= "Search codebase"+        case schemaShape (toolDefinitionInputSchema mcpDef) of+          SchemaObject props req -> do+            req @?= ["query"]+            length props @?= 2+            case lookup "query" props of+              Just s -> do+                schemaDescription s @?= Just "Search query"+                schemaShape s @?= SchemaString Nothing+              Nothing -> assertFailure "Expected query in props"+          _ -> assertFailure "Expected SchemaObject"+    , testCase "mcpDefinitionToTool converts mcp-server ToolDefinition to Ollama Tool" $ do+        let props =+              [ ("path", describedSchema "File path" (SchemaString Nothing))+              , ("content", describedSchema "File content" (SchemaString Nothing))+              ]+            s = schema (SchemaObject props ["path", "content"])+            mcpDef = mkToolDefinition "write_file" "Write file to disk" s+            ollamaTool = mcpDefinitionToTool mcpDef++        toolType ollamaTool @?= "function"+        let fn = toolFunction ollamaTool+        fnName fn @?= "write_file"+        fnDescription fn @?= Just "Write file to disk"+        case fnParameters fn of+          Just FunctionParameters {..} -> do+            fpType @?= "object"+            fpRequired @?= Just ["path", "content"]+            case fpProperties of+              Just pm -> Map.member "path" pm @?= True+              Nothing -> assertFailure "Expected properties"+          Nothing -> assertFailure "Expected parameters"+    , testCase "toolCallToMcpArgs converts Ollama ToolCall to mcp-server argument pairs" $ do+        let call = ToolCall (ToolCallFunction "greet" (Map.fromList [("name", "Alice")]))+            (fn, args) = toolCallToMcpArgs call+        fn @?= "greet"+        lookup "name" args @?= Just "Alice"+    , testCase "mcpContentToToolOutput extracts text from mcp-server Content" $ do+        let textContent = ContentText "Execution output"+        mcpContentToToolOutput textContent @?= "Execution output"++        let imgContent = ContentImage (ContentImageData "base64..." "image/png")+        mcpContentToToolOutput imgContent @?= "[Image: image/png]"++        case parseURI "file:///workspace/test.txt" of+          Just uri -> do+            let resContent = ContentEmbeddedResource (ResourceText uri "text/plain" "file contents")+            mcpContentToToolOutput resContent @?= "[Resource: file:///workspace/test.txt]"+          Nothing -> assertFailure "Failed to parse URI"+    ]
+ test/Test/Ollama/Unit/SchemaDerive.hs view
@@ -0,0 +1,170 @@+module Test.Ollama.Unit.SchemaDerive (tests) where++import Data.Map.Strict qualified as Map+import Data.Text (Text)+import GHC.Generics (Generic)+import Ollama.Types.Format+import Test.Tasty+import Test.Tasty.HUnit++-- ---------------------------------------------------------------------------+-- Test types+-- ---------------------------------------------------------------------------++data SimplePerson = SimplePerson+  { name :: Text+  , age :: Int+  }+  deriving stock (Generic)+  deriving anyclass (ToSchema)++data PersonWithOptional = PersonWithOptional+  { personName :: Text+  , personAge :: Int+  , personNickname :: Maybe Text+  }+  deriving stock (Generic)+  deriving anyclass (ToSchema)++data Address = Address+  { city :: Text+  , zipCode :: Text+  }+  deriving stock (Generic)+  deriving anyclass (ToSchema, ToJsonType)++data PersonWithAddress = PersonWithAddress+  { fullName :: Text+  , homeAddress :: Address+  }+  deriving stock (Generic)+  deriving anyclass (ToSchema)++data PersonWithHobbies = PersonWithHobbies+  { hobbyName :: Text+  , hobbies :: [Text]+  }+  deriving stock (Generic)+  deriving anyclass (ToSchema)++data Color = Red | Green | Blue+  deriving stock (Generic)+  deriving anyclass (ToSchema)++data PersonWithMaybeInt = PersonWithMaybeInt+  { pmName :: Text+  , pmScore :: Maybe Int+  }+  deriving stock (Generic)+  deriving anyclass (ToSchema)++-- ---------------------------------------------------------------------------+-- Tests+-- ---------------------------------------------------------------------------++tests :: TestTree+tests =+  testGroup+    "Unit SchemaDerive Tests"+    [ testSimpleRecord+    , testOptionalFields+    , testNestedRecord+    , testArrayFields+    , testEnumType+    , testMaybeUnwrapsType+    , testSchemaForAlias+    , testFormatFor+    , testMatchesManualSchema+    ]++testSimpleRecord :: TestTree+testSimpleRecord = testCase "Simple record produces correct schema" $ do+  let schema = schemaFor @SimplePerson+      props = schemaProperties schema+      req = schemaRequired schema+  assertEqual "Has 'name' property" (Just (Property JString)) (Map.lookup "name" props)+  assertEqual "Has 'age' property" (Just (Property JInteger)) (Map.lookup "age" props)+  assertEqual "Has 2 properties" 2 (Map.size props)+  assertBool "'name' is required" ("name" `elem` req)+  assertBool "'age' is required" ("age" `elem` req)+  assertEqual "2 required fields" 2 (length req)++testOptionalFields :: TestTree+testOptionalFields = testCase "Maybe fields are not required" $ do+  let schema = schemaFor @PersonWithOptional+      props = schemaProperties schema+      req = schemaRequired schema+  assertEqual "Has 3 properties" 3 (Map.size props)+  assertEqual+    "'personNickname' maps to JString"+    (Just (Property JString))+    (Map.lookup "personNickname" props)+  assertBool "'personName' is required" ("personName" `elem` req)+  assertBool "'personAge' is required" ("personAge" `elem` req)+  assertBool "'personNickname' is NOT required" ("personNickname" `notElem` req)+  assertEqual "2 required fields" 2 (length req)++testNestedRecord :: TestTree+testNestedRecord = testCase "Nested record becomes JObject" $ do+  let schema = schemaFor @PersonWithAddress+      props = schemaProperties schema+  case Map.lookup "homeAddress" props of+    Just (Property (JObject innerSchema)) -> do+      let innerProps = schemaProperties innerSchema+      assertEqual "Inner has 'city'" (Just (Property JString)) (Map.lookup "city" innerProps)+      assertEqual "Inner has 'zipCode'" (Just (Property JString)) (Map.lookup "zipCode" innerProps)+    other -> assertFailure $ "Expected JObject for homeAddress, got: " <> show other++testArrayFields :: TestTree+testArrayFields = testCase "List fields become JArray" $ do+  let schema = schemaFor @PersonWithHobbies+      props = schemaProperties schema+  assertEqual+    "'hobbies' maps to JArray JString"+    (Just (Property (JArray JString)))+    (Map.lookup "hobbies" props)++testEnumType :: TestTree+testEnumType = testCase "Simple enum produces schema with constructor names" $ do+  let schema = schemaFor @Color+      req = schemaRequired schema+  -- Enum constructors are stored in required list (as enum values)+  assertBool "Contains 'red'" ("red" `elem` req)+  assertBool "Contains 'green'" ("green" `elem` req)+  assertBool "Contains 'blue'" ("blue" `elem` req)+  assertEqual "3 enum values" 3 (length req)+  assertEqual "No properties" 0 (Map.size $ schemaProperties schema)++testMaybeUnwrapsType :: TestTree+testMaybeUnwrapsType = testCase "Maybe Int field maps to JInteger" $ do+  let schema = schemaFor @PersonWithMaybeInt+      props = schemaProperties schema+  assertEqual "'pmScore' maps to JInteger" (Just (Property JInteger)) (Map.lookup "pmScore" props)++testSchemaForAlias :: TestTree+testSchemaForAlias = testCase "schemaFor is equivalent to toSchema" $ do+  let s1 = schemaFor @SimplePerson+      s2 = toSchema @SimplePerson+  assertEqual "schemaFor == toSchema" s1 s2++testFormatFor :: TestTree+testFormatFor = testCase "formatFor wraps in SchemaFormat" $ do+  let fmt = formatFor @SimplePerson+      expected = SchemaFormat (schemaFor @SimplePerson)+  assertEqual "formatFor wraps schema" expected fmt++testMatchesManualSchema :: TestTree+testMatchesManualSchema = testCase "Derived schema matches manual DSL schema" $ do+  let derived = schemaFor @SimplePerson+      manual =+        buildSchema $+          emptyObject+            |+ ("name", JString)+            |+ ("age", JInteger)+            |!! ["name", "age"]+  assertEqual "Properties match" (schemaProperties manual) (schemaProperties derived)+  -- Required fields should contain the same elements (order may differ)+  let derivedReq = schemaRequired derived+      manualReq = schemaRequired manual+  assertBool "All derived required fields in manual" (all (`elem` manualReq) derivedReq)+  assertBool "All manual required fields in derived" (all (`elem` derivedReq) manualReq)