using System;
using System.Collections;
using System.Reflection;
using UnityEngine;
namespace LocalMcp.UnityServer
{
/// <summary>
/// Represents a JSON-RPC 2.0 request.
/// </summary>
[Serializable]
public class JsonRpcRequest
{
public string jsonrpc = "2.0";
public string method;
public object @params;
public object id;
/// <summary>
/// Parses a JSON string into a JsonRpcRequest object.
/// </summary>
public static JsonRpcRequest Parse(string json)
{
try
{
return JsonUtility.FromJson<JsonRpcRequest>(json);
}
catch (Exception e)
{
Debug.LogError($"[MCP] Failed to parse JSON-RPC request: {e.Message}");
return null;
}
}
}
/// <summary>
/// Represents a JSON-RPC 2.0 response.
/// </summary>
[Serializable]
public class JsonRpcResponse
{
public string jsonrpc = "2.0";
public object result;
public JsonRpcError error;
public object id;
/// <summary>
/// Creates a successful response.
/// </summary>
public static JsonRpcResponse Success(object result, object id)
{
return new JsonRpcResponse
{
result = result,
id = id
};
}
/// <summary>
/// Creates an error response.
/// </summary>
public static JsonRpcResponse Error(int code, string message, object id)
{
return new JsonRpcResponse
{
error = new JsonRpcError
{
code = code,
message = message
},
id = id
};
}
/// <summary>
/// Converts the response to a JSON string.
/// Note: We manually construct JSON because JsonUtility cannot serialize 'object' type fields or anonymous types.
/// </summary>
public string ToJson()
{
var sb = new System.Text.StringBuilder();
sb.Append("{");
sb.Append("\"jsonrpc\":\"2.0\"");
if (result != null)
{
sb.Append(",\"result\":");
sb.Append(SerializeObject(result));
}
if (error != null)
{
sb.Append(",\"error\":");
sb.Append(JsonUtility.ToJson(error));
}
if (id != null)
{
sb.Append(",\"id\":");
if (id is string)
{
sb.Append("\"").Append(EscapeJsonString((string)id)).Append("\"");
}
else
{
sb.Append(id.ToString());
}
}
sb.Append("}");
return sb.ToString();
}
/// <summary>
/// Serializes an object to JSON, handling anonymous types, arrays, and nested objects.
/// </summary>
private static string SerializeObject(object obj)
{
if (obj == null)
return "null";
var type = obj.GetType();
// Handle primitive types
if (obj is string str)
{
return "\"" + EscapeJsonString(str) + "\"";
}
if (obj is bool b)
{
return b ? "true" : "false";
}
if (obj is int || obj is long || obj is short || obj is byte)
{
return obj.ToString();
}
if (obj is float f)
{
return f.ToString(System.Globalization.CultureInfo.InvariantCulture);
}
if (obj is double d)
{
return d.ToString(System.Globalization.CultureInfo.InvariantCulture);
}
// Handle arrays and collections
if (obj is IEnumerable enumerable && !(obj is string))
{
var sb = new System.Text.StringBuilder();
sb.Append("[");
bool first = true;
foreach (var item in enumerable)
{
if (!first) sb.Append(",");
sb.Append(SerializeObject(item));
first = false;
}
sb.Append("]");
return sb.ToString();
}
// Check if it's an anonymous type or class with public properties
if (type.IsClass && type != typeof(string))
{
// For Unity serializable types, try JsonUtility first
if (type.GetCustomAttribute<SerializableAttribute>() != null && !type.Name.Contains("AnonymousType"))
{
try
{
string json = JsonUtility.ToJson(obj);
if (!string.IsNullOrEmpty(json) && json != "{}")
{
return json;
}
}
catch { }
}
// Use reflection for anonymous types or when JsonUtility fails
var sb = new System.Text.StringBuilder();
sb.Append("{");
bool first = true;
// Get all public properties (for anonymous types)
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var prop in properties)
{
if (!prop.CanRead) continue;
var value = prop.GetValue(obj);
if (!first) sb.Append(",");
sb.Append("\"").Append(prop.Name).Append("\":");
sb.Append(SerializeObject(value));
first = false;
}
// Also get public fields (for Unity-style serializable classes)
var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance);
foreach (var field in fields)
{
var value = field.GetValue(obj);
if (!first) sb.Append(",");
sb.Append("\"").Append(field.Name).Append("\":");
sb.Append(SerializeObject(value));
first = false;
}
sb.Append("}");
return sb.ToString();
}
// Fallback to ToString for unknown types
return "\"" + EscapeJsonString(obj.ToString()) + "\"";
}
/// <summary>
/// Escapes special characters in JSON strings.
/// </summary>
private static string EscapeJsonString(string str)
{
if (string.IsNullOrEmpty(str))
return str;
return str
.Replace("\\", "\\\\")
.Replace("\"", "\\\"")
.Replace("\n", "\\n")
.Replace("\r", "\\r")
.Replace("\t", "\\t");
}
}
/// <summary>
/// Represents a JSON-RPC error object.
/// </summary>
[Serializable]
public class JsonRpcError
{
public int code;
public string message;
public object data;
}
/// <summary>
/// Standard JSON-RPC error codes.
/// </summary>
public static class JsonRpcErrorCodes
{
public const int ParseError = -32700;
public const int InvalidRequest = -32600;
public const int MethodNotFound = -32601;
public const int InvalidParams = -32602;
public const int InternalError = -32603;
}
/// <summary>
/// Simple result class for success responses (JsonUtility requires explicit class, not anonymous type).
/// </summary>
[Serializable]
public class SimpleResult
{
public string status;
public string message;
public SimpleResult() { }
public SimpleResult(string status, string message)
{
this.status = status;
this.message = message;
}
}
/// <summary>
/// Helper class for creating JSON-RPC responses.
/// </summary>
public static class JsonRpcResponseHelper
{
/// <summary>
/// Creates a success response with a simple message.
/// </summary>
public static string SuccessMessage(string message, object id)
{
var response = JsonRpcResponse.Success(new SimpleResult("success", message), id);
return response.ToJson();
}
/// <summary>
/// Creates a success response with raw JSON result.
/// The resultJson should be a valid JSON object/value that will be used as-is in the result field.
/// </summary>
public static string SuccessJson(string resultJson, object id)
{
string idStr = id is string ? $"\"{id}\"" : id?.ToString() ?? "null";
return $"{{\"jsonrpc\":\"2.0\",\"id\":{idStr},\"result\":{resultJson}}}";
}
/// <summary>
/// Creates an error response with a message.
/// </summary>
public static string ErrorMessage(string message, object id)
{
var response = JsonRpcResponse.Error(JsonRpcErrorCodes.InternalError, message, id);
return response.ToJson();
}
/// <summary>
/// Creates a method not found error response.
/// </summary>
public static string MethodNotFound(string method, object id)
{
var response = JsonRpcResponse.Error(
JsonRpcErrorCodes.MethodNotFound,
$"Method '{method}' not found",
id
);
return response.ToJson();
}
/// <summary>
/// Creates an invalid params error response.
/// </summary>
public static string InvalidParams(string message, object id)
{
var response = JsonRpcResponse.Error(
JsonRpcErrorCodes.InvalidParams,
message,
id
);
return response.ToJson();
}
}
}