1#!/usr/bin/env python3

2"""Session management MCP tools.

3

4 This module provides tools for managing Claude session lifecycle including

5 initialization, checkpoints, and cleanup.

6

7 Refactored to use utility modules for reduced code duplication.

8"""

9

10 from __future__ import annotations

11

12 import shutil

13 import subprocess # nosec B404

14 from contextlib import suppress

15 from dataclasses import dataclass, field

16 from pathlib import Path

17 from typing import TYPE_CHECKING, Any

18

19 from session_buddy.di import get_sync_typed

20 from session_buddy.di.container import depends

21

22 if TYPE_CHECKING:

23 import psutil

24 from fastmcp import FastMCP

25

26 from session_buddy.core import SessionLifecycleManager

27 from session_buddy.utils.error_handlers import _get_logger

28

29

30@dataclass

31 class SessionOutputBuilder:

32 """Centralized output formatting with consistent styling."""

33

34 sections: list[str] = field(default_factory=list)

35

36 def add_header(self, title: str, separator_char: str = "=") -> None:

37 """Add formatted header."""

38 separator = separator_char * len(title)

39 self.sections.extend([title, separator])

40

41 def add_section(self, title: str, items: list[str]) -> None:

42 """Add formatted section with items."""

43 if title: 43 ↛ 45 line 43 didn't jump to line 45 because the condition on line 43 was always true

44 self.sections.append(f"\n{title}:")

45 self.sections.extend(items)

46

47 def add_status_item(self, name: str, status: bool, value: str = "") -> None:

48 """Add status indicator item."""

49 icon = "✅" if status else "❌"

50 display = f" • {name}: {icon}"

51 if value:

52 display += f" {value}"

53 self.sections.append(display)

54

55 def add_simple_item(self, item: str) -> None:

56 """Add simple item."""

57 self.sections.append(item)

58

59 def build(self) -> str:

60 """Build final output string."""

61 return "\n".join(self.sections)

62

63

64@dataclass

65 class SessionSetupResults:

66 """Results from session setup operations."""

67

68 uv_setup: list[str] = field(default_factory=list)

69 shortcuts_result: dict[str, Any] = field(default_factory=dict)

70 recommendations: list[str] = field(default_factory=list)

71

72

73# ============================================================================

74# Service Resolution

75# ============================================================================

76

77

78 def _get_session_manager() -> SessionLifecycleManager:

79 """Get or create SessionLifecycleManager instance.

80

81 Note:

82 Uses the Oneiric-backed service container for singleton resolution.

83

84 """

85 with suppress(Exception):

86 manager = get_sync_typed(SessionLifecycleManager) # type: ignore[no-any-return]

87 if isinstance(manager, SessionLifecycleManager): 87 ↛ 90 line 87 didn't jump to line 90

88 return manager

89

90 manager = SessionLifecycleManager()

91 depends.set(SessionLifecycleManager, manager)

92 return manager

93

94

95# ============================================================================

96# Session Shortcuts Management

97# ============================================================================

98

99

100 def _create_session_shortcuts() -> dict[str, Any]:

101 """Create Claude Code slash command shortcuts for session management.

102

103 Creates /start, /checkpoint, and /end shortcuts in ~/.claude/commands/

104 that map to session-mgmt MCP tools.

105

106 Returns:

107 Dict with 'created' bool, 'existed' bool, and 'shortcuts' list

108

109 """

110 claude_home = Path.home() / ".claude"

111 commands_dir = claude_home / "commands"

112

113 # Create commands directory if it doesn't exist

114 commands_dir.mkdir(parents=True, exist_ok=True)

115

