Skip to main content
Glama
Cyreslab-AI

Shodan MCP Server

search_shodan

Query Shodan's database to find internet-connected devices and services for cybersecurity research and threat intelligence analysis.

Instructions

Search Shodan's database for devices and services

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryYesShodan search query (e.g., 'apache country:US')
pageNoPage number for results pagination (default: 1)
facetsNoList of facets to include in the search results (e.g., ['country', 'org'])
max_itemsNoMaximum number of items to include in arrays (default: 5)
fieldsNoList of fields to include in the results (e.g., ['ip_str', 'ports', 'location.country_name'])
summarizeNoWhether to return a summary of the results instead of the full data (default: false)

Implementation Reference

  • Core implementation of the Shodan search functionality, making API call to /shodan/host/search, applying sampling and field filtering, handling errors including paid membership requirement.
    async search(query: string, page: number = 1, facets: string[] = [], maxItems: number = 5, selectedFields?: string[]): Promise<any> {
      try {
        const params: any = {
          query,
          page
        };
    
        if (facets.length > 0) {
          params.facets = facets.join(',');
        }
    
        const response = await this.axiosInstance.get("/shodan/host/search", { params });
        return this.sampleResponse(response.data, maxItems, selectedFields);
      } catch (error: unknown) {
        if (axios.isAxiosError(error)) {
          if (error.response?.status === 401) {
            return {
              error: "Unauthorized: The Shodan search API requires a paid membership. Your API key does not have access to this endpoint.",
              message: "The search functionality requires a Shodan membership subscription with API access. Please upgrade your Shodan plan to use this feature.",
              status: 401
            };
          }
          throw new McpError(
            ErrorCode.InternalError,
            `Shodan API error: ${error.response?.data?.error || error.message}`
          );
        }
        throw error;
      }
    }
  • MCP server tool handler for 'search_shodan': validates input parameters, calls ShodanClient.search, optionally summarizes results, formats response as MCP content.
    case "search_shodan": {
      const query = String(request.params.arguments?.query);
      if (!query) {
        throw new McpError(
          ErrorCode.InvalidParams,
          "Search query is required"
        );
      }
    
      const page = Number(request.params.arguments?.page) || 1;
      const facets = Array.isArray(request.params.arguments?.facets)
        ? request.params.arguments?.facets.map(String)
        : [];
      const maxItems = Number(request.params.arguments?.max_items) || 5;
      const fields = Array.isArray(request.params.arguments?.fields)
        ? request.params.arguments?.fields.map(String)
        : undefined;
      const summarize = Boolean(request.params.arguments?.summarize);
    
      try {
        const searchResults = await shodanClient.search(query, page, facets, maxItems, fields);
    
        // Check if we got an error response from the search method
        if (searchResults.error && searchResults.status === 401) {
          return {
            content: [{
              type: "text",
              text: JSON.stringify(searchResults, null, 2)
            }]
          };
        }
    
        if (summarize) {
          const summary = shodanClient.summarizeResults(searchResults);
          return {
            content: [{
              type: "text",
              text: JSON.stringify(summary, null, 2)
            }]
          };
        }
    
        return {
          content: [{
            type: "text",
            text: JSON.stringify(searchResults, null, 2)
          }]
        };
      } catch (error) {
        if (error instanceof McpError) {
          throw error;
        }
        throw new McpError(
          ErrorCode.InternalError,
          `Error searching Shodan: ${(error as Error).message}`
        );
      }
    }
  • Input schema definition for the search_shodan tool, specifying parameters like query, page, facets, max_items, fields, and summarize option.
    type: "object",
    properties: {
      query: {
        type: "string",
        description: "Shodan search query (e.g., 'apache country:US')"
      },
      page: {
        type: "number",
        description: "Page number for results pagination (default: 1)"
      },
      facets: {
        type: "array",
        items: {
          type: "string"
        },
        description: "List of facets to include in the search results (e.g., ['country', 'org'])"
      },
      max_items: {
        type: "number",
        description: "Maximum number of items to include in arrays (default: 5)"
      },
      fields: {
        type: "array",
        items: {
          type: "string"
        },
        description: "List of fields to include in the results (e.g., ['ip_str', 'ports', 'location.country_name'])"
      },
      summarize: {
        type: "boolean",
        description: "Whether to return a summary of the results instead of the full data (default: false)"
      }
    },
    required: ["query"]
  • src/index.ts:904-941 (registration)
    Tool registration in ListTools response: defines name, description, and input schema for search_shodan.
    name: "search_shodan",
    description: "Search Shodan's database for devices and services",
    inputSchema: {
      type: "object",
      properties: {
        query: {
          type: "string",
          description: "Shodan search query (e.g., 'apache country:US')"
        },
        page: {
          type: "number",
          description: "Page number for results pagination (default: 1)"
        },
        facets: {
          type: "array",
          items: {
            type: "string"
          },
          description: "List of facets to include in the search results (e.g., ['country', 'org'])"
        },
        max_items: {
          type: "number",
          description: "Maximum number of items to include in arrays (default: 5)"
        },
        fields: {
          type: "array",
          items: {
            type: "string"
          },
          description: "List of fields to include in the results (e.g., ['ip_str', 'ports', 'location.country_name'])"
        },
        summarize: {
          type: "boolean",
          description: "Whether to return a summary of the results instead of the full data (default: false)"
        }
      },
      required: ["query"]
    }
  • Helper method used by search to sample/truncate large arrays in responses (matches, data, ports) and filter to specific fields, reducing token usage.
    private sampleResponse(data: any, maxItems: number = 5, selectedFields?: string[]): any {
      if (!data) return data;
    
      // Clone the data to avoid modifying the original
      const result = JSON.parse(JSON.stringify(data));
    
      // Sample matches array if it exists and is longer than maxItems
      if (result.matches && Array.isArray(result.matches) && result.matches.length > maxItems) {
        result.matches = result.matches.slice(0, maxItems);
        result._sample_note = `Response truncated to ${maxItems} matches. Original count: ${data.matches.length}`;
      }
    
      // Sample data array if it exists and is longer than maxItems
      if (result.data && Array.isArray(result.data) && result.data.length > maxItems) {
        result.data = result.data.slice(0, maxItems);
        result._sample_note = `Response truncated to ${maxItems} data items. Original count: ${data.data.length}`;
      }
    
      // Sample ports array if it exists and is longer than maxItems
      if (result.ports && Array.isArray(result.ports) && result.ports.length > maxItems) {
        result.ports = result.ports.slice(0, maxItems);
        if (!result._sample_note) {
          result._sample_note = `Ports truncated to ${maxItems} items. Original count: ${data.ports.length}`;
        }
      }
    
      // Filter fields if selectedFields is provided
      if (selectedFields && selectedFields.length > 0 && typeof result === 'object') {
        this.filterFields(result, selectedFields);
      }
    
      return result;
    }

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/Cyreslab-AI/shodan-mcp-server'

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