diff --git a/src/Microsoft.Agents.A365.DevTools.Cli/Constants/JsonRpcConstants.cs b/src/Microsoft.Agents.A365.DevTools.Cli/Constants/JsonRpcConstants.cs
new file mode 100644
index 0000000..36951a3
--- /dev/null
+++ b/src/Microsoft.Agents.A365.DevTools.Cli/Constants/JsonRpcConstants.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace Microsoft.Agents.A365.DevTools.Cli.Constants;
+
+///
+/// JSON-RPC 2.0 specification constants
+///
+public static class JsonRpcConstants
+{
+ ///
+ /// JSON-RPC version string
+ ///
+ public const string Version = "2.0";
+
+ ///
+ /// JSON-RPC error codes per JSON-RPC 2.0 specification
+ /// See: https://www.jsonrpc.org/specification#error_object
+ ///
+ public static class ErrorCodes
+ {
+ ///
+ /// Invalid Request (-32600) - The JSON sent is not a valid Request object
+ ///
+ public const int InvalidRequest = -32600;
+
+ ///
+ /// Method not found (-32601) - The method does not exist / is not available
+ ///
+ public const int MethodNotFound = -32601;
+
+ ///
+ /// Invalid params (-32602) - Invalid method parameter(s)
+ ///
+ public const int InvalidParams = -32602;
+
+ ///
+ /// Internal error (-32603) - Internal JSON-RPC error
+ ///
+ public const int InternalError = -32603;
+ }
+
+ ///
+ /// HTTP status codes for MCP protocol
+ ///
+ public static class HttpStatusCodes
+ {
+ ///
+ /// Accepted (202) - Used for MCP notifications per Streamable HTTP spec
+ /// Notifications do not expect a response body
+ ///
+ public const int Accepted = 202;
+ }
+}
diff --git a/src/Microsoft.Agents.A365.DevTools.MockToolingServer/Constants/JsonRpcConstants.cs b/src/Microsoft.Agents.A365.DevTools.MockToolingServer/Constants/JsonRpcConstants.cs
new file mode 100644
index 0000000..032097b
--- /dev/null
+++ b/src/Microsoft.Agents.A365.DevTools.MockToolingServer/Constants/JsonRpcConstants.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace Microsoft.Agents.A365.DevTools.MockToolingServer.Constants;
+
+///
+/// JSON-RPC 2.0 specification constants
+///
+public static class JsonRpcConstants
+{
+ ///
+ /// JSON-RPC version string
+ ///
+ public const string Version = "2.0";
+
+ ///
+ /// JSON-RPC error codes per JSON-RPC 2.0 specification
+ /// See: https://www.jsonrpc.org/specification#error_object
+ ///
+ public static class ErrorCodes
+ {
+ ///
+ /// Invalid Request (-32600) - The JSON sent is not a valid Request object
+ ///
+ public const int InvalidRequest = -32600;
+
+ ///
+ /// Method not found (-32601) - The method does not exist / is not available
+ ///
+ public const int MethodNotFound = -32601;
+
+ ///
+ /// Invalid params (-32602) - Invalid method parameter(s)
+ ///
+ public const int InvalidParams = -32602;
+
+ ///
+ /// Internal error (-32603) - Internal JSON-RPC error
+ ///
+ public const int InternalError = -32603;
+ }
+
+ ///
+ /// HTTP status codes for MCP protocol
+ ///
+ public static class HttpStatusCodes
+ {
+ ///
+ /// Accepted (202) - Used for MCP notifications per Streamable HTTP spec
+ /// Notifications do not expect a response body
+ ///
+ public const int Accepted = 202;
+ }
+}
diff --git a/src/Microsoft.Agents.A365.DevTools.MockToolingServer/Server.cs b/src/Microsoft.Agents.A365.DevTools.MockToolingServer/Server.cs
index c1e0653..8765f89 100644
--- a/src/Microsoft.Agents.A365.DevTools.MockToolingServer/Server.cs
+++ b/src/Microsoft.Agents.A365.DevTools.MockToolingServer/Server.cs
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using Microsoft.Agents.A365.DevTools.MockToolingServer.Constants;
+
namespace Microsoft.Agents.A365.DevTools.MockToolingServer;
public static class Server
@@ -87,11 +89,13 @@ public static async Task Start(string[] args)
// JSON-RPC over HTTP for mock tools at /mcp-mock
app.MapPost("/agents/servers/{mcpServerName}", async (string mcpServerName, HttpRequest httpRequest, IMockToolExecutor executor, ILogger log) =>
{
+ // Declare idValue outside try block so catch handler can preserve original request ID.
+ // This ensures error responses include the correct ID from the client's request.
+ object? idValue = null;
try
{
using var doc = await JsonDocument.ParseAsync(httpRequest.Body);
var root = doc.RootElement;
- object? idValue = null;
if (root.TryGetProperty("id", out var idProp))
{
if (idProp.ValueKind == JsonValueKind.Number)
@@ -108,6 +112,8 @@ public static async Task Start(string[] args)
}
}
+ // Validate that 'method' field exists and is a string (JSON-RPC 2.0 requirement).
+ // All subsequent code can safely assume 'method' is non-null after this check.
if (!root.TryGetProperty("method", out var methodProp) || methodProp.ValueKind != JsonValueKind.String)
{
return Results.BadRequest(new { error = "Invalid or missing 'method' property." });
@@ -145,17 +151,35 @@ public static async Task Start(string[] args)
},
instructions = "Optional instructions for the client"
};
- return Results.Json(new { jsonrpc = "2.0", id = idValue, result = initializeResult });
+ return Results.Json(new { jsonrpc = JsonRpcConstants.Version, id = idValue, result = initializeResult });
}
if (string.Equals(method, "logging/setLevel", StringComparison.OrdinalIgnoreCase))
{
// Acknowledge but do nothing
- return Results.Json(new { jsonrpc = "2.0", id = idValue, result = new { } });
+ return Results.Json(new { jsonrpc = JsonRpcConstants.Version, id = idValue, result = new { } });
}
if (string.Equals(method, "tools/list", StringComparison.OrdinalIgnoreCase))
{
- var listResult = await executor.ListToolsAsync(mcpServerName);
- return Results.Json(new { jsonrpc = "2.0", id = idValue, result = listResult });
+ try
+ {
+ var listResult = await executor.ListToolsAsync(mcpServerName);
+ return Results.Json(new { jsonrpc = JsonRpcConstants.Version, id = idValue, result = listResult });
+ }
+ catch (ArgumentException ex)
+ {
+ // Unknown MCP server name - return JSON-RPC error (consistent with tools/call)
+ log.LogWarning(ex, "No mock tool store for '{McpServerName}' - returning error", mcpServerName);
+ return Results.Json(new
+ {
+ jsonrpc = JsonRpcConstants.Version,
+ id = idValue,
+ error = new
+ {
+ code = JsonRpcConstants.ErrorCodes.InvalidParams,
+ message = $"MCP server '{mcpServerName}' not found"
+ }
+ });
+ }
}
if (string.Equals(method, "tools/call", StringComparison.OrdinalIgnoreCase))
{
@@ -187,22 +211,81 @@ public static async Task Start(string[] args)
argsDict[prop.Name] = converted;
}
}
- var callResult = await executor.CallToolAsync(mcpServerName, name, argsDict!);
- // Detect error shape
- var errorProp = callResult.GetType().GetProperty("error");
- if (errorProp != null)
+ try
{
- return Results.Json(new { jsonrpc = "2.0", id = idValue, error = errorProp.GetValue(callResult) });
+ var callResult = await executor.CallToolAsync(mcpServerName, name, argsDict!);
+ // Detect error shape
+ var errorProp = callResult.GetType().GetProperty("error");
+ if (errorProp != null)
+ {
+ return Results.Json(new { jsonrpc = JsonRpcConstants.Version, id = idValue, error = errorProp.GetValue(callResult) });
+ }
+ return Results.Json(new { jsonrpc = JsonRpcConstants.Version, id = idValue, result = callResult });
+ }
+ catch (ArgumentException ex)
+ {
+ // Unknown MCP server name
+ log.LogWarning(ex, "No mock tools available for server '{McpServerName}'", mcpServerName);
+ return Results.Json(new
+ {
+ jsonrpc = JsonRpcConstants.Version,
+ id = idValue,
+ error = new
+ {
+ code = JsonRpcConstants.ErrorCodes.InvalidParams,
+ message = $"No mock tools available for server '{mcpServerName}'"
+ }
+ });
}
- return Results.Json(new { jsonrpc = "2.0", id = idValue, result = callResult });
}
- return Results.Json(new { jsonrpc = "2.0", id = idValue, error = new { code = -32601, message = $"Method ({method}) not found" } });
+ // Handle MCP ping requests (used by clients to verify connection health)
+ if (string.Equals(method, "ping", StringComparison.OrdinalIgnoreCase))
+ {
+ return Results.Json(new { jsonrpc = JsonRpcConstants.Version, id = idValue, result = new { } });
+ }
+ // Handle prompts/list requests (return empty list - no mock prompts)
+ if (string.Equals(method, "prompts/list", StringComparison.OrdinalIgnoreCase))
+ {
+ return Results.Json(new { jsonrpc = JsonRpcConstants.Version, id = idValue, result = new { prompts = Array.Empty