Skip to main content

๐Ÿ› ๏ธ Tool Development Guide

Learn how to develop, test, and deploy custom tools for the MCP ADR Analysis Server.


๐Ÿ“‹ Overviewโ€‹

The MCP ADR Analysis Server is built on the Model Context Protocol (MCP) and features 37 specialized tools. This guide will teach you how to:

  • Create new custom tools
  • Follow tool development best practices
  • Test and validate tools
  • Integrate tools with the server
  • Deploy tools to production

๐ŸŽฏ Prerequisitesโ€‹

  • TypeScript/JavaScript proficiency
  • Understanding of MCP protocol
  • Familiarity with the server architecture
  • Node.js โ‰ฅ20.0.0 installed

๐Ÿš€ Quick Startโ€‹

Create Your First Toolโ€‹

# 1. Create a new tool file
touch src/tools/my-custom-tool.ts

# 2. Create corresponding test
touch tests/tools/my-custom-tool.test.ts

# 3. Implement the tool (see template below)

# 4. Register the tool in src/index.ts

# 5. Run tests
npm test -- my-custom-tool

๐Ÿ“– Tool Development Templateโ€‹

Basic Tool Structureโ€‹

// src/tools/my-custom-tool.ts
import { McpAdrError } from '../types/index.js';
import { logger } from '../utils/enhanced-logging.js';
import { z } from 'zod';

/**
* My Custom Tool - Brief description of what it does
*
* @param args - Tool arguments
* @returns Tool result
*/
export async function myCustomTool(args: {
inputPath: string;
options?: {
mode?: 'quick' | 'comprehensive';
output?: string;
};
}): Promise<MyCustomToolResult> {
try {
// 1. Validate inputs
const schema = z.object({
inputPath: z.string().min(1),
options: z
.object({
mode: z.enum(['quick', 'comprehensive']).optional(),
output: z.string().optional(),
})
.optional(),
});

const validated = schema.parse(args);

// 2. Log operation start
logger.info('Starting my-custom-tool', { args: validated });

// 3. Perform main operation
const result = await performMainOperation(validated);

// 4. Log success
logger.info('my-custom-tool completed successfully', {
result: result.summary,
});

return result;
} catch (error) {
// 5. Handle errors gracefully
logger.error('my-custom-tool failed', { error });
throw McpAdrError.fromError(error, 'MY_CUSTOM_TOOL_ERROR');
}
}

async function performMainOperation(args: ValidatedArgs): Promise<Result> {
// Your tool logic here
return {
success: true,
data: {},
summary: 'Operation completed',
};
}

interface MyCustomToolResult {
success: boolean;
data: unknown;
summary: string;
}

Tool Registrationโ€‹

// src/index.ts
import { myCustomTool } from './tools/my-custom-tool.js';

// Add to tool definitions
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
// ... existing tools ...
{
name: 'my_custom_tool',
description: 'Brief description of what the tool does',
inputSchema: {
type: 'object',
properties: {
inputPath: {
type: 'string',
description: 'Path to input file or directory',
},
options: {
type: 'object',
properties: {
mode: {
type: 'string',
enum: ['quick', 'comprehensive'],
description: 'Operation mode',
},
},
},
},
required: ['inputPath'],
},
},
],
}));

// Add to tool handlers
server.setRequestHandler(CallToolRequestSchema, async request => {
const { name, arguments: args } = request.params;

switch (name) {
// ... existing cases ...
case 'my_custom_tool':
return { content: [{ type: 'text', text: JSON.stringify(await myCustomTool(args)) }] };
}
});

๐Ÿงช Testing Your Toolโ€‹

Unit Testsโ€‹

// tests/tools/my-custom-tool.test.ts
import { myCustomTool } from '../../src/tools/my-custom-tool';
import { describe, it, expect, beforeEach } from '@jest/globals';

describe('myCustomTool', () => {
beforeEach(() => {
// Setup test environment
});

it('should process input successfully', async () => {
const result = await myCustomTool({
inputPath: './test-data/sample.txt',
options: { mode: 'quick' },
});

expect(result.success).toBe(true);
expect(result.data).toBeDefined();
});

it('should handle invalid input', async () => {
await expect(
myCustomTool({
inputPath: '',
})
).rejects.toThrow();
});

it('should use default options when not provided', async () => {
const result = await myCustomTool({
inputPath: './test-data/sample.txt',
});

expect(result.success).toBe(true);
});
});

Integration Testsโ€‹

// tests/integration/my-custom-tool.test.ts
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';

describe('My Custom Tool Integration', () => {
let client: Client;

beforeAll(async () => {
const transport = new StdioClientTransport({
command: 'node',
args: ['dist/index.js'],
});

client = new Client(
{
name: 'test-client',
version: '1.0.0',
},
{ capabilities: {} }
);

await client.connect(transport);
});

it('should be available in tool list', async () => {
const tools = await client.listTools();
const tool = tools.tools.find(t => t.name === 'my_custom_tool');

expect(tool).toBeDefined();
expect(tool?.description).toContain('Brief description');
});

it('should execute successfully', async () => {
const result = await client.callTool({
name: 'my_custom_tool',
arguments: {
inputPath: './test-data/sample.txt',
},
});

expect(result.content).toBeDefined();
});
});

