PluginWorld
Ha

Haven-AI

MCP

Haven MCP server for local, non-custodial AI-agent payments — pay x402 (HTTP 402) APIs within on-chain-enforced budgets.

@d-hinders · v0.1.0 · MIT · updated yesterday

SECURITY

B

SCORE

71

INSTALLS

▲ 18.5K

PLUG IN

git clone https://github.com/d-hinders/Haven-AI.git

See the README to configure this MCP server

README


owner: "@d-hinders" status: current covers:

  • .nvmrc
  • .env.example
  • docker-compose.yml
  • package.json
  • packages/backend/package.json
  • packages/frontend/package.json
  • packages/sdk/package.json
  • packages/mcp/package.json
  • packages/mcp-server/package.json
  • packages/signer/package.json
  • packages/connect/package.json
  • packages/demo-merchant-mcp/package.json
  • .github/workflows/publish.yml
  • scripts/release-bump.mjs last-verified: "2026-09-30"

Haven

Haven is an agentic stablecoin payment wallet. Users create or link a Haven account, add funds to a Haven wallet, and give AI agents constrained spending ability through agent rules and budgets.

Haven is non-custodial smart account software. User funds stay in a user-controlled MetaMask Hybrid DeleGator smart account, shown in product copy as a Haven wallet. An agent's spending authority is a delegation you sign — a budget with audited caveat enforcers (period budget with native refill, optional recipient pin, expiry) that the DelegationManager enforces on-chain at redemption. Haven helps users configure that authority, construct payment payloads, relay independently signed transactions, and understand activity. It does not hold user or agent private keys, make API credentials sufficient to spend, or make discretionary transfer decisions — and it holds no key that can change an account's signer set or redeem a delegation you did not sign.

Are you an AI agent whose user has no Haven account yet?

Read /for-agents.md on the Haven host your user gave you — or the copy in this repository if you do not have that host yet.

Your user creates the account and the passkey: those are theirs, they need a human, and you should never ask for their password. You can do everything else — including running the connector command from the setup prompt they paste you, and managing the account from the shell with @haven_ai/cli.

Not to be confused with AGENTS.md. That file is for contributors to this repository — build, test and review conventions for changing Haven itself. It is not a guide for an agent that wants to USE Haven to pay for something; this section is.

Core Model

Agents should not be wallets. They should be payment actors with constrained authority.

User   -> Haven wallet / Hybrid DeleGator (funds and signer authority)
Agent  -> Haven credential (identity) + delegate signing key (payment signatures)
Haven  -> UI, API, pre-checks, relay, receipts, status
Signed delegation + caveat enforcers -> On-chain agent budget enforcement

API auth is identity. Signature is authority. On-chain delegation state is enforcement.

