{"name":"mcp_echo_server.nu","source":"// mcp_echo_server.nu — minimal MCP stdio server in NURL.\n//\n// Implements just enough of the protocol to register a single tool\n// `echo` and answer the client's tool calls. All transport is\n// newline-delimited JSON-RPC 2.0 over stdin/stdout. Logs go to\n// stderr — stdout is reserved for protocol bytes.\n//\n// Run by hand:\n//\n//     ./build.sh\n//     ./nurl.sh examples/mcp_echo_server.nu\n//     printf '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{}}\\n'\\\n//            '{\"jsonrpc\":\"2.0\",\"method\":\"notifications/initialized\"}\\n'\\\n//            '{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/list\"}\\n'\\\n//            '{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"tools/call\",'\\\n//                '\"params\":{\"name\":\"echo\",\"arguments\":{\"text\":\"hello\"}}}\\n' \\\n//       | ./examples/mcp_echo_server\n//\n// Or wire it into Claude Desktop / claude.ai by adding an MCP server\n// entry whose `command` invokes the compiled binary.\n\n$ `stdlib/ext/mcp.nu`\n$ `stdlib/core/string.nu`\n\n// ── Tool descriptor table ──────────────────────────────────────────\n//\n// Built once per tools/list. Keeping it inline avoids needing a\n// closure-in-struct registry — the server's known tools are a fixed\n// list at compile time.\n\n@ build_tools_list → ( Vec Json ) {\n    : Json schema ( json_obj_new )\n    ( json_obj_set schema `type` ( json_str_lit `object` ) )\n\n    : Json props ( json_obj_new )\n    : Json text_prop ( json_obj_new )\n    ( json_obj_set text_prop `type` ( json_str_lit `string` ) )\n    ( json_obj_set text_prop `description` ( json_str_lit `Text to echo` ) )\n    ( json_obj_set props `text` text_prop )\n    ( json_obj_set schema `properties` props )\n\n    : Json required ( json_arr_new )\n    ( json_arr_push required ( json_str_lit `text` ) )\n    ( json_obj_set schema `required` required )\n\n    : ( Vec Json ) tools ( vec_new [Json] )\n    ( vec_push [Json] tools\n    ( mcp_tool_descriptor `echo` `Echo the supplied text back to the caller.` schema ) )\n    ^ tools\n}\n\n// ── Tool dispatch ──────────────────────────────────────────────────\n//\n// Called from the tools/call handler. Returns the tool result Json\n// (including isError flag) — the caller wraps it in a JSON-RPC\n// response envelope.\n\n@ run_echo Json args → Json {\n    : ?Json text_j ( json_obj_get args `text` )\n    ?? text_j {\n        T tv → {\n            : s text ( json_str_data tv )\n            ^ ( mcp_tool_result_text text )\n        }\n        F → {\n            ^ ( mcp_tool_result_error `missing required argument: text` )\n        }\n    }\n    // Unreachable — match is exhaustive — but the compiler wants a\n    // trailing return for Json.\n    ^ ( mcp_tool_result_error `internal: shape mismatch` )\n}\n\n@ dispatch_tool s name Json args → Json {\n    ? != ( nurl_str_eq name `echo` ) 0 {\n        ^ ( run_echo args )\n    } {}\n    ^ ( mcp_tool_result_error `unknown tool` )\n}\n\n// ── Per-method handlers ────────────────────────────────────────────\n\n@ handle_initialize Json id → v {\n    : Json result ( mcp_initialize_result `nurl-echo-mcp` `0.1.0` )\n    ( mcp_send_message ( mcp_response_result id result ) )\n}\n\n@ handle_ping Json id → v {\n    : Json empty ( json_obj_new )\n    ( mcp_send_message ( mcp_response_result id empty ) )\n}\n\n@ handle_tools_list Json id → v {\n    : ( Vec Json ) tools ( build_tools_list )\n    : Json result ( mcp_tools_list_result tools )\n    ( mcp_send_message ( mcp_response_result id result ) )\n}\n\n@ handle_tools_call Json id Json params → v {\n    : ?Json name_j ( json_obj_get params `name` )\n    ?? name_j {\n        T nv → {\n            : s name ( json_str_data nv )\n            : ?Json args_j ( json_obj_get params `arguments` )\n            : Json args ?? args_j {\n                T av → ( json_clone av )\n                F → ( json_obj_new )\n            }\n            : Json result ( dispatch_tool name args )\n            ( json_free args )\n            ( mcp_send_message ( mcp_response_result id result ) )\n        }\n        F → {\n            ( mcp_send_message\n            ( mcp_response_error id mcp_err_invalid_params\n            `tools/call requires a \"name\" parameter` ) )\n        }\n    }\n}\n\n@ handle_unknown_method Json id s method → v {\n    : i mlen ( nurl_str_len method )\n    : String msg ( string_with_cap + 32 mlen )\n    ( string_push_str msg `unknown method: ` )\n    ( string_push_str msg method )\n    ( mcp_send_message\n    ( mcp_response_error id mcp_err_method_not_found ( string_data msg ) ) )\n    ( string_free msg )\n}\n\n// ── Main loop ──────────────────────────────────────────────────────\n\n@ handle Json req → v {\n    : ?Json method_j ( json_obj_get req `method` )\n    ?? method_j {\n        T mv → {\n            : s method ( json_str_data mv )\n            : ?Json id_opt ( json_obj_get req `id` )\n\n            // Notifications (no `id`) require no response. We branch first\n            // so that valid notifications never trigger a method-not-found\n            // reply for, e.g., `notifications/initialized`.\n            ?? id_opt {\n                T id → {\n                    ? != ( nurl_str_eq method `initialize` ) 0 {\n                        ( handle_initialize id )\n                    } {\n                        ? != ( nurl_str_eq method `ping` ) 0 {\n                            ( handle_ping id )\n                        } {\n                            ? != ( nurl_str_eq method `tools/list` ) 0 {\n                                ( handle_tools_list id )\n                            } {\n                                ? != ( nurl_str_eq method `tools/call` ) 0 {\n                                    : ?Json params_j ( json_obj_get req `params` )\n                                    : Json params ?? params_j {\n                                        T pv → ( json_clone pv )\n                                        F → ( json_obj_new )\n                                    }\n                                    ( handle_tools_call id params )\n                                    ( json_free params )\n                                } {\n                                    ( handle_unknown_method id method )\n                                } } } }\n                }\n                F → {\n                    // Notification — nothing to send. Trace at most.\n                    ? != ( nurl_str_eq method `notifications/initialized` ) 0 {\n                        ( mcp_log `client initialized` )\n                    } {}\n                }\n            }\n        }\n        F → {\n            ( mcp_log `request without method, ignoring` )\n        }\n    }\n}\n\n@ main → i {\n    ( mcp_log `nurl-echo-mcp 0.1.0 ready` )\n    : ~ b running T\n    ~ running {\n        : ?Json msg ( mcp_read_request )\n        ?? msg {\n            T req → {\n                ( handle req )\n                ( json_free req )\n            }\n            F _ → {\n                = running F\n            }\n        }\n    }\n    ( mcp_log `bye` )\n    ^ 0\n}\n","bytes":7292}