diff --git a/.claude-plugin/icon.png b/.claude-plugin/icon.png new file mode 100644 index 0000000..90a54cb Binary files /dev/null and b/.claude-plugin/icon.png differ diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 4766ae0..d3a5387 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -2,6 +2,25 @@ "name": "iwdp-mcp", "version": "0.5.3", "description": "iOS Safari debugging via ios-webkit-debug-proxy — MCP server with full WebKit Inspector Protocol support", + "author": { + "name": "Nikita Nemirovsky", + "url": "https://github.com/nnemirovsky" + }, + "homepage": "https://github.com/nnemirovsky/iwdp-mcp", + "repository": "https://github.com/nnemirovsky/iwdp-mcp", + "documentationUrl": "https://github.com/nnemirovsky/iwdp-mcp#readme", + "supportUrl": "https://github.com/nnemirovsky/iwdp-mcp/issues", + "privacyPolicyUrl": "https://github.com/nnemirovsky/iwdp-mcp/blob/main/PRIVACY.md", + "license": "MIT", + "keywords": [ + "ios", + "safari", + "webkit", + "debugging", + "web-inspector", + "iphone", + "mcp" + ], "mcpServers": { "iwdp-mcp": { "command": "${CLAUDE_PLUGIN_ROOT}/scripts/run.sh" diff --git a/CLAUDE.md b/.claude/CLAUDE.md similarity index 91% rename from CLAUDE.md rename to .claude/CLAUDE.md index 1399bbe..866a01d 100644 --- a/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -136,6 +136,20 @@ git tag vX.Y.Z git push origin vX.Y.Z ``` +`scripts/run.sh` downloads the server binary for the version in `plugin.json` and +checks it against that release's checksums file. Until the tag's release workflow +has published its assets, the plugin at that commit cannot start, so publish a +plugin version in the directory only after its GitHub release exists. + +## Plugin Directory + +- This file lives in `.claude/` because a `CLAUDE.md` at the plugin root draws a + validator warning. It still loads as project memory. +- No text file may name the listing icon or any other image or font file in the + plugin. That puts the plugin on a policy hold. +- The launcher downloading and running a binary is an expected policy hold: a + reviewer reads `scripts/run.sh` for each version. + ## Key Dependencies - `github.com/modelcontextprotocol/go-sdk` v1.4.0 — official MCP Go SDK diff --git a/PRIVACY.md b/PRIVACY.md new file mode 100644 index 0000000..9835386 --- /dev/null +++ b/PRIVACY.md @@ -0,0 +1,40 @@ +# Privacy policy + +iwdp-mcp runs entirely on your machine. It has no server of its own, collects no +telemetry, and sends nothing to the author or to any third party. + +## Network access + +* On first run, `scripts/run.sh` downloads the server binary for the installed + plugin version from this repository's GitHub releases, together with the + release's checksums file. GitHub sees that request like any other download. +* After that, the server talks only to `localhost`: to ios-webkit-debug-proxy, + which it may start for you, and through it to Safari on a device or simulator + you have connected and enabled Web Inspector on. + +## What it reads + +Only what you or Claude ask for through its tools, from the Safari page being +debugged. Depending on the tool, that can include the page's DOM, styles, console +output, network requests and responses, cookies (including httpOnly ones), local +and session storage, IndexedDB, and screenshots. That data can contain personal +information if the page you are debugging holds any. + +## What it writes + +* The downloaded binary, under the plugin's `bin/` directory. +* Screenshots, heap snapshots and large JSON results, under + `$TMPDIR/iwdp-mcp/`, so they can be handed to Claude as files. +* Changes to the debugged page when a tool is asked to make them, such as + navigating, running JavaScript, editing the DOM or CSS, or setting cookies and + storage. + +## What it passes to Claude + +Tool results go back to Claude Code, which handles them under Anthropic's terms, +not this plugin's. Avoid debugging pages that hold data you do not want in your +session. + +## Contact + +Questions go to [GitHub issues](https://github.com/nnemirovsky/iwdp-mcp/issues). \ No newline at end of file diff --git a/scripts/run.sh b/scripts/run.sh index 4082a64..1dbfa95 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -1,36 +1,64 @@ #!/usr/bin/env bash +# Launches the iwdp-mcp server, downloading its release binary on first run. +# +# The binary is pinned to this plugin's own version and checked against the +# release's checksums file before it is ever run. set -euo pipefail REPO="nnemirovsky/iwdp-mcp" BINARY_NAME="iwdp-mcp" -# Determine install location: prefer plugin root, fall back to script dir. -if [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then - INSTALL_DIR="${CLAUDE_PLUGIN_ROOT}/bin" -else - INSTALL_DIR="$(cd "$(dirname "$0")" && pwd)/../bin" +PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}" + +# Release tags match the plugin version, so a plugin version always runs the +# binary built from the same commit. +VERSION="$(sed -nE 's/^[[:space:]]*"version":[[:space:]]*"([^"]+)".*/\1/p' "${PLUGIN_ROOT}/.claude-plugin/plugin.json" | head -1)" +if [ -z "$VERSION" ]; then + echo "iwdp-mcp: cannot read the plugin version from ${PLUGIN_ROOT}/.claude-plugin/plugin.json" >&2 + exit 1 fi -BINARY="${INSTALL_DIR}/${BINARY_NAME}" +INSTALL_DIR="${PLUGIN_ROOT}/bin" +BINARY="${INSTALL_DIR}/${BINARY_NAME}-${VERSION}" -if [ ! -f "$BINARY" ]; then +if [ ! -x "$BINARY" ]; then OS="$(uname -s | tr '[:upper:]' '[:lower:]')" + case "$OS" in + darwin|linux) ;; + *) echo "iwdp-mcp: no release binary for ${OS}" >&2; exit 1 ;; + esac ARCH="$(uname -m)" case "$ARCH" in - x86_64) ARCH="amd64" ;; - aarch64) ARCH="arm64" ;; + x86_64|amd64) ARCH="amd64" ;; + aarch64|arm64) ARCH="arm64" ;; + *) echo "iwdp-mcp: no release binary for ${ARCH}" >&2; exit 1 ;; esac - # Resolve latest version tag (e.g. "v0.1.0" → "0.1.0") - VERSION="$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" | grep '"tag_name"' | sed -E 's/.*"v?([^"]+)".*/\1/')" ASSET="${BINARY_NAME}_${VERSION}_${OS}_${ARCH}" - URL="https://github.com/${REPO}/releases/download/v${VERSION}/${ASSET}" + BASE="https://github.com/${REPO}/releases/download/v${VERSION}" - echo "Downloading ${BINARY_NAME} v${VERSION} from ${URL}..." >&2 mkdir -p "$INSTALL_DIR" - curl -fsSL "$URL" -o "$BINARY" - chmod +x "$BINARY" - echo "Installed ${BINARY_NAME} v${VERSION} to ${BINARY}" >&2 + TMP="$(mktemp "${INSTALL_DIR}/.${ASSET}.XXXXXX")" + trap 'rm -f "$TMP"' EXIT + + echo "Downloading ${ASSET} from ${BASE}..." >&2 + curl -fsSL "${BASE}/${ASSET}" -o "$TMP" + + EXPECTED="$(curl -fsSL "${BASE}/${BINARY_NAME}_${VERSION}_checksums.txt" | awk -v f="$ASSET" '$2 == f { print $1 }')" + if command -v sha256sum >/dev/null 2>&1; then + ACTUAL="$(sha256sum "$TMP" | awk '{ print $1 }')" + else + ACTUAL="$(shasum -a 256 "$TMP" | awk '{ print $1 }')" + fi + if [ -z "$EXPECTED" ] || [ "$EXPECTED" != "$ACTUAL" ]; then + echo "iwdp-mcp: checksum mismatch for ${ASSET}, refusing to run it" >&2 + exit 1 + fi + + chmod 755 "$TMP" + mv "$TMP" "$BINARY" + trap - EXIT + echo "Installed ${ASSET} to ${BINARY}" >&2 fi exec "$BINARY" "$@"