Skip to main content

Nexhelm CLI (`nex`). Public client for Nexhelm AI — login, workspaces, workflows. No core IP; the backend is the boundary.

Project description

nexhelm-mcp

CLI + MCP server for Nexhelm. Lets Claude Code (and humans) design, verify, and debug Workflow Lab runs end-to-end without leaving the terminal.

Architecture

═══════════════ EXTERNAL ════════════════════════════════════════════
   human ──┐
           │ approves at gates, paste transcript
           ▼
   Claude Code (external) ─── reads/writes code in repo with approval
   │ stdio
   ▼
═══════════════ SURFACE TIER (this package) ═════════════════════════
   nexhelm-mcp server  +  nex CLI
     - agent broker, capability scopes, view_urls, exit codes
     - never inlines prompts; never returns LLM internals
     - persona-as-action surface:
         lab_workflow.{integration_engineer, designer, refine, run, verifier, trace, evaluator, show, list}
   │ HTTPS, public schemas only
   ▼
═══════════════ ENGINE TIER (Nexhelm FastAPI, blackbox) ═════════════
   Persona registry (proprietary):
     integration_engineer    services/personas/integration_engineer/
     workflow_designer       services/workflow_lab/designer.py
     verifier_orchestrator   services/workflow_lab/orchestrator.py
     workflow_evaluator      services/workflow_lab/evaluator.py
     (future) log_aggregator, firm_wide, code, infra, cfp, ...

   Each persona owns its prompts (private), model config (private), and
   exposes a stable public input/output schema. The HTTP boundary crosses
   ONLY public schemas. No prompt or chain-of-thought ever reaches the
   surface tier.

Agent broker, not REST wrapper. The surface tier is pure transport. Each agent in the broker registers via agents/__init__.py; the server and CLI loop over the registry. Adding a "code agent" or "CFP agent" is one new file plus one import.

Persona pattern (engine). New blackbox personas live in fastapi_app/app/services/personas/<name>/{persona.py, prompts.py, schemas.py}. prompts.py is private; schemas.py is the public contract; routers serialize only the schema.

One-click URLs everywhere. Every agent return value includes view_url (and verification_task_url where applicable) so Claude prints a link and the user clicks straight into the frontend.

Install

Recommended (any user, any machine): one-shot via install.sh:

bash backend/tools/nexhelm-mcp/install.sh
# pipx-installs `nex` and `nexhelm-mcp-server` on PATH.
# Prints the exact Claude Code MCP config block to paste.

Or manual:

cd backend/tools/nexhelm-mcp
python3.12 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
pytest -q       # 117 passing
nex --help

Two entrypoints (pyproject.toml):

  • nex — human CLI
  • nexhelm-mcp-server — stdio MCP server for Claude Code

Setup — nex auth setup -e EMAIL (recommended)

Email + password, no browser, no env files. Works on any machine.

nex auth setup -e you@nexhelm.ai --backend-url <BACKEND_URL>
# Password: ****
# bootstrap: supabase_url=https://<project>.supabase.co; required email domain @nexhelm.ai
# ok authenticated as you@nexhelm.ai
#
# Your clients (12)
# 1 c_001 Acme Wealth   ops@acme.com
# 2 c_002 Smith FO      ...
# Pick by # (Enter to skip): 2
# ok Saved to ~/.nex/config (mode 0600)

nex doctor

What's happening:

  1. Backend's GET /auth/cli-config returns the public Supabase URL + anon key
  2. CLI calls Supabase Auth /token?grant_type=password with email + password
  3. CLI POSTs the resulting Supabase access token to backend POST /auth/cli-token
  4. Backend gates @nexhelm.ai, mints a 24h HS256 service JWT, returns it + your clients
  5. CLI saves the bundle to ~/.nex/config

No frontend/.env.local needed. No shared HMAC secret on your machine.

Optional: nex auth login (browser / PKCE)

Same end state as setup -e, but uses Supabase OAuth (Google by default) instead of email + password. Useful if you'd rather not enter your password into a CLI. Requires a one-time Supabase dashboard step:

  • Auth → Providers: enable Google (or pass --provider <name>)
  • Auth → URL Configuration → Redirect URLs: add http://localhost:7866/callback

If redirect_to isn't whitelisted, Supabase falls back to the project's "Site URL" and the browser lands somewhere weird (usually the deployed frontend behind Vercel SSO). One allowlist entry fixes it for everyone.

