mcp-server: Library for building Model Context Protocol (MCP) servers

[ bsd3, json-rpc, library, mcp, network, program, server, service ] [ Propose Tags ] [ Report a vulnerability ]
This version is deprecated.

A fully featured library for building MCP (Model Context Protocol) servers. Supports both low-level fine-grained handling and high-level derived interfaces for prompts, resources, and tools. Includes JSON-RPC transport and stdin/stdout communication.


[Skip to Readme]

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

Versions [RSS] 0.1.0.0, 0.1.0.1, 0.1.0.2, 0.1.0.3, 0.1.0.4, 0.1.0.5, 0.1.0.6, 0.1.0.7, 0.1.0.8, 0.1.0.9, 0.1.0.10, 0.1.0.11, 0.1.0.12, 0.1.0.13, 0.1.0.14, 0.1.0.15, 0.1.0.16, 0.1.0.17, 0.1.0.18, 0.1.0.19, 0.1.0.20, 0.1.0.21, 0.2.0.0, 0.2.0.1 (info)
Change log CHANGELOG.md
Dependencies aeson (>=2 && <3), base (>=4.18 && <4.23), base64-bytestring (>=1.0 && <1.3), bytestring (>=0.10 && <0.13), containers (>=0.6 && <0.9), http-types (>=0.12 && <1), mcp-server, network-uri (>=2.6 && <2.8), scientific (>=0.3 && <0.4), stm (>=2.5 && <2.6), template-haskell (>=2.16 && <2.25), text (>=1.2 && <3), wai (>=3.2 && <4), warp (>=3.3 && <4) [details]
Tested with ghc ==9.6.7 || ==9.8.4 || ==9.10.3 || ==9.12.2 || ==9.14.1
License BSD-3-Clause
Copyright 2025 Tom Wells
Author Tom Wells
Maintainer drshade@gmail.com
Uploaded by drshade at 2026-08-01T07:10:34Z
Category Network, Server, Service, MCP, JSON-RPC
Home page https://github.com/drshade/haskell-mcp-server
Bug tracker https://github.com/drshade/haskell-mcp-server/issues
Source repo head: git clone https://github.com/drshade/haskell-mcp-server.git
Distributions LTSHaskell:0.1.0.21, Stackage:0.2.0.1
Reverse Dependencies 1 direct, 0 indirect [details]
Executables http-simple-example, complete-example, simple-example
Downloads 680 total (76 in the last 30 days)
Rating 2.0 (votes: 1) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs uploaded by user
Build status unknown [no reports yet]

Readme for mcp-server-0.2.0.0

[back to package description]

mcp-server

A fully-featured Haskell library for building Model Context Protocol (MCP) servers.

Features

  • Complete MCP Implementation: Dual-era server — legacy revisions 2024-11-05 through 2025-11-25 via the initialize handshake, and the stateless 2026-07-28 revision via per-request _meta (including server/discover, resultType, and cacheability fields)
  • Type-Safe API: Leverage Haskell's type system for robust MCP servers
  • Multiple Abstractions: Both low-level fine-grained control and high-level derived interfaces
  • Template Haskell Support: Automatic handler derivation from data types
  • Multiple Transports: STDIO and HTTP Streaming transport (MCP Streamable HTTP)

Supported MCP Features

  • Prompts: User-controlled prompt templates with arguments
  • Resources: Application-controlled readable resources
  • Resource Templates: Parameterized resources via URI templates
  • Tools: Model-controlled callable functions
  • Completions: Argument autocompletion for prompts and templates
  • Initialization Flow: Complete protocol lifecycle with version negotiation
  • Error Handling: Comprehensive error types and JSON-RPC error responses

Quick Start

Add the library mcp-server to your cabal file:

build-depends:
  mcp-server

Create a simple module, such as this example below:

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}

import MCP.Server
import MCP.Server.Derive

