Skip to content

Authenticate with Claude Code's native MCP OAuth; migrate skills to the current server - #8

Closed
tanvi-cd wants to merge 10 commits into
mainfrom
add-crustdata-plugin
Closed

tanvi-cd wants to merge 10 commits into
mainfrom
add-crustdata-plugin

Conversation

@tanvi-cd

@tanvi-cd tanvi-cd commented Jul 28, 2026 •

Copy link
Copy Markdown
Collaborator

Rebuilt on the latest main. Three things, ending in a large deletion.

1. Drop the plugin's custom login stack (6709537)

The plugin shipped a second OAuth client: /crustdata:login, a 450-line browser/PKCE/DCR flow, a ~/.crustdata credential store with a cross-process refresh lock, an MCP headersHelper, a SessionStart token refresh, and a PreToolUse gate. Claude Code already does all of this for an http MCP server, so the layer was duplicated work carrying its own failure modes (the Windows/WSL and Node-24 fixes in this very PR were all fixes to that layer).

install.crustdata.com already serves everything the native flow needs — verified live:

Probe Result
POST /mcp (no auth) 401 + www-authenticate: Bearer … resource_metadata=".../.well-known/oauth-protected-resource/mcp"
GET /.well-known/oauth-protected-resource/mcp 200 → authorization_servers: ["https://install.crustdata.com/"]
GET /.well-known/oauth-authorization-server 200 → PKCE S256, registration_endpoint (DCR), authorization_code + refresh_token

Removing headersHelper is the change that hands the connection back to the client: while one is configured the plugin supplies the Authorization header itself, and a client-managed token has nothing to attach to.

Sign-in is now: Claude prompts on first Crustdata tool use, or /mcp → crustdata → Authenticate. Nothing to paste, no command to remember.

The PreToolUse gate had to be deleted rather than left inert — it exits 2 when the store has no token, and the store is now permanently empty, so it would have blocked every Crustdata tool call on a working connection.

Skill sync keeps its own path and now reads CRUSTDATA_API_KEY only. The client's token stays inside Claude Code with no supported way for a hook subprocess to read it, so the previously shared credential is gone. Without the env var the SessionStart hook no-ops exactly as it always did when no key was configured; bundled skills are unaffected. This is the one behaviour change worth a second opinion.

Net: −1,599 lines, five test files gone. Remaining suite (21 tests) green on Node 18/20/22/24 × 3 OS; the hook was also run standalone in both auth states (no key → one stderr line, exit 0; with key → normal path).

2. Migration to the current MCP server

candidate-sourcing and email-enrichment were written against the previous server's per-endpoint tools; both are replaced with their Code Mode rewrites (same workflows, driven through execute/callTool). Every remaining connector instruction points at install.crustdata.com/mcp.

3. Cross-platform CI

The suite runs on ubuntu × macos × windows over Node 18/20/22/24, so the documented Node ≥ 18 floor is enforced rather than asserted. This survives the deletion and now covers the sync hook.

Notes for review

  • History is left as-is. Commits 1–4 harden the login flow that commit 10 deletes. Rebasing to surgically drop them would rewrite a branch that also carries the unrelated server migration; the net diff is what matters and is easy to read.
  • Upgrade path: existing installs re-authenticate once via /mcp. They also keep a stale ~/.crustdata/credentials.json (or $CRUSTDATA_CONFIG_DIR) holding a live refresh token — nothing reads it any more, it is safe to delete, and the release notes should say so.
  • Headless/CI: the env var no longer authenticates the MCP connection (only skill sync and direct API use). Native OAuth is interactive by nature. A headless setup needs its own server entry with a static Authorization header — which suppresses the 401 challenge and so cannot be layered on the same entry as native OAuth. Called out rather than half-solved.
  • plugin.json 0.2.0 → 0.3.0 (user-visible auth change).

tanvi-cd added 2 commits July 28, 2026 21:08
…tes, plugin-scoped gate matcher, 3-OS CI

- crustdata-login: detect WSL and hand the sign-in URL to Windows via
  powershell.exe Start-Process (printed-URL fallback unchanged).
- credential-store: retry the atomic rename briefly on EPERM/EACCES/EBUSY —
  Windows AV scans and the session-start read can hold the destination open,
  and a failed swap must not drop a fresh login.
- hooks.json: the PreToolUse gate matched ^mcp__crustdata__ only, but tools
  from a plugin-bundled MCP server are scoped mcp__plugin_<plugin>_<server>__*,
  so the gate never fired. Match both forms.
- README: document the supported platforms and their credential-store semantics.
- CI: run the suite on ubuntu/macos/windows x Node 20/24 so the platform
  claims are enforced, not asserted.
…odies + remaining URLs

The candidate-sourcing and email-enrichment skills were written against the
previous server's per-endpoint tools; replace both with their Code Mode
rewrites (same workflows, driven through execute/callTool) in the skills/
layout. Point every remaining connector instruction (README, authoring guide)
at install.crustdata.com/mcp — .mcp.json already was — and fix the README's
relative skill links, stale since the skills moved under skills/.

