Skip to main content
Glama
MoralisWeb3

Moralis MCP Server

Official
by MoralisWeb3

evm_getnfthistoricalfloorpricebycontract

Retrieve historical floor price data for NFT collections to analyze market trends and track valuation changes over time.

Instructions

Get timeseries historical floor prices for a given NFT collection. Refreshes every 30 minutes.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
chainNoThe chain to queryeth
intervalYesThe duration to query1d
addressYesThe address of the NFT contract
cursorNoThe cursor returned in the previous response (used for getting the next page).

Implementation Reference

  • Generic execution handler for all OpenAPI-derived tools. Validates inputs using Zod from inputSchema, constructs Axios request (URL, params, headers, body, auth), calls Moralis EVM API, formats response as MCP content. This implements the logic for 'evm_getnfthistoricalfloorpricebycontract'.
    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:96-120 (registration)
    Registers the MCP 'callTool' request handler. Looks up tool definition by name (e.g., 'evm_getnfthistoricalfloorpricebycontract'), invokes the shared executeApiTool handler.
    server.setRequestHandler(
      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 'listTools' request handler, exposing all extracted tools including 'evm_getnfthistoricalfloorpricebycontract'.
    server.setRequestHandler(ListToolsRequestSchema, async () => {
      const toolsForClient: Tool[] = Object.values(toolDefinitionMap).map(
        (def) => ({
          name: def.name,
          description: def.description,
          inputSchema: def.inputSchema,
        }),
      );
      return { tools: toolsForClient };
    });
  • src/server.ts:26-44 (registration)
    Dynamically extracts tool definitions from Moralis EVM OpenAPI spec using 'evm_' prefix, creates registry map populated during server setup. Includes the definition for 'evm_getnfthistoricalfloorpricebycontract'.
    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;
    }
  • Creates JSON Schema for tool inputs by combining path/query/header params and requestBody from OpenAPI operation. Used to generate inputSchema validated at runtime for 'evm_getnfthistoricalfloorpricebycontract'.
    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 };
    }
Behavior2/5

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

With no annotations provided, the description carries the full burden of behavioral disclosure. It mentions 'Refreshes every 30 minutes,' which adds useful context about data update frequency. However, it lacks critical details such as whether this is a read-only operation (implied but not stated), potential rate limits, authentication requirements, error handling, or pagination behavior (though 'cursor' in the schema hints at pagination). For a tool with no annotations, this is insufficient.

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 highly concise and front-loaded: two sentences that directly state the tool's purpose and a key behavioral trait ('Refreshes every 30 minutes'). There is zero waste or redundancy, making it efficient for an agent to parse.

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 tool's complexity (historical data retrieval with multiple parameters), lack of annotations, and no output schema, the description is incomplete. It covers the basic purpose and refresh rate but omits essential context such as output format, error conditions, pagination details (implied by 'cursor' but not explained), and comparison to sibling tools. This leaves significant gaps for an agent to use the tool effectively.

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 already documents all parameters thoroughly. The description doesn't add any parameter-specific semantics beyond what's in the schema (e.g., it doesn't explain 'interval' options or 'address' format). Baseline 3 is appropriate as the schema does the heavy lifting, but the description doesn't compensate with extra insights.

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

Purpose4/5

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

The description clearly states the tool's purpose: 'Get timeseries historical floor prices for a given NFT collection.' It specifies the verb ('Get'), resource ('historical floor prices'), and scope ('timeseries' for a 'given NFT collection'). However, it doesn't explicitly differentiate from sibling tools like 'evm_getnftfloorpricebycontract' (which likely provides current rather than historical data), so it misses full sibling distinction.

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

Usage Guidelines2/5

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

The description provides minimal usage guidance. It mentions 'Refreshes every 30 minutes,' which hints at data freshness but doesn't specify when to use this tool versus alternatives (e.g., for historical analysis vs. current prices). No explicit when/when-not instructions or named alternatives are provided, leaving the agent with little contextual direction.

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