{"name":"mcp_echo_server_http.nu","source":"// mcp_echo_server_http.nu — minimal MCP server over HTTP transport.\n//\n// Same business logic as `examples/mcp_echo_server.nu` (one `echo`\n// tool), but instead of stdio framing it speaks Streamable-HTTP per\n// the MCP transport spec — bound to a TCP port, suitable for browser-\n// based clients (claude.ai) and HTTP-only MCP integrations.\n//\n// Run by hand:\n//\n//     ./build.sh   # or .\\build.bat on Windows\n//     ./nurl.sh examples/mcp_echo_server_http.nu &\n//\n// Then probe via curl:\n//\n//     curl -s -X POST http://127.0.0.1:18770/mcp \\\n//          -H 'Content-Type: application/json' \\\n//          --data '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{}}'\n//     curl -s -X POST http://127.0.0.1:18770/mcp \\\n//          -H 'Content-Type: application/json' \\\n//          --data '{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/list\"}'\n//     curl -s -X POST http://127.0.0.1:18770/mcp \\\n//          -H 'Content-Type: application/json' \\\n//          --data '{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"tools/call\",\"params\":{\"name\":\"echo\",\"arguments\":{\"text\":\"hello\"}}}'\n//\n// Bind to 0.0.0.0 instead of 127.0.0.1 to expose on the LAN. For\n// production use, put NURL behind nginx/caddy for TLS or use the\n// native TLS in `stdlib/ext/http_server.nu` (`tcp_listen_tls`).\n\n$ `stdlib/ext/mcp.nu`\n$ `stdlib/ext/mcp_http.nu`\n$ `stdlib/ext/json.nu`\n$ `stdlib/std/net.nu`\n$ `stdlib/core/string.nu`\n\n// ── Tool descriptor table ──────────────────────────────────────────\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 runners ───────────────────────────────────────────────────\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 → ^ ( mcp_tool_result_error `missing required argument: text` )\n    }\n    ^ ( mcp_tool_result_error `internal: unreachable` )\n}\n\n@ dispatch_tool s name Json args → Json {\n    ? != 0 ( nurl_str_eq name `echo` ) {\n        ^ ( run_echo args )\n    } {}\n    ^ ( mcp_tool_result_error `unknown tool` )\n}\n\n// ── Per-method handlers (return-style for HTTP transport) ─────────\n\n@ handle_initialize Json id → Json {\n    : Json result ( mcp_initialize_result `nurl-echo-mcp-http` `0.1.0` )\n    ^ ( mcp_response_result id result )\n}\n\n@ handle_ping Json id → Json {\n    : Json empty ( json_obj_new )\n    ^ ( mcp_response_result id empty )\n}\n\n@ handle_tools_list Json id → Json {\n    : ( Vec Json ) tools ( build_tools_list )\n    : Json result ( mcp_tools_list_result tools )\n    ^ ( mcp_response_result id result )\n}\n\n@ handle_tools_call Json id Json params → Json {\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_response_result id result )\n        }\n        F → {\n            ^ ( mcp_response_error id mcp_err_invalid_params\n            `tools/call requires a \"name\" parameter` )\n        }\n    }\n}\n\n// ── HTTP-transport dispatcher ─────────────────────────────────────\n//\n// Mirror of mcp_echo_server.nu's `handle` — but where the stdio\n// version writes via `mcp_send_message`, this returns the response\n// Json so `mcp_http_handler` can wrap it in an HTTP envelope.\n// Notifications return None, becoming HTTP 202 Accepted on the wire.\n\n@ dispatch Json req → ?Json {\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            ?? id_opt {\n                T id → {\n                    ? != 0 ( nurl_str_eq method `initialize` ) {\n                        ^ @ ?Json { T ( handle_initialize id ) }\n                    } {}\n                    ? != 0 ( nurl_str_eq method `ping` ) {\n                        ^ @ ?Json { T ( handle_ping id ) }\n                    } {}\n                    ? != 0 ( nurl_str_eq method `tools/list` ) {\n                        ^ @ ?Json { T ( handle_tools_list id ) }\n                    } {}\n                    ? != 0 ( nurl_str_eq method `tools/call` ) {\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                        : Json out ( handle_tools_call id params )\n                        ( json_free params )\n                        ^ @ ?Json { T out }\n                    } {}\n                    // Unknown method.\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                    : Json err ( mcp_response_error id mcp_err_method_not_found\n                    ( string_data msg ) )\n                    ( string_free msg )\n                    ^ @ ?Json { T err }\n                }\n                F → {\n                    // Notification — log and skip.\n                    ? != 0 ( nurl_str_eq method `notifications/initialized` ) {\n                        ( mcp_log `client initialized` )\n                    } {}\n                    ^ @ ?Json { F @ Json { JNull } }\n                }\n            }\n        }\n        F → {\n            ( mcp_log `request without method, ignoring` )\n            ^ @ ?Json { F @ Json { JNull } }\n        }\n    }\n}\n\n// ── main ───────────────────────────────────────────────────────────\n\n@ main → i {\n    ( mcp_log `nurl-echo-mcp-http 0.1.0 listening on 127.0.0.1:18770/mcp` )\n\n    : ( @ ?Json Json ) f \\ Json req → ?Json { ^ ( dispatch req ) }\n    : !v NetErr r ( mcp_server_run_http `127.0.0.1` 18770 f )\n\n    ?? r {\n        T _ → {\n            ( mcp_log `server stopped cleanly` )\n            ^ 0\n        }\n        F e → {\n            ( nurl_eprint `[mcp] server error: ` )\n            ( nurl_eprint ( net_err_name e ) )\n            ( nurl_eprint `\\n` )\n            ^ 1\n        }\n    }\n}\n","bytes":7527}