Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Libraries/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -4533,7 +4533,7 @@ dotnet_diagnostic.S3994.severity = none
# Title : URI return values should not be strings
# Category : Major Code Smell
# Help Link: https://rules.sonarsource.com/csharp/RSPEC-3995
dotnet_diagnostic.S3995.severity = warning
dotnet_diagnostic.S3995.severity = suggestion

# Title : URI properties should not be strings
# Category : Major Code Smell
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class UriContent : AIContent
/// <exception cref="ArgumentNullException"><paramref name="uri"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="mediaType"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException"><paramref name="mediaType"/> is an invalid media type.</exception>
/// <exception cref="UriFormat"><paramref name="uri"/> is an invalid URL.</exception>
/// <exception cref="UriFormatException"><paramref name="uri"/> is an invalid URL.</exception>
/// <remarks>
/// A media type must be specified, so that consumers know what to do with the content.
/// If an exact media type is not known, but the category (e.g. image) is known, a wildcard
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,31 @@ public HostedMcpServerTool(string serverName, string serverAddress)
ServerAddress = Throw.IfNullOrWhitespace(serverAddress);
}

/// <summary>
/// Initializes a new instance of the <see cref="HostedMcpServerTool"/> class.
/// </summary>
/// <param name="serverName">The name of the remote MCP server.</param>
/// <param name="serverUrl">The URL of the remote MCP server.</param>
/// <exception cref="ArgumentNullException"><paramref name="serverName"/> or <paramref name="serverUrl"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException"><paramref name="serverName"/> is empty or composed entirely of whitespace.</exception>
/// <exception cref="ArgumentException"><paramref name="serverUrl"/> is not an absolute URL.</exception>
public HostedMcpServerTool(string serverName, Uri serverUrl)
: this(serverName, ValidateUrl(serverUrl))
{
}

private static string ValidateUrl(Uri serverUrl)
{
_ = Throw.IfNull(serverUrl);

if (!serverUrl.IsAbsoluteUri)
{
Throw.ArgumentException(nameof(serverUrl), "The provided URL is not absolute.");
}

return serverUrl.AbsoluteUri;
}

/// <inheritdoc />
public override string Name => "mcp";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class HostedMcpServerToolTests
[Fact]
public void Constructor_PropsDefault()
{
HostedMcpServerTool tool = new("serverName", "https://localhost/");
HostedMcpServerTool tool = new("serverName", new Uri("https://localhost/"));

Assert.Empty(tool.AdditionalProperties);

Expand Down Expand Up @@ -70,9 +70,14 @@ public void Constructor_Roundtrips()
[Fact]
public void Constructor_Throws()
{
Assert.Throws<ArgumentException>(() => new HostedMcpServerTool(string.Empty, "https://localhost/"));
Assert.Throws<ArgumentNullException>(() => new HostedMcpServerTool(null!, "https://localhost/"));
Assert.Throws<ArgumentException>(() => new HostedMcpServerTool("name", string.Empty));
Assert.Throws<ArgumentNullException>(() => new HostedMcpServerTool("name", null!));
Assert.Throws<ArgumentException>("serverName", () => new HostedMcpServerTool(string.Empty, "https://localhost/"));
Assert.Throws<ArgumentException>("serverName", () => new HostedMcpServerTool(string.Empty, new Uri("https://localhost/")));
Assert.Throws<ArgumentNullException>("serverName", () => new HostedMcpServerTool(null!, "https://localhost/"));
Assert.Throws<ArgumentNullException>("serverName", () => new HostedMcpServerTool(null!, new Uri("https://localhost/")));

Assert.Throws<ArgumentException>("serverAddress", () => new HostedMcpServerTool("name", string.Empty));
Assert.Throws<ArgumentException>("serverUrl", () => new HostedMcpServerTool("name", new Uri("/api/mcp", UriKind.Relative)));
Assert.Throws<ArgumentNullException>("serverAddress", () => new HostedMcpServerTool("name", (string)null!));
Assert.Throws<ArgumentNullException>("serverUrl", () => new HostedMcpServerTool("name", (Uri)null!));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public async Task RemoteMCP_ListTools()

ChatOptions chatOptions = new()
{
Tools = [new HostedMcpServerTool("deepwiki", "https://mcp.deepwiki.com/mcp") { ApprovalMode = HostedMcpServerToolApprovalMode.NeverRequire }],
Tools = [new HostedMcpServerTool("deepwiki", new Uri("https://mcp.deepwiki.com/mcp")) { ApprovalMode = HostedMcpServerToolApprovalMode.NeverRequire }],
};

ChatResponse response = await CreateChatClient()!.GetResponseAsync("Which tools are available on the wiki_tools MCP server?", chatOptions);
Expand All @@ -96,7 +96,7 @@ async Task RunAsync(bool streaming, bool requireSpecific)
{
ChatOptions chatOptions = new()
{
Tools = [new HostedMcpServerTool("deepwiki", "https://mcp.deepwiki.com/mcp")
Tools = [new HostedMcpServerTool("deepwiki", new Uri("https://mcp.deepwiki.com/mcp"))
{
ApprovalMode = requireSpecific ?
HostedMcpServerToolApprovalMode.RequireSpecific(null, ["read_wiki_structure", "ask_question"]) :
Expand Down Expand Up @@ -136,7 +136,7 @@ async Task RunAsync(bool streaming, bool requireSpecific, bool useConversationId
{
ChatOptions chatOptions = new()
{
Tools = [new HostedMcpServerTool("deepwiki", "https://mcp.deepwiki.com/mcp")
Tools = [new HostedMcpServerTool("deepwiki", new Uri("https://mcp.deepwiki.com/mcp"))
{
ApprovalMode = requireSpecific ?
HostedMcpServerToolApprovalMode.RequireSpecific(["read_wiki_structure", "ask_question"], null) :
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -929,7 +929,7 @@ public async Task McpToolCall_ApprovalRequired_NonStreaming(string role)

var chatOptions = new ChatOptions
{
Tools = [new HostedMcpServerTool("deepwiki", "https://mcp.deepwiki.com/mcp")]
Tools = [new HostedMcpServerTool("deepwiki", new Uri("https://mcp.deepwiki.com/mcp"))]
};
McpServerToolApprovalRequestContent approvalRequest;

Expand Down Expand Up @@ -1307,7 +1307,7 @@ public async Task McpToolCall_ApprovalNotRequired_NonStreaming(bool rawTool)

AITool mcpTool = rawTool ?
ResponseTool.CreateMcpTool("deepwiki", serverUri: new("https://mcp.deepwiki.com/mcp"), toolCallApprovalPolicy: new McpToolCallApprovalPolicy(GlobalMcpToolCallApprovalPolicy.NeverRequireApproval)).AsAITool() :
new HostedMcpServerTool("deepwiki", "https://mcp.deepwiki.com/mcp")
new HostedMcpServerTool("deepwiki", new Uri("https://mcp.deepwiki.com/mcp"))
{
ApprovalMode = HostedMcpServerToolApprovalMode.NeverRequire,
};
Expand Down Expand Up @@ -1723,7 +1723,7 @@ public async Task McpToolCall_ApprovalNotRequired_Streaming()

ChatOptions chatOptions = new()
{
Tools = [new HostedMcpServerTool("deepwiki", "https://mcp.deepwiki.com/mcp")
Tools = [new HostedMcpServerTool("deepwiki", new Uri("https://mcp.deepwiki.com/mcp"))
{
ApprovalMode = HostedMcpServerToolApprovalMode.NeverRequire,
}
Expand Down
Loading