Manual fallback: nex auth setup (legacy local-mint)

Backend devs only. Reads the shared HMAC secret from frontend/.env.local (or NEX_* env vars) and mints JWTs locally per request. No backend hop, no Supabase round-trip — but only works for users with that env file on disk. Don't ship to the team; tell them to use setup -e.

nex auth setup
# Your user_id (Supabase UUID): <paste>
# Your client_id: <paste>

The CLI + the backend now live on the same branch (marx/nex-cli-phase1, which is rebased on top of marx/firm-workflow-context-design). Point --backend-url at any backend running this branch. Once both merge to main/staging, point at staging/main as normal.

Configure Claude Code

{
  "mcpServers": {
    "nexhelm": { "command": "nexhelm-mcp-server" }
  }
}

The MCP server inherits the same ~/.nex/config and frontend/.env.local that the CLI uses, so once nex doctor is green, Claude Code is good to go.

Sandbox verification

Verification runs the agent as a dedicated sandbox user, not as you. Each org has one sandbox user (a real user_profiles row + a real Google or SharePoint account, distinct from any human operator). The verifier agent's user_id is swapped to that sandbox user at /agent/init and /agent/chat time, so every Drive / Gmail / Calendar / CRM call hits the sandbox tenant — your own data is untouchable by construction.

Before each run, the orchestrator wipes the sandbox account's Drive (or SharePoint scope) and rebuilds it from a blueprint — folders, docs, sheets — recorded on the org's sandbox_environments row. After finalise_run (or cancel_run) the same wipe runs again. So fixtures are declarative; you don't preload your own Drive.

One-time per org

  1. Create a real Google / Microsoft account for the sandbox (e.g. sandbox+<orgslug>@nexhelm.ai); have it sign up through the normal user-onboarding flow so a user_profiles row exists with the right organization_id.
  2. From the sandbox account, OAuth-connect Google Drive (or SharePoint) via the standard connect-apps flow so oauth_credentials is populated — get_fresh_access_token(sandbox_user_id, "google_drive") must succeed for setup/teardown to work.
  3. POST /workflow-lab/sandbox-client — creates the dummy clients row the verifier targets as target_client_id.
  4. POST /workflow-lab/sandbox-environments with provider + a blueprint JSON describing the directory layout the workflow expects, e.g.:
    {
      "provider": "google",
      "blueprint": {
        "container": { "type": "folder_root", "name_template": "WFL Sandbox — {{organization_name}}" },
        "folders":   [{ "path": "Tax Returns 2023" }],
        "docs":      [{ "path": "Tax Returns 2023", "title": "Doe_2023_1040", "text": "..." }],
        "sheets":    [{ "path": "Tax Returns 2023", "title": "Doe_2023_W2",   "rows": [...] }]
      }
    }
    
    DB triggers enforce exactly one sandbox user per org; supported providers are google and sharepoint.

Per run

Pass -s/--sandbox-user-id at design time; discover candidates with GET /workflow-lab/sandbox-accounts:

nex workflow designer -u <gdoc> -s <sandbox_user_uuid>      # bind run to sandbox user
nex workflow run <run_id>                                    # setup → drive wiped + rebuilt; agent runs as sandbox user
nex workflow evaluator <run_id> --verdict approved           # cleanup wipes drive again

Concurrency + post-hoc gate

  • Only one active run per sandbox user (enforced by idx_workflow_lab_runs_active_sandbox_user). Two SEs in the same org cannot verify concurrently; the second start-verification is rejected.
  • Sandbox isolates blast radius; the evaluator still scans the trace for side-effect tools (send_/create_/update_/delete_/post_/patch_). If any fired and no reviewer verdict was supplied, terminal status is awaiting_human_verify — a human has to walk the trace before the design is stamped verified.

CLI commands

Auth

Command What it does
nex auth setup [-e EMAIL] [-b BACKEND_URL] [-f FRONTEND_URL] [--user-id ID] [--client-id ID] [--skip-verify] Interactive setup → ~/.nex/config (mode 0600). With -e, does Supabase email/password login + numbered client picker.
nex auth status Show config + probe /workflow-lab/runs.
nex auth clear Delete ~/.nex/config.

Workflow

Persona-as-action grammar. Same names appear as MCP tools (agents.lab_workflow.<persona>) — CLI and MCP stay in sync by construction.

