MCP (Model Context Protocol) is how AI assistants such as Claude Code and Claude Desktop are given new tools. The simplest kind of MCP server is a console program: the assistant starts it, writes JSON-RPC requests to its standard input, one per line, and reads the replies from standard output.
That is something Delphi has always done well, and it means existing Delphi code - business rules, database access, anything Windows-specific - can be put in front of Claude without rewriting it. I wrote a small server in one .dpr, no third-party libraries: one tool looks up Delphi releases by name, BDS version, VERxxx define or CompilerVersion, and another lists the Delphi versions installed on the machine from the registry.
The trap
The obvious way to read lines from standard input is TStreamReader. Fed a file of test requests, it answered every one. Connected to Claude Code, it hung, and claude mcp list reported connection timed out after 30 seconds.
TStreamReader waits to fill its buffer before returning a line. The client sends one request and waits for the answer, so neither side moves. A file hides the problem because its end arrives straight away and releases the buffer.
The fix is to read the pipe directly. THandleStream.Read on a pipe returns as soon as any bytes are available, so collect bytes until a line feed arrives:
var
StdIn: THandleStream;
Pending: TBytes;
function Read_Line(out ALine: string): Boolean;
var
LBuffer: array [0..4095] of Byte;
LCount, LOld, I: Integer;
begin
repeat
for I := 0 to High(Pending) do
if Pending[I] = 10 then
begin
ALine := TEncoding.UTF8.GetString(Pending, 0, I).TrimRight;
Pending := Copy(Pending, I + 1, Length(Pending));
Exit(True);
end;
// Returns as soon as any input is available; 0 when the client has closed it
LCount := StdIn.Read(LBuffer, SizeOf(LBuffer));
if LCount <= 0 then
Exit(False);
LOld := Length(Pending);
SetLength(Pending, LOld + LCount);
Move(LBuffer, Pending[LOld], LCount);
until False;
end;
Decoding only whole lines also means a UTF-8 character split between two reads is never cut in half. The same trap applies to any Delphi console program talking to another process through pipes, not just MCP.
Connecting it
claude mcp add --transport stdio delphi-versions -- "C:\Tools\Delphi_Mcp.exe"
Then ask Claude “Which Delphi versions are installed on this PC?” and it calls the server.
The full article has the complete server source, the JSON-RPC exchange step by step, testing it by hand, Claude Desktop setup, and what to watch out for before exposing your own code as tools: