Skip to main content
Glama

Create Logins

keychain_create_logins

Batch 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

TableJSON Schema
NameRequiredDescriptionDefault
itemsYesLogin item payloads to create; each item follows create_login fields and returns its own ok/error result.
continueOnErrorNoContinue after failures and return per-item ok/error results when true.

Implementation Reference

  • 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'),
          ),
        };
      },
    );
  • 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;
      });
    }
  • 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);
    }
  • 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);
      });
    }
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations indicate a write operation (readOnlyHint=false, destructiveHint=false) and no destruction. The description adds that returned items are redacted by default and that continueOnError yields per-item results, but could specify what exactly is redacted. Overall, adds useful behavioral context beyond annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences (50 words), front-loaded with core purpose, no redundant phrases. Every sentence serves a clear purpose.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers purpose, usage context, error handling, and output behavior. No output schema exists, but description explains per-item results. Could be more explicit about the structure of these results, but sufficient for correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% so baseline is 3. The description adds value by clarifying the effect of 'continueOnError' and the default redaction of returned items, which is not fully evident from the schema alone.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states 'Create multiple login items in one call,' specifying the action (create), resource (login items), and batching scope. This distinguishes it from sibling tools like keychain_create_login (singular) and other create tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly advises 'Use this when you need several independent credentials at once' and references the alternative 'create_login.' Provides guidance on 'continueOnError' parameter and redaction behavior, helping the agent decide when to use this tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/icoretech/warden-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server