Getting started with Attributes
This page covers the shortest path from a plain Delphi class to a running MCP server: which units to add, which methods RTTI can see, the one attribute that matters, and how to host the result. For the full attribute vocabulary, the schema rules, and target management, see the user guide.
Prerequisites
- Delphi 11 Alexandria or newer (or C++Builder) with TMS AI Studio installed.
- A VCL, FMX, console, or service application. The server is non-visual.
- An MCP client to talk to. A desktop AI assistant that launches your executable over STDIO is the quickest way to see it work.
Add TMS.MCP.Attributes to the unit that declares your service class, and
TMS.MCP.Attributes.Server to the unit that creates the server. You do not need
TMS.MCP.Tools unless you also register tools by hand.
Write a plain class
Nothing about the class is special. It does not descend from a framework base
class, it holds whatever state it likes, and its methods have ordinary Delphi
signatures. The only requirement is that the methods you want to expose are
visible to RTTI — that means public or published, because the default
{$RTTI} setting emits method information for those two visibilities only. A
private or protected method is invisible to the reflection pass no matter
how you decorate it.
unit CatalogService;
interface
uses
TMS.MCP.Attributes;
type
TCatalogService = class
public
[TTMSMCPToolAttribute]
[TTMSMCPNameAttribute('lookup_price')]
[TTMSMCPDescriptionAttribute('Returns the current unit price of one catalog article')]
[TTMSMCPReadOnlyAttribute]
function LookupPrice(const ASku: string): Double;
[TTMSMCPToolAttribute]
[TTMSMCPNameAttribute('restock')]
[TTMSMCPDescriptionAttribute('Adds units to the stock level of one catalog article')]
procedure Restock(const ASku: string; AUnits: Integer);
end;
implementation
uses
System.SysUtils;
function TCatalogService.LookupPrice(const ASku: string): Double;
begin
Result := CatalogDatabase.PriceOf(ASku);
end;
procedure TCatalogService.Restock(const ASku: string; AUnits: Integer);
begin
if AUnits <= 0 then
raise Exception.Create('AUnits must be greater than zero');
CatalogDatabase.AddStock(ASku, AUnits);
end;
end.
[TTMSMCPToolAttribute] is the only attribute that is strictly required; every other one
refines what the generated tool looks like. Without a [TTMSMCPNameAttribute] the tool is
named <ClassName>_<MethodName> — TCatalogService_LookupPrice in the example
above — which works but reads poorly in a client, so name your tools explicitly.
Delphi lets you drop the Attribute suffix in an attribute reference, so
[TTMSMCPNameAttribute('restock')] may also be written [TTMSMCPName('restock')].
These pages use the full class name throughout: it matches the API reference, and
it keeps [TTMSMCPToolAttribute] distinct from the TTMSMCPTool class that the
attribute generates.
Host it
TTMSMCPServerFactory creates a
TTMSMCPAttributedServer and registers
the target in one call. By the time it returns, the server already carries one
tool per attributed method:
program CatalogServer;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
TMS.MCP.Attributes.Server,
TMS.MCP.Transport.STDIO,
CatalogService in 'CatalogService.pas';
var
Service: TCatalogService;
Server: TTMSMCPAttributedServer;
begin
Service := TCatalogService.Create;
try
{ CreateFromObject reflects over the class straight away, so the server
already carries one tool per attributed method when it returns. }
Server := TTMSMCPServerFactory.CreateFromObject(Service);
try
Server.ServerName := 'Catalog';
Server.ServerVersion := '1.0.0';
Server.Start;
Server.Run;
finally
Server.Free;
end;
finally
{ The factory does not take ownership of the service instance. }
Service.Free;
end;
end.
With no Transport assigned the server uses STDIO, which is what a desktop
assistant expects when it launches your executable. Run blocks and processes
messages until the transport closes. See
MCP Transports for the alternatives.
Ownership
The factory does not take ownership of the instance you pass it. The same is
true of TTMSMCPAttributedServer.AddObject. To hand an instance over, set
OwnsObject on the returned TTMSMCPTarget —
the target then frees it when the server is destroyed. Decide once, per target,
and do not free it yourself as well.
What the client sees
The client lists lookup_price and restock, each with the description you
wrote, and an input schema derived from the method parameters: ASku as a
string, AUnits as an integer, both required. lookup_price is advertised as
read-only, so a client may call it without asking the user first. Nothing else
is required to make the class reachable by an agent.
See also
- User guide — the full feature set
- Declaring tools with attributes — the attribute vocabulary
- Parameters and schema — how arguments are typed
- Hosting attributed servers — targets and ownership
- API reference