Skip to main content
Glama

search_in_document

Find specific text within Dynalist documents, returning matching nodes with optional parent context and children for comprehensive search results.

Instructions

Search for text in a Dynalist document. Returns matching nodes with optional parent context and children. WARNING: Many matches with parents/children can return many words.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
urlNoDynalist URL
file_idNoDocument ID (alternative to URL)
queryYesText to search for (case-insensitive)
search_notesNoAlso search in notes
parent_levelsNoHow many parent levels to include (0 = none, 1 = direct parent, 2+ = ancestors)
include_childrenNoInclude direct children (level 1) of each match
bypass_warningNoONLY use after receiving a size warning. Do NOT set true on first request.

Implementation Reference

  • Main handler function: parses input, fetches document, filters nodes matching query in content or notes, adds optional parent/children context, checks result size with warning, returns JSON array of matches.
    async ({ url, file_id, query, search_notes, parent_levels, include_children, bypass_warning }) => {
      let documentId = file_id;
    
      if (url) {
        const parsed = parseDynalistUrl(url);
        documentId = parsed.documentId;
      }
    
      if (!documentId) {
        return {
          content: [{ type: "text", text: "Error: Either 'url' or 'file_id' must be provided" }],
          isError: true,
        };
      }
    
      const doc = await client.readDocument(documentId);
      const nodeMap = buildNodeMap(doc.nodes);
      const queryLower = query.toLowerCase();
    
      const matches = doc.nodes
        .filter((node) => {
          const contentMatch = node.content?.toLowerCase().includes(queryLower);
          const noteMatch = search_notes && node.note?.toLowerCase().includes(queryLower);
          return contentMatch || noteMatch;
        })
        .map((node) => {
          const result: {
            id: string;
            content: string;
            note?: string;
            url: string;
            parents?: { id: string; content: string }[];
            children?: { id: string; content: string }[];
          } = {
            id: node.id,
            content: node.content,
            note: node.note || undefined,
            url: buildDynalistUrl(documentId!, node.id),
          };
    
          // Add parents if requested
          if (parent_levels > 0) {
            const parents = getAncestors(doc.nodes, node.id, parent_levels);
            if (parents.length > 0) {
              result.parents = parents;
            }
          }
    
          // Add children if requested
          if (include_children && node.children && node.children.length > 0) {
            result.children = node.children
              .map(childId => {
                const childNode = nodeMap.get(childId);
                return childNode ? { id: childNode.id, content: childNode.content } : null;
              })
              .filter((c): c is { id: string; content: string } => c !== null);
          }
    
          return result;
        });
    
      const resultText = matches.length > 0
        ? JSON.stringify(matches, null, 2)
        : `No matches found for "${query}"`;
    
      // Check content size
      const sizeCheck = checkContentSize(resultText, bypass_warning || false, [
        "Use a more specific query to reduce matches",
        "Use parent_levels: 0 to exclude parent context",
        "Use include_children: false to exclude children",
      ]);
    
      if (sizeCheck) {
        return {
          content: [{ type: "text", text: sizeCheck.warning }],
        };
      }
    
      return {
        content: [
          {
            type: "text",
            text: resultText,
          },
        ],
      };
    }
  • Zod input schema defining parameters for the search_in_document tool.
    {
      url: z.string().optional().describe("Dynalist URL"),
      file_id: z.string().optional().describe("Document ID (alternative to URL)"),
      query: z.string().describe("Text to search for (case-insensitive)"),
      search_notes: z.boolean().optional().default(true).describe("Also search in notes"),
      parent_levels: z.number().optional().default(1).describe("How many parent levels to include (0 = none, 1 = direct parent, 2+ = ancestors)"),
      include_children: z.boolean().optional().default(false).describe("Include direct children (level 1) of each match"),
      bypass_warning: z.boolean().optional().default(false).describe("ONLY use after receiving a size warning. Do NOT set true on first request."),
    },
  • MCP server.tool registration call for search_in_document, including name, description, schema, and handler function.
    server.tool(
      "search_in_document",
      "Search for text in a Dynalist document. Returns matching nodes with optional parent context and children. WARNING: Many matches with parents/children can return many words.",
      {
        url: z.string().optional().describe("Dynalist URL"),
        file_id: z.string().optional().describe("Document ID (alternative to URL)"),
        query: z.string().describe("Text to search for (case-insensitive)"),
        search_notes: z.boolean().optional().default(true).describe("Also search in notes"),
        parent_levels: z.number().optional().default(1).describe("How many parent levels to include (0 = none, 1 = direct parent, 2+ = ancestors)"),
        include_children: z.boolean().optional().default(false).describe("Include direct children (level 1) of each match"),
        bypass_warning: z.boolean().optional().default(false).describe("ONLY use after receiving a size warning. Do NOT set true on first request."),
      },
      async ({ url, file_id, query, search_notes, parent_levels, include_children, bypass_warning }) => {
        let documentId = file_id;
    
        if (url) {
          const parsed = parseDynalistUrl(url);
          documentId = parsed.documentId;
        }
    
        if (!documentId) {
          return {
            content: [{ type: "text", text: "Error: Either 'url' or 'file_id' must be provided" }],
            isError: true,
          };
        }
    
        const doc = await client.readDocument(documentId);
        const nodeMap = buildNodeMap(doc.nodes);
        const queryLower = query.toLowerCase();
    
        const matches = doc.nodes
          .filter((node) => {
            const contentMatch = node.content?.toLowerCase().includes(queryLower);
            const noteMatch = search_notes && node.note?.toLowerCase().includes(queryLower);
            return contentMatch || noteMatch;
          })
          .map((node) => {
            const result: {
              id: string;
              content: string;
              note?: string;
              url: string;
              parents?: { id: string; content: string }[];
              children?: { id: string; content: string }[];
            } = {
              id: node.id,
              content: node.content,
              note: node.note || undefined,
              url: buildDynalistUrl(documentId!, node.id),
            };
    
            // Add parents if requested
            if (parent_levels > 0) {
              const parents = getAncestors(doc.nodes, node.id, parent_levels);
              if (parents.length > 0) {
                result.parents = parents;
              }
            }
    
            // Add children if requested
            if (include_children && node.children && node.children.length > 0) {
              result.children = node.children
                .map(childId => {
                  const childNode = nodeMap.get(childId);
                  return childNode ? { id: childNode.id, content: childNode.content } : null;
                })
                .filter((c): c is { id: string; content: string } => c !== null);
            }
    
            return result;
          });
    
        const resultText = matches.length > 0
          ? JSON.stringify(matches, null, 2)
          : `No matches found for "${query}"`;
    
        // Check content size
        const sizeCheck = checkContentSize(resultText, bypass_warning || false, [
          "Use a more specific query to reduce matches",
          "Use parent_levels: 0 to exclude parent context",
          "Use include_children: false to exclude children",
        ]);
    
        if (sizeCheck) {
          return {
            content: [{ type: "text", text: sizeCheck.warning }],
          };
        }
    
        return {
          content: [
            {
              type: "text",
              text: resultText,
            },
          ],
        };
      }
    );
  • Helper function to retrieve ancestor (parent) nodes for providing context around search matches.
    function getAncestors(
      nodes: import("../dynalist-client.js").DynalistNode[],
      nodeId: string,
      levels: number
    ): { id: string; content: string }[] {
      if (levels <= 0) return [];
    
      const ancestors: { id: string; content: string }[] = [];
      let currentId = nodeId;
    
      for (let i = 0; i < levels; i++) {
        const parentInfo = findNodeParent(nodes, currentId);
        if (!parentInfo) break; // Reached root or node not found
    
        const parentNode = nodes.find(n => n.id === parentInfo.parentId);
        if (!parentNode) break;
    
        ancestors.push({ id: parentNode.id, content: parentNode.content });
        currentId = parentNode.id;
      }
    
      return ancestors;
    }
  • Helper to check result size and issue warnings or block large outputs, used before returning search results.
    function checkContentSize(
      content: string,
      bypassWarning: boolean,
      recommendations: string[]
    ): { warning: string; canBypass: boolean } | null {
      const tokenCount = estimateTokens(content);
    
      // If bypass was used preemptively (result is small), warn against this practice
      if (bypassWarning && tokenCount <= 5000) {
        return {
          warning: `⚠️ INCORRECT USAGE: You used bypass_warning: true preemptively.\n\n` +
            `The bypass_warning option should ONLY be used AFTER receiving a size warning, ` +
            `not on the first request. Please repeat the request WITHOUT bypass_warning to get the result.\n\n` +
            `This ensures you're aware of large results before they fill your context.`,
          canBypass: false,
        };
      }
    
      if (tokenCount <= 5000 || bypassWarning) {
        return null; // OK to return content
      }
    
      const canBypass = tokenCount <= 24500;
    
      let warning = `⚠️ LARGE RESULT WARNING\n`;
      warning += `This query would return ~${tokenCount.toLocaleString()} tokens which may fill your context.\n\n`;
      warning += `Recommendations:\n`;
      for (const rec of recommendations) {
        warning += `- ${rec}\n`;
      }
    
      if (canBypass) {
        warning += `\nTo receive the full result anyway (~${tokenCount.toLocaleString()} tokens), repeat the SAME request with bypass_warning: true`;
      } else {
        warning += `\n❌ Result too large (>${(24500).toLocaleString()} tokens). Please reduce the scope using the recommendations above.`;
      }
    
      return { warning, canBypass };
    }
