// The MCP server tree: the author's tools, resources and prompts as annotated
// functions under `internal/tools`, registered by the code moonmcp generates from
// them, and an entry point serving them over Streamable HTTP or stdio.

///|
let mcp_tools_src : String =
  #|///|
  #|/// Each annotated function is offered by the server; its parameters become the
  #|/// tool's input schema. Edit freely: `tools.g.mbt` is regenerated on build.
  #|#mcp.tool(description="Say hello to someone")
  #|fn hello(name : String) -> String {
  #|  "hello, " + name
  #|}
  #|
  #|///|
  #|#mcp.resource("about://server", description="What this server is")
  #|fn about() -> String {
  #|  "an MCP server scaffolded by mctl"
  #|}
  #|
  #|///|
  #|#mcp.prompt(description="Ask for a greeting in a given tone")
  #|fn greeting(name : String, tone? : String) -> String {
  #|  match tone {
  #|    Some(t) => "Greet \{name} in a \{t} tone."
  #|    None => "Greet \{name}."
  #|  }
  #|}

///|
/// What moonmcp's generator writes for `mcp_tools_src`, so a fresh project
/// compiles before its first build regenerates it.
let mcp_generated_src : String =
  #|// Generated by moonmcp from tools.mbt. Edit those files, not this one.
  #|
  #|///|
  #|pub fn register(mcp : @moonmcp.Server) -> Unit {
  #|  mcp.tool(
  #|    "hello",
  #|    args => ToJson::to_json(hello(@moonmcp.decode(args, "name"))),
  #|    input_schema={
  #|      "type": "object",
  #|      "properties": { "name": { "type": "string" } },
  #|      "required": ["name"],
  #|    },
  #|    description="Say hello to someone",
  #|  )
  #|  mcp.resource(
  #|    "about://server",
  #|    () => about(),
  #|    description="What this server is",
  #|  )
  #|  mcp.prompt(
  #|    "greeting",
  #|    args => {
  #|      greeting(
  #|        @moonmcp.decode(args, "name"),
  #|        tone?=@moonmcp.decode_option(args, "tone"),
  #|      )
  #|    },
  #|    description="Ask for a greeting in a given tone",
  #|    arguments=[
  #|      { name: "name", title: None, description: None, required: Some(true) },
  #|      { name: "tone", title: None, description: None, required: Some(false) },
  #|    ],
  #|  )
  #|}

///|
let mcp_test_src : String =
  #|///|
  #|async test "hello answers a tools/call" {
  #|  let mcp = @moonmcp.Server::new("test")
  #|  @tools.register(mcp)
  #|  let call : Json = {
  #|    "jsonrpc": "2.0",
  #|    "id": 1,
  #|    "method": "tools/call",
  #|    "params": {
  #|      "name": "hello",
  #|      "arguments": { "name": "moonbit" },
  #|      "_meta": {
  #|        "io.modelcontextprotocol/protocolVersion": "2026-07-28",
  #|        "io.modelcontextprotocol/clientCapabilities": {},
  #|      },
  #|    },
  #|  }
  #|  guard mcp.handle(call)
  #|    is Some({ "result": { "content": [{ "text": String(text), .. }], .. }, .. }) else {
  #|    fail("no text in the reply")
  #|  }
  #|  assert_eq(text, "hello, moonbit")
  #|}

///|
fn mcp_main(module_ : String) -> String {
  let src =
    #|///|
    #|/// The %svc MCP server: over Streamable HTTP on mooncat, or over stdio when
    #|/// started with `--stdio` by a client that runs it as a subprocess.
    #|async fn main {
    #|  let svc = @svc.ServiceContext::new(@config.Config::new())
    #|  let mcp = @moonmcp.Server::new("%svc", version="0.1.0")
    #|  @tools.register(mcp)
    #|  if @env.args().contains("--stdio") {
    #|    mcp.run_stdio()
    #|  } else {
    #|    let cfg = svc.config
    #|    println("%svc serving MCP on http://" + cfg.host + ":" + cfg.port.to_string() + "/mcp")
    #|    @mooncat.serve(mcp.to_asgi(), host=cfg.host, port=cfg.port)
    #|  }
    #|}
  src.replace_all(old="%svc", new=module_) + "\n"
}

///|
/// The MCP server tree for module `module_` under `dir`: the shared module,
/// configuration and context, `internal/tools` with the annotated functions,
/// their generated registration and a test, and the build rule that keeps the
/// registration in step with the functions.
pub fn generate_mcp_tree(
  module_ : String,
  style? : @style.Style,
  dir~ : String = "",
) -> Array[TreeFile] {
  let st = style.unwrap_or(@style.Style::gozero())
  let tools_pkg = module_ + "/internal/tools"
  let mcp = ("moonbitstack/moonmcp", "")
  let out = base_tree(
    module_,
    st,
    dir,
    deps=["moonbitstack/moonmcp", "moonbitstack/mooncat"],
    imports=[
      mcp,
      ("moonbitstack/mooncat", ""),
      ("moonbitlang/core/env", ""),
      (tools_pkg, ""),
    ],
    main=mcp_main(module_),
  ).map(f => {
    if f.path == at(dir, "moon.mod") {
      {
        ..f,
        content: f.content +
        "\nrule(name: \"mcp\", command: \"moonx moonbitstack/moonmcp/cmd/mcp@" +
        pins.get("moonbitstack/moonmcp").unwrap() +
        " -C \\\"$mod_dir\\\" \\\"$input\\\" -o \\\"$output\\\"\")\n",
      }
    } else {
      f
    }
  })
  let tests = pkg_file([("moonbitlang/async", "")]).replace(
    old="}\n",
    new="} for \"test\"\n",
  )
  out.push({
    path: at(dir, "internal/tools/moon.pkg"),
    content: pkg_file([mcp]) +
    "\n" +
    tests +
    "\ndev_build(rule: \"mcp\", input: \"tools.mbt\", output: \"tools.g.mbt\")\n\nformatter(ignore: [ \"tools.g.mbt\" ])\n",
    regen: Once,
  })
  out.push({
    path: at(dir, "internal/tools/tools.mbt"),
    content: mcp_tools_src + "\n",
    regen: Once,
  })
  out.push({
    path: at(dir, "internal/tools/tools.g.mbt"),
    content: mcp_generated_src + "\n",
    regen: Once,
  })
  out.push({
    path: at(dir, "internal/tools/tools_test.mbt"),
    content: mcp_test_src + "\n",
    regen: Once,
  })
  out
}

///|
/// Scaffold an MCP server under `/`: annotated tools, a resource and a
/// prompt, a test calling the tool, and an entry point serving them over
/// Streamable HTTP or, with `--stdio`, over stdio.
pub fn scaffold_mcp(name : String) -> Array[@plugin.GenFile] {
  let files = tree_plan(generate_mcp_tree(name, dir=name), _ => false)
  let readme_src =
    #|# %name
    #|
    #|An MCP server scaffolded by mctl.
    #|
    #|```bash
    #|moon test --target native               # the tests
    #|moon run --target native .              # serve on http://0.0.0.0:12000/mcp
    #|moon run --target native . -- --stdio   # serve over stdio
    #|```
    #|
    #|Tools, resources and prompts are the annotated functions in
    #|`internal/tools/tools.mbt`; `tools.g.mbt` is regenerated from them on every
    #|build.
  files.push({
    path: name + "/README.md",
    content: readme_src.replace_all(old="%name", new=name) + "\n",
  })
  files
}