Editor/JsonRpc.cs

Models for JSON-RPC 2.0 messages and errors used by the editor bridge. Defines incoming message shape (JsonRpcMessage), outgoing response (JsonRpcResponse) with helpers for success/failure, an error type (JsonRpcError), and a custom exception (McpException) that carries a JSON-RPC error code.

using System;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;

namespace ProtonMcpBridge;

/// <summary>
/// An incoming JSON-RPC 2.0 message: a request (has an id) or a notification (no id). MCP dropped
/// batching in revision 2025-06-18, so arrays don't occur.
/// </summary>
internal class JsonRpcMessage
{
	[JsonPropertyName("jsonrpc")]
	public string JsonRpc { get; set; }

	/// <summary>String or number per the spec, so it stays a node and echoes back untouched.</summary>
	[JsonPropertyName("id")]
	public JsonNode Id { get; set; }

	[JsonPropertyName("method")]
	public string Method { get; set; }

	[JsonPropertyName("params")]
	public JsonNode Params { get; set; }

	[JsonIgnore]
	public bool IsNotification => Method is not null && Id is null;
}

/// <summary>
/// An outgoing JSON-RPC 2.0 response. Carries either a result or an error, never both.
/// </summary>
internal class JsonRpcResponse
{
	[JsonPropertyName("jsonrpc")]
	public string JsonRpc { get; set; } = "2.0";

	[JsonPropertyName("id")]
	public JsonNode Id { get; set; }

	[JsonPropertyName("result")]
	[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
	public JsonNode Result { get; set; }

	[JsonPropertyName("error")]
	[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
	public JsonRpcError Error { get; set; }

	public static JsonRpcResponse Success(JsonNode id, JsonNode result)
	{
		return new JsonRpcResponse { Id = id, Result = result ?? new JsonObject() };
	}

	public static JsonRpcResponse Failure(JsonNode id, int code, string message)
	{
		return new JsonRpcResponse
		{
			Id = id,
			Error = new JsonRpcError { Code = code, Message = message },
		};
	}
}

internal class JsonRpcError
{
	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;

	[JsonPropertyName("code")]
	public int Code { get; set; }

	[JsonPropertyName("message")]
	public string Message { get; set; }

	[JsonPropertyName("data")]
	[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
	public JsonNode Data { get; set; }
}

/// <summary>
/// Thrown by protocol handlers to bubble a specific JSON-RPC error code back to the client.
/// </summary>
internal class McpException : Exception
{
	public int Code { get; }

	public McpException(int code, string message)
		: base(message)
	{
		Code = code;
	}
}