The transport layer handles the physical communication between the MCP client (LLM) and the server (Pharo). LLM-Pharo-MCP ships with two transports.
All transports extend McpTransport and implement three methods:
| Method | Description |
|---|---|
start |
Begin listening for messages |
stop |
Stop listening |
sendMessage: |
Send a JSON string to the client |
The transport communicates incoming messages to the server via the messageHandler: callback, which the server sets during start.
The standard transport for production use. Reads JSON-RPC messages line-by-line from stdin and writes responses to stdout.
transport := McpStdioTransport stdin: Stdio stdin stdout: Stdio stdout.
server := McpServer default.
server transport: transport.
server start.
start, spawns a background process that reads lines from the input streammessageHandler callbacksendMessage: writes the JSON string to the output stream followed by a newline, then flushes"Start"
server start.
"Check status"
transport isRunning. "=> true"
"Stop"
server stop.
transport isRunning. "=> false"
When configured as an MCP server in an AI client (like Claude Desktop), the client launches the Pharo process and communicates via stdio:
{
"mcpServers": {
"pharo": {
"command": "/path/to/pharo",
"args": ["--headless", "my-image.image", "eval", "McpServer startStdio"]
}
}
}
A transport designed for testing that stores messages in memory and allows programmatic message injection.
transport := McpInMemoryTransport new.
server := McpServer default.
server transport: transport.
server start.
"Simulate a client sending a request"
transport simulateReceive: '{"jsonrpc":"2.0","id":1,"method":"ping"}'.
"Check the server's response"
transport lastSentMessage.
"=> '{"jsonrpc":"2.0","id":1,"result":{}}'"
transport lastSentParsed.
"=> a Dictionary( 'jsonrpc'->'2.0' 'id'->1 'result'->a Dictionary() )"
"All messages sent by the server"
transport sentMessages.
"Clear the message history"
transport reset.
The test suite uses McpInMemoryTransport extensively:
McpServerTest >> setUp [
server := McpServer name: 'test-server' version: '1.0.0'.
transport := McpInMemoryTransport new.
server transport: transport.
server start
]
McpServerTest >> testPing [
| request response |
request := McpJsonRpcRequest id: 1 method: 'ping'.
transport simulateReceive: request asJsonString.
response := transport lastSentParsed.
self assert: (response at: 'id') equals: 1.
self assert: (response at: 'result') equals: Dictionary new
]
To implement a custom transport (HTTP, WebSocket, etc.), subclass McpTransport:
McpTransport subclass: #McpHttpTransport
instanceVariableNames: 'httpServer port'
classVariableNames: ''
package: 'MyPackage'.
McpHttpTransport >> start [
httpServer := ZnServer startDefaultOn: port.
httpServer
onRequestRespond: [ :req |
| body |
body := req entity string.
messageHandler value: body.
"Return the last response (simplified)"
ZnResponse ok: (ZnEntity json: self lastResponse) ]
]
McpHttpTransport >> stop [
httpServer stop
]
McpHttpTransport >> sendMessage: aJsonString [
"Store for the HTTP response"
lastResponse := aJsonString
]