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
Binary file added .claude-plugin/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
14 changes: 14 additions & 0 deletions CLAUDE.md → .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
40 changes: 40 additions & 0 deletions PRIVACY.md
Original file line number Diff line number Diff line change
@@ -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).
60 changes: 44 additions & 16 deletions scripts/run.sh
Original file line number Diff line number Diff line change
@@ -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" "$@"
Loading