Skip to content

Commit 0b1d7c7

Browse files
Copilotstephentoub
andcommitted
Include return description in tool description when UseStructuredContent is false
Co-authored-by: stephentoub <[email protected]>
1 parent 140b422 commit 0b1d7c7

File tree

2 files changed

+134
-1
lines changed

2 files changed

+134
-1
lines changed

src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ private static AIFunctionFactoryOptions CreateAIFunctionFactoryOptions(
118118
Tool tool = new()
119119
{
120120
Name = options?.Name ?? function.Name,
121-
Description = options?.Description ?? function.Description,
121+
Description = GetToolDescription(function, options),
122122
InputSchema = function.JsonSchema,
123123
OutputSchema = CreateOutputSchema(function, options, out bool structuredOutputRequiresWrapping),
124124
Icons = options?.Icons,
@@ -406,6 +406,57 @@ private static void ValidateToolName(string name)
406406
}
407407
}
408408

409+
/// <summary>
410+
/// Gets the tool description, synthesizing from both the function description and return description when appropriate.
411+
/// </summary>
412+
/// <remarks>
413+
/// When UseStructuredContent is true, the return description is included in the output schema.
414+
/// When UseStructuredContent is false (default), if there's a return description in the ReturnJsonSchema,
415+
/// it will be appended to the tool description so the information is still available to consumers.
416+
/// </remarks>
417+
private static string? GetToolDescription(AIFunction function, McpServerToolCreateOptions? options)
418+
{
419+
string? description = options?.Description ?? function.Description;
420+
421+
// If structured content is enabled, the return description will be in the output schema
422+
if (options?.UseStructuredContent is true)
423+
{
424+
return description;
425+
}
426+
427+
// When structured content is disabled, try to extract the return description from ReturnJsonSchema
428+
// and append it to the tool description so the information is available to consumers
429+
string? returnDescription = GetReturnDescription(function.ReturnJsonSchema);
430+
if (returnDescription is null)
431+
{
432+
return description;
433+
}
434+
435+
// Synthesize a combined description
436+
if (string.IsNullOrEmpty(description))
437+
{
438+
return $"Returns: {returnDescription}";
439+
}
440+
441+
return $"{description} Returns: {returnDescription}";
442+
}
443+
444+
/// <summary>
445+
/// Extracts the description property from a ReturnJsonSchema if present.
446+
/// </summary>
447+
private static string? GetReturnDescription(JsonElement? returnJsonSchema)
448+
{
449+
if (returnJsonSchema is not JsonElement schema ||
450+
schema.ValueKind != JsonValueKind.Object ||
451+
!schema.TryGetProperty("description", out JsonElement descriptionElement) ||
452+
descriptionElement.ValueKind != JsonValueKind.String)
453+
{
454+
return null;
455+
}
456+
457+
return descriptionElement.GetString();
458+
}
459+
409460
private static JsonElement? CreateOutputSchema(AIFunction function, McpServerToolCreateOptions? toolCreateOptions, out bool structuredOutputRequiresWrapping)
410461
{
411462
structuredOutputRequiresWrapping = false;

tests/ModelContextProtocol.Tests/Server/McpServerToolTests.cs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using ModelContextProtocol.Protocol;
55
using ModelContextProtocol.Server;
66
using Moq;
7+
using System.ComponentModel;
78
using System.Reflection;
89
using System.Runtime.InteropServices;
910
using System.Text.Json;
@@ -743,6 +744,87 @@ public void SupportsToolWithoutIcons()
743744
Assert.Null(tool.ProtocolTool.Icons);
744745
}
745746

747+
[Fact]
748+
public void ReturnDescription_StructuredOutputDisabled_IncludedInToolDescription()
749+
{
750+
// When UseStructuredContent is false (default), return description should be appended to tool description
751+
McpServerTool tool = McpServerTool.Create(ToolWithReturnDescription);
752+
753+
Assert.Equal("Tool that returns data. Returns: The computed result", tool.ProtocolTool.Description);
754+
Assert.Null(tool.ProtocolTool.OutputSchema);
755+
}
756+
757+
[Fact]
758+
public void ReturnDescription_StructuredOutputEnabled_NotIncludedInToolDescription()
759+
{
760+
// When UseStructuredContent is true, return description should be in the output schema, not in tool description
761+
McpServerTool tool = McpServerTool.Create(ToolWithReturnDescription, new() { UseStructuredContent = true });
762+
763+
Assert.Equal("Tool that returns data.", tool.ProtocolTool.Description);
764+
Assert.NotNull(tool.ProtocolTool.OutputSchema);
765+
// Verify the output schema contains the description
766+
Assert.True(tool.ProtocolTool.OutputSchema.Value.TryGetProperty("properties", out var properties));
767+
Assert.True(properties.TryGetProperty("result", out var result));
768+
Assert.True(result.TryGetProperty("description", out var description));
769+
Assert.Equal("The computed result", description.GetString());
770+
}
771+
772+
[Fact]
773+
public void ReturnDescription_NoFunctionDescription_OnlyReturnsDescription()
774+
{
775+
// When there's no function description but there's a return description
776+
McpServerTool tool = McpServerTool.Create(ToolWithOnlyReturnDescription);
777+
778+
Assert.Equal("Returns: The computed result", tool.ProtocolTool.Description);
779+
Assert.Null(tool.ProtocolTool.OutputSchema);
780+
}
781+
782+
[Fact]
783+
public void ReturnDescription_ExplicitDescriptionOption_SynthesizesWithReturnDescription()
784+
{
785+
// When Description is explicitly set in options and there's a return description,
786+
// the return description should be appended since UseStructuredContent is false
787+
McpServerTool tool = McpServerTool.Create(ToolWithReturnDescription, new() { Description = "Custom description" });
788+
789+
Assert.Equal("Custom description Returns: The computed result", tool.ProtocolTool.Description);
790+
Assert.Null(tool.ProtocolTool.OutputSchema);
791+
}
792+
793+
[Fact]
794+
public void ReturnDescription_NoReturnDescription_NoChange()
795+
{
796+
// When there's no return description, the tool description should remain unchanged
797+
McpServerTool tool = McpServerTool.Create(ToolWithoutReturnDescription);
798+
799+
Assert.Equal("Tool without return description.", tool.ProtocolTool.Description);
800+
Assert.Null(tool.ProtocolTool.OutputSchema);
801+
}
802+
803+
[Fact]
804+
public void ReturnDescription_StructuredOutputEnabled_WithExplicitDescription_NoSynthesis()
805+
{
806+
// When UseStructuredContent is true and Description is set, return description goes to output schema
807+
McpServerTool tool = McpServerTool.Create(ToolWithReturnDescription, new()
808+
{
809+
Description = "Custom description",
810+
UseStructuredContent = true
811+
});
812+
813+
// Description should not have the return description appended
814+
Assert.Equal("Custom description", tool.ProtocolTool.Description);
815+
Assert.NotNull(tool.ProtocolTool.OutputSchema);
816+
}
817+
818+
[Description("Tool that returns data.")]
819+
[return: Description("The computed result")]
820+
private static string ToolWithReturnDescription() => "result";
821+
822+
[return: Description("The computed result")]
823+
private static string ToolWithOnlyReturnDescription() => "result";
824+
825+
[Description("Tool without return description.")]
826+
private static string ToolWithoutReturnDescription() => "result";
827+
746828
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
747829
[JsonSerializable(typeof(DisposableToolType))]
748830
[JsonSerializable(typeof(AsyncDisposableToolType))]

0 commit comments

Comments
 (0)