116 shortcuts = {

117 "start": {

118 "file": "start.md",

119 "content": """---

120 description: Start session management for current project

121---

122

123 Please use the `mcp__session-buddy__start` tool to initialize session management for the current project.

124

125 This will:

126 1. Set up session tracking for the git repository

127 2. Initialize conversation memory and context

128 3. Prepare the project for enhanced Claude Code workflows

129 4. Install UV dependencies and automation tools

130 5. Create session management slash command shortcuts

131""",

132 },

133 "checkpoint": {

134 "file": "checkpoint.md",

135 "content": """---

136 argument-hint: [checkpoint-name]

137 description: Create a session checkpoint with progress summary

138---

139

140 Please use the `mcp__session-buddy__checkpoint` tool to create a session checkpoint.

141

142 This command will:

143 1. Create a checkpoint of the current development session

144 2. Analyze code quality and calculate quality scores

145 3. Summarize progress made so far

146 4. Document any pending tasks or context

147 5. Prepare for seamless session resumption

148

149 The tool will analyze the working directory and provide comprehensive quality metrics.

150""",

151 },

152 "end": {

153 "file": "end.md",

154 "content": """---

155 description: End current session with cleanup and summary

156---

157

158 Please use the `mcp__session-buddy__end` tool to gracefully end the current session.

159

160 This will:

161 1. Create a final checkpoint of all work completed

162 2. Generate session summary and insights

163 3. Clean up temporary resources

164 4. Prepare handoff documentation for next session

165 5. Store final quality metrics and learning data

166""",

167 },

168 }

169

170 created_shortcuts = []

171 existing_shortcuts = []

172

173 for shortcut_name, shortcut_data in shortcuts.items():

174 shortcut_path = commands_dir / shortcut_data["file"]

175

176 if shortcut_path.exists():

177 existing_shortcuts.append(shortcut_name)

178 else:

179 try:

180 shortcut_path.write_text(shortcut_data["content"])

181 created_shortcuts.append(shortcut_name)

182 _get_logger().info(f"Created slash command shortcut: /{shortcut_name}")

183 except Exception as e:

184 _get_logger().exception(

185 f"Failed to create shortcut /{shortcut_name}: {e}",

186 )

187

188 return {

189 "created": bool(created_shortcuts),

190 "existed": bool(existing_shortcuts) and not created_shortcuts,

191 "shortcuts": created_shortcuts or existing_shortcuts,

192 }

193

194

195# ============================================================================

196# Working Directory Detection

197# ============================================================================

198

199

200 def _check_environment_variables() -> str | None:

201 """Check for Claude Code environment variables."""

202 import os

203

204 for env_var in ("CLAUDE_WORKING_DIR", "CLIENT_PWD", "CLAUDE_PROJECT_DIR"):

205 if env_var in os.environ:

206 client_dir = os.environ[env_var]

207 if client_dir and Path(client_dir).exists(): 207 ↛ 204 line 207 didn't jump to line 204 because the condition on line 207 was always true

208 return client_dir

209 return None

210

211

212 def _check_working_dir_file() -> str | None:

213 """Check for the temporary file used by Claude's auto-start scripts."""

214 import tempfile

215

216 working_dir_file = Path(tempfile.gettempdir()) / "claude-git-working-dir"

217 if working_dir_file.exists():

218 with suppress(OSError, PermissionError, ValueError, UnicodeDecodeError):

219 stored_dir = working_dir_file.read_text().strip()

220 # Only use if it's NOT the session-mgmt-mcp server directory

221 if ( 221 ↛ 226 line 221 didn't jump to line 226 because the condition on line 221 was never true

222 stored_dir

223 and Path(stored_dir).exists()

224 and not stored_dir.endswith("session-mgmt-mcp")

225 ):

226 return stored_dir

227 return None

228

229

230 def _check_parent_process_cwd() -> str | None:

231 """Check parent process working directory (advanced)."""

232 with suppress(ImportError, Exception):

233 import psutil

234

235 parent_process = psutil.Process().parent()

236 if parent_process: 236 ↛ 246 line 236 didn't jump to line 246

237 parent_cwd = parent_process.cwd()

238 # Only use if it's a different directory and exists

