diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 8a1b0a0..ebece39 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -54,3 +54,43 @@ jobs: platforms: linux/amd64 tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + + publish-mcp: + needs: build-and-push + if: startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install MCP Publisher + run: | + curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" \ + | tar xz mcp-publisher + chmod +x mcp-publisher + + - name: Prepare server manifest + run: | + VERSION=${GITHUB_REF#refs/tags/v} + IMAGE_REPO=$(echo "${GITHUB_REPOSITORY}" | tr '[:upper:]' '[:lower:]') + IMAGE="ghcr.io/${IMAGE_REPO}:$VERSION" + jq --arg v "$VERSION" --arg image "$IMAGE" ' + .version = $v + | .packages = (.packages // [] | map( + if .registryType == "oci" then + (.identifier = $image) + else . + end + | (if has("version") then .version = $v else . end) + )) + ' server.json > server.publish.json + mv server.publish.json server.json + + - name: Login to MCP Registry + run: ./mcp-publisher login github-oidc + + - name: Publish to MCP Registry + run: ./mcp-publisher publish diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b15104..eceda79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,3 +7,5 @@ - add an Alpine-based Docker image build + entrypoint script plus usage docs for container publishing - publish the container automatically to GHCR using `.github/workflows/docker.yml` - update Docker builder stage to the latest stable Rust toolchain (1.91.1) for smaller, faster binaries +- add a self-contained pkgx pantry (`pkgx/`) with build/test metadata so `pkgx cratedocs` can install the server via the pkgx runtime, plus README instructions for using and upstreaming it +- add `just install-pkgx` to verify the pkgx pantry wiring end-to-end (falls back to a helpful message until the package is mirrored onto dist.pkgx.dev) diff --git a/README.md b/README.md index 341cd9a..f12ee67 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Rust Cargo Docs RAG MCP -`rust-cargo-docs-rag-mcp` is an MCP (Model Context Protocol) server that provides Rust crate documentation lookup and search tools intended for LLM assistants and other tooling. +`rust-cargo-docs-rag-mcp` is an MCP (Model Context Protocol) server that provides tools for Rust crate documentation lookup. It allows LLMs to look up documentation for Rust crates they are unfamiliar with. This README focuses on how to build, version, release, and install the project using two common paths: 1. pkgx (build/install locally from source) @@ -10,6 +10,30 @@ This README focuses on how to build, version, release, and install the project u ## Release / Versioning workflow (maintainers) +```bash +git clone https://github.com/promptexecution/rust-cargo-docs-rag-mcp.git +cd rust-cargo-docs-rag-mcp +cargo build --release +cargo install --path . +# Or install the pkgx-managed binary and check its version +just install-pkgx +``` + +### Installing with pkgx + +The repository includes a mini [pkgx pantry](./pkgx) so you can build and run the CLI through `pkgx` without touching your global toolchain: + +```bash +git clone https://github.com/promptexecution/rust-cargo-docs-rag-mcp.git +cd rust-cargo-docs-rag-mcp +export PKGX_PANTRY_PATH=$PWD/pkgx +export PKGX_PANTRY_DIR=$PWD/pkgx # pkgx^2 compatibility +pkgx cratedocs version +``` + +`pkgx` will download the tagged source tarball, compile `cratedocs` with the required Rust toolchain, and cache the result for subsequent runs. Once you're ready to upstream this package to the central [pkgx pantry](https://github.com/pkgxdev/pantry), copy `pkgx/projects/github.com/promptexecution/rust-cargo-docs-rag-mcp/package.yml` into a new PR there. + +## Running the Server This repository is wired to Cocogitto via `cog.toml`. Typical flow to create a release: 1. Install Cocogitto (once) @@ -66,6 +90,32 @@ Run in stdio mode: docker run --rm -e CRATEDOCS_MODE=stdio -i ghcr.io/promptexecution/rust-cargo-docs-rag-mcp:latest ``` +### Using Docker + +You can also build and run the server in an Alpine-based container. Prebuilt images are automatically published to GHCR via [`.github/workflows/docker.yml`](.github/workflows/docker.yml): + +```bash +docker pull ghcr.io/promptexecution/rust-cargo-docs-rag-mcp:latest +``` + +To build locally (useful before pushing to another registry): + +```bash +# Build the image (adjust the tag to match your registry) +docker build -t promptexecution/rust-cargo-docs-rag-mcp . + +# Run HTTP/SSE mode on port 8080 +docker run --rm -p 8080:8080 promptexecution/rust-cargo-docs-rag-mcp +``` + +Configuration is controlled through environment variables: +- `CRATEDOCS_MODE` (default `http`): switch to `stdio` to expose the stdio MCP server +- `CRATEDOCS_ADDRESS` (default `0.0.0.0:8080`): bind the HTTP server to a specific interface/port +- `CRATEDOCS_DEBUG` (default `false`): set to `true` to enable verbose logging in HTTP mode + +All additional arguments appended to `docker run ... -- ` are forwarded to the underlying `cratedocs` process. + +### Directly Testing Documentation Tools ### Environment Variables - `CRATEDOCS_MODE` (default: `http`) — set to `stdio` to run the stdio MCP server @@ -397,6 +447,17 @@ Then reference it normally in `mcp_settings.json`: --- +## Versioning & Releases + +This repository includes a [`cog.toml`](./cog.toml) profile wired to [`scripts/set-version.sh`](./scripts/set-version.sh) so [Cocogitto](https://github.com/cocogitto/cocogitto) can bump the crate version and regenerate the changelog automatically. + +Typical release flow: +1. `cargo install cocogitto` (once) +2. `cog bump minor` (or `patch`/`major`) – this updates `Cargo.toml`, `Cargo.lock`, and `CHANGELOG.md` +3. Review the generated changelog, run tests, and push the resulting tag/commit + +See [`CHANGELOG.md`](./CHANGELOG.md) for the latest published versions. + ## License MIT License diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 2cf991c..5ec0371 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -1,11 +1,19 @@ #!/bin/sh -set -e +set -eu -# docker/entrypoint.sh - small wrapper to start cratedocs with the configured mode MODE="${CRATEDOCS_MODE:-http}" ADDRESS="${CRATEDOCS_ADDRESS:-0.0.0.0:8080}" DEBUG="${CRATEDOCS_DEBUG:-false}" +if [ "$MODE" = "http" ]; then + if [ "$DEBUG" = "true" ]; then + exec /usr/local/bin/cratedocs http --address "$ADDRESS" --debug "$@" + else + exec /usr/local/bin/cratedocs http --address "$ADDRESS" "$@" + fi +else + exec /usr/local/bin/cratedocs "$MODE" "$@" +fi # If explicit args provided, run with those if [ "$#" -gt 0 ]; then exec /usr/local/bin/cratedocs "$@" diff --git a/justfile b/justfile index d3a80a7..1681007 100644 --- a/justfile +++ b/justfile @@ -16,6 +16,13 @@ pkgx-test: run: cargo run --bin cratedocs http --address 0.0.0.0:3000 --debug +install-pkgx: + @echo "Using pkgx pantry at {{invocation_directory()}}/pkgx" + PKGX_PANTRY_PATH={{invocation_directory()}}/pkgx \ + PKGX_PANTRY_DIR={{invocation_directory()}}/pkgx \ + pkgx cratedocs version || \ + (echo "pkgx failed (likely no network); see README for manual steps" && exit 1) + docker-build: docker build -t promptexecution/rust-cargo-docs-rag-mcp . diff --git a/pkgx/pkgx.yaml b/pkgx/pkgx.yaml new file mode 100644 index 0000000..cdc623b --- /dev/null +++ b/pkgx/pkgx.yaml @@ -0,0 +1,6 @@ +dependencies: + pkgx.sh/brewkit: ^0 || ^1 + +env: + PKGX_PANTRY_PATH: ${{srcroot}} + PKGX_PANTRY_DIR: ${{srcroot}} diff --git a/pkgx/projects/github.com/promptexecution/rust-cargo-docs-rag-mcp/package.yml b/pkgx/projects/github.com/promptexecution/rust-cargo-docs-rag-mcp/package.yml new file mode 100644 index 0000000..d68e014 --- /dev/null +++ b/pkgx/projects/github.com/promptexecution/rust-cargo-docs-rag-mcp/package.yml @@ -0,0 +1,25 @@ +distributable: + url: https://github.com/promptexecution/rust-cargo-docs-rag-mcp/archive/refs/tags/{{ version.tag }}.tar.gz + strip-components: 1 + +versions: + github: promptexecution/rust-cargo-docs-rag-mcp + +dependencies: + openssl.org: '>=1.1' + +build: + dependencies: + rust-lang.org: '>=1.91' + rust-lang.org/cargo: '*' + script: + - cargo install --locked \ + --root={{ prefix }} \ + --path=. \ + --bin cratedocs + +provides: + - bin/cratedocs + +test: + - cratedocs version | grep {{version}} diff --git a/scripts/set-version.sh b/scripts/set-version.sh index 87e9517..15de3fc 100755 --- a/scripts/set-version.sh +++ b/scripts/set-version.sh @@ -1,22 +1,52 @@ -#!/bin/sh -set -e +#!/usr/bin/env bash +set -euo pipefail -if [ -z "$1" ]; then - echo "Usage: $0 " >&2 - exit 2 +if [ "$#" -ne 1 ]; then + echo "usage: $0 " >&2 + exit 1 fi -VER="$1" -# Update Cargo.toml version field -if command -v perl >/dev/null 2>&1; then - perl -0777 -pe "s/^version\s*=\s*\".*\"/version = \"${VER}\"/m" -i Cargo.toml -else - sed -E "s/^version[[:space:]]*=.*$/version = \"${VER}\"/" Cargo.toml > Cargo.toml.tmp && mv Cargo.toml.tmp Cargo.toml -fi +version="$1" -# Regenerate lockfile -if command -v cargo >/dev/null 2>&1; then - cargo generate-lockfile || true -fi +# Update Cargo.toml package version (first occurrence only to avoid dependency matches) +python3 - "$version" <<'PY' +import pathlib, re, sys +version = sys.argv[1] +path = pathlib.Path("Cargo.toml") +text = path.read_text() +new_text, count = re.subn(r'(?m)^(version\s*=\s*)"[^"]+"', rf'\1"{version}"', text, count=1) +if count != 1: + raise SystemExit("Could not update version in Cargo.toml") +path.write_text(new_text) +PY + +# Update Cargo.lock entry for this crate +python3 - "$version" <<'PY' +import pathlib, sys +version = sys.argv[1] +path = pathlib.Path("Cargo.lock") +lines = path.read_text().splitlines() +out_lines = [] +in_pkg = False +target = 'name = "rust-cargo-docs-rag-mcp"' +updated = False +for line in lines: + stripped = line.strip() + if stripped == target: + in_pkg = True + out_lines.append(line) + continue + if in_pkg and stripped.startswith("version = "): + out_lines.append(f'version = "{version}"') + in_pkg = False + updated = True + continue + if stripped.startswith("name = ") and stripped != target: + in_pkg = False + out_lines.append(line) + +if not updated: + raise SystemExit("Could not update version in Cargo.lock") -exit 0 +path.write_text("\n".join(out_lines) + "\n") +PY diff --git a/server.json b/server.json new file mode 100644 index 0000000..bbe1c9d --- /dev/null +++ b/server.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json", + "name": "io.github.promptexecution/rust-cargo-docs-rag-mcp", + "title": "Rust Cargo Docs RAG", + "description": "Lookup Rust crate and item documentation via docs.rs and crates.io search.", + "websiteUrl": "https://github.com/promptexecution/rust-cargo-docs-rag-mcp", + "repository": { + "url": "https://github.com/promptexecution/rust-cargo-docs-rag-mcp", + "source": "github" + }, + "version": "0.3.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/promptexecution/rust-cargo-docs-rag-mcp:0.3.0", + "runtimeHint": "docker", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "name": "CRATEDOCS_MODE", + "value": "stdio", + "description": "Ensure the container exposes the MCP stdio transport when run by clients." + } + ] + } + ] +}