No description
  • Python 99.2%
  • Shell 0.8%
Find a file
claude c3abc9f5cb feat: per-cousin token paths, identity warning, project auto-discovery
Addresses all 4 issues from Dziadek's (ollama, DeepSeek-v4-pro)
wide-review 2026-05-18:

#1 - README stale vs code: README rewritten to match v0.2.0+ REST API
     architecture. Removed `brew install infisical` as a hard
     dependency (only needed for the user-login fallback path), removed
     `subprocess.run` narrative from § Security (single subprocess call
     left is the keychain lookup, documented as such), removed the
     phantom `infisical-mcp-discover-project-id` reference.

#2 - per-cousin machine identity token path: new INFISICAL_MCP_COUSIN
     env var (default `claude`) selects which `<cousin>-client-secret`
     file under ~/.platformctl-runtime/infisical/ to read. Backward-
     compatible: when COUSIN=claude, the keychain account default
     stays `p@durlej.me` to match v0.2.0; for any other cousin it
     defaults to `p+<cousin>@durlej.me` per the operator-cousin email
     pattern in agent-souls' canonical runbook. Each cousin's MCP
     registration sets its own INFISICAL_MCP_COUSIN, so once per-cousin
     client-secret files exist the audit log distinguishes them
     automatically (the seam for follow-up #1 in
     infisical-machine-identity.md § Open follow-ups).

#3 - project_id auto-discovery: `_resolve_project` now falls through to
     a `list_projects()` call (30s cached) when neither the tool arg
     nor INFISICAL_MCP_DEFAULT_PROJECT_ID is set. If exactly one
     workspace is visible, it's used automatically; any other count
     surfaces a helpful error pointing the agent at `list_projects`.
     `list_projects` itself now returns a `hint` field guiding the
     operator to set the env var when default is missing, with the
     exact UUID baked in for one-workspace setups.

#4 - keychain user-login fallback warning: every tool response that
     used the user-login path now carries an `auth_warning` field
     naming both the operator account being audited as the actor AND
     the cousin identity that should have been used, plus the
     bootstrap path that would switch to machine identity. Models
     reading the response see this in-band so they can't accidentally
     assume audit isolation when none is in effect.

Other:
- Bumped version 0.2.0 -> 0.3.0
- Added COUSIN and INFISICAL_ACCOUNT to public __all__
- New env var INFISICAL_MCP_COUSIN documented in README + roadmap §
  per-cousin Universal Auth updated to reflect that the seam now
  exists.

End-to-end verified (all three modes against home-platform workspace):
- machine_identity (cousin=claude, default): no warning, no hint
- auto-discovery (no DEFAULT_PROJECT_ID): list_projects emits the
  exact env-var-set hint; get_secret resolves the single workspace
  automatically and returns the right value
- user_login fallback (forced via INFISICAL_MCP_RUNTIME_TOKEN=/nonexistent
  + INFISICAL_MCP_COUSIN=ollama): auth=user_login with explicit
  auth_warning naming "operator identity (p@durlej.me) ... NOT cousin
  identity (ollama)"

Closes #1, #2, #3, #4.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 09:12:36 +02:00
.gitignore feat: initial infisical-mcp - read-only secrets access via CLI subprocess 2026-05-18 08:15:08 +02:00
infisical-mcp feat: initial infisical-mcp - read-only secrets access via CLI subprocess 2026-05-18 08:15:08 +02:00
infisical_mcp.py feat: per-cousin token paths, identity warning, project auto-discovery 2026-05-18 09:12:36 +02:00
LICENSE feat: initial infisical-mcp - read-only secrets access via CLI subprocess 2026-05-18 08:15:08 +02:00
pyproject.toml feat: per-cousin token paths, identity warning, project auto-discovery 2026-05-18 09:12:36 +02:00
README.md feat: per-cousin token paths, identity warning, project auto-discovery 2026-05-18 09:12:36 +02:00

infisical-mcp

Read-only MCP wrapper around Infisical's REST API so agents can pull secrets from a self-hosted Infisical instance instead of asking the operator for the same API keys every session.

What it gives you

Three read-only tools, defaulted to safe behavior:

