Some models (I use local ones) may frequently use different parameter name capitalization, so it needs to be made case-insensitive, so I had to change this method in Tools:
function TTMSMCPTool.SetArgs(AParams: TJSONObject): TArray;
var
Prop: TTMSMCPToolProperty;
Param: TJSONValue;
l: TTMSMCPValues;
I: Integer;
begin
SetLength(Result, FProperties.Count);
l := TTMSMCPValues.Create;
try
for I := 0 to FProperties.Count - 1 do
begin
Prop := FProperties[I];
Param := nil;
if Assigned(AParams) then
// Param := AParams.Values[Prop.Name];
begin
// Case-insensitive lookup — models cannot be relied upon to match
// the declared parameter casing (e.g. "fileRel" vs "FileRel").
Param := AParams.Values[Prop.Name]; // fast path: exact match
if Param = nil then
for var Pair in AParams do
if SameText(Pair.JsonString.Value, Prop.Name) then
begin
Param := Pair.JsonValue;
Break;
end;
end;
if (Param = nil) and Prop.Required then
RaiseJsonRpcError(TTMSMCPErrorCode.ecInvalidParams, Format('Required parameter "%s" is missing', [Prop.Name]));
if Param <> nil then
begin
try
l.Add(Prop.Name, TValue.FromJSON(Param, Prop.PropertyType));
if (FOwner is TTMSMCPServer) and Assigned((FOwner as TTMSMCPServer).OnBeforeExecute) then
(FOwner as TTMSMCPServer).OnBeforeExecute(Self, AParams, l);
except
on E: Exception do
RaiseJsonRpcError(TTMSMCPErrorCode.ecInvalidParams,
Format('Invalid value for parameter "%s": %s', [Prop.Name, E.Message]));
end;
end;
end;
Result := l.ToValueArray;
finally
l.Free
end;
end;
And then with some MCP-compatible clients, a badly formatted response breaks the chain when it trickles down to some internal XML, so it has to be escaped, so I had to change this in Server (also note that all errors should nudge the model to reconsider its approach, syntax, formatting, or whatever, or it may just keep banging its head against the same wall over and over again):
function TTMSMCPCustomServer.HandleToolsCall(const Params: TJSONValue): TJSONValue;
var
JsonResult: TJSONObject;
ToolResult, NameValue, ArgumentsValue: TJSONValue;
ToolName: string;
Arguments: TJSONObject;
ContentArray: TJSONArray;
ContentItem: TJSONObject;
Tool: TTMSMCPTool;
function SanitizeErrorText(const S: string): string;
begin
Result := S
.Replace('&', '(amp)', [rfReplaceAll])
.Replace('<', '(', [rfReplaceAll])
.Replace('>', ')', [rfReplaceAll]);
end;
begin
if not FInitialized then
RaiseJsonRpcError(TTMSMCPErrorCode.ecServerNotInitialized, 'Server not initialized');
JsonResult := TJSONObject.Create;
if not (Params is TJSONObject) then
RaiseJsonRpcError(TTMSMCPErrorCode.ecInvalidParams, 'Invalid params');
ToolName := '';
NameValue := TJSONObject(Params).GetValue('name');
if Assigned(NameValue) and (NameValue is TJSONString) then
ToolName := TJSONString(NameValue).Value
else
RaiseJsonRpcError(TTMSMCPErrorCode.ecInvalidParams, 'Missing tool name');
ArgumentsValue := TJSONObject(Params).GetValue('arguments');
if Assigned(ArgumentsValue) and (ArgumentsValue is TJSONObject) then
Arguments := TJSONObject(ArgumentsValue)
else
Arguments := TJSONObject.Create;
Tool := Tools.FindByName(ToolName);
if Assigned(Tool) then
begin
try
ToolResult := Tool.ExecuteMethod(Arguments);
JsonResult.AddPair('isError', TJSONBool.Create(False));
ContentArray := TJSONArray.Create;
ContentItem := TJSONObject.Create;
ContentItem.AddPair('type', 'text');
ContentItem.AddPair('text', ToolResult.ToString);
ContentArray.Add(ContentItem);
JsonResult.AddPair('content', ContentArray);
ToolResult.Free;
Exit(JsonResult);
except
on E: Exception do
begin
var ReceivedArgs := '';
if Assigned(Arguments) then
ReceivedArgs := SanitizeErrorText(Arguments.ToJSON);
var ErrMsg :=
'Error executing tool ' + ToolName + ': ' +
SanitizeErrorText(E.Message) + #10 +
'Arguments received: ' + ReceivedArgs + #10 +
'Please retry with corrected arguments.';
JsonResult.AddPair('isError', TJSONBool.Create(True));
ContentArray := TJSONArray.Create;
ContentItem := TJSONObject.Create;
ContentItem.AddPair('type', 'text');
ContentItem.AddPair('text', ErrMsg);
ContentArray.Add(ContentItem);
JsonResult.AddPair('content', ContentArray);
Exit(JsonResult);
end;
end;
end;
JsonResult.AddPair('isError', TJSONBool.Create(True));
ContentArray := TJSONArray.Create;
ContentItem := TJSONObject.Create;
ContentItem.AddPair('type', 'text');
ContentItem.AddPair('text', Format('Unknown tool: %s', [ToolName]));
ContentArray.Add(ContentItem);
JsonResult.AddPair('content', ContentArray);
Exit(JsonResult);
end;
I would appreciate it if you can review and plug these fixes into your components.
TIA, Alex.