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

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