Create Logins
keychain_create_loginsBatch create multiple login credentials in one call, returning per-item success or error results. Supports continuing on failure to process all items.
Instructions
Create multiple login items in one call. Use this when you need several independent credentials at once, with the same login-item behavior as create_login. Set continueOnError to keep going after a failure and receive per-item ok/error results; returned items are redacted by default.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| items | Yes | Login item payloads to create; each item follows create_login fields and returns its own ok/error result. | |
| continueOnError | No | Continue after failures and return per-item ok/error results when true. |
Implementation Reference
- src/tools/registerTools.ts:2162-2236 (registration)MCP tool registration for 'create_logins' (actual tool name: 'keychain_create_logins' with prefix) - registers the tool with input schema for creating multiple login items, calls sdk.createLogins()
registerTool( `${deps.toolPrefix}.create_logins`, { title: 'Create Logins', description: 'Create multiple login items in one call. Use this when you need several independent credentials at once, with the same login-item behavior as create_login. Set continueOnError to keep going after a failure and receive per-item ok/error results; returned items are redacted by default.', annotations: mutatingToolAnnotations, inputSchema: { items: z .array( z.object({ name: z.string().describe('Display name for the login item.'), username: z .string() .optional() .describe('Login username or email address.'), password: z .string() .optional() .describe('Password to store on the login item.'), uris: urisSchema, totp: z .string() .optional() .describe('TOTP secret or otpauth value for the login item.'), notes: z .string() .optional() .describe('Optional free-form notes for the login item.'), fields: itemFieldsSchema, attachments: itemAttachmentsSchema, favorite: z .boolean() .optional() .describe('Mark the item as a favorite when true.'), organizationId: optionalOrganizationIdSchema, collectionIds: collectionIdsSchema, folderId: folderIdSchema, }), ) .describe( 'Login item payloads to create; each item follows create_login fields and returns its own ok/error result.', ), continueOnError: z .boolean() .optional() .describe( 'Continue after failures and return per-item ok/error results when true.', ), }, _meta: toolMeta, }, async (input, extra) => { if (isReadOnly) return readonlyBlocked(); const sdk = await deps.getSdk(extra.authInfo); const results = await sdk.createLogins({ items: input.items.map((it) => ({ ...it, uris: normalizeUrisInput(it.uris), })), continueOnError: input.continueOnError, }); const structuredContent = { results }; return { structuredContent, content: structuredTextContent( structuredContent, [ `Created ${results.length} login(s):`, ...results.map(formatBatchCreateResult), ].join('\n'), ), }; }, ); - src/sdk/keychainSdk.ts:1778-1820 (handler)SDK implementation of createLogins() - the actual handler that creates multiple login items in a session, calling createLoginForSession for each item with optional continueOnError
async createLogins(input: { items: { name: string; username?: string; password?: string; uris?: UriInput[]; totp?: string; notes?: string; fields?: ItemFieldInput[]; attachments?: { filename: string; contentBase64: string }[]; reveal?: boolean; favorite?: boolean; organizationId?: string; collectionIds?: string[]; folderId?: string; }[]; continueOnError?: boolean; }): Promise<{ ok: boolean; item?: unknown; error?: string }[]> { const continueOnError = input.continueOnError ?? true; return this.bw.withSession(async (session) => { if (this.syncOnWrite()) { await this.bw .runForSession(session, ['sync'], { timeoutMs: 120_000 }) .catch(() => {}); } const results: { ok: boolean; item?: unknown; error?: string }[] = []; for (const it of input.items) { try { const created = await this.createLoginForSession(session, it); results.push({ ok: true, item: created }); } catch (err) { const msg = err && typeof err === 'object' && 'message' in err ? String((err as { message?: unknown }).message) : String(err); results.push({ ok: false, error: msg }); if (!continueOnError) break; } } return results; }); } - src/sdk/keychainSdk.ts:194-296 (helper)Helper method createLoginForSession() that performs the actual Bitwarden CLI item creation for each login - gets template, deep clones, sets fields, runs 'bw create item', handles attachments and collection assignments
private async createLoginForSession( session: string, input: { name: string; username?: string; password?: string; uris?: UriInput[]; totp?: string; notes?: string; fields?: ItemFieldInput[]; attachments?: { filename: string; contentBase64: string }[]; reveal?: boolean; favorite?: boolean; organizationId?: string; collectionIds?: string[]; folderId?: string; }, ): Promise<unknown> { const tpl = (await this.bw.getTemplateItemForSession(session)) as AnyRecord; const item = deepClone(tpl); item.type = ITEM_TYPE.login; item.name = input.name; item.notes = input.notes ?? ''; item.favorite = input.favorite ?? false; if (input.organizationId) item.organizationId = input.organizationId; if (input.folderId) item.folderId = input.folderId; item.fields = normalizeFields(input.fields) ?? []; const login = ( item.login && typeof item.login === 'object' ? (item.login as AnyRecord) : {} ) as AnyRecord; if (input.username !== undefined) login.username = input.username; if (input.password !== undefined) login.password = input.password; if (input.totp !== undefined) login.totp = input.totp; if (input.uris !== undefined) login.uris = normalizeUris(input.uris); item.login = login; // Set collectionIds optimistically; we'll also enforce with item-collections edit. if (input.collectionIds) item.collectionIds = input.collectionIds; const encoded = encodeJsonForBw(item); const { stdout } = await this.bw.runForSession( session, ['create', 'item', encoded], { timeoutMs: 120_000 }, ); const created = this.parseBwJson<AnyRecord>(stdout); if (input.attachments?.length) { const dir = await mkdtemp(join(tmpdir(), 'keychain-attach-')); try { for (const att of input.attachments) { const safeBase = basename(att.filename || 'attachment.bin').replace( /[^A-Za-z0-9._-]+/g, '_', ); const safeName = safeBase.length > 0 ? safeBase : 'attachment.bin'; const p = join(dir, safeName); await writeFile(p, Buffer.from(att.contentBase64, 'base64')); const { stdout: attOut } = await this.bw.runForSession( session, [ 'create', 'attachment', '--file', p, '--itemid', String(created.id), ], { timeoutMs: 120_000 }, ); // IMPORTANT: bw may return the full item JSON (including secrets) here. // Never parse or include it in our response. void attOut; } } finally { await rm(dir, { recursive: true, force: true }); } } if (input.collectionIds?.length) { const encodedCols = encodeJsonForBw(input.collectionIds); await this.bw .runForSession( session, ['edit', 'item-collections', String(created.id), encodedCols], { timeoutMs: 120_000 }, ) .catch(() => {}); } // Refetch so attachments metadata is accurate, but redact secrets by default. const { stdout: gotOut } = await this.bw.runForSession( session, ['get', 'item', String(created.id)], { timeoutMs: 60_000 }, ); const got = JSON.parse(gotOut) as AnyRecord; return this.maybeRedact(got, input.reveal); } - src/sdk/keychainSdk.ts:1750-1776 (helper)SDK's createLogin() - single login creation method that wraps createLoginForSession() with sync-on-write logic
async createLogin(input: { name: string; username?: string; password?: string; uris?: UriInput[]; totp?: string; notes?: string; fields?: ItemFieldInput[]; attachments?: { filename: string; contentBase64: string }[]; reveal?: boolean; favorite?: boolean; organizationId?: string; collectionIds?: string[]; folderId?: string; }): Promise<unknown> { return this.bw.withSession(async (session) => { if (this.syncOnWrite()) { await this.bw .runForSession(session, ['sync'], { timeoutMs: 120_000, }) .catch(() => {}); } return this.createLoginForSession(session, input); }); }