Skip to main content
Glama

Manage Monica documents and photos

monica_manage_media

Manage documents and photos in Monica CRM by listing, viewing, uploading, or deleting media files for contacts.

Instructions

List, inspect, upload, or delete documents and photos stored in Monica. Use mediaType to pick the resource.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
mediaTypeYes
actionYes
mediaIdNo
contactIdNo
limitNo
pageNo
filePathNo
base64DataNo
fileNameNo
mimeTypeNo

Implementation Reference

  • Registers the 'monica_manage_media' tool with server.registerTool, providing title, description, input schema, and a dispatching handler that routes to document or photo handlers based on mediaType.
    export function registerMediaTools(context: ToolRegistrationContext): void {
      const { server, client, logger } = context;
    
      server.registerTool(
        'monica_manage_media',
        {
          title: 'Manage Monica documents and photos',
          description:
            'List, inspect, upload, or delete documents and photos stored in Monica. Use mediaType to pick the resource.',
          inputSchema: mediaInputShape
        },
        async (rawInput: unknown) => {
          const input = mediaInputSchema.parse(rawInput);
    
          if (input.mediaType === 'document') {
            return handleDocument({ input: input as DocumentInput, client, logger });
          }
    
          return handlePhoto({ input: input as PhotoInput, client, logger });
        }
      );
    }
  • Defines the input schema using Zod (mediaInputShape and mediaInputSchema) with validation rules for mediaType, action, and other parameters specific to Monica media management.
    const mediaInputShape = {
        mediaType: z.enum(['document', 'photo']),
        action: z.enum(['list', 'get', 'upload', 'delete']),
        mediaId: z.number().int().positive().optional(),
        contactId: z.number().int().positive().optional(),
        limit: z.number().int().min(1).max(100).optional(),
        page: z.number().int().min(1).optional(),
        filePath: z.string().min(1).optional(),
        base64Data: z.string().min(1).optional(),
        fileName: z.string().min(1).optional(),
        mimeType: z.string().min(1).optional()
      } as const;
    
    const mediaInputSchema = z
      .object(mediaInputShape)
      .superRefine((input, ctx) => {
        if (['get', 'delete'].includes(input.action) && input.mediaId == null) {
          ctx.addIssue({
            code: z.ZodIssueCode.custom,
            message: 'mediaId is required for get and delete actions.',
            path: ['mediaId']
          });
        }
    
        if (input.action === 'upload') {
          if (!input.filePath && !input.base64Data) {
            ctx.addIssue({
              code: z.ZodIssueCode.custom,
              message: 'Provide either filePath or base64Data when uploading media.',
              path: ['filePath']
            });
          }
    
          if (input.filePath && input.base64Data) {
            ctx.addIssue({
              code: z.ZodIssueCode.custom,
              message: 'Choose either filePath or base64Data, not both.',
              path: ['filePath']
            });
          }
    
          if (!input.filePath && !input.fileName) {
            ctx.addIssue({
              code: z.ZodIssueCode.custom,
              message: 'fileName is required when providing base64Data directly.',
              path: ['fileName']
            });
          }
        }
    
      });
    
    type MediaInput = z.infer<typeof mediaInputSchema>;
    type DocumentInput = MediaInput & { mediaType: 'document' };
  • Handler for document operations (list, get, upload, delete) using Monica client API, with response building and logging.
    async function handleDocument({ input, client, logger }: HandlerArgs<DocumentInput>) {
      switch (input.action) {
        case 'list': {
          const response = await client.listDocuments({
            contactId: input.contactId,
            limit: input.limit,
            page: input.page
          });
          const documents = response.data.map(normalizeDocument);
    
          return buildListResponse({
            mediaType: input.mediaType,
            action: input.action,
            contactId: input.contactId,
            records: documents,
            pagination: response.meta
          });
        }
    
        case 'get': {
          if (input.mediaId == null) {
            return missingMediaIdError('document', input.action);
          }
    
          const response = await client.getDocument(input.mediaId);
          const document = normalizeDocument(response.data);
    
          return buildSingleRecordResponse({
            mediaType: input.mediaType,
            action: input.action,
            mediaId: input.mediaId,
            record: document,
            text: `Document ${document.originalFilename} (ID ${document.id}).`
          });
        }
    
        case 'upload': {
          const upload = await prepareUploadPayload(input);
          const response = await client.uploadDocument({
            base64Data: upload.base64Data,
            fileName: upload.fileName,
            mimeType: upload.mimeType,
            contactId: input.contactId
          });
          const document = normalizeDocument(response.data);
          logger.info({ documentId: document.id, contactId: input.contactId }, 'Uploaded Monica document');
    
          return buildSingleRecordResponse({
            mediaType: input.mediaType,
            action: input.action,
            record: document,
            text: `Uploaded document ${document.originalFilename} (ID ${document.id}).`
          });
        }
    
        case 'delete': {
          if (input.mediaId == null) {
            return missingMediaIdError('document', input.action);
          }
    
          await client.deleteDocument(input.mediaId);
          logger.info({ documentId: input.mediaId }, 'Deleted Monica document');
    
          return {
            content: [
              {
                type: 'text' as const,
                text: `Deleted document ID ${input.mediaId}.`
              }
            ],
            structuredContent: {
              mediaType: input.mediaType,
              action: input.action,
              mediaId: input.mediaId,
              deleted: true
            }
          };
        }
    
        default:
          return unknownActionError(input.action);
      }
    }
  • Handler for photo operations (list, get, upload, delete) using Monica client API, with response building and logging.
    async function handlePhoto({ input, client, logger }: HandlerArgs<PhotoInput>) {
      switch (input.action) {
        case 'list': {
          const response = await client.listPhotos({
            contactId: input.contactId,
            limit: input.limit,
            page: input.page
          });
          const photos = response.data.map(normalizePhoto);
    
          return buildListResponse({
            mediaType: input.mediaType,
            action: input.action,
            contactId: input.contactId,
            records: photos,
            pagination: response.meta
          });
        }
    
        case 'get': {
          if (input.mediaId == null) {
            return missingMediaIdError('photo', input.action);
          }
    
          const response = await client.getPhoto(input.mediaId);
          const photo = normalizePhoto(response.data);
    
          return buildSingleRecordResponse({
            mediaType: input.mediaType,
            action: input.action,
            mediaId: input.mediaId,
            record: photo,
            text: `Photo ${photo.originalFilename} (ID ${photo.id}).`
          });
        }
    
        case 'upload': {
          const upload = await prepareUploadPayload(input);
          const response = await client.uploadPhoto({
            base64Data: upload.base64Data,
            fileName: upload.fileName,
            mimeType: upload.mimeType,
            contactId: input.contactId
          });
          const photo = normalizePhoto(response.data);
          logger.info({ photoId: photo.id, contactId: input.contactId }, 'Uploaded Monica photo');
    
          return buildSingleRecordResponse({
            mediaType: input.mediaType,
            action: input.action,
            record: photo,
            text: `Uploaded photo ${photo.originalFilename} (ID ${photo.id}).`
          });
        }
    
        case 'delete': {
          if (input.mediaId == null) {
            return missingMediaIdError('photo', input.action);
          }
    
          await client.deletePhoto(input.mediaId);
          logger.info({ photoId: input.mediaId }, 'Deleted Monica photo');
    
          return {
            content: [
              {
                type: 'text' as const,
                text: `Deleted photo ID ${input.mediaId}.`
              }
            ],
            structuredContent: {
              mediaType: input.mediaType,
              action: input.action,
              mediaId: input.mediaId,
              deleted: true
            }
          };
        }
    
        default:
          return unknownActionError(input.action);
      }
    }
  • Helper function to prepare upload payload by reading file or using base64, resolving MIME types.
    async function prepareUploadPayload(input: MediaInput) {
      if (!input.filePath && !input.base64Data) {
        throw new Error('Provide either filePath or base64Data for uploads.');
      }
    
      if (input.filePath) {
        const resolvedPath = resolveFilePath(input.filePath);
    
        try {
          const fileBuffer = await readFile(resolvedPath);
          const fileName = input.fileName ? basename(input.fileName) : basename(resolvedPath);
          const mimeType = resolveMimeType(fileName, input.mimeType);
    
          return {
            base64Data: fileBuffer.toString('base64'),
            fileName,
            mimeType
          };
        } catch (error) {
          throw new Error(`Unable to read file at ${resolvedPath}: ${(error as Error).message}`);
        }
      }
    
      if (!input.base64Data || !input.fileName) {
        throw new Error('Provide base64Data and fileName when not using filePath.');
      }
    
      const fileName = basename(input.fileName);
      return {
        base64Data: input.base64Data,
        fileName,
        mimeType: resolveMimeType(fileName, input.mimeType)
      };
    }
