Skip to content

Commit e029d80

Browse files
jpicklykclaude
andcommitted
merge: resolve conflicts with main after input validation hardening
Keep refactored code (extension methods, transaction wrapper, toWorkItemOrNull) and incorporate time range validation + offset bounds check from main. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2 parents fb3a4c9 + 2d48c85 commit e029d80

File tree

2 files changed

+46
-39
lines changed

2 files changed

+46
-39
lines changed

current/src/main/kotlin/io/github/jpicklyk/mcptask/current/infrastructure/repository/SQLiteWorkItemRepository.kt

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W
4141
databaseManager.suspendedTransaction("Failed to get WorkItem by id") {
4242
val row = WorkItemsTable.selectAll().where { WorkItemsTable.id eq id }.singleOrNull()
4343
if (row != null) {
44-
Result.Success(mapRowToWorkItem(row))
44+
Result.Success(toWorkItem(row))
4545
} else {
4646
Result.Error(RepositoryError.NotFound(id, "WorkItem not found with id: $id"))
4747
}
@@ -120,7 +120,7 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W
120120
val items = WorkItemsTable.selectAll()
121121
.where { WorkItemsTable.parentId eq parentId }
122122
.limit(limit)
123-
.mapNotNull { mapRowToWorkItemSafe(it) }
123+
.mapNotNull { toWorkItemOrNull(it) }
124124
Result.Success(items)
125125
}
126126

@@ -129,7 +129,7 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W
129129
val items = WorkItemsTable.selectAll()
130130
.where { WorkItemsTable.role eq role.name.lowercase() }
131131
.limit(limit)
132-
.mapNotNull { mapRowToWorkItemSafe(it) }
132+
.mapNotNull { toWorkItemOrNull(it) }
133133
Result.Success(items)
134134
}
135135

@@ -138,7 +138,7 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W
138138
val items = WorkItemsTable.selectAll()
139139
.where { WorkItemsTable.depth eq depth }
140140
.limit(limit)
141-
.mapNotNull { mapRowToWorkItemSafe(it) }
141+
.mapNotNull { toWorkItemOrNull(it) }
142142
Result.Success(items)
143143
}
144144

@@ -147,7 +147,7 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W
147147
val row = WorkItemsTable.selectAll()
148148
.where { WorkItemsTable.parentId.isNull() and (WorkItemsTable.depth eq 0) }
149149
.singleOrNull()
150-
Result.Success(row?.let { mapRowToWorkItemSafe(it) })
150+
Result.Success(row?.let { toWorkItemOrNull(it) })
151151
}
152152

153153
override suspend fun search(query: String, limit: Int): Result<List<WorkItem>> =
@@ -158,7 +158,7 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W
158158
(WorkItemsTable.title like pattern) or (WorkItemsTable.summary like pattern)
159159
}
160160
.limit(limit)
161-
.mapNotNull { mapRowToWorkItemSafe(it) }
161+
.mapNotNull { toWorkItemOrNull(it) }
162162
Result.Success(items)
163163
}
164164

@@ -172,7 +172,7 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W
172172
databaseManager.suspendedTransaction("Failed to find children of WorkItem") {
173173
val items = WorkItemsTable.selectAll()
174174
.where { WorkItemsTable.parentId eq parentId }
175-
.mapNotNull { mapRowToWorkItemSafe(it) }
175+
.mapNotNull { toWorkItemOrNull(it) }
176176
Result.Success(items)
177177
}
178178

@@ -218,7 +218,7 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W
218218
.orderBy(sortColumn, order)
219219
.limit(limit)
220220
.offset(offset.toLong())
221-
.mapNotNull { mapRowToWorkItemSafe(it) }
221+
.mapNotNull { toWorkItemOrNull(it) }
222222

