@rui.branco/1password-mcp 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +122 -122
  2. package/index.js +1301 -1296
  3. package/package.json +1 -1
  4. package/setup.js +213 -210
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rui.branco/1password-mcp",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "1Password MCP server for any MCP-compatible AI client (Claude Code, Codex, Antigravity, Cursor, etc.) - list vaults and items, get secrets, generate passwords, manage items via service accounts. Multi-instance like jira-mcp.",
5
5
  "main": "index.js",
6
6
  "bin": {
package/setup.js CHANGED
@@ -1,210 +1,213 @@
1
- #!/usr/bin/env node
2
-
3
- const readline = require("readline");
4
- const fs = require("fs");
5
- const path = require("path");
6
-
7
- const configDir = path.join(process.env.HOME, ".config/1password-mcp");
8
- const configPath = path.join(configDir, "config.json");
9
-
10
- let args = process.argv.slice(2);
11
- if (args[0] === "setup") args = args.slice(1);
12
-
13
- function loadConfig() {
14
- try {
15
- if (fs.existsSync(configPath)) {
16
- return JSON.parse(fs.readFileSync(configPath, "utf8"));
17
- }
18
- } catch {}
19
- return null;
20
- }
21
-
22
- function saveConfig(cfg) {
23
- if (!fs.existsSync(configDir)) fs.mkdirSync(configDir, { recursive: true });
24
- fs.writeFileSync(configPath, JSON.stringify(cfg, null, 2));
25
- try {
26
- fs.chmodSync(configPath, 0o600);
27
- } catch {}
28
- }
29
-
30
- // Non-interactive: setup add <name> <token> [description]
31
- // setup add-desktop <name> <accountName> [description]
32
- if (args[0] === "add" && args.length >= 3) {
33
- const [, name, token, ...descArgs] = args;
34
- const description = descArgs.join(" ").trim();
35
- const config = loadConfig() || {};
36
- if (!config.instances) config.instances = [];
37
- const existing = config.instances.findIndex((i) => i.name === name);
38
- const instance = { name, authType: "serviceAccount", token };
39
- if (description) instance.description = description;
40
- if (existing >= 0) config.instances[existing] = instance;
41
- else config.instances.push(instance);
42
- if (!config.defaultInstance) config.defaultInstance = name;
43
- saveConfig(config);
44
- console.log(`Instance "${name}" saved to ${configPath} (serviceAccount)`);
45
- process.exit(0);
46
- }
47
-
48
- if (args[0] === "add-desktop" && args.length >= 3) {
49
- const [, name, accountName, ...descArgs] = args;
50
- const description = descArgs.join(" ").trim();
51
- const config = loadConfig() || {};
52
- if (!config.instances) config.instances = [];
53
- const existing = config.instances.findIndex((i) => i.name === name);
54
- const instance = { name, authType: "desktop", accountName };
55
- if (description) instance.description = description;
56
- if (existing >= 0) config.instances[existing] = instance;
57
- else config.instances.push(instance);
58
- if (!config.defaultInstance) config.defaultInstance = name;
59
- saveConfig(config);
60
- console.log(`Instance "${name}" saved to ${configPath} (desktop: ${accountName})`);
61
- console.log("Make sure the 1Password 8 desktop app is installed and the CLI integration is enabled (Settings → Developer → Connect with 1Password CLI).");
62
- process.exit(0);
63
- }
64
-
65
- // Non-interactive: setup remove <name>
66
- if (args[0] === "remove" && args.length >= 2) {
67
- const config = loadConfig();
68
- if (!config || !config.instances) {
69
- console.error("No config found.");
70
- process.exit(1);
71
- }
72
- config.instances = config.instances.filter((i) => i.name !== args[1]);
73
- if (config.defaultInstance === args[1]) {
74
- config.defaultInstance = config.instances[0]?.name || null;
75
- }
76
- saveConfig(config);
77
- console.log(`Instance "${args[1]}" removed.`);
78
- process.exit(0);
79
- }
80
-
81
- // Interactive
82
- const rl = readline.createInterface({
83
- input: process.stdin,
84
- output: process.stdout,
85
- });
86
-
87
- function ask(q) {
88
- return new Promise((r) => rl.question(q, r));
89
- }
90
-
91
- async function setup() {
92
- console.log("\n=== 1Password MCP Setup ===\n");
93
- const existing = loadConfig();
94
- if (existing?.instances?.length) {
95
- console.log("Existing instances:");
96
- for (const inst of existing.instances) {
97
- const isDefault = inst.name === existing.defaultInstance ? " (default)" : "";
98
- const desc = inst.description ? ` - ${inst.description}` : "";
99
- console.log(` - ${inst.name}${isDefault}${desc}`);
100
- }
101
- console.log();
102
- const action = await ask("Add another instance? (y/N): ");
103
- if (action.trim().toLowerCase() !== "y") {
104
- rl.close();
105
- return;
106
- }
107
- }
108
-
109
- console.log("Pick auth mode:");
110
- console.log(" 1. Desktop app (recommended for personal use — uses 1Password 8 biometric unlock, full vault access)");
111
- console.log(" 2. Service account token (recommended for CI/shared — needs admin to provision, scoped vaults only)\n");
112
- const mode = (await ask("Auth mode (1/2, default 1): ")).trim() || "1";
113
-
114
- const name = (await ask("Instance name (e.g., work): ")).trim();
115
- const description = (await ask("Description (optional): ")).trim();
116
-
117
- const config = existing || { instances: [] };
118
- if (!config.instances) config.instances = [];
119
- const idx = config.instances.findIndex((i) => i.name === name);
120
- let instance;
121
-
122
- if (mode === "2") {
123
- console.log("\nCreate a service account token at:");
124
- console.log(" https://start.1password.com/developer-tools/infrastructure-secrets/serviceaccount");
125
- const token = (await ask("Service account token (ops_...): ")).trim();
126
- instance = { name, authType: "serviceAccount", token };
127
- } else {
128
- console.log("\n=== One-time 1Password 8 setup (required for desktop auth) ===");
129
- console.log("");
130
- console.log("Open the 1Password 8 desktop app (not the browser — the Mac/Windows app).");
131
- console.log("");
132
- console.log(" 1. Open Settings (cmd+, on Mac / ctrl+, on Windows)");
133
- console.log(" 2. Click the 'Developer' tab on the left");
134
- console.log(" 3. Tick 'Integrate with 1Password CLI'");
135
- console.log(" (under 'Command-Line Interface (CLI)')");
136
- console.log(" 4. Tick 'Integrate with other apps'");
137
- console.log(" (under 'Integrate with the 1Password SDKs')");
138
- console.log(" ** THIS IS THE KEY ONE — without it the SDK can't unlock. **");
139
- console.log(" 5. (Optional) Settings → Security → 'Unlock using Touch ID'");
140
- console.log("");
141
- console.log("The first time the MCP calls 1Password you'll get a system prompt");
142
- console.log("asking to authorize '1password-mcp' — click Approve.");
143
- console.log("");
144
- console.log("=== Finding your account name ===");
145
- console.log("");
146
- console.log("Open 1Password 8 → click the account switcher in the top-left.");
147
- console.log("The signin address shown next to your name is what you want here,");
148
- console.log("e.g. 'my.1password.com' or 'my-team.1password.com'.");
149
- console.log("");
150
- const accountName = (await ask("Account name (e.g., my.1password.com): ")).trim();
151
- if (!accountName) {
152
- console.error("Account name is required. Aborting.");
153
- rl.close();
154
- process.exit(1);
155
- }
156
- instance = { name, authType: "desktop", accountName };
157
-
158
- // Validate by actually trying to talk to the desktop app. This will pop the
159
- // OS-level authorization prompt on first use.
160
- console.log("\nValidating with 1Password desktop app (you may get an auth prompt)…");
161
- try {
162
- const sdk = require("@1password/sdk");
163
- const client = await sdk.createClient({
164
- auth: new sdk.DesktopAuth(accountName),
165
- integrationName: "1password-mcp-setup",
166
- integrationVersion: "v1",
167
- });
168
- const probe = [];
169
- for await (const v of await client.vaults.list()) {
170
- probe.push(v);
171
- if (probe.length >= 1) break;
172
- }
173
- console.log(`Validated. Saw vault: ${probe[0]?.title || "(none visible)"}.`);
174
- } catch (e) {
175
- console.error("");
176
- console.error("Validation failed: " + e.message);
177
- console.error("");
178
- console.error("Common causes:");
179
- console.error(" - 'Integrate with other apps' is NOT ticked in 1Password Settings → Developer");
180
- console.error(" - 1Password 8 desktop app is not running or locked");
181
- console.error(" - Account name is wrong (check the account switcher in 1Password 8)");
182
- console.error("");
183
- console.error("Fix the issue and re-run this setup.");
184
- rl.close();
185
- process.exit(1);
186
- }
187
- }
188
-
189
- if (description) instance.description = description;
190
- if (idx >= 0) config.instances[idx] = instance;
191
- else config.instances.push(instance);
192
-
193
- if (!config.defaultInstance) config.defaultInstance = name;
194
- else {
195
- const setDefault = await ask(`Set "${name}" as default? (y/N): `);
196
- if (setDefault.trim().toLowerCase() === "y") config.defaultInstance = name;
197
- }
198
-
199
- saveConfig(config);
200
- console.log(`\nConfig saved to ${configPath} (mode 0600)`);
201
- console.log("\nAdd to Claude Code with:");
202
- console.log(" claude mcp add --transport stdio 1password -- node $HOME/WebstormProjects/1password-mcp/index.js");
203
- rl.close();
204
- }
205
-
206
- setup().catch((e) => {
207
- console.error("Setup failed:", e.message);
208
- rl.close();
209
- process.exit(1);
210
- });
1
+ #!/usr/bin/env node
2
+
3
+ const readline = require("readline");
4
+ const fs = require("fs");
5
+ const os = require("os");
6
+ const path = require("path");
7
+
8
+ const configDir = path.join(os.homedir(), ".config", "1password-mcp");
9
+ const configPath = path.join(configDir, "config.json");
10
+
11
+ let args = process.argv.slice(2);
12
+ if (args[0] === "setup") args = args.slice(1);
13
+
14
+ function loadConfig() {
15
+ try {
16
+ if (fs.existsSync(configPath)) {
17
+ return JSON.parse(fs.readFileSync(configPath, "utf8"));
18
+ }
19
+ } catch {}
20
+ return null;
21
+ }
22
+
23
+ function saveConfig(cfg) {
24
+ if (!fs.existsSync(configDir)) fs.mkdirSync(configDir, { recursive: true });
25
+ fs.writeFileSync(configPath, JSON.stringify(cfg, null, 2));
26
+ try {
27
+ fs.chmodSync(configPath, 0o600);
28
+ } catch {}
29
+ }
30
+
31
+ // Non-interactive: setup add <name> <token> [description]
32
+ // setup add-desktop <name> <accountName> [description]
33
+ if (args[0] === "add" && args.length >= 3) {
34
+ const [, name, token, ...descArgs] = args;
35
+ const description = descArgs.join(" ").trim();
36
+ const config = loadConfig() || {};
37
+ if (!config.instances) config.instances = [];
38
+ const existing = config.instances.findIndex((i) => i.name === name);
39
+ const instance = { name, authType: "serviceAccount", token };
40
+ if (description) instance.description = description;
41
+ if (existing >= 0) config.instances[existing] = instance;
42
+ else config.instances.push(instance);
43
+ if (!config.defaultInstance) config.defaultInstance = name;
44
+ saveConfig(config);
45
+ console.log(`Instance "${name}" saved to ${configPath} (serviceAccount)`);
46
+ process.exit(0);
47
+ }
48
+
49
+ if (args[0] === "add-desktop" && args.length >= 3) {
50
+ const [, name, accountName, ...descArgs] = args;
51
+ const description = descArgs.join(" ").trim();
52
+ const config = loadConfig() || {};
53
+ if (!config.instances) config.instances = [];
54
+ const existing = config.instances.findIndex((i) => i.name === name);
55
+ const instance = { name, authType: "desktop", accountName };
56
+ if (description) instance.description = description;
57
+ if (existing >= 0) config.instances[existing] = instance;
58
+ else config.instances.push(instance);
59
+ if (!config.defaultInstance) config.defaultInstance = name;
60
+ saveConfig(config);
61
+ console.log(`Instance "${name}" saved to ${configPath} (desktop: ${accountName})`);
62
+ console.log("Make sure the 1Password 8 desktop app is installed and the CLI integration is enabled (Settings → Developer → Connect with 1Password CLI).");
63
+ process.exit(0);
64
+ }
65
+
66
+ // Non-interactive: setup remove <name>
67
+ if (args[0] === "remove" && args.length >= 2) {
68
+ const config = loadConfig();
69
+ if (!config || !config.instances) {
70
+ console.error("No config found.");
71
+ process.exit(1);
72
+ }
73
+ config.instances = config.instances.filter((i) => i.name !== args[1]);
74
+ if (config.defaultInstance === args[1]) {
75
+ config.defaultInstance = config.instances[0]?.name || null;
76
+ }
77
+ saveConfig(config);
78
+ console.log(`Instance "${args[1]}" removed.`);
79
+ process.exit(0);
80
+ }
81
+
82
+ // Interactive
83
+ const rl = readline.createInterface({
84
+ input: process.stdin,
85
+ output: process.stdout,
86
+ });
87
+
88
+ function ask(q) {
89
+ return new Promise((r) => rl.question(q, r));
90
+ }
91
+
92
+ async function setup() {
93
+ console.log("\n=== 1Password MCP Setup ===\n");
94
+ const existing = loadConfig();
95
+ if (existing?.instances?.length) {
96
+ console.log("Existing instances:");
97
+ for (const inst of existing.instances) {
98
+ const isDefault = inst.name === existing.defaultInstance ? " (default)" : "";
99
+ const desc = inst.description ? ` - ${inst.description}` : "";
100
+ console.log(` - ${inst.name}${isDefault}${desc}`);
101
+ }
102
+ console.log();
103
+ const action = await ask("Add another instance? (y/N): ");
104
+ if (action.trim().toLowerCase() !== "y") {
105
+ rl.close();
106
+ return;
107
+ }
108
+ }
109
+
110
+ console.log("Pick auth mode:");
111
+ console.log(" 1. Desktop app (recommended for personal use — uses 1Password 8 biometric unlock, full vault access)");
112
+ console.log(" 2. Service account token (recommended for CI/shared — needs admin to provision, scoped vaults only)\n");
113
+ const mode = (await ask("Auth mode (1/2, default 1): ")).trim() || "1";
114
+
115
+ const name = (await ask("Instance name (e.g., work): ")).trim();
116
+ const description = (await ask("Description (optional): ")).trim();
117
+
118
+ const config = existing || { instances: [] };
119
+ if (!config.instances) config.instances = [];
120
+ const idx = config.instances.findIndex((i) => i.name === name);
121
+ let instance;
122
+
123
+ if (mode === "2") {
124
+ console.log("\nCreate a service account token at:");
125
+ console.log(" https://start.1password.com/developer-tools/infrastructure-secrets/serviceaccount");
126
+ const token = (await ask("Service account token (ops_...): ")).trim();
127
+ instance = { name, authType: "serviceAccount", token };
128
+ } else {
129
+ console.log("\n=== One-time 1Password 8 setup (required for desktop auth) ===");
130
+ console.log("");
131
+ console.log("Open the 1Password 8 desktop app (not the browser — the Mac/Windows app).");
132
+ console.log("");
133
+ console.log(" 1. Open Settings (cmd+, on Mac / ctrl+, on Windows)");
134
+ console.log(" 2. Click the 'Developer' tab on the left");
135
+ console.log(" 3. Tick 'Integrate with 1Password CLI'");
136
+ console.log(" (under 'Command-Line Interface (CLI)')");
137
+ console.log(" 4. Tick 'Integrate with other apps'");
138
+ console.log(" (under 'Integrate with the 1Password SDKs')");
139
+ console.log(" ** THIS IS THE KEY ONE — without it the SDK can't unlock. **");
140
+ console.log(" 5. (Optional) Settings → Security → 'Unlock using Touch ID'");
141
+ console.log("");
142
+ console.log("The first time the MCP calls 1Password you'll get a system prompt");
143
+ console.log("asking to authorize '1password-mcp' — click Approve.");
144
+ console.log("");
145
+ console.log("=== Finding your account name ===");
146
+ console.log("");
147
+ console.log("Open 1Password 8 → click the account switcher in the top-left.");
148
+ console.log("The signin address shown next to your name is what you want here,");
149
+ console.log("e.g. 'my.1password.com' or 'my-team.1password.com'.");
150
+ console.log("");
151
+ const accountName = (await ask("Account name (e.g., my.1password.com): ")).trim();
152
+ if (!accountName) {
153
+ console.error("Account name is required. Aborting.");
154
+ rl.close();
155
+ process.exit(1);
156
+ }
157
+ instance = { name, authType: "desktop", accountName };
158
+
159
+ // Validate by actually trying to talk to the desktop app. This will pop the
160
+ // OS-level authorization prompt on first use.
161
+ console.log("\nValidating with 1Password desktop app (you may get an auth prompt)…");
162
+ try {
163
+ const sdk = require("@1password/sdk");
164
+ const client = await sdk.createClient({
165
+ auth: new sdk.DesktopAuth(accountName),
166
+ integrationName: "1password-mcp-setup",
167
+ integrationVersion: "v1",
168
+ });
169
+ const probe = [];
170
+ for await (const v of await client.vaults.list()) {
171
+ probe.push(v);
172
+ if (probe.length >= 1) break;
173
+ }
174
+ console.log(`Validated. Saw vault: ${probe[0]?.title || "(none visible)"}.`);
175
+ } catch (e) {
176
+ console.error("");
177
+ console.error("Validation failed: " + e.message);
178
+ console.error("");
179
+ console.error("Common causes:");
180
+ console.error(" - 'Integrate with other apps' is NOT ticked in 1Password Settings → Developer");
181
+ console.error(" - 1Password 8 desktop app is not running or locked");
182
+ console.error(" - Account name is wrong (check the account switcher in 1Password 8)");
183
+ console.error("");
184
+ console.error("Fix the issue and re-run this setup.");
185
+ rl.close();
186
+ process.exit(1);
187
+ }
188
+ }
189
+
190
+ if (description) instance.description = description;
191
+ if (idx >= 0) config.instances[idx] = instance;
192
+ else config.instances.push(instance);
193
+
194
+ if (!config.defaultInstance) config.defaultInstance = name;
195
+ else {
196
+ const setDefault = await ask(`Set "${name}" as default? (y/N): `);
197
+ if (setDefault.trim().toLowerCase() === "y") config.defaultInstance = name;
198
+ }
199
+
200
+ saveConfig(config);
201
+ console.log(`\nConfig saved to ${configPath} (mode 0600)`);
202
+ console.log("\nAdd to Claude Code with:");
203
+ console.log(
204
+ ` claude mcp add --transport stdio 1password -- node ${path.join(__dirname, "index.js")}`,
205
+ );
206
+ rl.close();
207
+ }
208
+
209
+ setup().catch((e) => {
210
+ console.error("Setup failed:", e.message);
211
+ rl.close();
212
+ process.exit(1);
213
+ });