( permissionContext: ToolPermissionContext, mcpTools: Tools, )
| 343 | * @returns Combined, deduplicated array of built-in and MCP tools |
| 344 | */ |
| 345 | export function assembleToolPool( |
| 346 | permissionContext: ToolPermissionContext, |
| 347 | mcpTools: Tools, |
| 348 | ): Tools { |
| 349 | const builtInTools = getTools(permissionContext) |
| 350 | |
| 351 | // Filter out MCP tools that are in the deny list |
| 352 | const allowedMcpTools = filterToolsByDenyRules(mcpTools, permissionContext) |
| 353 | |
| 354 | // Sort each partition for prompt-cache stability, keeping built-ins as a |
| 355 | // contiguous prefix. The server's claude_code_system_cache_policy places a |
| 356 | // global cache breakpoint after the last prefix-matched built-in tool; a flat |
| 357 | // sort would interleave MCP tools into built-ins and invalidate all downstream |
| 358 | // cache keys whenever an MCP tool sorts between existing built-ins. uniqBy |
| 359 | // preserves insertion order, so built-ins win on name conflict. |
| 360 | // Avoid Array.toSorted (Node 20+) — we support Node 18. builtInTools is |
| 361 | // readonly so copy-then-sort; allowedMcpTools is a fresh .filter() result. |
| 362 | const byName = (a: Tool, b: Tool) => a.name.localeCompare(b.name) |
| 363 | return uniqBy( |
| 364 | [...builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)), |
| 365 | 'name', |
| 366 | ) |
| 367 | } |
| 368 | |
| 369 | /** |
| 370 | * Get all tools including both built-in tools and MCP tools. |
no test coverage detected