/* * Copyright (C) 2025 Mcp-Vala Project * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Author: Mcp-Vala Project */ /** * Compatibility converters for MCP protocol message handling. * * This namespace contains converter implementations that help with * different message formats in the MCP protocol, particularly for * handling Content-Length headers. */ namespace Mcp.Core { /** * Converter that injects Content-Length headers into JSON-RPC messages. * * This converter takes JSON-RPC messages that don't have Content-Length headers * and injects them once a full JSON message has been sent with a newline at the end. */ public class InjectContentLength : GLib.Object, GLib.Converter { private StringBuilder buffer; private bool in_message; /** * Creates a new InjectContentLength converter. */ public InjectContentLength () { buffer = new StringBuilder (); in_message = false; } /** * Converts input data by injecting Content-Length headers. * * @param inbuf Input buffer containing data to convert * @param outbuf Output buffer to store converted data * @param flags Conversion flags * @param bytes_read Number of bytes read from input * @param bytes_written Number of bytes written to output * @return ConverterResult indicating conversion status * @throws Error If conversion fails */ public GLib.ConverterResult convert (uint8[] inbuf, uint8[] outbuf, GLib.ConverterFlags flags, out size_t bytes_read, out size_t bytes_written) throws Error { bytes_read = 0; bytes_written = 0; // Append input data to our buffer for (size_t i = 0; i < inbuf.length; i++) { char c = (char)inbuf[i]; buffer.append_c (c); bytes_read++; // Check if we have a complete JSON message (ending with newline) if (c == '\n') { // Extract the JSON message string json_str = buffer.str.strip (); // Skip empty lines if (json_str.length > 0) { // Create the message with Content-Length header string message_with_header = "Content-Length: %d\r\n\r\n%s".printf ( json_str.length, json_str); // Check if we have enough space in output buffer if (bytes_written + message_with_header.length > outbuf.length) { return GLib.ConverterResult.CONVERTED; } // Write the message with header to output buffer for (int j = 0; j < message_with_header.length; j++) { outbuf[bytes_written] = (uint8)message_with_header[j]; bytes_written++; } } // Reset buffer for next message buffer.truncate (0); } } // If we have remaining data but no newline, check if we're at end of input if (buffer.len > 0 && (flags & GLib.ConverterFlags.INPUT_AT_END) != 0) { string json_str = buffer.str.strip (); if (json_str.length > 0) { // Create the message with Content-Length header string message_with_header = "Content-Length: %d\r\n\r\n%s".printf ( json_str.length, json_str); // Check if we have enough space in output buffer if (bytes_written + message_with_header.length > outbuf.length) { return GLib.ConverterResult.CONVERTED; } // Write the message with header to output buffer for (int j = 0; j < message_with_header.length; j++) { outbuf[bytes_written] = (uint8)message_with_header[j]; bytes_written++; } } buffer.truncate (0); } return GLib.ConverterResult.CONVERTED; } /** * Resets the converter state. */ public void reset () { buffer.truncate (0); in_message = false; } } /** * Converter that strips Content-Length headers from JSON-RPC messages. * * This converter removes Content-Length headers and adds a newline * at the end of every JSON blob sent. */ public class StripContentLength : GLib.Object, GLib.Converter { private StringBuilder buffer; private bool in_headers; private size_t content_length; private bool have_content_length; /** * Creates a new StripContentLength converter. */ public StripContentLength () { buffer = new StringBuilder (); in_headers = true; content_length = 0; have_content_length = false; } /** * Converts input data by stripping Content-Length headers. * * @param inbuf Input buffer containing data to convert * @param outbuf Output buffer to store converted data * @param flags Conversion flags * @param bytes_read Number of bytes read from input * @param bytes_written Number of bytes written to output * @return ConverterResult indicating conversion status * @throws Error If conversion fails */ public GLib.ConverterResult convert (uint8[] inbuf, uint8[] outbuf, GLib.ConverterFlags flags, out size_t bytes_read, out size_t bytes_written) throws Error { bytes_read = 0; bytes_written = 0; // Append input data to our buffer for (size_t i = 0; i < inbuf.length; i++) { char c = (char)inbuf[i]; buffer.append_c (c); bytes_read++; } // Process the buffer string buffer_str = buffer.str; int pos = 0; while (pos < buffer_str.length) { if (in_headers) { // Look for end of headers (\r\n\r\n) int header_end = buffer_str.index_of ("\r\n\r\n", pos); if (header_end == -1) { // Headers not complete yet break; } // Parse headers to extract Content-Length string headers = buffer_str.substring (pos, header_end - pos); string[] header_lines = headers.split ("\r\n"); foreach (string line in header_lines) { if (line.down ().has_prefix ("content-length:")) { string[] parts = line.split (":", 2); if (parts.length == 2) { content_length = (size_t)int.parse (parts[1].strip ()); have_content_length = true; } break; } } // Move past headers pos = header_end + 4; in_headers = false; } else { // We're in the body content if (have_content_length) { // Check if we have the complete body if (pos + content_length <= buffer_str.length) { string json_content = buffer_str.substring (pos, (int)content_length); // Check if we have enough space in output buffer if (bytes_written + json_content.length + 1 > outbuf.length) { return GLib.ConverterResult.CONVERTED; } // Write JSON content to output buffer for (int j = 0; j < json_content.length; j++) { outbuf[bytes_written] = (uint8)json_content[j]; bytes_written++; } // Add newline outbuf[bytes_written] = (uint8)'\n'; bytes_written++; // Move past this message pos += (int)content_length; // Reset for next message in_headers = true; have_content_length = false; content_length = 0; } else { // Body not complete yet break; } } else { // No Content-Length header found, just look for newline int newline_pos = buffer_str.index_of ("\n", pos); if (newline_pos == -1) { // No newline found, check if we're at end of input if ((flags & GLib.ConverterFlags.INPUT_AT_END) != 0) { // Take everything remaining string remaining = buffer_str.substring (pos); // Check if we have enough space in output buffer if (bytes_written + remaining.length + 1 > outbuf.length) { return GLib.ConverterResult.CONVERTED; } // Write remaining content to output buffer for (int j = 0; j < remaining.length; j++) { outbuf[bytes_written] = (uint8)remaining[j]; bytes_written++; } // Add newline outbuf[bytes_written] = (uint8)'\n'; bytes_written++; pos = buffer_str.length; } else { // Wait for more data break; } } else { // Found a newline, extract the content string content = buffer_str.substring (pos, newline_pos - pos); // Check if we have enough space in output buffer if (bytes_written + content.length + 1 > outbuf.length) { return GLib.ConverterResult.CONVERTED; } // Write content to output buffer for (int j = 0; j < content.length; j++) { outbuf[bytes_written] = (uint8)content[j]; bytes_written++; } // Add newline outbuf[bytes_written] = (uint8)'\n'; bytes_written++; // Move past this message pos = newline_pos + 1; // Reset for next message in_headers = true; } } } } // Remove processed data from buffer if (pos > 0) { string remaining = buffer_str.substring (pos); buffer.truncate (0); buffer.append (remaining); } return GLib.ConverterResult.CONVERTED; } /** * Resets the converter state. */ public void reset () { buffer.truncate (0); in_headers = true; content_length = 0; have_content_length = false; } } }