Follow-up after merge: re-cut the Releases .skill bundles from these bodies.
@tanvi-cd
tanvi-cd force-pushed the add-crustdata-plugin branch from 23716d9 to 7812764 Compare July 28, 2026 15:39
@tanvi-cd tanvi-cd changed the title Crustdata Claude Code plugin: MCP connection, OAuth sign-in, skill sync — cross-platform Cross-platform hardening + migration to the current MCP server Jul 28, 2026
tanvi-cd added 8 commits July 28, 2026 21:12
First real windows-latest run: statSync reports 0o666 for a writable file
regardless of the mode passed (Node only maps the read-only attribute), so
the exact-0o644 pin is POSIX-only. Keep it there; on win32 assert the actual
C6 invariant — no exec bit leaks through (0o666 has none).
…er successful sign-in)

Both windows-latest Node 24 CI runs aborted identically: the sign-in fully
succeeds (code exchange + store write + confirmation printed), then
process.exit() races the loopback server's still-closing handles and libuv
asserts (src/win/async.c, STATUS_STACK_BUFFER_OVERRUN). Two changes:
wait for server.close() to complete (bounded at 1s so a lingering connection
can't stall the result), and let the event loop drain naturally with
process.exitCode — keeping an unref'd 3s process.exit backstop in case some
handle unexpectedly holds the loop open.
main already serves 0.1.0, and Claude Code detects plugin updates by version —
without the bump, existing installs would never pull the migrated skills and
cross-platform fixes in this branch.
…sync personal-email path

Mirrors the same fix at the source of truth (skills-internal, same branch
day): the GitHub-fallback snippet passed a LinkedIn URL as profile_url,
which the API rejects with a 400 — steer to crustdata_person_id — and the
personal-email note predated batch_person_identify, the async v2 path that
does resolve personal emails (1 credit per matched identifier).
Mirrors the source-of-truth commit: six-phase renumber (async
batch_person_identify becomes a real Phase 2 Branch B, the empty
email-substring phase is dropped), standalone Candidate verification
section, and no version-comparative phrasing anywhere in bodies, READMEs,
or evals.
Keeps this branch's skills/ byte-identical with the source of truth
(skills-internal) so the publish sync after its merge cannot conflict here.
… OAuth

The plugin shipped a second OAuth client: /crustdata:login, a 450-line
browser/PKCE/DCR flow, a ~/.crustdata credential store with a cross-process
refresh lock, an MCP headersHelper, a SessionStart token refresh, and a
PreToolUse gate. Claude Code already does all of this for an http MCP server,
so the layer was duplicated work with its own failure modes.

install.crustdata.com serves everything the native flow needs — verified live:
an unauthenticated POST /mcp answers 401 with a www-authenticate challenge
carrying resource_metadata=.../.well-known/oauth-protected-resource/mcp, which
resolves to an authorization server advertising PKCE S256, a registration
endpoint (DCR) and refresh grants. Dropping headersHelper is the change that
hands the connection back to the client: while one is configured the plugin
supplies the Authorization header itself, and a client-managed token has
nothing to attach to.

Signing in is now: Claude prompts on first use, or /mcp -> crustdata ->
Authenticate. Nothing to paste, no command to remember.

The PreToolUse gate had to be deleted rather than left inert. It exits 2 when
the store holds no token, and the store is now permanently empty, so it would
have blocked every Crustdata tool call on an otherwise working connection.

Skill sync keeps its own path and now reads CRUSTDATA_API_KEY only. The
client's token stays inside Claude Code with no supported way for a hook
subprocess to read it, so the previously shared credential is gone. Without
the env var the SessionStart hook no-ops exactly as it always did when no key
was configured; bundled skills are unaffected.

Existing installs keep a stale ~/.crustdata/credentials.json (or
$CRUSTDATA_CONFIG_DIR) holding a live refresh token. Nothing reads it any
more; it is safe to delete and the release notes should say so.
@tanvi-cd tanvi-cd changed the title Cross-platform hardening + migration to the current MCP server Authenticate with Claude Code's native MCP OAuth; migrate skills to the current server Aug 2, 2026
@tanvi-cd

tanvi-cd commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator Author

Superseded by crustdata/skills-internal#258 — the plugin is now maintained from skills-internal rather than here.

That PR adds a plugin/ folder holding this repo's entire source except skills/ (which the existing publish-to-public.yml already owns), plus a publish-plugin.yml hook that mirrors it onto this repo's root with rsync --delete. Deletion propagation is the reason it's a mirror rather than a copy: dropping a file there drops it here, which is what makes the login-stack removal in this PR actually land.

The tree in plugin/ is this PR's final state, so everything here ships when that one merges. Verified by running the exact mirror command against a real checkout of this repo's main: the 10 login-stack files delete, the manifests/README/hook update, and skills/ comes through untouched.

Closing rather than merging so there's one path into this repo. Nothing is lost — the branch stays if anything needs to be recovered.

@tanvi-cd tanvi-cd closed this Aug 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant