diff --git a/CLAUDE.md b/CLAUDE.md index 301485c..e59b61d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -50,6 +50,18 @@ To add a new source: create `sources//` with a `tools.py` that exposes `register(mcp, ...)`, then call it from `server.build_server`. Keep tool names prefixed with the source so they don't collide. +The server is launched via `uv run` (see `.mcp.json`), so `uv` must be on the +user's PATH. A `SessionStart` hook (`hooks/hooks.json`) checks for uv at +session start and, when missing, prints install instructions to the user (and +tells Claude the tools are unavailable) instead of letting the server fail +silently. It ships in two shell-pinned flavours so it runs whichever shell +Claude Code picks: `hooks/check-uv.sh` (bash; macOS/Linux/Windows+Git Bash) +and `hooks/check-uv.ps1` (PowerShell; Windows without Git Bash) — both emit +the same SessionStart JSON. Neither depends on uv itself, and both stay silent +on the happy path. The `.ps1` path has not been exercised on a real Windows +host yet (cross-platform `shell`-field behaviour is undocumented), so verify +there before relying on it. + ## Instructions * To determine if a change was successful, run the test suite. diff --git a/README.md b/README.md index 0bff534..7a14a43 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,28 @@ This repository ships as a Claude Code plugin (the `.claude-plugin/` directory and the `research-person` skill under `skills/`). You can also run the MCP server stand-alone against any MCP client. +## Requirements + +[**uv**](https://docs.astral.sh/uv/) must be installed and on your `PATH` — +the MCP server is launched with `uv run` (see `.mcp.json`), which also +installs the Python dependencies on first run. Without uv the server cannot +start and none of the tools load. Install it with: + +```bash +# macOS / Linux +curl -LsSf https://astral.sh/uv/install.sh | sh + +# Windows (PowerShell) +irm https://astral.sh/uv/install.ps1 | iex +``` + +then restart Claude Code. As a convenience the plugin runs a `SessionStart` +hook that detects a missing uv and prints these instructions instead of +failing silently. The check ships in two flavours so it works regardless of +the shell Claude Code uses: `hooks/check-uv.sh` (bash — macOS, Linux, and +Windows with Git Bash) and `hooks/check-uv.ps1` (PowerShell — Windows +without Git Bash); `hooks/hooks.json` pins each to its shell. + ## Configuration Every knob can be set three ways. Precedence, highest to lowest: diff --git a/hooks/check-uv.ps1 b/hooks/check-uv.ps1 new file mode 100644 index 0000000..9bacc2b --- /dev/null +++ b/hooks/check-uv.ps1 @@ -0,0 +1,40 @@ +# SessionStart hook (PowerShell variant) for the genealogy plugin. +# +# Mirrors check-uv.sh for Windows hosts where Claude Code runs hooks via +# PowerShell (i.e. Git Bash is not installed). The genealogy MCP server is +# launched with `uv run`; if `uv` is missing the server never starts and its +# tools silently fail to load. This warns the user instead. +# +# Stays silent (exit 0, no output) on the happy path. +$ErrorActionPreference = 'Stop' + +if (Get-Command uv -ErrorAction SilentlyContinue) { + exit 0 +} + +# uv not found. Emit the same SessionStart JSON shape as check-uv.sh: +# systemMessage -> shown to the user +# hookSpecificOutput.additionalContext -> injected into Claude's context +# ConvertTo-Json handles all escaping (newlines, non-ASCII) for us. +$userMsg = @" +⚠️ genealogy plugin: 'uv' was not found on your PATH. + +The genealogy MCP server is launched with 'uv run', so its tools (heredis_*, geneteka_*, basia_*, …) will not load until uv is installed. + +Install uv → https://docs.astral.sh/uv/getting-started/installation/ + irm https://astral.sh/uv/install.ps1 | iex +Then restart Claude Code. +"@ + +$ctx = "The 'uv' CLI is not installed on this machine. The genealogy MCP server runs via 'uv run', so all genealogy plugin tools (heredis_*, gedcom_*, geneteka_*, genbaza_*, lubgens_*, basia_*, genpod_*, genealogyindexer_*, familysearch_*) are unavailable until the user installs uv (https://docs.astral.sh/uv) and restarts Claude Code. If asked to use any of these tools, explain this rather than attempting to call them." + +$payload = [ordered]@{ + systemMessage = $userMsg + hookSpecificOutput = [ordered]@{ + hookEventName = 'SessionStart' + additionalContext = $ctx + } +} + +$payload | ConvertTo-Json -Compress -Depth 5 +exit 0 diff --git a/hooks/check-uv.sh b/hooks/check-uv.sh new file mode 100755 index 0000000..0f7c5cd --- /dev/null +++ b/hooks/check-uv.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env sh +# SessionStart hook for the genealogy plugin. +# +# The genealogy MCP server is launched via `uv run` (see .mcp.json). If `uv` +# is not on the user's PATH the server never starts and its tools silently +# fail to load — a confusing failure mode. This hook checks for uv at session +# start and, when it is missing, surfaces a clear, actionable message to the +# user and tells Claude the tools are unavailable. +# +# Stays silent (exit 0, no output) on the happy path so it never adds noise. +set -eu + +if command -v uv >/dev/null 2>&1; then + exit 0 +fi + +# uv not found. Emit SessionStart JSON: +# systemMessage -> shown to the user +# hookSpecificOutput.additionalContext -> injected into Claude's context +# Newlines are written as literal \n (valid JSON escapes); printf's %s passes +# the backslash-n through untouched. +user_msg="⚠️ genealogy plugin: 'uv' was not found on your PATH.\n\nThe genealogy MCP server is launched with 'uv run', so its tools (heredis_*, geneteka_*, basia_*, …) will not load until uv is installed.\n\nInstall uv → https://docs.astral.sh/uv/getting-started/installation/\n curl -LsSf https://astral.sh/uv/install.sh | sh\nThen restart Claude Code." + +ctx="The 'uv' CLI is not installed on this machine. The genealogy MCP server runs via 'uv run', so all genealogy plugin tools (heredis_*, gedcom_*, geneteka_*, genbaza_*, lubgens_*, basia_*, genpod_*, genealogyindexer_*, familysearch_*) are unavailable until the user installs uv (https://docs.astral.sh/uv) and restarts Claude Code. If asked to use any of these tools, explain this rather than attempting to call them." + +printf '{"systemMessage":"%s","hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"%s"}}\n' "$user_msg" "$ctx" +exit 0 diff --git a/hooks/hooks.json b/hooks/hooks.json new file mode 100644 index 0000000..ae8cdef --- /dev/null +++ b/hooks/hooks.json @@ -0,0 +1,36 @@ +{ + "hooks": { + "SessionStart": [ + { + "matcher": "startup", + "hooks": [ + { + "type": "command", + "command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/check-uv.sh\"", + "shell": "bash" + }, + { + "type": "command", + "command": "Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force; & \"${CLAUDE_PLUGIN_ROOT}/hooks/check-uv.ps1\"", + "shell": "powershell" + } + ] + }, + { + "matcher": "resume", + "hooks": [ + { + "type": "command", + "command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/check-uv.sh\"", + "shell": "bash" + }, + { + "type": "command", + "command": "Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force; & \"${CLAUDE_PLUGIN_ROOT}/hooks/check-uv.ps1\"", + "shell": "powershell" + } + ] + } + ] + } +}