Behavior2/5

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

With no annotations provided, the description carries full burden for behavioral disclosure. It lists actions (list, inspect, upload, delete) but fails to describe critical traits: permissions required for upload/delete, rate limits, whether deletions are permanent, response formats, or error handling. For a multi-action tool with mutation capabilities, this leaves significant gaps.

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 appropriately concise with two sentences that front-load the core actions. Every word earns its place, though it could be more structured by separating actions from guidance. No wasted verbiage.

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

Completeness2/5

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

Given the tool's complexity (10 parameters, multiple actions including mutations), lack of annotations, and no output schema, the description is incomplete. It doesn't cover behavioral aspects, parameter dependencies, or expected outputs, leaving the agent poorly equipped to use this tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate but adds minimal value. It mentions mediaType to 'pick the resource' but doesn't explain the action parameter's role, when mediaId/contactId are needed, or how filePath/base64Data/fileName/mimeType interact for uploads. With 10 parameters and no schema descriptions, this is inadequate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose with specific verbs (list, inspect, upload, delete) and resources (documents and photos stored in Monica). It distinguishes this tool from siblings like monica_list_contacts or monica_manage_note by focusing on media management. However, it doesn't explicitly differentiate from all siblings beyond the resource scope.

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

Usage Guidelines2/5

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

The description provides minimal guidance with 'Use mediaType to pick the resource,' but offers no explicit when-to-use criteria, no exclusions, and no alternatives. It doesn't explain when to choose this tool over other monica_manage_* tools or how it relates to siblings like monica_manage_contact (which might handle contact-specific media).

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/Jacob-Stokes/monica-mcp'

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