diff --git a/current/src/main/kotlin/io/github/jpicklyk/mcptask/current/application/tools/items/QueryItemsTool.kt b/current/src/main/kotlin/io/github/jpicklyk/mcptask/current/application/tools/items/QueryItemsTool.kt index 66ae2037..7c649e5c 100644 --- a/current/src/main/kotlin/io/github/jpicklyk/mcptask/current/application/tools/items/QueryItemsTool.kt +++ b/current/src/main/kotlin/io/github/jpicklyk/mcptask/current/application/tools/items/QueryItemsTool.kt @@ -476,7 +476,7 @@ Operations: get, search, overview val data = buildJsonObject { put("item", workItemToJson(item)) - put("childCounts", roleCounToJson(childCounts)) + put("childCounts", roleCountToJson(childCounts)) put("children", JsonArray(children.map { workItemToMinimalJson(it) })) } @@ -508,7 +508,7 @@ Operations: get, search, overview put("title", JsonPrimitive(item.title)) put("role", JsonPrimitive(item.role.name.lowercase())) put("priority", JsonPrimitive(item.priority.name.lowercase())) - put("childCounts", roleCounToJson(childCounts)) + put("childCounts", roleCountToJson(childCounts)) if (includeChildren) { val children = when (val result = context.workItemRepository().findChildren(item.id)) { is Result.Success -> result.data @@ -567,7 +567,7 @@ Operations: get, search, overview item.tags?.let { put("tags", JsonPrimitive(it)) } } - private fun roleCounToJson(counts: Map): JsonObject = buildJsonObject { + private fun roleCountToJson(counts: Map): JsonObject = buildJsonObject { for (role in Role.entries) { put(role.name.lowercase(), JsonPrimitive(counts[role] ?: 0)) } diff --git a/current/src/main/kotlin/io/github/jpicklyk/mcptask/current/infrastructure/repository/SQLiteWorkItemRepository.kt b/current/src/main/kotlin/io/github/jpicklyk/mcptask/current/infrastructure/repository/SQLiteWorkItemRepository.kt index b4e4aa1c..8a728c38 100644 --- a/current/src/main/kotlin/io/github/jpicklyk/mcptask/current/infrastructure/repository/SQLiteWorkItemRepository.kt +++ b/current/src/main/kotlin/io/github/jpicklyk/mcptask/current/infrastructure/repository/SQLiteWorkItemRepository.kt @@ -42,7 +42,7 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W newSuspendedTransaction(db = databaseManager.getDatabase()) { val row = WorkItemsTable.selectAll().where { WorkItemsTable.id eq id }.singleOrNull() if (row != null) { - Result.Success(mapRowToWorkItem(row)) + Result.Success(toWorkItem(row)) } else { Result.Error(RepositoryError.NotFound(id, "WorkItem not found with id: $id")) } @@ -133,7 +133,7 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W val items = WorkItemsTable.selectAll() .where { WorkItemsTable.parentId eq parentId } .limit(limit) - .mapNotNull { mapRowToWorkItemSafe(it) } + .mapNotNull { toWorkItemOrNull(it) } Result.Success(items) } } catch (e: Exception) { @@ -145,7 +145,7 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W val items = WorkItemsTable.selectAll() .where { WorkItemsTable.role eq role.name.lowercase() } .limit(limit) - .mapNotNull { mapRowToWorkItemSafe(it) } + .mapNotNull { toWorkItemOrNull(it) } Result.Success(items) } } catch (e: Exception) { @@ -157,7 +157,7 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W val items = WorkItemsTable.selectAll() .where { WorkItemsTable.depth eq depth } .limit(limit) - .mapNotNull { mapRowToWorkItemSafe(it) } + .mapNotNull { toWorkItemOrNull(it) } Result.Success(items) } } catch (e: Exception) { @@ -169,7 +169,7 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W val row = WorkItemsTable.selectAll() .where { WorkItemsTable.parentId.isNull() and (WorkItemsTable.depth eq 0) } .singleOrNull() - Result.Success(row?.let { mapRowToWorkItemSafe(it) }) + Result.Success(row?.let { toWorkItemOrNull(it) }) } } catch (e: Exception) { Result.Error(RepositoryError.DatabaseError("Failed to find root WorkItem: ${e.message}", e)) @@ -183,7 +183,7 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W (WorkItemsTable.title like pattern) or (WorkItemsTable.summary like pattern) } .limit(limit) - .mapNotNull { mapRowToWorkItemSafe(it) } + .mapNotNull { toWorkItemOrNull(it) } Result.Success(items) } } catch (e: Exception) { @@ -203,7 +203,7 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W newSuspendedTransaction(db = databaseManager.getDatabase()) { val items = WorkItemsTable.selectAll() .where { WorkItemsTable.parentId eq parentId } - .mapNotNull { mapRowToWorkItemSafe(it) } + .mapNotNull { toWorkItemOrNull(it) } Result.Success(items) } } catch (e: Exception) { @@ -273,7 +273,7 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W .orderBy(sortColumn, order) .limit(limit) .offset(offset.toLong()) - .mapNotNull { mapRowToWorkItemSafe(it) } + .mapNotNull { toWorkItemOrNull(it) } Result.Success(items) } @@ -333,7 +333,7 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W newSuspendedTransaction(db = databaseManager.getDatabase()) { val counts = WorkItemsTable.selectAll() .where { WorkItemsTable.parentId eq parentId } - .mapNotNull { mapRowToWorkItemSafe(it) } + .mapNotNull { toWorkItemOrNull(it) } .groupBy { it.role } .mapValues { (_, items) -> items.size } Result.Success(counts) @@ -347,7 +347,7 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W val items = WorkItemsTable.selectAll() .where { WorkItemsTable.parentId.isNull() } .limit(limit) - .mapNotNull { mapRowToWorkItemSafe(it) } + .mapNotNull { toWorkItemOrNull(it) } Result.Success(items) } } catch (e: Exception) { @@ -364,7 +364,7 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W val current = queue.removeFirst() val children = WorkItemsTable.selectAll() .where { WorkItemsTable.parentId eq current } - .mapNotNull { mapRowToWorkItemSafe(it) } + .mapNotNull { toWorkItemOrNull(it) } results.addAll(children) queue.addAll(children.map { it.id }) } @@ -383,7 +383,7 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W Result.Success( WorkItemsTable.selectAll() .where { WorkItemsTable.id inList entityIds } - .mapNotNull { mapRowToWorkItemSafe(it) } + .mapNotNull { toWorkItemOrNull(it) } ) } } catch (e: Exception) { @@ -416,7 +416,7 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W WorkItemsTable.id.castTo(VarCharColumnType(36)).like("$formattedPrefix%") } .limit(limit) - .mapNotNull { mapRowToWorkItemSafe(it) } + .mapNotNull { toWorkItemOrNull(it) } Result.Success(items) } } catch (e: Exception) { @@ -452,7 +452,7 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W val inputEntityIds = itemIds.map { EntityID(it, WorkItemsTable) } val inputItems = WorkItemsTable.selectAll() .where { WorkItemsTable.id inList inputEntityIds } - .mapNotNull { mapRowToWorkItemSafe(it) } + .mapNotNull { toWorkItemOrNull(it) } inputItems.forEach { cache[it.id.toString()] = it } // BFS upward: collect all parentIds that need fetching @@ -461,7 +461,7 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W val fetchEntityIds = toFetch.map { EntityID(UUID.fromString(it), WorkItemsTable) } val fetched = WorkItemsTable.selectAll() .where { WorkItemsTable.id inList fetchEntityIds } - .mapNotNull { mapRowToWorkItemSafe(it) } + .mapNotNull { toWorkItemOrNull(it) } fetched.forEach { cache[it.id.toString()] = it } toFetch = fetched.mapNotNull { it.parentId }.map { it.toString() }.toSet() - cache.keys } @@ -506,7 +506,7 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W }.reduce { acc, op -> acc or op } } - private fun mapRowToWorkItem(row: ResultRow): WorkItem { + private fun toWorkItem(row: ResultRow): WorkItem { return WorkItem( id = row[WorkItemsTable.id].value, parentId = row[WorkItemsTable.parentId], @@ -530,15 +530,15 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W } /** - * Safe variant of [mapRowToWorkItem] for bulk-read operations. + * Safe variant of [toWorkItem] that returns null on failure, for bulk-read operations. * * Returns null and logs a warning if a row contains data that fails domain validation * (e.g. an oversized title written by an older version of the server). This prevents * a single corrupt row from crashing an entire list query. */ - private fun mapRowToWorkItemSafe(row: ResultRow): WorkItem? { + private fun toWorkItemOrNull(row: ResultRow): WorkItem? { return try { - mapRowToWorkItem(row) + toWorkItem(row) } catch (e: Exception) { logger.warn( "Skipping corrupt WorkItem row (id={}): {}", diff --git a/current/src/main/kotlin/io/github/jpicklyk/mcptask/current/interfaces/mcp/CurrentMcpServer.kt b/current/src/main/kotlin/io/github/jpicklyk/mcptask/current/interfaces/mcp/CurrentMcpServer.kt index 0924c818..0faa3a73 100644 --- a/current/src/main/kotlin/io/github/jpicklyk/mcptask/current/interfaces/mcp/CurrentMcpServer.kt +++ b/current/src/main/kotlin/io/github/jpicklyk/mcptask/current/interfaces/mcp/CurrentMcpServer.kt @@ -79,13 +79,7 @@ class CurrentMcpServer( val toolContext = ToolExecutionContext(repositoryProvider, noteSchemaService) logger.info("Repository provider and tool context initialized") - // Configure MCP server - val serverName = System.getenv("MCP_SERVER_NAME") ?: "mcp-task-orchestrator-current" - val server = configureServer(serverName) - mcpSdkServer = server - - // Register MCP tools - val adapter = McpToolAdapter() + // Build tool list val tools = listOf( // Phase 1: CRUD ManageItemsTool(), @@ -106,6 +100,15 @@ class CurrentMcpServer( // Phase 3: Context GetContextTool() ) + + // Configure MCP server + val serverName = System.getenv("MCP_SERVER_NAME") ?: "mcp-task-orchestrator-current" + val toolNames = tools.joinToString(", ") { it.name } + val server = configureServer(serverName, tools.size, toolNames) + mcpSdkServer = server + + // Register MCP tools + val adapter = McpToolAdapter() adapter.registerToolsWithServer(server, tools, toolContext) logger.info("Registered ${tools.size} MCP tools") @@ -129,6 +132,15 @@ class CurrentMcpServer( mcpSdkServer?.close() } + private fun registerCommonCleanup(server: Server) { + shutdownCoordinator?.addCleanupAction("Close MCP Server") { + runBlocking { server.close() } + } + shutdownCoordinator?.addCleanupAction("Close Database") { + databaseManager.shutdown() + } + } + private suspend fun runStdioTransport(server: Server, serverName: String, toolCount: Int) { logger.info("Starting MCP server with stdio transport...") @@ -137,12 +149,7 @@ class CurrentMcpServer( outputStream = System.out.asSink().buffered() ) - shutdownCoordinator?.addCleanupAction("Close MCP Server") { - runBlocking { server.close() } - } - shutdownCoordinator?.addCleanupAction("Close Database") { - databaseManager.shutdown() - } + registerCommonCleanup(server) val done = Job() server.onClose { @@ -176,11 +183,11 @@ class CurrentMcpServer( ktorServer.stop(gracePeriodMillis = 1000, timeoutMillis = 5000) done.complete() } - shutdownCoordinator?.addCleanupAction("Close MCP Server") { - runBlocking { server.close() } - } - shutdownCoordinator?.addCleanupAction("Close Database") { - databaseManager.shutdown() + registerCommonCleanup(server) + + server.onClose { + logger.info("Server closed") + if (shutdownCoordinator == null) databaseManager.shutdown() } if (shutdownCoordinator == null) { @@ -203,7 +210,7 @@ class CurrentMcpServer( /** * Configures the MCP SDK server with capabilities for tools, prompts, and resources. */ - private fun configureServer(serverName: String): Server { + private fun configureServer(serverName: String, toolCount: Int, toolNames: String): Server { return Server( serverInfo = Implementation( name = serverName, @@ -217,7 +224,7 @@ class CurrentMcpServer( logging = JsonObject(emptyMap()) ) ), - instructions = "Current (v3) MCP Task Orchestrator — 13 tools: manage_items, query_items, manage_notes, query_notes, manage_dependencies, query_dependencies, advance_item, get_next_status, get_next_item, get_blocked_items, complete_tree, create_work_tree, get_context" + instructions = "Current (v3) MCP Task Orchestrator — $toolCount tools: $toolNames" ) } }