Skip to main content
Glama
stevereiner
by stevereiner

checkin_document

Check in edited documents to Alfresco with version control, comments, and optional renaming using the Alfresco REST API.

Instructions

Check in a document after editing using Alfresco REST API.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
node_idYes
commentNo
major_versionNo
file_pathNo
new_nameNo

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • Core handler function executing the checkin_document tool: uploads edited file content, creates new version (major/minor), unlocks the document, cleans up local checkout tracking and files, with progress reporting.
    async def checkin_document_impl(
        node_id: str, 
        comment: str = "", 
        major_version: bool = False,
        file_path: str = "",
        new_name: str = "",
        ctx: Context = None
    ) -> str:
        """Check in a document after editing using Alfresco REST API.
        
        Args:
            node_id: Original node ID to check in (not working copy)
            comment: Check-in comment (default: empty)
            major_version: Whether to create a major version (default: False = minor version)
            file_path: Specific file path to upload (if empty, auto-detects from checkout folder)
            new_name: Optional new name for the file during checkin (default: keep original name)
            ctx: MCP context for progress reporting
        
        Returns:
            Check-in confirmation with version details and cleanup status
        """
        if ctx:
            await ctx.info(f"Checking in document: {node_id}")
            await ctx.info("Validating parameters...")
            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 checkin: 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("Finding checkout file...")
                await ctx.report_progress(0.2)
            
            # Find the file to upload
            checkout_file_path = None
            checkout_data = {}
            working_copy_id = None
            
            if file_path:
                # Use specific file path provided - handle quotes and path expansion
                cleaned_file_path = file_path.strip().strip('"').strip("'")
                
                # Handle macOS/Unix path expansion (~/Documents, etc.)
                if cleaned_file_path.startswith('~'):
                    cleaned_file_path = os.path.expanduser(cleaned_file_path)
                
                checkout_file_path = pathlib.Path(cleaned_file_path)
                if not checkout_file_path.exists():
                    return safe_format_output(f"❌ Specified file not found: {cleaned_file_path} (cleaned from: {file_path})")
                
                # Linux-specific: Check file permissions
                if not os.access(checkout_file_path, os.R_OK):
                    return safe_format_output(f"❌ File not readable (permission denied): {cleaned_file_path}")
            else:
                # Auto-detect from checkout folder
                downloads_dir = pathlib.Path.home() / "Downloads"
                checkout_dir = downloads_dir / "checkout"
                checkout_manifest_path = checkout_dir / ".checkout_manifest.json"
                
                if checkout_manifest_path.exists():
                    try:
                        with open(checkout_manifest_path, 'r') as f:
                            checkout_data = json.load(f)
                    except:
                        checkout_data = {}
                
                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']
                    locked_node_id = checkout_info.get('locked_node_id', clean_node_id)  # Updated for lock API
                    checkout_file_path = checkout_dir / checkout_filename
                    
                    if not checkout_file_path.exists():
                        return safe_format_output(f"❌ Checkout file not found: {checkout_file_path}. File may have been moved or deleted.")
                else:
                    return safe_format_output(f"❌ No locked document found for node {clean_node_id}. Use checkout_document first, or specify file_path manually.")
            
            if ctx:
                await ctx.info(f"Uploading file: {checkout_file_path.name}")
                await ctx.report_progress(0.4)
            
            # Read the file content
            with open(checkout_file_path, 'rb') as f:
                file_content = f.read()
            
            logger.info(f"Checkin file: {checkout_file_path.name} ({len(file_content)} bytes)")
            # Get original node info using high-level core client
            node_response = core_client.nodes.get(node_id=clean_node_id)
            if not hasattr(node_response, 'entry'):
                return safe_format_output(f"❌ Failed to get node information for: {clean_node_id}")
            
            node_info = node_response.entry
            original_filename = getattr(node_info, 'name', f"document_{clean_node_id}")
            
            if ctx:
                await ctx.info("Uploading new content with versioning using high-level API...")
                await ctx.report_progress(0.7)
            
            # **USE HIGH-LEVEL API: update_node_content.sync()**
            # Use new name if provided, otherwise keep original filename
            final_filename = new_name.strip() if new_name.strip() else original_filename
            
            # Create File object with content
            file_obj = File(
                payload=BytesIO(file_content),
                file_name=final_filename,
                mime_type="application/octet-stream"
            )
            
            # Use high-level update_node_content API instead of manual httpx
            try:
                version_type = "major" if major_version else "minor"
                logger.info(f"Updating content for {clean_node_id} ({version_type} version)")
                # Ensure raw client is initialized before using it
                if not core_client.is_initialized:
                    return safe_format_output("❌ Error: Alfresco server unavailable")
                # Use high-level update_node_content API
                content_response = update_node_content_sync(
                    node_id=clean_node_id,
                    client=core_client.raw_client,
                    body=file_obj,
                    major_version=major_version,
                    comment=comment if comment else None,
                    name=new_name.strip() if new_name.strip() else None
                )
                
                if not content_response:
                    return safe_format_output(f"❌ Failed to update document content using high-level API")
                
                logger.info(f"Content updated successfully for {clean_node_id}")
                
                # CRITICAL: Unlock the document after successful content update to complete checkin
                try:
                    logger.info(f"Unlocking document after successful checkin: {clean_node_id}")
                    unlock_response = core_client.versions.cancel_checkout(node_id=clean_node_id)
                    logger.info(f"Document unlocked successfully after checkin: {clean_node_id}")
                except Exception as unlock_error:
                    error_str = str(unlock_error)
                    if "404" in error_str:
                        logger.info(f"Document was not locked (already unlocked): {clean_node_id}")
                    elif "405" in error_str:
                        logger.warning(f"Server doesn't support unlock APIs: {clean_node_id}")
                    else:
                        logger.error(f"Failed to unlock document after checkin: {clean_node_id} - {error_str}")
                        # Don't fail the entire checkin if unlock fails - content was updated successfully
                    
            except Exception as api_error:
                return safe_format_output(f"❌ Failed to update document content: {str(api_error)}")
            
            # Get updated node info to show version details using high-level core client
            updated_node_response = core_client.nodes.get(node_id=clean_node_id)
            updated_node = updated_node_response.entry if hasattr(updated_node_response, 'entry') else {}
            
            # Extract version using multiple access methods (same as get_node_properties)
            new_version = 'Unknown'
            if hasattr(updated_node, 'properties') and updated_node.properties:
                try:
                    # Try to_dict() method first
                    if hasattr(updated_node.properties, 'to_dict'):
                        props_dict = updated_node.properties.to_dict()
                        new_version = props_dict.get('cm:versionLabel', 'Unknown')
                        logger.info(f"Version found via to_dict(): {new_version}")
                    # Try direct attribute access
                    elif hasattr(updated_node.properties, 'cm_version_label') or hasattr(updated_node.properties, 'cm:versionLabel'):
                        new_version = getattr(updated_node.properties, 'cm_version_label', getattr(updated_node.properties, 'cm:versionLabel', 'Unknown'))
                        logger.info(f"Version found via attributes: {new_version}")
                    # Try dict-like access
                    elif hasattr(updated_node.properties, '__getitem__'):
                        new_version = updated_node.properties.get('cm:versionLabel', 'Unknown') if hasattr(updated_node.properties, 'get') else updated_node.properties['cm:versionLabel'] if 'cm:versionLabel' in updated_node.properties else 'Unknown'
                        logger.info(f"Version found via dict access: {new_version}")
                    else:
                        logger.warning(f"Version properties - type: {type(updated_node.properties)}, methods: {dir(updated_node.properties)}")
                except Exception as version_error:
                    logger.error(f"Error extracting version: {version_error}")
                    new_version = 'Unknown'
            else:
                logger.warning("No properties found for version extraction")
            
            if ctx:
                await ctx.info("Cleaning up checkout tracking...")
                await ctx.report_progress(0.9)
            
            # Clean up checkout tracking
            cleanup_status = "ℹ️  No checkout tracking to clean up"
            if checkout_data and 'checkouts' in checkout_data and clean_node_id in checkout_data['checkouts']:
                del checkout_data['checkouts'][clean_node_id]
                
                checkout_manifest_path = pathlib.Path.home() / "Downloads" / "checkout" / ".checkout_manifest.json"
                with open(checkout_manifest_path, 'w') as f:
                    json.dump(checkout_data, f, indent=2)
                
                # Optionally remove the checkout file
                try:
                    checkout_file_path.unlink()
                    cleanup_status = "🗑️  Local checkout file cleaned up"
                except:
                    cleanup_status = "WARNING:  Local checkout file cleanup failed"
            
            if ctx:
                await ctx.info("Checkin completed: Content updated + Document unlocked + Version created!")
                await ctx.report_progress(1.0)
            
            # Format file size
            file_size = len(file_content)
            if file_size < 1024:
                size_str = f"{file_size} bytes"
            elif file_size < 1024 * 1024:
                size_str = f"{file_size / 1024:.1f} KB"
            else:
                size_str = f"{file_size / (1024 * 1024):.1f} MB"
            
            # Clean JSON-friendly formatting (no markdown syntax)
            return safe_format_output(f"""✅ Document checked in successfully!
    
    📄 Document: {final_filename}
    🔢 New Version: {new_version} ({version_type})
    📝 Comment: {comment if comment else '(no comment)'}
    📊 File Size: {size_str}
    🔗 Node ID: {clean_node_id}
    {f"📝 Renamed: {original_filename} → {final_filename}" if new_name.strip() else ""}
    
    {cleanup_status}
    
    Next Steps:
    🔓 Document is now UNLOCKED and available for others to edit
    ✅ New version has been created with your changes
    ✅ You can continue editing by using checkout_document again
    
    Status: Content updated → Document unlocked → Checkin complete!""")
            
        except Exception as e:
            logger.error(f"Checkin failed: {str(e)}")
            return safe_format_output(f"❌ Checkin failed: {str(e)}") 
  • Registers the checkin_document tool with FastMCP (@mcp.tool), defines input schema via parameters and docstring, acts as thin wrapper calling the core impl.
    @mcp.tool
    async def checkin_document(
        node_id: str, 
        comment: str = "", 
        major_version: bool = False,
        file_path: str = "",
        new_name: str = "",
        ctx: Context = None
    ) -> str:
        """Check in a document after editing using Alfresco REST API."""
        return await checkin_document_impl(node_id, comment, major_version, file_path, new_name, ctx)
  • Detailed schema documentation in the impl function docstring, describing parameters, usage, and return value for input/output validation.
    """Check in a document after editing using Alfresco REST API.
    
    Args:
        node_id: Original node ID to check in (not working copy)
        comment: Check-in comment (default: empty)
        major_version: Whether to create a major version (default: False = minor version)
        file_path: Specific file path to upload (if empty, auto-detects from checkout folder)
        new_name: Optional new name for the file during checkin (default: keep original name)
        ctx: MCP context for progress reporting
    
    Returns:
        Check-in confirmation with version details and cleanup status
    """
