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
1 change: 1 addition & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "walkerhughes-mcps",
"description": "MCP servers that give agents direct access to external services: the Harbor evaluation hub, with more to come.",
"owner": {
"name": "Walker Hughes",
"url": "https://github.com/walkerhughes"
Expand Down
2 changes: 1 addition & 1 deletion harbor-mcp/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "harbor-mcp",
"version": "0.3.0",
"version": "0.4.0",
"description": "MCP server for the Harbor hub: inspect evaluation jobs, trials, and uploads; publish tasks and datasets when writes are enabled.",
"author": {
"name": "Walker Hughes"
Expand Down
6 changes: 4 additions & 2 deletions harbor-mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Two separate commands. The first opens a prompt that expects only the `owner/rep

Requires [`uv`](https://docs.astral.sh/uv/) on your PATH; the plugin builds its own environment from the checked-in `uv.lock` on first launch, which takes a few seconds before the tools appear.

Then run `/harbor-mcp:setup`. It checks your credentials, walks you through login if needed, and summarizes what is currently in your account.
If you have already run `harbor auth login`, that is the whole setup: the server reads `~/.harbor/credentials.json` on its own. Ask the agent to call `whoami` to confirm.

## Credentials

Expand All @@ -32,7 +32,9 @@ harbor auth login

That mints a key scoped to the logged-in user and stores it in `~/.harbor/credentials.json`, which harbor reads on its own. The plugin needs no further configuration, and no key ever goes into a config file or a `.env`.

You do not need a global harbor install: harbor ships inside the plugin's environment, so `uv run --project <plugin-dir> harbor auth login` works too, which is what `/harbor-mcp:setup` falls back to.
You do not need a global harbor install: harbor ships inside the plugin's environment, so `uv run --project <plugin-dir> harbor auth login` works too.

There is no setup skill. If a tool hits an authentication error, it returns the recovery steps (`harbor auth status`, then `login` or `logout`) in its `suggestions`, so the agent gets them exactly when they are needed and they cost nothing otherwise. The plugin's standing guidance lives in the MCP server's `instructions`, which Claude Code loads into every session. A `CLAUDE.md` at the plugin root would *not* work: the [plugins reference](https://code.claude.com/docs/en/plugins-reference) states it is not loaded as project context.

`harbor auth status` shows what is stored, but it only reads that local file, so it still reports success for a key that has since been revoked. `whoami` is what confirms the credential is live; it returns the key *id* and source, never the key. Restart the server after changing credentials.

Expand Down
89 changes: 0 additions & 89 deletions harbor-mcp/skills/setup/SKILL.md

This file was deleted.

14 changes: 11 additions & 3 deletions harbor-mcp/src/harbor_mcp/infra/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,18 @@

logger = logging.getLogger(__name__)

# The whole auth recovery procedure lives here rather than in a skill: it costs
# nothing until auth actually fails, which is the only moment it is useful.
AUTH_SUGGESTIONS = [
"Set HARBOR_API_KEY in this repo's .env file (see .env.example).",
"Mint a key by running `harbor auth login` in a terminal.",
"Call the whoami tool to verify credentials once set.",
"Run `harbor auth status` in a terminal to see whether a credential is "
"stored. It exits 0 either way, so read its output, not its exit code.",
"If it reports `Not authenticated`, ask the user to run `harbor auth login` "
"themselves; it is an interactive OAuth flow and stores a key scoped to "
"them in ~/.harbor/credentials.json.",
"If it reports a logged-in user, the stored key is revoked or expired: ask "
"the user to run `harbor auth logout` and then `harbor auth login` again.",
"The server must be restarted to pick up new credentials. Then call whoami "
"to confirm.",
]


Expand Down
5 changes: 4 additions & 1 deletion harbor-mcp/src/harbor_mcp/tools/writes.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ def _writes_disabled() -> str | None:
return None
return error_response(
"Write tools are disabled.",
suggestions=["Set HARBOR_MCP_ENABLE_WRITES=true in .env to enable hub writes."],
suggestions=[
"Ask the user to export HARBOR_MCP_ENABLE_WRITES=true and restart "
"the server. Read-only is the intended default."
],
)


Expand Down
9 changes: 8 additions & 1 deletion harbor-mcp/tests/unit/test_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,14 @@ async def tool() -> str:

payload = json.loads(await tool())
assert "Not authenticated" in payload["error"]
assert any("HARBOR_API_KEY" in s for s in payload["suggestions"])
# The suggestions are the whole auth recovery procedure, since there is no
# setup skill: diagnose with `harbor auth status`, then login.
suggestions = " ".join(payload["suggestions"])
assert "harbor auth status" in suggestions
assert "harbor auth login" in suggestions
# Must not send anyone back to .env; the server stopped reading it, and a
# stale key there is what shadowed a good credentials.json once already.
assert ".env" not in suggestions


async def test_guarded_tool_maps_postgrest_error():
Expand Down
Loading