Skip to main content
Glama
stevereiner
by stevereiner

cancel_checkout

Cancel document checkout in Alfresco to discard working copies and restore the original version.

Instructions

Cancel checkout of a document, discarding any working copy.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
node_idYes

Implementation Reference

  • FastMCP tool registration and handler wrapper for 'cancel_checkout'. Registers the tool with the MCP server using @mcp.tool decorator and delegates execution to the core implementation.
    @mcp.tool
    async def cancel_checkout(
        node_id: str,
        ctx: Context = None
    ) -> str:
        """Cancel checkout of a document, discarding any working copy."""
        return await cancel_checkout_impl(node_id, ctx)
  • Main handler implementation for cancel_checkout tool. Performs Alfresco API call to cancel checkout/unlock, handles errors, cleans up local tracking files, and formats output with progress reporting.
    async def cancel_checkout_impl(
        node_id: str,
        ctx: Context = None
    ) -> str:
        """Cancel checkout of a document, discarding any working copy.
        
        Args:
            node_id: Original node ID that was checked out
            ctx: MCP context for progress reporting
        
        Returns:
            Cancellation confirmation and cleanup status
        """
        if ctx:
            await ctx.info(f"Cancelling checkout for: {node_id}")
            await ctx.report_progress(0.1)
        
        if not node_id.strip():
            return safe_format_output("❌ Error: node_id is required")
        
        try:
            logger.info(f"Starting cancel checkout: node {node_id}")
            core_client = await get_core_client()
            
            # Clean the node ID
            clean_node_id = node_id.strip()
            if clean_node_id.startswith('alfresco://'):
                clean_node_id = clean_node_id.split('/')[-1]
            
            if ctx:
                await ctx.info("Checking node status...")
                await ctx.report_progress(0.3)
            
            # Get node information to validate using high-level core client
            node_response = core_client.nodes.get(node_id=clean_node_id)
            
            if not hasattr(node_response, 'entry'):
                return f"ERROR: Failed to get node information for: {clean_node_id}"
            
            node_info = node_response.entry
            filename = getattr(node_info, 'name', f"document_{clean_node_id}")
            
            if ctx:
                await ctx.info(">> Performing Alfresco unlock using high-level client...")
                await ctx.report_progress(0.5)
            
            # Use high-level core client unlock method
            try:
                logger.info(f"Attempting to unlock document: {clean_node_id}")
                unlock_response = core_client.versions.cancel_checkout(node_id=clean_node_id)
                if unlock_response and hasattr(unlock_response, 'entry'):
                    api_status = "✅ Document unlocked in Alfresco"
                else:
                    api_status = "✅ Document unlocked in Alfresco"
                logger.info(f"Document unlocked successfully: {clean_node_id}")
            except Exception as unlock_error:
                error_str = str(unlock_error)
                if "404" in error_str:
                    # Document might not be locked
                    api_status = "ℹ️ Document was not locked in Alfresco"
                    logger.info(f"Document was not locked: {clean_node_id}")
                elif "405" in error_str:
                    # Server doesn't support lock/unlock APIs
                    api_status = "WARNING: Server doesn't support lock/unlock APIs (treating as unlocked)"
                    logger.warning(f"Server doesn't support unlock API for {clean_node_id}")
                else:
                    api_status = f"WARNING: Alfresco unlock failed: {error_str}"
                    logger.error(f"Failed to unlock document {clean_node_id}: {error_str}")
            
            if ctx:
                await ctx.info("Cleaning up local files...")
                await ctx.report_progress(0.7)
            
            # Clean up local checkout tracking
            downloads_dir = pathlib.Path.home() / "Downloads"
            checkout_dir = downloads_dir / "checkout"
            checkout_manifest_path = checkout_dir / ".checkout_manifest.json"
            
            checkout_data = {}
            cleanup_status = [api_status]
            
            if checkout_manifest_path.exists():
                try:
                    with open(checkout_manifest_path, 'r') as f:
                        checkout_data = json.load(f)
                except:
                    checkout_data = {}
            
            # Check if this node is tracked in local checkouts
            if 'checkouts' in checkout_data and clean_node_id in checkout_data['checkouts']:
                checkout_info = checkout_data['checkouts'][clean_node_id]
                checkout_filename = checkout_info['local_file']
                checkout_file_path = checkout_dir / checkout_filename
                
                # Remove local checkout file
                try:
                    if checkout_file_path.exists():
                        checkout_file_path.unlink()
                        cleanup_status.append("🗑️ Local checkout file removed")
                        logger.info(f"Removed local checkout file: {checkout_file_path}")
                    else:
                        cleanup_status.append("ℹ️ Local checkout file already removed")
                except Exception as e:
                    cleanup_status.append(f"WARNING: Could not remove local file: {e}")
                    logger.warning(f"Failed to remove local file {checkout_file_path}: {e}")
                
                # Remove from tracking
                del checkout_data['checkouts'][clean_node_id]
                
                # Update manifest
                try:
                    with open(checkout_manifest_path, 'w') as f:
                        json.dump(checkout_data, f, indent=2)
                    cleanup_status.append(">> Checkout tracking updated")
                except Exception as e:
                    cleanup_status.append(f"WARNING: Could not update tracking: {e}")
            else:
                cleanup_status.append("ℹ️ No local checkout tracking found")
            
            if ctx:
                await ctx.info("Document unlocked!")
                await ctx.report_progress(1.0)
            
            # Clean JSON-friendly formatting (no markdown syntax)
            result = f"🔓 Document Unlocked\n\n"
            result += f">> Document: {filename}\n"
            result += f"ID: Node ID: {clean_node_id}\n"
            result += f"🕒 Unlocked: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
            result += f"🧹 Cleanup Status:\n"
            
            for status in cleanup_status:
                result += f"   {status}\n"
            
            result += f"\nINFO: Note: Document is now available for others to edit."
            result += f"\nWARNING: Important: Any unsaved changes in the local file have been discarded."
            
            return safe_format_output(result)
            
        except Exception as e:
            error_msg = f"❌ Cancel checkout failed: {str(e)}"
            if ctx:
                await ctx.error(error_msg)
            logger.error(f"Cancel checkout failed: {e}")
            return safe_format_output(error_msg) 

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/stevereiner/python-alfresco-mcp-server'

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