Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-mcp

Model Context Protocol server for Pudu language documentation and compiler tools

0.1.1Apache-2.01

InstallClose

Docs.pudu

Pudu86 lines3.7 KB

GitHub ↗
1/** @App.Tools.Docs.Handler — documentation search and reading tools */2module PuduLangMcp.App.Tools.Docs34import Std.List as List5import Std.Option as Option6import Std.Text as Text7import PuduLangMcp.App.Context as Context8import PuduLangMcp.Constants.Server as Server9import PuduLangMcp.Domain.Catalog.Arguments as Arguments10import PuduLangMcp.Domain.Docs.Chapter as Chapter11import PuduLangMcp.Domain.Docs.Search as Search12import PuduLangMcp.Errors.ToolError as ToolError13import PuduLangMcp.Utils.TextBounds as TextBounds1415/// The groups a document can belong to.16export const GROUPS: Array[Str] = ["docs", "pages", "releases", "examples"]1718/// The group read when a caller names none.19const DEFAULT_GROUP: Str = "docs"2021/// Characters of context shown around a search hit.22const SNIPPET_WIDTH: Int = 2402324/// Answers the best-matching documentation sections for `query`.25export fn search(context: &Context.Context, args: &Arguments.Args) -> Result[Str, ToolError.ToolFailure] {26  let query = Option.unwrapOr(Arguments.text(args, "query"), "")27  let group = groupOf(args) ?28  let limit = Arguments.integerOr(args, "limit", Server.DEFAULT_SEARCH_LIMIT)29  let sections = match group {30    case Some(wanted) => context.sections.filter(|held: Chapter.Section| held.group == wanted)31    case None => context.sections32  }33  let hits = Search.search(&sections, query, limit)34  if hits.isEmpty() {35    return Ok("No documentation matches '" + query + "'. Try other words, or pudu_reference_search for a library name.")36  }37  let first = Option.unwrapOr(List.first(&Search.terms(query)), query)38  var lines: Array[Str] = []39  var number = 140  for hit in hits {41    let held = hit.section42    lines = lines.push(show(number) + ". " + held.group + "/" + held.chapter + "#" + held.anchor + " — " + held.chapterTitle + " › " + held.heading)43    lines = lines.push("   " + TextBounds.snippet(held.text, first, SNIPPET_WIDTH))44    number = number + 145  }46  Ok(lines.join("\n") + "\n\nRead one with pudu_docs_read using its slug and, for a section, its anchor.")47}4849/// Answers one document, or one of its sections.50export fn read(context: &Context.Context, args: &Arguments.Args) -> Result[Str, ToolError.ToolFailure] {51  let group = Option.unwrapOr(groupOf(args) ?, DEFAULT_GROUP)52  let slug = Option.unwrapOr(Arguments.text(args, "slug"), "").trim()53  let chapter = match Chapter.find(&context.chapters, group, slug) {54    case Some(found) => found55    case None => {56      let known = Chapter.inGroup(&context.chapters, group).map(|held: Chapter.Chapter| held.slug)57      return Err(ToolError.NotFound("Document '" + group + "/" + slug + "' (" + group + " has: " + known.join(", ") + ")"))58    }59  }60  match Arguments.text(args, "section") {61    case Some(wanted) => {62      if Text.isBlank(wanted) { return Ok(TextBounds.bounded(chapter.markdown, Server.DOC_CHARS)) }63      match Chapter.sectionIn(&chapter, wanted) {64        case Some(held) => Ok(TextBounds.bounded("## " + held.heading + "\n\n" + held.text, Server.DOC_CHARS))65        case None => {66          let anchors = Chapter.sectionsOf(&chapter).map(|held: Chapter.Section| held.anchor)67          Err(ToolError.NotFound("Section '" + wanted + "' of " + group + "/" + slug + " (sections: " + anchors.join(", ") + ")"))68        }69      }70    }71    case None => Ok(TextBounds.bounded(chapter.markdown, Server.DOC_CHARS))72  }73}7475/// The `group` argument, checked against the known groups.76fn groupOf(args: &Arguments.Args) -> Result[Option[Str], ToolError.ToolFailure] {77  match Arguments.text(args, "group") {78    case None => Ok(None)79    case Some(given) => {80      let wanted = given.trim()81      if wanted.isEmpty() { return Ok(None) }82      if GROUPS.contains(wanted) { Ok(Some(wanted)) } else { Err(ToolError.OutOfRange("group", "one of " + GROUPS.join(", "))) }83    }84  }85}86