223223
Result.Success(items)
224224
}
@@ -251,7 +251,7 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W
251251
databaseManager.suspendedTransaction("Failed to count children by role") {
252252
val counts = WorkItemsTable.selectAll()
253253
.where { WorkItemsTable.parentId eq parentId }
254-
.mapNotNull { mapRowToWorkItemSafe(it) }
254+
.mapNotNull { toWorkItemOrNull(it) }
255255
.groupBy { it.role }
256256
.mapValues { (_, items) -> items.size }
257257
Result.Success(counts)
@@ -262,7 +262,7 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W
262262
val items = WorkItemsTable.selectAll()
263263
.where { WorkItemsTable.parentId.isNull() }
264264
.limit(limit)
265-
.mapNotNull { mapRowToWorkItemSafe(it) }
265+
.mapNotNull { toWorkItemOrNull(it) }
266266
Result.Success(items)
267267
}
268268

@@ -276,7 +276,7 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W
276276
val current = queue.removeFirst()
277277
val children = WorkItemsTable.selectAll()
278278
.where { WorkItemsTable.parentId eq current }
279-
.mapNotNull { mapRowToWorkItemSafe(it) }
279+
.mapNotNull { toWorkItemOrNull(it) }
280280
results.addAll(children)
281281
queue.addAll(children.map { it.id })
282282
}
@@ -291,7 +291,7 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W
291291
Result.Success(
292292
WorkItemsTable.selectAll()
293293
.where { WorkItemsTable.id inList entityIds }
294-
.mapNotNull { mapRowToWorkItemSafe(it) }
294+
.mapNotNull { toWorkItemOrNull(it) }
295295
)
296296
}
297297
}
@@ -316,7 +316,7 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W
316316
WorkItemsTable.id.castTo<String>(VarCharColumnType(36)).like("$formattedPrefix%")
317317
}
318318
.limit(limit)
319-
.mapNotNull { mapRowToWorkItemSafe(it) }
319+
.mapNotNull { toWorkItemOrNull(it) }
320320
Result.Success(items)
321321
}
322322
}
@@ -349,7 +349,7 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W
349349
val inputEntityIds = itemIds.map { EntityID(it, WorkItemsTable) }
350350
val inputItems = WorkItemsTable.selectAll()
351351
.where { WorkItemsTable.id inList inputEntityIds }
352-
.mapNotNull { mapRowToWorkItemSafe(it) }
352+
.mapNotNull { toWorkItemOrNull(it) }
353353
inputItems.forEach { cache[it.id.toString()] = it }
354354

355355
// BFS upward: collect all parentIds that need fetching
@@ -358,7 +358,7 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W
358358
val fetchEntityIds = toFetch.map { EntityID(UUID.fromString(it), WorkItemsTable) }
359359
val fetched = WorkItemsTable.selectAll()
360360
.where { WorkItemsTable.id inList fetchEntityIds }
361-
.mapNotNull { mapRowToWorkItemSafe(it) }
361+
.mapNotNull { toWorkItemOrNull(it) }
362362
fetched.forEach { cache[it.id.toString()] = it }
363363
toFetch = fetched.mapNotNull { it.parentId }.map { it.toString() }.toSet() - cache.keys
364364
}
@@ -445,7 +445,7 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W
445445
}.reduce { acc, op -> acc or op }
446446
}
447447