-- Define your data types
data MyPrompt = Recipe { idea :: Text } | Shopping { items :: Text }
data MyResource = Menu | Specials
data MyTool = Search { query :: Text } | Order { item :: Text }

-- Implement handlers. Every handler receives the per-request 'ClientContext'
-- (the caller's bearer token and principal on the HTTP transport) first.
handlePrompt :: ClientContext -> MyPrompt -> IO Content
handlePrompt _ (Recipe idea) = pure $ ContentText $ "Recipe for " <> idea
handlePrompt _ (Shopping items) = pure $ ContentText $ "Shopping list: " <> items

handleResource :: ClientContext -> URI -> MyResource -> IO ResourceContent
handleResource _ uri Menu = pure $ ResourceText uri "text/plain" "Today's menu..."
handleResource _ uri Specials = pure $ ResourceText uri "text/plain" "Daily specials..."

handleTool :: ClientContext -> MyTool -> IO Content
handleTool _ (Search query) = pure $ ContentText $ "Search results for " <> query
handleTool _ (Order item) = pure $ ContentText $ "Ordered " <> item

-- Derive handlers automatically
main :: IO ()
main = runMcpServerStdio serverInfo handlers
  where
    serverInfo = McpServerInfo
      { serverName = "My MCP Server"
      , serverVersion = "1.0.0"
      , serverInstructions = "A sample MCP server"
      }
    handlers = McpServerHandlers
      { prompts = Just $(derivePromptHandler ''MyPrompt 'handlePrompt)
      , resources = Just $(deriveResourceHandler ''MyResource 'handleResource)
      , tools = Just $(deriveToolHandler ''MyTool 'handleTool)
      }

Advanced Template Haskell Features

Automatic Naming Conventions

Constructor names are automatically converted to snake_case for MCP names:

data MyTool = GetValue | SetValue | SearchItems
-- Becomes: "get_value", "set_value", "search_items"

Typed Tool Arguments

Tool arguments are decoded from full JSON values, and the generated inputSchema mirrors the field types:

data Color = Red | Green | Blue          -- all-nullary type: string enum
data Filters = Filters                   -- record: nested JSON object
  { tags     :: [Text]                   -- list: JSON array
  , maxCount :: Maybe Int                -- Maybe: optional field
  }

data MyTool = Search
  { query   :: Text
  , color   :: Color                     -- "red" | "green" | "blue"
  , filters :: Filters                   -- { "tags": [...], "maxCount": ... }
  , limit   :: Maybe Int
  }

Primitive fields (Int, Integer, Double, Float, Bool, Text) are parsed leniently: the native JSON type and its string representation are both accepted (42 or "42"), since many clients send numbers and booleans as strings.

Prompt arguments are string-valued per the MCP specification, so prompt records are limited to primitive and enumeration fields.

Tool Results

Simple handlers can return plain Content (or Text). Return a full ToolResult for multiple content blocks, structured content, or to report execution failures with isError — which the spec prefers over protocol errors, so the model can see what went wrong and react:

handleTool :: ClientContext -> MyTool -> IO ToolResult
handleTool _ (Search q _ _ _)
  | T.null q  = pure $ toolError "query must not be empty"
  | otherwise = pure $ toolResult [ContentText ("Results for " <> q)]

Prompt handlers can likewise return a PromptResult with a description and a multi-message conversation (user and assistant roles).

Nested Parameter Types

You can nest parameter types with automatic unwrapping:

-- Parameter record types
data GetValueParams = GetValueParams { _gvpKey :: Text }
data SetValueParams = SetValueParams { _svpKey :: Text, _svpValue :: Text }

-- Main tool type
data SimpleTool
    = GetValue GetValueParams
    | SetValue SetValueParams
    deriving (Show, Eq)

The Template Haskell derivation recursively unwraps single-parameter constructors until it reaches a record type, then extracts all fields for the MCP schema.

Resource URI Generation

Resources automatically get resource:// URIs based on constructor names:

data MyResource = Menu | Specials
-- Generates: "resource://menu", "resource://specials"

Resource Templates

Record constructors become parameterized resource templates (RFC 6570 URI templates), with one percent-decoded path segment per field:

data MyResource
    = Menu                                            -- static: resource://menu
    | ProductDetail { sku :: Text }                   -- resource://product_detail/{sku}
    | OrderItem { orderId :: Int, itemName :: Text }  -- resource://order_item/{orderId}/{itemName}

The read handler derived by deriveResourceHandler matches template URIs (e.g. resource://product_detail/ABC123) and decodes the segments into the constructor's fields — typed fields like Int are parsed, and a failing segment yields an invalid-params error. Advertise the templates via resources/templates/list with:

resourceTemplates = Just $(deriveResourceTemplates ''MyResource)

Argument Completion

Provide a completions handler to serve completion/complete for prompt arguments and resource-template parameters:

handleComplete :: ClientContext -> CompletionRef -> ArgumentName -> Text -> Map Text Text -> IO (Either Error CompletionResult)
handleComplete _ (CompletionRefPrompt "recipe") "idea" partial _ =
    pure $ Right $ completionResult $
        filter (T.isPrefixOf partial) ["pancakes", "pasta", "pizza"]
handleComplete _ _ _ _ _ = pure $ Right $ completionResult []

The completions capability is advertised automatically when the handler is present.

Unsupported Patterns

We do not support positional (unnamed) parameters:

-- ❌ This won't work - no field names
data SimpleTool
    = GetValue Int
    | SetValue Int Text

All parameter types must ultimately resolve to records with named fields to generate proper MCP schemas.

Custom Descriptions

You can provide custom descriptions for constructors and fields using the *WithDescription variants:

-- Define descriptions for constructors and fields
descriptions :: [(String, String)]
descriptions =
  [ ("Recipe", "Generate a recipe for a specific dish")     -- Constructor description
  , ("Search", "Search our menu database")                  -- Constructor description
  , ("idea", "The dish you want a recipe for")              -- Field description
  , ("query", "Search terms to find menu items")            -- Field description
  ]

-- Use in derivation
handlers = McpServerHandlers
  { prompts = Just $(derivePromptHandlerWithDescription ''MyPrompt 'handlePrompt descriptions)
  , tools = Just $(deriveToolHandlerWithDescription ''MyTool 'handleTool descriptions)
  , resources = Just $(deriveResourceHandlerWithDescription ''MyResource 'handleResource descriptions)
  }

Manual Handler Implementation

For fine-grained control, implement handlers manually:

import MCP.Server

-- Manual handler implementation. Every handler receives the per-request
-- 'ClientContext' as its first argument. Prompt arguments are string-valued
-- (Map Text Text); tool arguments are full JSON values (Map Text Value).
promptListHandler :: ClientContext -> IO [PromptDefinition]
promptGetHandler :: ClientContext -> PromptName -> Map Text Text -> IO (Either Error PromptResult)
-- ... implement your custom logic

main :: IO ()
main = runMcpServerStdio serverInfo handlers
  where
    handlers = McpServerHandlers
      { prompts = Just (promptListHandler, promptGetHandler)
      , resources = Nothing  -- Not supported
      , tools = Nothing      -- Not supported
      }

Change Notifications

Servers whose tool/prompt/resource lists change at runtime can push change notifications. Create a notifier, hand its source to the transport, and call the notifier when things change:

main :: IO ()
main = do
    (notifier, source) <- newMcpNotifier
    _ <- forkIO $ appLogic notifier   -- calls notifyToolsListChanged etc.
    runMcpServerStdioWithConfig
        defaultStdioConfig { stdioNotifications = Just source }
        serverInfo handlers

Delivery is transport- and era-aware, and the listChanged/subscribe capabilities are advertised automatically where delivery is actually possible:

  • Modern clients (2026-07-28) open a subscriptions/listen stream (a long-lived SSE response over HTTP) and receive only the notification types they opted into, tagged with their subscription id — including notifications/resources/updated for watched URIs.
  • Legacy stdio clients receive spontaneous untagged notifications after initialize.
  • Legacy HTTP clients have no delivery channel (this library does not offer the deprecated GET SSE stream), so nothing is advertised to them.

HTTP Transport (NEW!)

The library supports the MCP Streamable HTTP transport. Compile your executable with ghc-options: -threaded — Warp requires the threaded runtime:

import MCP.Server.Transport.Http

-- Simple HTTP server (localhost:3000/mcp)
main = runMcpServerHttp serverInfo handlers

-- Custom configuration
main = runMcpServerHttpWithConfig customConfig serverInfo handlers
  where
    customConfig = defaultHttpConfig
      { httpPort = 8080
      , httpHost = "0.0.0.0"
      , httpEndpoint = "/api/mcp"
      , httpVerbose = True     -- Enable detailed logging
      , httpAllowedOrigins = Just ["https://app.example.com"]
          -- Origin validation (DNS-rebinding protection): requests with an
          -- Origin header outside this list are rejected with 403. Nothing
          -- disables the check (only for servers unreachable from browsers).
      }

Bearer-token authentication (optional): supply an httpAuthorize callback to validate the Authorization: Bearer token each request presents. Return Just principal to authorize (the principal — any JSON Value, e.g. a role — reaches your handlers as clientPrincipal in the ClientContext), or Nothing to reject the request with 401. Token policy lives entirely in your application; the library only threads the identity through:

    customConfig = defaultHttpConfig
      { httpAuthorize = Just $ \mtoken -> case mtoken of
          Just "secret-admin-token" -> pure $ Just (String "admin")
          Just "secret-user-token"  -> pure $ Just (String "user")
          _                         -> pure Nothing
      }

Features:

  • CORS enabled for web clients
  • POST /mcp for JSON-RPC messages (GET returns 405 — this library does not offer server-initiated SSE streams)
  • Dual-era protocol support: legacy revisions (2024-11-052025-11-25) negotiate via initialize; the stateless 2026-07-28 revision declares its version per request in _meta, with full request-metadata header validation (MCP-Protocol-Version, Mcp-Method, Mcp-Name including the base64 sentinel encoding)
  • Optional pluggable bearer-token authentication via httpAuthorize
  • Origin validation via httpAllowedOrigins
  • Cacheability hints for modern list/read results via httpCacheHints

Examples

The library includes several examples:

  • examples/Simple/: Basic key-value store using Template Haskell derivation (STDIO)
  • examples/Complete/: Full-featured example with prompts, resources, and tools (STDIO)
  • examples/HttpSimple/: HTTP version of the simple key-value store

Docker Usage

I like to build and publish my MCP servers to Docker - which means that it's much easier to configure assistants such as Claude Desktop to run them.

# Build the image
docker build -t haskell-mcp-server .

# Run different examples
docker run -i --entrypoint="/usr/local/bin/simple-example" haskell-mcp-server

And then configure Claude by editing claude_desktop_config.json:

{
    "mcpServers": {
       "simple-example": {
            "command": "docker",
            "args": [
                "run",
                "-i",
                "--entrypoint=/usr/local/bin/simple-example",
                "haskell-mcp-server"
            ]
        }
    }
}

Documentation

Contributing

Contributions are welcome! Please see the issue tracker for open issues and feature requests.

Disclaimer - AI Assistance

I am not sure whether there is any stigma associated with this but Claude helped me write a lot of this library. I started with a very specific specification of what I wanted to achieve and worked shoulder-to-shoulder with Claude to implement and refactor the library until I was happy with it. A few of the features such as the Derive functions are a little out of my comfort zone to have manually written, so I appreciated having an expert guide me here - however I do suspect that this implementation may be sub-par and I do intend to refactor and rewrite large pieces of this through regular maintenance.

License

BSD-3-Clause