Creating Tools and Resources
This guide explains how to create powerful tools and resources for your MCP server that enable AI models to perform actions and access data.
Introduction
Two of the most powerful features of the Model Context Protocol (MCP) are Tools and Resources. These features allow AI models to interact with external systems, perform actions, and access data sources. This guide shows you how to implement both in your MCP server.
Understanding Tools vs. Resources
Feature | Tools | Resources |
---|---|---|
Purpose | Perform actions and operations | Provide access to data |
Direction | AI calls tools to take action | AI queries resources to get information |
Use Case | Calculations, API calls, data manipulation | Knowledge bases, databases, document retrieval |
Return Value | Result of the operation | Relevant data matching the query |
Implementing Tools
Tools allow AI models to execute functions and perform actions on behalf of users. Here's how to create and use tools in your MCP server:
Basic Tool Structure
Every tool consists of these components:
// src/tools/example-tool.js
export const exampleTool = {
name: 'example_tool', // Unique name for the tool
description: 'Description of what the tool does', // Helps the AI understand when to use it
parameters: {
// JSON Schema definition of the parameters
type: 'object',
properties: {
param1: {
type: 'string',
description: 'Description of parameter 1',
},
param2: {
type: 'number',
description: 'Description of parameter 2',
},
},
required: ['param1'], // List parameters that are required
},
execute: async (params) => {
// Implementation of the tool functionality
// This function is called when the AI uses the tool
return { result: 'processed result' };
},
};
Example: Weather Tool
Let's implement a weather tool that fetches current weather information:
// src/tools/weather.js
import axios from 'axios';
export const weatherTool = {
name: 'get_weather',
description: 'Get current weather information for a location',
parameters: {
type: 'object',
properties: {
location: {
type: 'string',
description: 'The city and country, e.g. "London, UK"',
},
units: {
type: 'string',
enum: ['metric', 'imperial'],
description: 'Units of measurement',
default: 'metric',
},
},
required: ['location'],
},
execute: async ({ location, units = 'metric' }) => {
try {
// Replace with your actual weather API key and endpoint
const apiKey = process.env.WEATHER_API_KEY;
const response = await axios.get(
`https://api.weatherapi.com/v1/current.json?key=${apiKey}&q=${encodeURIComponent(location)}&units=${units}`
);
const data = response.data;
return {
location: data.location.name,
country: data.location.country,
temperature: data.current.temp_c,
condition: data.current.condition.text,
humidity: data.current.humidity,
wind_speed: data.current.wind_kph,
units: units === 'metric' ? 'celsius' : 'fahrenheit',
retrieved_at: new Date().toISOString(),
};
} catch (error) {
if (error.response && error.response.status === 404) {
throw new Error(`Could not find weather data for location: ${location}`);
}
throw new Error(`Failed to fetch weather data: ${error.message}`);
}
},
};
Example: Database Query Tool
Here's a tool to perform database queries:
// src/tools/database-query.js
import { pool } from '../services/database';
export const databaseQueryTool = {
name: 'database_query',
description: 'Query the product database for information',
parameters: {
type: 'object',
properties: {
query_type: {
type: 'string',
enum: ['product', 'category', 'inventory'],
description: 'The type of query to perform',
},
search_term: {
type: 'string',
description: 'Search term or identifier',
},
limit: {
type: 'number',
description: 'Maximum number of results to return',
default: 10,
},
},
required: ['query_type', 'search_term'],
},
execute: async ({ query_type, search_term, limit = 10 }) => {
try {
let query;
let params;
switch (query_type) {
case 'product':
query = 'SELECT * FROM products WHERE name ILIKE $1 OR description ILIKE $1 LIMIT $2';
params = [`%${search_term}%`, limit];
break;
case 'category':
query = 'SELECT * FROM products WHERE category = $1 LIMIT $2';
params = [search_term, limit];
break;
case 'inventory':
query = 'SELECT * FROM inventory WHERE product_id = $1 LIMIT $2';
params = [search_term, limit];
break;
default:
throw new Error(`Unsupported query_type: ${query_type}`);
}
const result = await pool.query(query, params);
return {
query_type,
search_term,
count: result.rows.length,
results: result.rows,
};
} catch (error) {
throw new Error(`Database query failed: ${error.message}`);
}
},
};
Registering Tools
Register your tools in the tools index file:
// src/tools/index.js
import { calculatorTool } from './calculator';
import { weatherTool } from './weather';
import { databaseQueryTool } from './database-query';
export const tools = [
calculatorTool,
weatherTool,
databaseQueryTool,
];
Then configure your MCP server to use these tools:
// src/index.js
import { MCPServer } from '@trmx/server';
import { tools } from './tools';
const server = new MCPServer({
port: 3000,
tools, // Register all tools
});
server.start().then(() => {
console.log('MCP server running with tools on port 3000');
});
Implementing Resources
Resources provide AI models with access to external data sources like knowledge bases, databases, or files.
Basic Resource Structure
Every resource consists of these components:
// src/resources/example-resource.js
export const exampleResource = {
name: 'example_resource', // Unique name for the resource
description: 'Description of what data this resource provides', // Helps the AI understand when to use it
fetch: async (query) => {
// Implementation of the resource query
// This function is called when the AI queries this resource
return [
{ id: '1', title: 'Result 1', content: 'Content of result 1' },
{ id: '2', title: 'Result 2', content: 'Content of result 2' },
];
},
};
Example: Documents Resource
Let's implement a resource for accessing a document database:
// src/resources/documents.js
import { searchDocuments } from '../services/document-service';
export const documentsResource = {
name: 'documents',
description: 'Access to company documents, policies, and knowledge base',
fetch: async (query) => {
try {
// Search for documents matching the query
const documents = await searchDocuments(query);
// Transform documents into a consistent format
return documents.map(doc => ({
id: doc.id,
title: doc.title,
content: doc.content,
url: doc.url,
last_updated: doc.updatedAt,
category: doc.category,
}));
} catch (error) {
console.error('Error fetching documents:', error);
return []; // Return empty array on error
}
},
};
Example: Product Database Resource
Here's a resource for accessing product data:
// src/resources/products.js
import { pool } from '../services/database';
export const productsResource = {
name: 'products',
description: 'Access to product catalog and inventory information',
fetch: async (query) => {
try {
// Query the database for products matching the query
const result = await pool.query(
`SELECT p.id, p.name, p.description, p.price, p.category,
i.stock_level, i.warehouse_location
FROM products p
LEFT JOIN inventory i ON p.id = i.product_id
WHERE p.name ILIKE $1
OR p.description ILIKE $1
OR p.category ILIKE $1
LIMIT 10`,
[`%${query}%`]
);
// Return formatted product data
return result.rows.map(product => ({
id: product.id,
name: product.name,
description: product.description,
price: product.price,
category: product.category,
in_stock: product.stock_level > 0,
stock_level: product.stock_level,
location: product.warehouse_location,
}));
} catch (error) {
console.error('Error querying products:', error);
return []; // Return empty array on error
}
},
};
Registering Resources
Register your resources in the resources index file:
// src/resources/index.js
import { documentsResource } from './documents';
import { productsResource } from './products';
export const resources = [
documentsResource,
productsResource,
];
Then configure your MCP server to use these resources:
// src/index.js
import { MCPServer } from '@trmx/server';
import { tools } from './tools';
import { resources } from './resources';
const server = new MCPServer({
port: 3000,
tools,
resources, // Register all resources
});
server.start().then(() => {
console.log('MCP server running with tools and resources on port 3000');
});
Advanced Tool and Resource Features
Tool Permissions
Restrict tool usage based on user roles:
// src/tools/admin-tool.js
export const adminTool = {
name: 'admin_operations',
description: 'Perform administrative operations',
permissions: ['admin'], // Only users with admin role can use this tool
parameters: {
// parameters definition
},
execute: async (params) => {
// Implementation
},
};
Resource Caching
Implement caching to improve performance:
// src/resources/cached-documents.js
import { createClient } from 'redis';
import { searchDocuments } from '../services/document-service';
// Create Redis client
const redis = createClient({
url: process.env.REDIS_URL,
});
redis.connect().catch(console.error);
export const cachedDocumentsResource = {
name: 'documents',
description: 'Access to company documents with caching',
fetch: async (query) => {
try {
// Try to get cached results
const cacheKey = `docs:${query.toLowerCase()}`;
const cachedData = await redis.get(cacheKey);
if (cachedData) {
return JSON.parse(cachedData);
}
// If not cached, fetch from database
const documents = await searchDocuments(query);
const results = documents.map(doc => ({
id: doc.id,
title: doc.title,
content: doc.content,
url: doc.url,
}));
// Cache the results (expire after 1 hour)
await redis.set(cacheKey, JSON.stringify(results), { EX: 3600 });
return results;
} catch (error) {
console.error('Error in cached documents resource:', error);
return [];
}
},
};
Metrics and Monitoring
Track usage of tools and resources:
// src/middleware/metrics.js
import { collectMetrics } from '../services/metrics-service';
export function toolMetricsMiddleware(toolObj) {
const originalExecute = toolObj.execute;
toolObj.execute = async (params, context) => {
const startTime = Date.now();
let status = 'success';
try {
const result = await originalExecute(params, context);
return result;
} catch (error) {
status = 'error';
throw error;
} finally {
const duration = Date.now() - startTime;
// Record metrics
collectMetrics({
type: 'tool',
name: toolObj.name,
duration,
status,
userId: context.userId,
params: JSON.stringify(params),
});
}
};
return toolObj;
}
Apply the middleware when registering tools:
import { toolMetricsMiddleware } from './middleware/metrics';
const toolsWithMetrics = tools.map(tool => toolMetricsMiddleware(tool));
const server = new MCPServer({
tools: toolsWithMetrics,
// Other config...
});
Testing Tools and Resources
Unit Testing Tools
// tests/tools/calculator.test.js
import { calculatorTool } from '../../src/tools/calculator';
describe('Calculator Tool', () => {
test('adds two numbers correctly', async () => {
const result = await calculatorTool.execute({
operation: 'add',
a: 5,
b: 3,
});
expect(result.result).toBe(8);
});
test('throws error on division by zero', async () => {
await expect(calculatorTool.execute({
operation: 'divide',
a: 10,
b: 0,
})).rejects.toThrow('Division by zero');
});
});
Integration Testing
// tests/integration/tools-api.test.js
import request from 'supertest';
import { app } from '../../src/app';
describe('Tools API', () => {
test('calls calculator tool successfully', async () => {
const response = await request(app)
.post('/api/tools/call')
.set('Authorization', `Bearer ${process.env.TEST_API_KEY}`)
.send({
name: 'calculator',
arguments: {
operation: 'multiply',
a: 4,
b: 5,
},
});
expect(response.status).toBe(200);
expect(response.body.result).toBe(20);
});
});
Best Practices
For Tools
- Clear Documentation: Provide detailed descriptions and parameter information
- Error Handling: Implement robust error handling with clear error messages
- Input Validation: Validate all inputs before processing
- Timeout Management: Add timeouts for external API calls
- Idempotency: Make tool operations idempotent when possible
For Resources
- Rate Limiting: Implement rate limiting for external data sources
- Pagination: Support pagination for large result sets
- Caching: Cache frequently accessed data
- Context Relevance: Return only the most relevant data for the query
- Data Formatting: Provide consistent data structure across resources
Implementing Tool and Resource Versioning
As your MCP server evolves, you may need to update tools and resources while maintaining backward compatibility:
// src/tools/calculator.js
export const calculatorToolV1 = {
name: 'calculator',
version: '1.0',
// V1 implementation...
};
export const calculatorToolV2 = {
name: 'calculator',
version: '2.0',
// V2 implementation with new features...
};
Handle versioning in your tools registry:
// src/tools/index.js
import { calculatorToolV1, calculatorToolV2 } from './calculator';
// Default to latest versions
export const tools = [
calculatorToolV2,
];
// Export specific versions for backward compatibility
export const toolsV1 = [
calculatorToolV1,
];