๐ŸŽจ Tool Design Patternsโ€‹

1. Single Responsibilityโ€‹

Each tool should do one thing well:

// โœ… Good: Clear, focused purpose
export async function analyzeCodeQuality(filePath: string) { ... }

// โŒ Bad: Too many responsibilities
export async function analyzeEverything(filePath: string) { ... }

2. Composabilityโ€‹

Design tools to work together:

// Tool A: Analyze
const analysis = await analyzeProject(projectPath);

// Tool B: Generate (uses Tool A's output)
const adrs = await generateAdrs({
analysis: analysis,
outputPath: './docs/adrs',
});

// Tool C: Validate (uses Tool B's output)
const validation = await validateAdrs(adrs.outputPath);

3. Idempotencyโ€‹

Same input should produce same output:

// โœ… Good: Deterministic
export async function formatAdr(content: string) {
return content.trim().replace(/\s+/g, ' ');
}

// โŒ Bad: Non-deterministic
export async function formatAdrWithTimestamp(content: string) {
return `${content}\n\nGenerated: ${new Date()}`;
}

4. Error Handlingโ€‹

Always handle errors gracefully:

try {
const result = await riskyOperation();
return { success: true, data: result };
} catch (error) {
if (error instanceof ValidationError) {
return { success: false, error: 'Invalid input' };
}
throw McpAdrError.fromError(error, 'OPERATION_FAILED');
}

๐Ÿ“Š Tool Categoriesโ€‹

The server organizes tools into categories:

1. Analysis Toolsโ€‹

  • Project ecosystem analysis
  • Code quality assessment
  • Architectural context extraction

2. Generation Toolsโ€‹

  • ADR creation
  • Documentation generation
  • Rule set creation

3. Validation Toolsโ€‹

  • ADR validation
  • Project health scoring
  • Deployment readiness

4. Workflow Toolsโ€‹

  • Interactive planning
  • Troubleshooting guides
  • Tool orchestration

5. Research Toolsโ€‹

  • Research question generation
  • Research integration
  • Knowledge incorporation

๐Ÿ”ง Advanced Topicsโ€‹

Caching Resultsโ€‹

import { CacheManager } from '../utils/cache.js';

const cache = new CacheManager({ ttl: 3600000 });

export async function expensiveAnalysis(input: string) {
const cacheKey = `analysis:${input}`;

return cache.getOrSet(cacheKey, async () => {
return await performExpensiveOperation(input);
});
}

AI Integrationโ€‹

import { AIExecutor } from '../utils/ai-executor.js';

export async function generateWithAI(prompt: string) {
const aiExecutor = AIExecutor.getInstance();

const result = await aiExecutor.executePrompt({
prompt,
temperature: 0.7,
maxTokens: 1000,
});

return result.content;
}

Progress Trackingโ€‹

export async function longRunningOperation(
items: string[],
onProgress?: (progress: number) => void
) {
for (let i = 0; i < items.length; i++) {
await processItem(items[i]);
onProgress?.(Math.round(((i + 1) / items.length) * 100));
}
}

โœ… Quality Checklistโ€‹

Before submitting your tool:

  • Written comprehensive unit tests (>80% coverage)
  • Added integration tests
  • Documented all parameters with JSDoc
  • Added tool to LLM_CONTEXT.md
  • Updated API reference documentation
  • Validated input parameters with Zod
  • Implemented error handling
  • Added logging statements
  • Tested with realistic data
  • Reviewed performance implications
  • Updated CHANGELOG.md


๐ŸŽฏ Examplesโ€‹

Example 1: Simple Analysis Toolโ€‹

export async function countAdrs(directoryPath: string): Promise<number> {
const files = await readdir(directoryPath);
const adrFiles = files.filter(f => f.endsWith('.md'));
return adrFiles.length;
}

Example 2: Complex Generation Toolโ€‹

export async function generateAdrFromTemplate(options: {
templatePath: string;
variables: Record<string, string>;
outputPath: string;
}): Promise<GenerationResult> {
// 1. Load template
const template = await readFile(options.templatePath, 'utf-8');

// 2. Replace variables
let content = template;
for (const [key, value] of Object.entries(options.variables)) {
content = content.replace(new RegExp(`{{${key}}}`, 'g'), value);
}

// 3. Write output
await writeFile(options.outputPath, content);

return {
success: true,
outputPath: options.outputPath,
linesGenerated: content.split('\n').length,
};
}

๐Ÿ’ฌ Need Help?โ€‹


Last Updated: 2025-10-12