| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326 |
- /*
- * 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;
- }
- }
- }
|