fix(timesheets): shared create service for ts create and MCP - #68
Closed
jernejk wants to merge 2 commits into
Closed
fix(timesheets): shared create service for ts create and MCP#68jernejk wants to merge 2 commits into
jernejk wants to merge 2 commits into
Conversation
…y and staging stdio Locks the observable MCP contract before the CLI/MCP unification slices touch it. - Golden tools/list taken from the real stdio server, accounting off (18) and on (47) - Per-tool result goldens for all 47 tools plus generated empty and API-error cases - tools/call envelopes over stdio for six tools, proven identical to the direct calls - CLI/MCP parity runner with per-case permitted differences and write-traffic assertions - Reflection test mapping every tool to a registered CLI command, with a shrink-only allowlist - scripts/e2e/test-mcp-smoke.sh: staging stdio gate with non-production check and cleanup ConfigPaths honours TIMEPRO_CLI_CONFIG_DIR so a child MCP host can run against an isolated config instead of the developer's own tenants and feature flags. Closes #63 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
MCP create_timesheet built its own TimesheetRequest with no sell price, so a server that
cannot derive one answered 400 and entries created through MCP could differ from CLI ones
on the same inputs.
TimesheetCreateService now owns every resolution a create needs — sell price from the
client rate for the billable type, category from the repo mapping then recent entries,
location from the WFH defaults, deducted minutes, iteration, and the read-back that turns
an empty write response into the saved row. `ts create` and the MCP tool are adapters over
it. The interactive missing-rate workflow stays in the command: PrepareAsync reports a
missing rate and never writes one, and MCP answers with the `tp rate create` recovery
command instead.
The CreateTimesheet result golden changes deliberately. The tool returned the API's empty
body serialised as literal `null`, which no client could use; it now returns the CLI's
`{"success":true,"timesheetId":N,"timesheet":{...}}` document, serialised with the CLI's
options so the two are byte-identical. The create row in McpCliParityTable is at parity
with no permitted differences.
The populated golden case now writes the note and start time of the day's existing row so
the empty-body read-back has something to find.
Closes #57
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
One or more issues must be addressed before approval.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds a shared timesheet creation service used by both CLI and MCP, including pricing, category/location resolution, and read-back.
Changes:
- Refactors CLI and MCP creation through
TimesheetCreateService. - Adds parity, golden, configuration-isolation, and staging smoke coverage.
- Updates registrations and repository guidance.
File summaries
| File | Description |
|---|---|
| tests/SSW.TimePro.Cli.Tests/Infrastructure/ConfigPathsTests.cs | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Tests/Features/Timesheets/LessOptionTests.cs | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Mcp/WeekTokens.cs | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Mcp/TestConfigService.cs | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Mcp/McpToolResultGoldenTests.cs | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Mcp/McpToolInventory.cs | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Mcp/McpToolHost.cs | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Mcp/McpToolCase.cs | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Mcp/McpStdioToolCallTests.cs | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Mcp/McpStdioDiscoveryTests.cs | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Mcp/McpStdioClient.cs | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Mcp/JsonDiff.cs | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Mcp/IsolatedConfigDirectory.cs | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Mcp/Golden.cs | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Mcp/CliRunner.cs | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Mcp/CliMcpParityTests.cs | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Mcp/CliConsole.cs | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Mcp/CliBinary.cs | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/UpdateTimesheet.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/UpdateTimesheet.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/UpdateLeave.dryRun.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/UpdateLeave.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/SearchClients.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/SearchClients.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/SearchClients.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/QueryTimesheets.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/QueryTimesheets.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/QueryTimesheets.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ListRecurringInvoices.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ListRecurringInvoices.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ListRecurringInvoices.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ListProducts.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ListProducts.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ListProducts.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ListPaidReceipts.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ListPaidReceipts.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ListPaidReceipts.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ListLocations.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ListLocations.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ListLocations.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ListIterations.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ListIterations.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ListIterations.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ListInvoices.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ListInvoices.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ListInvoices.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ListCreditNotes.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ListCreditNotes.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ListCreditNotes.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ListClientRates.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ListClientRates.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ListClientRates.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ListCategories.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ListCategories.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ListCategories.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ListBillableTypes.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ListBillableTypes.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ListBillableTypes.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ListAllSkus.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ListAllSkus.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ListAllSkus.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ImportLeaveBalances.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/ImportLeaveBalances.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetUnpaidInvoicesByClient.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetUnpaidInvoicesByClient.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetUnpaidInvoicesByClient.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetUnbilledTimesheetsForClient.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetUnbilledTimesheetsForClient.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetUnbilledTimesheetsForClient.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetTimesheets.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetTimesheets.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetTimesheets.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetSuggestedTimesheets.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetSuggestedTimesheets.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetSuggestedTimesheets.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetRecurringInvoice.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetRecurringInvoice.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetRecurringInvoice.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetReceiptDetail.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetReceiptDetail.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetReceiptDetail.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetProjectsSummary.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetProjectsSummary.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetProjectsSummary.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetProjectsForClient.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetProjectsForClient.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetProjectsForClient.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetProductDiscountsForClient.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetProductDiscountsForClient.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetProductDiscountsForClient.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetProduct.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetProduct.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetProduct.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetPrepaidStatusPdf.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetPrepaidStatusPdf.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetPrepaidStatus.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetPrepaidStatus.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetPrepaidStatus.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetLocationAndMapping.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetLeaveEntries.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetLeaveEntries.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetLeaveEntries.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetLeaveBalanceStatus.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetLeaveBalanceStatus.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetLeaveBalanceStatus.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetLeaveBalance.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetLeaveBalance.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetLeaveBalance.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetInvoiceTimesheets.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetInvoiceTimesheets.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetInvoiceTimesheets.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetInvoicesByClient.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetInvoicesByClient.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetInvoicesByClient.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetInvoiceReceipts.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetInvoiceReceipts.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetInvoiceReceipts.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetInvoiceLines.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetInvoiceLines.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetInvoiceLines.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetInvoice.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetInvoice.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetInvoice.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetCurrentUser.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetCurrentUser.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetCurrentUser.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetCrmBookings.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetCrmBookings.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetCrmBookings.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetClientsWithOutstandingTime.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetClientsWithOutstandingTime.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetClientsWithOutstandingTime.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetClientRate.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetClientRate.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetClientRate.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetClientOutstanding.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetClientOutstanding.empty.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/GetClientOutstanding.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/DeleteTimesheet.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/DeleteTimesheet.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/CreateTimesheet.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/CreateTimesheet.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/CreateLeave.dryRun.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/CreateLeave.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/CheckWeek.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/CheckWeek.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/AcceptSuggestedTimesheet.populated.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Tools/AcceptSuggestedTimesheet.apiError.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Parity/UpdateTimesheet.0.mcp.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Parity/UpdateTimesheet.0.cli.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Parity/UpdateLeave.6.mcp.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Parity/UpdateLeave.6.cli.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Parity/ImportLeaveBalances.7.mcp.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Parity/ImportLeaveBalances.7.cli.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Parity/GetLeaveBalanceStatus.8.mcp.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Parity/GetLeaveBalanceStatus.8.cli.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Parity/DeleteTimesheet.3.mcp.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Parity/DeleteTimesheet.3.cli.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Parity/DeleteTimesheet.2.mcp.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Parity/DeleteTimesheet.2.cli.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Parity/CreateTimesheet.9.mcp.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Parity/CreateTimesheet.9.cli.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Parity/CreateLeave.5.mcp.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Parity/CreateLeave.5.cli.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Parity/CheckWeek.4.mcp.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Parity/CheckWeek.4.cli.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Parity/AcceptSuggestedTimesheet.1.mcp.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Parity/AcceptSuggestedTimesheet.1.cli.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Calls/update_timesheet.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Calls/list_iterations.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Calls/get_timesheets.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Calls/get_leave_entries.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Calls/get_client_rate.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Goldens/Mcp/Calls/delete_timesheet.json | Updated as part of this pull request. |
| tests/SSW.TimePro.Cli.Integration/Features/McpTimesheetWriteTests.cs | Updated as part of this pull request. |
| src/SSW.TimePro.Cli/Program.cs | Updated as part of this pull request. |
| src/SSW.TimePro.Cli/Infrastructure/Config/ConfigPaths.cs | Updated as part of this pull request. |
| src/SSW.TimePro.Cli/Features/Timesheets/TimesheetCreateService.cs | Updated as part of this pull request. |
| src/SSW.TimePro.Cli/Features/Mcp/Tools/TimesheetMcpTools.cs | Updated as part of this pull request. |
| src/SSW.TimePro.Cli/Features/Mcp/McpHostCommand.cs | Updated as part of this pull request. |
| scripts/e2e/test-mcp-smoke.sh | Updated as part of this pull request. |
| AGENTS.md | Updated as part of this pull request. |
| .gitignore | Updated as part of this pull request. |
Review details
Suppressed comments (5)
scripts/e2e/mcp_smoke.py:95
- The timer does not enforce the advertised timeout here:
readline()blocks until a line arrives, so the loop never gets back todeadline.is_set()while a hung MCP child is silent. Use a non-blocking/select-based read or a reader thread/queue so a stalled server cannot hang the E2E job indefinitely.
if deadline.is_set():
raise SmokeFailure(f"timed out waiting for response {request_id}")
line = self._process.stdout.readline()
if line == "":
scripts/e2e/mcp_smoke.py:303
- After a successful create, the failure path only reports
created_idand closes the process; it never attempts deletion. Any exception after the write (for example, a read-back mismatch or a tool error) therefore leaves staging data behind, despite the script's cleanup guarantee. Move deletion into a failure-safe cleanup path and make cleanup failure fail the run.
except SmokeFailure as failure:
print(f"MCP smoke FAILED: {failure}", file=sys.stderr)
if created_id is not None:
print(
f"LEFTOVER TEST DATA: timesheet {created_id} on {date} "
scripts/e2e/mcp_smoke.py:265
created_idis assigned only after the read-back succeeds, so if the create succeeds butget_timesheetsfails or returns an unexpected result, theexceptpath seesNoneand cannot identify the row for cleanup. Capture the returnedtimesheetIdimmediately, then replace it with the matched row's ID once read-back succeeds.
after = call_tool(process, "get_timesheets", {"date": date})
matches = [entry for entry in after if entry.get("notes") == note]
scripts/e2e/mcp_smoke.py:274
- Allowing
Nonehere means the smoke gate passes even ifcreate_timesheetregresses to the old null/empty result; it only verifies that some row exists. Since this change makes the returned ID part of the create result, require the reported ID to equal the row read back.
if reported_id not in (None, created_id):
tests/SSW.TimePro.Cli.Integration/Mcp/McpStdioClient.cs:65
- If
InitializeAsyncfails due to a timeout, cancellation, or a protocol error,StartAsyncexits without disposingclientor its child process. The MCP host can remain blocked on stdin after a failed test, and repeated failures leak processes; dispose the client in a catch before rethrowing.
- Files reviewed: 191/192 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| READ_TIMEOUT_SECONDS = 90 | ||
|
|
||
| CLIENT_ID = "NWIND" | ||
| PROJECT_ID = os.environ.get("TIMEPRO_MCP_SMOKE_PROJECT", "8W52M2") |
Comment on lines
+132
to
+137
| var active = rate?.Rate is not null | ||
| && (string.IsNullOrEmpty(rate.ExpiryDate) || RateResolver.IsActive(DateTime.Parse(rate.ExpiryDate), date)); | ||
|
|
||
| return active | ||
| ? RateResolver.SellPriceFor(billableId, rate!.Rate ?? 0m, rate.PrepaidRate ?? 0m) | ||
| : null; |
| # | ||
| # TIMEPRO_MCP_SMOKE_TP how to invoke the candidate CLI (default: tp) | ||
| # TIMEPRO_MCP_SMOKE_TENANT tenant config to bind (default: ssw-staging) | ||
| # TIMEPRO_MCP_SMOKE_PROJECT Northwind project that uses iterations (default: 8W52M2) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Slice 2 of #38. MCP
create_timesheetbuilt its ownTimesheetRequestwith noSellPrice, so areal server that cannot derive one answered 400 — the regression harness's staging smoke gate was
red on exactly this.
ts createmeanwhile resolved the client rate and sell price, discovered thecategory, handled deducted minutes and iteration, and returned the saved entry.
Both surfaces are now adapters over one
TimesheetCreateService.Closes #57
Based on #65 (
test/mcp-regression-harness), notmain— it uses the harness'sCliRunner,McpToolHost,NorthwindApi,McpCliParityTableand goldens. Merge #65 first; I will rebase ontomainon request.What changed
Features/Timesheets/TimesheetCreateService.cs, same prepare/apply shape asTimesheetUpdateService:TimesheetCreateOptions→PrepareAsync→ a plan carrying the fullyresolved
TimesheetRequest→ApplyAsyncposts and re-reads. It owns sell price (regular rate forB/W, prepaid forBPP), category (explicit → repo mapping → last fortnight's entries),location (WFH defaults + alias resolution), deducted minutes → hours, iteration and the read-back.
ts createis an adapter and keeps its preview, confirmation and interactive missing-rateworkflow:
PrepareAsyncreturns a missing-rate outcome and never creates a rate.CreateTimesheetcalls the same service. On a missing rate it returns an error with thetp rate createrecovery command and writes nothing — no implicit rate, no timesheet.TimesheetCreateServiceregistered in both DI containers (Program.cs,McpHostCommand.cs).scripts/e2e/mcp_smoke.py: dropped the wrapper that explained why create was expected to fail, andit now cross-checks the reported
timesheetIdagainst the row it reads back.Wire shape (per the agreed policy)
The CLI
ts create --jsondocument is unchanged. The MCP result does change, deliberately: thetool used to serialise the API's empty body as literal
null(locked inGoldens/Mcp/Tools/CreateTimesheet.populated.json), which no client could use. It now returns theCLI's
{"success":true,"timesheetId":N,"timesheet":{...}}, serialised with the CLI's options so thetwo documents are byte-identical — the policy's permitted exception, since
nullwas never a usablecontract. The create row in
McpCliParityTableisExpectParity = truewith no permitteddifferences. The
tools/listdiscovery goldens are untouched: no argument, description or schemachanged.
How verified
By execution:
New tests:
CliMcpCreatePayloadTests(12) — canonicalisedSaveTimesheetpayload equality betweents createand the tool forB/BPP/Wpricing, category precedence (explicit / repo mapping /recent entries, asserting the query is skipped when the mapping hits), location alias and WFH
default, iteration, and the identical result document. Plus: an expired rate produces zero
SaveClientRateand zeroSaveTimesheetrequests on both surfaces, and an empty write responseis followed by a read-back, never a second
SaveTimesheet.TimesheetCreateServiceTests(21) — the service's own resolution rules and apply outcomes.CliConsole:CliRunnerredirects the process-wideConsole.Out, so the two harness classes thatrun the real command tree now share one xUnit collection. Without it they raced and contaminated
each other's CLI goldens.
Forced failure (the checks can fail): setting
SellPrice = nullin the service fails 4 unit tests,the three pricing payload-equality cases and the create parity row.
Staging (
--tenant ssw-staging,isProduction: false,apiUrlcontainsstaging), CLI from sourceon project
8W52M2with an iteration:The MCP stdio gate is green end to end for the first time:
Both staging rows were deleted and their absence verified. No staging rate was created.
By inspection only: the interactive missing-rate prompt (
ts createwithout--yes/--json) — thenon-interactive and
--reject-if-rate-expiredbranches are covered by tests, the prompt itself isnot automatable here.
Left out
create_timesheetstill has no deducted-minutes argument, so that dimension of thepayload-equality suite is CLI-only. Adding the argument changes the tool schema and the discovery
goldens, which the policy puts in a contract release, not this patch.
DateTime.Parseon the rate expiry keeps its existing culture-sensitive behaviour; moving it toinvariant is a separate fix.
Risks
method/path/body, forbidden requests, call counts) are the guard, and staging confirmed a real
create/read-back/delete on both surfaces.
nullcreate result see a JSON object instead. That is the intended,documented break; nothing could have been reading a value out of
null.🤖 Generated with Claude Code