Skip to main content
Glama

search_tasks

Find tasks using full-text search with relevance ranking. Apply filters like dartboard, status, and exclusion terms to narrow results.

Instructions

Full-text search across tasks with relevance ranking. Alternative to list_tasks for text-based discovery. Supports quoted phrases, exclusions (-term), and inline filters (dartboard:Name, status:Done, assignee:Name).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryYesSearch query. Supports quoted phrases ("exact match"), exclusions (-term), inline filters (dartboard:Name, status:Done), and regular terms.
dartboardNoOptional dartboard filter (dart_id or name)
include_completedNoInclude completed tasks in results (default: false)
limitNoMax results to return (default: 50, max: 500)

Implementation Reference

  • Main handler for the search_tasks tool. Validates input, parses the query string, fetches tasks from the Dart API with optional filters, performs client-side relevance scoring, sorts by score, applies limit, and returns results with progressive detail levels.
    export async function handleSearchTasks(input: SearchTasksInput): Promise<SearchTasksOutput> {
      // Defensive input handling
      const safeInput = input || {};
    
      // Validate required fields
      if (!safeInput.query || typeof safeInput.query !== 'string') {
        throw new ValidationError('query is required and must be a string', 'query');
      }
    
      const query = safeInput.query.trim();
      if (query.length === 0) {
        throw new ValidationError('query cannot be empty or whitespace-only', 'query');
      }
    
      const DART_TOKEN = process.env.DART_TOKEN;
      if (!DART_TOKEN) {
        throw new DartAPIError(
          'DART_TOKEN environment variable is required. Get your token from: https://app.dartai.com/?settings=account',
          401
        );
      }
    
      // Initialize Dart API client
      const client = new DartClient({ token: DART_TOKEN });
    
      // Parse query into structured search terms
      const queryParsed = parseQuery(query);
    
      // Validate that we have at least some search terms (not just exclusions and filters)
      if (queryParsed.terms.length === 0 && queryParsed.phrases.length === 0) {
        // If only filters were provided (e.g. "dartboard:X"), that's a list operation not search
        if (Object.keys(queryParsed.filters).length > 0) {
          throw new ValidationError(
            'query must contain search terms in addition to filters. Use list_tasks for filter-only queries.',
            'query'
          );
        }
        throw new ValidationError(
          'query must contain at least one search term or phrase (not just exclusions)',
          'query'
        );
      }
    
      // Validate limit
      const limit = validateLimit(safeInput.limit);
    
      // Resolve dartboard: explicit parameter takes precedence over inline filter
      let dartboardId: string | undefined;
      const dartboardName = safeInput.dartboard || queryParsed.filters.dartboard;
      if (dartboardName) {
        dartboardId = await resolveDartboard(dartboardName, client);
      }
    
      // Resolve other inline filters
      const statusFilter = queryParsed.filters.status;
      const assigneeFilter = queryParsed.filters.assignee;
    
      // Client-side search: fetch tasks matching filters, then score locally
      const searchMethod = 'client_side';
    
      // Fetch tasks for client-side search
      const tasks = await fetchAllTasks(client, dartboardId, safeInput.include_completed, statusFilter, assigneeFilter);
    
      // Perform client-side search with relevance scoring
      const searchResults = performClientSideSearch(tasks, queryParsed);
    
      // Sort by relevance descending
      searchResults.sort((a, b) => b.relevance_score - a.relevance_score);
    
      // Apply limit
      const limitedResults = searchResults.slice(0, limit);
    
      // Apply progressive detail levels based on relevance
      const resultsWithDetail = applyProgressiveDetail(limitedResults);
    
      return {
        tasks: resultsWithDetail,
        total_results: searchResults.length,
        query_parsed: queryParsed,
        search_method: searchMethod,
      };
    }
  • Parses the search query into structured terms, phrases, exclusions, and inline filters. Supports quoted phrases, -term exclusions, and key:value filters for dartboard, status, assignee, priority.
    function parseQuery(query: string): QueryParsed {
      const phrases: string[] = [];
      const exclusions: string[] = [];
      const terms: string[] = [];
      const filters: Record<string, string> = {};
    
      let remaining = query;
      let match: RegExpExecArray | null;
    
      // Extract inline filters first (key:value where key is a known filter)
      // Supports quoted values: dartboard:"My Board" or dartboard:'My Board'
      // and unquoted values: dartboard:Personal/agnt
      const filterRegex = /(\w+):(?:"([^"]+)"|'([^']+)'|(\S+))/g;
      while ((match = filterRegex.exec(remaining)) !== null) {
        const key = match[1].toLowerCase();
        const value = match[2] || match[3] || match[4];
        if (INLINE_FILTER_KEYS.has(key)) {
          filters[key] = value;
          remaining = remaining.replace(match[0], ' ');
        }
      }
    
      // Extract quoted phrases (both " and ')
      const phraseRegex = /["']([^"']+)["']/g;
      while ((match = phraseRegex.exec(remaining)) !== null) {
        phrases.push(match[1].toLowerCase());
        remaining = remaining.replace(match[0], ' ');
      }
    
      // Extract exclusions (words starting with -)
      const exclusionRegex = /-(\w+)/g;
      while ((match = exclusionRegex.exec(remaining)) !== null) {
        exclusions.push(match[1].toLowerCase());
        remaining = remaining.replace(match[0], ' ');
      }
    
      // Extract remaining terms (split by whitespace, filter empty)
      const remainingTerms = remaining
        .split(/\s+/)
        .map(t => t.trim().toLowerCase())
        .filter(t => t.length > 0);
    
      terms.push(...remainingTerms);
    
      return { terms, phrases, exclusions, filters };
    }
  • Validates the limit parameter. Default is 50, max is 500, must be a positive integer.
    function validateLimit(limit?: number): number {
      if (limit === undefined || limit === null) {
        return 50; // Default limit
      }
    
      if (typeof limit !== 'number' || !Number.isInteger(limit)) {
        throw new ValidationError('limit must be an integer', 'limit');
      }
    
      if (limit < 1) {
        throw new ValidationError('limit must be at least 1', 'limit');
      }
    
      if (limit > 500) {
        throw new ValidationError('limit must not exceed 500 (max allowed)', 'limit');
      }
    
      return limit;
    }
  • Fetches all tasks from the Dart API with pagination. Caps at 2000 tasks without a dartboard filter, 10000 with one. Optionally filters out completed tasks.
    async function fetchAllTasks(
      client: DartClient,
      dartboardId?: string,
      includeCompleted?: boolean,
      status?: string,
      assignee?: string,
    ): Promise<DartTask[]> {
      const allTasks: DartTask[] = [];
      const fetchLimit = 100; // Smaller pages to avoid API 500 errors on large responses
      const maxTotalTasks = dartboardId ? 10000 : 2000; // Tighter limit without dartboard filter
      let offset = 0;
      let hasMore = true;
    
      while (hasMore && allTasks.length < maxTotalTasks) {
        const response = await client.listTasks({
          dartboard: dartboardId,
          status,
          assignee,
          limit: fetchLimit,
          offset,
        });
    
        allTasks.push(...response.tasks);
    
        hasMore = offset + fetchLimit < response.total;
        offset += fetchLimit;
      }
    
      if (!includeCompleted) {
        return allTasks.filter(task => !task.completed_at);
      }
    
      return allTasks;
    }
  • src/index.ts:776-802 (registration)
    Registration of the search_tasks tool in the MCP server's tool list, defining its name, description, and input schema with required query field.
    // Search
    {
      name: 'search_tasks',
      description: 'Full-text search across tasks with relevance ranking. Alternative to list_tasks for text-based discovery. Supports quoted phrases, exclusions (-term), and inline filters (dartboard:Name, status:Done, assignee:Name).',
      inputSchema: {
        type: 'object',
        properties: {
          query: {
            type: 'string',
            description: 'Search query. Supports quoted phrases ("exact match"), exclusions (-term), inline filters (dartboard:Name, status:Done), and regular terms.',
          },
          dartboard: {
            type: 'string',
            description: 'Optional dartboard filter (dart_id or name)',
          },
          include_completed: {
            type: 'boolean',
            description: 'Include completed tasks in results (default: false)',
          },
          limit: {
            type: 'integer',
            description: 'Max results to return (default: 50, max: 500)',
          },
        },
        required: ['query'],
      },
    },
Behavior5/5

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

No annotations provided, so description carries full burden. It discloses relevance ranking, support for quoted phrases, exclusions, and inline filters, giving agents a clear understanding of search capabilities and syntax.

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 concise sentences that are front-loaded with the core purpose and usage guidance. Every word adds value with no redundancy.

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

Completeness5/5

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

Given no output schema and four parameters, the description sufficiently explains the tool's features (relevance ranking, syntax) and its relationship to list_tasks, making it complete for agent use.

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 covers 100% of parameters. Description adds value by explaining that the query parameter supports specific syntax (quotes, exclusions, inline filters), going beyond the schema's general descriptions.

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 'Full-text search across tasks with relevance ranking', specifying the verb (search), resource (tasks), and distinguishing it from list_tasks as an alternative for text-based discovery.

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

Usage Guidelines4/5

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

Explicitly positions the tool as an alternative to list_tasks for text-based discovery, giving clear context on when to use it. Lacks explicit 'when not to use' but the sibling differentiation suffices.

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/standardbeagle/dart-query'

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