Skip to main content

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

FeatureToolsResources
PurposePerform actions and operationsProvide access to data
DirectionAI calls tools to take actionAI queries resources to get information
Use CaseCalculations, API calls, data manipulationKnowledge bases, databases, document retrieval
Return ValueResult of the operationRelevant 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

  1. Clear Documentation: Provide detailed descriptions and parameter information
  2. Error Handling: Implement robust error handling with clear error messages
  3. Input Validation: Validate all inputs before processing
  4. Timeout Management: Add timeouts for external API calls
  5. Idempotency: Make tool operations idempotent when possible

For Resources

  1. Rate Limiting: Implement rate limiting for external data sources
  2. Pagination: Support pagination for large result sets
  3. Caching: Cache frequently accessed data
  4. Context Relevance: Return only the most relevant data for the query
  5. 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,
];

Next Steps