@rui.branco/1password-mcp 0.1.3 → 0.1.4
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 +207 -17
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -361,7 +361,7 @@ const TOOL_DEFINITIONS = [
|
|
|
361
361
|
{
|
|
362
362
|
name: "op_search_items",
|
|
363
363
|
description:
|
|
364
|
-
"Search items by title across vaults. If `vault` is omitted, searches every vault the account can see.",
|
|
364
|
+
"Search items by title across vaults. When the user asks for a specific credential by name (e.g. 'give me the wifi password', 'what's my GitHub login'), call this with `reveal: true` to get the password/credential values back in a single call — no follow-up op_get_item needed. If `vault` is omitted, searches every vault the account can see. When there are 1–5 matches with reveal=true, return all of them; the user already consented to seeing concealed values by asking for them.",
|
|
365
365
|
inputSchema: {
|
|
366
366
|
type: "object",
|
|
367
367
|
properties: {
|
|
@@ -375,7 +375,12 @@ const TOOL_DEFINITIONS = [
|
|
|
375
375
|
},
|
|
376
376
|
limit: {
|
|
377
377
|
type: "number",
|
|
378
|
-
description: "Max results (default 25).",
|
|
378
|
+
description: "Max results (default 25, or 5 when reveal=true to avoid fetching too many items).",
|
|
379
|
+
},
|
|
380
|
+
reveal: {
|
|
381
|
+
type: "boolean",
|
|
382
|
+
description:
|
|
383
|
+
"If true, fetch each match's full item and return concealed field values (passwords, TOTPs) in cleartext. Use this when the user is asking for a credential by name — one call gets search + retrieval + reveal. Defaults to false.",
|
|
379
384
|
},
|
|
380
385
|
instance: {
|
|
381
386
|
type: "string",
|
|
@@ -514,6 +519,65 @@ const TOOL_DEFINITIONS = [
|
|
|
514
519
|
required: ["vault", "title"],
|
|
515
520
|
},
|
|
516
521
|
},
|
|
522
|
+
{
|
|
523
|
+
name: "op_update_item",
|
|
524
|
+
description:
|
|
525
|
+
"Update an existing item. Patch fields by title — e.g. to change the password on 'GitHub', pass fields: [{title: 'password', value: 'new-pwd'}]. To add a new field, include its type. Omitting fields leaves them untouched. Can also rename the item (title) or replace its tags.",
|
|
526
|
+
inputSchema: {
|
|
527
|
+
type: "object",
|
|
528
|
+
properties: {
|
|
529
|
+
vault: { type: "string", description: "Vault id or title." },
|
|
530
|
+
item: { type: "string", description: "Item id or title to update." },
|
|
531
|
+
title: { type: "string", description: "New title (optional)." },
|
|
532
|
+
tags: {
|
|
533
|
+
type: "array",
|
|
534
|
+
items: { type: "string" },
|
|
535
|
+
description: "Replace the item's tag list (optional).",
|
|
536
|
+
},
|
|
537
|
+
fields: {
|
|
538
|
+
type: "array",
|
|
539
|
+
description:
|
|
540
|
+
"Field patches: [{title, value, type?, sectionId?}]. Existing fields are matched by title (case-insensitive) and their values replaced. New fields require `type`.",
|
|
541
|
+
items: {
|
|
542
|
+
type: "object",
|
|
543
|
+
properties: {
|
|
544
|
+
title: { type: "string" },
|
|
545
|
+
value: { type: "string" },
|
|
546
|
+
type: { type: "string" },
|
|
547
|
+
sectionId: { type: "string" },
|
|
548
|
+
},
|
|
549
|
+
required: ["title", "value"],
|
|
550
|
+
},
|
|
551
|
+
},
|
|
552
|
+
instance: {
|
|
553
|
+
type: "string",
|
|
554
|
+
description: "Instance name. Uses default if omitted.",
|
|
555
|
+
},
|
|
556
|
+
},
|
|
557
|
+
required: ["vault", "item"],
|
|
558
|
+
},
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
name: "op_update_vault",
|
|
562
|
+
description:
|
|
563
|
+
"Update a vault's title and/or description. Pass at least one of them.",
|
|
564
|
+
inputSchema: {
|
|
565
|
+
type: "object",
|
|
566
|
+
properties: {
|
|
567
|
+
vault: { type: "string", description: "Vault id or title." },
|
|
568
|
+
title: { type: "string", description: "New title (optional)." },
|
|
569
|
+
description: {
|
|
570
|
+
type: "string",
|
|
571
|
+
description: "New description (optional).",
|
|
572
|
+
},
|
|
573
|
+
instance: {
|
|
574
|
+
type: "string",
|
|
575
|
+
description: "Instance name. Uses default if omitted.",
|
|
576
|
+
},
|
|
577
|
+
},
|
|
578
|
+
required: ["vault"],
|
|
579
|
+
},
|
|
580
|
+
},
|
|
517
581
|
{
|
|
518
582
|
name: "op_delete_item",
|
|
519
583
|
description: "Delete an item by vault + item reference.",
|
|
@@ -709,15 +773,19 @@ async function listItemsHandler(args) {
|
|
|
709
773
|
return `# Items in ${vault.title} (${items.length})\n\n${lines.join("\n")}`;
|
|
710
774
|
}
|
|
711
775
|
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
776
|
+
/**
|
|
777
|
+
* Core search implementation — pulled out of the handler so it can be unit
|
|
778
|
+
* tested with a fake client. Accepts an already-resolved client.
|
|
779
|
+
*/
|
|
780
|
+
async function searchItems(client, { query, vault, reveal, limit }) {
|
|
781
|
+
const wantReveal = !!reveal;
|
|
782
|
+
const max = limit || (wantReveal ? 5 : 25);
|
|
783
|
+
const q = String(query).toLowerCase();
|
|
716
784
|
|
|
717
785
|
let targetVaults;
|
|
718
|
-
if (
|
|
719
|
-
const v = await resolveVault(client,
|
|
720
|
-
if (!v) throw new Error(`Vault "${
|
|
786
|
+
if (vault) {
|
|
787
|
+
const v = await resolveVault(client, vault);
|
|
788
|
+
if (!v) throw new Error(`Vault "${vault}" not found.`);
|
|
721
789
|
targetVaults = [v];
|
|
722
790
|
} else {
|
|
723
791
|
targetVaults = await collect(await client.vaults.list());
|
|
@@ -727,19 +795,66 @@ async function searchItemsHandler(args) {
|
|
|
727
795
|
for (const v of targetVaults) {
|
|
728
796
|
for await (const it of await client.items.list(v.id)) {
|
|
729
797
|
if ((it.title || "").toLowerCase().includes(q)) {
|
|
730
|
-
hits.push({ ...it, _vaultTitle: v.title });
|
|
731
|
-
if (hits.length >=
|
|
798
|
+
hits.push({ ...it, _vaultId: v.id, _vaultTitle: v.title });
|
|
799
|
+
if (hits.length >= max) break;
|
|
732
800
|
}
|
|
733
801
|
}
|
|
734
|
-
if (hits.length >=
|
|
802
|
+
if (hits.length >= max) break;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
if (hits.length === 0) return `No items matched "${query}".`;
|
|
806
|
+
|
|
807
|
+
if (!wantReveal) {
|
|
808
|
+
const lines = hits.map(
|
|
809
|
+
(it) =>
|
|
810
|
+
`- **${it.title}** (id: ${it.id}, vault: ${it._vaultTitle}, category: ${it.category})`,
|
|
811
|
+
);
|
|
812
|
+
return `# Search: "${query}" (${hits.length})\n\n${lines.join("\n")}`;
|
|
735
813
|
}
|
|
736
814
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
815
|
+
// reveal=true: fetch each hit's full item in parallel and render every
|
|
816
|
+
// field (including concealed values). One-shot path for "give me the X
|
|
817
|
+
// credential" style queries.
|
|
818
|
+
const full = await Promise.all(
|
|
819
|
+
hits.map(async (it) => {
|
|
820
|
+
try {
|
|
821
|
+
const item = await client.items.get(it._vaultId, it.id);
|
|
822
|
+
return { item, _vaultTitle: it._vaultTitle, _error: null };
|
|
823
|
+
} catch (e) {
|
|
824
|
+
return { item: it, _vaultTitle: it._vaultTitle, _error: e.message };
|
|
825
|
+
}
|
|
826
|
+
}),
|
|
741
827
|
);
|
|
742
|
-
|
|
828
|
+
|
|
829
|
+
const sections = full.map((entry) => {
|
|
830
|
+
if (entry._error) {
|
|
831
|
+
return `## ${entry.item.title}\n\n_Failed to fetch: ${entry._error}_`;
|
|
832
|
+
}
|
|
833
|
+
const it = entry.item;
|
|
834
|
+
const fields = (it.fields || [])
|
|
835
|
+
.map((f) => redactField(f, true))
|
|
836
|
+
.filter((f) => f.value !== undefined && f.value !== "");
|
|
837
|
+
const fieldLines = fields.length
|
|
838
|
+
? fields
|
|
839
|
+
.map((f) => {
|
|
840
|
+
const otp = f.otp ? ` (OTP now: ${f.otp})` : "";
|
|
841
|
+
return `- **${f.title}** (${f.type}): ${f.value}${otp}`;
|
|
842
|
+
})
|
|
843
|
+
.join("\n")
|
|
844
|
+
: "_(no fields)_";
|
|
845
|
+
const websites = (it.websites || []).map((w) => w.url).filter(Boolean);
|
|
846
|
+
const websiteLine = websites.length
|
|
847
|
+
? `- **URLs:** ${websites.join(", ")}\n`
|
|
848
|
+
: "";
|
|
849
|
+
return `## ${it.title} (${it.category})\n\n- **Vault:** ${entry._vaultTitle}\n- **Item ID:** ${it.id}\n${websiteLine}\n${fieldLines}`;
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
return `# Search (revealed): "${query}" (${hits.length})\n\n${sections.join("\n\n")}`;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
async function searchItemsHandler(args) {
|
|
856
|
+
const client = await getClient(getInstanceByName(args.instance));
|
|
857
|
+
return searchItems(client, args);
|
|
743
858
|
}
|
|
744
859
|
|
|
745
860
|
async function getItemHandler(args) {
|
|
@@ -823,6 +938,76 @@ function generatePasswordHandler(args) {
|
|
|
823
938
|
return `Generated (${type}):\n${pwd}`;
|
|
824
939
|
}
|
|
825
940
|
|
|
941
|
+
async function updateItemHandler(args) {
|
|
942
|
+
const client = await getClient(getInstanceByName(args.instance));
|
|
943
|
+
const vault = await resolveVault(client, args.vault);
|
|
944
|
+
if (!vault) throw new Error(`Vault "${args.vault}" not found.`);
|
|
945
|
+
const itemRef = await resolveItem(client, vault, args.item);
|
|
946
|
+
if (!itemRef) {
|
|
947
|
+
throw new Error(
|
|
948
|
+
`Item "${args.item}" not found in vault "${vault.title}".`,
|
|
949
|
+
);
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
const current = await client.items.get(vault.id, itemRef.id);
|
|
953
|
+
const updated = { ...current };
|
|
954
|
+
|
|
955
|
+
if (args.title) updated.title = args.title;
|
|
956
|
+
if (args.tags) updated.tags = args.tags;
|
|
957
|
+
|
|
958
|
+
if (Array.isArray(args.fields) && args.fields.length > 0) {
|
|
959
|
+
const existing = current.fields || [];
|
|
960
|
+
const patchesByTitle = new Map(
|
|
961
|
+
args.fields.map((p) => [p.title.toLowerCase(), p]),
|
|
962
|
+
);
|
|
963
|
+
|
|
964
|
+
// Patch existing fields in place, preserving id/type/sectionId
|
|
965
|
+
const patchedExisting = existing.map((f) => {
|
|
966
|
+
const patch = patchesByTitle.get((f.title || "").toLowerCase());
|
|
967
|
+
if (!patch) return f;
|
|
968
|
+
patchesByTitle.delete((f.title || "").toLowerCase());
|
|
969
|
+
return { ...f, value: patch.value };
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
// Remaining patches = new fields to add (must specify type)
|
|
973
|
+
const additions = [];
|
|
974
|
+
for (const patch of patchesByTitle.values()) {
|
|
975
|
+
if (!patch.type) {
|
|
976
|
+
throw new Error(
|
|
977
|
+
`Field "${patch.title}" doesn't exist on the item — pass a 'type' to add it as a new field.`,
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
const ft = sdk.ItemFieldType[patch.type];
|
|
981
|
+
if (!ft) throw new Error(`Unknown field type "${patch.type}".`);
|
|
982
|
+
additions.push({
|
|
983
|
+
id: patch.title.toLowerCase().replace(/\s+/g, "_"),
|
|
984
|
+
title: patch.title,
|
|
985
|
+
fieldType: ft,
|
|
986
|
+
value: patch.value,
|
|
987
|
+
sectionId: patch.sectionId,
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
updated.fields = [...patchedExisting, ...additions];
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
const result = await client.items.put(updated);
|
|
994
|
+
return `Updated item "${result.title}" (id: ${result.id}) in vault "${vault.title}".`;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
async function updateVaultHandler(args) {
|
|
998
|
+
const client = await getClient(getInstanceByName(args.instance));
|
|
999
|
+
const vault = await resolveVault(client, args.vault);
|
|
1000
|
+
if (!vault) throw new Error(`Vault "${args.vault}" not found.`);
|
|
1001
|
+
if (!args.title && args.description === undefined) {
|
|
1002
|
+
throw new Error("Provide at least one of title or description.");
|
|
1003
|
+
}
|
|
1004
|
+
await client.vaults.update(vault.id, {
|
|
1005
|
+
title: args.title || vault.title,
|
|
1006
|
+
description: args.description !== undefined ? args.description : vault.description || "",
|
|
1007
|
+
});
|
|
1008
|
+
return `Updated vault "${args.title || vault.title}" (id: ${vault.id}).`;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
826
1011
|
async function createItemHandler(args) {
|
|
827
1012
|
const client = await getClient(getInstanceByName(args.instance));
|
|
828
1013
|
const vault = await resolveVault(client, args.vault);
|
|
@@ -909,8 +1094,10 @@ const HANDLERS = {
|
|
|
909
1094
|
op_get_secret: getSecretHandler,
|
|
910
1095
|
op_generate_password: generatePasswordHandler,
|
|
911
1096
|
op_create_item: createItemHandler,
|
|
1097
|
+
op_update_item: updateItemHandler,
|
|
912
1098
|
op_delete_item: deleteItemHandler,
|
|
913
1099
|
op_create_vault: createVaultHandler,
|
|
1100
|
+
op_update_vault: updateVaultHandler,
|
|
914
1101
|
op_delete_vault: deleteVaultHandler,
|
|
915
1102
|
};
|
|
916
1103
|
|
|
@@ -996,6 +1183,7 @@ if (typeof module !== "undefined") {
|
|
|
996
1183
|
resolveItem,
|
|
997
1184
|
redactField,
|
|
998
1185
|
formatItem,
|
|
1186
|
+
searchItems,
|
|
999
1187
|
// handler map + tool list (for drift guard tests)
|
|
1000
1188
|
TOOL_DEFINITIONS,
|
|
1001
1189
|
HANDLERS,
|
|
@@ -1006,5 +1194,7 @@ if (typeof module !== "undefined") {
|
|
|
1006
1194
|
searchItemsHandler,
|
|
1007
1195
|
getItemHandler,
|
|
1008
1196
|
generatePasswordHandler,
|
|
1197
|
+
updateItemHandler,
|
|
1198
|
+
updateVaultHandler,
|
|
1009
1199
|
};
|
|
1010
1200
|
}
|
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.4",
|
|
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": {
|