( permissionContext: ToolPermissionContext, mcpTools: Tools, )
| 376 | * @returns Combined, deduplicated array of built-in and MCP tools |
| 377 | */ |
| 378 | export function assembleToolPool( |
| 379 | permissionContext: ToolPermissionContext, |
| 380 | mcpTools: Tools, |
| 381 | ): Tools { |
| 382 | const builtInTools = getTools(permissionContext) |
| 383 | |
| 384 | // Filter out MCP tools that are in the deny list |
| 385 | const allowedMcpTools = filterToolsByDenyRules(mcpTools, permissionContext) |
| 386 | |
| 387 | // Sort each partition for prompt-cache stability, keeping built-ins as a |
| 388 | // contiguous prefix. The server's claude_code_system_cache_policy places a |
| 389 | // global cache breakpoint after the last prefix-matched built-in tool; a flat |
| 390 | // sort would interleave MCP tools into built-ins and invalidate all downstream |
| 391 | // cache keys whenever an MCP tool sorts between existing built-ins. uniqBy |
| 392 | // preserves insertion order, so built-ins win on name conflict. |
| 393 | // Avoid Array.toSorted (Node 20+) — we support Node 18. builtInTools is |
| 394 | // readonly so copy-then-sort; allowedMcpTools is a fresh .filter() result. |
| 395 | const byName = (a: Tool, b: Tool) => a.name.localeCompare(b.name) |
| 396 | return uniqBy( |
| 397 | [...builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)), |
| 398 | 'name', |
| 399 | ) |
| 400 | } |
| 401 | |
| 402 | /** |
| 403 | * Get all tools including both built-in tools and MCP tools. |
no test coverage detected