Skip to main content
Glama
martinbowling

Clipboard to Supabase MCP Helper

upload_clipboard_image

Upload clipboard images to Supabase Storage and get shareable URLs automatically.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • Main handler function that captures the current clipboard image using platform-specific methods, saves it temporarily, uploads to Supabase storage with fallback upload methods, and returns the public URL. Handles errors and cleanup.
    export const uploadCurrentClipboardImage = asyncHandler(async (): Promise<string> => {
      const filename = path.join(TMP, `${uuid()}.png`);
      
      try {
        // Check if platform is supported
        if (!isPlatformSupported()) {
          return `Unsupported platform: ${getPlatformName()}`;
        }
        
        // Try to get image from clipboard using platform-specific implementation
        logger.debug('MCP request: Checking clipboard for image');
        const hasImage = await getImageFromClipboard(filename);
        
        if (!hasImage) {
          return 'No image in clipboard';
        }
        
        // Verify the file exists and has content
        try {
          const stats = statSync(filename);
          if (stats.size === 0) {
            logger.debug('Empty image file detected');
            return 'No valid image in clipboard';
          }
          
          // Log image size for debugging
          logger.debug(`Image size: ${Math.round(stats.size / 1024)}KB`);
        } catch (err) {
          logger.debug('Image file not found or inaccessible');
          return 'Failed to capture clipboard image';
        }
        
        logger.info('MCP request: Image found in clipboard, preparing to upload');
        
        // Read image file
        const data = await fs.readFile(filename);
        
        // Create unique path for Supabase
        const filePath = `clips/${path.basename(filename)}`;
        
        // Upload to Supabase and get URL
        const publicUrl = await uploadFileToSupabase(data, filePath);
        
        logger.info(`MCP request: Successfully uploaded ${filePath} → ${publicUrl}`);
        
        // Return URL (don't write to clipboard in this case)
        return publicUrl;
      } catch (error) {
        const errorMessage = `Error uploading clipboard image: ${error instanceof Error ? error.message : 'Unknown error'}`;
        logger.error(errorMessage);
        
        return `Error: ${error instanceof AppError ? error.message : 'Upload failed'}`;
      } finally {
        // Safely clean up temp file (ignores ENOENT errors)
        await safeRemove(filename);
      }
    });
  • src/server.ts:30-52 (registration)
    Registers the 'upload_clipboard_image' MCP tool with no input parameters. The handler calls uploadCurrentClipboardImage from daemon.ts and formats the response as MCP content block.
    server.tool(
      "upload_clipboard_image",
      {}, // empty object for no parameters
      async () => {
        try {
          const url = await uploadCurrentClipboardImage();
          logger.info(`MCP tool called: upload_clipboard_image → ${url}`);
          return {
            content: [
              { type: "text", text: url }
            ]
          };
        } catch (error) {
          const errorMessage = `Error uploading clipboard image: ${error instanceof Error ? error.message : 'Unknown error'}`;
          logger.error(errorMessage);
          return {
            content: [
              { type: "text", text: `Error: Failed to upload image` }
            ]
          };
        }
      }
    );
  • Registers the 'upload_clipboard_image' MCP tool (HTTP server variant) with no input parameters. Thin wrapper around uploadCurrentClipboardImage from daemon.ts.
    server.tool(
      "upload_clipboard_image",
      {}, // empty object for no parameters
      async () => {
        try {
          const url = await uploadCurrentClipboardImage();
          logger.info(`MCP tool called: upload_clipboard_image → ${url}`);
          return {
            content: [
              { type: "text", text: url }
            ]
          };
        } catch (error) {
          const errorMessage = `Error uploading clipboard image: ${error instanceof Error ? error.message : 'Unknown error'}`;
          logger.error(errorMessage);
          return {
            content: [
              { type: "text", text: `Error: Failed to upload image` }
            ]
          };
        }
      }
    );
  • Helper function that uploads image buffer to Supabase storage using multiple fallback methods (Buffer, ArrayBuffer, Uint8Array) to handle compatibility issues, returns public URL.
    async function uploadFileToSupabase(buffer: Buffer, filePath: string): Promise<string> {
      // Reject large images early
      if (buffer.byteLength > MAX_IMAGE_SIZE) {
        throw new AppError(`Image too large (${Math.round(buffer.byteLength / 1024 / 1024)}MB > 8MB)`, 'IMAGE_TOO_LARGE');
      }
    
      logger.debug(`Starting Supabase upload for ${filePath} (${buffer.byteLength} bytes)`);
      
      // Try different methods of uploading
      try {
        // First try: Use buffer directly
        logger.debug('Attempting upload with Buffer directly');
        const { error: error1 } = await supabase.storage
          .from(BUCKET)
          .upload(filePath, buffer, {
            contentType: 'image/png',
            upsert: true
          });
        
        if (!error1) {
          logger.debug('Upload succeeded with direct Buffer');
          const { data: urlData } = supabase.storage
            .from(BUCKET)
            .getPublicUrl(filePath);
          return urlData.publicUrl;
        }
        
        logger.debug(`First upload attempt failed: ${error1.message || 'Unknown error'}`);
        
        // Second try: Use buffer.buffer (ArrayBuffer)
        logger.debug('Attempting upload with buffer.buffer (ArrayBuffer)');
        const { error: error2 } = await supabase.storage
          .from(BUCKET)
          .upload(filePath, buffer.buffer, {
            contentType: 'image/png',
            upsert: true
          });
        
        if (!error2) {
          logger.debug('Upload succeeded with buffer.buffer');
          const { data: urlData } = supabase.storage
            .from(BUCKET)
            .getPublicUrl(filePath);
          return urlData.publicUrl;
        }
        
        logger.debug(`Second upload attempt failed: ${error2.message || 'Unknown error'}`);
        
        // Third try: Use Uint8Array
        logger.debug('Attempting upload with Uint8Array conversion');
        const uint8Array = new Uint8Array(buffer);
        const { error: error3 } = await supabase.storage
          .from(BUCKET)
          .upload(filePath, uint8Array, {
            contentType: 'image/png',
            upsert: true
          });
        
        if (!error3) {
          logger.debug('Upload succeeded with Uint8Array');
          const { data: urlData } = supabase.storage
            .from(BUCKET)
            .getPublicUrl(filePath);
          return urlData.publicUrl;
        }
        
        logger.debug(`Third upload attempt failed: ${error3.message || 'Unknown error'}`);
        
        // All attempts failed, throw detailed error
        throw new AppError(
          `Supabase upload failed after 3 attempts. Last error: ${error3.message || 'Unknown error'}`,
          'UPLOAD_ERROR'
        );
      } catch (err) {
        if (err instanceof AppError) {
          throw err;
        }
        
        // Catch and log any other errors with as much detail as possible
        const errorMsg = err instanceof Error ? err.message : String(err);
        logger.error(`Unexpected error during upload: ${errorMsg}`);
        logger.error(`Error details: ${JSON.stringify(err)}`);
        
        throw new AppError(`Supabase upload failed: ${errorMsg}`, 'UPLOAD_ERROR');
      }
    }
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/martinbowling/clipboard-to-supabase-mcp-helper'

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