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

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

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) 
Behavior2/5

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

No annotations are provided, so the description carries the full burden of behavioral disclosure. It mentions 'discarding any working copy,' which indicates a destructive action, but does not cover other critical aspects like permissions required, whether the action is reversible, error conditions, or response behavior. This is inadequate for a mutation tool with zero annotation coverage.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that front-loads the core action and effect without unnecessary words. Every part of the sentence contributes directly to understanding the tool's purpose, making it appropriately concise and well-structured.

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

Completeness3/5

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

Given that there is an output schema (which may cover return values), the description does not need to explain outputs. However, for a destructive tool with no annotations and minimal parameter guidance, the description is incomplete—it lacks details on behavioral implications, error handling, and usage context, which are crucial for safe and effective tool invocation.

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

Parameters3/5

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

The input schema has one parameter ('node_id') with 0% description coverage, and the tool description does not add any parameter-specific information. With a single parameter, the baseline is higher, but the description fails to explain what 'node_id' represents (e.g., document identifier) or its format, leaving semantics unclear beyond the schema's basic structure.

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

Purpose5/5

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

The description clearly states the action ('Cancel checkout') and the resource ('a document'), specifying the effect ('discarding any working copy'). It distinguishes from sibling tools like 'checkout_document' and 'checkin_document' by focusing on cancellation rather than initiation or completion of checkout.

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

Usage Guidelines3/5

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

The description implies usage when a checkout needs to be canceled, but does not explicitly state when to use this tool versus alternatives like 'checkin_document' or other document management tools. It lacks guidance on prerequisites (e.g., must have an active checkout) or exclusions, leaving usage context partially inferred.

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

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