The attribute layer turns an ordinary Delphi class into an MCP server surface. Instead of building a TTMSMCPTool, declaring each input property, and wiring a callback, you mark a method with [TTMSMCPToolAttribute], describe it and its parameters with a handful of metadata attributes, and hand the class to a TTMSMCPAttributedServer. The server reflects over the type with RTTI, derives the tool name, description, behaviour hints, input schema, and return type from the declaration itself, and invokes the method for you when a client calls it. The result is that the documentation a model reads lives next to the code it describes, and cannot drift away from it.
Implement it on a target to receive a reference to the hosting server.
Minimal example
Turn a plain class into a tool surfacePascalC++
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.
// C++Builder cannot declare the RTTI attributes of the Delphi API on a class.
// Declare the attributed class in a Delphi unit and use it from C++, or - as
// here - expose the same two operations through the tool builder, which is
// the manual counterpart of the attributes.
class TLookupPriceHandler : public TCppInterfacedObject<TTMSMCPMethod>
{
public:
TValue __fastcall Invoke(const TValue *Args, const NativeInt Args_High)
{
return TValue::From<double>(CatalogPriceOf(TValue(Args[0]).AsString()));
}
};
class TRestockHandler : public TCppInterfacedObject<TTMSMCPMethod>
{
public:
TValue __fastcall Invoke(const TValue *Args, const NativeInt Args_High)
{
int Units = TValue(Args[1]).AsInteger();
if (Units <= 0)
throw Exception("AUnits must be greater than zero");
CatalogAddStock(TValue(Args[0]).AsString(), Units);
return TValue();
}
};
void RegisterCatalogTools(TTMSMCPServer *AServer)
{
TTMSMCPTool::CreateBuilder();
TTMSMCPToolBuilder::Name("lookup_price");
TTMSMCPToolBuilder::Description("Returns the current unit price of one catalog article");
TTMSMCPToolBuilder::ReturnType(TTMSMCPToolPropertyType::ptFloat);
TTMSMCPToolBuilder::ReadOnlyHint(true);
TTMSMCPToolBuilder::ExecuteCallback(_di_TTMSMCPMethod(new TLookupPriceHandler()));
TTMSMCPToolBuilder::AddProperty();
TTMSMCPToolPropertyBuilder::Name("ASku");
TTMSMCPToolPropertyBuilder::PropertyType(TTMSMCPToolPropertyType::ptString);
TTMSMCPToolPropertyBuilder::Required(true);
TTMSMCPToolPropertyBuilder::End();
AServer->Tools->Add(TTMSMCPToolBuilder::Build());
TTMSMCPTool::CreateBuilder();
TTMSMCPToolBuilder::Name("restock");
TTMSMCPToolBuilder::Description("Adds units to the stock level of one catalog article");
TTMSMCPToolBuilder::ExecuteCallback(_di_TTMSMCPMethod(new TRestockHandler()));
TTMSMCPToolBuilder::AddProperty();
TTMSMCPToolPropertyBuilder::Name("ASku");
TTMSMCPToolPropertyBuilder::PropertyType(TTMSMCPToolPropertyType::ptString);
TTMSMCPToolPropertyBuilder::Required(true);
TTMSMCPToolPropertyBuilder::End();
TTMSMCPToolBuilder::AddProperty();
TTMSMCPToolPropertyBuilder::Name("AUnits");
TTMSMCPToolPropertyBuilder::PropertyType(TTMSMCPToolPropertyType::ptInteger);
TTMSMCPToolPropertyBuilder::Required(true);
TTMSMCPToolPropertyBuilder::End();
AServer->Tools->Add(TTMSMCPToolBuilder::Build());
}