The settings and tools dialogs
Everything a TTMSMCPClient needs — a service, a key, a model name, a list of
servers with their commands and arguments — is configuration a user will
eventually want to change without a rebuild. TTMSMCPClientSettingsDialog is
that screen, already written: a tabbed window that edits the client's Servers
collection on one tab and its API keys and per-provider models on another, with
buttons to start a connection and inspect the tools it found.
TTMSMCPClientToolsDialog is the small window behind that last button, and it
can be used on its own. Both are optional — nothing in the client depends on
them — and both leave persistence to you, through two events.
The settings dialog
Use it whenever the server list or the keys are not fixed at build time, because
writing the same screen by hand is a day of work for no advantage. Drop a
TTMSMCPClientSettingsDialog on the form, point its Client property at the
client, and call Execute:
procedure TForm1.FormCreate(Sender: TObject);
begin
{ The dialog edits the client it is assigned to; nothing else is needed
to make it work. }
SettingsDialog.Client := MCPClient;
SettingsDialog.OnAPIKeysChanged := DoAPIKeysChanged;
SettingsDialog.OnServersChanged := DoServersChanged;
end;
procedure TForm1.SettingsButtonClick(Sender: TObject);
begin
{ Execute shows the dialog modally by default; pass False for a
modeless window and use Close to dismiss it from code. }
SettingsDialog.Execute;
end;
procedure TForm1.DoAPIKeysChanged(Sender: TObject);
begin
{ Fires on every keystroke in an API key or model field, so keep the
handler cheap. The keys are written encrypted with the password
supplied as the second argument. }
MCPClient.APIKeys.SaveToFile(ChangeFileExt(ParamStr(0), '.keys'),
FKeyFilePassword);
end;
procedure TForm1.DoServersChanged(Sender: TObject);
begin
{ Fires when a server is added, modified, or deleted. }
MCPClient.Servers.SaveToJSONFile(
ChangeFileExt(ParamStr(0), '-config.json'));
end;
Execute takes an optional AModal argument that defaults to True and returns
the modal result; pass False for a modeless window and call Close to dismiss
it from code. The dialog is built for VCL and FMX from the same source, so the
same two lines work in either framework.
What the dialog edits
Knowing which properties the window reaches matters, because anything outside
that set still has to be configured in code. The Servers tab lists
Client.Servers by DisplayName and edits the selected entry's
TransportType, Command, Args, EnvironmentVariables, and URL, showing
only the fields the chosen transport uses — command, arguments and environment
for STDIO, the URL for SSE and streamable HTTP. New and Delete add and remove
collection entries; Modify writes the edits back. A status area shows whether
the selected connection is running and how many tools it found, with a button to
Start or Stop it and a second button that opens the tools dialog for it.
The API Keys tab edits Client.APIKeys and the matching model name from
Client.Settings side by side, one row per service, plus host and port for the
two local runtimes. What it does not touch is the rest of Settings —
MaxTokens, Temperature, Reasoning, CacheControl and the others stay
wherever your code left them, and Service itself is not selected here either.
A typical application therefore keeps its own provider combo box and lets the
dialog handle only keys, models, and servers.
Persisting what the dialog changes
Wire these two events every time, because the dialog edits the live component
and nothing else. OnAPIKeysChanged fires whenever a key or model field is
edited — on each keystroke, so keep the handler cheap — and OnServersChanged
fires when a server is added, modified, or deleted. The natural pair is
APIKeys.SaveToFile, which encrypts the whole key set with a password, and
Servers.SaveToJSONFile, which writes the configuration format described in
Connecting to MCP servers:
procedure TForm1.RestoreConfiguration;
begin
FKeyFileName := ChangeFileExt(ParamStr(0), '.keys');
FConfigFileName := ChangeFileExt(ParamStr(0), '-config.json');
{ Keys first: a connection that needs the model is useless without one. }
if TFile.Exists(FKeyFileName) then
MCPClient.APIKeys.LoadFromFile(FKeyFileName, FKeyFilePassword);
{ LoadFromJSONFile rebuilds the collection and starts every entry. }
if TFile.Exists(FConfigFileName) then
MCPClient.Servers.LoadFromJSONFile(FConfigFileName);
end;
procedure TForm1.DoAPIKeysChanged(Sender: TObject);
begin
{ Fires per keystroke in an API key or model field - keep it cheap. }
MCPClient.APIKeys.SaveToFile(FKeyFileName, FKeyFilePassword);
end;
procedure TForm1.DoServersChanged(Sender: TObject);
begin
{ Fires when a server is added, modified, or deleted. }
MCPClient.Servers.SaveToJSONFile(FConfigFileName);
end;
Load both back on start-up in the same order and the user's configuration survives a restart. The key file is encrypted with the password you pass, so that password has to come from somewhere stable — a per-installation value, or one derived from the machine — rather than being regenerated each run.
The tools dialog
Show this when someone needs to see what a connection actually exposes — during
development to confirm a server registered what you expected, and in production
as a "what can this agent do" window. TTMSMCPClientToolsDialog lists tool
names on the left and shows the description of the selected one on the right. It
is not a design-time component, so create it in code, and it holds no reference
to a client: it asks for the array to display through OnInitListBox:
procedure TForm1.FormCreate(Sender: TObject);
begin
{ The tools dialog is not a design-time component - create it and own
it yourself. }
FToolsDialog := TTMSMCPClientToolsDialog.Create(Self);
FToolsDialog.OnInitListBox := DoToolsDialogInitListBox;
end;
procedure TForm1.ShowToolsButtonClick(Sender: TObject);
begin
if ServerListBox.ItemIndex >= 0 then
FToolsDialog.Execute;
end;
procedure TForm1.DoToolsDialogInitListBox(Sender: TObject;
var AArray: TJSONArray);
begin
{ Hand back the connection's own tool array - the dialog reads it and
does not take ownership, so never pass a copy you then free. }
AArray := MCPClient.Servers[ServerListBox.ItemIndex].GetTools;
end;
The AArray you assign is the raw tools/list payload — the same array
TTMSMCPClientServerItem.GetTools returns. Assign that array directly. It
belongs to the connection, the dialog only reads it, and building a copy to hand
over means owning a TJSONArray that nothing will free.
Putting it together
A configuration button that does the whole job opens the settings dialog, persists whatever changed, repairs the event wiring the dialog disturbed, and leaves the tools dialog available for inspecting the result:
procedure TForm1.FormCreate(Sender: TObject);
begin
{ The settings dialog edits the client; the tools dialog reports on it. }
SettingsDialog.Client := MCPClient;
SettingsDialog.OnAPIKeysChanged := DoAPIKeysChanged;
SettingsDialog.OnServersChanged := DoServersChanged;
FToolsDialog := TTMSMCPClientToolsDialog.Create(Self);
FToolsDialog.OnInitListBox := DoToolsDialogInitListBox;
end;
procedure TForm1.ConfigureButtonClick(Sender: TObject);
var
I: Integer;
begin
SettingsDialog.Execute;
{ A server started from the dialog has had its OnGetToolsList replaced,
so reattach the handler after the dialog closes if the application
relies on it. }
for I := 0 to MCPClient.Servers.Count - 1 do
MCPClient.Servers[I].OnGetToolsList := DoServerToolsReady;
RefreshServerList;
end;
procedure TForm1.InspectButtonClick(Sender: TObject);
begin
{ Same tool list the settings dialog shows behind its Tools button,
reachable without opening the settings dialog first. }
if ServerListBox.ItemIndex >= 0 then
FToolsDialog.Execute;
end;
procedure TForm1.DoToolsDialogInitListBox(Sender: TObject;
var AArray: TJSONArray);
begin
AArray := MCPClient.Servers[ServerListBox.ItemIndex].GetTools;
end;
procedure TForm1.DoServersChanged(Sender: TObject);
begin
MCPClient.Servers.SaveToJSONFile(
ChangeFileExt(ParamStr(0), '-config.json'));
end;
The repair step is the one worth reading twice. Starting a connection from the
settings dialog assigns that connection's OnGetToolsList to the dialog's own
handler, replacing yours — so an application that uses that event as its
"ready to ask" signal has to reattach it after Execute returns.
Common mistakes
- Calling
Executewithout assigningClient. The dialog opens with nothing to edit and every field disabled. - Expecting the dialog to save anything. It edits the live component only.
Without
OnAPIKeysChangedandOnServersChangedhandlers the user's work is gone at shutdown. - Doing heavy work in
OnAPIKeysChanged. It fires per keystroke. - Losing
OnGetToolsList. Starting a server from the dialog replaces that connection's handler; reattach it afterwards. - Freeing the array returned from
OnInitListBox. It is the connection's own list, not a copy made for the dialog. - Assuming the dialog configures the whole client.
Serviceand the generation settings are not on it.
See also
- Connecting to MCP servers — the collection the dialog edits
- Querying a language model — the settings it does not reach
TTMSMCPClientSettingsDialog,TTMSMCPClientToolsDialog,TTMSMCPCustomDialog