239 if ( 239 ↛ 245 line 239 didn't jump to line 245 because the condition on line 239 was never true

240 parent_cwd

241 and Path(parent_cwd).exists()

242 and parent_cwd != str(Path.cwd())

243 and not parent_cwd.endswith("session-mgmt-mcp")

244 ):

245 return parent_cwd

246 return None

247

248

249 def _is_git_repository(repo_path: Path) -> bool:

250 """Check if a path is a git repository."""

251 return repo_path.is_dir() and (repo_path / ".git").exists()

252

253

254 def _safe_get_mtime(repo_path: Path) -> float | None:

255 """Safely get modification time of a repository."""

256 try:

257 return repo_path.stat().st_mtime

258 except Exception:

259 return None

260

261

262 def _collect_git_repos(projects_path: Path) -> list[tuple[float, str]]:

263 """Collect git repositories with modification times from a directory."""

264 git_repos: list[tuple[float, str]] = []

265 for repo_path in projects_path.iterdir():

266 if _is_git_repository(repo_path):

267 mtime = _safe_get_mtime(repo_path)

268 if mtime is not None: 268 ↛ 265 line 268 didn't jump to line 265 because the condition on line 268 was always true

269 git_repos.append((mtime, str(repo_path)))

270 return git_repos

271

272

273 def _get_most_recent_client_repo(git_repos: list[tuple[float, str]]) -> str | None:

274 """Get most recent repository excluding the server itself."""

275 git_repos.sort(reverse=True)

276 for _mtime, repo_path_str in git_repos: 276 ↛ 279 line 276 didn't jump to line 279 because the loop on line 276 didn't complete

277 if not repo_path_str.endswith("session-mgmt-mcp"): 277 ↛ 276 line 277 didn't jump to line 276 because the condition on line 277 was always true

278 return repo_path_str

279 return None

280

281

282 def _find_recent_git_repository() -> str | None:

283 """Look for recent git repositories in common project directories."""

284 for projects_dir in ("/Users/les/Projects", str(Path.home() / "Projects")): 284 ↛ 294 line 284 didn't jump to line 294 because the loop on line 284 didn't complete

285 projects_path = Path(projects_dir)

286 if not projects_path.exists(): 286 ↛ 287 line 286 didn't jump to line 287 because the condition on line 286 was never true

287 continue

288

289 git_repos = _collect_git_repos(projects_path)

290 if git_repos: 290 ↛ 284 line 290 didn't jump to line 284 because the condition on line 290 was always true

291 if repo := _get_most_recent_client_repo(git_repos): 291 ↛ 284 line 291 didn't jump to line 284 because the condition on line 291 was always true

292 return repo

293

294 return None

295

296

297 def _get_client_working_directory() -> str | None:

298 """Auto-detect the client's working directory using multiple detection methods."""

299 # Method 1: Check for Claude Code environment variables

300 if client_dir := _check_environment_variables(): 300 ↛ 301 line 300 didn't jump to line 301 because the condition on line 300 was never true

301 return client_dir

302

303 # Method 2: Check for the temporary file used by Claude's auto-start scripts

304 if client_dir := _check_working_dir_file(): 304 ↛ 305 line 304 didn't jump to line 305 because the condition on line 304 was never true

305 return client_dir

306

307 # Method 3: Check parent process working directory (advanced)

308 if client_dir := _check_parent_process_cwd(): 308 ↛ 309 line 308 didn't jump to line 309 because the condition on line 308 was never true

309 return client_dir

310

311 # Method 4: Look for recent git repositories in common project directories

312 if client_dir := _find_recent_git_repository(): 312 ↛ 315 line 312 didn't jump to line 315 because the condition on line 312 was always true

313 return client_dir

314

315 return None

316

317

318# ============================================================================

319# Environment Setup Operations

320# ============================================================================

321

322

323 async def _perform_environment_setup(result: dict[str, Any]) -> SessionSetupResults:

324 """Perform all environment setup tasks. Target complexity: ≤5."""

325 working_dir = Path(result["working_directory"])

326

327 uv_setup = _setup_uv_dependencies(working_dir)