448-
private fun mapRowToWorkItem(row: ResultRow): WorkItem {
448+
private fun toWorkItem(row: ResultRow): WorkItem {
449449
return WorkItem(
450450
id = row[WorkItemsTable.id].value,
451451
parentId = row[WorkItemsTable.parentId],
@@ -469,15 +469,15 @@ class SQLiteWorkItemRepository(private val databaseManager: DatabaseManager) : W
469469
}
470470

471471
/**
472-
* Safe variant of [mapRowToWorkItem] for bulk-read operations.
472+
* Safe variant of [toWorkItem] that returns null on failure, for bulk-read operations.
473473
*
474474
* Returns null and logs a warning if a row contains data that fails domain validation
475475
* (e.g. an oversized title written by an older version of the server). This prevents
476476
* a single corrupt row from crashing an entire list query.
477477
*/
478-
private fun mapRowToWorkItemSafe(row: ResultRow): WorkItem? {
478+
private fun toWorkItemOrNull(row: ResultRow): WorkItem? {
479479
return try {
480-
mapRowToWorkItem(row)
480+
toWorkItem(row)
481481
} catch (e: Exception) {
482482
logger.warn(
483483
"Skipping corrupt WorkItem row (id={}): {}",

current/src/main/kotlin/io/github/jpicklyk/mcptask/current/interfaces/mcp/CurrentMcpServer.kt

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,7 @@ class CurrentMcpServer(
7979
val toolContext = ToolExecutionContext(repositoryProvider, noteSchemaService)
8080
logger.info("Repository provider and tool context initialized")
8181

82-
// Configure MCP server
83-
val serverName = System.getenv("MCP_SERVER_NAME") ?: "mcp-task-orchestrator-current"
84-
val server = configureServer(serverName)
85-
mcpSdkServer = server
86-
87-
// Register MCP tools
88-
val adapter = McpToolAdapter()
82+
// Build tool list
8983
val tools = listOf(
9084
// Phase 1: CRUD
9185
ManageItemsTool(),
@@ -106,6 +100,15 @@ class CurrentMcpServer(
106100
// Phase 3: Context
107101
GetContextTool()
108102
)
103+
104+
// Configure MCP server
105+
val serverName = System.getenv("MCP_SERVER_NAME") ?: "mcp-task-orchestrator-current"
106+
val toolNames = tools.joinToString(", ") { it.name }
107+
val server = configureServer(serverName, tools.size, toolNames)
108+
mcpSdkServer = server
109+
110+
// Register MCP tools
111+
val adapter = McpToolAdapter()
109112
adapter.registerToolsWithServer(server, tools, toolContext)
110113
logger.info("Registered ${tools.size} MCP tools")
111114

@@ -129,6 +132,15 @@ class CurrentMcpServer(
129132
mcpSdkServer?.close()
130133
}
131134

135+
private fun registerCommonCleanup(server: Server) {
136+
shutdownCoordinator?.addCleanupAction("Close MCP Server") {
137+
runBlocking { server.close() }
138+
}
139+
shutdownCoordinator?.addCleanupAction("Close Database") {
140+
databaseManager.shutdown()
141+
}
142+
}
143+
132144
private suspend fun runStdioTransport(server: Server, serverName: String, toolCount: Int) {
133145
logger.info("Starting MCP server with stdio transport...")
134146

@@ -137,12 +149,7 @@ class CurrentMcpServer(
137149
outputStream = System.out.asSink().buffered()
138150
)
139151

140-
shutdownCoordinator?.addCleanupAction("Close MCP Server") {
141-
runBlocking { server.close() }
142-
}
143-
shutdownCoordinator?.addCleanupAction("Close Database") {
144-
databaseManager.shutdown()
145-
}
152+
registerCommonCleanup(server)
146153

147154
val done = Job()
148155
server.onClose {
@@ -176,11 +183,11 @@ class CurrentMcpServer(
176183
ktorServer.stop(gracePeriodMillis = 1000, timeoutMillis = 5000)
177184
done.complete()
178185
}
179-
shutdownCoordinator?.addCleanupAction("Close MCP Server") {
180-
runBlocking { server.close() }
181-
}
182-
shutdownCoordinator?.addCleanupAction("Close Database") {
183-
databaseManager.shutdown()
186+
registerCommonCleanup(server)
187+
188+
server.onClose {
189+
logger.info("Server closed")
190+
if (shutdownCoordinator == null) databaseManager.shutdown()
184191
}
185192

186193
if (shutdownCoordinator == null) {
@@ -203,7 +210,7 @@ class CurrentMcpServer(
203210
/**
204211
* Configures the MCP SDK server with capabilities for tools, prompts, and resources.
205212
*/
206-
private fun configureServer(serverName: String): Server {
213+
private fun configureServer(serverName: String, toolCount: Int, toolNames: String): Server {
207214
return Server(
208215
serverInfo = Implementation(
209216
name = serverName,
@@ -217,7 +224,7 @@ class CurrentMcpServer(
217224
logging = JsonObject(emptyMap())
218225
)
219226
),
220-
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"
227+
instructions = "Current (v3) MCP Task Orchestrator — $toolCount tools: $toolNames"
221228
)
222229
}
223230
}

0 commit comments

Comments
 (0)