Skip to content

Repository files navigation

iVALT — Human in the Loop

@ivalt/agent-auth

Human-in-the-loop authentication for AI agents

Biometric approval with PKI-signed attestation — via Model Context Protocol (MCP).

npm version license node

Website · Report issue

When an agent hits a sensitive step, this MCP server pauses the workflow, sends an iVALT push to the approver's phone, and returns a PKI-signed attestation after biometric approval (FaceID / fingerprint, plus optional location, time, and device checks).

Agent step → request_approval → iVALT push → human approves → PKI attestation → agent continues

Two ways to use iVALT

This one repo is the single source of truth for every distribution surface. Pick the path that matches what you're doing:

Use case 1 — Install the plugin Use case 2 — Use the MCP in your agent workflow
Who A developer working inside Cursor or Claude An agent / app calling iVALT programmatically
What ships MCP server + iv-auth skill, one click MCP server only
How Cursor plugin / deeplink · Claude .mcpb / Claude Code plugin npx @ivalt/agent-auth over stdio, or hosted HTTP
Guide install/cursor.md · install/claude.md see below

The skill matters: the MCP server gives the capability, the iv-auth skill gives the agent the instructions to use it correctly (async flow, gate on approved). Use case 1 ships both together; use case 2 is the raw server.

Use case 1 — Install the plugin (Cursor / Claude)

Host Method Link
Cursor One-click deeplink, mcp.json, or plugin (MCP + skill) install/cursor.md
Claude Desktop .mcpb Desktop Extension (one-click) install/claude.md
Claude Code /plugin marketplace add iVALT-Inc/agent-auth (MCP + skill) install/claude.md

The packaging lives alongside the server: .cursor-plugin/plugin.json (Cursor), .claude-plugin/ (Claude Code), manifest.json (Claude Desktop .mcpb).

Use case 2 — Use the MCP in your agent workflow

No clone or build — run directly from npm:

npx -y @ivalt/agent-auth

Any MCP client (mcp.json / claude_desktop_config.json)

{
  "mcpServers": {
    "ivalt": {
      "command": "npx",
      "args": ["-y", "@ivalt/agent-auth"],
      "env": {
        "IVALT_API_KEY": "your-key",
        "IVALT_DEFAULT_MOBILE": "+1..."
      }
    }
  }
}

Python / LangGraph (langchain-mcp-adapters)

from langchain_mcp_adapters.client import MultiServerMCPClient

client = MultiServerMCPClient({
    "ivalt": {
        "command": "npx",
        "args": ["-y", "@ivalt/agent-auth"],
        "env": {"IVALT_API_KEY": "your-key", "IVALT_DEFAULT_MOBILE": "+1..."},
        "transport": "stdio",
    }
})
tools = await client.get_tools()   # request_approval, request_approval_async, check_status

Requirements

  • Node.js 18+
  • iVALT credential (optional) — without IVALT_API_KEY, the server uses iVALT's public demo proxy (ondemandid.com); set a key to call api.ivalt.com directly
  • Approver phone — set IVALT_DEFAULT_MOBILE (E.164) or pass approver_mobile on each tool call

Transports

  • stdio — launched locally via npx (Cursor, Claude Desktop, LangGraph)
  • HTTP (Streamable-HTTP) — hostable endpoint for multi-tenant / remote use (Docker)

The agent never sees the iVALT key — it lives only in this server's env.

Tools

Tool Behavior
request_approval Blocking. Sends the request and waits up to timeout_s (default 90, max 120) for approval. Returns the attestation.
request_approval_async Returns a request_id immediately. For long-running cloud agents.
check_status Polls a request_idpending / approved (with attestation) / denied / expired.

Shared inputs: action (required), reason, approver_mobile (E.164, overrides the default — this is how one endpoint serves many tenants), factors (biometric | location | time | device).

Approved responses return a normalized attestation:

{
  "status": "approved",
  "attestation": {
    "approved": true,
    "request_id": "a518d10b-...",
    "approver": { "mobile": "+1...", "name": "Jane Doe", "email": "jane@example.com" },
    "approver_mobile": "+1...",
    "request_from": "iVALT MCP: <action>",
    "factors_verified": { "biometric": true, "location": true, "time": false, "device": true },
    "location": { "latitude": "32.84...", "longitude": "-79.86...", "address": "..." },
    "signed_at": "2026-06-26T...Z",
    "pki": { "public_key": "MIICIjANBgkq...", "signature": null, "key_ref": "a518d10b-..." },
    "raw": { }
  }
}