328 shortcuts_result = _create_session_shortcuts()

329 recommendations = result["quality_data"].get("recommendations", [])

330

331 return SessionSetupResults(

332 uv_setup=uv_setup,

333 shortcuts_result=shortcuts_result,

334 recommendations=recommendations,

335 )

336

337

338 def _setup_uv_dependencies(current_dir: Path) -> list[str]:

339 """Set up UV dependencies and requirements.txt generation."""

340 output = []

341 output.extend(("\n" + "=" * 50, "📦 UV Package Management Setup", "=" * 50))

342

343 # Check if uv is available

344 uv_available = shutil.which("uv") is not None

345 if not uv_available:

346 output.extend(

347 (

348 "⚠️ UV not found in PATH",

349 "💡 Install UV: curl -LsSf https://astral.sh/uv/install.sh | sh",

350 )

351 )

352 return output

353

354 # Check for pyproject.toml

355 pyproject_path = current_dir / "pyproject.toml"

356 if pyproject_path.exists():

357 output.append("✅ Found pyproject.toml - UV project detected")

358

359 # Run uv sync if dependencies need updating

360 try:

361 sync_result = subprocess.run(

362 ["uv", "sync"],

363 check=False,

364 cwd=current_dir,

365 capture_output=True,

366 text=True,

367 timeout=60,

368 )

369

370 if sync_result.returncode == 0: 370 ↛ 373 line 370 didn't jump to line 373 because the condition on line 370 was always true

371 output.append("✅ UV dependencies synchronized")

372 else:

373 output.append(f"⚠️ UV sync had issues: {sync_result.stderr}")

374 except subprocess.TimeoutExpired:

375 output.append(

376 "⚠️ UV sync timed out - dependencies may need manual attention",

377 )

378 except Exception as e:

379 output.append(f"⚠️ UV sync error: {e}")

380 else:

381 output.extend(

382 (

383 "ℹ️ No pyproject.toml found",

384 "💡 Consider running 'uv init' to create a new UV project",

385 )

386 )

387

388 return output

389

390

391# ============================================================================

392# Output Formatting Helpers

393# ============================================================================

394

395

396 def _add_session_info_to_output(

397 output_builder: SessionOutputBuilder,

398 result: dict[str, Any],

399) -> None:

400 """Add session information to output. Target complexity: ≤5."""

401 output_builder.add_simple_item(f"📁 Current project: {result['project']}")

402 output_builder.add_simple_item(

403 f"📂 Working directory: {result['working_directory']}",

404 )

405 output_builder.add_simple_item(f"🏠 Claude directory: {result['claude_directory']}")

406 output_builder.add_simple_item(

407 f"📊 Initial quality score: {result['quality_score']}/100",

408 )

409

410 # Add project context info

411 context = result["project_context"]

412 context_items = sum(1 for detected in context.values() if detected)

413 output_builder.add_simple_item(

414 f"🎯 Project context: {context_items}/{len(context)} indicators detected",

415 )

416

417

418 def _add_environment_info_to_output(

419 output_builder: SessionOutputBuilder,

420 setup_results: SessionSetupResults,

421) -> None:

422 """Add environment setup info to output. Target complexity: ≤5."""

423 # Add UV setup

424 output_builder.sections.extend(setup_results.uv_setup)

425

426 # Add recommendations

427 if setup_results.recommendations:

428 output_builder.add_section(

429 "💡 Setup recommendations",

430 [f" • {rec}" for rec in setup_results.recommendations[:3]],

431 )

432

433 # Add shortcuts

434 shortcuts = setup_results.shortcuts_result

435 if shortcuts.get("created"):

436 output_builder.add_section(

437 "🔧 Created session management shortcuts",

438 [f" • /{shortcut}" for shortcut in shortcuts["shortcuts"]],

439 )

440 elif shortcuts.get("existed"): 440 ↛ exit line 440 didn't return from function '_add_environment_info_to_output' because the condition on line 440 was always true

