From 1e263f08c0401b5c1eeb22968b23a01612bd7af9 Mon Sep 17 00:00:00 2001 From: Chris Hendrickson Date: Sat, 25 Apr 2026 16:01:36 -0400 Subject: [PATCH] fix: tolerate numeric args from MCP JSON in all kanban commands Replace fragile 'as String'/'as String?' casts in callAsTool() with string interpolation so that numeric values sent by MCP JSON clients (double instead of String) no longer throw a type cast error. Fixes: type 'double' is not a subtype of type 'String' in type cast Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../kanban/lib/src/commands/add_comment_command.dart | 4 ++-- .../kanban/lib/src/commands/archive_command.dart | 2 +- packages/kanban/lib/src/commands/board_command.dart | 8 +++++--- packages/kanban/lib/src/commands/create_command.dart | 8 ++++---- packages/kanban/lib/src/commands/delete_command.dart | 2 +- packages/kanban/lib/src/commands/get_command.dart | 2 +- packages/kanban/lib/src/commands/link_command.dart | 6 +++--- packages/kanban/lib/src/commands/list_command.dart | 10 ++++++---- packages/kanban/lib/src/commands/move_command.dart | 4 ++-- packages/kanban/lib/src/commands/search_command.dart | 12 +++++++----- .../kanban/lib/src/commands/unarchive_command.dart | 4 ++-- packages/kanban/lib/src/commands/unlink_command.dart | 4 ++-- packages/kanban/lib/src/commands/update_command.dart | 10 +++++----- 13 files changed, 41 insertions(+), 35 deletions(-) diff --git a/packages/kanban/lib/src/commands/add_comment_command.dart b/packages/kanban/lib/src/commands/add_comment_command.dart index a7a0811..3acf6ab 100644 --- a/packages/kanban/lib/src/commands/add_comment_command.dart +++ b/packages/kanban/lib/src/commands/add_comment_command.dart @@ -35,8 +35,8 @@ class AddCommentCommand extends DewCommand with DewToolCommand { @override Future callAsTool(Map args) async { - final id = (args['id'] as String).toUpperCase(); - final comment = args['comment'] as String; + final id = '${args['id']}'.toUpperCase(); + final comment = '${args['comment']}'; final context = await ProjectContext.find(fs: _fs); final store = TicketStore( diff --git a/packages/kanban/lib/src/commands/archive_command.dart b/packages/kanban/lib/src/commands/archive_command.dart index 3c3a30e..9cef477 100644 --- a/packages/kanban/lib/src/commands/archive_command.dart +++ b/packages/kanban/lib/src/commands/archive_command.dart @@ -30,7 +30,7 @@ class ArchiveCommand extends DewCommand with DewToolCommand { @override Future callAsTool(Map args) async { - final id = (args['id'] as String).toUpperCase(); + final id = '${args['id']}'.toUpperCase(); final context = await ProjectContext.find(fs: _fs); final config = context.config.kanban; diff --git a/packages/kanban/lib/src/commands/board_command.dart b/packages/kanban/lib/src/commands/board_command.dart index 5fa7f7a..e8bfcf3 100644 --- a/packages/kanban/lib/src/commands/board_command.dart +++ b/packages/kanban/lib/src/commands/board_command.dart @@ -27,9 +27,11 @@ class BoardCommand extends DewCommand with DewToolCommand { @override Future callAsTool(Map args) async { - final typeFilter = args['type'] as String?; - final labelFilter = args['label'] as String?; - final milestoneFilter = args['milestone'] as String?; + final typeFilter = args['type'] != null ? '${args['type']}' : null; + final labelFilter = args['label'] != null ? '${args['label']}' : null; + final milestoneFilter = args['milestone'] != null + ? '${args['milestone']}' + : null; final context = await ProjectContext.find(fs: _fs); final config = context.config.kanban; diff --git a/packages/kanban/lib/src/commands/create_command.dart b/packages/kanban/lib/src/commands/create_command.dart index 6b1b7ac..a2c4ffb 100644 --- a/packages/kanban/lib/src/commands/create_command.dart +++ b/packages/kanban/lib/src/commands/create_command.dart @@ -43,10 +43,10 @@ class CreateCommand extends DewCommand with DewToolCommand { final context = await ProjectContext.find(fs: _fs); final config = context.config.kanban; - final title = args['title'] as String; - final typeId = args['type'] as String; - final columnArg = args['column'] as String?; - final body = args['body'] as String? ?? ''; + final title = '${args['title']}'; + final typeId = '${args['type']}'; + final columnArg = args['column'] != null ? '${args['column']}' : null; + final body = args['body'] != null ? '${args['body']}' : ''; final milestones = _toStringList(args['milestone']); final labels = _toStringList(args['label']); diff --git a/packages/kanban/lib/src/commands/delete_command.dart b/packages/kanban/lib/src/commands/delete_command.dart index 49e2d98..ecd5215 100644 --- a/packages/kanban/lib/src/commands/delete_command.dart +++ b/packages/kanban/lib/src/commands/delete_command.dart @@ -28,7 +28,7 @@ class DeleteCommand extends DewCommand with DewToolCommand { @override Future callAsTool(Map args) async { - final id = (args['id'] as String).toUpperCase(); + final id = '${args['id']}'.toUpperCase(); final context = await ProjectContext.find(fs: _fs); final store = TicketStore( kanbanDir: context.dirs.kanban, diff --git a/packages/kanban/lib/src/commands/get_command.dart b/packages/kanban/lib/src/commands/get_command.dart index a256aba..93019e9 100644 --- a/packages/kanban/lib/src/commands/get_command.dart +++ b/packages/kanban/lib/src/commands/get_command.dart @@ -29,7 +29,7 @@ class GetCommand extends DewCommand with DewToolCommand { @override Future callAsTool(Map args) async { - final id = (args['id'] as String).toUpperCase(); + final id = '${args['id']}'.toUpperCase(); final context = await ProjectContext.find(fs: _fs); final store = TicketStore( kanbanDir: context.dirs.kanban, diff --git a/packages/kanban/lib/src/commands/link_command.dart b/packages/kanban/lib/src/commands/link_command.dart index 51eaaf3..aeb4635 100644 --- a/packages/kanban/lib/src/commands/link_command.dart +++ b/packages/kanban/lib/src/commands/link_command.dart @@ -40,9 +40,9 @@ class LinkCommand extends DewCommand with DewToolCommand { @override Future callAsTool(Map args) async { - final id = (args['id'] as String).toUpperCase(); - final targetId = (args['target'] as String).toUpperCase(); - final type = args['type'] as String; + final id = '${args['id']}'.toUpperCase(); + final targetId = '${args['target']}'.toUpperCase(); + final type = '${args['type']}'; if (id == targetId) throw ArgumentError('A ticket cannot be linked to itself.'); diff --git a/packages/kanban/lib/src/commands/list_command.dart b/packages/kanban/lib/src/commands/list_command.dart index 3484a76..b8fc102 100644 --- a/packages/kanban/lib/src/commands/list_command.dart +++ b/packages/kanban/lib/src/commands/list_command.dart @@ -38,10 +38,12 @@ class ListCommand extends DewCommand with DewToolCommand { @override Future callAsTool(Map args) async { - final columnFilter = args['column'] as String?; - final typeFilter = args['type'] as String?; - final labelFilter = args['label'] as String?; - final milestoneFilter = args['milestone'] as String?; + final columnFilter = args['column'] != null ? '${args['column']}' : null; + final typeFilter = args['type'] != null ? '${args['type']}' : null; + final labelFilter = args['label'] != null ? '${args['label']}' : null; + final milestoneFilter = args['milestone'] != null + ? '${args['milestone']}' + : null; final includeArchived = args['include-archived'] as bool? ?? false; final context = await ProjectContext.find(fs: _fs); diff --git a/packages/kanban/lib/src/commands/move_command.dart b/packages/kanban/lib/src/commands/move_command.dart index bd4ef71..2bbd7ce 100644 --- a/packages/kanban/lib/src/commands/move_command.dart +++ b/packages/kanban/lib/src/commands/move_command.dart @@ -31,8 +31,8 @@ class MoveCommand extends DewCommand with DewToolCommand { @override Future callAsTool(Map args) async { - final id = (args['id'] as String).toUpperCase(); - final column = args['column'] as String; + final id = '${args['id']}'.toUpperCase(); + final column = '${args['column']}'; final context = await ProjectContext.find(fs: _fs); final config = context.config.kanban; diff --git a/packages/kanban/lib/src/commands/search_command.dart b/packages/kanban/lib/src/commands/search_command.dart index 7aa36c7..6bb16a2 100644 --- a/packages/kanban/lib/src/commands/search_command.dart +++ b/packages/kanban/lib/src/commands/search_command.dart @@ -46,11 +46,13 @@ class SearchCommand extends DewCommand with DewToolCommand { @override Future callAsTool(Map args) async { - final query = (args['query'] as String).toLowerCase(); - final columnFilter = args['column'] as String?; - final typeFilter = args['type'] as String?; - final labelFilter = args['label'] as String?; - final milestoneFilter = args['milestone'] as String?; + final query = '${args['query']}'.toLowerCase(); + final columnFilter = args['column'] != null ? '${args['column']}' : null; + final typeFilter = args['type'] != null ? '${args['type']}' : null; + final labelFilter = args['label'] != null ? '${args['label']}' : null; + final milestoneFilter = args['milestone'] != null + ? '${args['milestone']}' + : null; final includeArchived = args['include-archived'] as bool? ?? false; final context = await ProjectContext.find(fs: _fs); diff --git a/packages/kanban/lib/src/commands/unarchive_command.dart b/packages/kanban/lib/src/commands/unarchive_command.dart index 384ff9b..f30a0f8 100644 --- a/packages/kanban/lib/src/commands/unarchive_command.dart +++ b/packages/kanban/lib/src/commands/unarchive_command.dart @@ -35,7 +35,7 @@ class UnarchiveCommand extends DewCommand with DewToolCommand { @override Future callAsTool(Map args) async { - final id = (args['id'] as String).toUpperCase(); + final id = '${args['id']}'.toUpperCase(); final context = await ProjectContext.find(fs: _fs); final config = context.config.kanban; @@ -50,7 +50,7 @@ class UnarchiveCommand extends DewCommand with DewToolCommand { if (ticket == null) throw ArgumentError('Ticket $id not found.'); if (ticket.column != 'archive') return '$id is not archived.'; - final columnArg = args['column'] as String?; + final columnArg = args['column'] != null ? '${args['column']}' : null; final targetColumn = columnArg ?? config.columns.first.id; if (!config.columns.any((c) => c.id == targetColumn)) { throw ArgumentError( diff --git a/packages/kanban/lib/src/commands/unlink_command.dart b/packages/kanban/lib/src/commands/unlink_command.dart index d6d6666..c41d301 100644 --- a/packages/kanban/lib/src/commands/unlink_command.dart +++ b/packages/kanban/lib/src/commands/unlink_command.dart @@ -30,8 +30,8 @@ class UnlinkCommand extends DewCommand with DewToolCommand { @override Future callAsTool(Map args) async { - final id = (args['id'] as String).toUpperCase(); - final targetId = (args['target'] as String).toUpperCase(); + final id = '${args['id']}'.toUpperCase(); + final targetId = '${args['target']}'.toUpperCase(); final context = await ProjectContext.find(fs: _fs); final store = TicketStore( diff --git a/packages/kanban/lib/src/commands/update_command.dart b/packages/kanban/lib/src/commands/update_command.dart index d9095b7..918ef75 100644 --- a/packages/kanban/lib/src/commands/update_command.dart +++ b/packages/kanban/lib/src/commands/update_command.dart @@ -37,11 +37,11 @@ class UpdateCommand extends DewCommand with DewToolCommand { @override Future callAsTool(Map args) async { - final id = (args['id'] as String).toUpperCase(); - final title = args['title'] as String?; - final typeId = args['type'] as String?; - final column = args['column'] as String?; - final body = args['body'] as String?; + final id = '${args['id']}'.toUpperCase(); + final title = args['title'] != null ? '${args['title']}' : null; + final typeId = args['type'] != null ? '${args['type']}' : null; + final column = args['column'] != null ? '${args['column']}' : null; + final body = args['body'] != null ? '${args['body']}' : null; final rawMilestones = args['milestone'] as List?; final milestones = rawMilestones != null && rawMilestones.isNotEmpty ? rawMilestones.cast().where((s) => s.isNotEmpty).toList()