# NOTICE
This file is kept for brevity - but served as my starting specification before writing the library. This may no longer be useful and will be superceded by examples in the examples/ folder.
# Haskell MCP Server Library
Fully featured haskell library for building MCP Servers.
## MCP Specification - Model Context Protocol
The MCP spec is a specialisation of the JSON-RPC specification, exposing a number of requests and responses. This library allows developers to build these servers easily in haskell.
This library is compliant with the following MCP specifications for building MCP Servers which can be found here:
| Item | Spec URL |
| Overview | https://modelcontextprotocol.io/specification/2024-11-05/server |
| Prompts | https://modelcontextprotocol.io/specification/2024-11-05/server/prompts |
| Resources | https://modelcontextprotocol.io/specification/2024-11-05/server/resources |
| Tools | https://modelcontextprotocol.io/specification/2024-11-05/server/tools |
# Library itself
Supports 2 levels of granularity:
## 1. Lowest-level fine grained handling of Prompts, Resources and Tools, e.g.:
Using an example of a grocery shopping MCP server:
```haskell
import MCP.Server.Types (Content(ContentText, ContentImage), Error(InvalidPromptName, MissingRequiredParams, ResourceNotFound, InternalError, UnknownTool), PromptDefinition, ResourceDefinition, ArgumentDefinition, InputSchemaDefinition(..), PaginatedResult(..), Cursor)
import Network.URI (URI(URI))
import qualified Data.Map as Map
type PromptName = Text
type ToolName = Text
type ArgumentName = Text
type ArgumentValue = Text
handleListPrompts :: (Monad m) => Maybe Cursor -> m (PaginatedResult [PromptDefinition])
handleListPrompts cursor =
pure $ PaginatedResult
{ paginatedItems =
[ PromptDefinition
{ promptDefinitionName = "recipe"
, promptDefinitionDescription = "Generate a detailed recipe for a particular idea"
, promptDefinitionArguments =
[ ArgumentDefinition
{ argumentDefinitionName = "idea"
, argumentDefinitionDescription = "inspiring idea for the recipe"
, argumentDefinitionRequired = True
}
]
}
]
, paginatedNextCursor = Nothing -- No more pages
}
handleListResources :: (Monad m) => Maybe Cursor -> m (PaginatedResult [ResourceDefinition])
handleListResources cursor =
pure $ PaginatedResult
{ paginatedItems =
[ ResourceDefinition
{ resourceDefinitionURI = "file:///product_categories"
, resourceDefinitionName = "product_categories"
, resourceDefinitionDescription = "List of product categories"
, resourceDefinitionMimeType = "text/plain"
}
]
, paginatedNextCursor = Nothing -- No more pages
}
handleListTools :: (Monad m) => Maybe Cursor -> m (PaginatedResult [ToolDefinition])
handleListTools cursor =
pure $ PaginatedResult
{ paginatedItems =
[ ToolDefinition
{ toolDefinitionName = "search_for_product"
, toolDefinitionDescription = "Search for products in the catalog"
, toolDefinitionInputSchema =
InputSchemaDefinitionObject
{ properties =
[ ("q"
, InputSchemaDefinitionProperty
{ type = "string"
, description = "Matching this query, using 'contains' semantics"
}
)
, ("category"
, InputSchemaDefinitionProperty
{ type = "string"
, description = "Limit to searching within this category (optional)"
}
)
]
, required = ["q"]
}
}
]
, paginatedNextCursor = Nothing -- No more pages
}
handleGetPrompt :: (Monad m) => PromptName -> [(ArgumentName, ArgumentValue)] -> m (Either Error Content)
handleGetPrompt "recipe" args = do
let idea = Map.lookup "idea" $ Map.fromList args -- idea is required
in case idea of
Just idea' -> pure $ Right $ ContentText $ "Recipe prompt for " <> idea' <> " ..."
Nothing -> pure $ Left $ MissingRequiredParams "field 'idea' is missing"
handleGetPrompt prompt _ = pure $ Left $ InvalidPromptName $ prompt <> " unknown!"
handleReadResource :: (Monad m) => URI -> m (Either Error Content)
handleReadResource uri = do
case uriPath uri of
"/product_categories" -> pure $ Right $ ContentText $ "category 1, category 2"
unknown -> pure $ Left $ ResourceNotFound $ "Resouce " <> unknown <> " not found!"
handleCallTool :: (Monad m) => ToolName -> [(ArgumentName, ArgumentValue)] -> m (Either Error Content)
handleCallTool "search_for_product" args = do
let q = Map.lookup "q" $ Map.fromList args -- q is required
let category = Map.lookup "category" $ Map.fromList args -- category is optional
in case q of
Nothing -> pure $ Left $ MissingRequiredParams "field 'q' is missing"
Just q' -> pure $ Right $ ContentText $ "product 1, product 2"
handleCallTool tool _ = pure $ Left $ UnknownTool $ tool <> " unknown!"
-- Running the MCP server using STDIO
main :: IO ()
main =
runMcpServerStdIn
McpServerInfo
{ serverName = "Products available at your Grocery Store"
, serverVersion = "0.1.0"
, serverInstructions = "Use these tools to fetch information about products available at your grocery store, including prices, ingredients, descriptions etc."
}
McpServerHandlers
{ prompts = Just (handleListPrompts, handleGetPrompt)
, resources = Just (handleListResources, handleReadResource)
, tools = Just (handleListTools, handleCallTool)
}
```
## 2. High-level derived interfaces for simpler definitions
High level MCP server definitions take advantage of Template Haskell for generating all the boilerplate of the low-level definitions, but allow for much simpler and type safe definitions such as in the example below.
```haskell
import MCP.Server.Types (Content(ContentText, ContentImage))
data MyPrompt
= Recipe { idea :: Text }
| Shopping { description :: Text }
data MyResource
= ProductCategories
| SaleItems
| HeadlineBannerAd
data MyTool
= SearchForProduct { q :: Text, category :: Maybe Text }
| AddToCart { sku :: Text }
| Checkout
handlePrompt :: (MonadIO m) => MyPrompt -> m Content
handlePrompt (Recipe idea) =
pure $ ContentText "Recipe prompt goes here..."
handlePrompt (Shopping description) =
pure $ ContentText "Shopping prompt goes here..."
handleResource :: (MonadIO m) => MyResource -> m Content
handleResource ProductCategories = pure $ ContentText "category 1, category 2"
handleResource SaleItems = pure $ ContentText "item 1, item 2, item 3"
handleResource HeadlineBannerAd = pure $ ContentImage { mimeType = "image/png", data = "..." }
handleTool :: (MonadIO m) => MyTool -> m Content
handleTool (SearchForProduct q category) = pure $ ContentText "search results..."
handleTool (AddToCart sku) = pure $ ContentText "Item added to cart!"
handleTool Checkout = pure $ ContentText "checkout completed!"
main =
-- Derive the handlers
let prompts = $(derivePromptHandler ''MyPrompt 'handlePrompt)
resources = $(deriveResourceHandler ''MyResource 'handleResource)
tools = $(deriveToolHandler ''MyTool 'handleTool)
in runMcpServerStdIn
McpServerInfo
{ serverName = "Products available at your Grocery Store"
, serverVersion = "0.1.0"
, serverInstructions = "Use these tools to fetch information about products available at your grocery store, including prices, ingredients, descriptions etc."
}
McpServerHandlers
{ prompts = Just prompts
, resources = Just resources
, tools = Just tools
}
```