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 CLInexhelm-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:
- Backend's
GET /auth/cli-configreturns the public Supabase URL + anon key - CLI calls Supabase Auth
/token?grant_type=passwordwith email + password - CLI POSTs the resulting Supabase access token to backend
POST /auth/cli-token - Backend gates
@nexhelm.ai, mints a 24h HS256 service JWT, returns it + your clients - 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
- 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 auser_profilesrow exists with the rightorganization_id. - From the sandbox account, OAuth-connect Google Drive (or SharePoint)
via the standard connect-apps flow so
oauth_credentialsis populated —get_fresh_access_token(sandbox_user_id, "google_drive")must succeed for setup/teardown to work. POST /workflow-lab/sandbox-client— creates the dummyclientsrow the verifier targets astarget_client_id.POST /workflow-lab/sandbox-environmentswithprovider+ ablueprintJSON 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 aregoogleandsharepoint.
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 secondstart-verificationis 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 isawaiting_human_verify— a human has to walk the trace before the design is stampedverified.
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 vocabulary — send_*, 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_grantstable (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
- Create
src/nexhelm_mcp/agents/<name>.pyexposingAGENT = Agent(...)with one or moreAgentTool(...)entries. EachAgentTool.scopemust be added toauth.PHASE_1_SCOPES(or a new scope set for that agent). - Register in
agents/__init__.py. - Add typed wrappers in
server.pyso Claude gets a proper schema. - Optionally add CLI subcommands in
cli.py. - Add tests with
respxmocking the backend.
The lab_workflow agent is the reference template.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nexhelm-0.3.1.tar.gz.
File metadata
- Download URL: nexhelm-0.3.1.tar.gz
- Upload date:
- Size: 77.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
abb41d14085243d4e11d29675632aa7ff0cf9542733ba2256141fba0b43f8b53
|
|
| MD5 |
bf0a1fbc5d55028ca445b17a33ac822e
|
|
| BLAKE2b-256 |
aee2b16114ddf786ce52f4c0faa3253a794ed570d12120c346e57aa0118202cc
|
File details
Details for the file nexhelm-0.3.1-py3-none-any.whl.
File metadata
- Download URL: nexhelm-0.3.1-py3-none-any.whl
- Upload date:
- Size: 87.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ec2059d4a0edee13a40dea0ee26249e9fbdfca308b710c67f962795ec89505fa
|
|
| MD5 |
9eac948e76924d7ae047b95ec8bf19b8
|
|
| BLAKE2b-256 |
da6f9322f8e6abf8e87cfa9eab817d960d4bfdbc7d4e709e8008045458d98149
|