Command What it does
nex workflow integration-engineer [-f FILE | --transcript-text TEXT | -u GDOC_URL] [--json] Run the integration_engineer persona: identify integration gaps. Returns capabilities the workflow needs and which are covered by existing Nexhelm tools vs missing. Run before designer when you suspect new integrations may be required. Powers the engineering loop (see below).
nex workflow sandbox-accounts [--include-disabled] [--json] List sandbox user accounts available to the operator. Surfaces sandbox_user_id + email + organization + which providers each sandbox has sandbox_environments rows for (note: this is snapshot-scope provisioning, not OAuth/API-key connectivity — Wealthbox tokens etc. don't show up here). Use to discover the value for designer --sandbox-user-id.
nex workflow designer [-f FILE | --transcript-text TEXT | -u GDOC_URL | --transcript-doc-id ID] [-s SANDBOX_USER_ID] [--target-client-id ID] [--json] Run the designer persona on a transcript → returns run_id, view_url, proposal. The designer prompt favors fully-autonomous workflows (≥80% agent-only steps); HITL pauses are flagged [HITL: <reason>] only when there's a hard regulatory reason. -s/--sandbox-user-id binds the run to a sandbox account; the resulting task_definition lives in the operator's org but the sandbox user gets a direct edit permission so they can resolve it via Refine-with-AI URLs. Discover ids via nex workflow sandbox-accounts.
nex workflow refine RUN_ID -m "FEEDBACK" | -F FILE [--json] Re-run the designer with operator feedback. Produces a NEW run id. The source run is auto-cancelled to free the single active-sandbox slot (idx_workflow_lab_runs_active_sandbox_user) — no need to cancel manually before refining. For sandbox runs that already passed start_verification, the auto-cancel routes through full cleanup so adapter state stays symmetric.
nex workflow run RUN_ID [-k PROMPT] [--as EMAIL] [--json] Autonomous end-to-end. Auto-fires the verifier (idempotent) then drains /langchain-agent/chat. Returns the agent transcript + a stop_reason so you see where it stopped: complete (call evaluator), interaction (HITL pause — refine to remove, or finish in dashboard), error (revise design), cut_off (network blip — re-run). Default kickoff prompt instructs the agent to be autonomous and fail loudly rather than ask. Sandbox runs auto-switch identity: if the run has sandbox_user_id, the backend transparently swaps the chat's user_id to the sandbox user (so its OAuth tokens / API keys drive tool calls) without forcing read_only — sandbox writes are allowed by design. No --as needed. --as EMAIL is still available for non-sandbox impersonation (e.g. debug against a real user's data); always READ-ONLY (write tools filtered). Operator must be in backend's IMPERSONATION_ALLOWLIST.
nex workflow verifier RUN_ID [--json] Manual path: spawn a client_task only. Prefer run for autonomous verification.
nex workflow trace RUN_ID [--json] Run state + verifier conversation rendered inline (role + tool calls).
nex workflow evaluator RUN_ID [--verifier-thread-id ID] [--verifier-execution-id ID] [--verdict approved|rejected] [--note TEXT] [--json] Run the evaluator persona: stamp run with trace ids + verdict. Verdict omitted ⇒ auto-classify.
nex workflow show RUN_ID [--json] Fetch a single run by id.
nex workflow list [-n LIMIT] [--sandbox-user-id ID] [--active] [--json] List runs for the configured user (1–200, default 50). --sandbox-user-id filters to runs bound to that sandbox; pair with --active to drop terminal-status rows — handy for finding which run is holding the active-sandbox slot.
nex workflow cancel [RUN_ID] [--sandbox-user-id ID] [--all-active] [--json] Cancel a run. Three mutually-exclusive modes: positional RUN_ID cancels one specific run; --sandbox-user-id cancels every non-terminal run bound to that sandbox; --all-active cancels every non-terminal run owned by the operator. Useful as a manual rescue when refine/designer can't auto-resolve (e.g. a run is stuck in setting_up).

Diagnostics

Command What it does
nex doctor End-to-end env checks (config, secret, JWT mint, backend reachability). Exit 0 = green.

MCP tools exposed to Claude Code

10 tools, each gated by a PHASE_1_SCOPES capability (auth.py):

