Skip to main content
Glama
Noveum

API-Market MCP Server

by Noveum

Search_for_nearby_places

Find places within a specified radius of a location by inputting latitude/longitude, distance, and type of place. Supports multiple languages and integrates with the API-Market MCP Server for easy access.

Instructions

Search for places around location within a specified radius.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
languageNoThe two-letter language code in which to return results (ISO 639-1)
locationYesThe latitude/longitude around which to retrieve places
radiusNoThe distance (in meters) within which to return results. Max = 10000 m.
typeNoThe type of places that are returned

Implementation Reference

  • Generic handler for executing any tool, including "Search_for_nearby_places". It resolves the tool by name or ID, constructs the API endpoint URL from the tool ID, handles parameters based on HTTP method (query for GET, body for others), sends the axios request to the configured apiBaseUrl, and returns the response as text content.
    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;
      }
    });
  • Dynamically generates and registers MCP tools from OpenAPI specifications listed in the openApiSpec file (modified_files.txt). The tool name like "Search_for_nearby_places" would be derived from operationId, summary, or method+path. Constructs inputSchema from parameters and requestBody, sets content-type headers, stores in tools map for lookup during execution.
    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);
        }
      }
    }
  • Handler for listing all registered tools, including the schemas. Note: schema generation happens during registration.
    this.server.setRequestHandler(ListToolsRequestSchema, async () => {
      return {
        tools: Array.from(this.tools.values()),
      };
    });
    
    // 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;
      }
    });
  • Helper to read the list of OpenAPI spec files from config.openApiSpec (modified_files.txt generated by utils/modify_api.py).
    private async listOfFilePaths(): Promise<string[]> {
      const lines = [];
      try {
        const fileStream = fs.createReadStream(this.config.openApiSpec);
    
        const rl = readline.createInterface({
          input: fileStream,
          crlfDelay: Infinity
        });
    
        for await (const line of rl) {
          lines.push(line);
        }
    
        return lines; // Return the array of lines    
      } catch (error) {
        console.error(`Error reading file paths from ${this.config.openApiSpec}:`, error);
        throw new Error(`Failed to read API specifications list: ${error.message}`);
      }
    
    }
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 of behavioral disclosure. It mentions searching for places but does not describe key behavioral traits such as rate limits, authentication needs, error handling, or the format of results (e.g., list of places with details). This leaves significant gaps in understanding how the tool operates beyond basic functionality.

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 directly states the tool's purpose without unnecessary words. It is front-loaded with the core action and scope, making it easy to parse quickly. There is no wasted language, and it effectively communicates the essential information in a compact form.

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 a search tool with 4 parameters and no output schema, the description is incomplete. It lacks details on behavioral aspects (e.g., result format, pagination, errors) and does not provide usage context or differentiate from siblings. Without annotations or an output schema, the description fails to offer sufficient context for an AI agent to fully understand how to invoke and interpret results from this tool.

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%, meaning all parameters are documented in the input schema with clear descriptions (e.g., 'location' as latitude/longitude, 'radius' in meters with a max). The description adds no additional meaning beyond the schema, such as examples or constraints not covered. With high schema coverage, the baseline score of 3 is appropriate as the description does not compensate but also does not detract.

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 as 'Search for places around location within a specified radius,' which includes a specific verb ('Search') and resource ('places'), and specifies the spatial scope ('around location within a specified radius'). However, it does not explicitly differentiate from its sibling 'Search_for_places_by_text,' which appears to be a text-based alternative, leaving some ambiguity about when to use each.

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. It mentions the action but does not specify contexts, prerequisites, or exclusions, such as comparing it to 'Search_for_places_by_text' for text-based queries or other location-based tools in the sibling list. This lack of explicit usage instructions limits its effectiveness for an AI agent.

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