Tool What it returns
list_projects() Workspaces (projects) the current credentials can see. Useful for one-time INFISICAL_MCP_DEFAULT_PROJECT_ID discovery.
list_secrets(project_id, env, path, recursive, mask_values=True) Secret names in a folder. Values are masked (***(42 chars)) by default — set mask_values=False only if you need to compare values en masse.
get_secret(name, project_id, env, path) One secret's value. The only tool that returns plaintext — call it once you know which key you want.

No write tools (set_secret / delete_secret) are exposed. The risk profile of letting a long-running agent rotate or wipe production secrets is too high for the convenience it would buy; if you need it, write it explicitly in a separate utility, not through an MCP tool surface.

Every tool response includes an auth field with value "machine_identity" or "user_login" naming which auth method was used, so the model and operator can see at a glance whether the canonical path is engaged or the workstation fallback kicked in.

Auth model (v0.2.0+)

Two methods with explicit precedence. The MCP calls Infisical's REST API directly with urllib.request — there is no infisical CLI subprocess anywhere on the hot path. The CLI is only relevant if you want to bootstrap the user-login fallback (infisical login).

1. Machine identity Token Auth (preferred, canonical)

Reads the access token from ~/.platformctl-runtime/infisical/<cousin>-client-secret (default cousin: claude; override via INFISICAL_MCP_COUSIN env var or the full path via INFISICAL_MCP_RUNTIME_TOKEN) and uses it as a direct Bearer per the runbook in pdurlej/agent-souls:practices/infisical-machine-identity.md.

Audit log at Infisical shows the cousin's machine identity (currently shared "Instance Admin Identity" in 0.1 MVP; per-cousin Universal Auth is the documented follow-up). This is the right path for any identity-isolation-sensitive call.

Bootstrap the file once per workstation per cousin via the runbook above (operator action, mode 0600). The MCP picks it up automatically on the next call.

2. User-login JWT (fallback, identity-leaky)

Reads the JTWToken out of the macOS Keychain entry that infisical login maintains. Audit log shows the operator's email (INFISICAL_MCP_ACCOUNT, default p@durlej.me), NOT the cousin. This is honest for an interactive operator session but explicitly forbidden by Rule 2 of the runbook for runtime agent work — so the MCP only falls back here when the machine identity file is missing, and attaches a warning field to the response so the model can see "you are reading as the operator, not as the cousin".

To refresh the user JWT when it expires:

infisical login --domain=https://infisical.pdurlej.com/api

When neither is available

Every tool returns a structured error pointing at both bootstrap paths:

{
  "error": "no infisical credentials available",
  "hint": "Either run `infisical login ...` to refresh the user JWT, OR bootstrap the machine identity client secret file per pdurlej/agent-souls:practices/infisical-machine-identity.md.",
  "checked": {
    "machine_identity_path": "/Users/.../claude-client-secret",
    "machine_identity_exists": false,
    "cousin": "claude",
    "keyring_account": "p@durlej.me"
  }
}

The server never prompts interactively — that would hang stdio.

Token reads are not cached in process

Tokens are read from disk / Keychain on every API call. Rotations are picked up automatically, no MCP restart needed. (Result data — project list, secret list — IS cached for 30 seconds to avoid hammering Infisical, but credentials are not.)

Install

Requires:

  • Network access to a self-hosted Infisical instance (the REST API at INFISICAL_MCP_DOMAIN, default https://infisical.pdurlej.com/api).
  • Python 3.11+ with the mcp package on the import path.
  • macOS keychain access — only used if the user-login fallback path is active. If you only use the machine identity path, the infisical CLI is not required at all.
git clone https://git.pdurlej.com/pdurlej/infisical-mcp.git ~/.local/share/infisical-mcp
ln -sf ~/.local/share/infisical-mcp/infisical-mcp ~/.local/bin/infisical-mcp
chmod +x ~/.local/bin/infisical-mcp

Configure

Set at minimum the default project (one-time):

# Discover the project ID with the MCP itself (after registration):
#   call list_projects() once; the response lists your workspaces.
# Or grep the Infisical CLI's backup directory if you have it:
ls ~/.infisical/secrets-backup/ 2>/dev/null | head -1
# project ID is the UUID embedded in the backup file names, between
# "project_secrets_" and the next underscore.

export INFISICAL_MCP_DEFAULT_PROJECT_ID=<your-project-uuid>

Env vars (all optional):

Var Default Purpose
INFISICAL_MCP_DOMAIN https://infisical.pdurlej.com/api Self-hosted instance URL
INFISICAL_MCP_DEFAULT_PROJECT_ID (empty) Project UUID used when tool callers don't pass project_id
INFISICAL_MCP_DEFAULT_ENV prod Environment slug
INFISICAL_MCP_COUSIN claude Cousin identity — selects which <cousin>-client-secret file is read
INFISICAL_MCP_RUNTIME_TOKEN ~/.platformctl-runtime/infisical/<cousin>-client-secret Full override of the machine identity token path
INFISICAL_MCP_ACCOUNT p+<cousin>@durlej.me (or p@durlej.me for cousin=claude to match v0.2.0) Keychain account for user-login fallback

Register

Claude Code (user scope)

claude mcp add -s user infisical \
  -e INFISICAL_MCP_DEFAULT_PROJECT_ID=<your-project-uuid> \
  -e INFISICAL_MCP_COUSIN=claude \
  -- ~/.local/bin/infisical-mcp

claude mcp list | grep infisical   # → ✓ Connected

Codex CLI

codex mcp add infisical \
  --env INFISICAL_MCP_DEFAULT_PROJECT_ID=<your-project-uuid> \
  --env INFISICAL_MCP_COUSIN=codex \
  ~/.local/bin/infisical-mcp

Each cousin's MCP registration sets its own INFISICAL_MCP_COUSIN, so the audit log at Infisical distinguishes them once per-cousin client secret files are bootstrapped. Until then they all share the same claude-client-secret (the runbook's 0.1 MVP), and INFISICAL_MCP_COUSIN just controls the keychain-fallback email.

Usage from the model side

Typical flow when the agent needs a secret it doesn't have in context:

1. list_secrets(path="/home-platform/providers", mask_values=True)
   → sees ANTHROPIC_API_KEY, OPENAI_API_KEY, ... (names only)
2. get_secret(name="ANTHROPIC_API_KEY", path="/home-platform/providers")
   → gets the actual value
3. uses it; doesn't re-ask the operator next session.

Models can rely on INFISICAL_MCP_DEFAULT_PROJECT_ID for the project and INFISICAL_MCP_DEFAULT_ENV (typically "prod") for the environment, so typical calls collapse to:

get_secret(name="ANTHROPIC_API_KEY", path="/home-platform/providers")

Security

  • Read-only tool surface. No mutation tools exist.
  • list_secrets masks values by default; the model's transcript doesn't accumulate plaintext values from listing operations.
  • Tokens (machine identity file OR keychain JWT) are read fresh on every API call — never cached in process memory, never written anywhere. Token rotation by re-bootstrap or infisical login is picked up immediately.
  • API calls go through urllib.request with parameterized URLs (urlencoded). No shell=True, no string interpolation of user-controlled input into URLs. The only subprocess call is to security find-generic-password for the keychain fallback, with an explicit argv list.
  • Project ID is validated as a non-empty string before being passed to the REST API.
  • 30-second in-memory cache on list_projects and list_secrets results — short enough that secret rotations propagate quickly, long enough to avoid hammering Infisical when an agent loops.
  • When the user-login fallback is active, the response includes an explicit warning field so the model knows it is reading as the operator, not as the cousin. The auth field always names the path taken.

Roadmap

  • Per-cousin Universal Auth machine identities — tracked upstream in pdurlej/agent-souls:practices/infisical-machine-identity.md § Open follow-ups § 1. The INFISICAL_MCP_COUSIN env var is already the seam — once per-cousin client-secret files exist, set the env var per cousin's MCP registration and the audit log will distinguish claude from codex from ollama automatically.
  • Universal-auth refresh — use the RefreshToken from Keychain to auto-refresh the user JWT in-process when machine identity is unavailable, so a brief expiry doesn't require manual infisical login. Currently we just surface the hint.
  • Path completion — surface the folder tree via list_folders() so agents can discover the structure without trial-and-error on path.
  • Multi-environment searchfind_secret(name) that searches across dev/staging/prod and returns where the key lives.

License

MIT. See LICENSE.