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