From 3622e323b7c6bd202aaffe0b155cd8a54aef32f7 Mon Sep 17 00:00:00 2001 From: Thomas Aubry Date: Wed, 6 May 2026 17:19:12 +0200 Subject: [PATCH] Add remote MCP inspector test --- justfile | 10 +++++ mcp/md_docrs_remote.py | 83 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 mcp/md_docrs_remote.py diff --git a/justfile b/justfile index d19cc18..e5af935 100644 --- a/justfile +++ b/justfile @@ -61,6 +61,14 @@ test-worker: echo just curl-worker-target "tokio::sync::Mutex" "x86_64-unknown-linux-gnu" +# Launch MCP Inspector against the hosted md-docrs Worker adapter. +mcp-inspector-remote: + npx @modelcontextprotocol/inspector uv run --with mcp python mcp/md_docrs_remote.py + +# Smoke-test the hosted md-docrs Worker adapter without opening MCP Inspector. +mcp-remote-smoke spec="anyhow::Error": + uv run --with mcp python mcp/md_docrs_remote.py --smoke {{ spec }} + # Run the native Markdown server locally. server-dev: cargo run -p md-docrs-server -- --port 8080 --bind 127.0.0.1 @@ -86,6 +94,8 @@ help-commands: @echo " cargo run -p md-docrs-cli -- anyhow" @echo " cargo run -p md-docrs-cli -- --target x86_64-unknown-linux-gnu tokio::sync::Mutex" @echo " cargo run -p md-docrs-server -- --port 8080 --bind 127.0.0.1" + @echo " just mcp-inspector-remote" + @echo " just mcp-remote-smoke anyhow::Error" @echo " dist plan --tag v0.1.0" @echo " dist build --tag v0.1.0" @echo " cargo release 0.2.0" diff --git a/mcp/md_docrs_remote.py b/mcp/md_docrs_remote.py new file mode 100644 index 0000000..aec6a9e --- /dev/null +++ b/mcp/md_docrs_remote.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +"""MCP adapter for the hosted md-docrs Worker.""" + +from __future__ import annotations + +import argparse +import os +import urllib.error +import urllib.parse +import urllib.request + +from mcp.server.fastmcp import FastMCP + + +DEFAULT_BASE_URL = "https://md-docrs.workedonmymachine.com" +USER_AGENT = "md-docrs-remote-mcp/0.1" + +mcp = FastMCP("md-docrs-remote") + + +def base_url() -> str: + return os.environ.get("MD_DOCRS_BASE_URL", DEFAULT_BASE_URL).rstrip("/") + + +def fetch_docs(spec: str, target: str | None = None) -> str: + params = {"spec": spec} + if target: + params["target"] = target + + url = f"{base_url()}/?{urllib.parse.urlencode(params)}" + request = urllib.request.Request( + url, + headers={ + "Accept": "text/markdown", + "User-Agent": USER_AGENT, + }, + ) + + try: + with urllib.request.urlopen(request, timeout=60) as response: + return response.read().decode("utf-8") + except urllib.error.HTTPError as err: + body = err.read().decode("utf-8", errors="replace") + raise RuntimeError(f"md-docrs returned HTTP {err.code}: {body}") from err + + +@mcp.tool() +def rust_docs(spec: str, target: str | None = None) -> str: + """Fetch Rust docs.rs documentation as Markdown. + + Spec format: crate[@version][::path::to::item]. + Examples: anyhow, anyhow::Error, tokio@1.52.1::sync::Mutex. + """ + return fetch_docs(spec, target) + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Run an MCP server backed by the remote md-docrs Worker." + ) + parser.add_argument( + "--smoke", + metavar="SPEC", + help="fetch one spec from the remote Worker and print Markdown", + ) + parser.add_argument( + "--target", + help="optional docs.rs target triple for --smoke", + ) + return parser.parse_args() + + +def main() -> None: + args = parse_args() + if args.smoke: + print(fetch_docs(args.smoke, args.target), end="") + return + + mcp.run() + + +if __name__ == "__main__": + main()