This guide outlines the step-by-step process for adding a new tool to the Fantasy Top MCP Server.
Adding a new tool involves the following main steps:
- Add the API method to
FantasyApiService - Create the tool implementation in the appropriate tools file
- Register the tool in
mcp-server.ts - Update the system prompt in
system-prompt.txt - Build and test the tool
First, add a new method to the FantasyApiService class in src/services/fantasyApi.ts:
async getNewToolData(param1: string, param2?: number): Promise<any> {
try {
console.log(`Fetching data for ${param1}`);
if (!param1 || param1.trim() === '') {
throw new Error('Parameter 1 is required');
}
// Make the API call
const response = await this.apiClient.get(`/endpoint/path/${param1}`, {
params: { additionalParam: param2 }
});
console.log('API response:', {
status: response.status,
statusText: response.statusText,
data: response.data
});
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
console.error('Detailed error fetching data:', {
message: error.message,
status: error.response?.status,
statusText: error.response?.statusText,
data: error.response?.data,
});
throw new Error(`Failed to fetch data: ${error.response?.data?.message || error.message}`);
}
throw error;
}
}Add the tool to the appropriate tools file (e.g., playerTools.ts, heroTools.ts):
// Zod schema for tool parameters
const newToolParams = z.object({
param1: z.string().describe('Description of the first parameter'),
param2: z.number().optional().describe('Description of the optional second parameter')
});// Implement the tool function
async function newToolFunction({ param1, param2 }: z.infer<typeof newToolParams>): Promise<McpToolResponse> {
try {
const apiService = new FantasyApiService();
console.log('Executing new tool with params:', param1, param2);
// Special handling for certain parameter types if needed
// For example, if param1 can be a name instead of ID:
if (needsSpecialHandling(param1)) {
// Handle special case
try {
// Convert name to ID or perform other transformation
const transformedParam = await transformParameter(param1);
param1 = transformedParam;
} catch (transformError) {
console.error('Error transforming parameter:', transformError);
// Handle error or continue with original parameter
}
}
// Call the API service method
const response = await apiService.getNewToolData(param1, param2);
// Handle empty response
if (!response) {
return {
content: [
{
type: 'text',
text: `No data found for parameter: ${param1}`
}
]
};
}
// Format and return the response
return {
content: [
{
type: 'text',
text: formatResponse(response) // Create a helper function to format complex responses
}
]
};
} catch (error) {
console.error('Error in newToolFunction:', error);
return {
content: [
{
type: 'text',
text: `Failed to execute tool: ${error instanceof Error ? error.message : 'Unknown error'}`
}
],
isError: true
};
}
}
// Helper function to format response (if needed)
function formatResponse(data: any): string {
// Format the data as needed
return `Formatted Response:
- Key1: ${data.key1}
- Key2: ${data.key2}
- Key3: ${data.key3}`;
}// Add to the exported tools object
export const playerTools = {
// ... existing tools ...
newTool: {
description: 'Clear description of what the tool does and when to use it',
parameters: newToolParams,
implementation: newToolFunction
}
};Add the tool registration to src/mcp-server.ts:
// Add the new tool
server.tool(
"newToolName", // This is the name that will be used to invoke the tool
"Detailed description of the tool explaining what it does, when to use it, and any special considerations.", // This appears in tool documentation
playerTools.newTool.parameters.shape, // The parameter schema
playerTools.newTool.implementation // The implementation function
);Add the tool to the list of available tools in the system prompt in claude-mcp-integration/system-prompt.txt:
## Available Tools and Their Functions
- **Player Tools**:
* searchByName: Combined hero/player search
* ...
* newToolName: Brief description of what the tool does
Build the project to apply all changes:
npm run buildTest the tool by restarting the server and making queries that should trigger its use.
- Make sure the tool is registered in
mcp-server.ts - Verify that the build completed successfully
- Check the MCP client logs to ensure the tool appears in the list of available tools
- Check that the API endpoint exists and is correctly implemented
- Verify parameter formatting is correct
- Look for any special cases in parameter handling
- Ensure the Zod schema correctly defines all required parameters
- Check for proper type conversion where needed
Here's a complete example of adding the getPlayerGameStats tool:
async getPlayerGameStats(playerId: string): Promise<{ totalBurns: number; totalLevelUps: number; totalTactics: number; totalDecks: number }> {
try {
console.log('Fetching game stats for player with ID:', playerId);
if (!playerId || playerId.trim() === '') {
throw new Error('Player ID is required');
}
// Player ID is the wallet address
const response = await this.apiClient.get(`/player/game-stats/${playerId}`);
console.log('Player game stats response:', {
status: response.status,
statusText: response.statusText,
data: response.data
});
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
console.error('Detailed error fetching player game stats:', {
message: error.message,
status: error.response?.status,
statusText: error.response?.statusText,
data: error.response?.data,
});
throw new Error(`Failed to fetch player game stats: ${error.response?.data?.message || error.message}`);
}
throw error;
}
}// Zod schema for player game stats parameters
const playerGameStatsParams = z.object({
playerId: z.string().describe('The wallet address of the player to get game stats for')
});
// Get player game stats
async function getPlayerGameStats({ playerId }: z.infer<typeof playerGameStatsParams>): Promise<McpToolResponse> {
try {
const apiService = new FantasyApiService();
console.log('Fetching game stats for player:', playerId);
// If playerId doesn't look like an Ethereum address, try searching for the player first
if (!playerId.startsWith('0x') || playerId.length !== 42) {
console.log('Player ID does not look like an Ethereum address, searching for player by name first');
try {
const players = await apiService.searchPlayers(playerId);
if (players && players.length > 0) {
// Use the first matching player's ID
playerId = players[0].id;
console.log(`Found player with ID: ${playerId}`);
} else {
return {
content: [
{
type: 'text',
text: `Could not find player with name/handle: "${playerId}". Please provide a valid player wallet address or a name that matches a known player.`
}
],
isError: true
};
}
} catch (searchError) {
console.error('Error searching for player:', searchError);
// Continue with the original ID, the API will return an error if invalid
}
}
const response = await apiService.getPlayerGameStats(playerId);
if (!response) {
return {
content: [
{
type: 'text',
text: `No game stats found for player: ${playerId}`
}
]
};
}
return {
content: [
{
type: 'text',
text: `Player Game Stats for ${playerId}:
- Total Burns: ${response.totalBurns || 0}
- Total Level Ups: ${response.totalLevelUps || 0}
- Total Tactics: ${response.totalTactics || 0}
- Total Decks: ${response.totalDecks || 0}`
}
]
};
} catch (error) {
console.error('Error in getPlayerGameStats:', error);
return {
content: [
{
type: 'text',
text: `Failed to get player game stats: ${error instanceof Error ? error.message : 'Unknown error'}`
}
],
isError: true
};
}
}
// Add to exported tools object
export const playerTools = {
// ... existing tools ...
getPlayerGameStats: {
description: 'Get quick game stats for a player including total burns, level ups, tactics, and decks',
parameters: playerGameStatsParams,
implementation: getPlayerGameStats
}
};// Add the getPlayerGameStats tool
server.tool(
"getPlayerGameStats",
"Get quick game stats for a player including total burns, level ups, tactics, and decks. This provides a summary of a player's activity metrics in a single view. You can provide either a wallet address (starts with 0x) or a player name (the tool will attempt to find their wallet address).",
playerTools.getPlayerGameStats.parameters.shape,
playerTools.getPlayerGameStats.implementation
);## Available Tools and Their Functions
- **Player Tools**:
* searchByName: Combined hero/player search
* ...
* getPlayerGameStats: Get quick game stats for a player including total burns, level ups, tactics, and decks