Table of Contents

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 SettingsMaxTokens, 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 Execute without assigning Client. The dialog opens with nothing to edit and every field disabled.
  • Expecting the dialog to save anything. It edits the live component only. Without OnAPIKeysChanged and OnServersChanged handlers 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. Service and the generation settings are not on it.

See also