Skip to main content
Glama

keychain_search_items

Search vault items like logins, notes, and SSH keys using text queries and filters for organization, folder, collection, or URL to locate specific credentials and data.

Instructions

Search vault items by text and filters (org/folder/collection/url).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
textNo
typeNo
organizationIdNo
folderIdNo
collectionIdNo
urlNo
trashNo
limitNo

Implementation Reference

  • Tool definition and handler registration for search_items.
      `${deps.toolPrefix}.search_items`,
      {
        title: 'Search Items',
        description:
          'Search vault items by text and filters (org/folder/collection/url).',
        annotations: { readOnlyHint: true },
        inputSchema: {
          text: z.string().optional(),
          type: z
            .enum(['login', 'note', 'ssh_key', 'card', 'identity'])
            .optional(),
          organizationId: z
            .union([z.string(), z.literal('null'), z.literal('notnull')])
            .optional(),
          folderId: z
            .union([z.string(), z.literal('null'), z.literal('notnull')])
            .optional(),
          collectionId: z.string().optional(),
          url: z.string().optional(),
          trash: z.boolean().optional(),
          limit: z.number().int().min(1).max(500).optional(),
        },
        _meta: toolMeta,
      },
      async (input, extra) => {
        const sdk = await deps.getSdk(extra.authInfo);
        const items = await sdk.searchItems(input);
        const minimal = items.map((i) => sdk.minimalSummary(i));
        return {
          structuredContent: { results: minimal },
          content: [{ type: 'text', text: `Found ${minimal.length} item(s).` }],
        };
      },
    );
  • Actual implementation of item search logic using Bitwarden CLI.
    async searchItems(input: SearchItemsInput): Promise<unknown[]> {
      const { limit } = input;
      const rawText = (input.text ?? '').trim();
      const tokens = rawText.includes('|')
        ? rawText
            .split('|')
            .map((s) => s.trim())
            .filter((s) => s.length > 0)
        : rawText.length > 0
          ? [rawText]
          : [];
    
      const orgFilter = input.organizationId;
      const orgId =
        orgFilter && orgFilter !== 'null' && orgFilter !== 'notnull'
          ? orgFilter
          : undefined;
    
      const folderFilter = input.folderId;
      const folderId =
        folderFilter && folderFilter !== 'null' && folderFilter !== 'notnull'
          ? folderFilter
          : undefined;
    
      const items = await this.bw.withSession(async (session) => {
        const baseArgs: string[] = ['list', 'items'];
        if (input.url) baseArgs.push('--url', input.url);
        if (folderId) baseArgs.push('--folderid', folderId);
        if (input.collectionId)
          baseArgs.push('--collectionid', input.collectionId);
        if (orgId) baseArgs.push('--organizationid', orgId);
        if (input.trash) baseArgs.push('--trash');
    
        // NOTE: bw's `--search` does not treat "a | b" as "a OR b". If callers pass
        // a pipe-delimited string (common when combining name + username), we split
        // and union the results.
        const terms = tokens.length ? tokens : [undefined];
        const byId = new Map<string, unknown>();
    
        for (const term of terms) {
          const args = [...baseArgs];
          if (term) args.push('--search', term);
          const { stdout } = await this.bw.runForSession(session, args, {
            timeoutMs: 120_000,
          });
          const results = this.parseBwJson<unknown[]>(stdout);
          for (const raw of results) {
            if (!raw || typeof raw !== 'object') continue;
            const id = (raw as { id?: unknown }).id;
            if (typeof id === 'string' && id.length > 0) byId.set(id, raw);
          }
        }
    
        return [...byId.values()];
      });
    
      const orgFiltered = items.filter((raw) => {
        if (!raw || typeof raw !== 'object') return false;
        const item = raw as AnyRecord;
    
        if (orgFilter === 'null') {
          return item.organizationId == null;
        }
        if (orgFilter === 'notnull') {
          return typeof item.organizationId === 'string' && item.organizationId;
        }
        return true;
      });
    
      const folderFiltered = orgFiltered.filter((raw) => {
        if (!raw || typeof raw !== 'object') return false;
        const item = raw as AnyRecord;
    
        if (folderFilter === 'null') {
          return item.folderId == null;
        }
        if (folderFilter === 'notnull') {
          return typeof item.folderId === 'string' && item.folderId;
        }
        return true;
      });
    
      const filtered = folderFiltered.filter((raw) => {
        if (!raw || typeof raw !== 'object') return false;
        const item = raw as AnyRecord;
        if (!input.type) return true;
        if (input.type === 'ssh_key') return isSshKeyItem(item);
        if (input.type === 'login') return item.type === ITEM_TYPE.login;
        if (input.type === 'card') return item.type === ITEM_TYPE.card;
        if (input.type === 'identity') return item.type === ITEM_TYPE.identity;
        if (input.type === 'note')
          return item.type === ITEM_TYPE.note && !isSshKeyItem(item);
        return true;
      });
    
      return typeof limit === 'number' ? filtered.slice(0, limit) : filtered;
    }

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