packages feed

mcp-server-0.1.0.0: SPEC.md

# 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
            }

```