@rui.branco/1password-mcp 0.1.4 → 0.1.6
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.
- package/index.js +140 -44
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -175,6 +175,17 @@ function invalidateClient(name) {
|
|
|
175
175
|
clientCache.delete(name);
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Detect errors that indicate the IPC / desktop-app session is dead so the
|
|
180
|
+
* cached SDK client can be discarded and a fresh one created on retry.
|
|
181
|
+
*/
|
|
182
|
+
function isSessionError(e) {
|
|
183
|
+
if (e instanceof sdk.DesktopSessionExpiredError) return true;
|
|
184
|
+
// Belt-and-suspenders: catch IPC / auth wording from the native layer even
|
|
185
|
+
// when the SDK wraps it in a generic Error.
|
|
186
|
+
return /IPC|session.expired|desktop.auth|socket.closed/i.test(e.message);
|
|
187
|
+
}
|
|
188
|
+
|
|
178
189
|
// ============================================================================
|
|
179
190
|
// Helpers — vault/item resolution, redaction, formatting
|
|
180
191
|
// ============================================================================
|
|
@@ -522,22 +533,22 @@ const TOOL_DEFINITIONS = [
|
|
|
522
533
|
{
|
|
523
534
|
name: "op_update_item",
|
|
524
535
|
description:
|
|
525
|
-
"Update an existing item
|
|
536
|
+
"Update any part of an existing item — title, category, fields, tags, websites, sections, or notes. Omitted parameters are preserved. Fields are patched by title (existing fields match case-insensitively; new fields require `type`). Tags, websites, and sections are REPLACED when passed.",
|
|
526
537
|
inputSchema: {
|
|
527
538
|
type: "object",
|
|
528
539
|
properties: {
|
|
529
540
|
vault: { type: "string", description: "Vault id or title." },
|
|
530
541
|
item: { type: "string", description: "Item id or title to update." },
|
|
531
542
|
title: { type: "string", description: "New title (optional)." },
|
|
532
|
-
|
|
533
|
-
type: "
|
|
534
|
-
|
|
535
|
-
|
|
543
|
+
category: {
|
|
544
|
+
type: "string",
|
|
545
|
+
description:
|
|
546
|
+
"New category (rare). One of Login, SecureNote, Password, ApiCredentials, Database, Server, Email, Identity, CreditCard, etc.",
|
|
536
547
|
},
|
|
537
548
|
fields: {
|
|
538
549
|
type: "array",
|
|
539
550
|
description:
|
|
540
|
-
"Field patches: [{title, value, type?, sectionId?}]. Existing fields
|
|
551
|
+
"Field patches: [{title, value, type?, sectionId?}]. Existing fields matched by title have their values replaced. New fields require `type` (Text | Concealed | Totp | Email | Url | Phone).",
|
|
541
552
|
items: {
|
|
542
553
|
type: "object",
|
|
543
554
|
properties: {
|
|
@@ -549,6 +560,46 @@ const TOOL_DEFINITIONS = [
|
|
|
549
560
|
required: ["title", "value"],
|
|
550
561
|
},
|
|
551
562
|
},
|
|
563
|
+
tags: {
|
|
564
|
+
type: "array",
|
|
565
|
+
items: { type: "string" },
|
|
566
|
+
description:
|
|
567
|
+
"Replace the tag list. Omit to preserve existing tags; pass [] to clear.",
|
|
568
|
+
},
|
|
569
|
+
websites: {
|
|
570
|
+
type: "array",
|
|
571
|
+
description:
|
|
572
|
+
"Replace the website list. Each website is {url, label?, autofillBehavior?}. Omit to preserve existing websites.",
|
|
573
|
+
items: {
|
|
574
|
+
type: "object",
|
|
575
|
+
properties: {
|
|
576
|
+
url: { type: "string" },
|
|
577
|
+
label: { type: "string" },
|
|
578
|
+
autofillBehavior: {
|
|
579
|
+
type: "string",
|
|
580
|
+
description: "'AnywhereOnWebsite' (default) or 'Never'.",
|
|
581
|
+
},
|
|
582
|
+
},
|
|
583
|
+
required: ["url"],
|
|
584
|
+
},
|
|
585
|
+
},
|
|
586
|
+
sections: {
|
|
587
|
+
type: "array",
|
|
588
|
+
description: "Replace the section list. Each section is {title, id?}.",
|
|
589
|
+
items: {
|
|
590
|
+
type: "object",
|
|
591
|
+
properties: {
|
|
592
|
+
title: { type: "string" },
|
|
593
|
+
id: { type: "string" },
|
|
594
|
+
},
|
|
595
|
+
required: ["title"],
|
|
596
|
+
},
|
|
597
|
+
},
|
|
598
|
+
notes: {
|
|
599
|
+
type: "string",
|
|
600
|
+
description:
|
|
601
|
+
"Replace the notesPlain field. Omit to preserve existing notes.",
|
|
602
|
+
},
|
|
552
603
|
instance: {
|
|
553
604
|
type: "string",
|
|
554
605
|
description: "Instance name. Uses default if omitted.",
|
|
@@ -952,44 +1003,81 @@ async function updateItemHandler(args) {
|
|
|
952
1003
|
const current = await client.items.get(vault.id, itemRef.id);
|
|
953
1004
|
const updated = { ...current };
|
|
954
1005
|
|
|
1006
|
+
// --- title (rename) ---
|
|
955
1007
|
if (args.title) updated.title = args.title;
|
|
1008
|
+
|
|
1009
|
+
// --- category (rarely changed, but the SDK allows it) ---
|
|
1010
|
+
if (args.category) {
|
|
1011
|
+
const cat = sdk.ItemCategory[args.category];
|
|
1012
|
+
if (!cat) throw new Error(`Unknown category "${args.category}".`);
|
|
1013
|
+
updated.category = cat;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
// --- tags (replace when passed; preserved when omitted) ---
|
|
956
1017
|
if (args.tags) updated.tags = args.tags;
|
|
957
1018
|
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1019
|
+
// --- websites (replace when passed; preserved when omitted) ---
|
|
1020
|
+
if (Array.isArray(args.websites)) {
|
|
1021
|
+
updated.websites = args.websites.map((w) => ({
|
|
1022
|
+
url: w.url,
|
|
1023
|
+
label: w.label || "website",
|
|
1024
|
+
autofillBehavior:
|
|
1025
|
+
w.autofillBehavior === "Never"
|
|
1026
|
+
? sdk.AutofillBehavior.Never
|
|
1027
|
+
: sdk.AutofillBehavior.AnywhereOnWebsite,
|
|
1028
|
+
}));
|
|
1029
|
+
}
|
|
963
1030
|
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
1031
|
+
// --- sections (replace when passed; preserved when omitted) ---
|
|
1032
|
+
if (Array.isArray(args.sections)) {
|
|
1033
|
+
updated.sections = args.sections.map((s) => ({
|
|
1034
|
+
id: s.id || s.title.toLowerCase().replace(/\s+/g, "_"),
|
|
1035
|
+
title: s.title,
|
|
1036
|
+
}));
|
|
1037
|
+
}
|
|
971
1038
|
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
title: patch.title,
|
|
985
|
-
fieldType: ft,
|
|
986
|
-
value: patch.value,
|
|
987
|
-
sectionId: patch.sectionId,
|
|
988
|
-
});
|
|
1039
|
+
// --- fields (patch by title, add new ones with explicit type) ---
|
|
1040
|
+
const nextFields = [...(current.fields || [])];
|
|
1041
|
+
const fieldIndexByTitle = new Map(
|
|
1042
|
+
nextFields.map((f, idx) => [(f.title || "").toLowerCase(), idx]),
|
|
1043
|
+
);
|
|
1044
|
+
|
|
1045
|
+
function upsertField({ title, value, type, sectionId }) {
|
|
1046
|
+
const key = title.toLowerCase();
|
|
1047
|
+
const existingIdx = fieldIndexByTitle.get(key);
|
|
1048
|
+
if (existingIdx !== undefined) {
|
|
1049
|
+
nextFields[existingIdx] = { ...nextFields[existingIdx], value };
|
|
1050
|
+
return;
|
|
989
1051
|
}
|
|
990
|
-
|
|
1052
|
+
if (!type) {
|
|
1053
|
+
throw new Error(
|
|
1054
|
+
`Field "${title}" doesn't exist on the item — pass a 'type' to add it as a new field.`,
|
|
1055
|
+
);
|
|
1056
|
+
}
|
|
1057
|
+
const ft = sdk.ItemFieldType[type];
|
|
1058
|
+
if (!ft) throw new Error(`Unknown field type "${type}".`);
|
|
1059
|
+
const added = {
|
|
1060
|
+
id: title.toLowerCase().replace(/\s+/g, "_"),
|
|
1061
|
+
title,
|
|
1062
|
+
fieldType: ft,
|
|
1063
|
+
value,
|
|
1064
|
+
};
|
|
1065
|
+
if (sectionId) added.sectionId = sectionId;
|
|
1066
|
+
fieldIndexByTitle.set(key, nextFields.length);
|
|
1067
|
+
nextFields.push(added);
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
if (Array.isArray(args.fields)) {
|
|
1071
|
+
for (const patch of args.fields) upsertField(patch);
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// --- notes: stored as a 'notesPlain' Text field, so patch it the same way ---
|
|
1075
|
+
if (args.notes !== undefined) {
|
|
1076
|
+
upsertField({ title: "notesPlain", value: args.notes, type: "Text" });
|
|
991
1077
|
}
|
|
992
1078
|
|
|
1079
|
+
updated.fields = nextFields;
|
|
1080
|
+
|
|
993
1081
|
const result = await client.items.put(updated);
|
|
994
1082
|
return `Updated item "${result.title}" (id: ${result.id}) in vault "${vault.title}".`;
|
|
995
1083
|
}
|
|
@@ -1140,14 +1228,22 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1140
1228
|
isError: true,
|
|
1141
1229
|
};
|
|
1142
1230
|
}
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1231
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
1232
|
+
try {
|
|
1233
|
+
const result = await handler(args || {});
|
|
1234
|
+
return { content: [{ type: "text", text: String(result) }] };
|
|
1235
|
+
} catch (e) {
|
|
1236
|
+
if (attempt === 0 && isSessionError(e)) {
|
|
1237
|
+
// Stale IPC connection — purge cached clients and retry once so the
|
|
1238
|
+
// next getClient() call creates a fresh session.
|
|
1239
|
+
clientCache.clear();
|
|
1240
|
+
continue;
|
|
1241
|
+
}
|
|
1242
|
+
return {
|
|
1243
|
+
content: [{ type: "text", text: `Error: ${e.message}` }],
|
|
1244
|
+
isError: true,
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1151
1247
|
}
|
|
1152
1248
|
});
|
|
1153
1249
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rui.branco/1password-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "1Password MCP server for Claude Code - 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": {
|