Configuring the server
A server's configuration is a contract it publishes. During initialize the
client reads the server's identity, its instructions, and the set of
capabilities it declares, and every later request is shaped by what was agreed
there. A capability you never enable is one the client will never use, however
much code sits behind it — so configuration is not boilerplate, it is the part
that decides which half of the component is reachable. This guide covers
identity, the fluent builder, each capability flag and what turning it on
commits you to, change notifications, pagination, and logging.
Identity and instructions
Four properties describe the server to the client and, through it, to the model:
| Property | Purpose |
|---|---|
ServerName |
The protocol-level name. Use the product name. |
ServerVersion |
Your version string, in MAJOR.MINOR.RELEASE.BUILD form. |
ServerDescription |
One line explaining what the server is for. |
Instructions |
Server-wide guidance passed to the model in the initialize result. |
Instructions is the most under-used of the four. It is free text the model
reads before deciding anything, so it is the right place for rules that apply
across every tool: which tool to prefer, what units your numbers are in, what
vocabulary your domain uses, what the model must always state in its answer.
Rules you would otherwise repeat in every tool description belong here once.
Using the builder
TTMSMCPServer.CreateBuilder returns a builder class; each method returns the
builder again, so the whole configuration reads as one expression and the
capability set is visible at a glance:
procedure TForm1.BuildServer;
begin
Server := TTMSMCPServer.CreateBuilder
.Name('InventoryServer')
.ServerVersion('2.1.0')
.Description('Warehouse inventory and replenishment')
.Instructions('Prefer stock_level before reorder. Quantities are in units, ' +
'never cases. Always state the warehouse you used.')
.EnableToolNotifications(True)
.EnableResourceNotifications(True)
.EnablePromptNotifications(True)
.EnableResourceSubscriptions(True)
.EnableLogging(True)
.EnableCompletions(True)
.EnableSampling(True)
.EnableElicitation(True)
.EnableTasks(True)
.DefaultTaskPollInterval(3000)
.DefaultPageSize(100)
.Build;
Server.Start;
end;
Build returns the configured TTMSMCPServer. The builder covers identity,
instructions, every capability flag, the default page size, the default task
poll interval, and the transport; anything it does not cover is a plain property
assignment afterwards.
Capability flags
Each flag advertises a capability during initialize. A client will not use a
capability the server did not declare, and declaring one commits you to handling
the requests it invites:
| Property | Declares | What it commits you to |
|---|---|---|
EnableToolNotifications |
tools.listChanged |
Calling SendToolsChangedNotification when the tool set changes. |
EnableResourceNotifications |
resources.listChanged |
Calling SendResourcesChangedNotification when the resource set changes. |
EnablePromptNotifications |
prompts.listChanged |
Calling SendPromptsChangedNotification when the prompt set changes. |
EnableResourceSubscriptions |
resources.subscribe |
Handling resources/subscribe and sending SendResourceUpdatedNotification. |
EnableLogging |
logging |
Pushing entries with SendLogMessage, and filtering them against CurrentLogLevel yourself. |
EnableCompletions |
completions |
Assigning OnCompletion to supply argument suggestions. |
EnableSampling |
sampling |
Being able to call RequestSampling — the client runs the completion. |
EnableElicitation |
elicitation |
Being able to call RequestElicitation to ask the user a question. |
EnableTasks |
tasks |
Long-running tools/call execution; see Tasks and elicitation. |
All of them default to False. Turn on only what the server actually
implements — an advertised capability that misbehaves is worse than one that was
never offered.
Change notifications
When the tool, resource, or prompt set changes at runtime, the client needs to be told or it will keep working from a stale list. Each collection has its own broadcast, plus a targeted update for a single subscribed resource:
procedure TForm1.AddSeasonalTool;
begin
{ EnableToolNotifications must be on for the client to accept this. }
Server.Tools.RegisterTool('holiday_stock',
'Returns seasonal stock reserved for the holiday period',
function(const Args: array of TValue): TValue
begin
Result := HolidayStock(Args[0].AsString);
end,
ptInteger);
Server.SendToolsChangedNotification;
end;
procedure TForm1.PublishPriceList(const AURI: string);
begin
RefreshPriceList(AURI);
{ Goes to every client subscribed to this URI. }
Server.SendResourceUpdatedNotification(AURI);
end;
| Method | Sends to |
|---|---|
SendToolsChangedNotification |
Every connected client. |
SendResourcesChangedNotification |
Every connected client. |
SendPromptsChangedNotification |
Every connected client. |
SendResourceUpdatedNotification(AURI) |
Every client subscribed to that URI. |
SendResourceUpdatedForSession(ASessionId, AURI) |
One session, if it is subscribed. |
SendProgressNotification(AToken, AProgress, ATotal, AMessage) |
The client that supplied the progress token. |
SendNotificationToSession(ASessionId, AJsonRpc) |
One session, raw JSON-RPC. |
BroadcastNotification(AJsonRpc) |
Every session, raw JSON-RPC. |
The notification only travels if the matching capability flag is on. Sending one with the flag off is a silent no-op from the client's point of view.
Pagination
DefaultPageSize caps how many items a list request returns at once; the client
pages through the rest with a cursor. It defaults to 0, meaning no paging —
every tool, resource, and prompt comes back in one response. Set it when a
collection grows past a few dozen entries, so a tools/list does not hand the
model a wall of text it has to read in full before choosing.
procedure TForm1.ConfigurePagination;
var
I: Integer;
begin
{ Without this, tools/list returns every tool in one response. }
Server.DefaultPageSize := 25;
for I := 0 to CatalogSectionCount - 1 do
Server.Tools.RegisterTool(
Format('search_%s', [CatalogSectionKey(I)]),
Format('Searches the %s section of the catalog', [CatalogSectionName(I)]),
function(const Args: array of TValue): TValue
begin
Result := SearchSection(Args[0].AsString);
end,
ptString);
{ The client now pages through the tool list with a cursor. }
Server.SendToolsChangedNotification;
end;
Logging
With EnableLogging on, the client can raise or lower the server's minimum log
level at runtime through logging/setLevel, and CurrentLogLevel reports what
it asked for. The filtering is yours to apply — SendLogMessage does not
test CurrentLogLevel; it broadcasts every entry to every connected client once
the server is initialized. Check the level before building an entry that is
expensive to produce:
procedure TForm1.ConfigureLogging;
begin
Server.EnableLogging := True;
Server.CurrentLogLevel := llInfo;
Server.OnLogLevelChanged := ServerLogLevelChanged;
end;
procedure TForm1.ServerLogLevelChanged(Sender: TObject; ALevel: TTMSMCPLogLevel);
begin
{ The client asked for a different minimum level through logging/setLevel. }
MemoLog.Lines.Add('Client set log level to ' + GetEnumName(TypeInfo(TTMSMCPLogLevel), Ord(ALevel)));
end;
procedure TForm1.ReportReorder(const AProduct: string; AUnits: Integer);
begin
{ SendLogMessage does not filter - it broadcasts every entry. Test the
level yourself before doing work to build one. }
if Server.CurrentLogLevel <= llInfo then
Server.SendLogMessage(llInfo, 'inventory',
Format('Reordered %d units of %s', [AUnits, AProduct]));
end;
TTMSMCPLogLevel runs llDebug, llInfo, llNotice, llWarning, llError,
llCritical, llAlert, llEmergency. OnLogLevelChanged fires when the client
changes the level, which is also a useful signal that a client is paying
attention to the log stream at all.
OnLog is a separate, local event carrying the server's own diagnostic
messages — it does not go to the client and is not gated by EnableLogging.
Completions
EnableCompletions advertises the completions capability, which lets a client
offer as-you-type suggestions while a user fills in an argument of a prompt or a
resource template. The server answers each completion/complete request through
OnCompletion:
procedure TForm1.ConfigureCompletions;
begin
Server.EnableCompletions := True;
Server.OnCompletion := ServerCompletion;
end;
procedure TForm1.ServerCompletion(Sender: TObject; const ARef,
AArgument: TJSONObject; var AValues: TArray<string>; var ATotal: Integer;
var AHasMore: Boolean);
var
ArgumentName, Partial: string;
begin
{ ARef says what is being completed - a prompt or a resource template.
AArgument carries the argument name and what has been typed so far. }
ArgumentName := AArgument.GetValue<string>('name', '');
Partial := AArgument.GetValue<string>('value', '');
if ArgumentName = 'region' then
AValues := MatchingRegions(Partial)
else
AValues := [];
ATotal := Length(AValues);
AHasMore := False;
end;
The handler receives ARef — what is being completed, a prompt or a resource
template — and AArgument, the argument name plus the partial value typed so
far. It answers through three var parameters: AValues with the suggestions,
ATotal with the number of matches that exist, and AHasMore when the list was
truncated. With EnableCompletions on but no handler assigned, the server still
answers the request — with an empty list, which is worse than not advertising the
capability at all.
Error codes
Failures reach the client as JSON-RPC errors. TTMSMCPErrorCode (declared in
TMS.MCP.Helpers) holds the standard codes plus the server's own extensions:
| Value | Code | Raised when |
|---|---|---|
ecParseError |
-32700 | The request was not valid JSON. |
ecInvalidRequest |
-32600 | The JSON was valid but not a JSON-RPC request. |
ecMethodNotFound |
-32601 | The method is unknown — or the capability behind it is off, which is how a sampling/createMessage arriving with EnableSampling false is answered. |
ecInvalidParams |
-32602 | Parameters are missing or the wrong shape: no tool name, no resource URI, no prompt name. |
ecInternalError |
-32603 | The handler failed, including a sampling request arriving with no OnSamplingRequest assigned. |
ecServerNotInitialized |
-32000 | A request arrived before initialize completed. |
ecUnknownTool |
-32001 | Reserved for an unknown tool. See the note below — the current server does not use it. |
ecResourceNotFound |
-32002 | No resource or template matches the requested URI. The tasks methods reuse this number for an unknown task id — including one belonging to another session, so a client cannot probe for tasks it does not own. |
ecPromptNotFound |
-32003 | prompts/get named a prompt that is not in the collection. |
ecOperationFailed |
-32004 | Reading a resource or building a prompt raised. |
ecURLElicitationRequired |
-32042 | Reserved for a request that cannot proceed until the user completes a URL-mode elicitation. |
A failing tools/call is the exception to the table: it does not come back
as a JSON-RPC error. An unknown tool name, or an exception escaping a tool
method, returns a normal result with isError set to true and the message as a
text content item — which is what the MCP specification asks for, so the model
sees the failure and can react to it instead of the client swallowing a
transport-level error. That is why ecUnknownTool is declared but never raised.
Inside a task the same failure is reported through SetTaskError instead, with
code -32602.
Combining identity, capabilities, and notifications
A realistic server sets its identity and instructions, turns on the capabilities it implements, pages its collections, and then keeps the client's view current as things change. Each flag is there because a later call depends on it:
procedure TForm1.ConfigureCatalogServer;
begin
Server := TTMSMCPServer.CreateBuilder
.Name('CatalogServer')
.ServerVersion('3.0.0')
.Description('Product catalog search and maintenance')
.Instructions('Search before editing. Prices are in euro, excluding VAT. ' +
'Always name the catalog section you searched.')
.EnableToolNotifications(True)
.EnableResourceNotifications(True)
.EnableResourceSubscriptions(True)
.EnableLogging(True)
.DefaultPageSize(25)
.Build;
Server.CurrentLogLevel := llInfo;
Server.OnLogLevelChanged := ServerLogLevelChanged;
Server.Start;
end;
procedure TForm1.PublishSeasonalCatalog(const AURI: string);
begin
{ Capabilities declared above are what make these two calls meaningful:
EnableToolNotifications for the list change, EnableResourceSubscriptions
for the targeted resource update, EnableLogging for the audit line. }
Server.Tools.RegisterTool('search_seasonal',
'Searches the seasonal catalog section',
function(const Args: array of TValue): TValue
begin
Result := SearchSeasonal(Args[0].AsString);
end,
ptString);
Server.SendToolsChangedNotification;
RefreshSeasonalCatalog(AURI);
Server.SendResourceUpdatedNotification(AURI);
Server.SendLogMessage(llInfo, 'catalog',
'Seasonal catalog published and search tool registered');
end;
That is the whole loop in one place — builder-set identity and instructions, three capability flags, a page size, and then the notification and log calls those flags make meaningful. Turn any one flag off and the corresponding call becomes a silent no-op from the client's point of view, which is what makes configuration and runtime behaviour a single subject rather than two.
Common mistakes
- Enabling a capability with nothing behind it.
EnableCompletions := Truewithout anOnCompletionhandler advertises argument completion and then answers nothing. - Changing a collection without notifying. Adding a tool at runtime while
EnableToolNotificationsis off leaves every connected client using the list it fetched at connect time. - Putting cross-cutting rules in every tool description. That is what
Instructionsis for. The tool description should describe the tool. - Leaving
DefaultPageSizeat 0 with a large collection. The whole list goes to the model in one response, consuming context before any work starts. - Expecting
OnLogto reach the client. It is local.SendLogMessageis the one that travels.
See also
- Sessions and state — per-client state and targeted notifications
- Tasks and elicitation — the long-running capabilities
- Tools, Resources, Prompts
TTMSMCPServer,TTMSMCPServerBuilder