Make harbor auth login the only auth path; snapshot the account at setup#3
Merged
Merged
Conversation
…nt at setup
Authentication is now scoped to the logged-in user via `harbor auth login`,
which stores a key in ~/.harbor/credentials.json that harbor reads on its own.
This deletes the .env credential path added in the previous change.
Removed:
- load_dotenv in server.py, back to glue only. Harbor already resolves
credentials, so the loader was duplicating work for a path we no longer want.
- tests/unit/test_server.py, which only covered that loader.
- The HARBOR_API_KEY passthrough in .mcp.json. HARBOR_MCP_ENABLE_WRITES stays:
it is a local toggle, not a credential. Nothing in CI regresses, because the
test suite launches the server directly and inherits the process environment
rather than going through .mcp.json.
HARBOR_API_KEY still works as an override, because harbor itself checks it
first. That is now documented as a CI and scripting path rather than an
interactive one.
Setup skill rewrite:
- Step 1 is `harbor auth status`, which replaces four separate bash probes
(harbor on PATH, credentials.json present, .env present, key line in .env).
It exits 0 whether or not the user is authenticated, so the skill branches on
the output string, not the exit code.
- Falls back to `uv run --project ${CLAUDE_PLUGIN_ROOT} harbor auth status`,
since harbor ships inside the plugin's environment. Users no longer need a
global harbor install to authenticate.
- `whoami` is retained as the validator: `harbor auth status` only reads a local
file, so it reports success for a key that has since been revoked.
- New step calls list_jobs once and summarizes the account into the
conversation, so the session starts with context. Deliberately not cached to
disk: reading a cache costs the same one tool call as calling list_jobs, and
job status and cost change underneath it.
Net 108 lines removed.
Verified: ruff clean, 69 unit tests pass. `harbor auth status` exercised in both
branches, including with harbor absent from PATH and with an empty HOME. With
HARBOR_API_KEY unset, the plugin launch command authenticates from
credentials.json (whoami reports key_source "file", no key in the payload) and
list_jobs returns 20 of 55 jobs.
walkerhughes
force-pushed
the
feat/login-only-auth-and-account-snapshot
branch
from
July 25, 2026 06:18
cf44a0a to
8f20714
Compare
Fixes the plugin failing to connect with an opaque JSON-RPC -32000. Root cause: .mcp.json named "uv" as the command, which assumes uv is on whatever PATH the MCP client spawns the server with. It often is not. This machine's uv is a Homebrew install at /opt/homebrew/bin/uv, and that directory is absent from the minimal PATH some launch contexts provide, so the spawn failed with "env: uv: No such file or directory" before the server ever ran. Reproduced directly: launching the installed plugin under `env -i PATH=/usr/bin:/bin` fails, while the same command with the full PATH succeeds. This is also why warming the plugin's venv did not help; the venv was never the problem, and cold start measures ~4.8s including a full handshake. scripts/start-server.sh now: - resolves the plugin root from its own location rather than CLAUDE_PLUGIN_ROOT, so it works however it is launched - searches PATH, then /opt/homebrew/bin, /usr/local/bin, ~/.local/bin and ~/.cargo/bin, covering Homebrew on both architectures, the standalone installer and cargo install - exits 127 with an actionable message on stderr when uv is genuinely missing, instead of surfacing as -32000 with no explanation - keeps stdout clean, since that is the JSON-RPC channel Also drops the .mcp.json env block entirely. HARBOR_MCP_ENABLE_WRITES is inherited from the environment like any other variable, and removing the block means nothing in the config can interfere with PATH inheritance. Verified: under `env -i PATH=/usr/bin:/bin`, the previously failing case, the server now completes initialize and lists all 15 tools. With every uv candidate pointed at a nonexistent path, it exits 127, writes 0 bytes to stdout, and prints the remediation to stderr. The local dev path (cwd=harbor-mcp, CLAUDE_PLUGIN_ROOT unset) still starts. Ruff clean, 69 unit tests pass.
walkerhughes
added a commit
that referenced
this pull request
Jul 25, 2026
… fix (#4) * fix: drop inherited VIRTUAL_ENV in the launcher uv already ignores a VIRTUAL_ENV that does not match the project, but it warns loudly on stderr. That warning reads like a cause of failure and already sent one debugging session chasing it instead of the real PATH problem. Verified: with a foreign VIRTUAL_ENV set, the warning count on stderr is now 0 and the server still lists all 15 tools. The minimal-PATH case that produced -32000 still starts. * fix: bump plugin version to 0.2.0 so installed copies pick up the launcher The launcher fix in #3 never reached anyone. Claude Code extracts an installed plugin to a cache path keyed by the manifest version (~/.claude/plugins/cache/<marketplace>/<plugin>/<version>), and #3 left that at 0.1.0. The marketplace git clone advanced to 0637d00 and carries the launcher, but the extracted copy under cache/.../harbor-mcp/0.1.0 still has "command": "uv" and no scripts/ directory, so the -32000 persisted through marketplace updates and reinstalls alike. Confirmed on a live install: installed_plugins.json still records version 0.1.0 with gitCommitSha 2c1dfc2, the commit from #2. - Bumps the version to 0.2.0, which changes the cache key and forces a fresh extraction. - Recovers the VIRTUAL_ENV fix that was pushed to #3's branch after it merged and was therefore orphaned. - Adds a plugin-version workflow that fails a PR touching shipped plugin files without a version bump. Tests and evals are excluded, since they are not copied into the plugin cache and cannot strand a user. Verified both ways: it passes with the bump and blocks when the version is left unchanged. - Documents the requirement in the root README.
walkerhughes
added a commit
that referenced
this pull request
Jul 25, 2026
…lugin dir (#5) This is the actual cause of the -32000 that has blocked the plugin since #2. Both earlier diagnoses were wrong. The MCP server log tells it plainly. Under the original `command: "uv"` config: Server stderr: error: Failed to spawn: `harbor-mcp` Caused by: No such file or directory (os error 2) and after #3 switched to the launcher script: Server stderr: bash: ./scripts/start-server.sh: No such file or directory Connection failed after 10ms: MCP error -32000: Connection closed Both are the same bug. `${CLAUDE_PLUGIN_ROOT:-.}` expanded to `.`, never to the plugin directory, so the server was launched against whatever project the user was in. The `:-default` form is handled by environment-variable expansion, which does not know CLAUDE_PLUGIN_ROOT, so the default always won. Only the bare form is substituted by the plugin loader. What the earlier diagnoses got wrong: - Not a uv PATH problem. "Failed to spawn: harbor-mcp" is uv's own error, so uv was found and running; it had simply resolved the wrong project. - Not a cold-start timeout. The log shows a 30000ms timeout and failures at 10-40ms, and the venv is present. Adds tests/unit/test_plugin_config.py, which fails if a :-default reappears on CLAUDE_PLUGIN_ROOT or if the referenced launcher is missing or non-executable. Verified it catches the real bug: reintroducing `:-.` fails two of its tests. This class of breakage is invisible to every other test, since it only surfaces at MCP connect time as an opaque -32000 in ~/Library/Caches/claude-cli-nodejs/<project>/mcp-logs-plugin-harbor-mcp-harbor-hub/. Bumps to 0.3.0 so installed copies actually receive it. Local development now runs ./scripts/start-server.sh directly, since .mcp.json is plugin-only; the README says so and explains why a :-default must not be added back.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Scopes authentication to the logged-in user and drops the
.envcredential path added in #2. Net 108 lines removed.Authentication
harbor auth loginmints a key scoped to the logged-in user and stores it in~/.harbor/credentials.json, which harbor reads on its own. That made most of the previous machinery redundant.Removed:
load_dotenvinserver.py. Harbor already resolves credentials, so the loader duplicated work for a path we no longer want.server.pyis back to 30 lines of glue.tests/unit/test_server.py, which only covered that loader.HARBOR_API_KEYpassthrough in.mcp.json.HARBOR_MCP_ENABLE_WRITESstays: it is a local toggle, not a credential.HARBOR_API_KEYstill works as an override because harbor itself checks it first. It is now documented as a CI and scripting path rather than an interactive one.CI is unaffected. The test suite launches the server via
sys.executable -m harbor_mcp.serverand inherits the process environment, so it never goes through.mcp.json.Setup skill rewrite
Step 1 is now
harbor auth status, replacing four separate bash probes (harbor on PATH,credentials.jsonpresent,.envpresent, key line inside.env).Two details worth calling out:
harbor auth statusexits 0 whether or not the user is authenticated, so the skill branches on the output string, not the exit code. Keying off$?would report everyone as authenticated.whoamiis retained as the validator.harbor auth statusonly reads a local file, so it reportsLogged infor a key that has since been revoked or expired. Status proves a credential exists;whoamiproves it is live.The skill falls back to
uv run --project ${CLAUDE_PLUGIN_ROOT} harbor auth login, since harbor ships inside the plugin's own environment. Users no longer need a global harbor install to authenticate, so this removes a prerequisite rather than adding one.Account snapshot
After auth succeeds, setup calls
list_jobsonce and summarizes into the conversation: total count, the few most recent, anything running or errored.Deliberately not cached to disk. Reading a cache file costs the agent exactly one tool call, the same as calling
list_jobs, so a cache saves nothing on latency or tokens while returning stale data as job statuses flip and costs accrue. The measured payload is 1412 chars for 5 rows, so on-demand is cheap. The transcript is the cache, and it is session-scoped and never misleadingly stale.check_task_publishedandresolve_datasetboth require an explicit org and name, so there is nothing to enumerate for the registry; the skill skips them.Verification
harbor auth status, authenticatedLogged in as walkerhughes (API key sk-harbor-mdAIchfaDmNc…)harbor auth status, emptyHOMENot authenticated. Run \harbor auth login`.` (exit 0 in both cases)HARBOR_API_KEYunsetwhoamireturnskey_source: "file", no key in payloadlist_jobsthrough the pluginAlso splits the two
/plugincommands in the READMEs onto separate blocks. Pasting them together lands both lines in the marketplace prompt's input field and fails with a confusing shorthand error.