Skip to main content
Glama
Noveum

API-Market MCP Server

by Noveum

distance_and_duration_bw_starts_and_stops

Calculate optimal route distance and driving duration between origin and destination coordinates on real road networks, avoiding tolls, highways, or ferries, with customizable start times.

Instructions

Calculate length and driving time of the optimal routes between origin and destination points on the real road network.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
avoid_ferriesNoAvoid ferries
avoid_highwaysNoAvoid highways
avoid_tollsNoAvoid tolls
destinationsNoList of destinations described as semicolon-delimited coordinate pairs with latitudes and longitudes. If not specified, an n x n matrix will be generated using the origins. Maximum 25 pairs per request.
originsYesList of origins described as semicolon-delimited coordinate pairs with latitudes and longitudes. Maximum 25 pairs per request.
start_timeNoTime when travel is expected to start. You can specify the time as an integer in seconds since midnight, January 1, 1970 UTC or you can use 'now' to specify the current time.

Implementation Reference

  • This is the core handler function for executing any MCP tool call, including 'distance_and_duration_bw_starts_and_stops'. It looks up the tool by name or ID, reconstructs the corresponding API endpoint path from the tool ID (e.g., POST-distance_and_duration_bw_starts_and_stops -> /distance/and_duration_bw/starts/and_stops or similar based on transformation), constructs the axios request with parameters as query or body, calls the external API at apiBaseUrl, and returns the response.
    // Handle tool execution
    this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
      const { id, name, arguments: params } = request.params;
    
      console.error("Received request:", request.params);
      console.error("Using parameters from arguments:", params);
    
      // Find tool by ID or name
      let tool: Tool | undefined;
      let toolId: string | undefined;
    
      if (id) {
        toolId = id.trim();
        tool = this.tools.get(toolId);
      } else if (name) {
        // Search for tool by name
        for (const [tid, t] of this.tools.entries()) {
          if (t.name === name) {
            tool = t;
            toolId = tid;
            break;
          }
        }
      }
    
      if (!tool || !toolId) {
        console.error(
          `Available tools: ${Array.from(this.tools.entries())
            .map(([id, t]) => `${id} (${t.name})`)
            .join(", ")}`,
        );
        throw new Error(`Tool not found: ${id || name}`);
      }
    
      console.error(`Executing tool: ${toolId} (${tool.name})`);
    
      try {
        // Extract method and path from tool ID
        const [method, ...pathParts] = toolId.split("-");
        let path = "/" + pathParts.join("/")
          .replace(/-/g, "/")
          .replaceAll('!', "-");
    
    
        // Ensure base URL ends with slash for proper joining
        const baseUrl = this.config.apiBaseUrl.endsWith("/")
          ? this.config.apiBaseUrl
          : `${this.config.apiBaseUrl}/`;
    
    
        // Remove leading slash from path to avoid double slashes
        const cleanPath = path.startsWith("/") ? path.slice(1) : path;
    
        let url;
        try {
          // Validate that the path results in a valid URL
          // Construct the full URL
          url = new URL(cleanPath, baseUrl).toString();
        } catch (error) {
          throw new Error(`Invalid path generated from tool ID ${toolId}: ${error.message}`);
        }
        // Prepare request configuration
        this.config.headers = this.config.headers || {};
        const contentType = this.headers.get(toolId);
        if (contentType) {
          this.config.headers['Content-Type'] = contentType;
        }
        const config: any = {
          method: method.toLowerCase(),
          url: url,
          headers: this.config.headers,
        };
    
        // Handle different parameter types based on HTTP method
        if (method.toLowerCase() === "get") {
          // For GET requests, ensure parameters are properly structured
          if (params && typeof params === "object") {
            // Handle array parameters properly
            const queryParams: Record<string, string> = {};
            for (const [key, value] of Object.entries(params)) {
              if (Array.isArray(value)) {
                // Join array values with commas for query params
                queryParams[key] = value.join(",");
              } else if (value !== undefined && value !== null) {
                // Convert other values to strings
                queryParams[key] = String(value);
              }
            }
            config.params = queryParams;
          }
        } else {
          // For POST, PUT, PATCH - send as body
          config.data = params;
        }
    
        console.error("Final request config:", config);
    
        try {
    
          const response = await axios(config);
          return {
            content: [{
              type: "text",
              text: JSON.stringify(response.data, null, 2)
            }]
          };
        } catch (error) {
          if (axios.isAxiosError(error)) {
            console.error("Request failed:", {
              status: error.response?.status,
              statusText: error.response?.statusText,
              data: error.response?.data,
              headers: error.response?.headers,
            });
            throw new Error(
              `API request failed: ${error.message} - ${JSON.stringify(error.response?.data)}`,
            );
          }
          throw error;
        }
    
      } catch (error) {
        if (axios.isAxiosError(error)) {
          throw new Error(`API request failed: ${error.message}`);
        }
        throw error;
      }
    });
  • Parses list of OpenAPI specs from config.openApiSpec (modified_files.txt), loads each spec, iterates over paths and methods, generates toolId from method-path, sets tool.name from op.summary (shortened to 'distance and duration bw starts and stops' -> 'distance_and_duration_bw_starts_and_stops'), builds inputSchema from params and body schema, registers tool in this.tools map.
    private async parseOpenAPISpec(): Promise<void> {
      const paths = await this.listOfFilePaths()
      for (const cur_path of paths) {
    
        if (!cur_path || cur_path.trim() === '') {
          console.error('Skipping empty path');
          continue;
        }
    
        try {
          const spec = await this.loadOpenAPISpec(path.resolve(__dirname, cur_path));
    
          // Convert each OpenAPI path to an MCP tool
          for (const [path, pathItem] of Object.entries(spec.paths)) {
            if (!pathItem) continue;
    
            for (const [method, operation] of Object.entries(pathItem)) {
              if (method === "parameters" || !operation) continue;
    
              const op = operation as OpenAPIV3.OperationObject;
              // Create a clean tool ID by removing the leading slash and replacing special chars
    
              const cleanPath = path.replace(/^\//, "");
              const toolId = `${method.toUpperCase()}-${cleanPath}`.replace(
                /[^a-zA-Z0-9-_]/g,
                "-",
              );
              const tool: Tool = {
                name:
                  (op.operationId || op.summary || `${method.toUpperCase()} ${path}`).replace(/\s+/g, "_"),
                description:
                  op.description ||
                  `Make a ${method.toUpperCase()} request to ${path}`,
                inputSchema: {
                  type: "object",
                  properties: {},
                  // Add any additional properties from OpenAPI spec
                },
              };
    
              // Add parameters from operation
              if (op.parameters) {
                for (const param of op.parameters) {
                  if ("name" in param && "in" in param) {
                    const paramSchema = param.schema as OpenAPIV3.SchemaObject;
                    tool.inputSchema.properties[param.name] = {
                      type: paramSchema.type || "string",
                      description: param.description || `${param.name} parameter`,
                    };
                    if (param.required) {
                      tool.inputSchema.required = tool.inputSchema.required || [];
                      tool.inputSchema.required.push(param.name);
                    }
                  }
                }
              }
    
              // Add request body if present (for POST, PUT, etc.)
              if (op.requestBody) {
                const requestBody = op.requestBody as OpenAPIV3.RequestBodyObject;
                const content = requestBody.content;
    
                // Usually we'd look for application/json content type
                if (content?.['application/json']) {
                  this.headers.set(toolId, 'application/json');
                  const jsonSchema = content['application/json'].schema as OpenAPIV3.SchemaObject;
    
                  // If it's a reference, we'd need to resolve it
                  // For simplicity, assuming it's an inline schema
                  if (jsonSchema.properties) {
                    // Add all properties from the request body schema
                    for (const [propName, propSchema] of Object.entries(jsonSchema.properties)) {
                      tool.inputSchema.properties[propName] = propSchema;
                    }
    
                    // Add required properties if defined
                    if (jsonSchema.required) {
                      tool.inputSchema.required = tool.inputSchema.required || [];
                      tool.inputSchema.required.push(...jsonSchema.required);
                    }
                  }
                }
                else if (content?.['application/x-www-form-urlencoded']) {
                  this.headers.set(toolId, 'application/x-www-form-urlencoded');
                  const urlencodedSchema = content['application/x-www-form-urlencoded'].schema as OpenAPIV3.SchemaObject;
    
                  if (urlencodedSchema.properties) {
                    for (const [propName, propSchema] of Object.entries(urlencodedSchema.properties)) {
                      tool.inputSchema.properties[propName] = propSchema;
                    }
    
                    if (urlencodedSchema.required) {
                      tool.inputSchema.required = tool.inputSchema.required || [];
                      tool.inputSchema.required.push(...urlencodedSchema.required);
                    }
                  }
                }
              }
    
              this.tools.set(toolId, tool);
            }
          }
        } catch (error) {
          console.error(`Error parsing OpenAPI spec from ${cur_path}:`, error);
        }
      }
    }
  • Hardcoded manual shortening of operation summaries in OpenAPI specs. Maps the original summary 'Calculate distances and durations between a set of origins and destinations.' to 'distance and duration bw starts and stops', which becomes the MCP tool name 'distance_and_duration_bw_starts_and_stops' after replacing spaces with underscores in the server code.
    summaries = ["A fast text-to-image model that makes high-quality images in 4 steps", "Finding the best route and get multiple stops driving directions", "Finding the best route between an origin and a destination", "Calculate distances and durations between a set of origins and destinations."]
    shortened_summaries = ["text-to-image", "best route for multiple stops", "best route bw start and stop", "distance and duration bw starts and stops"]
  • Dynamically constructs the inputSchema for each tool (including this one) from OpenAPI operation parameters (query/path/header) and requestBody schema (JSON or form), marking required fields.
    if (op.parameters) {
      for (const param of op.parameters) {
        if ("name" in param && "in" in param) {
          const paramSchema = param.schema as OpenAPIV3.SchemaObject;
          tool.inputSchema.properties[param.name] = {
            type: paramSchema.type || "string",
            description: param.description || `${param.name} parameter`,
          };
          if (param.required) {
            tool.inputSchema.required = tool.inputSchema.required || [];
            tool.inputSchema.required.push(param.name);
          }
        }
      }
    }
    
    // Add request body if present (for POST, PUT, etc.)
    if (op.requestBody) {
      const requestBody = op.requestBody as OpenAPIV3.RequestBodyObject;
      const content = requestBody.content;
    
      // Usually we'd look for application/json content type
      if (content?.['application/json']) {
        this.headers.set(toolId, 'application/json');
        const jsonSchema = content['application/json'].schema as OpenAPIV3.SchemaObject;
    
        // If it's a reference, we'd need to resolve it
        // For simplicity, assuming it's an inline schema
        if (jsonSchema.properties) {
          // Add all properties from the request body schema
          for (const [propName, propSchema] of Object.entries(jsonSchema.properties)) {
            tool.inputSchema.properties[propName] = propSchema;
          }
    
          // Add required properties if defined
          if (jsonSchema.required) {
            tool.inputSchema.required = tool.inputSchema.required || [];
            tool.inputSchema.required.push(...jsonSchema.required);
          }
        }
      }
      else if (content?.['application/x-www-form-urlencoded']) {
        this.headers.set(toolId, 'application/x-www-form-urlencoded');
        const urlencodedSchema = content['application/x-www-form-urlencoded'].schema as OpenAPIV3.SchemaObject;
    
        if (urlencodedSchema.properties) {
          for (const [propName, propSchema] of Object.entries(urlencodedSchema.properties)) {
            tool.inputSchema.properties[propName] = propSchema;
          }
    
          if (urlencodedSchema.required) {
            tool.inputSchema.required = tool.inputSchema.required || [];
            tool.inputSchema.required.push(...urlencodedSchema.required);
          }
        }
      }
    }
Behavior2/5

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

With no annotations provided, the description carries full burden for behavioral disclosure. It mentions 'optimal routes' and 'real road network' but doesn't specify what service/provider is used, rate limits, authentication needs, error conditions, or what the output format looks like. For a tool with 6 parameters and no output schema, this leaves significant behavioral gaps.

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, well-structured sentence that efficiently communicates the core functionality. It's front-loaded with the main purpose and contains no redundant information or unnecessary elaboration.

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?

For a tool with 6 parameters, no annotations, and no output schema, the description is insufficient. It doesn't explain what the tool returns (distance units, time format, matrix structure), error handling, rate limits, or service dependencies. The agent would struggle to use this effectively without trial and error.

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 6 parameters thoroughly. The description adds minimal value beyond what's in the schema - it mentions 'origin and destination points' which aligns with the origins/destinations parameters, but doesn't provide additional context about parameter interactions or usage patterns.

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 tool's purpose with specific verbs ('calculate length and driving time') and resources ('optimal routes between origin and destination points on the real road network'). It distinguishes itself from sibling tools like 'best_route_bw_start_and_stop' by focusing on distance/duration calculation rather than route details.

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 no guidance on when to use this tool versus alternatives like 'best_route_bw_start_and_stop' or 'best_route_for_multiple_stops'. It doesn't mention prerequisites, limitations, or comparative use cases, leaving the agent to infer usage context.

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

Related 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/Noveum/api-market-mcp-server'

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