compat.vala 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. /*
  2. * Copyright (C) 2025 Mcp-Vala Project
  3. *
  4. * This library is free software; you can redistribute it and/or
  5. * modify it under the terms of the GNU Lesser General Public
  6. * License as published by the Free Software Foundation; either
  7. * version 2.1 of the License, or (at your option) any later version.
  8. *
  9. * This library is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. * Lesser General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU Lesser General Public
  15. * License along with this library; if not, write to the Free Software
  16. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  17. *
  18. * Author: Mcp-Vala Project
  19. */
  20. /**
  21. * Compatibility converters for MCP protocol message handling.
  22. *
  23. * This namespace contains converter implementations that help with
  24. * different message formats in the MCP protocol, particularly for
  25. * handling Content-Length headers.
  26. */
  27. namespace Mcp.Core {
  28. /**
  29. * Converter that injects Content-Length headers into JSON-RPC messages.
  30. *
  31. * This converter takes JSON-RPC messages that don't have Content-Length headers
  32. * and injects them once a full JSON message has been sent with a newline at the end.
  33. */
  34. public class InjectContentLength : GLib.Object, GLib.Converter {
  35. private StringBuilder buffer;
  36. private bool in_message;
  37. /**
  38. * Creates a new InjectContentLength converter.
  39. */
  40. public InjectContentLength () {
  41. buffer = new StringBuilder ();
  42. in_message = false;
  43. }
  44. /**
  45. * Converts input data by injecting Content-Length headers.
  46. *
  47. * @param inbuf Input buffer containing data to convert
  48. * @param outbuf Output buffer to store converted data
  49. * @param flags Conversion flags
  50. * @param bytes_read Number of bytes read from input
  51. * @param bytes_written Number of bytes written to output
  52. * @return ConverterResult indicating conversion status
  53. * @throws Error If conversion fails
  54. */
  55. public GLib.ConverterResult convert (uint8[] inbuf, uint8[] outbuf, GLib.ConverterFlags flags,
  56. out size_t bytes_read, out size_t bytes_written) throws Error {
  57. bytes_read = 0;
  58. bytes_written = 0;
  59. // Append input data to our buffer
  60. for (size_t i = 0; i < inbuf.length; i++) {
  61. char c = (char)inbuf[i];
  62. buffer.append_c (c);
  63. bytes_read++;
  64. // Check if we have a complete JSON message (ending with newline)
  65. if (c == '\n') {
  66. // Extract the JSON message
  67. string json_str = buffer.str.strip ();
  68. // Skip empty lines
  69. if (json_str.length > 0) {
  70. // Create the message with Content-Length header
  71. string message_with_header = "Content-Length: %d\r\n\r\n%s".printf (
  72. json_str.length, json_str);
  73. // Check if we have enough space in output buffer
  74. if (bytes_written + message_with_header.length > outbuf.length) {
  75. return GLib.ConverterResult.CONVERTED;
  76. }
  77. // Write the message with header to output buffer
  78. for (int j = 0; j < message_with_header.length; j++) {
  79. outbuf[bytes_written] = (uint8)message_with_header[j];
  80. bytes_written++;
  81. }
  82. }
  83. // Reset buffer for next message
  84. buffer.truncate (0);
  85. }
  86. }
  87. // If we have remaining data but no newline, check if we're at end of input
  88. if (buffer.len > 0 && (flags & GLib.ConverterFlags.INPUT_AT_END) != 0) {
  89. string json_str = buffer.str.strip ();
  90. if (json_str.length > 0) {
  91. // Create the message with Content-Length header
  92. string message_with_header = "Content-Length: %d\r\n\r\n%s".printf (
  93. json_str.length, json_str);
  94. // Check if we have enough space in output buffer
  95. if (bytes_written + message_with_header.length > outbuf.length) {
  96. return GLib.ConverterResult.CONVERTED;
  97. }
  98. // Write the message with header to output buffer
  99. for (int j = 0; j < message_with_header.length; j++) {
  100. outbuf[bytes_written] = (uint8)message_with_header[j];
  101. bytes_written++;
  102. }
  103. }
  104. buffer.truncate (0);
  105. }
  106. return GLib.ConverterResult.CONVERTED;
  107. }
  108. /**
  109. * Resets the converter state.
  110. */
  111. public void reset () {
  112. buffer.truncate (0);
  113. in_message = false;
  114. }
  115. }
  116. /**
  117. * Converter that strips Content-Length headers from JSON-RPC messages.
  118. *
  119. * This converter removes Content-Length headers and adds a newline
  120. * at the end of every JSON blob sent.
  121. */
  122. public class StripContentLength : GLib.Object, GLib.Converter {
  123. private StringBuilder buffer;
  124. private bool in_headers;
  125. private size_t content_length;
  126. private bool have_content_length;
  127. /**
  128. * Creates a new StripContentLength converter.
  129. */
  130. public StripContentLength () {
  131. buffer = new StringBuilder ();
  132. in_headers = true;
  133. content_length = 0;
  134. have_content_length = false;
  135. }
  136. /**
  137. * Converts input data by stripping Content-Length headers.
  138. *
  139. * @param inbuf Input buffer containing data to convert
  140. * @param outbuf Output buffer to store converted data
  141. * @param flags Conversion flags
  142. * @param bytes_read Number of bytes read from input
  143. * @param bytes_written Number of bytes written to output
  144. * @return ConverterResult indicating conversion status
  145. * @throws Error If conversion fails
  146. */
  147. public GLib.ConverterResult convert (uint8[] inbuf, uint8[] outbuf, GLib.ConverterFlags flags,
  148. out size_t bytes_read, out size_t bytes_written) throws Error {
  149. bytes_read = 0;
  150. bytes_written = 0;
  151. // Append input data to our buffer
  152. for (size_t i = 0; i < inbuf.length; i++) {
  153. char c = (char)inbuf[i];
  154. buffer.append_c (c);
  155. bytes_read++;
  156. }
  157. // Process the buffer
  158. string buffer_str = buffer.str;
  159. int pos = 0;
  160. while (pos < buffer_str.length) {
  161. if (in_headers) {
  162. // Look for end of headers (\r\n\r\n)
  163. int header_end = buffer_str.index_of ("\r\n\r\n", pos);
  164. if (header_end == -1) {
  165. // Headers not complete yet
  166. break;
  167. }
  168. // Parse headers to extract Content-Length
  169. string headers = buffer_str.substring (pos, header_end - pos);
  170. string[] header_lines = headers.split ("\r\n");
  171. foreach (string line in header_lines) {
  172. if (line.down ().has_prefix ("content-length:")) {
  173. string[] parts = line.split (":", 2);
  174. if (parts.length == 2) {
  175. content_length = (size_t)int.parse (parts[1].strip ());
  176. have_content_length = true;
  177. }
  178. break;
  179. }
  180. }
  181. // Move past headers
  182. pos = header_end + 4;
  183. in_headers = false;
  184. } else {
  185. // We're in the body content
  186. if (have_content_length) {
  187. // Check if we have the complete body
  188. if (pos + content_length <= buffer_str.length) {
  189. string json_content = buffer_str.substring (pos, (int)content_length);
  190. // Check if we have enough space in output buffer
  191. if (bytes_written + json_content.length + 1 > outbuf.length) {
  192. return GLib.ConverterResult.CONVERTED;
  193. }
  194. // Write JSON content to output buffer
  195. for (int j = 0; j < json_content.length; j++) {
  196. outbuf[bytes_written] = (uint8)json_content[j];
  197. bytes_written++;
  198. }
  199. // Add newline
  200. outbuf[bytes_written] = (uint8)'\n';
  201. bytes_written++;
  202. // Move past this message
  203. pos += (int)content_length;
  204. // Reset for next message
  205. in_headers = true;
  206. have_content_length = false;
  207. content_length = 0;
  208. } else {
  209. // Body not complete yet
  210. break;
  211. }
  212. } else {
  213. // No Content-Length header found, just look for newline
  214. int newline_pos = buffer_str.index_of ("\n", pos);
  215. if (newline_pos == -1) {
  216. // No newline found, check if we're at end of input
  217. if ((flags & GLib.ConverterFlags.INPUT_AT_END) != 0) {
  218. // Take everything remaining
  219. string remaining = buffer_str.substring (pos);
  220. // Check if we have enough space in output buffer
  221. if (bytes_written + remaining.length + 1 > outbuf.length) {
  222. return GLib.ConverterResult.CONVERTED;
  223. }
  224. // Write remaining content to output buffer
  225. for (int j = 0; j < remaining.length; j++) {
  226. outbuf[bytes_written] = (uint8)remaining[j];
  227. bytes_written++;
  228. }
  229. // Add newline
  230. outbuf[bytes_written] = (uint8)'\n';
  231. bytes_written++;
  232. pos = buffer_str.length;
  233. } else {
  234. // Wait for more data
  235. break;
  236. }
  237. } else {
  238. // Found a newline, extract the content
  239. string content = buffer_str.substring (pos, newline_pos - pos);
  240. // Check if we have enough space in output buffer
  241. if (bytes_written + content.length + 1 > outbuf.length) {
  242. return GLib.ConverterResult.CONVERTED;
  243. }
  244. // Write content to output buffer
  245. for (int j = 0; j < content.length; j++) {
  246. outbuf[bytes_written] = (uint8)content[j];
  247. bytes_written++;
  248. }
  249. // Add newline
  250. outbuf[bytes_written] = (uint8)'\n';
  251. bytes_written++;
  252. // Move past this message
  253. pos = newline_pos + 1;
  254. // Reset for next message
  255. in_headers = true;
  256. }
  257. }
  258. }
  259. }
  260. // Remove processed data from buffer
  261. if (pos > 0) {
  262. string remaining = buffer_str.substring (pos);
  263. buffer.truncate (0);
  264. buffer.append (remaining);
  265. }
  266. return GLib.ConverterResult.CONVERTED;
  267. }
  268. /**
  269. * Resets the converter state.
  270. */
  271. public void reset () {
  272. buffer.truncate (0);
  273. in_headers = true;
  274. content_length = 0;
  275. have_content_length = false;
  276. }
  277. }
  278. }