Tool Integration
Overview
Claude Code’s tool system allows it to interact with your file system and execute commands. Additionally, the Model Context Protocol (MCP) enables you to create custom tools that extend Claude’s capabilities.
Key Concepts
Tool Definition
Tools in Claude Code are defined with:
- Type-safe parameter schemas
- Runtime validation
- Composable execution patterns
Tool Integration Pattern
// Tool integration pattern for type-safe tools
interface Tool<TParams = any, TResult = any> {
name: string;
description: string;
parameters: z.ZodSchema<TParams>;
execute: (params: TParams) => Promise<TResult>;
}
class ToolRegistry {
private tools = new Map<string, Tool>();
register<TParams, TResult>(tool: Tool<TParams, TResult>): void {
this.tools.set(tool.name, tool);
}
async execute(name: string, params: unknown): Promise<unknown> {
const tool = this.tools.get(name);
if (!tool) {
throw new Error(`Tool ${name} not found`);
}
const validatedParams = tool.parameters.parse(params);
return tool.execute(validatedParams);
}
getToolSchemas(): Array<{name: string; description: string; parameters: any}> {
return Array.from(this.tools.values()).map(tool => ({
name: tool.name,
description: tool.description,
parameters: zodToJsonSchema(tool.parameters),
}));
}
}
// Example tool implementation
const searchTool: Tool<{query: string}, {results: string[]}> = {
name: 'search',
description: 'Search for information',
parameters: z.object({
query: z.string().min(1),
}),
async execute(params) {
// Implement search logic
return { results: [`Result for: ${params.query}`] };
},
};Key Benefits
- Type Safety: Compile-time and runtime validation
- Dynamic Management: Registry pattern for flexible tool loading
- Composability: Chain tools for complex workflows
Built-in Tools
1. File Operations Tool
The str_replace_editor_tool provides file manipulation capabilities:
// Tool use appears in assistant messages
interface ToolUseBlock {
type: 'tool_use';
id: string;
name: 'str_replace_editor_tool';
input: {
command: 'view' | 'str_replace' | 'create';
path: string;
// Additional fields based on command
};
}View Command
// Reading files
interface ViewCommand {
command: 'view';
path: string;
view_range?: [number, number]; // Optional line range
}
// Example usage in a prompt
const readFilePrompt = `
Please read the file at src/index.ts and analyze its structure.
`;Create Command
// Creating new files
interface CreateCommand {
command: 'create';
path: string;
file_text: string;
}
// Example usage
const createFilePrompt = `
Create a new TypeScript configuration file at tsconfig.json with strict settings.
`;String Replace Command
// Editing existing files
interface StrReplaceCommand {
command: 'str_replace';
path: string;
old_str: string;
new_str: string;
}
// Example usage
const editFilePrompt = `
In the file src/app.ts, replace the function "oldFunction" with a new async version.
`;2. Bash Command Tool
Execute shell commands:
interface BashToolUse {
type: 'tool_use';
id: string;
name: 'bash';
input: {
command: string;
};
}
// Example usage
const runTestsPrompt = `
Run the test suite and show me the results.
`;Parsing Tool Uses from Responses
import { query, type SDKMessage } from "@anthropic-ai/claude-code";
interface ToolExecution {
tool: string;
input: any;
result?: any;
error?: string;
}
async function extractToolUses(prompt: string): Promise<ToolExecution[]> {
const executions: ToolExecution[] = [];
for await (const message of query({
prompt,
abortController: new AbortController()
})) {
if (message.type === 'assistant') {
const content = message.data.message.content;
if (Array.isArray(content)) {
for (const block of content) {
if (block.type === 'tool_use') {
executions.push({
tool: block.name,
input: block.input
});
} else if (block.type === 'tool_result') {
// Match result with its tool use
const execution = executions.find(
e => e.tool === block.tool_use_id
);
if (execution) {
execution.result = block.content;
execution.error = block.is_error ? block.content : undefined;
}
}
}
}
}
}
return executions;
}Model Context Protocol (MCP)
MCP allows you to create custom tools that Claude can use. Here’s how to build and integrate MCP servers:
Creating an MCP Server
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// Create server instance
const server = new McpServer({
name: "my-tools-server",
version: "1.0.0",
description: "Custom tools for Claude"
});
// Define a custom tool
server.tool(
"database-query",
{
description: "Execute a database query",
inputSchema: z.object({
query: z.string().describe("SQL query to execute"),
database: z.string().describe("Database name")
})
},
async ({ query, database }) => {
try {
// Your database logic here
const result = await executeQuery(query, database);
return {
content: [{
type: "text",
text: JSON.stringify(result, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Error: ${error.message}`
}],
isError: true
};
}
}
);
// Start the server
const transport = new StdioServerTransport();
await server.connect(transport);Advanced MCP Server with Multiple Tools
import { promises as fs } from 'fs';
import { exec } from 'child_process';
import { promisify } from 'util';
const execAsync = promisify(exec);
// Tool 1: JSON file operations
server.tool(
"json-file-ops",
{
description: "Read, write, or modify JSON files",
inputSchema: z.object({
operation: z.enum(['read', 'write', 'update']),
path: z.string(),
data: z.any().optional(),
jsonPath: z.string().optional()
})
},
async ({ operation, path, data, jsonPath }) => {
switch (operation) {
case 'read':
const content = await fs.readFile(path, 'utf-8');
const json = JSON.parse(content);
return {
content: [{
type: "text",
text: JSON.stringify(json, null, 2)
}]
};
case 'write':
await fs.writeFile(path, JSON.stringify(data, null, 2));
return {
content: [{
type: "text",
text: `Successfully wrote to ${path}`
}]
};
case 'update':
const existing = JSON.parse(await fs.readFile(path, 'utf-8'));
// Simple jsonPath implementation
const keys = jsonPath.split('.');
let target = existing;
for (let i = 0; i < keys.length - 1; i++) {
target = target[keys[i]];
}
target[keys[keys.length - 1]] = data;
await fs.writeFile(path, JSON.stringify(existing, null, 2));
return {
content: [{
type: "text",
text: `Updated ${jsonPath} in ${path}`
}]
};
}
}
);
// Tool 2: Git operations
server.tool(
"git-ops",
{
description: "Perform git operations",
inputSchema: z.object({
command: z.enum(['status', 'diff', 'commit', 'log']),
message: z.string().optional(),
files: z.array(z.string()).optional()
})
},
async ({ command, message, files }) => {
let gitCommand = '';
switch (command) {
case 'status':
gitCommand = 'git status --porcelain';
break;
case 'diff':
gitCommand = 'git diff --cached';
break;
case 'commit':
if (files) {
await execAsync(`git add ${files.join(' ')}`);
}
gitCommand = `git commit -m "${message}"`;
break;
case 'log':
gitCommand = 'git log --oneline -10';
break;
}
const { stdout, stderr } = await execAsync(gitCommand);
return {
content: [{
type: "text",
text: stdout || stderr
}],
isError: !!stderr
};
}
);
// Tool 3: API testing
server.tool(
"api-test",
{
description: "Test REST API endpoints",
inputSchema: z.object({
url: z.string(),
method: z.enum(['GET', 'POST', 'PUT', 'DELETE']),
headers: z.record(z.string()).optional(),
body: z.any().optional()
})
},
async ({ url, method, headers, body }) => {
const response = await fetch(url, {
method,
headers: {
'Content-Type': 'application/json',
...headers
},
body: body ? JSON.stringify(body) : undefined
});
const responseBody = await response.json();
return {
content: [{
type: "text",
text: JSON.stringify({
status: response.status,
headers: Object.fromEntries(response.headers),
body: responseBody
}, null, 2)
}]
};
}
);MCP Resources
Resources provide data that Claude can access:
// Provide configuration data
server.resource(
"config://app",
async () => {
const config = await loadAppConfiguration();
return {
contents: [{
uri: "config://app",
mimeType: "application/json",
text: JSON.stringify(config, null, 2)
}]
};
}
);
// Provide environment information
server.resource(
"env://variables",
async () => {
const safeEnv = Object.entries(process.env)
.filter(([key]) => !key.includes('SECRET') && !key.includes('KEY'))
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {});
return {
contents: [{
uri: "env://variables",
mimeType: "application/json",
text: JSON.stringify(safeEnv, null, 2)
}]
};
}
);
// Provide database schema
server.resource(
"schema://database",
async () => {
const schema = await getDatabaseSchema();
return {
contents: [{
uri: "schema://database",
mimeType: "text/plain",
text: schema
}]
};
}
);MCP Prompts
Define reusable prompt templates:
// Code review prompt
server.prompt(
"code-review",
{
description: "Perform a comprehensive code review",
inputSchema: z.object({
files: z.array(z.string()).describe("Files to review"),
focus: z.enum(['security', 'performance', 'style', 'all']).optional()
})
},
({ files, focus = 'all' }) => ({
messages: [{
role: "user",
content: {
type: "text",
text: `Please perform a ${focus} code review on the following files: ${files.join(', ')}
Focus areas:
${focus === 'all' || focus === 'security' ? '- Security vulnerabilities' : ''}
${focus === 'all' || focus === 'performance' ? '- Performance optimizations' : ''}
${focus === 'all' || focus === 'style' ? '- Code style and best practices' : ''}
Provide specific line numbers and actionable suggestions.`
}
}]
})
);
// Test generation prompt
server.prompt(
"generate-tests",
{
description: "Generate unit tests for code",
inputSchema: z.object({
file: z.string(),
framework: z.enum(['jest', 'mocha', 'vitest']).default('jest')
})
},
({ file, framework }) => ({
messages: [{
role: "user",
content: {
type: "text",
text: `Generate comprehensive unit tests for ${file} using ${framework}.
Requirements:
- Test all exported functions and classes
- Include edge cases and error scenarios
- Use proper mocking for dependencies
- Aim for >80% code coverage
- Follow ${framework} best practices`
}
}]
})
);Configuring MCP in Claude Desktop
Add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"my-tools": {
"command": "node",
"args": ["/path/to/your/mcp-server.js"],
"env": {
"DATABASE_URL": "postgresql://localhost/mydb"
}
},
"file-tools": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/projects"]
},
"github-tools": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_..."
}
}
}
}Using Tools in Your Application
1. Tool Usage Monitor
class ToolUsageMonitor {
private toolUsage: Map<string, number> = new Map();
async monitorQuery(prompt: string) {
for await (const message of query({
prompt,
abortController: new AbortController()
})) {
if (message.type === 'assistant') {
const content = message.data.message.content;
if (Array.isArray(content)) {
for (const block of content) {
if (block.type === 'tool_use') {
const count = this.toolUsage.get(block.name) || 0;
this.toolUsage.set(block.name, count + 1);
console.log(`Tool used: ${block.name}`);
console.log(`Input:`, JSON.stringify(block.input, null, 2));
}
}
}
}
}
}
getUsageStats() {
return Object.fromEntries(this.toolUsage);
}
}2. Tool Result Validator
class ToolResultValidator {
validateFileOperation(result: any): boolean {
// Validate file operations completed successfully
if (result.error) {
console.error('File operation failed:', result.error);
return false;
}
// Check for common issues
if (result.content?.includes('Permission denied')) {
console.error('Permission issue detected');
return false;
}
return true;
}
validateBashCommand(result: any): boolean {
// Check exit codes and stderr
if (result.exitCode && result.exitCode !== 0) {
console.error('Command failed with exit code:', result.exitCode);
return false;
}
return true;
}
}3. Custom Tool Wrapper
class CustomToolWrapper {
async executeWithTools(
prompt: string,
customTools: Map<string, (input: any) => Promise<any>>
) {
const results: any[] = [];
for await (const message of query({
prompt,
abortController: new AbortController()
})) {
if (message.type === 'assistant') {
const content = message.data.message.content;
if (Array.isArray(content)) {
for (const block of content) {
if (block.type === 'tool_use' && customTools.has(block.name)) {
try {
const toolFn = customTools.get(block.name)!;
const result = await toolFn(block.input);
results.push({
tool: block.name,
input: block.input,
result
});
} catch (error) {
console.error(`Tool ${block.name} failed:`, error);
}
}
}
}
}
}
return results;
}
}Advanced Patterns
Tool Composition
Create complex workflows by composing multiple tools:
class ToolChain {
constructor(private registry: ToolRegistry) {}
async execute(steps: Array<{tool: string; params: any}>): Promise<any[]> {
const results = [];
for (const step of steps) {
const result = await this.registry.execute(step.tool, step.params);
results.push(result);
}
return results;
}
}Error Handling
Implement robust error handling for tool execution:
class SafeToolRegistry extends ToolRegistry {
async execute(name: string, params: unknown): Promise<unknown> {
try {
return await super.execute(name, params);
} catch (error) {
if (error instanceof z.ZodError) {
throw new ToolValidationError(name, error);
}
throw new ToolExecutionError(name, error);
}
}
}Best Practices
- Clear Tool Descriptions: Improve AI tool selection with descriptive names and documentation
- Parameter Validation: Use Zod schemas for comprehensive validation
- Error Context: Provide detailed error messages for debugging
- Tool Versioning: Implement versioning for compatibility
- Result Caching: Cache tool results when appropriate
- Tool Safety: Always validate tool inputs and outputs
- Permission Control: Use appropriate permission modes
- Rate Limiting: Be mindful of tool execution frequency
- Logging: Log all tool uses for debugging and auditing
- Testing: Test MCP servers thoroughly before deployment
- Security: Never expose sensitive operations through tools
Troubleshooting
Common Issues
Circular Dependencies
Problem: Tools referencing each other cause TypeScript compilation errors.
Solution: Use lazy loading and interface segregation:
// Use interfaces instead of concrete types
interface ISearchTool {
search(query: string): Promise<string[]>;
}
// Lazy load dependencies
const getSearchTool = () => require('./search-tool').searchTool;Type Inference Issues
Problem: TypeScript cannot infer tool parameter types.
Solution: Use explicit type parameters:
registry.register<SearchParams, SearchResult>(searchTool);Next Steps
- Explore Practical Examples for real-world tool usage
- Review Claude Code Hooks for extending functionality
- Learn about Advanced Patterns for complex tool orchestration
- Check out TypeScript SDK Documentation for more SDK features
Tags: claude-code typescript tools mcp integration automation sdk