Skip to content

Conversation

@GreenShadeZhang
Copy link
Member

@GreenShadeZhang GreenShadeZhang commented Nov 25, 2025

PR Type

Enhancement


Description

  • Update ModelContextProtocol NuGet packages to version 0.4.0-preview.3

  • Replace deprecated API calls with new transport and client interfaces

  • Add null check for MCP client initialization in executor

  • Update content block filtering to use new TextContentBlock type


Diagram Walkthrough

flowchart LR
  A["ModelContextProtocol 0.1.0-preview.11"] -- "upgrade to" --> B["ModelContextProtocol 0.4.0-preview.3"]
  B -- "requires API updates" --> C["SseClientTransport → HttpClientTransport"]
  B -- "requires API updates" --> D["McpClientFactory → McpClient.CreateAsync"]
  B -- "requires API updates" --> E["ContentBlock → TextContentBlock"]
  C --> F["Updated McpClientManager"]
  D --> F
  E --> G["Updated MCPToolExecutor"]
  F --> H["MCP Integration Ready"]
  G --> H
Loading

File Walkthrough

Relevant files
Enhancement
McpClientManager.cs
Update MCP client initialization for new API                         

src/Infrastructure/BotSharp.Core/MCP/Managers/McpClientManager.cs

  • Removed unused ModelContextProtocol.Protocol.Transport import
  • Changed return type from IMcpClient? to McpClient?
  • Replaced SseClientTransport with HttpClientTransport for SSE
    configuration
  • Updated factory call from McpClientFactory.CreateAsync to
    McpClient.CreateAsync
+3/-4     
MCPToolExecutor.cs
Update MCP tool executor for new API and add safety checks

src/Infrastructure/BotSharp.Core/Routing/Executor/MCPToolExecutor.cs

  • Changed import from ModelContextProtocol.Client to
    ModelContextProtocol.Protocol
  • Updated dictionary type from Dictionary to
    Dictionary
  • Added null check for MCP client before calling tool
  • Updated content block filtering to use TextContentBlock type casting
    instead of string comparison
  • Fixed code formatting (spacing around colon in class declaration)
+14/-8   
Configuration changes
Program.cs
Configure HTTP transport for test MCP server                         

tests/BotSharp.PizzaBot.MCPServer/Program.cs

  • Added .WithHttpTransport() configuration to MCP server builder
+1/-0     
Dependencies
Directory.Packages.props
Upgrade ModelContextProtocol NuGet packages                           

Directory.Packages.props

  • Updated ModelContextProtocol package version from 0.1.0-preview.11 to
    0.4.0-preview.3
  • Updated ModelContextProtocol.AspNetCore package version from
    0.1.0-preview.11 to 0.4.0-preview.3
+2/-2     

@qodo-code-review
Copy link

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Missing auditing: The new MCP tool execution path and client null-handling do not emit any audit logs for
critical actions such as tool invocation result handling, making it unclear who performed
what action and its outcome.

