johnmcarbajal/haskell-mcp-server
If you are the rightful owner of haskell-mcp-server and would like to certify it and/or have it hosted online, please leave a comment on the right or send an email to henry@mcphub.com.
A Haskell-based Model Context Protocol (MCP) server implementation using Stack.
Haskell MCP Server
A Model Context Protocol (MCP) server implementation in Haskell using Stack.
Features
- JSON-RPC 2.0 Protocol: Full compliance with MCP specification
- WebSocket Communication: Real-time bidirectional communication
- Tool System: Extensible tool registration and execution
- Resource Management: Resource listing and reading capabilities
- Type Safety: Leverages Haskell's type system for robust protocol handling
Built-in Tools
- echo - Echo back input messages
- current_time - Get the current system time
- calculate - Perform basic arithmetic calculations
Built-in Resources
- config://server.json - Server configuration information
Prerequisites
- Stack (Haskell build tool)
- GHC 9.2+ (will be installed automatically by Stack)
Quick Start
-
Clone and build the project:
git clone <your-repo-url> cd haskell-mcp-server stack build
-
Run the server:
stack exec haskell-mcp-server-exe
Or specify a custom port:
stack exec haskell-mcp-server-exe -- 8080
-
Test the server: The server will listen on
ws://127.0.0.1:3000
(or your specified port) for WebSocket connections.
Development
Project Structure
haskell-mcp-server/
āāā app/
ā āāā Main.hs # Application entry point
āāā src/
ā āāā MCP/
ā āāā Types.hs # MCP protocol types
ā āāā Server.hs # Core server implementation
ā āāā Tools.hs # Tool definitions and handlers
āāā test/
ā āāā Spec.hs # Test suite
āāā package.yaml # Package configuration
āāā stack.yaml # Stack configuration
āāā README.md
Adding Custom Tools
To add a new tool, create a Tool
definition and handler:
myTool :: Tool
myTool = Tool
{ toolName = "my_tool"
, description = "Description of what the tool does"
, inputSchema = object
[ "type" .= ("object" :: Text)
, "properties" .= object
[ "param" .= object
[ "type" .= ("string" :: Text)
, "description" .= ("Parameter description" :: Text)
]
]
, "required" .= (["param"] :: [Text])
]
}
myToolHandler :: Value -> IO ToolResult
myToolHandler args = do
-- Your tool logic here
return $ ToolResult
[ ContentItem "text" "Tool result" ]
Nothing
Then register it in Main.hs
:
addTool server myTool myToolHandler
Adding Custom Resources
Similarly, for resources:
let myResource = Resource
{ resourceUri = "custom://my-resource"
, resourceName = "My Resource"
, resourceDescription = Just "Custom resource description"
, mimeType = Just "application/json"
}
addResource server myResource $ return $ object
[ "data" .= ("resource content" :: Text) ]
Testing
Run the test suite:
stack test
Building for Production
Create an optimized build:
stack build --ghc-options="-O2"
Protocol Compliance
This server implements the Model Context Protocol specification:
- Initialization: Proper handshake with protocol version negotiation
- Tool Listing: Dynamic tool discovery via
tools/list
- Tool Execution: Safe tool execution via
tools/call
- Resource Management: Resource listing and reading via
resources/list
andresources/read
- Error Handling: Standard JSON-RPC error responses
Dependencies
Key dependencies include:
aeson
- JSON serialization/deserializationwebsockets
- WebSocket server implementationstm
- Software Transactional Memory for concurrent statetext
- Efficient text processingcontainers
- Data structures (Map, etc.)
License
BSD3 License - see LICENSE file for details.
Contributing
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature
) - Make your changes
- Add tests for new functionality
- Ensure all tests pass (
stack test
) - Commit your changes (
git commit -am 'Add amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
Example Client Usage
Here's how to connect to the server using a WebSocket client:
JavaScript Client Example
const ws = new WebSocket('ws://127.0.0.1:3000');
// Initialize the connection
ws.onopen = () => {
// Send initialize request
ws.send(JSON.stringify({
jsonrpc: "2.0",
method: "initialize",
params: {
protocolVersion: { major: 2024, minor: 11 },
capabilities: {
roots: { listChanged: true },
sampling: {}
},
clientInfo: {
name: "test-client",
version: "1.0.0"
}
},
id: 1
}));
};
ws.onmessage = (event) => {
const response = JSON.parse(event.data);
console.log('Received:', response);
// After initialization, send initialized notification
if (response.id === 1) {
ws.send(JSON.stringify({
jsonrpc: "2.0",
method: "initialized",
params: {}
}));
// List available tools
ws.send(JSON.stringify({
jsonrpc: "2.0",
method: "tools/list",
params: {},
id: 2
}));
// Call echo tool
ws.send(JSON.stringify({
jsonrpc: "2.0",
method: "tools/call",
params: {
name: "echo",
arguments: { message: "Hello, MCP!" }
},
id: 3
}));
}
};
Python Client Example
import asyncio
import websockets
import json
async def test_client():
uri = "ws://127.0.0.1:3000"
async with websockets.connect(uri) as websocket:
# Initialize
init_request = {
"jsonrpc": "2.0",
"method": "initialize",
"params": {
"protocolVersion": {"major": 2024, "minor": 11},
"capabilities": {
"roots": {"listChanged": True},
"sampling": {}
},
"clientInfo": {
"name": "python-test-client",
"version": "1.0.0"
}
},
"id": 1
}
await websocket.send(json.dumps(init_request))
response = await websocket.recv()
print("Init response:", json.loads(response))
# Send initialized notification
await websocket.send(json.dumps({
"jsonrpc": "2.0",
"method": "initialized",
"params": {}
}))
# List tools
await websocket.send(json.dumps({
"jsonrpc": "2.0",
"method": "tools/list",
"params": {},
"id": 2
}))
tools_response = await websocket.recv()
print("Tools:", json.loads(tools_response))
# Call calculator tool
await websocket.send(json.dumps({
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "calculate",
"arguments": {"expression": "2 + 3 * 4"}
},
"id": 3
}))
calc_response = await websocket.recv()
print("Calculation result:", json.loads(calc_response))
# Run the client
asyncio.run(test_client())
Configuration
The server can be configured by modifying the defaultServer
function in MCP/Server.hs
:
-- Custom server configuration
customServer :: IO MCPServer
customServer = do
server <- defaultServer
return server
{ serverInfo = ServerInfo
{ name = "my-custom-server"
, version = "1.0.0"
, protocolVersion = MCPVersion 2024 11
}
}
Troubleshooting
Common Issues
-
Port already in use: Change the port number when starting the server
stack exec haskell-mcp-server-exe -- 8080
-
Build errors: Ensure you have the latest Stack version
stack upgrade stack clean stack build
-
WebSocket connection refused: Check that the server is running and the port is correct
Debug Mode
For verbose logging, you can modify the server to include more debug output:
-- In MCP/Server.hs, add more putStrLn statements
handleMessage server req = do
putStrLn $ "Received method: " ++ T.unpack (method req)
-- ... rest of the function
Performance Notes
- The server uses STM (Software Transactional Memory) for thread-safe state management
- WebSocket connections are handled concurrently using lightweight Haskell threads
- Tool handlers should be designed to be non-blocking for optimal performance
API Reference
Core Methods
Method | Description | Parameters |
---|---|---|
initialize | Initialize the MCP connection | InitializeParams |
initialized | Notification that initialization is complete | None |
tools/list | List available tools | None |
tools/call | Execute a tool | ToolCallArgs |
resources/list | List available resources | None |
resources/read | Read a resource | {uri: string} |
Error Codes
Code | Description |
---|---|
-32700 | Parse error |
-32600 | Invalid request |
-32601 | Method not found |
-32602 | Invalid params |
-32603 | Internal error |
Roadmap
- Add support for streaming tool responses
- Implement resource subscriptions
- Add built-in file system tools
- Implement tool result caching
- Add configuration file support
- Performance optimizations
- Docker containerization
Support
For questions, issues, or contributions:
- Open an issue on GitHub
- Check the MCP specification for protocol details
- Review the Haskell documentation for language-specific questions