441 output_builder.add_simple_item("\n✅ Session shortcuts already exist")

442

443

444 def _add_project_section_to_output(

445 output_builder: SessionOutputBuilder,

446 result: dict[str, Any],

447) -> None:

448 """Add project information to output. Target complexity: ≤3."""

449 output_builder.add_simple_item(f"📁 Project: {result['project']}")

450 output_builder.add_simple_item(

451 f"📂 Working directory: {result['working_directory']}",

452 )

453 output_builder.add_simple_item(f"📊 Quality score: {result['quality_score']}/100")

454

455

456 def _add_quality_section_to_output(

457 output_builder: SessionOutputBuilder,

458 breakdown: dict[str, Any],

459) -> None:

460 """Add quality breakdown to output. Target complexity: ≤5."""

461 quality_items = [

462 f" • Code quality: {breakdown['code_quality']:.1f}/40",

463 f" • Project health: {breakdown['project_health']:.1f}/30",

464 f" • Dev velocity: {breakdown['dev_velocity']:.1f}/20",

465 f" • Security: {breakdown['security']:.1f}/10",

466 ]

467 output_builder.add_section("📈 Quality breakdown", quality_items)

468

469

470 def _add_health_section_to_output(

471 output_builder: SessionOutputBuilder,

472 health: dict[str, Any],

473) -> None:

474 """Add system health to output. Target complexity: ≤5."""

475 output_builder.add_section("🏥 System health", [])

476 output_builder.add_status_item("UV package manager", health["uv_available"])

477 output_builder.add_status_item("Git repository", health["git_repository"])

478 output_builder.add_status_item("Claude directory", health["claude_directory"])

479

480

481 def _add_project_context_to_output(

482 output_builder: SessionOutputBuilder,

483 context: dict[str, Any],

484) -> None:

485 """Add project context to output. Target complexity: ≤5."""

486 context_items = sum(1 for detected in context.values() if detected)

487 output_builder.add_simple_item(

488 f"\n🎯 Project context: {context_items}/{len(context)} indicators",

489 )

490

491 key_indicators = [

492 ("pyproject.toml", context.get("has_pyproject_toml", False)),

493 ("Git repository", context.get("has_git_repo", False)),

494 ("Test suite", context.get("has_tests", False)),

495 ("Documentation", context.get("has_docs", False)),

496 ]

497

498 for name, detected in key_indicators:

499 output_builder.add_status_item(name, detected)

500

501

502# ============================================================================

503# Checkpoint-Specific Helpers

504# ============================================================================

505

506

507 async def _handle_auto_store_reflection(

508 result: dict[str, Any],

509 output: list[str],

510) -> None:

511 """Handle selective auto-store reflection logic."""

512 auto_store_decision = result.get("auto_store_decision")

513 if not auto_store_decision: 513 ↛ 516 line 513 didn't jump to line 516 because the condition on line 513 was always true

514 return

515

516 if auto_store_decision.should_store:

517 from session_buddy.reflection_tools import get_reflection_database

518 from session_buddy.utils.reflection_utils import generate_auto_store_tags

519

520 try:

521 db = await get_reflection_database()

522

523 # Create meaningful checkpoint summary

524 checkpoint_content = f"Quality score: {result['quality_score']}/100. "

525 if auto_store_decision.metadata.get("delta"):

526 delta = auto_store_decision.metadata["delta"]

527 direction = (

528 "improved"

529 if auto_store_decision.reason.value == "quality_improvement"

530 else "changed"

531 )

532 checkpoint_content += f"Quality {direction} by {delta} points. "

533

534 checkpoint_content += (

535 f"Project: {_get_session_manager().current_project or 'unknown'}. "

536 )

537 checkpoint_content += f"Timestamp: {result['timestamp']}"

538

539 # Generate semantic tags

540 tags = generate_auto_store_tags(

541 reason=auto_store_decision.reason,

542 project=_get_session_manager().current_project,

543 quality_score=result["quality_score"],

544 )

