Tools Overview
Building tools with MCP Framework - create custom tools that extend AI model capabilities
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-toolThis generates a tool template in src/tools/MyTool.ts.
Manual Creation
- Create a new TypeScript file in
src/tools/ - Extend the
MCPToolclass - 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}`);
}
}Title and Icons
Tools can set a title for human-readable display and icons for client UI rendering. The title is separate from the programmatic name and is intended for display in tool pickers, dashboards, and other client interfaces.
import { MCPTool } from "mcp-framework";
import { z } from "zod";
class WeatherTool extends MCPTool {
name = "get_weather";
title = "Weather Information"; // Human-readable display name
description = "Get current weather for a location";
icons = [{ src: "https://example.com/weather.png", mimeType: "image/png", sizes: ["48x48"] }];
schema = {
location: {
type: z.string(),
description: "City or address to get weather for",
},
};
async execute({ location }) {
// ...
}
}The icons property accepts an array of icon objects, each with:
src— URL to the icon imagemimeType— MIME type of the image (e.g.,"image/png","image/svg+xml")sizes— Array of size strings (e.g.,["48x48", "96x96"])
Tool Annotations
Annotations provide behavioral hints that help clients make UX decisions about your tools. For example, a client might auto-approve tools marked as read-only, or show a confirmation dialog for destructive tools.
class DatabaseTool extends MCPTool {
name = "query_db";
description = "Query the database";
annotations = {
readOnlyHint: true, // Tool doesn't modify state
destructiveHint: false, // Tool is not destructive
idempotentHint: true, // Safe to retry
openWorldHint: false, // Doesn't access external systems
};
schema = {
query: {
type: z.string(),
description: "SQL query to execute",
},
};
async execute({ query }) {
// ...
}
}Annotations Are Hints
Annotations are advisory only and are NOT enforced by the framework or the MCP protocol. Clients may use them to improve the user experience, but they should not be relied upon for security or correctness guarantees.
Available annotation properties:
| Property | Type | Default | Description |
|---|---|---|---|
readOnlyHint | boolean | false | Tool does not modify any state |
destructiveHint | boolean | true | Tool may perform destructive operations |
idempotentHint | boolean | false | Calling the tool multiple times with the same input has the same effect |
openWorldHint | boolean | true | Tool may interact with external systems beyond its host |
New Content Types
In addition to returning plain text, tools can now return audio content, resource links, and embedded resources.
Audio Content
Return audio data as base64-encoded content:
async execute(input) {
const audioData = await generateSpeech(input.text);
return { type: 'audio', data: audioData, mimeType: 'audio/wav' };
}Resource Links
Return a URI reference to an existing resource. The client can then fetch it independently:
async execute(input) {
return {
type: 'resource_link',
uri: 'file:///project/src/main.rs',
name: 'main.rs',
mimeType: 'text/x-rust'
};
}Embedded Resources
Return a resource with inline content included directly in the response:
async execute(input) {
return {
type: 'resource',
resource: {
uri: 'file:///README.md',
mimeType: 'text/markdown',
text: '# Hello'
}
};
}Content Annotations
All content types support optional annotations that indicate the intended audience, priority, and freshness of the content:
async execute(input) {
return {
type: 'text',
text: 'Result for user',
annotations: {
audience: ['user'], // Who this is for: 'user', 'assistant', or both
priority: 0.9, // 0.0 (optional) to 1.0 (critical)
lastModified: '2025-01-12T15:00:58Z'
}
};
}| Property | Type | Description |
|---|---|---|
audience | string[] | Who the content is intended for. Values: 'user', 'assistant', or both. Omit to indicate both. |
priority | number | Importance from 0.0 (background/optional) to 1.0 (critical). Used by clients to order or filter content. |
lastModified | string | ISO 8601 timestamp of when the content was last modified. |
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
- Explore Advanced Tool Features (progress, cancellation, logging, elicitation, tasks, and more)
- Learn about API Integration
- Learn about Elicitation — request user input during tool execution
- Learn about Prompts
- Learn about Resources