Tool Args Notes
agents.list unscoped; tool registry browse
agents.lab_workflow.integration_engineer transcript_text? | transcript_url? gap analysis pre-design
agents.lab_workflow.designer transcript_text? | transcript_url? | transcript_doc_id?, target_client_id? designer prompt biased toward HITL-free workflows
agents.lab_workflow.refine run_id, feedback re-runs designer with feedback; produces NEW run id
agents.lab_workflow.run run_id, kickoff_prompt?, as_email? autonomous run. drains chat stream; returns events, stop_reason, thread_id. long-running. as_email impersonates that user (READ-ONLY): operator's email must be in backend IMPERSONATION_ALLOWLIST; backend resolves email→user_id; write tools filtered.
agents.lab_workflow.verifier run_id manual path; spawns client_task only
agents.lab_workflow.trace run_id run + verifier conversation
agents.lab_workflow.evaluator run_id, verifier_thread_id?, verifier_execution_id?, reviewer_verdict?, reviewer_note? stamps verdict
agents.lab_workflow.show run_id run record
agents.lab_workflow.list limit=50 recent runs

Impersonation (admin, read-only)

For verifying a workflow against a real (non-sandbox) user's data without writing to their systems. Sandbox runs do not need --as — the backend auto-switches identity via the client_task → workflow_lab_run → sandbox_user_id lookup and allows writes (sandbox is throwaway by design). Use --as for the genuinely different case: "run as Alice from firmcorp so I can see what her Drive looks like".

nex workflow run --as <email> runs the agent against the target's OAuth tokens (Drive/Gmail/Calendar/...) but with write tools filtered out of the agent's vocabularysend_*, create_*, update_*, delete_*, upload_*, post_*, etc. The LLM literally cannot call them, so accidental writes are impossible by construction (not by HITL gate).

nex workflow run <run_id> --as alice@firmcorp.com
# magenta banner: "impersonating user_email=alice@firmcorp.com (READ-ONLY)"
# kickoff: <default kickoff>
# running... agent may take 30-300s on complex workflows
# [agent transcript with no writes possible]

Allowlist gate (backend): your email must be in IMPERSONATION_ALLOWLIST (env var, comma-separated, case-insensitive):

# in your backend env (e.g. .env.local for local dev):
IMPERSONATION_ALLOWLIST=admin@nexhelm.ai,ops@nexhelm.ai

Without that, chat returns 403 and the CLI surfaces the error. The check runs at app/services/impersonation.py::resolve_impersonation and is re-validated on every chat call — there's no token-level grant. The target email is resolved to a user_id via user_profiles (404 if no match).

The CLI looks up the operator's email in ~/.nex/config (field user_email, set by nex auth setup -e <email>). If missing, --as fails fast with a clear error before any backend call.

What's filtered: the prefix list in impersonation.py:_WRITE_TOOL_PREFIXES plus a hand-audited override list. Tools matching set_thread_name and a small list of harmless mutations are kept. New tools that should be considered writes can be added to _WRITE_TOOL_NAMES.

Future (post-MVP):

  • Replace env-var allowlist with an impersonation_grants table (organization-scoped admin role + time-bound grants + audit log).
  • Per-tool allow/deny lists so an admin can opt-in to a narrow write (e.g. send a single confirmation email, scoped + signed).

Engineering loop — adding an integration

The full lifecycle from "user pastes a transcript" to "PR opened":

human pastes transcript
        │
        ▼
1. ANALYZE        nex workflow integration-engineer -u <gdoc>
                  → capabilities table, which exist, which are missing
        │
        ▼ (only if any are missing)
2. PLAN/IMPLEMENT external Claude Code (Claude Code) invokes the
                  add-integration skill at ~/.claude/skills/add-integration.md
                  Five phases with explicit approval gates:
                  CONSUME GAP REPORT  → wait `plan`
                  PROPOSE PLAN        → wait `approve`
                  IMPLEMENT           → wait `lab`
                  LAB VERIFY          → wait `pr`
                  PR
                  External Claude does code reads/writes locally; nex never
                  edits code.
        │
        ▼
3. DESIGN         nex workflow designer -u <gdoc>
                                       [--sandbox-user-id <id>]
                  → run_id, view_url, proposal
                  Designer prompt is biased toward fully-autonomous workflows
                  (≥80% agent-only steps); HITL pauses are flagged
                  `[HITL: <reason>]` only for hard regulatory gates.
                  Pass `--sandbox-user-id` to bind to a sandbox account
                  (discover with `nex workflow sandbox-accounts`); the run
                  then auto-switches identity in `run` (no `--as` needed).
        │
        ▼ (optional: review proposal, refine if needed)
