forked from bai/curriculum-project-hub
97f7972cc5
Drop markdown_to_pdf MCP surface, implementation, tests, and md-to-pdf dependency. Roles that still list markdown_to_pdf must be cleaned before startup. Co-authored-by: Hong Jiarong <me@jrhim.com> Co-committed-by: Hong Jiarong <me@jrhim.com>
130 lines
4.0 KiB
TypeScript
130 lines
4.0 KiB
TypeScript
export const DEFAULT_CLAUDE_BUILT_IN_TOOLS = [
|
|
"Read",
|
|
"Write",
|
|
"Bash",
|
|
"Glob",
|
|
"Grep",
|
|
"WebFetch",
|
|
"WebSearch",
|
|
] as const;
|
|
|
|
export const CPH_HUB_MCP_SERVER_NAME = "cph_hub";
|
|
export const CPH_HUB_MCP_TOOL_IDS = [
|
|
"send_file",
|
|
"feishu_read_context",
|
|
"feishu_download_resource",
|
|
"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<string, readonly string[]>([
|
|
["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"]],
|
|
["web_fetch", ["WebFetch"]],
|
|
["web_search", ["WebSearch"]],
|
|
["Read", ["Read"]],
|
|
["Write", ["Write"]],
|
|
["Bash", ["Bash"]],
|
|
["Glob", ["Glob"]],
|
|
["Grep", ["Grep"]],
|
|
["WebFetch", ["WebFetch"]],
|
|
["WebSearch", ["WebSearch"]],
|
|
]);
|
|
|
|
const ROLE_TOOL_TO_CPH_HUB_MCP_TOOL = new Map<string, CphHubMcpToolId>([
|
|
["send_file", "send_file"],
|
|
["feishu_read_context", "feishu_read_context"],
|
|
["feishu_download_resource", "feishu_download_resource"],
|
|
["request_approval", "request_approval"],
|
|
["mcp__cph_hub__send_file", "send_file"],
|
|
["mcp__cph_hub__feishu_read_context", "feishu_read_context"],
|
|
["mcp__cph_hub__feishu_download_resource", "feishu_download_resource"],
|
|
["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<T>(items: T[], item: T): void {
|
|
if (!items.includes(item)) items.push(item);
|
|
}
|