Skip to main content
Glama
notes.ts6.38 kB
/** * Notes operation handlers for tool execution * * Handles notes operations including retrieving and creating notes for records */ import { CallToolRequest } from '@modelcontextprotocol/sdk/types.js'; import { createErrorResult } from '../../../../utils/error-handler.js'; import { parseResourceUri } from '../../../../utils/uri-parser.js'; import { ResourceType } from '../../../../types/attio.js'; import { NotesToolConfig, CreateNoteToolConfig } from '../../../tool-types.js'; import { formatResponse } from '../../formatters.js'; import { hasResponseData } from '../../error-types.js'; /** * Handle notes operations */ export async function handleNotesOperation( request: CallToolRequest, toolConfig: NotesToolConfig, resourceType: ResourceType ) { const directId = resourceType === ResourceType.COMPANIES ? (request.params.arguments?.companyId as string) : resourceType === ResourceType.DEALS ? (request.params.arguments?.dealId as string) : (request.params.arguments?.personId as string); const uri = request.params.arguments?.uri as string; if (!directId && !uri) { const idParamName = resourceType === ResourceType.COMPANIES ? 'companyId' : resourceType === ResourceType.DEALS ? 'dealId' : 'personId'; return createErrorResult( new Error(`Either ${idParamName} or uri parameter is required`), `/${resourceType}/notes`, 'GET', { status: 400, message: 'Missing required parameter' } ); } let notesTargetId = directId; try { if (uri) { try { const [, uriId] = parseResourceUri(uri); notesTargetId = uriId; } catch (error: unknown) { return createErrorResult( error instanceof Error ? error : new Error('Invalid URI format'), uri, 'GET', { status: 400, message: 'Invalid URI format' } ); } } const limit = request.params.arguments?.limit as number; const offset = request.params.arguments?.offset as number; const notes = await toolConfig.handler(notesTargetId, limit, offset); const formattedResult = toolConfig.formatResult!(notes); return formatResponse(formattedResult); } catch (error: unknown) { return createErrorResult( error instanceof Error ? error : new Error('Unknown error'), uri || `/${resourceType}/${notesTargetId}/notes`, 'GET', hasResponseData(error) ? error.response.data : {} ); } } /** * Handle createNote operations */ export async function handleCreateNoteOperation( request: CallToolRequest, toolConfig: CreateNoteToolConfig, resourceType: ResourceType ) { const directId = resourceType === ResourceType.COMPANIES ? (request.params.arguments?.companyId as string) : resourceType === ResourceType.DEALS ? (request.params.arguments?.dealId as string) : (request.params.arguments?.personId as string); const uri = request.params.arguments?.uri as string; /** * Parameter Mapping Strategy for Note Creation * * This function supports multiple parameter names for backward compatibility * and to accommodate different API clients: * * - title: Primary parameter name (preferred) * - noteTitle: Legacy/alternative parameter name for title * - content: Primary parameter name (preferred) * - noteText: Legacy/alternative parameter name for content * * The fallback pattern (primary || legacy) ensures compatibility while * encouraging use of the standardized parameter names. */ const title = (request.params.arguments?.title || request.params.arguments?.noteTitle) as string; const content = (request.params.arguments?.content || request.params.arguments?.noteText) as string; if (!title || !content) { return createErrorResult( new Error('Both title and content are required'), `/${resourceType}/notes`, 'POST', { status: 400, message: 'Missing required parameters' } ); } if (!directId && !uri) { const idParamName = resourceType === ResourceType.COMPANIES ? 'companyId' : resourceType === ResourceType.DEALS ? 'dealId' : 'personId'; return createErrorResult( new Error(`Either ${idParamName} or uri parameter is required`), `/${resourceType}/notes`, 'POST', { status: 400, message: 'Missing required parameter' } ); } let noteTargetId = directId; try { if (uri) { try { const [, uriId] = parseResourceUri(uri); noteTargetId = uriId; } catch (error: unknown) { return createErrorResult( error instanceof Error ? error : new Error('Invalid URI format'), uri, 'POST', { status: 400, message: 'Invalid URI format' } ); } } // Inject mock data when E2E_MODE is true if (process.env.E2E_MODE === 'true') { // Simulate error for invalid IDs in E2E mode if (noteTargetId.startsWith('invalid-')) { return createErrorResult( new Error(`Record not found: ${noteTargetId}`), `/${resourceType}/${noteTargetId}/notes`, 'POST', { status: 404, message: 'Record not found' } ); } const mockNote = { id: `mock-note-${Date.now()}-${Math.random().toString(36).substr(2, 4)}`, parent_object: resourceType, parent_record_id: noteTargetId, title: title, content_markdown: content, content_plaintext: content, content: content, // Added for Error 2 fix format: 'plaintext', created_at: new Date().toISOString(), tags: [], }; return mockNote; // Return the raw mockNote object } const note = await toolConfig.handler(noteTargetId, title, content); const formattedResult = toolConfig.formatResult ? toolConfig.formatResult(note) : `Note added to ${resourceType.slice(0, -1)} ${noteTargetId}: ${ note.title || 'Untitled' }`; return formatResponse(formattedResult); } catch (error: unknown) { return createErrorResult( error instanceof Error ? error : new Error('Unknown error'), uri || `/${resourceType}/${noteTargetId}/notes`, 'POST', hasResponseData(error) ? error.response.data : {} ); } }

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