Skip to content

Latest commit

 

History

History
355 lines (299 loc) · 10.5 KB

File metadata and controls

355 lines (299 loc) · 10.5 KB

Adding a New Tool to Fantasy Top MCP Server

This guide outlines the step-by-step process for adding a new tool to the Fantasy Top MCP Server.

Overview

Adding a new tool involves the following main steps:

  1. Add the API method to FantasyApiService
  2. Create the tool implementation in the appropriate tools file
  3. Register the tool in mcp-server.ts
  4. Update the system prompt in system-prompt.txt
  5. Build and test the tool

Detailed Steps

1. Add the API Method to FantasyApiService

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;
  }
}

2. Create the Tool Implementation

Add the tool to the appropriate tools file (e.g., playerTools.ts, heroTools.ts):

A. Create the Zod Schema for Parameters

// 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')
});

B. Implement the Tool Function

// 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}`;
}

C. Add the Tool to the Export Object

// 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
  }
};

3. Register the Tool in mcp-server.ts

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
);

4. Update the System Prompt in system-prompt.txt

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

5. Build and Test

Build the project to apply all changes:

npm run build

Test the tool by restarting the server and making queries that should trigger its use.

Common Issues and Troubleshooting

Tool Not Available to Claude

  • 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

API Call Errors

  • Check that the API endpoint exists and is correctly implemented
  • Verify parameter formatting is correct
  • Look for any special cases in parameter handling

Parameter Type Errors

  • Ensure the Zod schema correctly defines all required parameters
  • Check for proper type conversion where needed

Example: Adding getPlayerGameStats Tool

Here's a complete example of adding the getPlayerGameStats tool:

1. API Method in FantasyApiService

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;
  }
}

2. Tool Implementation in playerTools.ts

// 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
  }
};

3. Registration in mcp-server.ts

// 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
);

4. System Prompt Update in system-prompt.txt

## 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