Skip to main content
Glama

lookup_record

Search DEVONthink records by attributes like filename, path, URL, tags, comment, or content hash to locate specific documents and information.

Instructions

Look up records in DEVONthink by a specific attribute.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
lookupTypeYesThe attribute to look up by
valueYesThe value to match against the specified attribute
tagsNoAdditional tags to match (only used when lookupType is 'tags')
matchAnyTagNoWhen true, match records that have ANY of the specified tags; when false (default), match records that have ALL tags
databaseNameNoLimit lookup to this database; uses all open databases if omitted
limitNoMaximum number of results to return

Implementation Reference

  • The 'run' function handles the logic for the 'lookup_record' tool, executing a JXA script to interact with DEVONthink and process the search based on the provided attributes.
      run: async (args, executor) => {
        const { lookupType, value, tags, matchAnyTag, databaseName, limit } = args;
    
        // Merge value into tags list when lookupType is 'tags'
        const tagList = lookupType === "tags" ? [value, ...(tags ?? [])] : (tags ?? []);
    
        const script = `
          ${JXA_APP}
          var lookupType = ${jxaLiteral(lookupType)};
          var value = ${jxaLiteral(value)};
          var tagList = ${jxaLiteral(tagList)};
          var matchAnyTag = ${jxaLiteral(matchAnyTag ?? false)};
          var dbName = ${jxaLiteral(databaseName ?? null)};
          var limit = ${jxaLiteral(limit ?? null)};
    
          ${JXA_RESOLVE_DB}
    
          var results = [];
    
          if (lookupType === "filename") {
            // Search by name across the scoped database or all databases
            var searchQuery = "name:" + value;
            var searchOpts = {};
            if (db) searchOpts["in"] = db.root();
            results = app.search(searchQuery, searchOpts);
            // Further filter to exact filename matches
            results = results.filter(function(r) {
              return r.name() === value;
            });
    
          } else if (lookupType === "path") {
            // Look up by filesystem path
            if (db) {
              var rec = app.getRecordAt(value, {in: db});
              if (rec && rec.uuid()) results = [rec];
            } else {
              // Try each open database
              var dbs = app.databases();
              for (var i = 0; i < dbs.length; i++) {
                try {
                  var r = app.getRecordAt(value, {in: dbs[i]});
                  if (r && r.uuid()) { results.push(r); break; }
                } catch(e) {}
              }
            }
    
          } else if (lookupType === "url") {
            // Search by URL
            var urlResults = app.lookupRecordsWithURL(value);
            if (urlResults) results = urlResults;
    
          } else if (lookupType === "tags") {
            // Look up by tags using DEVONthink's tag lookup
            if (matchAnyTag) {
              // Union: results that match any of the tags
              var seen = {};
              for (var t = 0; t < tagList.length; t++) {
                var tagResults = app.lookupRecordsWithTags([tagList[t]]);
                if (tagResults) {
                  for (var j = 0; j < tagResults.length; j++) {
                    var u = tagResults[j].uuid();
                    if (!seen[u]) {
                      seen[u] = true;
                      results.push(tagResults[j]);
                    }
                  }
                }
              }
            } else {
              // Intersection: results that match all of the tags
              results = app.lookupRecordsWithTags(tagList) || [];
            }
    
          } else if (lookupType === "comment") {
            // Search by comment using DEVONthink search
            var commentQuery = "comment:" + value;
            var commentOpts = {};
            if (db) commentOpts["in"] = db.root();
            results = app.search(commentQuery, commentOpts);
            // Filter to actual comment matches
            results = results.filter(function(r) {
              var c = r.comment();
              return c && c.indexOf(value) !== -1;
            });
    
          } else if (lookupType === "contentHash") {
            // Look up by content hash (MD5/SHA)
            var hashResults = app.lookupRecordsWithContentHash(value);
            if (hashResults) results = hashResults;
          }
    
          // Filter by database if specified and not already scoped
          if (dbName && lookupType !== "filename" && lookupType !== "path" && lookupType !== "comment") {
            results = results.filter(function(r) {
              try { return r.database().name() === dbName; } catch(e) { return false; }
            });
          }
    
          // Apply limit
          if (limit && limit > 0 && results.length > limit) {
            results = results.slice(0, limit);
          }
    
          var summaries = results.map(function(record) {
            return ${JXA_RECORD_SUMMARY};
          });
    
          JSON.stringify(summaries);
        `;
    
        const result = executor.run(script);
        return JSON.parse(result.stdout);
      },
    });
  • The 'schema' property defines the input parameters for the 'lookup_record' tool, including 'lookupType', 'value', 'tags', 'matchAnyTag', 'databaseName', and 'limit', using Zod for validation.
    schema: z.object({
      lookupType: z
        .enum(LOOKUP_TYPE_VALUES)
        .describe("The attribute to look up by"),
      value: z
        .string()
        .describe("The value to match against the specified attribute"),
      tags: z
        .array(z.string())
        .optional()
        .describe("Additional tags to match (only used when lookupType is 'tags')"),
      matchAnyTag: z
        .boolean()
        .optional()
        .describe(
          "When true, match records that have ANY of the specified tags; " +
            "when false (default), match records that have ALL tags"
        ),
      databaseName: z
        .string()
        .optional()
        .describe("Limit lookup to this database; uses all open databases if omitted"),
      limit: z
        .number()
        .int()
        .positive()
        .optional()
        .describe("Maximum number of results to return"),
    }),
  • The 'lookupRecordTool' object is created using 'defineTool', registering the tool with the name 'lookup_record'.
    export const lookupRecordTool = defineTool({
      name: "lookup_record",
      description: "Look up records in DEVONthink by a specific attribute.",
      schema: z.object({
        lookupType: z
          .enum(LOOKUP_TYPE_VALUES)
          .describe("The attribute to look up by"),
        value: z
          .string()
          .describe("The value to match against the specified attribute"),
        tags: z
          .array(z.string())
          .optional()
          .describe("Additional tags to match (only used when lookupType is 'tags')"),
        matchAnyTag: z
          .boolean()
          .optional()
          .describe(
            "When true, match records that have ANY of the specified tags; " +
              "when false (default), match records that have ALL tags"
          ),
        databaseName: z
          .string()
          .optional()
          .describe("Limit lookup to this database; uses all open databases if omitted"),
        limit: z
          .number()
          .int()
          .positive()
          .optional()
          .describe("Maximum number of results to return"),
      }),
      run: async (args, executor) => {
        const { lookupType, value, tags, matchAnyTag, databaseName, limit } = args;
    
        // Merge value into tags list when lookupType is 'tags'
        const tagList = lookupType === "tags" ? [value, ...(tags ?? [])] : (tags ?? []);
    
        const script = `
          ${JXA_APP}
          var lookupType = ${jxaLiteral(lookupType)};
          var value = ${jxaLiteral(value)};
          var tagList = ${jxaLiteral(tagList)};
          var matchAnyTag = ${jxaLiteral(matchAnyTag ?? false)};
          var dbName = ${jxaLiteral(databaseName ?? null)};
          var limit = ${jxaLiteral(limit ?? null)};
    
          ${JXA_RESOLVE_DB}
    
          var results = [];
    
          if (lookupType === "filename") {
            // Search by name across the scoped database or all databases
            var searchQuery = "name:" + value;
            var searchOpts = {};
            if (db) searchOpts["in"] = db.root();
            results = app.search(searchQuery, searchOpts);
            // Further filter to exact filename matches
            results = results.filter(function(r) {
              return r.name() === value;
            });
    
          } else if (lookupType === "path") {
            // Look up by filesystem path
            if (db) {
              var rec = app.getRecordAt(value, {in: db});
              if (rec && rec.uuid()) results = [rec];
            } else {
              // Try each open database
              var dbs = app.databases();
              for (var i = 0; i < dbs.length; i++) {
                try {
                  var r = app.getRecordAt(value, {in: dbs[i]});
                  if (r && r.uuid()) { results.push(r); break; }
                } catch(e) {}
              }
            }
    
          } else if (lookupType === "url") {
            // Search by URL
            var urlResults = app.lookupRecordsWithURL(value);
            if (urlResults) results = urlResults;
    
          } else if (lookupType === "tags") {
            // Look up by tags using DEVONthink's tag lookup
            if (matchAnyTag) {
              // Union: results that match any of the tags
              var seen = {};
              for (var t = 0; t < tagList.length; t++) {
                var tagResults = app.lookupRecordsWithTags([tagList[t]]);
                if (tagResults) {
                  for (var j = 0; j < tagResults.length; j++) {
                    var u = tagResults[j].uuid();
                    if (!seen[u]) {
                      seen[u] = true;
                      results.push(tagResults[j]);
                    }
                  }
                }
              }
            } else {
              // Intersection: results that match all of the tags
              results = app.lookupRecordsWithTags(tagList) || [];
            }
    
          } else if (lookupType === "comment") {
            // Search by comment using DEVONthink search
            var commentQuery = "comment:" + value;
            var commentOpts = {};
            if (db) commentOpts["in"] = db.root();
            results = app.search(commentQuery, commentOpts);
            // Filter to actual comment matches
            results = results.filter(function(r) {
              var c = r.comment();
              return c && c.indexOf(value) !== -1;
            });
    
          } else if (lookupType === "contentHash") {
            // Look up by content hash (MD5/SHA)
            var hashResults = app.lookupRecordsWithContentHash(value);
            if (hashResults) results = hashResults;
          }
    
          // Filter by database if specified and not already scoped
          if (dbName && lookupType !== "filename" && lookupType !== "path" && lookupType !== "comment") {
            results = results.filter(function(r) {
              try { return r.database().name() === dbName; } catch(e) { return false; }
            });
          }
    
          // Apply limit
          if (limit && limit > 0 && results.length > limit) {
            results = results.slice(0, limit);
          }
    
          var summaries = results.map(function(record) {
            return ${JXA_RECORD_SUMMARY};
          });
    
          JSON.stringify(summaries);
        `;
    
        const result = executor.run(script);
        return JSON.parse(result.stdout);
      },
    });
