Files
sbroenne__mcp-server-excel/specs/SESSION-API-REDESIGN-IMPLEMENTATION-NOTES.md
T
Stefan Broenner df928e95f4 Refactor (#188)
* feat: align CLI with MCP Server session API pattern

MAJOR CHANGE: CLI now uses MCP-aligned session terminology
- Old: batch-begin, batch-commit, batch-list
- New: open, save, list (primary commands)
- Legacy batch commands still work but map to new session commands

IMPLEMENTATION:
✓ BatchCommands: Renamed Begin() → Open(), Commit() → Save()
✓ Program.cs: Updated routing for new command names
✓ Program.cs: Added backward compatibility for old batch commands
✓ Program.cs: Updated help text to show session lifecycle
✓ Program.cs: Banner now says 'MCP-aligned Session API'
✓ CommandHelper: Updated --batch-id → --session-id parameter detection
✓ Error messages: Updated terminology from 'batch' to 'session'
✓ All file operations: Updated comments and output messages

BACKWARD COMPATIBILITY:
✓ Old batch-begin/batch-commit/batch-list commands still work
✓ --batch-id flag still accepted (mapped to --session-id internally)
✓ Both terminologies coexist during transition

TESTING:
✓ CLI builds: 0 warnings, 0 errors
✓ MCP smoke test passed
✓ Help text displays new session commands and deprecation notice

DOCUMENTATION:
✓ CLI README already updated in previous commit with session API examples
✓ Help output now clearly shows primary commands and deprecation path

This aligns the CLI completely with the MCP Server's session-based API,
making both interfaces consistent and simplifying user experience.

* feat: split save/close commands - save keeps session open, close discards changes

* refactor: remove backward compatibility layer - legacy GetBatch method and usage

* fix: improve Range command error messages - use RangeHelpers specific errors

- All Range operations now use RangeHelpers.ResolveRange() with specificError out parameter
- Instead of cryptic '0x800A03EC' COM error, users now get helpful messages like:
  * 'Sheet \\'Validation_Summary\\' not found. Available sheets: Sheet1, Sheet2'
  * 'Named range \\'Sales\\' not found. Available named ranges: Parameters, Region'
- Includes suggestions to use excel_worksheet or excel_namedrange tools
- Affects: Get/Set Values/Formulas, Copy, Clear, Insert, Delete, Find, Replace, Sort, Hyperlinks, NumberFormat operations

* fix: simplify COM error messages - remove LLM guidance

Removed verbose step-by-step guidance from error messages. LLMs don't need to be told what to do, just what failed.

Changed from:
  'This usually means: (1) Sheet doesn't exist, (2) Range invalid, or (3) Session not open.
   Use excel_worksheet(action: list) to verify...'

To:
  'Cannot read range X on sheet Y: Insufficient memory to continue the execution of the program'

Files changed:
- RangeCommands.Values.cs: Simplified GetValuesAsync and SetValuesAsync COM error messages
- RangeCommands.Formulas.cs: Simplified GetFormulasAsync and SetFormulasAsync COM error messages
- ExcelToolsBase.cs: Simplified session validation error - just state facts, no instructions

* docs: add error message style guidance - facts not instructions

Added Best Practice #9 and Error Message Style section to mcp-server-guide.

Key principle: Error messages should state facts (what failed, why), not prescribe solutions.
LLMs are intelligent agents that determine next steps - they don't need step-by-step guidance.

Example:
  ❌ Wrong: 'This usually means: (1) Sheet doesn't exist... Use excel_worksheet(list)...'
  ✅ Correct: 'Cannot read range X on sheet Y: Insufficient memory'

This matches the simplifications in commit 21d4c58.

* fix: return JSON errors from OpenSessionAsync instead of throwing

Changed OpenSessionAsync to catch exceptions from CreateSessionAsync and return JSON with success=false instead of letting them bubble up as unhandled exceptions.

Fixes the generic 'An error occurred invoking excel_file' message shown when file was already open or other session creation errors occurred.

Files changed:
- ExcelFileTool.cs: Wrapped CreateSessionAsync in try-catch, return JSON errors

* refactor: Replace McpException with standard .NET exceptions

CRITICAL INSIGHT (Phase 8): Discovered that McpException usage was unnecessary complexity.
Since we catch ALL exceptions before the MCP SDK sees them and return JSON responses,
the SDK never encounters McpException - it gets our JSON error response instead.

CHANGES:
- Removed McpException catch blocks from all 11 tool files
- Updated ExcelToolsBase to throw ArgumentException (validation) and InvalidOperationException (state)
- Replaced McpException throws in:
  * ExcelFileTool (5 throws)
  * ExcelConnectionTool (17 throws)
  * All 9 'unknown action' switch branches
  * ExcelWorksheetTool invalid visibility

ARCHITECTURAL BENEFIT:
- Removed unnecessary coupling to ModelContextProtocol.McpException
- Single catch (Exception ex) block now handles all cases
- Uses standard .NET exception types for clearer semantics:
  * ArgumentException for parameter validation
  * InvalidOperationException for invalid state
- Simpler, more idiomatic C# code
- Same error handling quality (descriptive JSON responses)

REMAINING:
- ~86 parameter validation throws still using McpException (will be caught as Exception)
- These still work correctly but should be migrated to ArgumentException in future PRs
- Build: 0 warnings, 0 errors

RATIONALE:
The MCP SDK wraps our tool invocations with try-catch. Our code catches exceptions
BEFORE the SDK sees them and returns JSON with isError=true. Therefore, whether we
throw McpException, ArgumentException, or any other Exception is irrelevant to the
SDK - it receives JSON in all cases. Using standard .NET exceptions is cleaner,
doesn't add SDK coupling, and makes the code more maintainable.

* refactor: Complete migration to ArgumentException for ALL parameter validations

Complete removal of McpException pattern across all 11 tool files:
- ExcelDataModelTool: 23 throws (all measure/table/relationship parameters)
- ExcelWorksheetTool: 16 throws (all sheet/color/visibility parameters)
- ExcelVbaTool: 10 throws (all module/path parameters)
- ExcelTableTool: 5 throws (all table/column/format parameters)
- ExcelQueryTableTool: 10 throws (all query table parameters)
- ExcelRangeTool: 2 throws (shift direction validation)
- ExcelPivotTableTool: 4 throws (aggregation/sort direction validation)
- ExcelPowerQueryTool: 12 throws (completed in prior batch)
- ExcelNamedRangeTool: 9 throws (completed in prior batch)
- ExcelConnectionTool: 17 throws (completed in prior batch)
- ExcelFileTool: 5 throws (completed in prior batch)

TOTAL MIGRATION: 117/~117 parameter validation throws replaced

Architecture Insight:
- Standard .NET exceptions (ArgumentException for invalid input)
- Single catch (Exception ex) block handles all cases
- No SDK coupling - clean separation of concerns
- Build: 0 warnings, 0 errors (maintained throughout)

Breaking Simplification:
- Removed unnecessary McpException abstraction layer
- SDK-side exception handling wraps all tool invocations
- No behavior change to callers (still returns JSON with isError=true)
- Cleaner, more maintainable code with standard .NET patterns

* refactor: Simplify async command invocation by removing unnecessary lambda expressions

* fix: update .gitignore to exclude Jekyll generated files

* Fix MCP Server tests: tool discovery and enum completeness

- Fixed ToolDiscoveryTests: Removed non-existent 'excel_batch' from expected tools list
- Fixed ActionEnumCompletenessTests: Added FileAction.Open/Save/Close mappings to ActionExtensions
- Fixed tool naming: Changed 'excel_conditional_format' to 'excel_conditionalformat' (matches naming convention)
- Enum cleanup: Removed unused BatchAction enum and its ToActionString() mapping

Test results: 60 passing (was 58), 9 failing (was 11)
Remaining failures are pre-existing issues (Power Query COM errors, invalid session handling)

* Update documentation: 12 tools with 168 operations

- Added excel_conditionalformat tool (2 actions: add-rule, clear-rules)
- Removed excel_batch from tool lists (doesn't exist)
- Updated operation counts: 166 → 168
- Updated tool counts: 11 → 12
- Added conditional formatting examples to CLI README
- Updated all READMEs: main, MCP Server, CLI, VS Code extension, GitHub pages
- Updated range action counts: 45 → 43 (moved conditional formatting to dedicated tool)
- Added excel_querytable (8 actions) to complete tool list
- Updated excel_file action count: 3 → 6 (open, save, close, create-empty, close-workbook, test)

* Delete incorrect MCP Server tests expecting exceptions

Removed tests that expected McpException for invalid sessions/missing parameters.
Per CRITICAL-RULES.md Rule 17, MCP tools should return JSON with success=false
for business errors, NOT throw exceptions.

Tests deleted:
- DetailedErrorMessageTests.cs (entire file - 8 tests)
- ExcelMcpServerTests.ExcelWorksheet_InvalidSession_ShouldThrowError

Test results: 59 passing (was 60), 2 failing (was 9)
Remaining 2 failures are legitimate Power Query COM errors (RPC failures)

* Fix ExcelBatch async disposal and timeout handling

- Replace synchronous Task.Wait() with async DisposeAsync pattern
- Add proper CancellationTokenSource disposal in finally blocks
- Use ConfigureAwait(false) for library code
- Fix timeout handling to properly cancel operations
- Add comprehensive disposal tests with timeout scenarios

* Remove ExportAsync methods - consolidate with ViewAsync

All three command types (PowerQuery, VBA, Connection) now use ViewAsync
instead of ExportAsync to return code/data directly rather than writing
to files. This simplifies the API by eliminating duplicate functionality.

Changes:
- Removed ExportAsync from IPowerQueryCommands, IVbaCommands, IConnectionCommands interfaces
- Removed ExportAsync implementations from all three command files
- Updated CLI commands to use ViewAsync instead (return code directly)
- Updated MCP Server tools to use ViewAsync instead (return JSON with code)
- Updated VBA test to use ViewAsync instead of ExportAsync
- Updated POWERQUERY-FUTURE-STATE-SPEC.md to remove ExportAsync references

Tests now verify code is returned in ViewAsync result rather than written to file.

* WIP: CLI async-to-sync conversion attempt - reverting to clean state

* Save WIP

* Align MCP tooling with updated actions

* - Refactored tools to reduce cognizant load on LLM
- Fix stability issues when working on multiple files at the same time

* Increase STA thread cleanup timeout from 3 to 10 seconds to prevent potential deadlocks during disposal

* Refactor QueryTable handling to use unified PowerQueryHelpers

- Updated QueryTableCommand to utilize PowerQueryHelpers for creating QueryTables.
- Introduced ConnectionHelpers for retrieving connection strings and command texts.
- Consolidated QueryTable creation options into PowerQueryHelpers.QueryTableCreateOptions.
- Removed redundant methods and classes related to QueryTable options.
- Enhanced error handling and validation for connection strings.
- Updated tests to reflect changes in QueryTable creation and options application.

* Revert "Refactor QueryTable handling to use unified PowerQueryHelpers"

This reverts commit 3603afcb71.

* Switch PowerQuery flows to inline M code

* Fix formula string formatting in JSON deserialization tests

* Fix test failures: formula syntax, PowerQuery Update, and QueryTable API usage

- Fixed JSON syntax in formula tests (removed extra quotes in array)
- Fixed raw string literal quote handling in Excel formulas
- Fixed PowerQuery Update to preserve worksheet data:
  * Capture QueryTable destination and ResultRange before deletion
  * Clear only ResultRange (not entire sheet)
  * Recreate at same location with clearEntireSheet: false
  * Eliminates legacy clearEntireSheet anti-pattern
- Fixed QueryTable tests to pass M code directly instead of file paths:
  * Removed file I/O operations (7 occurrences)
  * Aligned with PowerQueryCommands.Create() API signature

All 9 originally failing tests now pass (2 formula + 7 QueryTable/PowerQuery).

* Include mcp.json in version control

- Added .vscode/mcp.json to .gitignore exceptions
- Tracks MCP server configuration for project

* Add sheet movement and cross-workbook operations: Move, CopyToWorkbook, MoveToWorkbook
2025-11-17 09:27:59 +00:00

9.8 KiB

Session API Redesign - Implementation Notes

Date: 2025-11-13 Branch: feature/session-api-redesign-spec

Implementation Status

✅ Completed Foundation (Ready for Use)

  1. SessionManager in Core (src/ExcelMcp.Core/SessionManager.cs)

    • Maps sessionId → IExcelBatch for both MCP Server and CLI
    • Methods: CreateSessionAsync, GetSession, SaveSessionAsync, CloseSessionAsync
  2. ExcelToolsBase Enhanced (src/ExcelMcp.McpServer/Tools/ExcelToolsBase.cs)

    • Static SessionManager instance accessible via GetSessionManager()
    • New WithSessionAsync method for session-based operations
    • Legacy WithBatchAsync marked [Obsolete] but still functional
  3. excel_file Tool Updated (src/ExcelMcp.McpServer/Tools/ExcelFileTool.cs)

    • ✅ Open action - creates session, returns sessionId
    • ✅ Save action - persists changes for active session
    • ✅ Close action - closes session without saving
    • ✅ Tool description updated with session lifecycle guidance
  4. Build Status

    • ✅ Solution builds successfully (0 errors, 0 warnings)
    • ✅ Core and ComInterop unchanged (IExcelBatch, batchId preserved)

🚧 In Progress - Tool Migration Pattern

Pattern for Migrating Tools:

// OLD (batchId + excelPath):
public static async Task<string> ExcelTool(
    ToolAction action,
    string excelPath,      // ❌ Remove
    string? param = null,
    string? batchId = null) // ❌ Change to sessionId

private static async Task<string> MethodAsync(Commands commands, string excelPath, string? param, string? batchId)
{
    var result = await ExcelToolsBase.WithBatchAsync(batchId, excelPath, save: false, ...);
}

// NEW (sessionId only):
public static async Task<string> ExcelTool(
    ToolAction action,
    string sessionId,      // ✅ Required session ID
    string? param = null)

private static async Task<string> MethodAsync(Commands commands, string sessionId, string? param)
{
    var result = await ExcelToolsBase.WithSessionAsync(sessionId, async (batch) => ...);
}

Steps Per Tool:

  1. Update tool method signature: Remove excelPath, change batchId? → sessionId (required)
  2. Update tool description: Remove batch guidance, add session requirement
  3. Update all private method signatures: Replace excelPath and batchId with sessionId
  4. Replace WithBatchAsync → WithSessionAsync in all private methods
  5. Remove save parameter (sessions handle save via excel_file actions)

📋 Remaining Work

Tools Needing Migration (11 tools):

  • ❌ ExcelPowerQueryTool.cs - 11 methods to migrate
  • ❌ ExcelWorksheetTool.cs
  • ❌ ExcelRangeTool.cs
  • ❌ ExcelTableTool.cs
  • ❌ ExcelVbaTool.cs
  • ❌ ExcelConnectionTool.cs
  • ❌ ExcelDataModelTool.cs
  • ❌ ExcelPivotTableTool.cs
  • ❌ ExcelNamedRangeTool.cs
  • ❌ ExcelQueryTableTool.cs
  • ❌ BatchSessionTool.cs - DELETE (replaced by excel_file Open/Save/Close)

CLI Migration:

  • Add session-open, session-save, session-close commands
  • Update all feature commands to accept --session-id instead of --batch-id
  • Map CLI --session-id → Core batchId internally

Testing:

  • Update MCP Server tests to use excel_file open/save/close workflow
  • Add session lifecycle tests
  • Verify backwards compatibility during transition

Documentation:

  • Update all tool descriptions
  • Update README with session workflow examples
  • Update MCP prompts to explain session lifecycle
  • Delete batch-related documentation

Remaining TODOs

1. Core Layer - Keep IExcelBatch and batchId (NO CHANGES)

  • Core layer will NOT be changed - keep all existing IExcelBatch interfaces and batchId parameters
  • Core layer maintains its internal batch-based implementation
  • Only MCP Server and CLI will expose the session abstraction to users

2. Introduce SessionManager and wire excel_file open/save/close (Core + MCP Server)

  • Add a SessionManager in src/ExcelMcp.Core responsible for session lifecycle (shared by MCP Server and CLI):
    • Internal ConcurrentDictionary<string, IExcelBatch> _activeSessions (Core still uses IExcelBatch)
    • CreateSession(string filePath) → opens via ExcelSession.BeginBatchAsync and returns sessionId
    • GetSession(string sessionId) → returns IExcelBatch?
    • SaveSession(string sessionId) → calls Save on the batch
    • CloseSession(string sessionId) → calls DisposeAsync, removes entry
  • Extend FileAction in ExcelFileTool with Open, Save, and Close
  • Update ExcelFile(...) tool method to:
    • open → call SessionManager.CreateSession(filePath) and serialize a JSON response including sessionId, filePath, and suggestedNextActions
    • save → call SessionManager.SaveSession(sessionId) and return success/failure JSON
    • close → call SessionManager.CloseSession(sessionId) and return success/failure JSON (never saves)

3. Convert MCP tools to sessionId parameter (map to Core's batchId)

  • For each MCP tool in src/ExcelMcp.McpServer/Tools/** (connections, datamodel, file, namedrange, pivottable, powerquery, querytable, range, table, vba, worksheet, etc.):
    • Remove excelPath parameter except for excel_file open/create-empty actions
    • Remove optional batchId parameter
    • Add [Required] string sessionId parameter (exposed to LLMs as "sessionId")
    • Inside tool methods, map sessionId → batchId when calling Core commands:
      // MCP tool accepts sessionId from LLM
      public static async Task<string> ExcelPowerQuery(
          PowerQueryAction action,
          string? sessionId = null,  // ✅ Exposed to LLMs as sessionId
          ...)
      {
          // Get batch from session manager using sessionId
          var batch = SessionManager.GetSession(sessionId);
          if (batch == null) {
              return JsonSerializer.Serialize(new OperationResult {
                  Success = false,
                  ErrorMessage = $"Session '{sessionId}' not found",
                  IsError = true
              });
          }
      
          // Core still uses IExcelBatch with its internal batchId
          return await batch.ExecuteAsync((ctx, ct) => {
              return await commands.ImportAsync(batch, queryName, mCodeFile);
          });
      }
      
  • Ensure business errors are returned as JSON and McpException is only used for protocol/validation errors

4. Remove batch infrastructure (BatchSessionTool, WithBatchAsync, CLI BatchCommands)

  • Delete src/ExcelMcp.McpServer/Tools/BatchSessionTool.cs and remove any references to it.
  • Delete src/ExcelMcp.CLI/Commands/BatchCommands.cs and remove corresponding wiring and help text from src/ExcelMcp.CLI/Program.cs.
  • Remove ExcelToolsBase.WithBatchAsync and any if (batchId != null) { ... } else { ... } dual-path logic.
  • Rename any remaining _activeBatches fields to _activeSessions (or remove entirely if replaced by SessionManager).

5. Update CLI to use --session-id parameter (map to Core's batchId)

  • Add or extend CLI file commands to mirror the MCP excel_file actions (open, save, close) and print/accept sessionId for scripting
  • Update all feature CLI commands to accept --session-id parameter (exposed to users as "session-id")
  • Inside CLI commands, map --session-id → batchId when calling Core:
    // CLI accepts --session-id from user
    public int Import(string[] args)
    {
        var sessionId = args.GetOption("--session-id");  // ✅ CLI uses sessionId
    
        // Map sessionId → batchId for Core layer
        var task = Task.Run(async () => {
            await using var batch = await ExcelSession.BeginBatchAsync(
                filePath, batchId: sessionId);  // ✅ Core still uses batchId
            return await _commands.ImportAsync(batch, queryName, mCodeFile);
        });
    }
    
  • Ensure CLI no longer creates temporary batches/sessions implicitly; instead, users/LLMs call open first, then operate, then close

6. Update tests for session lifecycle and adjust existing coverage

  • ComInterop/Core tests:
    • Keep all existing IExcelBatch/BeginBatchAsync references - Core layer unchanged
    • Keep behavior and patterns identical (same Excel COM semantics, timeouts, etc.)
  • MCP tests:
    • Rewrite flows that currently use excel_batch to use excel_file open/save/close and sessionId
    • Add tests for:
      • excel_file(open) returning a valid sessionId
      • excel_file(save) succeeds for a valid session
      • excel_file(close) closes without saving and returns success
      • Operations with missing/invalid sessionId return structured JSON errors
      • Read-only workflows: open → read → close (no save)
      • Discard workflows: open → modify → close (no save)

7. Update docs and prompts for session-only workflows

  • Delete batch-related prompt artifacts:
    • src/ExcelMcp.McpServer/Prompts/Content/excel_batch.md (or equivalent).
    • ExcelBatchModePrompts usage and any batch-mode guidance.
  • Update excel_file documentation and tool descriptions to:
    • Emphasize the required open/save/close lifecycle.
    • Make clear that close never saves and save is an explicit action.
  • Update all tool [Description] attributes to:
    • Describe sessionId as a required parameter.
    • Remove references to excelPath for non-file tools.
  • Update README.md, examples/*.ps1, examples/*.sh, and any relevant docs to show:
    • Default workflow: open → operations(sessionId) → save → close.
    • Read-only workflow: open → read → close.
    • Discard workflow: open → modify → close (no save).

This file captures the current implementation state of the Session API redesign and the remaining work needed to fully implement the "sessions only" Open/Save/Close model described in specs/SESSION-API-REDESIGN-SPEC.md.
Use it as a checklist while completing the refactor across ComInterop, Core, MCP Server, CLI, tests, and documentation.