Behavior3/5

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

With no annotations provided, the description carries the full burden. It discloses that the search is case-insensitive (implied from schema), returns matching nodes with optional context, and warns about potential large outputs. However, it doesn't mention authentication requirements, rate limits, error conditions, or what happens when no matches are found. The WARNING about size is helpful but incomplete.

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 appropriately concise with two sentences. The first sentence states the core functionality, and the second provides an important operational warning. No wasted words, though it could be slightly more structured by separating purpose from behavioral notes.

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?

For a search tool with 7 parameters, no annotations, and no output schema, the description is minimally adequate. It covers the basic purpose and one important behavioral warning, but lacks information about output format, error handling, authentication, and comparison with sibling tools. The 100% schema coverage helps, but the description itself is incomplete for a complex tool.

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?

Schema description coverage is 100%, so the schema already documents all 7 parameters thoroughly. The description adds minimal value beyond what's in the schema - it mentions 'optional parent context and children' which corresponds to 'parent_levels' and 'include_children' parameters, but doesn't provide additional semantic context. Baseline 3 is appropriate when schema does the heavy lifting.

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 tool's purpose: 'Search for text in a Dynalist document. Returns matching nodes with optional parent context and children.' It specifies the verb ('search'), resource ('Dynalist document'), and output characteristics. However, it doesn't explicitly differentiate from sibling 'search_documents' tool, which appears to search across documents rather than within one.

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

Usage Guidelines2/5

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

The description provides no guidance on when to use this tool versus alternatives like 'search_documents' or other document manipulation tools. The WARNING about many matches is operational rather than usage guidance. There's no mention of prerequisites, typical use cases, or comparison with sibling tools.

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/cristip73/dynalist-mcp'

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