Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/kan-967-set-board-sort-echo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
bump: patch
---

Fixed the MCP `set_board_sort` tool reporting `null` for a dimension that was
omitted from the request but actually resolved and persisted. When you set only
the field (or only the order), the response now echoes the concrete value that
was saved, matching the CLI.
7 changes: 5 additions & 2 deletions crates/kanban-mcp/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ impl McpContext {
&mut self,
sort: Option<BoardSortField>,
order: Option<SortOrder>,
) -> KanbanResult<()> {
) -> KanbanResult<(BoardSortField, SortOrder)> {
let config = self.inner.app_config();
let field = sort.unwrap_or_else(|| {
config
Expand All @@ -91,7 +91,10 @@ impl McpContext {
.and_then(|s| SortOrder::from_str(s).ok())
.unwrap_or(DEFAULT_BOARD_SORT_LIVE.1)
});
self.inner.set_board_sort(field, order)
self.inner.set_board_sort(field, order)?;
// Return the RESOLVED pair so the tool echoes the value actually
// persisted, not the (possibly-omitted) raw request.
Ok((field, order))
}

/// Create a board from a full spec + optional client id, funneling through
Expand Down
8 changes: 5 additions & 3 deletions crates/kanban-mcp/src/tools/board.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,13 +176,15 @@ impl KanbanMcpServer {
.map(parse_board_sort_field)
.transpose()?;
let order = req.order.as_deref().map(parse_sort_order).transpose()?;
locked_write(&self.ctx, |ctx| {
let (resolved_field, resolved_order) = locked_write(&self.ctx, |ctx| {
ctx.set_board_sort(field, order).map_err(kanban_err_to_mcp)
})
.await?;
// Echo the RESOLVED values actually persisted, so an omitted dimension
// is reported concretely instead of null (CLI parity).
to_call_tool_result_json(serde_json::json!({
"board_sort_field": field.map(|f| f.to_string()),
"board_sort_order": order.map(|o| o.to_string()),
"board_sort_field": resolved_field.to_string(),
"board_sort_order": resolved_order.to_string(),
}))
}

Expand Down
21 changes: 21 additions & 0 deletions crates/kanban-mcp/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2966,3 +2966,24 @@ async fn tool_get_board_stamps_archived_at_for_archived_board() {
"archived board get must stamp archived_at: {archived}"
);
}

// KAN-967: set_board_sort must echo the RESOLVED value actually persisted, not
// the raw (possibly-omitted) request. Omitting `order` must not report null.
#[tokio::test]
async fn test_mcp_set_board_sort_echoes_resolved_order_when_omitted() {
let (server, _tmp) = setup_server().await;
let resp = text_payload(
&server
.tool_set_board_sort(Parameters(kanban_mcp::SetBoardSortRequest {
sort: Some("name".into()),
order: None,
}))
.await
.unwrap(),
);
assert_eq!(resp["board_sort_field"], "name");
assert!(
resp["board_sort_order"].is_string(),
"omitted order must echo the resolved value, not null: {resp}"
);
}
Loading