Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand Down
83 changes: 83 additions & 0 deletions mcp/md_docrs_remote.py
Original file line number Diff line number Diff line change
@@ -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()
Loading