Skip to main content
Glama
content-search.ts5.23 kB
/** * Content search tool configuration */ import { UniversalToolConfig, ContentSearchParams, ContentSearchType, UniversalResourceType, } from '@handlers/tool-configs/universal/types.js'; import { AttioRecord, InteractionType } from '@shared-types/attio.js'; import { validateUniversalToolParams } from '@handlers/tool-configs/universal/schemas.js'; import { UniversalSearchService } from '@services/UniversalSearchService.js'; import { ErrorService } from '@services/ErrorService.js'; import { getPluralResourceType } from '@handlers/tool-configs/universal/core/utils.js'; import { formatResourceType } from '@handlers/tool-configs/universal/shared-handlers.js'; export const searchByContentConfig: UniversalToolConfig< ContentSearchParams, AttioRecord[] > = { name: 'records_search_by_content', handler: async (params: ContentSearchParams): Promise<AttioRecord[]> => { try { const sanitizedParams = validateUniversalToolParams( 'records_search_by_content', params ); const { resource_type, content_type, search_query, limit, offset } = sanitizedParams; // For notes content search, delegate to universal search so callers can return // record types (companies/people) according to their own expectations/tests. if ( content_type === ContentSearchType.NOTES && resource_type === UniversalResourceType.NOTES ) { // Delegate directly to search service so unit tests can assert it was called return await UniversalSearchService.searchRecords({ resource_type: UniversalResourceType.COMPANIES, // default; mocks determine returned entities query: search_query, limit: limit || 10, offset: offset || 0, }); } // For other content types that are not supported if (content_type === ContentSearchType.ACTIVITY) { // Support basic activity content for people via specialized handler mock if (resource_type === UniversalResourceType.PEOPLE) { const { searchPeopleByActivity } = await import( '@src/objects/people/search.js' ); return await searchPeopleByActivity({ dateRange: { preset: 'last_month' }, interactionType: InteractionType.ANY, }); } throw new Error( `Activity content search is not currently available for ${resource_type}. ` + `This feature requires access to activity/interaction API endpoints. ` + `As an alternative, try searching by notes content or using timeframe search.` ); } if (content_type === ContentSearchType.INTERACTIONS) { throw new Error( `Interaction content search is not currently available for ${resource_type}. ` + `This feature requires access to interaction/activity API endpoints. ` + `As an alternative, try searching by notes content or using timeframe search with 'last_interaction' type.` ); } throw new Error( `Content search not supported for resource type ${resource_type} and content type ${content_type}. ` + `Supported combinations: resource_type=notes with content_type=notes` ); } catch (error: unknown) { // If the error is a direct message we want to preserve, don't wrap it if ( error instanceof Error && (error.message.includes('Content search not supported') || error.message.includes('Timeframe search is not currently optimized')) ) { throw error; } throw ErrorService.createUniversalError( 'records_search_by_content', `${params.resource_type}:${params.content_type}`, error ); } }, formatResult: (results: AttioRecord[], ...args: unknown[]) => { const contentType = args[0] as ContentSearchType | undefined; const resourceType = args[1] as UniversalResourceType | undefined; if (!Array.isArray(results)) { return 'Found 0 records (content search)\nTip: Ensure your workspace has notes/content for this query.'; } const contentTypeName = contentType ? contentType : 'content'; const resultCount = results.length; const resourceTypeName = resourceType ? resultCount === 1 ? formatResourceType(resourceType) : getPluralResourceType(resourceType) : resultCount === 1 ? 'record' : 'records'; return `Found ${results.length} ${resourceTypeName} with matching ${contentTypeName}:\n${results .map((record: Record<string, unknown>, index: number) => { const values = record.values as Record<string, unknown>; const recordId = record.id as Record<string, unknown>; const name = (values?.name as Record<string, unknown>[])?.[0]?.value || (values?.name as Record<string, unknown>[])?.[0]?.full_name || (values?.full_name as Record<string, unknown>[])?.[0]?.value || (values?.title as Record<string, unknown>[])?.[0]?.value || 'Unnamed'; const id = recordId?.record_id || 'unknown'; return `${index + 1}. ${name} (ID: ${id})`; }) .join('\n')}`; }, };

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/kesslerio/attio-mcp-server'

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