Skip to main content
Glama
MoralisWeb3

Moralis MCP Server

Official
by MoralisWeb3

evm_gettimeseriestokenanalytics

Analyze token performance over time by fetching historical buy/sell volume, liquidity, and FDV data for up to 200 tokens across multiple blockchains.

Instructions

Fetch timeseries buy volume, sell volume, liquidity and FDV for multiple tokens. Accepts an array of up to 200 tokens, each requiring chain and tokenAddress.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
timeframeYesThe timeframe to query1d
requestBodyYesBody

Implementation Reference

  • The core handler function that implements the execution logic for all dynamically registered tools, including 'evm_gettimeseriestokenanalytics'. It validates inputs using Zod schema derived from OpenAPI, builds the HTTP request (URL, params, headers, body, auth), calls the Moralis API via axios, handles errors, and returns formatted results.
    export async function executeApiTool(
      toolName: string,
      definition: McpToolDefinition,
      toolArgs: JsonObject,
      allSecuritySchemes: Record<string, any>,
      token?: string,
    ): Promise<CallToolResult> {
      try {
        // Validate arguments against the input schema
        let validatedArgs: JsonObject;
        try {
          const zodSchema = getZodSchemaFromJsonSchema(
            definition.inputSchema,
            toolName,
          );
          const argsToParse =
            typeof toolArgs === 'object' && toolArgs !== null ? toolArgs : {};
          validatedArgs = zodSchema.parse(argsToParse);
        } catch (error: unknown) {
          if (error instanceof ZodError) {
            const validationErrorMessage = `Invalid arguments for tool '${toolName}': ${error.errors.map((e) => `${e.path.join('.')} (${e.code}): ${e.message}`).join(', ')}`;
            return { content: [{ type: 'text', text: validationErrorMessage }] };
          } else {
            const errorMessage =
              error instanceof Error ? error.message : String(error);
            return {
              content: [
                {
                  type: 'text',
                  text: `Internal error during validation setup: ${errorMessage}`,
                },
              ],
            };
          }
        }
    
        // Prepare URL, query parameters, headers, and request body
        let urlPath = definition.pathTemplate;
        const queryParams: Record<string, any> = {};
        const headers: Record<string, string> = {
          Accept: 'application/json',
          'X-Moralis-Platform': 'MCP',
        };
        let requestBodyData: any = undefined;
    
        // Apply parameters to the URL path, query, or headers
        definition.executionParameters.forEach((param) => {
          const value = validatedArgs[param.name];
          if (typeof value !== 'undefined' && value !== null) {
            if (param.in === 'path') {
              urlPath = urlPath.replace(
                `{${param.name}}`,
                encodeURIComponent(String(value)),
              );
            } else if (param.in === 'query') {
              queryParams[param.name] = value;
            } else if (param.in === 'header') {
              headers[param.name.toLowerCase()] = String(value);
            }
          }
        });
    
        // Ensure all path parameters are resolved
        if (urlPath.includes('{')) {
          throw new Error(`Failed to resolve path parameters: ${urlPath}`);
        }
    
        // Construct the full URL
        const requestUrl = `${definition.baseUrl}${urlPath}`;
    
        // Handle request body if needed
        if (
          definition.requestBodyContentType &&
          typeof validatedArgs['requestBody'] !== 'undefined'
        ) {
          requestBodyData = validatedArgs['requestBody'];
          headers['content-type'] = definition.requestBodyContentType;
        }
    
        // Apply security requirements if available
        // Security requirements use OR between array items and AND within each object
        const appliedSecurity = definition.securityRequirements?.find((req) => {
          // Try each security requirement (combined with OR)
          return Object.entries(req).every(([schemeName, scopesArray]) => {
            const scheme = allSecuritySchemes[schemeName];
            if (!scheme) return false;
    
            // API Key security (header, query, cookie)
            if (scheme.type === 'apiKey') {
              return !!token || !!Config.MORALIS_API_KEY;
            }
    
            return false;
          });
        });
    
        // If we found matching security scheme(s), apply them
        if (appliedSecurity) {
          // Apply each security scheme from this requirement (combined with AND)
          for (const [schemeName, scopesArray] of Object.entries(appliedSecurity)) {
            const scheme = allSecuritySchemes[schemeName];
    
            // API Key security
            if (scheme?.type === 'apiKey') {
              const apiKey = token || Config.MORALIS_API_KEY;
              if (apiKey) {
                if (scheme.in === 'header') {
                  headers[scheme.name.toLowerCase()] = apiKey;
                  console.error(
                    `Applied API key '${schemeName}' in header '${scheme.name}'`,
                  );
                }
              }
            }
          }
        }
        // Log warning if security is required but not available
        else if (definition.securityRequirements?.length > 0) {
          // First generate a more readable representation of the security requirements
          const securityRequirementsString = definition.securityRequirements
            .map((req) => {
              const parts = Object.entries(req)
                .map(([name, scopesArray]) => {
                  const scopes = scopesArray as string[];
                  if (scopes.length === 0) return name;
                  return `${name} (scopes: ${scopes.join(', ')})`;
                })
                .join(' AND ');
              return `[${parts}]`;
            })
            .join(' OR ');
    
          console.warn(
            `Tool '${toolName}' requires security: ${securityRequirementsString}, but no suitable credentials found.`,
          );
        }
    
        // Prepare the axios request configuration
        const config: AxiosRequestConfig = {
          method: definition.method.toUpperCase(),
          url: requestUrl,
          params: queryParams,
          headers: headers,
          ...(requestBodyData !== undefined && { data: requestBodyData }),
        };
    
        // Log request info to stderr (doesn't affect MCP output)
        console.error(
          `Executing tool "${toolName}": ${config.method} ${config.url}`,
        );
    
        // Execute the request
        const response = await axios(config);
    
        // Process and format the response
        let responseText = '';
        const contentType = response.headers['content-type']?.toLowerCase() || '';
    
        // Handle JSON responses
        if (
          contentType.includes('application/json') &&
          typeof response.data === 'object' &&
          response.data !== null
        ) {
          try {
            responseText = JSON.stringify(response.data, null, 2);
          } catch (e) {
            responseText = '[Stringify Error]';
          }
        }
        // Handle string responses
        else if (typeof response.data === 'string') {
          responseText = response.data;
        }
        // Handle other response types
        else if (response.data !== undefined && response.data !== null) {
          responseText = String(response.data);
        }
        // Handle empty responses
        else {
          responseText = `(Status: ${response.status} - No body content)`;
        }
    
        // Return formatted response
        return {
          content: [
            {
              type: 'text',
              text: `API Response (Status: ${response.status}):\n${responseText}`,
            },
          ],
        };
      } catch (error: unknown) {
        // Handle errors during execution
        let errorMessage: string;
    
        // Format Axios errors specially
        if (axios.isAxiosError(error)) {
          errorMessage = formatApiError(error);
        }
        // Handle standard errors
        else if (error instanceof Error) {
          errorMessage = error.message;
        }
        // Handle unexpected error types
        else {
          errorMessage = `Unexpected error: ${String(error)}`;
        }
    
        // Log error to stderr
        console.error(
          `Error during execution of tool '${toolName}':`,
          errorMessage,
        );
    
        // Return error message to client
        return { content: [{ type: 'text', text: errorMessage }] };
      }
    }
  • src/server.ts:97-120 (registration)
    Registers the MCP CallToolRequest handler that dispatches tool calls (including 'evm_gettimeseriestokenanalytics') to the executeApiTool function based on the name lookup in toolDefinitionMap.
      CallToolRequestSchema,
      async (request: CallToolRequest, c): Promise<CallToolResult> => {
        const { name: toolName, arguments: toolArgs } = request.params;
        const toolDefinition = toolDefinitionMap[toolName];
        if (!toolDefinition) {
          console.error(`Error: Unknown tool requested: ${toolName}`);
          return {
            content: [
              {
                type: 'text',
                text: `Error: Unknown tool requested: ${toolName}`,
              },
            ],
          };
        }
        return executeApiTool(
          toolName,
          toolDefinition,
          toolArgs ?? {},
          securitySchemes,
          c.authInfo?.token,
        );
      },
    );
  • src/server.ts:85-94 (registration)
    Registers the MCP ListToolsRequest handler that returns the list of all available tools, including 'evm_gettimeseriestokenanalytics', extracted from the EVM OpenAPI spec with 'evm_' prefix.
    server.setRequestHandler(ListToolsRequestSchema, async () => {
      const toolsForClient: Tool[] = Object.values(toolDefinitionMap).map(
        (def) => ({
          name: def.name,
          description: def.description,
          inputSchema: def.inputSchema,
        }),
      );
      return { tools: toolsForClient };
    });
  • Generates the input schema (JSON Schema7) for each tool from the OpenAPI operation's parameters and requestBody. This schema is used for Zod validation in the handler for 'evm_gettimeseriestokenanalytics'.
    export function generateInputSchemaAndDetails(
      operation: OpenAPIV3.OperationObject,
    ): {
      inputSchema: JSONSchema7 | boolean;
      parameters: OpenAPIV3.ParameterObject[];
      requestBodyContentType?: string;
    } {
      const properties: { [key: string]: JSONSchema7 | boolean } = {};
      const required: string[] = [];
    
      // Process parameters
      const allParameters: OpenAPIV3.ParameterObject[] = Array.isArray(
        operation.parameters,
      )
        ? operation.parameters.map((p) => p as OpenAPIV3.ParameterObject)
        : [];
    
      allParameters.forEach((param) => {
        if (!param.name || !param.schema) return;
    
        const paramSchema = mapOpenApiSchemaToJsonSchema(
          param.schema as OpenAPIV3.SchemaObject,
        );
        if (typeof paramSchema === 'object') {
          paramSchema.description = param.description || paramSchema.description;
        }
    
        properties[param.name] = paramSchema;
        if (param.required) required.push(param.name);
      });
    
      // Process request body (if present)
      let requestBodyContentType: string | undefined = undefined;
    
      if (operation.requestBody) {
        const opRequestBody = operation.requestBody as OpenAPIV3.RequestBodyObject;
        const jsonContent = opRequestBody.content?.['application/json'];
        const firstContent = opRequestBody.content
          ? Object.entries(opRequestBody.content)[0]
          : undefined;
    
        if (jsonContent?.schema) {
          requestBodyContentType = 'application/json';
          const bodySchema = mapOpenApiSchemaToJsonSchema(
            jsonContent.schema as OpenAPIV3.SchemaObject,
          );
    
          if (typeof bodySchema === 'object') {
            bodySchema.description =
              opRequestBody.description ||
              bodySchema.description ||
              'The JSON request body.';
          }
    
          properties['requestBody'] = bodySchema;
          if (opRequestBody.required) required.push('requestBody');
        } else if (firstContent) {
          const [contentType] = firstContent;
          requestBodyContentType = contentType;
    
          properties['requestBody'] = {
            type: 'string',
            description:
              opRequestBody.description ||
              `Request body (content type: ${contentType})`,
          };
    
          if (opRequestBody.required) required.push('requestBody');
        }
      }
    
      // Combine everything into a JSON Schema
      const inputSchema: JSONSchema7 = {
        type: 'object',
        properties,
        ...(required.length > 0 && { required }),
      };
    
      return { inputSchema, parameters: allParameters, requestBodyContentType };
    }
  • Helper that fetches the EVM OpenAPI spec, dereferences it, extracts tools using prefix 'evm_', filters blacklist, and creates the toolDefinitionMap used for registration and execution.
    async function mapToolDefinitions(config: SchemaConfig) {
      const spec = (await getSpec(config.specUrl)) as OpenAPIV3DocumentX;
      const api = (await SwaggerParser.dereference(spec)) as OpenAPIV3DocumentX;
    
      const tools = extractToolsFromApi(api, config.prefix);
      const blacklist = Array.isArray(api['x-mcp-blacklist'])
        ? api['x-mcp-blacklist'].map((e) => `${config.prefix}${e.toLowerCase()}`)
        : [];
    
      const toolDefinitionMap: Record<string, McpToolDefinition> = {};
      for (const tool of tools) {
        if (blacklist.includes(tool.name)) continue;
        toolDefinitionMap[tool.name] = {
          ...tool,
          baseUrl: config.baseUrl,
        };
      }
      return toolDefinitionMap;
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It mentions the token limit (200) but does not disclose other behavioral traits such as rate limits, authentication needs, error handling, or response format. For a data-fetching tool with no annotation coverage, this leaves significant gaps in understanding its operation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that front-loads the purpose and key constraint. Every word earns its place with no redundancy, making it easy to parse quickly.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity of fetching timeseries analytics for multiple tokens, no annotations, and no output schema, the description is incomplete. It lacks details on response structure, error conditions, rate limits, and how the metrics are calculated or returned, which are crucial for effective tool use.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema fully documents both parameters. The description adds minimal value by mentioning the token array requirement and its structure, but does not provide additional semantics beyond what the schema already specifies. Baseline 3 is appropriate when the schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the specific action ('Fetch'), the resource ('timeseries buy volume, sell volume, liquidity and FDV'), and the scope ('for multiple tokens'). It distinguishes from siblings like 'evm_getmultipletokenanalytics' (likely cross-sectional) by specifying timeseries data, and from 'evm_gettimeseriesvolume' by listing additional metrics beyond volume.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage for timeseries analytics on tokens, but does not explicitly state when to use this tool versus alternatives like 'evm_getmultipletokenanalytics' or 'evm_gettimeseriesvolume'. It mentions the token array constraint, but lacks guidance on prerequisites or exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/MoralisWeb3/moralis-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server