Notes on the live shape: iVALT returns the device's registered public key in raw.imei (mapped to pki.public_key), the iVALT request UUID in raw.request_id, and geo data when the device shares it. This endpoint does not return a separate detached signature, so pki.signature is typically null while pki.public_key carries the PKI material. The complete upstream payload is always preserved under raw.

Configuration

All config is via environment variables (see .env.example).

Var Default Purpose
IVALT_API_KEY / IVALT_CONNECTION_ID (none) iVALT credential; presence switches upstream to direct.
IVALT_UPSTREAM auto Force direct or proxy.
IVALT_DEFAULT_MOBILE (none) Default approver phone (E.164). If unset, callers must pass approver_mobile per request.
IVALT_REQUEST_FROM iVALT MCP Label on the approval request.
IVALT_DEFAULT_FACTORS (none) Comma list of default factors.
PORT 8080 HTTP transport port.
MCP_AUTH_TOKEN (none) Bearer token required on POST /mcp.

Hosted HTTP transport

To connect to a hosted server instead of running locally (no Node needed on the client):

client = MultiServerMCPClient({
    "ivalt": {
        "url": "https://your-host/mcp",
        "transport": "streamable_http",
        "headers": {"Authorization": "Bearer <MCP_AUTH_TOKEN>"},
    }
})

Distribution & release

One source, one version, many shelves. The version in package.json is the single source of truth; npm run sync:version stamps it into every manifest so they never drift.

.
├── package.json                    # npm: @ivalt/agent-auth (the MCP server)
├── skills/iv-auth/SKILL.md         # the skill, shipped with the plugins (and npm)
├── manifest.json                   # Claude Desktop .mcpb (built into dist/)
├── .cursor-plugin/plugin.json      # Cursor plugin (MCP + skill)
├── .claude-plugin/                 # Claude Code plugin + marketplace (MCP + skill)
├── install/                        # one-click install docs (deeplink, .mcpb, /plugin)
└── .github/workflows/release.yml   # tag vX.Y.Z → npm publish + attach .mcpb

Cut a release:

npm version patch          # bump package.json (the source of truth)
npm run sync:version       # propagate to all manifests, commit
git push --follow-tags     # the tag triggers .github/workflows/release.yml

The workflow publishes to npm and attaches dist/ivalt-agent-auth.mcpb to the GitHub Release. Registry listings (official MCP registry, Smithery, Glama, etc.) and the Cursor/Claude marketplaces point back at this repo + npm. Requires an NPM_TOKEN repo secret.

Build the .mcpb locally:

npm run build:mcpb         # → dist/ivalt-agent-auth.mcpb

Development

git clone https://github.com/iVALT-Inc/agent-auth.git
cd agent-auth
npm install

# stdio (what Cursor/Claude/LangGraph spawn)
npm run start:stdio

# HTTP transport
MCP_AUTH_TOKEN=secret PORT=8080 npm run start:http
curl http://localhost:8080/health

Quick wiring check:

node test/smoke.js stdio
node test/smoke.js http http://localhost:8080/mcp secret
# add SMOKE_FIRE=1 to also fire a real push to IVALT_DEFAULT_MOBILE

Docker (HTTP transport)

docker build -t ivalt-mcp .
docker run -p 8080:8080 \
  -e MCP_AUTH_TOKEN=secret \
  -e IVALT_API_KEY=your-key \
  -e IVALT_DEFAULT_MOBILE=+1... \
  ivalt-mcp
curl http://localhost:8080/health

The multi-tenant router connects to POST /mcp with the bearer token and passes a per-tenant approver_mobile in each tool call.

Notes

  • request_id encodes the approver mobile + issue time; iVALT's result endpoint keys on the mobile, so avoid concurrent in-flight requests for the same phone.
  • Approval window is ~60s; blocking request_approval polls every 2s.
  • HTTP runs in stateless mode (GET/DELETE /mcp return 405).

License

MIT

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages