Skip to main content
Glama

search

Find documents and records in DEVONthink databases using flexible search parameters including query terms, group scoping, record type filters, and comparison modes.

Instructions

Search DEVONthink records. Examples: {"query": "invoice"} or {"query": "project review", "groupPath": "/Meetings", "databaseName": "MyDB"}. Note: groupPath requires databaseName and must be database-relative (e.g., "/Meetings" not "/MyDB/Meetings").

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryYesSearch query (wildcards * and ? are supported)
groupUuidNoUUID of the group to search within
groupIdNoNumeric ID of the group to search within (requires databaseName)
groupPathNoDatabase-relative path of the group to search within (requires databaseName)
databaseNameNoDatabase name (required for groupId or groupPath scoping)
useCurrentGroupNoScope search to the currently selected group in DEVONthink
recordTypeNoFilter results to a specific record type
comparisonNoComparison mode for the search
excludeSubgroupsNoExclude records from subgroups when scoping to a group
limitNoMaximum number of results to return

Implementation Reference

  • The search tool is defined and implemented here, including its input schema (zod validation) and the run handler which executes JXA script for DEVONthink.
    export const searchTool = defineTool({
      name: "search",
      description:
        'Search DEVONthink records. Examples: {"query": "invoice"} or ' +
        '{"query": "project review", "groupPath": "/Meetings", "databaseName": "MyDB"}. ' +
        "Note: groupPath requires databaseName and must be database-relative " +
        '(e.g., "/Meetings" not "/MyDB/Meetings").',
      schema: z.object({
        query: z.string().describe("Search query (wildcards * and ? are supported)"),
        groupUuid: z
          .string()
          .optional()
          .describe("UUID of the group to search within"),
        groupId: z
          .number()
          .int()
          .nonnegative()
          .optional()
          .describe("Numeric ID of the group to search within (requires databaseName)"),
        groupPath: z
          .string()
          .optional()
          .describe(
            "Database-relative path of the group to search within (requires databaseName)"
          ),
        databaseName: z
          .string()
          .optional()
          .describe("Database name (required for groupId or groupPath scoping)"),
        useCurrentGroup: z
          .boolean()
          .optional()
          .describe("Scope search to the currently selected group in DEVONthink"),
        recordType: z
          .enum(RECORD_TYPE_VALUES)
          .optional()
          .describe("Filter results to a specific record type"),
        comparison: z
          .enum(COMPARISON_VALUES)
          .optional()
          .describe("Comparison mode for the search"),
        excludeSubgroups: z
          .boolean()
          .optional()
          .describe("Exclude records from subgroups when scoping to a group"),
        limit: z
          .number()
          .int()
          .positive()
          .optional()
          .describe("Maximum number of results to return"),
      }),
      run: async (args, executor) => {
        const {
          query,
          groupUuid,
          groupId,
          groupPath,
          databaseName,
          useCurrentGroup,
          recordType,
          comparison,
          excludeSubgroups,
          limit,
        } = args;
    
        const script = `
          ${JXA_APP}
          var query = ${jxaLiteral(query)};
          var groupUuid = ${jxaLiteral(groupUuid ?? null)};
          var groupId = ${jxaLiteral(groupId ?? null)};
          var groupPath = ${jxaLiteral(groupPath ?? null)};
          var dbName = ${jxaLiteral(databaseName ?? null)};
          var useCurrentGroup = ${jxaLiteral(useCurrentGroup ?? false)};
          var recordType = ${jxaLiteral(recordType ?? null)};
          var comparison = ${jxaLiteral(comparison ?? null)};
          var excludeSubgroups = ${jxaLiteral(excludeSubgroups ?? false)};
          var limit = ${jxaLiteral(limit ?? null)};
    
          ${JXA_RESOLVE_DB}
    
          // Resolve the search scope group if specified
          var searchIn = null;
          if (groupUuid) {
            searchIn = app.getRecordWithUuid(groupUuid);
            if (!searchIn || !searchIn.uuid()) throw new Error("Group not found for UUID: " + groupUuid);
          } else if (typeof groupId === "number" && groupId >= 0) {
            if (!db) throw new Error("databaseName is required when using groupId");
            searchIn = db.getRecordAt(groupId);
            if (!searchIn || !searchIn.uuid()) throw new Error("Group not found for ID: " + groupId);
          } else if (groupPath) {
            if (!db) throw new Error("databaseName is required when using groupPath");
            searchIn = app.getRecordAt(groupPath, {in: db});
            if (!searchIn || !searchIn.uuid()) throw new Error("Group not found at path: " + groupPath);
          } else if (useCurrentGroup) {
            searchIn = app.currentGroup();
          }
    
          // Build search options
          var searchOpts = {};
          if (searchIn) searchOpts["in"] = searchIn;
          else if (db) searchOpts["in"] = db.root();
          if (comparison) searchOpts["comparison"] = comparison;
          if (excludeSubgroups) searchOpts["excludeSubgroups"] = true;
    
          // Execute search
          var results = app.search(query, searchOpts);
    
          // Filter by record type if specified
          if (recordType) {
            results = results.filter(function(r) { return r.type() === recordType; });
          }
    
          // Apply limit
          if (limit && limit > 0 && results.length > limit) {
            results = results.slice(0, limit);
          }
    
          // Map to summaries
          var summaries = results.map(function(record) {
            return ${JXA_RECORD_SUMMARY};
          });
    
          JSON.stringify(summaries);
        `;
    
        const result = executor.run(script);
        return JSON.parse(result.stdout);
      },
    });

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/mnott/Devon'

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