3a. REFINE        nex workflow refine <run_id> -m "tighten X / drop Y"
                  → NEW run_id with refined proposal. Source run is
                  auto-cancelled to free the active-sandbox slot.
                  Loop here until the proposal is close enough to verify.
        │
        ▼
4. RUN            nex workflow run <run_id>
                  → autonomous: idempotently spawn verifier client_task,
                    POST kickoff to /langchain-agent/chat, drain SSE.
                  → prints conversation + stop_reason:
                      complete     → step 5
                      interaction  → revise (refine) or finish in dashboard
                      error        → revise the task definition; re-run
                      cut_off      → network/backend blip; re-run
                  This is the visibility primitive — operator sees where
                  the agent stopped and revises until it runs end-to-end
                  without intervention. (Manual fallback: `verifier` +
                  finish in dashboard + `trace`.)
        │
        ▼
5. FINALISE       nex workflow evaluator <run_id> --verdict approved|rejected
                  --verifier-thread-id <from `run` output>
        │
        ▼
6. PR             external Claude opens PR via `gh pr create` (linked from
                  the skill); engineering review takes over.

Engineering vs verification — separation of concerns. Nex runs the analyze + design + verify orchestration (engine tier); external Claude reads and writes code in the repo (with approval gates from the skill); human approves at every gate. The CLI is the only thing the human ever runs; the MCP server is the only thing external Claude calls. No double- implementation between nex and Claude.

Where the skill lives. External, in ~/.claude/skills/add-integration.md. Deliberately outside the codebase — keeps your code engine and the external AI/human persona separate. Update the skill independently of any repo PR.

Code layout (src/nexhelm_mcp/)

File Role
cli.py Typer app with auth/workflow/doctor command groups
server.py FastMCP server, one wrapper per agent tool
agents/lab_workflow.py Reference agent over /workflow-lab/*
auth.py HS256 JWT minting (≤90s TTL); PHASE_1_SCOPES
client.py BackendClient httpx wrapper
config.py ~/.nex/config (0600); reads secret from frontend/.env.local at runtime
supabase_auth.py Email/password login + client list helper
login.py Phase-2 PKCE browser flow placeholder

Adding a new agent

  1. Create src/nexhelm_mcp/agents/<name>.py exposing AGENT = Agent(...) with one or more AgentTool(...) entries. Each AgentTool.scope must be added to auth.PHASE_1_SCOPES (or a new scope set for that agent).
  2. Register in agents/__init__.py.
  3. Add typed wrappers in server.py so Claude gets a proper schema.
  4. Optionally add CLI subcommands in cli.py.
  5. Add tests with respx mocking the backend.

The lab_workflow agent is the reference template.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nexhelm-0.3.6.tar.gz (81.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

nexhelm-0.3.6-py3-none-any.whl (91.7 kB view details)

Uploaded Python 3

File details

Details for the file nexhelm-0.3.6.tar.gz.

File metadata

  • Download URL: nexhelm-0.3.6.tar.gz
  • Upload date:
  • Size: 81.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for nexhelm-0.3.6.tar.gz
Algorithm Hash digest
SHA256 5fe6a896370644dd748748430a0fe1dd4260dd46a76d1c0d38fcce12dad1d331
MD5 86f2f09d6f652458daaa3822e615f188
BLAKE2b-256 2b16f3cc6ac5f01635a7fd0fa5a73b6e5c883b40efe17f9a2ab5812f18efed13

See more details on using hashes here.

File details

Details for the file nexhelm-0.3.6-py3-none-any.whl.

File metadata

  • Download URL: nexhelm-0.3.6-py3-none-any.whl
  • Upload date:
  • Size: 91.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for nexhelm-0.3.6-py3-none-any.whl
Algorithm Hash digest
SHA256 f1621a1d722e366d99acceef1e8f2d43409c943eb1f91ef57e9ab100fbb699f2
MD5 84d5dcdb0dffad0d68cf95f7353b4e5c
BLAKE2b-256 0f5fe30c83c15c5f10da4ba2f49619e23416dfb888cbce1de75be696d4f89ea5

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page