Skip to main content
Glama

search_portfolio

Search local portfolio elements by name, keywords, tags, or descriptions to quickly find personas, skills, templates, and other assets using metadata-based lookups.

Instructions

Search your local portfolio by content name, metadata, keywords, tags, or description. This searches your local elements using the portfolio index for fast metadata-based lookups.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryYesSearch query. Can match element names, keywords, tags, triggers, or descriptions. Examples: 'creative writer', 'debug', 'code review', 'research'.
typeNoLimit search to specific element type. If not specified, searches all types.
fuzzy_matchNoEnable fuzzy matching for approximate name matches. Defaults to true.
max_resultsNoMaximum number of results to return. Defaults to 20.
include_keywordsNoInclude keyword matching in search. Defaults to true.
include_tagsNoInclude tag matching in search. Defaults to true.
include_triggersNoInclude trigger word matching in search (for personas). Defaults to true.
include_descriptionsNoInclude description text matching in search. Defaults to true.

Implementation Reference

  • Registers the 'search_portfolio' MCP tool with full input schema definition and handler that delegates to the internal server.searchPortfolio method.
    { tool: { name: "search_portfolio", description: "Search your local portfolio by content name, metadata, keywords, tags, or description. This searches your local elements using the portfolio index for fast metadata-based lookups.", inputSchema: { type: "object", properties: { query: { type: "string", description: "Search query. Can match element names, keywords, tags, triggers, or descriptions. Examples: 'creative writer', 'debug', 'code review', 'research'.", }, type: { type: "string", enum: ["personas", "skills", "templates", "agents", "memories", "ensembles"], description: "Limit search to specific element type. If not specified, searches all types.", }, fuzzy_match: { type: "boolean", description: "Enable fuzzy matching for approximate name matches. Defaults to true.", }, max_results: { type: "number", description: "Maximum number of results to return. Defaults to 20.", }, include_keywords: { type: "boolean", description: "Include keyword matching in search. Defaults to true.", }, include_tags: { type: "boolean", description: "Include tag matching in search. Defaults to true.", }, include_triggers: { type: "boolean", description: "Include trigger word matching in search (for personas). Defaults to true.", }, include_descriptions: { type: "boolean", description: "Include description text matching in search. Defaults to true.", }, }, required: ["query"], }, }, handler: (args: SearchPortfolioArgs) => server.searchPortfolio({ query: args.query, elementType: args.type as any, fuzzyMatch: args.fuzzy_match, maxResults: args.max_results, includeKeywords: args.include_keywords, includeTags: args.include_tags, includeTriggers: args.include_triggers, includeDescriptions: args.include_descriptions }) },
  • TypeScript interface definition for the searchPortfolio method signature used by the tool handler.
    searchPortfolio(options: {query: string; elementType?: string; fuzzyMatch?: boolean; maxResults?: number; includeKeywords?: boolean; includeTags?: boolean; includeTriggers?: boolean; includeDescriptions?: boolean}): Promise<any>; searchAll(options: {query: string; sources?: string[]; elementType?: string; page?: number; pageSize?: number; sortBy?: string}): Promise<any>; // New unified config and sync handlers handleConfigOperation(options: any): Promise<any>; handleSyncOperation(options: any): Promise<any>; // Enhanced Index tools findSimilarElements(options: {elementName: string; elementType?: string; limit: number; threshold: number}): Promise<any>; getElementRelationships(options: {elementName: string; elementType?: string; relationshipTypes?: string[]}): Promise<any>; searchByVerb(options: {verb: string; limit: number}): Promise<any>; getRelationshipStats(): Promise<any>; }
  • Core search implementation in PortfolioIndexManager.search() that performs multi-field search over indexed portfolio elements, used by the tool handler.
    public async search(query: string, options: SearchOptions = {}): Promise<SearchResult[]> { const index = await this.getIndex(); // Normalize query for security const normalizedQuery = UnicodeValidator.normalize(query); if (!normalizedQuery.isValid) { logger.warn('Invalid Unicode in search query', { issues: normalizedQuery.detectedIssues }); return []; } const safeQuery = normalizedQuery.normalizedContent.toLowerCase().trim(); const queryTokens = safeQuery.split(/\s+/).filter(token => token.length > 0); if (queryTokens.length === 0) { return []; } const results: SearchResult[] = []; const seenPaths = new Set<string>(); const maxResults = options.maxResults || 20; // Helper to add unique results const addResult = (entry: IndexEntry, matchType: SearchResult['matchType'], score: number = 1) => { if (!seenPaths.has(entry.filePath) && results.length < maxResults) { // Filter by element type if specified if (options.elementType && entry.elementType !== options.elementType) { return; } seenPaths.add(entry.filePath); results.push({ entry, matchType, score }); } }; // 1. Search by name (highest priority) for (const [name, entry] of index.byName) { if (this.matchesQuery(name, queryTokens)) { addResult(entry, 'name', 3); } } // 2. Search by filename for (const [filename, entry] of index.byFilename) { if (this.matchesQuery(filename, queryTokens)) { addResult(entry, 'filename', 2.5); } } // 3. Search by keywords if (options.includeKeywords !== false) { for (const [keyword, entries] of index.byKeyword) { if (this.matchesQuery(keyword, queryTokens)) { for (const entry of entries) { addResult(entry, 'keyword', 2); } } } } // 4. Search by tags if (options.includeTags !== false) { for (const [tag, entries] of index.byTag) { if (this.matchesQuery(tag, queryTokens)) { for (const entry of entries) { addResult(entry, 'tag', 2); } } } } // 5. Search by triggers if (options.includeTriggers !== false) { for (const [trigger, entries] of index.byTrigger) { if (this.matchesQuery(trigger, queryTokens)) { for (const entry of entries) { addResult(entry, 'trigger', 1.8); } } } } // 6. Search by description if (options.includeDescriptions !== false) { for (const [_, entry] of index.byName) { if (entry.metadata.description && this.matchesQuery(entry.metadata.description.toLowerCase(), queryTokens)) { addResult(entry, 'description', 1.5); } } } // Sort by score (descending) results.sort((a, b) => b.score - a.score); logger.debug('Portfolio search completed', { query: safeQuery, resultCount: results.length, totalIndexed: index.byName.size }); return results; }
  • findByName helper for exact and fuzzy matching of element names by metadata or filename, supporting the search functionality.
    public async findByName(name: string, options: SearchOptions = {}): Promise<IndexEntry | null> { const index = await this.getIndex(); // Normalize input for security const normalizedName = UnicodeValidator.normalize(name); if (!normalizedName.isValid) { logger.warn('Invalid Unicode in search name', { issues: normalizedName.detectedIssues }); return null; } const safeName = normalizedName.normalizedContent; // Try exact match first (case insensitive) const exactMatch = index.byName.get(safeName.toLowerCase()); if (exactMatch) { logger.debug('Found exact name match', { name: safeName, filePath: exactMatch.filePath }); return exactMatch; } // Try filename match const filenameMatch = index.byFilename.get(safeName.toLowerCase()); if (filenameMatch) { logger.debug('Found filename match', { name: safeName, filePath: filenameMatch.filePath }); return filenameMatch; } // Try fuzzy matching if enabled if (options.fuzzyMatch !== false) { const fuzzyMatch = this.findFuzzyMatch(safeName, index, options); if (fuzzyMatch) { logger.debug('Found fuzzy match', { name: safeName, matchName: fuzzyMatch.metadata.name, filePath: fuzzyMatch.filePath }); return fuzzyMatch; } } logger.debug('No match found for name', { name: safeName }); return null; }

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/DollhouseMCP/DollhouseMCP'

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