export const DEFAULT_CLAUDE_BUILT_IN_TOOLS = ["Read", "Write", "Bash", "Glob", "Grep"] as const; export const CPH_HUB_MCP_SERVER_NAME = "cph_hub"; export const CPH_HUB_MCP_TOOL_IDS = ["send_file", "feishu_read_context", "request_approval"] as const; export type CphHubMcpToolId = (typeof CPH_HUB_MCP_TOOL_IDS)[number]; export interface ClaudeSdkToolConfig { readonly tools: readonly string[]; readonly allowedTools: readonly string[]; } const ROLE_TOOL_TO_CLAUDE_BUILT_INS = new Map([ ["read_file", ["Read"]], ["write_file", ["Write"]], ["list_files", ["Glob"]], ["search_files", ["Grep"]], ["bash", ["Bash"]], // ADR-0017 replaced cph custom tools with Bash commands. Granting either // cph role tool therefore exposes the SDK Bash tool; cph-only Bash narrowing // would need a separate command-policy layer. ["cph_check", ["Bash"]], ["cph_build", ["Bash"]], ["Read", ["Read"]], ["Write", ["Write"]], ["Bash", ["Bash"]], ["Glob", ["Glob"]], ["Grep", ["Grep"]], ]); const ROLE_TOOL_TO_CPH_HUB_MCP_TOOL = new Map([ ["send_file", "send_file"], ["feishu_read_context", "feishu_read_context"], ["request_approval", "request_approval"], ["mcp__cph_hub__send_file", "send_file"], ["mcp__cph_hub__feishu_read_context", "feishu_read_context"], ["mcp__cph_hub__request_approval", "request_approval"], ]); const SUPPORTED_ROLE_TOOLS = new Set([ ...ROLE_TOOL_TO_CLAUDE_BUILT_INS.keys(), ...ROLE_TOOL_TO_CPH_HUB_MCP_TOOL.keys(), ]); export function claudeSdkToolConfigForRole(roleTools: readonly string[] | undefined): ClaudeSdkToolConfig { if (roleTools === undefined) { const mcpTools = CPH_HUB_MCP_TOOL_IDS.map(claudeMcpToolName); return { tools: [...DEFAULT_CLAUDE_BUILT_IN_TOOLS], allowedTools: [...DEFAULT_CLAUDE_BUILT_IN_TOOLS, ...mcpTools], }; } const builtIns: string[] = []; const allowedTools: string[] = []; for (const roleTool of roleTools) { assertSupportedRoleTool(roleTool); for (const tool of ROLE_TOOL_TO_CLAUDE_BUILT_INS.get(roleTool) ?? []) { pushUnique(builtIns, tool); pushUnique(allowedTools, tool); } const mcpTool = ROLE_TOOL_TO_CPH_HUB_MCP_TOOL.get(roleTool); if (mcpTool !== undefined) { pushUnique(allowedTools, claudeMcpToolName(mcpTool)); } } return { tools: builtIns, allowedTools }; } export function cphHubMcpToolsForRole(roleTools: readonly string[] | undefined): readonly CphHubMcpToolId[] { if (roleTools === undefined) return [...CPH_HUB_MCP_TOOL_IDS]; const tools: CphHubMcpToolId[] = []; for (const roleTool of roleTools) { assertSupportedRoleTool(roleTool); const mcpTool = ROLE_TOOL_TO_CPH_HUB_MCP_TOOL.get(roleTool); if (mcpTool !== undefined) pushUnique(tools, mcpTool); } return tools; } export function roleToolsAllow(roleTools: readonly string[] | undefined, roleTool: string): boolean { if (roleTools === undefined) return true; for (const configured of roleTools) { assertSupportedRoleTool(configured); if (configured === roleTool) return true; if (ROLE_TOOL_TO_CPH_HUB_MCP_TOOL.get(configured) === roleTool) return true; } return false; } export function assertSupportedRoleTools(roleTools: readonly string[]): void { for (const roleTool of roleTools) assertSupportedRoleTool(roleTool); } function claudeMcpToolName(tool: CphHubMcpToolId): string { return `mcp__${CPH_HUB_MCP_SERVER_NAME}__${tool}`; } function assertSupportedRoleTool(roleTool: string): void { if (!SUPPORTED_ROLE_TOOLS.has(roleTool)) { throw new Error(`unknown role tool id: ${roleTool}`); } } function pushUnique(items: T[], item: T): void { if (!items.includes(item)) items.push(item); }