Haven runs one live on-chain policy rail: the delegation rail (epic #821). Funds move account→recipient directly — no funding leg, no approval queue, and a payment outside the budget, recipient pin or expiry reverts during gas estimation rather than queueing for a human.

The legacy Safe + AllowanceModule rail is RETIRED (epic #1440), not frozen: nothing can enter it, nothing on it can spend, and its execution machinery is deleted. The closure sequence is recorded once in the decision log and is not restated here (#2640). Existing Safe rows are untouched and still readable to a direct database query, but since #2413 no account, agent or dashboard surface displays them — balances, tokens and agents no longer render there. Transaction history is the named exception: GET /transactions carries no rail predicate on either its account or its agent list, so legacy names still appear in its picklists. Any residual AllowanceModule permission remains on-chain and outside Haven until the Safe owner revokes it externally. The Smart Sessions session rail is likewise retired (#834). See docs/security/delegation-rail-security-model.md, docs/product/account-recovery.md for what a legacy Safe owner can still do, and your non-custody exit path.

What's in the Repo

This is a TypeScript monorepo:

Package Description
packages/backend Fastify API for auth, Haven wallets, agents, payments, x402/MPP, receipts, the Fortnox accounting feed, and OpenAPI (the legacy approval queue is gone entirely — its route was deregistered and its table dropped by #2055)
packages/frontend Next.js dashboard for Haven accounts, Haven wallets, agent rules, connect-agent handoff, and activity
packages/sdk @haven_ai/sdk for direct agent integrations, tool definitions, x402/MPP quote/pay/resume helpers, and payment state handling
packages/mcp @haven_ai/mcp local stdio MCP server that reads a local credential file and signs locally
packages/mcp-server @haven_ai/mcp-server hosted/keyless Streamable HTTP MCP server that constructs and relays but never signs
packages/signer @haven_ai/signer local edge signer used with hosted MCP; it holds the delegate key locally and exposes sign-only tools
packages/cli @haven_ai/cli terminal-native, scriptable parallel to the dashboard (login, reads, backend-only management)
packages/demo-merchant-mcp Internal x402 demo merchant MCP server for Base USDC test purchases and Swedish invoice output
packages/qa-agent Internal QA harness for the dev environment: deterministic money-flow scenarios and dev seeding (not published)

Team Docs

Prerequisites

  • Node.js 24 (LTS) — pinned in .nvmrc; CI and the Docker images run the same major. (The published agent-runtime packages declare a lower engines floor, >=22 — see mcp-runtime-compatibility.) With nvm or fnm, run nvm use / fnm use in the repo root to match it automatically (otherwise npm install warns EBADENGINE).
  • Docker Desktop (for local hosting) — docker.com/products/docker-desktop
  • A browser wallet (MetaMask, Rabby, etc.) with Gnosis Chain or Base configured — optional: signup uses a passkey, and a wallet is only needed if you want to enrol one as an account signer

Getting Started

1. Clone and install

git clone https://github.com/d-hinders/Haven-AI.git
cd Haven-AI
npm install

2. Configure environment

cp .env.example .env

Edit .env and fill in the required values:

Variable Required Description
DATABASE_URL Yes PostgreSQL connection string (default works with Docker)
JWT_SECRET Yes Secret for dashboard auth tokens; use a long random string in production
RPC_URL No Gnosis Chain RPC (default: https://rpc.gnosischain.com)
RPC_URL_BASE No Base RPC (default: https://mainnet.base.org)
RELAYER_PRIVATE_KEY Yes for on-chain execution EOA private key that pays gas for relayed transactions; it cannot access user funds
GNOSISSCAN_API_KEY No Gnosis explorer API key for transaction display
BASESCAN_API_KEY No Base explorer API key when using an Etherscan-style Base source; Base currently defaults to Blockscout for transactions
COINGECKO_API_KEY No Token price lookups
FRONTEND_URL No Backend CORS/link base (default: http://localhost:3000)
NEXT_PUBLIC_API_URL No Frontend backend URL override (default through local rewrite: http://localhost:3001)
NEXT_PUBLIC_HAVEN_MCP_URL No Hosted MCP URL shown in connect-agent snippets
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID No WalletConnect project id; injected wallet connectors can still work without it
ANTHROPIC_API_KEY No Only for the optional Claude agent demo script

Setting up the relayer wallet:

The relayer is a throwaway EOA that pays gas for on-chain agent payments. It never has access to user funds — it just submits transactions. Generate one:

node -e "const{ethers}=require('ethers');const w=ethers.Wallet.createRandom();console.log('Address:',w.address);console.log('Key:',w.privateKey)"

Fund the relayer with the native token for each chain you plan to use:

  • Gnosis Chain: 0.01 xDAI (enough for thousands of transactions)
  • Base: 0.001 ETH

Put the private key in RELAYER_PRIVATE_KEY.

3. Start PostgreSQL

Make sure Docker Desktop is running:

npm run docker:up

4. Start the dev servers

npm run dev

5. Create your Haven account

  1. Go to http://localhost:3000
  2. Click Get Early Access → create an account
  3. Pick your network, then create the account with a passkey — one Face ID / Touch ID prompt
  4. You'll land on the dashboard with your Haven wallet address

One onboarding path, no transaction. Signup provisions a counterfactual passkey-owned Hybrid DeleGator (POST /accounts/hybrid) — zero transactions and no gas, so no faucet is needed to get an address. You need native gas tokens only if you later send from the account yourself. Creating or importing a Safe is retired (#1984) and answers HTTP 410; there is no deploy modal.

6. Create an agent

  1. Go to the Agents tab in the dashboard
  2. Click Create Agent
  3. Pick the Haven wallet and network the agent will spend from
  4. Set the agent budget: token, amount, and reset period
  5. Sign the budget delegation with your passkey or wallet — one signature, no transaction for you to pay for
  6. Save the one-time Haven credential when the Done step appears
  7. Use Connect your agent to add Haven to Claude Code, Cursor, VS Code, Codex CLI, OpenCode, Goose, Amp, or another runtime

The credential contains an agent API key and a delegate signing key. Haven stores only the API-key hash/prefix and never stores the delegate private key. If the API key is exposed or lost, use Payment credentials on the agent detail page to rotate it; the new key is shown once and the old key stops working. If the delegate signing key is exposed or lost, a delegation-rail agent is re-keyed, not replaced: the same agent gets a new signing key and a new API key, keeping its id, name, history and the remainder of its budget — see Replacing an agent's signing key. Legacy AllowanceModule agents are historical records only in the dashboard, without payment-credential, pause/resume, re-key, or revoke controls there. Owners manage any remaining Safe permission outside Haven where they have access; replacement agents use the live delegation flow.

Agent Integration

Most users connect an agent through the dashboard's Connect your agent flow. The hosted MCP path sends only the API key to the hosted MCP endpoint; the delegate signing key stays local with the runtime or @haven_ai/signer.

Developers can also integrate directly with @haven_ai/sdk. The SDK wraps direct payments, quote-first x402/MPP flows, and ready-made tool definitions for Claude/OpenAI-style tool calling.

Install

npm install @haven_ai/sdk@alpha

Direct SDK payment

import { HavenClient } from '@haven_ai/sdk'

const haven = new HavenClient({
  apiKey: 'sk_agent_xxx',          // from Haven dashboard
  delegateKey: '0x...',             // delegate signing key held by the agent runtime
  baseUrl: 'http://localhost:3001',
})

const result = await haven.pay({
  token: 'EURe',
  amount: '5.00',
  to: '0xrecipient...',
})

console.log(result.txHash)      // 0x...
console.log(result.explorerUrl) // https://gnosisscan.io/tx/0x... (or basescan.org for Base)

Tool-calling integration

The SDK ships with pre-built tool definitions for Claude and OpenAI:

import { HavenClient, havenTools } from '@haven_ai/sdk'

const haven = new HavenClient({ apiKey, delegateKey })

const tools = havenTools.claude() // or havenTools.openai()

// When your model returns a Haven tool call:
const toolCall = { name: 'get_allowances', input: {} }
const result = await haven.executeTool(toolCall.name, toolCall.input)

if (toolCall.name === 'get_allowances') {
  // Use this for budget, remaining amount, reset-period, or "what can I spend?" questions.
}

See packages/sdk/README.md for the full SDK reference, payment state machine, x402/MPP helpers, and error handling.

MCP paths

Path Package Use when Custody boundary
Hosted MCP @haven_ai/mcp-server + @haven_ai/signer You want one hosted URL across agent runtimes Hosted server receives API identity only; local signer holds the delegate key
Local MCP @haven_ai/mcp You want a local stdio server beside the agent runtime Local process reads the credential file and signs locally
Direct SDK @haven_ai/sdk You are writing custom TypeScript agent code Your runtime holds the delegate key and calls Haven with signed payloads

Testing the Payment Flow

After creating an agent, you can test payments several ways:

Option A: Hosted MCP connection

Use the dashboard's Connect your agent Done step. It creates runtime-specific snippets and one-click deep links where supported. The hosted snippets include the API key only; they do not include the delegate signing key. The local signer or runtime secret store handles signing.

Option B: SDK simulation script

Tests the raw API flow — no AI involved:

# Add to .env:
# AGENT_API_KEY=sk_agent_...    (from step 7 above)
# DELEGATE_PRIVATE_KEY=0x...     (from step 7 above)
# PAYMENT_TO=0x...               (any recipient address)

cd packages/backend
npm run test:payment

This creates a payment intent, signs it with the delegate key, submits it, and confirms on-chain. Output includes the Gnosisscan transaction link.

Option C: Claude agent demo

An optional Claude-powered demo that turns a user task into a Haven tool call:

# Add to .env:
# ANTHROPIC_API_KEY=sk-ant-...   (from console.anthropic.com)
# (plus the same AGENT_API_KEY, DELEGATE_PRIVATE_KEY, PAYMENT_TO as above)

cd packages/backend
npm run agent:demo

Or with a custom task:

npm run agent:demo -- "Pay 0.01 EURe to 0xABC... for API access"

Claude receives the task, calls the make_payment tool when appropriate, Haven validates the signed request and relays it on-chain, and Claude summarizes the result.

What this proves: A real AI agent requested and signed a payment from a user-controlled smart account within strict on-chain guardrails, without holding keys to the account and without understanding blockchain mechanics.

How It Works

Architecture

Agent runtime
  -> SDK / local MCP / hosted MCP
  -> Haven API (identity, policy mirror, construct, relay, status)
  -> Signed delegation + caveat enforcers (on-chain budget enforcement)
  -> Haven wallet / Hybrid DeleGator (user funds)
  1. Agent sends a simple payment intent: { token: "EURe", amount: "5.00", to: "0x..." }
  2. Haven authenticates the API key, loads the Haven wallet, and selects the agent's budget delegation for that token and recipient
  3. Haven prepares a redeeming UserOperation and returns the account's exact EIP-712 typed data to sign
  4. Agent/runtime signs locally with the delegate key; the key never goes to Haven
  5. Haven submits the sponsored UserOperation without changing amount, token, recipient, or authority boundary
  6. The caveat enforcers check budget, recipient and expiry on-chain during gas estimation — anything outside the envelope reverts there. Funds move account→recipient directly, with no funding leg

Payment API (3-step flow)

Step Endpoint What happens
1. Create intent POST /payments Haven validates, selects the budget delegation, returns typed data to sign
2. Sign & submit POST /payments/:id/sign Agent signs the typed data, Haven verifies and submits the sponsored UserOperation
3. Check status GET /payments/:id Poll until confirmed / failed

All endpoints authenticate with Authorization: Bearer sk_agent_xxx. Authentication is not payment authority: executable transfers still require the agent-held delegate signature and the on-chain delegation it redeems against.

An account on the retired Safe rail (execution_rail='allowance_module') gets HTTP 410 from all of these, fail-closed with nothing written (#1986).

For x402 and MPP, the SDK and MCP tools use quote/pay/resume flows. The preferred merchant scheme is ERC-7710 direct settlement — one leg, account→merchant, no funding hop. Where a merchant's facilitator cannot redeem a delegation chain (still most of them today), the EIP-3009 bridge (#946) transiently funds the agent EOA from the budget delegation so it can sign a standard X-PAYMENT header; that path deliberately reintroduces a bounded funding leg, which is why the sweep and delegate-balance monitoring machinery is kept. Production merchant facilitation, acquiring, fiat/card rails, settlement, swaps, yield, and advice are not current Haven production surfaces.

Security Model

Independent layers keep the API and signing boundaries separate:

Layer What it does Where it lives
Hybrid DeleGator smart account User-managed signer set (passkey and/or wallet), holds all funds On-chain
Signed delegation + caveat enforcers Per-agent period budget with native refill, optional recipient pin, expiry — checked at redemption On-chain
Delegate signing key Signs payment payloads within the delegated budget Agent/runtime/user environment
Haven policy mirror Pre-checks, audit trail, status, and copy — a mirror, never the real spend control Haven backend
Credential scoping API-key identity, prefix display, rotation, and revocation state Haven backend

If Haven is compromised, API keys alone cannot sign transactions. For a live delegation agent, an account owner can pause or revoke it in Haven, and can revoke the underlying on-chain authority directly without needing Haven — see the independent exit path. The dashboard exposes no pause or revoke controls for legacy Safe records; any residual Safe permission must be revoked externally by the Safe owner.

Approver (Safe owner) management is retired (#1988/#1989, epic #1440). Haven never signed an owner change and no longer constructs one: the backend routes and the Settings surface are both deleted. Owners of a legacy Safe manage its owner set directly through Safe's own interfaces with their own key — which was always true, and is the reason removing Haven's builder takes nothing away that Haven was the only source of. Haven no longer relays an owner-signed Safe transaction either: #1989 deleted the screen that composed one, and #2847 deleted POST /safe/exec, the route that relayed it for gas. A wallet-owned Safe loses nothing (sign at app.safe.global); a passkey-owned Safe currently has no self-serve way to move funds out, because Haven's passkey Safe signer is a custom WebAuthn scheme Safe's own interfaces cannot drive. The epic's Base-mainnet census found no passkey-owned Safe, which is why this was accepted as a narrowing — but a census is not a proof and it does not cover non-mainnet accounts. Full user-facing wording: docs/product/account-recovery.md.

Key Management

Key Who holds it What it can do
Account signer (passkey / wallet) User device or wallet/hardware environment Full account control: add or remove signers, grant and revoke agent budgets. On a legacy Safe, the equivalent is the Safe owner key
Delegate private key Your agent Sign payment intents within the delegated budget only
Agent API key Your agent Authenticate with Haven API; no signing ability
Hosted MCP bearer token Agent runtime config Same API identity role as the agent API key
Relayer key Haven server Pay gas for independently valid signed transactions; no fund access

Haven never holds account signer keys, Safe owner keys, or delegate private keys.

For architecture constraints around custody, transfer-service risk, relaying, x402/merchant demos, fiat/card rails, swaps, and investment advice, use docs/regulatory/casp-risk-guardrails.md as the required perimeter guardrail.

API Reference

Authentication

Agent endpoints use Bearer token auth:

Authorization: Bearer sk_agent_xxx

Dashboard endpoints use the signed-in user's JWT. The OpenAPI contract is served at /openapi.json.

Endpoints

Surface Auth Examples
Dashboard auth None/JWT /auth/signup, /auth/login, /auth/me
Haven wallets JWT /user/accounts (list/rename/re-default/unlink), balances and account views. The whole /user/safes* prefix and PUT /user/safe answer 410 naming their replacement (#2914) — the Safe vocabulary is retired from the wire. Creating or importing an account is separately retired — POST /user/accounts, /user/accounts/deploy, /safe/deploy and PUT /user/account all answer 410 (#1984). Create a Haven account on the delegation rail with POST /accounts/hybrid.
Agents JWT /agents, /agents/:id, /agents/:id/pause, /agents/:id/resume, /agents/:id/revoke, /agents/:id/rotate-key
Agent payments API key /payments, /payments/:id/sign, /payments/:id, /payments
Agent info API key /machine-payments/agent, /machine-payments/allowances, /machine-payments/receipts, /machine-payments/:id/status, resume-state endpoints
x402 API key or protocol challenge /x402 (the legacy internal mpp_demo flow at /demo/mpp/* and POST /machine-payments/authorize is retired — the latter now refuses with HTTP 410, #1328)
Activity JWT /agent-activity/* for payments, MCP tool calls, and last activity

/agents/:id/allowances and /agents/:id/allowances/:tokenAddress are retired 410 tombstones. Live agent budgets are signed delegation grants, not allowance rows.

Payment intent request

POST /payments
{
  "token": "EURe",
  "amount": "5.00",
  "to": "0xrecipient..."
}

Sign sign_data.typed_data verbatim, never the bare hash. The account validates the typed data, not the 4337 hash; @haven_ai/sdk and the MCP signer do this for you.

There is no over-budget approval queue on the delegation rail. A request the remaining period budget cannot cover is refused 403 delegation_budget_exceeded at a pre-check before anything is built (#3503); one to a recipient outside the grant's pin, or outside its validity window, finds no active delegation and is refused 403; anything that still reaches the chain out of policy reverts during on-chain gas estimation — none becomes a pending approval. There is no 202 on this route: #2055 dropped the approval_requests table, so no row is left to replay, and #2105 removed the response from the published contract. Do not keep a pending_approval branch alive.

Payment intent response

{
  "payment_id": "uuid",
  "status": "pending_signature",
  "expires_at": "2025-01-01T00:10:00Z",
  "sign_data": {
    "hash": "0x...",
    "signature_scheme": "eip712_userop",
    "typed_data": { "domain": {}, "types": {}, "primaryType": "PackedUserOperation", "message": {} },
    "components": {
      "account": "0x...",
      "token": "0x...",
      "to": "0x...",
      "amount": "5000000000000000000"
    },
    "instructions": "Sign sign_data.typed_data with your delegate key using EIP-712 (signTypedData)..."
  }
}

Sign and execute

POST /payments/:id/sign
{
  "signature": "0x...65_byte_signature"
}

Response on success:

{
  "payment_id": "uuid",
  "status": "confirmed",
  "tx_hash": "0x...",
  "chain_id": 100,
  "explorer_url": "https://gnosisscan.io/tx/0x..."
}

Available Scripts

Command What it does
npm run dev Start backend + frontend in dev mode
npm run build Build SDK, connect, MCP packages, signer, CLI, backend, and frontend
npm run test Run workspace tests where configured
npm run typecheck Run workspace type checks
npm run preflight Run the gates CI will run on your diff, derived from the workflow files (#3150). Start here before pushing. preflight:all ignores the diff and runs every gate; preflight:list prints the plan without running it
npm run quality Typecheck and unit tests across workspaces, then the root build chain. Despite the name this is not the pre-push gate list — it covers none of the ratchets (lint:*, check:*, the coupling gates), and the root build omits two packages. Use preflight
npm run docker:up Start PostgreSQL container
npm run docker:down Stop PostgreSQL container
npm run docker:logs Tail PostgreSQL logs

From packages/backend/:

Command What it does
npm run test:payment Run payment simulation script
npm run agent:demo Run Claude agent payment demo

Project Structure

Haven-AI/
|-- ABOUT_HAVEN.md             # Product and architecture mental model
|-- docs/                      # architecture, product/UX, contributing, operations, regulatory docs (see docs/README.md)
|-- packages/
|   |-- backend/               # Fastify API, database migrations, relaying, OpenAPI
|   |-- frontend/              # Next.js dashboard and connect-agent UX
|   |-- sdk/                   # @haven_ai/sdk
|   |-- mcp/                   # Local stdio MCP server; signs locally from a credential file
|   |-- mcp-server/            # Hosted/keyless Streamable HTTP MCP server
|   |-- signer/                # Local edge signer paired with hosted MCP
|   |-- cli/                   # @haven_ai/cli terminal parallel to the dashboard
|   |-- demo-merchant-mcp/     # Internal x402 merchant MCP demo
|   `-- qa-agent/              # Internal QA harness (not published)
|-- .env.example               # Environment variable template
`-- docker-compose.yml         # PostgreSQL for local dev

Supported Networks & Tokens

Gnosis Chain (chainId: 100)

Token Symbol Decimals Address
xDAI xDAI 18 Native
EURe EURe 18 0xcB444e90D8198415266c6a2724b7900fb12FC56E
USDC.e USDC.e 6 0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0

Base (chainId: 8453)

Token Symbol Decimals Address
ETH ETH 18 Native
USDC USDC 6 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913

Tech Stack

  • TypeScript throughout (backend + frontend)
  • Fastify — backend API
  • Next.js 15 — frontend dashboard
  • PostgreSQL — agents, policies, payment intents, receipts, audit trail
  • Safe + AllowanceModule — legacy rail, retired (#1440): reads and owner-signed relay only, no @safe-global/protocol-kit
  • MetaMask smart-accounts-kit + permissionless — Hybrid DeleGator delegation rail
  • wagmi + viem — wallet connection + blockchain interaction
  • ethers v6 — backend blockchain operations
  • Model Context Protocol — local and hosted agent tool connections
  • Tailwind CSS — styling
  • Gnosis Chain + Base — supported EVM networks
  • Anthropic SDK — Claude agent demo

Contributing — Hosted Setup & Dev Workflow

Haven runs in production on Vercel (frontend) and Railway (backend + Postgres). Two long-lived branches deploy: dev auto-deploys to the shared dev environment, and main auto-deploys to production. The five published npm packages (@haven_ai/sdk, signer, mcp, connect, cli) are also published automatically from main on a version bump — see Releasing npm packages. For the dev environment's setup and env vars, see docs/operations/dev-environment.md.

Repository workflow

All changes go through pull requests — no direct pushes to main or dev. Feature work flows feature/* → dev → main; the dev-gate workflow only lets dev or hotfix/* merge into main.

  1. Branch off dev → make your changes
  2. Push the branch and open a PR into dev on GitHub
  3. CI runs automatically (type-check + build per surface: SDK, CLI, backend, frontend, MCP, connect, signer)
  4. Vercel posts a preview URL as a comment on the PR — click to test the frontend live
  5. Once CI is green, the PR can merge into dev and auto-deploys to the dev environment
  6. Promote dev → main with a separate PR (the only normal path into main); merging triggers automatic production deploys to Vercel + Railway (~2 min). Emergency fixes can use a hotfix/* branch straight into main.

Frontend-only changes

The Vercel preview URL points at the production Railway backend. You can test most frontend changes directly against the preview URL — no local setup needed beyond the PR.

Backend changes — test locally first

Vercel previews share the prod backend, so backend changes can't be tested via the PR preview alone. Run the backend locally before opening the PR:

# 1. Start the local Postgres
npm run docker:up

# 2. In one terminal — run the backend on :3001
npm run dev -w packages/backend

# 3. In another terminal — run the frontend on :3000
#    (set NEXT_PUBLIC_API_URL=http://localhost:3001 in your .env)
npm run dev -w packages/frontend

Test the full flow locally, then push and open the PR. Once merged, watch the Railway deploy logs to confirm the change deployed cleanly in prod.

Inspecting prod

Collaborators have Viewer access to the Railway project — you can see services, deploy logs, and runtime logs, but not change env vars. If a deploy fails or behaves unexpectedly:

  • Railway → backend service → Deployments — build logs and runtime logs
  • Railway → Postgres → Data — inspect tables (read-only with Viewer role)
  • Vercel previews — every PR has a preview URL with its own build logs (linked from the PR comment)

If you need an env var changed in Railway or a secret rotated, ping the project owner.

Releasing npm packages

The five npx-installed packages — @haven_ai/sdk, @haven_ai/signer, @haven_ai/mcp, @haven_ai/connect, @haven_ai/cli — are published to npm automatically. You never run npm publish by hand.

# 1. Bump all published packages atomically (versions, cross-package pins,
#    and source version constants) and verify the connect bundle.
npm run release:bump -- <new-version>   # e.g. 0.1.17-alpha.0

# 2. Commit on a release branch and open the PR into `dev` — NOT `main`.
#    `dev-gate` only lets `dev` or `hotfix/*` into `main`, so a release/*
#    branch aimed at `main` fails by design. Get it green, merge to `dev`.

# 3. Promote `dev → main` (a merge commit, never a squash). Publishing fires
#    on THIS step, not on the dev merge — the promotion stays a human step.

On the promotion to main, the Publish packages workflow (.github/workflows/publish.yml) rebuilds dist in dependency order and publishes only the packages whose package.json version is not yet on npm. The dist-tag is derived from the version: a prerelease like 0.1.17-alpha.0 publishes under --tag alpha, a stable 0.2.0 under latest. The connector install command the dashboard hands out names @alpha unless that deployment sets HAVEN_CONNECTOR_CHANNEL (#2422; production leaves it unset), so the prerelease line is what real users get.

  • Trigger model: version bump = the gate. npm rejects republishing an existing version, so a normal (non-bump) commit is a no-op.
  • Two channels, one file (#2421, epic #2420): the paragraph above is the prod channel (main → alpha/latest). The same workflow also fires on a package-touching push to dev and publishes a 0.0.0-dev.<YYYYMMDDHHMM>.<shortsha> snapshot of all five packages under the dev dist-tag — built by the ordinary bump script in a throwaway CI tree, with nothing committed. 0.0.0- sorts below every real version, so no ^0.1.x range resolves to a snapshot by accident. A snapshot can reach neither alpha nor latest, and the main path refuses a 0.0.0-dev.* version; the enforcement points are named in the workflow's header comment. Loop and owner steps: docs/operations/package-dev-channel.md. One file rather than two because npm's trusted-publisher entries are pinned to the workflow filename.
  • Auth for PUBLISHING: npm Trusted Publishing (OIDC) — no long-lived credential. The workflow grants the publish job id-token: write and upgrades npm to ≥ 11.5.1; npm then authenticates the short-lived GitHub Actions OIDC token against a trusted publisher configured per package on npm (pointing at d-hinders/Haven-AI + workflow publish.yml, environment field empty — see the bullet below for why that matters). Nothing to leak or rotate on this path, and it's exempt from the 2FA one-time-password prompt that blocks token-based publishes.
  • Moving latest is a SECOND job with a DIFFERENT credential (#2647): OIDC authorises npm publish and nothing else, so npm dist-tag add cannot ride on it — putting it in the publish job failed every tag move with E401 on the 0.1.35-alpha.0 release, leaving latest a release behind alpha. A separate promote-tags job holds a granular npm token in the main-only npm-production-tags GitHub Environment, and has no id-token: write, so it can move a tag and cannot publish. It has to be a separate job: an environment: is job-level, and adding one to the publish job would break its trusted-publisher entries (registered with an empty environment field). So there IS one long-lived secret now, and it expires — npm caps write-enabled granular tokens at 90 days, this one on 2026-12-06, after which releases publish and latest silently stops moving. Two consequences worth reading: a promotion can be half green (published, latest unmoved), and the durable answer is to stop needing two dist-tags at all.
  • Provenance: Trusted Publishing auto-generates a signed sigstore provenance statement. npm rejects the upload (E422) unless each package.json declares a repository.url (with the monorepo directory) matching the repo — each published package must carry this.
  • Adding a new published package? Before its first release, (1) configure a trusted publisher for it on npm (package → Settings → Trusted Publisher → GitHub Actions: d-hinders / Haven-AI / publish.yml), and (2) give its package.json a repository block. Skipping either makes the first publish fail — on auth (EOTP/OIDC) or provenance (E422) respectively.
  • Failure isolation (#1159): one package's publish failure does not abort the others — every package is attempted, the run summary reports each outcome (published / skipped / failed), and the job fails at the end naming the failures. Before this, a failure early in the list silently skipped everything after it.
  • Not published this way: mcp-server (Docker → Railway), backend, and frontend (Vercel/Railway) deploy from branches directly — main to production and dev to the shared dev environment.
  • Full details, the dist-wipe rationale, and a manual fallback live in scripts/README.md.

License

Private — not open source.

SIMILAR PLUGINS