Behavior2/5

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

No annotations are provided, so the description carries full burden. It states the tool performs a check-in operation, implying a write/mutation action, but doesn't disclose behavioral traits such as required permissions, whether it creates new versions, what happens to the checked-out state, or error conditions. The mention of 'Alfresco REST API' adds some context but is insufficient for a mutation tool.

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 purpose ('Check in a document') and adds necessary context ('after editing using Alfresco REST API'). There is no wasted verbiage or redundancy, making it appropriately concise for the tool's complexity.

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 (5 parameters, mutation operation, no annotations) and the presence of an output schema (which might cover return values), the description is incomplete. It lacks critical details like parameter meanings, usage constraints, and behavioral transparency needed for safe and effective invocation, especially for a write tool with multiple inputs.

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 no parameter information. It doesn't explain what 'node_id' refers to, the purpose of 'comment' or 'major_version', or how 'file_path' and 'new_name' interact with the check-in process. With 5 parameters undocumented in both schema and description, this is a significant gap.

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 action ('Check in') and resource ('a document') with the specific context 'after editing using Alfresco REST API.' It distinguishes from siblings like 'cancel_checkout' and 'checkout_document' by focusing on the check-in operation. However, it doesn't explicitly differentiate from all siblings, such as 'update_node_properties' which might also modify documents.

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 'after editing,' implying usage post-modification, but lacks explicit when-to-use rules, prerequisites (e.g., document must be checked out first), or alternatives. No mention of when not to use this tool or comparisons to siblings like 'update_node_properties' for non-versioned changes.

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