Referred Code
public async Task<bool> ExecuteAsync(RoleDialogModel message)
{
    try
    {
        // Convert arguments to dictionary format expected by mcpdotnet
        Dictionary<string, object?> argDict = JsonToDictionary(message.FunctionArgs);

        var clientManager = _services.GetRequiredService<McpClientManager>();
        var client = await clientManager.GetMcpClientAsync(_mcpServerId);

        if (client == null)
        {
            message.Content = $"MCP client for server {_mcpServerId} not found.";
            return false;
        }

        // Call the tool through mcpdotnet
        var result = await client.CallToolAsync(_functionName, !argDict.IsNullOrEmpty() ? argDict : []);

        // Extract the text content from the result
        var json = string.Join("\n", result.Content.Where(c => c is TextContentBlock).Select(c => ((TextContentBlock)c).Text));


 ... (clipped 4 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Null client handling: While a null-check was added for the MCP client, the error path sets a user-facing message
and returns false without logging or capturing context such as server ID or function name
for diagnostics.

Referred Code
if (client == null)
{
    message.Content = $"MCP client for server {_mcpServerId} not found.";
    return false;
}

// Call the tool through mcpdotnet

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Input validation: JSON arguments are parsed into a dictionary and passed to tool execution without explicit
validation or sanitization, which may be acceptable depending on upstream guarantees but
is not visible in this diff.

Referred Code
public async Task<bool> ExecuteAsync(RoleDialogModel message)
{
    try
    {
        // Convert arguments to dictionary format expected by mcpdotnet
        Dictionary<string, object?> argDict = JsonToDictionary(message.FunctionArgs);

        var clientManager = _services.GetRequiredService<McpClientManager>();
        var client = await clientManager.GetMcpClientAsync(_mcpServerId);

        if (client == null)
        {
            message.Content = $"MCP client for server {_mcpServerId} not found.";
            return false;
        }

        // Call the tool through mcpdotnet
        var result = await client.CallToolAsync(_functionName, !argDict.IsNullOrEmpty() ? argDict : []);

        // Extract the text content from the result
        var json = string.Join("\n", result.Content.Where(c => c is TextContentBlock).Select(c => ((TextContentBlock)c).Text));


 ... (clipped 4 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Use standard JSON deserialization method

Replace the custom JsonToDictionary implementation with a call to
System.Text.Json.JsonSerializer.Deserialize to robustly handle all JSON
structures and prevent potential data loss.

src/Infrastructure/BotSharp.Core/Routing/Executor/MCPToolExecutor.cs [59-69]

 private static Dictionary<string, object?> JsonToDictionary(string? json)
 {
     if (string.IsNullOrEmpty(json))
     {
         return [];
     }
 
-    using JsonDocument doc = JsonDocument.Parse(json);
-    JsonElement root = doc.RootElement;
-    return JsonElementToDictionary(root);
+    return JsonSerializer.Deserialize<Dictionary<string, object?>>(json) ?? [];
 }
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a flaw in the custom JSON parsing logic that could lead to data loss and proposes replacing it with the standard JsonSerializer.Deserialize, which is more robust, efficient, and idiomatic.

Medium
Improve JSON deserialization for robustness

Remove the JsonElementToDictionary method because it only handles JSON objects,
which can lead to silent data loss for other JSON types like arrays.

src/Infrastructure/BotSharp.Core/Routing/Executor/MCPToolExecutor.cs [71-84]

-private static Dictionary<string, object?> JsonElementToDictionary(JsonElement element)
-{
-    Dictionary<string, object?> dictionary = [];
+// This method can be removed.
 
-    if (element.ValueKind == JsonValueKind.Object)
-    {
-        foreach (JsonProperty property in element.EnumerateObject())
-        {
-            dictionary[property.Name] = JsonElementToValue(property.Value);
-        }
-    }
-
-    return dictionary;
-}
-
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that the custom JsonElementToDictionary method is not robust and can lead to silent data loss for non-object JSON, proposing its removal in favor of a more reliable standard library approach.

Medium
Learned
best practice
Add null/empty result checks

Add null checks for the tool call result and its content before accessing them
to prevent runtime exceptions when the server returns no content.

src/Infrastructure/BotSharp.Core/Routing/Executor/MCPToolExecutor.cs [37-40]

 var result = await client.CallToolAsync(_functionName, !argDict.IsNullOrEmpty() ? argDict : []);
+if (result?.Content == null || !result.Content.Any())
+{
+    message.Content = $"No content returned from tool {_functionName} of MCP server {_mcpServerId}.";
+    message.Data = null;
+    return false;
+}
 
 var json = string.Join("\n", result.Content.Where(c => c is TextContentBlock).Select(c => ((TextContentBlock)c).Text));
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why:
Relevant best practice - Guard against nulls and invalid states before access to avoid NullReferenceExceptions and ensure robust error handling.

Low
  • More

@Oceania2018 Oceania2018 merged commit 1e511b7 into SciSharp:master Nov 25, 2025
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants