diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index 0e3e1b2..b940075 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -1,11 +1,12 @@ { "name": "1password", - "version": "1.1.0", - "description": "1Password plugin for Cursor — securely manage development secrets.", + "displayName": "1Password", + "version": "1.2.0", + "description": "1Password Developer Environments for Cursor: MCP tools to create, import, and manage project secrets; an agent skill with the full import-and-mount workflow; and a hook that validates local .env mounts before shell commands run. Requires the 1Password desktop app on macOS or Linux with Labs MCP Server enabled. Not supported on Windows.", "author": { "name": "1Password" }, - "homepage": "https://1password.com", + "homepage": "https://www.1password.dev/", "repository": "https://github.com/1Password/cursor-plugin", "license": "MIT", "keywords": [ @@ -16,8 +17,13 @@ "env", "dotenv", "hooks", - "validation" + "validation", + "mcp", + "developer-environments", + "linux" ], "logo": "assets/logo.svg", - "hooks": "hooks/hooks.json" + "hooks": "./hooks/hooks.json", + "skills": "./skills/", + "mcpServers": "./mcp.json" } diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..95ed20e --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +.DS_Store + +# Environment files (keep templates committable) +.env +.env.* +!.env.example +!.env.sample +!.env.template +!.env.dist + +# Python +__pycache__/ +*.py[cod] + +# Local dev hook wiring (use when team policy blocks local plugin imports) +.cursor/hooks.json diff --git a/README.md b/README.md index ffb5555..1ae2a36 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,23 @@ # 1Password Plugin for Cursor -The official [1Password](https://1password.com) plugin for [Cursor](https://cursor.com). It brings 1Password's secret management capabilities directly into your editor, helping you develop securely without leaving your workflow. +The official [1Password](https://1password.com) plugin for [Cursor](https://cursor.com). It ships three pieces that work together: **hooks** that validate locally mounted `.env` files, an **agent skill** with the complete Developer Environment workflow, and **MCP configuration** for the 1Password desktop app server. Secret values stay in 1Password — the agent sees variable names and mount paths, not secret contents. + +Install the **plugin** (not a hand-configured MCP entry alone). The bundled `1password-environments` skill is the authoritative agent workflow; the MCP server's built-in documentation resources cover tool basics only and omit import-and-mount steps. For more on 1Password's developer tools, see the [1Password Developer Documentation](https://developer.1password.com). ## Requirements - [1Password](https://1password.com) subscription -- [1Password for Mac or Linux](https://1password.com/downloads) +- [1Password desktop app](https://1password.com/downloads) on **macOS or Linux** - [Cursor](https://cursor.com) -- [sqlite3](https://www.sqlite.org/) installed and available in your `PATH` (pre-installed on macOS; install via your package manager on Linux) -> **Note:** 1Password Environments local `.env` mounts only apply on **macOS and Linux**. **`hooks.json`** invokes **`./scripts/validate-mounted-env-files`** with no extension. On **macOS / Linux**, that runs the **Bash** script. On **Windows** the shell looks for a real file by trying suffixes from **`PATHEXT`** until one matches on disk. That yields **`validate-mounted-env-files.cmd`**, which returns **`allow`** and skips validation so agent shells are not blocked. +Additional requirements by feature: + +- **Hooks** — [sqlite3](https://www.sqlite.org/) installed and available in your `PATH` (pre-installed on macOS; install via your package manager on Linux) +- **MCP** — 1Password Labs **MCP Server** experiment enabled in the desktop app (`onepassword://settings/labs`). If the setting is missing, your account may not have the `ai-local-mcp-server` feature flag. The plugin's `mcp.json` launches the `1password-mcp` command from your `PATH`, as described in the [1Password MCP server documentation](https://www.1password.dev/environments/mcp-server). + +> **Platform support:** MCP, local `.env` mounts, and mount validation are supported on **macOS and Linux**. On **Windows**, the validation hook returns `allow` and skips checks so agent shells are not blocked; MCP and local mounts are not available. **`hooks.json`** invokes **`./scripts/validate-mounted-env-files`** with no extension. On macOS and Linux, that runs the Bash script. On Windows, the shell resolves **`validate-mounted-env-files.cmd`** via `PATHEXT`. ## Installation and Setup @@ -24,7 +30,7 @@ Before using this plugin, you'll need to configure your secrets in 1Password: ### Step 2: Install the plugin -Install from the [Cursor Marketplace](https://cursor.com/marketplace): +Install from the [Cursor Marketplace](https://cursor.com/marketplace). This registers hooks, the `1password-environments` agent skill, and `mcp.json` together: 1. Open **Cursor Settings** > **Plugins**. 2. Search for **1password**. @@ -32,6 +38,30 @@ Install from the [Cursor Marketplace](https://cursor.com/marketplace): Or use the command palette: `Ctrl+Shift+P` (or `Cmd+Shift+P` on macOS) > **Plugins: Install Plugin** > search for `1password`. +Or install directly: + +``` +/add-plugin 1password +``` + +### Step 3: Enable MCP in 1Password (required for Environment management) + +Enable the **MCP Server** experiment in the 1Password desktop app: open **Settings → Labs** (or use `onepassword://settings/labs`) and turn on **MCP Server**. The plugin's `mcp.json` connects Cursor to that server after this step. + +The plugin registers the same MCP command 1Password documents for other clients: + +```json +{ + "mcpServers": { + "1password": { + "command": "1password-mcp" + } + } +} +``` + +Install the 1Password desktop app on macOS or Linux so `1password-mcp` is available on your `PATH`. For platform-specific install paths and troubleshooting, see the [1Password MCP server documentation](https://www.1password.dev/environments/mcp-server). + ## Features ### Hooks @@ -40,7 +70,7 @@ Or use the command palette: `Ctrl+Shift+P` (or `Cmd+Shift+P` on macOS) > **Plugi Validates locally mounted `.env` files from [1Password Environments](https://developer.1password.com/docs/environments) before any shell command executes. When required environment files are missing, disabled, or misconfigured, the hook blocks execution and surfaces actionable error messages so the Cursor Agent can guide you to a fix. -This hook was originally developed in the [1Password Cursor Hooks](https://github.com/1Password/cursor-hooks) repository. For the full setup guide, see [Validate local `.env` files with Cursor Agent](https://developer.1password.com/docs/environments/cursor-hook-validate/). +This hook was originally developed in the [1Password Agent Hooks](https://github.com/1Password/agent-hooks) repository. For the full setup guide, see [Validate local `.env` files with Cursor Agent](https://developer.1password.com/docs/environments/cursor-hook-validate/). **How it works:** @@ -122,26 +152,65 @@ DEBUG=1 echo '{"command": "echo test", "workspace_roots": ["/path/to/your/projec When not running in debug mode, the hook writes logs to `/tmp/1password-cursor-hooks.log`. Log entries include timestamps and details about 1Password queries, validation results, and permission decisions. +### MCP and agent skill + +The plugin connects Cursor to the local 1Password MCP server and bundles the **`1password-environments`** skill (`skills/1password-environments/SKILL.md`). Agents should read that skill before calling MCP tools — it defines the complete workflow for importing a plain `.env` file, appending variables, and mounting at the source path. The MCP server's built-in docs cover tool basics but omit those import-and-mount steps. + +See `skills/1password-environments/reference.md` for setup, mount conflicts, and shell validation details. + +#### Example prompts + +- "List my 1Password Environments" +- "Mount my staging Environment as `.env` in this repo" +- "What variables are in my production Environment?" +- "Create a new Environment called `my-app-dev`" +- "Create an Environment from my project `.env` file" +- "Import `.env` into 1Password and mount it here" +- "Add a placeholder for my OpenAI API key" + +#### MCP tools + +| Tool | Description | +|------|-------------| +| `authenticate` | Authenticate with the 1Password desktop app; returns `accountId` | +| `list_environments` | List Developer Environments for an account | +| `create_environment` | Create a new Developer Environment | +| `rename_environment` | Rename an existing Developer Environment | +| `list_variables` | List variable names in an Environment (no values) | +| `append_variables` | Add or update Environment variables | +| `create_local_env_file` | Mount an Environment as a local `.env` file | +| `list_local_env_files` | List existing local `.env` mounts for an Environment | + +Confirm the MCP server is connected in **Cursor Settings → MCP** after installing the plugin and enabling the Labs experiment in 1Password. + ## Plugin Structure ``` -1password/ +cursor-plugin/ ├── .cursor-plugin/ │ └── plugin.json # Plugin manifest ├── hooks/ -│ └── hooks.json # Hook event configuration +│ └── hooks.json # beforeShellExecution mount validation +├── skills/ +│ └── 1password-environments/ +│ ├── SKILL.md # Agent skill for MCP workflows +│ └── reference.md # Setup, mount conflict, and troubleshooting +├── mcp.json # MCP server configuration ├── assets/ │ └── logo.svg # Plugin logo ├── scripts/ +│ ├── lib/ +│ │ └── telemetry.sh # Opt-in telemetry helpers for the validation hook │ ├── validate-mounted-env-files # Bash hook (macOS / Linux) │ └── validate-mounted-env-files.cmd # Windows cmd wrapper returns allow (validation skipped) ├── LICENSE └── README.md ``` + ## Telemetry -The plugin emits **opt-in** telemetry so 1Password can understand plugin adoption and the prevalence of common failure modes (missing files, disabled mounts). Two event types are emitted: +The validation hook emits **opt-in** telemetry so 1Password can understand plugin adoption and the prevalence of common failure modes (missing files, disabled mounts). Two event types are emitted: - `agent_hook_execution` — fired once per hook invocation; carries the hook name, plugin version, client (`cursor`), bucketed duration, decision (`allow`/`deny`), reason for deny, validation mode (`default`/`configured`), and a count of mounts checked. - `agent_hook_install` — fired once per `(hook_name, plugin_version)` on the first hook run after installation or upgrade; `install_method` is `plugin_marketplace`. @@ -159,6 +228,7 @@ The plugin emits **opt-in** telemetry so 1Password can understand plugin adoptio ## Resources - [Validate local `.env` files with Cursor Agent](https://developer.1password.com/docs/environments/cursor-hook-validate/) — full setup guide on the 1Password Developer site +- [1Password MCP server documentation](https://www.1password.dev/environments/mcp-server) - [1Password Agent Hooks](https://github.com/1Password/agent-hooks) — the original hooks repository this plugin is based on - [1Password Environments](https://developer.1password.com/docs/environments) — documentation for 1Password's environment and secrets management - [1Password Local `.env` Files](https://developer.1password.com/docs/environments/local-env-file) — how local `.env` file mounting works diff --git a/mcp.json b/mcp.json new file mode 100644 index 0000000..b271b81 --- /dev/null +++ b/mcp.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "1password": { + "command": "1password-mcp", + "args": [] + } + } +} diff --git a/skills/1password-environments/SKILL.md b/skills/1password-environments/SKILL.md new file mode 100644 index 0000000..b367dae --- /dev/null +++ b/skills/1password-environments/SKILL.md @@ -0,0 +1,145 @@ +--- +name: 1password-environments +description: >- + Manage 1Password Developer Environments via the bundled MCP server. Use when + creating, importing, or mounting .env files; listing Environment variable names; + adding or updating Environment variables; renaming environments; or calling any + 1Password MCP tool. Import-from-.env always includes create_local_env_file at + the source .env path unless the user explicitly opts out of mounting. +--- + +# 1Password Environments + +Workflow for the 1Password MCP tools bundled with this plugin. Read this skill +before calling MCP tools — the MCP server's built-in docs cover tool basics but +omit import-and-mount steps. Conceptual background and edge cases: +[reference.md](reference.md). + +## Prerequisites + +- **macOS or Linux** with the 1Password desktop app installed (MCP, local `.env` mounts, and mount validation are not supported on Windows) +- **MCP Server** Labs experiment enabled in the desktop app (`onepassword://settings/labs`) +- Plugin installed (registers `1password-mcp` MCP config, this skill, and mount validation hooks together) + +Setup details: [reference.md](reference.md) + +## Not done until + +**Import / create from `.env`** (including "using values from the project `.env`"): + +- [ ] Environment created or resolved +- [ ] Variables appended via `append_variables` +- [ ] `create_local_env_file` at the **source** `.env` absolute path — **always** +- [ ] Mount verified with `list_local_env_files` + +Mounting at the source `.env` path is mandatory, not optional follow-up. The +**only** exception is the user explicitly opting out ("without mounting", "do not +mount", "skip the mount"). Never ask "want me to mount?" — just mount. + +Stopping after `create_environment` + `append_variables` is **incomplete**. +`list_variables` is not mount verification. Do not report success until the mount +checklist is done. + +**Mount only:** mount exists at the requested path (`list_local_env_files`). + +## Do not + +- Skip `create_local_env_file` on import unless the user explicitly opted out +- Report success before the import checklist (including mount) is complete +- Offer mounting as optional follow-up — it is mandatory on import +- Call `create_environment` when `list_environments` already shows that name — ask the user first (see **Duplicate environment name**) +- Reveal secret values in chat +- Read a mounted `.env` path — once mounted, the path is a live FIFO (named pipe); use `list_variables` instead + +## MCP tools + +Discover tool schemas for `plugin-1password-1password` before invoking tools +(`GetMcpTools`, MCP tool descriptors, or equivalent). + +Request params use **camelCase** (`accountId`, `environmentId`); responses may use +snake_case (`account_id`, `environment_id`). Pass every required param. + +Non-obvious schema details: + +- `create_local_env_file`: `mountPath` must be the **absolute** path of the source `.env` file (macOS/Linux) +- Environment-level tools may prompt the user for per-environment approval on first use + +If MCP calls fail with authentication errors, call `authenticate`, then retry with +the returned account ID (use as `accountId` in subsequent calls). + +## Resolve environment + +After `authenticate` → `list_environments`: + +1. Match `environmentName` exactly (case-sensitive) to get `environmentId` +2. If no match, ask the user for the correct name — do not guess +3. If multiple accounts/environments confuse the match, list names only and ask + +## Duplicate environment name + +Before **`create_environment`**, call **`list_environments`** and check whether the +target `environmentName` already exists. + +If it does, **stop and ask the user** how they want to proceed. Offer options such as: + +- **Use the existing environment** — skip `create_environment`; use its `environmentId` for later steps (`append_variables`, mount, etc.) +- **Use a different name** — wait for a new name from the user, then `create_environment` +- **Cancel** — do not create or modify anything + +Do not silently choose one of these paths. Do not call `create_environment` with a +name that already exists unless the user has explicitly chosen a different name. + +## Import from a `.env` file + +Default path: `{workspace_root}/.env` unless the user names another path. + +1. **Read** the `.env` file with the Read tool to get its keys and values. Strip optional surrounding quotes from values. Pass values to MCP only — never paste secret values into chat. +2. **`authenticate`** → `accountId` +3. **`list_environments`** — if the target name already exists, follow **Duplicate environment name** and wait for the user's choice. Otherwise **`create_environment`** (new name) or resolve the existing environment per the user's choice. +4. **`append_variables`** with all variables (see **Concealed variables**) +5. **Mount** — **always** (skip only if the user explicitly said not to mount): + - If the `.env` is **git-tracked**, stop and tell the user to delete it and commit that removal before mounting ([local .env file docs](https://www.1password.dev/environments/local-env-file.md)) + - `list_local_env_files` — skip `create_local_env_file` only if a mount already exists at the source path + - `create_local_env_file` with `accountId`, `environmentId`, `environmentName`, `mountPath` (absolute path of the original `.env`) + - `list_local_env_files` again to verify + +If shell commands are blocked because 1Password expects a mount at the path, see +[reference.md](reference.md) (mount conflict). + +## Other flows + +**Create new Environment:** authenticate → `list_environments` → if the name exists, follow **Duplicate environment name** → `create_environment` only when the name is available or the user chose a different name. + +**Mount existing Environment:** authenticate → resolve environment → step 5 above. + +**Inspect names:** authenticate → resolve environment → `list_variables` → summarize names only. + +**Rename:** authenticate → resolve environment → confirm name → `rename_environment`. + +**Add/update variables:** authenticate → resolve environment → `list_variables` → `append_variables`. + +## Concealed variables + +When calling `append_variables`, set `concealed` per variable: + +- Set `concealed: true` for API keys, tokens, passwords, private keys, and connection strings with credentials. +- Set `concealed: false` for ports, public URLs, feature flags, and non-sensitive config unless the user says otherwise. +- When unsure, default to `concealed: true`. + +## Safety + +- Never reveal secret values in chat +- Ask before changing variables unless the request is explicit + +## Plugin hook + +The plugin runs `validate-mounted-env-files` on `beforeShellExecution`. It blocks +shell commands when 1Password expects a mount that is missing, disabled, or not a +FIFO (for example a plain `.env` still on disk at the mount path). Reading the +`.env` with the Read tool is unaffected. + +Validation modes and recovery steps: [reference.md](reference.md) + +## Troubleshooting + +Mount conflict, validation modes, setup: [reference.md](reference.md) diff --git a/skills/1password-environments/reference.md b/skills/1password-environments/reference.md new file mode 100644 index 0000000..48299a9 --- /dev/null +++ b/skills/1password-environments/reference.md @@ -0,0 +1,103 @@ +# 1Password Environments — reference + +Supplement to [SKILL.md](SKILL.md). Use for setup, mount conflicts, and shell +validation behavior. + +## Plugin components + +This plugin ships: + +| Component | Path | Purpose | +|-----------|------|---------| +| MCP config | `mcp.json` | Connects Cursor to the 1Password desktop MCP server via `1password-mcp` (macOS/Linux) | +| Agent skill | `skills/1password-environments/SKILL.md` | Import, mount, and Environment management workflow | +| Mount validation hook | `hooks/hooks.json` → `scripts/validate-mounted-env-files` | Blocks shell when mounts are missing or misconfigured | + +Install the plugin from the Cursor marketplace — do not add the MCP server +manually in user settings without the bundled skill and hooks. + +## Official documentation + +Fetch hosted docs when you need product context, security boundaries, or +edge cases beyond this plugin's workflow. Index: +[llms.txt](https://www.1password.dev/llms.txt) + +| Topic | Doc | +|-------|-----| +| Local `.env` mounts (FIFO, git conflicts, dotenv compatibility) | [local-env-file.md](https://www.1password.dev/environments/local-env-file.md) | +| MCP server (setup, auth prompts, security model) | [mcp-server.md](https://www.1password.dev/environments/mcp-server.md) | +| Mount validation hook (canonical guide) | [agent-hook-validate.md](https://www.1password.dev/environments/agent-hook-validate.md) | + +## Setup + +**Requirements** + +- **macOS or Linux** with the [1Password desktop app](https://1password.com/downloads) installed +- **1Password Cursor plugin installed** (marketplace or local symlink) so this skill, hooks, and MCP config load together +- 1Password Labs **MCP Server** experiment enabled in the desktop app (`onepassword://settings/labs`) +- Access to a 1Password account with Developer Environments enabled +- `1password-mcp` on your `PATH` (registered by the desktop app when MCP is enabled) +- `sqlite3` in `PATH` for mount validation (pre-installed on macOS; install via your package manager on Linux) + +On **Windows**, the validation hook is a no-op and MCP/local mounts are unavailable. + +MCP server setup and platform details: [mcp-server.md](https://www.1password.dev/environments/mcp-server.md) + +**When things fail** + +- Authentication or environment access fails — the 1Password desktop app may need approval, unlocking, or account access +- MCP server unavailable — enable the **1Password Labs MCP Server** experiment via `onepassword://settings/labs`. If the Labs setting is missing, the account may lack the `ai-local-mcp-server` feature flag or an admin may have disabled the local MCP server — see [mcp-server.md](https://www.1password.dev/environments/mcp-server.md) +- `create_local_env_file` fails — confirm the user is on macOS or Linux +- Shell commands denied while 1Password expects a mount that is missing or disabled — see **Mount conflict** below + +## Mount validation hook + +Canonical hook guide: [agent-hook-validate.md](https://www.1password.dev/environments/agent-hook-validate.md). +This plugin's hook adds repo-scoped validation via `.1password/environments.toml` (below). + +`scripts/validate-mounted-env-files` runs on `beforeShellExecution`. It blocks +**all** shell commands when 1Password expects a mount at a path that is missing, +disabled, or not a FIFO (for example a plain `.env` still on disk at the mount +path). Reading the `.env` with the Read tool is unaffected — only shell commands +are gated. + +The hook **fails open**: if 1Password is not installed, the database is +unavailable, or `sqlite3` is missing, shell commands proceed normally. + +### Validation modes + +Scope depends on `.1password/environments.toml` at the project root: + +| Configuration | Behavior | +|---------------|----------| +| No TOML file (or no `mount_paths` field) | **Default mode** — all 1Password mount destinations for this workspace are validated | +| `mount_paths = [".env", ...]` | **Configured mode** — only listed paths are validated | +| `mount_paths = []` | Validation disabled for this repo; all shell commands allowed | + +Example configured mode: + +```toml +mount_paths = [".env", "billing.env"] +``` + +For each path, the hook checks that the file exists, is a FIFO (named pipe), and +is enabled in 1Password. + +### Mount conflict + +If shell commands are blocked with a message about missing, invalid, or disabled +environment files: + +1. Check whether 1Password already has a destination for the same path (`list_local_env_files`, or the 1Password app Destinations tab) +2. Resolve by one of: + - Temporarily set `mount_paths = []` in `.1password/environments.toml` to disable mount validation for this repo + - Fix the mount in 1Password (enable the destination, or remove it until migration finishes) + +The import itself does not need shell — Read the `.env` directly to get its keys +and values, then use MCP tools per [SKILL.md](SKILL.md). + +### Debugging the hook + +- **Cursor Settings → Hooks → Execution Log** — look for `beforeShellExecution` entries tied to `validate-mounted-env-files` +- **Debug run:** `DEBUG=1 echo '{"command": "echo test", "workspace_roots": ["/path/to/project"]}' | ./scripts/validate-mounted-env-files` +- **Log file:** `/tmp/1password-cursor-hooks.log` (when not in debug mode)