import org.springaicommunity.claude.agent.sdk.ClaudeClient;
import org.springaicommunity.claude.agent.sdk.ClaudeSyncClient;
import org.springaicommunity.claude.agent.sdk.config.PermissionMode;
import org.springaicommunity.claude.agent.sdk.hooks.HookRegistry;
import org.springaicommunity.claude.agent.sdk.mcp.McpServerConfig;
import org.springaicommunity.claude.agent.sdk.transport.CLIOptions;
import org.springaicommunity.claude.agent.sdk.types.control.HookInput;
import org.springaicommunity.claude.agent.sdk.types.control.HookOutput;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
// Test files
Path testDir = Files.createTempDirectory("mcp-hooks-test");
Files.writeString(testDir.resolve("allowed.txt"), "This file can be read.");
Files.writeString(testDir.resolve("secret.txt"), "CONFIDENTIAL data.");
// Statistics
AtomicInteger mcpCalls = new AtomicInteger(0);
AtomicInteger mcpBlocks = new AtomicInteger(0);
// MCP server
McpServerConfig.McpStdioServerConfig filesystemServer = new McpServerConfig.McpStdioServerConfig(
"npx", List.of("-y", "@modelcontextprotocol/server-filesystem", testDir.toString()), Map.of()
);
// Hooks for MCP interception
HookRegistry hooks = new HookRegistry();
hooks.registerPreToolUse(input -> {
var preToolUse = (HookInput.PreToolUseInput) input;
String toolName = preToolUse.toolName();
if (toolName.startsWith("mcp__")) {
mcpCalls.incrementAndGet();
String[] parts = toolName.split("__");
System.out.printf("[Hook:PreMCP] Server=%s, Tool=%s%n",
parts.length > 1 ? parts[1] : "?",
parts.length > 2 ? parts[2] : "?");
// Block access to secret files
String filePath = preToolUse.getArgument("path", String.class).orElse("");
if (filePath.toLowerCase().contains("secret")) {
mcpBlocks.incrementAndGet();
System.out.println("[Hook:PreMCP] BLOCKED: Secret file access");
return HookOutput.block("Access denied: Cannot read secret files.");
}
}
return HookOutput.allow();
});
hooks.registerPostToolUse(input -> {
var postToolUse = (HookInput.PostToolUseInput) input;
if (postToolUse.toolName().startsWith("mcp__")) {
System.out.println("[Hook:PostMCP] Completed: " + postToolUse.toolName());
}
return HookOutput.allow();
});
try (ClaudeSyncClient client = ClaudeClient.sync()
.workingDirectory(testDir)
.model(CLIOptions.MODEL_HAIKU)
.permissionMode(PermissionMode.BYPASS_PERMISSIONS)
.mcpServer("fs", filesystemServer)
.allowedTools(List.of("mcp__fs__read_file", "mcp__fs__list_directory"))
.hookRegistry(hooks)
.build()) {
// This will work
client.connect("Read allowed.txt");
// ... process response
// This will be blocked by our hook
client.query("Read secret.txt");
// ... Claude reports the file couldn't be accessed
}
System.out.println("MCP calls: " + mcpCalls.get());
System.out.println("MCP blocks: " + mcpBlocks.get());