Skip to main content
Glama
DrumRobot

claude-session-manager

by DrumRobot

rename_session

Change session titles by adding descriptive prefixes to organize Claude conversations within project folders.

Instructions

Rename a session by adding a title prefix to the first message

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
project_nameYesProject folder name
session_idYesSession ID (filename without .jsonl)
new_titleYesNew title to add as prefix

Implementation Reference

  • Core handler function that modifies the session's JSONL file by prefixing the new title to the first user message's text content, handling queue-operations and IDE tags appropriately.
    def rename_session(project_name: str, session_id: str, new_title: str) -> bool:
        """Rename a session by adding title prefix to first message."""
        base_path = get_base_path()
        project_path = base_path / project_name
        jsonl_file = project_path / f"{session_id}.jsonl"
    
        if not jsonl_file.exists():
            return False
    
        lines = []
        first_user_idx = -1
        original_message = None
    
        try:
            with open(jsonl_file, 'r', encoding='utf-8') as f:
                for i, line in enumerate(f):
                    lines.append(line)
                    line_stripped = line.strip()
                    if line_stripped:
                        try:
                            entry = json.loads(line_stripped)
                            entry_type = entry.get('type')
    
                            if entry_type == 'queue-operation' and original_message is None:
                                if entry.get('operation') == 'enqueue':
                                    content_arr = entry.get('content', [])
                                    for item in content_arr:
                                        if isinstance(item, dict) and item.get('type') == 'text':
                                            txt = item.get('text', '')
                                            if txt and not txt.strip().startswith('<ide_'):
                                                original_message = txt
                                                break
    
                            if entry_type == 'user' and first_user_idx == -1:
                                first_user_idx = i
    
                        except json.JSONDecodeError:
                            pass
    
            if first_user_idx == -1:
                return False
    
            entry = json.loads(lines[first_user_idx].strip())
            message = entry.get('message', {})
            content_list = message.get('content', [])
    
            if original_message is not None:
                text_idx = -1
                for idx, item in enumerate(content_list):
                    if isinstance(item, dict) and item.get('type') == 'text':
                        text_content = item.get('text', '')
                        if text_content.strip().startswith('<ide_'):
                            continue
                        text_idx = idx
                        break
    
                if text_idx >= 0:
                    content_list[text_idx]['text'] = f"{new_title}\n\n{original_message}"
                else:
                    insert_pos = 0
                    for idx, item in enumerate(content_list):
                        if isinstance(item, dict) and item.get('type') == 'text':
                            text_content = item.get('text', '')
                            if text_content.strip().startswith('<ide_'):
                                insert_pos = idx + 1
                    content_list.insert(insert_pos, {'type': 'text', 'text': f"{new_title}\n\n{original_message}"})
            else:
                for item in content_list:
                    if isinstance(item, dict) and item.get('type') == 'text':
                        old_text = item.get('text', '')
                        old_text = re.sub(r'^[^\n]+\n\n', '', old_text)
                        item['text'] = f"{new_title}\n\n{old_text}"
                        break
    
            entry['message']['content'] = content_list
            lines[first_user_idx] = json.dumps(entry, ensure_ascii=False) + '\n'
    
            with open(jsonl_file, 'w', encoding='utf-8') as f:
                f.writelines(lines)
    
            return True
    
        except Exception:
            return False
  • Registers the 'rename_session' tool with the MCP server, defining its name, description, and input schema.
    Tool(
        name="rename_session",
        description="Rename a session by adding a title prefix to the first message",
        inputSchema={
            "type": "object",
            "properties": {
                "project_name": {
                    "type": "string",
                    "description": "Project folder name"
                },
                "session_id": {
                    "type": "string",
                    "description": "Session ID (filename without .jsonl)"
                },
                "new_title": {
                    "type": "string",
                    "description": "New title to add as prefix"
                }
            },
            "required": ["project_name", "session_id", "new_title"]
        }
    ),
  • MCP tool dispatcher branch that extracts arguments and calls the rename_session handler function.
    elif name == "rename_session":
        project_name = arguments.get("project_name", "")
        session_id = arguments.get("session_id", "")
        new_title = arguments.get("new_title", "")
        success = rename_session(project_name, session_id, new_title)
        result = {"success": success, "message": "Session renamed" if success else "Failed to rename session"}
  • Helper function used in listing sessions to parse titles from session files, related to how titles are handled.
    def parse_session_summary(file_path: Path) -> dict | None:
        """Parse session file for summary info."""
        session_id = file_path.stem
        info = {
            "session_id": session_id,
            "title": f"Session {session_id[:8]}",
            "message_count": 0,
            "created_at": None,
            "updated_at": None,
        }
    
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                first_user_content = None
                for line in f:
                    line = line.strip()
                    if not line:
                        continue
                    try:
                        entry = json.loads(line)
                        entry_type = entry.get('type')
    
                        if entry_type in ('user', 'assistant'):
                            info["message_count"] += 1
                            timestamp = entry.get('timestamp', '')
                            if timestamp:
                                if not info["created_at"] or timestamp < info["created_at"]:
                                    info["created_at"] = timestamp
                                if not info["updated_at"] or timestamp > info["updated_at"]:
                                    info["updated_at"] = timestamp
    
                            if entry_type == 'user' and first_user_content is None:
                                message = entry.get('message', {})
                                content_list = message.get('content', [])
                                for item in content_list:
                                    if isinstance(item, dict) and item.get('type') == 'text':
                                        text = item.get('text', '').strip()
                                        text = re.sub(r'<ide_[^>]*>.*?</ide_[^>]*>', '', text, flags=re.DOTALL).strip()
                                        if text:
                                            first_user_content = text
                                            break
                    except json.JSONDecodeError:
                        continue
    
                if first_user_content:
                    if '\n\n' in first_user_content:
                        info["title"] = first_user_content.split('\n\n')[0][:100]
                    elif '\n' in first_user_content:
                        info["title"] = first_user_content.split('\n')[0][:100]
                    else:
                        info["title"] = first_user_content[:100]
    
        except Exception:
            return None
    
        return info if info["message_count"] > 0 else None
    
    
    def delete_session(project_name: str, session_id: str) -> bool:
        """Delete a session (move to .bak folder, or delete if empty)."""
        base_path = get_base_path()
        project_path = base_path / project_name
        jsonl_file = project_path / f"{session_id}.jsonl"
    
        if not jsonl_file.exists():
            return False
    
        # If file is empty (0 bytes), just delete it without backing up
        if jsonl_file.stat().st_size == 0:
            jsonl_file.unlink()
            return True
    
        backup_dir = base_path / ".bak"
        backup_dir.mkdir(exist_ok=True)
        backup_file = backup_dir / f"{project_name}_{session_id}.jsonl"
        jsonl_file.rename(backup_file)
        return True
    
    
    def rename_session(project_name: str, session_id: str, new_title: str) -> bool:
        """Rename a session by adding title prefix to first message."""
        base_path = get_base_path()
        project_path = base_path / project_name
        jsonl_file = project_path / f"{session_id}.jsonl"
    
        if not jsonl_file.exists():
            return False
    
        lines = []
        first_user_idx = -1
        original_message = None
    
        try:
            with open(jsonl_file, 'r', encoding='utf-8') as f:
                for i, line in enumerate(f):
                    lines.append(line)
                    line_stripped = line.strip()
                    if line_stripped:
                        try:
                            entry = json.loads(line_stripped)
                            entry_type = entry.get('type')
    
                            if entry_type == 'queue-operation' and original_message is None:
                                if entry.get('operation') == 'enqueue':
                                    content_arr = entry.get('content', [])
                                    for item in content_arr:
                                        if isinstance(item, dict) and item.get('type') == 'text':
                                            txt = item.get('text', '')
                                            if txt and not txt.strip().startswith('<ide_'):
                                                original_message = txt
                                                break
    
                            if entry_type == 'user' and first_user_idx == -1:
                                first_user_idx = i
    
                        except json.JSONDecodeError:
                            pass
    
            if first_user_idx == -1:
                return False
    
            entry = json.loads(lines[first_user_idx].strip())
            message = entry.get('message', {})
            content_list = message.get('content', [])
    
            if original_message is not None:
                text_idx = -1
                for idx, item in enumerate(content_list):
                    if isinstance(item, dict) and item.get('type') == 'text':
                        text_content = item.get('text', '')
                        if text_content.strip().startswith('<ide_'):
                            continue
                        text_idx = idx
                        break
    
                if text_idx >= 0:
                    content_list[text_idx]['text'] = f"{new_title}\n\n{original_message}"
                else:
                    insert_pos = 0
                    for idx, item in enumerate(content_list):
                        if isinstance(item, dict) and item.get('type') == 'text':
                            text_content = item.get('text', '')
                            if text_content.strip().startswith('<ide_'):
                                insert_pos = idx + 1
                    content_list.insert(insert_pos, {'type': 'text', 'text': f"{new_title}\n\n{original_message}"})
            else:
                for item in content_list:
                    if isinstance(item, dict) and item.get('type') == 'text':
                        old_text = item.get('text', '')
                        old_text = re.sub(r'^[^\n]+\n\n', '', old_text)
                        item['text'] = f"{new_title}\n\n{old_text}"
                        break
    
            entry['message']['content'] = content_list
            lines[first_user_idx] = json.dumps(entry, ensure_ascii=False) + '\n'
    
            with open(jsonl_file, 'w', encoding='utf-8') as f:
                f.writelines(lines)
    
            return True
    
        except Exception:
            return False

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/DrumRobot/claude-session-manager'

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