545

546 # Store the reflection

547 await db.store_reflection(checkpoint_content, tags)

548 output.append(f"\n{result['auto_store_summary']}")

549 except Exception as e:

550 _get_logger().exception(f"Failed to store checkpoint reflection: {e}")

551 output.append(f"⚠️ Reflection storage failed: {e}")

552 else:

553 # Show why we skipped auto-store

554 output.append(f"\n{result.get('auto_store_summary', '')}")

555

556

557 async def _handle_auto_compaction(output: list[str]) -> None:

558 """Handle automatic compaction analysis and execution."""

559 from session_buddy.server_optimized import (

560 _execute_auto_compact,

561 should_suggest_compact,

562 )

563

564 should_compact, reason = should_suggest_compact()

565 output.extend(("\n🔄 Automatic Compaction Analysis", f"📊 {reason}"))

566

567 if should_compact: 567 ↛ 580 line 567 didn't jump to line 580 because the condition on line 567 was always true

568 output.append("\n🔄 Executing automatic compaction...")

569 try:

570 await _execute_auto_compact()

571 output.append("✅ Context automatically optimized")

572 except Exception as e:

573 output.extend(

574 (

575 f"⚠️ Auto-compact skipped: {e!s}",

576 "💡 Consider running /compact manually",

577 )

578 )

579 else:

580 output.append("✅ Context appears well-optimized for current session")

581

582

583# ============================================================================

584# End Session Formatting Helpers

585# ============================================================================

586

587

588 def _format_successful_end(summary: dict[str, Any]) -> list[str]:

589 """Format successful session end output."""

590 output = [

591 f"📁 Project: {summary['project']}",

592 f"📊 Final quality score: {summary['final_quality_score']}/100",

593 f"⏰ Session ended: {summary['session_end_time']}",

594 ]

595

596 output.extend(_format_recommendations(summary.get("recommendations", [])))

597 output.extend(_format_session_summary(summary))

598

599 output.extend(

600 [

601 "\n✅ Session ended successfully!",

602 "💡 Use the session data to improve future development workflows.",

603 ],

604 )

605

606 return output

607

608

609 def _format_recommendations(recommendations: list[str]) -> list[str]:

610 """Format recommendations section."""

611 if not recommendations: 611 ↛ 612 line 611 didn't jump to line 612 because the condition on line 611 was never true

612 return []

613

614 output = ["\n🎯 Final recommendations for future sessions:"]

615 output.extend(f" • {rec}" for rec in recommendations[:5])

616 return output

617

618

619 def _format_session_summary(summary: dict[str, Any]) -> list[str]:

620 """Format session summary section."""

621 output = [

622 "\n📝 Session Summary:",

623 f" • Working directory: {summary['working_directory']}",

624 " • Session data has been logged for future reference",

625 " • All temporary resources have been cleaned up",

626 ]

627

628 # Add handoff documentation info

629 handoff_doc = summary.get("handoff_documentation")

630 if handoff_doc:

631 output.append(f" • Handoff documentation: {handoff_doc}")

632

633 return output

634

635

636# ============================================================================

637# Tool Implementations

638# ============================================================================

639

640

641 async def _start_impl(working_directory: str | None = None) -> str:

642 """Initialize session with comprehensive setup. Target complexity: ≤8."""

643 output_builder = SessionOutputBuilder()

644 output_builder.add_header("🚀 Claude Session Initialization via MCP Server")

645

646 try:

647 result = await _get_session_manager().initialize_session(working_directory)

648

649 if result["success"]:

650 _add_session_info_to_output(output_builder, result)

651 setup_results = await _perform_environment_setup(result)

652 _add_environment_info_to_output(output_builder, setup_results)

653 output_builder.add_simple_item(

654 "\n✅ Session initialization completed successfully!",

655 )

656 else:

657 output_builder.add_simple_item(

658 f"❌ Session initialization failed: {result['error']}",

659 )

660

