Building Tools with MCP Framework
Power of Tools
Tools are the powerhouse of your MCP server - they let AI models interact with external services, process data, and perform complex operations with type safety!
What is a Tool?
A tool is a MCP class that defines:
- What inputs it accepts
- What it does with those inputs
- What it returns to the AI model
Here's a simple example:
import { MCPTool } from "mcp-framework";
import { z } from "zod";
interface GreetingInput {
name: string;
language: string;
}
class GreetingTool extends MCPTool<GreetingInput> {
name = "greeting";
description = "Generate a greeting in different languages";
schema = {
name: {
type: z.string(),
description: "Name to greet",
},
language: {
type: z.enum(["en", "es", "fr"]),
description: "Language code (en, es, fr)",
},
};
async execute({ name, language }) {
const greetings = {
en: `Hello ${name}!`,
es: `¡Hola ${name}!`,
fr: `Bonjour ${name}!`,
};
return greetings[language];
}
}
Creating Tools
Using the CLI
The fastest way to create a new tool:
mcp add tool my-tool
This generates a tool template in src/tools/MyTool.ts
.
Manual Creation
- Create a new TypeScript file in
src/tools/
- Extend the
MCPTool
class - Define your interface and implementation
Tool Architecture
Every tool has three main parts:
1. Input Schema
schema = {
email: {
type: z.string().email(),
description: "User's email address",
},
count: {
type: z.number().min(1),
description: "Number of items to process",
},
};
2. Metadata
name = "email-sender";
description = "Sends emails to specified addresses";
3. Execution Logic
async execute(input: MyInput) {
// Your tool's core functionality
return result;
}
Type Safety
Type Safety First
MCP Framework leverages TypeScript and Zod to provide end-to-end type safety!
interface DataInput {
userId: number;
fields: string[];
}
class DataTool extends MCPTool<DataInput> {
schema = {
userId: {
type: z.number(),
description: "User ID to fetch data for",
},
fields: {
type: z.array(z.string()),
description: "Fields to include in response",
},
};
}
Error Handling
Tools should handle errors gracefully:
async execute(input: MyInput) {
try {
const result = await this.processData(input);
return result;
} catch (error) {
if (error.code === 'NETWORK_ERROR') {
throw new Error('Unable to reach external service');
}
throw new Error(`Operation failed: ${error.message}`);
}
}
Best Practices
Pro Tips
Following these practices will make your tools more reliable and maintainable!
-
Clear Names
name = "fetch-user-data"; // Good
name = "fud"; // Bad -
Detailed Descriptions
Descriptions are also read by the LLMs - so make sure to make them detailed
description =
"Fetches user data including profile, preferences, and settings"; -
Descriptive Input Validation
schema = {
age: {
type: z.number().min(0).max(150),
description: "User's age (0-150)",
},
};
Next Steps
- Learn about API Integration
- Learn about Prompts
- Learn about Resources