Skip to main content
Glama

search

Search across files with full-text keyword queries. Get ranked matches including excerpts and tags for instant snippet previews.

Instructions

Full-text (SQLite FTS5) keyword search across files. Returns ranked matches with inline match_excerpt and title_highlight (no follow-up read_file needed for snippets) plus tags, est_tokens, size_bytes, and aggregate total_est_tokens. Read-only; no side effects, auth, or rate limits. FTS is tokenised: it WILL miss URLs, hyphenated terms, and partial substrings — fall back to regex_search for those. project_id: null searches only the KB; omit the field to span everything; tags[] requires ALL listed tags to match. For prompt-ready bundled bodies use bundle_search.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryYesSearch query
project_idNoFilter by project ID. Pass null to search ONLY Knowledge Base files.
tagsNoFilter by tags (all must match)
favoriteNoFilter by favorite status

Implementation Reference

  • MCP server tool registration for 'search' — defines the tool name, description, Zod schema for inputs (query, project_id, tags, favorite), and handler that calls the core search function.
    server.tool(
      "search",
      "Full-text (SQLite FTS5) keyword search across files. Returns ranked matches with inline match_excerpt and title_highlight (no follow-up `read_file` needed for snippets) plus tags, est_tokens, size_bytes, and aggregate `total_est_tokens`. Read-only; no side effects, auth, or rate limits. FTS is tokenised: it WILL miss URLs, hyphenated terms, and partial substrings — fall back to `regex_search` for those. `project_id: null` searches only the KB; omit the field to span everything; `tags[]` requires ALL listed tags to match. For prompt-ready bundled bodies use `bundle_search`.",
      {
        query: z.string().describe("Search query"),
        project_id: z.number().nullable().optional().describe("Filter by project ID. Pass null to search ONLY Knowledge Base files."),
        tags: z.array(z.string()).optional().describe("Filter by tags (all must match)"),
        favorite: z.boolean().optional().describe("Filter by favorite status"),
      },
      async ({ query, project_id, tags, favorite }) => {
        const filters: any = { query };
        if (project_id !== undefined) filters.project_id = project_id;
        if (tags !== undefined) filters.tags = tags;
        if (favorite !== undefined) filters.favorite = favorite;
    
        const result = search(filters);
        const annotated = attachTags(result.map(annotateTokens));
        const total_est_tokens = annotated.reduce((s, f) => s + (f.est_tokens ?? 0), 0);
        return {
          content: [{ type: "text", text: JSON.stringify({ matches: annotated, total_est_tokens }, null, 2) }],
        };
      }
    );
  • Core 'search' function — executes FTS5 full-text query against SQLite with optional filters for project_id, tags, and favorite. Returns ranked file records with match excerpts and title highlights.
    export function search(filters: SearchFilters): FileRecordWithRank[] {
      const db = getDatabase();
    
      // snippet(fts_index, 1, ...) targets the `content` column (column 1; title
      // is column 0). Markers are agent-parsable triple-bracket pairs that are
      // unlikely to collide with real markdown content.
      let sql = `
        SELECT files.*,
               fts_index.rank,
               snippet(fts_index, 1, '<<<', '>>>', '…', 16) AS match_excerpt,
               highlight(fts_index, 0, '<<<', '>>>') AS title_highlight
        FROM fts_index
        JOIN files ON files.id = fts_index.rowid
        WHERE fts_index MATCH ?
      `;
      const params: any[] = [filters.query];
    
      if (filters.project_id !== undefined) {
        sql += " AND files.project_id = ?";
        params.push(filters.project_id);
      }
    
      if (filters.favorite !== undefined && filters.favorite) {
        sql += " AND EXISTS (SELECT 1 FROM favorites WHERE favorites.file_id = files.id)";
      }
    
      if (filters.tags !== undefined && filters.tags.length > 0) {
        // All tags must be present (AND condition)
        for (const tag of filters.tags) {
          sql += ` AND EXISTS (
            SELECT 1 FROM file_tags
            JOIN tags ON tags.id = file_tags.tag_id
            WHERE file_tags.file_id = files.id AND tags.name = ?
          )`;
          params.push(tag);
        }
      }
    
      sql += " ORDER BY rank LIMIT 50";
    
      const stmt = db.prepare(sql);
      return stmt.all(...params) as FileRecordWithRank[];
    }
  • SearchFilters interface defining the schema for the search function's filters: query (string), project_id (optional number), tags (optional string array), favorite (optional boolean).
    export interface SearchFilters {
      query: string;
      project_id?: number;
      tags?: string[];
      favorite?: boolean;
    }
  • Core package exports — re-exports the 'search' function from the metadata module.
    export {
      addTags, removeTags, setFavorite, search,
      registerProject, unregisterProject, discoverFiles, listTags, listProjects,
  • bundleSearch function — calls the core 'search' function (line 104) then bundles matched file contents into a prompt-ready format.
    export async function bundleSearch(
      filters: SearchFilters,
      opts: BundleOptions = {}
    ): Promise<BundleResult> {
      const format: BundleFormat = opts.format ?? "xml";
      const max_tokens = opts.max_tokens ?? 50000;
    
      const hits = search(filters);
    
      const included: BundleIncludedItem[] = [];
      const skipped: BundleSkippedItem[] = [];
      const docs: DocFields[] = [];
Behavior5/5

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

No annotations are provided, so the description carries full burden. It explicitly states the tool is read-only with no side effects, auth, or rate limits. It also warns about FTS tokenization limitations that cause missed matches for certain patterns.

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 concise (3-4 sentences) and front-loaded with purpose and key features. It covers usage, limitations, and return fields without redundancy. However, it could be slightly more structured (e.g., bullet points) but overall efficient.

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 the complexity of full-text search and the lack of an output schema, the description comprehensively explains what the tool returns (match_excerpt, title_highlight, tags, est_tokens, size_bytes, total_est_tokens) and its limitations. It also guides the user to alternatives for specific use cases.

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 coverage is 100%, providing baseline 3. The description adds extra meaning beyond the schema: it clarifies that project_id:null restricts to KB only, omitting the field searches everything, and tags require all listed tags to match. This adds valuable context.

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 the tool performs full-text (SQLite FTS5) keyword search across files, returning ranked matches with specific fields. It distinguishes from siblings like regex_search and bundle_search by naming them and explaining when to use each.

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

Usage Guidelines5/5

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

The description explicitly states when to use this tool (full-text searches), when not to (URLs, hyphens, partial substrings), and provides fallback alternatives (regex_search, bundle_search). It also explains special parameter behaviors like project_id:null and tags requiring ALL matches.

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/safiyu/ctxnest'

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