661 except Exception as e:

662 _get_logger().exception("Session initialization error: %s", str(e))

663 output_builder.add_simple_item(

664 f"❌ Unexpected error during initialization: {e}",

665 )

666

667 return output_builder.build()

668

669

670 async def _checkpoint_impl(working_directory: str | None = None) -> str:

671 """Implementation for checkpoint tool."""

672 # Auto-detect client working directory if not provided

673 if not working_directory:

674 working_directory = _get_client_working_directory()

675

676 output = []

677 output.extend(

678 (

679 f"🔍 Claude Session Checkpoint - {_get_session_manager().current_project or 'Current Project'}",

680 "=" * 50,

681 )

682 )

683

684 try:

685 # Determine if this is a manual checkpoint (always true for explicit tool calls)

686 result = await _get_session_manager().checkpoint_session(

687 working_directory,

688 is_manual=True,

689 )

690

691 if result["success"]:

692 # Add quality assessment output

693 output.extend(result["quality_output"])

694

695 # Add git checkpoint output

696 output.extend(result["git_output"])

697

698 # Handle selective auto-store reflection

699 try:

700 await _handle_auto_store_reflection(result, output)

701 except Exception as e:

702 _get_logger().warning(

703 f"Auto-store reflection error (non-critical): {e}"

704 )

705 # Continue - this is not critical for checkpoint success

706

707 # Auto-compact when needed

708 try:

709 await _handle_auto_compaction(output)

710 except Exception as e:

711 _get_logger().warning(f"Auto-compaction error (non-critical): {e}")

712 output.append(f"\n⚠️ Auto-compaction skipped: {e!s}")

713

714 output.extend(

715 (

716 f"\n⏰ Checkpoint completed at: {result['timestamp']}",

717 "\n💡 This checkpoint includes intelligent conversation management and optimization.",

718 )

719 )

720 else:

721 output.append(f"❌ Checkpoint failed: {result['error']}")

722

723 except Exception as e:

724 _get_logger().exception("Checkpoint error: %s", str(e))

725 output.append(f"❌ Unexpected checkpoint error: {e}")

726

727 return "\n".join(output)

728

729

730 async def _end_impl(working_directory: str | None = None) -> str:

731 """Implementation for end tool."""

732 # Auto-detect client working directory if not provided

733 if not working_directory:

734 working_directory = _get_client_working_directory()

735

736 output = [

737 "🏁 Claude Session End - Cleanup and Handoff",

738 "=" * 50,

739 ]

740

741 try:

742 result = await _get_session_manager().end_session(working_directory)

743

744 if result["success"]:

745 output.extend(_format_successful_end(result["summary"]))

746 else:

747 output.append(f"❌ Session end failed: {result['error']}")

748

749 except Exception as e:

750 _get_logger().exception("Session end error: %s", str(e))

751 output.append(f"❌ Unexpected error during session end: {e}")

752

753 return "\n".join(output)

754

755

756 async def _status_impl(working_directory: str | None = None) -> str:

757 """Get comprehensive session status. Target complexity: ≤8."""

758 output_builder = SessionOutputBuilder()

759 output_builder.add_header("📊 Claude Session Status Report")

760

761 try:

762 result = await _get_session_manager().get_session_status(working_directory)

763

764 if result["success"]:

765 _add_project_section_to_output(output_builder, result)

766 _add_quality_section_to_output(output_builder, result["quality_breakdown"])

767 _add_health_section_to_output(output_builder, result["system_health"])

768 _add_project_context_to_output(output_builder, result["project_context"])

769

770 # Recommendations

771 recommendations = result["recommendations"]

772 if recommendations: 772 ↛ 778 line 772 didn't jump to line 778 because the condition on line 772 was always true

773 output_builder.add_section(

774 "💡 Recommendations",

775 [f" • {rec}" for rec in recommendations[:3]],

776 )

777

778 output_builder.add_simple_item(

779 f"\n⏰ Status generated: {result['timestamp']}",

780 )

