Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions .cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -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": [
Expand All @@ -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/",

@scottisloud scottisloud Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rishiy15 if I am reading OS-267 correctly, we actually need to omit this, right?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this only matters if we named the skills directory something else. The docs state:

If a manifest field is specified (e.g., "skills": "./my-skills/"), it replaces folder discovery for that component. The default folder is not also scanned.

So as of now, it is a no-op since the folder name specified is the default name. I do not have a strong preference for either keeping or removing it.

"mcpServers": "./mcp.json"
}
16 changes: 16 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -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
88 changes: 79 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.
Comment thread
scottisloud marked this conversation as resolved.

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

Expand All @@ -24,14 +30,38 @@ 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**.
3. Click **Install**.

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).

Comment thread
scottisloud marked this conversation as resolved.
## Features

### Hooks
Expand All @@ -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:**

Expand Down Expand Up @@ -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`.
Expand All @@ -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
Expand Down
8 changes: 8 additions & 0 deletions mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"mcpServers": {
"1password": {
"command": "1password-mcp",
"args": []
}
}
}
145 changes: 145 additions & 0 deletions skills/1password-environments/SKILL.md
Original file line number Diff line number Diff line change
@@ -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)
Loading