Model Context Protocol in OCaml
1open Mcp_sdk
2
3(* Set up the formatter for capturing evaluation output *)
4let capture_output f =
5 let buffer = Buffer.create 1024 in
6 let fmt = Format.formatter_of_buffer buffer in
7 let result = f fmt in
8 Format.pp_print_flush fmt ();
9 (result, Buffer.contents buffer)
10
11(* Helper for extracting string value from JSON *)
12let get_string_param json name =
13 match json with
14 | `Assoc fields ->
15 (match List.assoc_opt name fields with
16 | Some (`String value) -> value
17 | _ -> failwith (Printf.sprintf "Missing or invalid parameter: %s" name))
18 | _ -> failwith "Expected JSON object"
19
20(* Initialize the OCaml toploop with standard libraries *)
21let initialize_toploop () =
22 (* Initialize the toplevel environment *)
23 Toploop.initialize_toplevel_env ();
24
25 (* Set up the toplevel as if using the standard OCaml REPL *)
26 Clflags.nopervasives := false;
27 Clflags.real_paths := true;
28 Clflags.recursive_types := false;
29 Clflags.strict_sequence := false;
30 Clflags.applicative_functors := true;
31
32 (* Return success message *)
33 "OCaml evaluation environment initialized"
34
35(* Evaluate an OCaml toplevel phrase *)
36let evaluate_phrase phrase =
37 (* Parse the input text as a toplevel phrase *)
38 let lexbuf = Lexing.from_string phrase in
39
40 (* Capture both success/failure status and output *)
41 try
42 let parsed_phrase = !Toploop.parse_toplevel_phrase lexbuf in
43 let (success, output) = capture_output (fun fmt ->
44 Toploop.execute_phrase true fmt parsed_phrase
45 ) in
46
47 (* Return structured result with status and captured output *)
48 if success then
49 `Assoc [
50 ("success", `Bool true);
51 ("output", `String output);
52 ]
53 else
54 `Assoc [
55 ("success", `Bool false);
56 ("error", `String "Execution failed");
57 ("output", `String output);
58 ]
59 with e ->
60 (* Handle parsing or other errors with more detailed messages *)
61 let error_msg = match e with
62 | Syntaxerr.Error err ->
63 let msg = match err with
64 | Syntaxerr.Unclosed _ -> "Syntax error: Unclosed delimiter"
65 | Syntaxerr.Expecting _ -> "Syntax error: Expecting a different token"
66 | Syntaxerr.Not_expecting _ -> "Syntax error: Unexpected token"
67 | Syntaxerr.Applicative_path _ -> "Syntax error: Invalid applicative path"
68 | Syntaxerr.Variable_in_scope _ -> "Syntax error: Variable in scope"
69 | Syntaxerr.Other _ -> "Syntax error"
70 | _ -> "Syntax error (unknown kind)"
71 in
72 msg
73
74 | Lexer.Error (err, _) ->
75 let msg = match err with
76 | Lexer.Illegal_character _ -> "Lexer error: Illegal character"
77 | Lexer.Illegal_escape _ -> "Lexer error: Illegal escape sequence"
78 | Lexer.Unterminated_comment _ -> "Lexer error: Unterminated comment"
79 | Lexer.Unterminated_string -> "Lexer error: Unterminated string"
80 | Lexer.Unterminated_string_in_comment _ -> "Lexer error: Unterminated string in comment"
81 | Lexer.Invalid_literal _ -> "Lexer error: Invalid literal"
82 | _ -> "Lexer error (unknown kind)"
83 in
84 msg
85 | _ -> Printexc.to_string e
86 in
87 `Assoc [
88 ("success", `Bool false);
89 ("error", `String error_msg);
90 ]
91
92(* Create evaluation server *)
93let server = create_server
94 ~name:"OCaml Evaluation Server"
95 ~version:"0.1.0" () |>
96 fun server ->
97 (* Set default capabilities *)
98 configure_server server ~with_tools:true ()
99
100(* Toplevel environment state management *)
101let toplevel_initialized = ref false
102
103(* Initialize OCaml toplevel on first use *)
104let ensure_toploop_initialized () =
105 if not !toplevel_initialized then begin
106 let _ = initialize_toploop () in
107 toplevel_initialized := true;
108 end
109
110(* Register eval tool *)
111let _ = add_tool server
112 ~name:"ocaml_eval"
113 ~description:"Evaluates OCaml toplevel phrases and returns the result"
114 ~schema_properties:[
115 ("code", "string", "OCaml code to evaluate")
116 ]
117 ~schema_required:["code"]
118 (fun args ->
119 ensure_toploop_initialized ();
120
121 try
122 (* Extract code parameter *)
123 let code = get_string_param args "code" in
124
125 (* Execute the code *)
126 let result = evaluate_phrase code in
127
128 (* Return formatted result *)
129 let success = match result with
130 | `Assoc fields -> (
131 match List.assoc_opt "success" fields with
132 | Some (`Bool true) -> true
133 | _ -> false
134 )
135 | _ -> false
136 in
137
138 let output = match result with
139 | `Assoc fields -> (
140 match List.assoc_opt "output" fields with
141 | Some (`String s) -> s
142 | _ -> (
143 match List.assoc_opt "error" fields with
144 | Some (`String s) -> s
145 | _ -> "Unknown result"
146 )
147 )
148 | _ -> "Unknown result"
149 in
150
151 (* Create a tool result with colorized output *)
152 create_tool_result [
153 TextContent output
154 ] ~is_error:(not success)
155
156 with
157 | Failure msg ->
158 Log.error (Printf.sprintf "Error in OCaml eval tool: %s" msg);
159 create_tool_result [TextContent (Printf.sprintf "Error: %s" msg)] ~is_error:true
160 )
161
162(* Run the server with the default scheduler *)
163let () =
164 Eio_main.run @@ fun env->
165 Mcp_server.run_server env server