781

782 else:

783 output_builder.add_simple_item(f"❌ Status check failed: {result['error']}")

784

785 except Exception as e:

786 _get_logger().exception("Status check error: %s", str(e))

787 output_builder.add_simple_item(f"❌ Unexpected error during status check: {e}")

788

789 return output_builder.build()

790

791

792# ============================================================================

793# MCP Tool Registration

794# ============================================================================

795

796

797 def register_session_tools(mcp_server: FastMCP) -> None:

798 """Register all session management tools with the MCP server."""

799

800 @mcp_server.tool()

801 async def start(working_directory: str | None = None) -> str:

802 """Initialize Claude session with comprehensive setup including UV dependencies and automation tools.

803

804 Args:

805 working_directory: Optional working directory override (defaults to PWD environment variable or current directory)

806

807 """

808 return await _start_impl(working_directory)

809

810 @mcp_server.tool()

811 async def checkpoint(working_directory: str | None = None) -> str:

812 """Perform mid-session quality checkpoint with workflow analysis and optimization recommendations.

813

814 Args:

815 working_directory: Optional working directory override (defaults to PWD environment variable or current directory)

816

817 """

818 return await _checkpoint_impl(working_directory)

819

820 @mcp_server.tool()

821 async def end(working_directory: str | None = None) -> str:

822 """End Claude session with cleanup, learning capture, and handoff file creation.

823

824 Args:

825 working_directory: Optional working directory override (defaults to PWD environment variable or current directory)

826

827 """

828 return await _end_impl(working_directory)

829

830 @mcp_server.tool()

831 async def status(working_directory: str | None = None) -> str:

832 """Get current session status and project context information with health checks.

833

834 Args:

835 working_directory: Optional working directory override (defaults to PWD environment variable or current directory)

836

837 """

838 return await _status_impl(working_directory)

839

840 @mcp_server.tool()

841 async def health_check() -> str:

842 """Simple health check that doesn't require database or session context."""

843 import os

844 import platform

845 import time

846

847 try:

848 working_directory = str(Path.cwd())

849 except FileNotFoundError:

850 # Handle case where current working directory doesn't exist

851 working_directory = "[Current directory unavailable]"

852

853 health_info = {

854 "server_status": "✅ Active",

855 "timestamp": time.time(),

856 "platform": platform.system(),

857 "python_version": platform.python_version(),

858 "process_id": os.getpid(),

859 "working_directory": working_directory,

860 }

861

862 return f"""🏥 MCP Server Health Check

863================================

864 Server Status: {health_info["server_status"]}

865 Platform: {health_info["platform"]}

866 Python: {health_info["python_version"]}

867 Process ID: {health_info["process_id"]}

868 Working Directory: {health_info["working_directory"]}

869 Timestamp: {health_info["timestamp"]}

870

871✅ MCP server is operational and responding to requests."""

872

873 @mcp_server.tool()

874 async def server_info() -> str:

875 """Get basic server information without requiring session context."""

876 import time

877

878 try:

879 # Check if we can access basic file system info

880 home_dir = Path.home()

881

882 try:

883 current_dir = Path.cwd()

884 except FileNotFoundError:

885 # Handle case where current working directory doesn't exist

886 current_dir = Path("[Current directory unavailable]")

887

888 return f"""📊 Session-mgmt MCP Server Information

889===========================================

890🏠 Home Directory: {home_dir}

891📁 Current Directory: {current_dir}

892🕐 Server Time: {time.strftime("%Y-%m-%d %H:%M:%S")}

893🔧 FastMCP Framework: Active

894🌐 Transport: streamable-http

895📡 Endpoint: /mcp

896

897✅ Server is running and accessible."""

898

899 except Exception as e:

900 return f"⚠️ Server info error: {e!s}"

901

902 @mcp_server.tool()

903 async def ping() -> str:

904 """Simple ping endpoint to test MCP connectivity."""

905 return "🏓 Pong! MCP server is responding"