Behavior2/5

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

No annotations are provided, so the description must carry the full burden of behavioral disclosure. It fails to state whether the operation is read-only, what happens when no matches are found, or the format/structure of returned results. 'Look up' implies safety but does not explicitly guarantee non-destructive behavior.

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

Conciseness4/5

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

The description is a single, efficient sentence with no redundant words. However, given the tool's complexity (6 parameters, no output schema, no annotations), it is arguably undersized rather than optimally concise, lacking necessary behavioral context.

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

Completeness3/5

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

While the comprehensive input schema (100% coverage) reduces the description's burden for parameter explanation, the absence of annotations and output schema leaves significant gaps regarding return values, error handling, and safety guarantees that the description fails to address. Minimum viable for the complexity level.

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

Parameters3/5

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

With 100% schema description coverage, the input parameters are fully documented in the schema itself. The description adds minimal semantic value beyond the schema, merely referencing 'specific attribute' which maps to the lookupType enum. Baseline score applies.

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

Purpose4/5

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

The description clearly states the verb (look up), resource (records in DEVONthink), and method (by a specific attribute). However, it does not explicitly differentiate from sibling tools like 'search' or 'get_record_by_identifier', which could cause selection ambiguity.

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

Usage Guidelines3/5

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

The phrase 'by a specific attribute' provides implied usage context, suggesting use when searching via known metadata fields (filename, path, etc.). However, it lacks explicit guidance on when to prefer this over 'search' or 'get_record_by_identifier', and states no prerequisites or exclusions.

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

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