name: "zoomeye_search"
command: "python3"
args:
  - "-c"
  - |
    import sys
    import json
    import base64
    import requests
    import os

    # ==================== ZoomEye配置 ====================
    # 请在此处配置您的ZoomEye账号信息
    # 您也可以在环境变量中设置：ZOOMEYE_API_KEY
    # enable 默认为 false，需开启才能调用该MCP
    ZOOMEYE_API_KEY = ""  # 请填写您的ZoomEye API密钥
    # ==================================================

    # ZoomEye API基础URL
    base_url = "https://api.zoomeye.org/v2/search"

    # 解析参数（从JSON字符串或命令行参数）
    def parse_args():
        # 尝试从第一个参数读取JSON配置
        if len(sys.argv) > 1:
            try:
                # 确保 sys.argv[1] 是字符串
                arg1 = str(sys.argv[1])
                # 尝试解析为JSON
                config = json.loads(arg1)
                # 确保返回的是字典类型
                if isinstance(config, dict):
                    return config
            except (json.JSONDecodeError, TypeError, ValueError):
                # 如果不是JSON，使用传统的位置参数方式
                pass

        # 传统位置参数方式（向后兼容）
        # 参数位置：query=1, page=2, pagesize=3, fields=4, sub_type=5
        config = {}
        if len(sys.argv) > 1:
            config['query'] = str(sys.argv[1])
        if len(sys.argv) > 2:
            try:
                config['page'] = int(sys.argv[2])
            except (ValueError, TypeError):
                pass
        if len(sys.argv) > 3:
            try:
                config['pagesize'] = int(sys.argv[3])
            except (ValueError, TypeError):
                pass
        if len(sys.argv) > 4:
            config['fields'] = str(sys.argv[4])
        if len(sys.argv) > 5:
            config['sub_type'] = str(sys.argv[5])
        return config

    try:
        config = parse_args()

        # 确保 config 是字典类型
        if not isinstance(config, dict):
            error_result = {
                "status": "error",
                "message": f"参数解析错误: 期望字典类型，但得到 {type(config).__name__}",
                "type": "TypeError"
            }
            print(json.dumps(error_result, ensure_ascii=False, indent=2))
            sys.exit(1)

        # 从配置或环境变量获取api_key
        api_key = os.getenv('ZOOMEYE_API_KEY', ZOOMEYE_API_KEY).strip()
        query = config.get('query', '').strip()

        if not api_key:
            error_result = {
                "status": "error",
                "message": "缺少ZoomEye配置: api_key（ZoomEye API密钥）",
                "required_config": ["api_key"],
                "note": "请在YAML文件的ZOOMEYE_API_KEY配置项中填写您的API密钥，或在环境变量ZOOMEYE_API_KEY中设置。API密钥可在ZoomEye个人中心获取: https://www.zoomeye.org/user"
            }
            print(json.dumps(error_result, ensure_ascii=False, indent=2))
            sys.exit(1)

        if not query:
            error_result = {
                "status": "error",
                "message": "缺少必需参数: query（搜索查询语句）",
                "required_params": ["query"],
                "examples": [
                    'app:"Apache"',
                    'title:"登录"',
                    'domain:"example.com"',
                    'ip:"1.1.1.1"',
                    'port:80',
                    'country:"CN"',
                    'city:"Beijing"'
                ]
            }
            print(json.dumps(error_result, ensure_ascii=False, indent=2))
            sys.exit(1)

        # 构建请求参数（ZoomEye API 使用 POST 请求，参数在请求体中）
        # ZoomEye API 使用 qbase64 参数（base64编码的查询）
        data = {
            'qbase64': base64.b64encode(query.encode('utf-8')).decode('utf-8')
        }

        # 可选参数
        if 'page' in config and config['page'] is not None:
            try:
                page = int(config['page'])
                if page > 0:
                    data['page'] = page
            except (ValueError, TypeError):
                pass

        if 'pagesize' in config and config['pagesize'] is not None:
            try:
                pagesize = int(config['pagesize'])
                if pagesize > 0 and pagesize <= 10000:
                    data['pagesize'] = pagesize
            except (ValueError, TypeError):
                pass

        if 'fields' in config and config['fields']:
            data['fields'] = str(config['fields']).strip()

        if 'sub_type' in config and config['sub_type']:
            sub_type = str(config['sub_type']).strip().lower()
            if sub_type in ('v4', 'v6', 'web'):
                data['sub_type'] = sub_type

        if 'facets' in config and config['facets']:
            data['facets'] = str(config['facets']).strip()

        if 'ignore_cache' in config and config['ignore_cache'] is not None:
            ignore_cache_val = config['ignore_cache']
            if isinstance(ignore_cache_val, bool):
                data['ignore_cache'] = ignore_cache_val
            elif isinstance(ignore_cache_val, str):
                data['ignore_cache'] = ignore_cache_val.lower() in ('true', '1', 'yes')
            elif isinstance(ignore_cache_val, (int, float)):
                data['ignore_cache'] = ignore_cache_val != 0

        # 设置请求头（ZoomEye API 使用 API Key 认证）
        headers = {
            'API-KEY': api_key,
            'Content-Type': 'application/json'
        }

        # 发送请求（ZoomEye API 使用 POST 方法）
        try:
            response = requests.post(base_url, json=data, headers=headers, timeout=30)
            response.raise_for_status()

            result_data = response.json()

            # 检查ZoomEye API返回的错误
            # ZoomEye API 成功时 code 为 60000
            if result_data.get('code') != 60000:
                error_code = result_data.get('code', 'unknown')
                error_msg = result_data.get('message', '未知错误')
                error_result = {
                    "status": "error",
                    "message": f"ZoomEye API错误: {error_msg}",
                    "error_code": error_code,
                    "suggestion": "请检查API密钥是否正确，或查询语句是否符合ZoomEye语法"
                }
                print(json.dumps(error_result, ensure_ascii=False, indent=2))
                sys.exit(1)

            # 格式化输出结果
            # ZoomEye API 返回的数据在 data 字段中
            data_list = result_data.get('data', [])
            output = {
                "status": "success",
                "query": result_data.get('query', query),
                "total": result_data.get('total', 0),
                "page": result_data.get('page', 1),
                "pagesize": result_data.get('pagesize', 10),
                "results_count": len(data_list),
                "results": data_list,
                "message": f"成功获取 {len(data_list)} 条结果"
            }

            print(json.dumps(output, ensure_ascii=False, indent=2))

        except requests.exceptions.RequestException as e:
            error_result = {
                "status": "error",
                "message": f"请求失败: {str(e)}",
                "suggestion": "请检查网络连接或ZoomEye API服务状态"
            }
            print(json.dumps(error_result, ensure_ascii=False, indent=2))
            sys.exit(1)

    except Exception as e:
        error_result = {
            "status": "error",
            "message": f"执行出错: {str(e)}",
            "type": type(e).__name__
        }
        print(json.dumps(error_result, ensure_ascii=False, indent=2))
        sys.exit(1)
enabled: false
short_description: "ZoomEye网络空间搜索引擎，支持灵活的查询参数配置"
description: |
  ZoomEye（钟馗之眼）是一个网络空间测绘搜索引擎，可以通过多种查询条件搜索互联网资产。

  **主要功能：**
  - 支持多种查询语法（app、title、domain、ip、port、country、city等）
  - 灵活的字段返回配置
  - 分页查询支持（最多10000条/页）
  - 支持IPv4、IPv6和Web资产搜索
  - 支持统计项（facets）查询
  - 丰富的资产信息返回

  **使用场景：**
  - 资产发现和枚举
  - 漏洞影响范围评估
  - 安全态势感知
  - 威胁情报收集
  - Bug bounty信息收集

  **查询语法说明：**

  **搜索规则：**
  - 搜索范围包括设备（IPv4、IPv6）和网站（域名）
  - 搜索字符串不区分大小写，使用引号包裹（如 `"Cisco System"`）
  - 使用 `==` 进行精确匹配（区分大小写）

  **逻辑运算符：**
  - `=` - 搜索包含关键词的资产，如 `title="knownsec"`
  - `==` - 精确匹配（区分大小写），如 `title=="knownsec"`
  - `||` - 或运算，如 `service="ssh" || service="http"`
  - `&&` - 与运算，如 `device="router" && after="2020-01-01"`
  - `!=` - 非运算，如 `country="US" && subdivisions!="new york"`
  - `()` - 优先级处理，如 `(country="US" && port!=80) || (country="US" && title!="404 Not Found")`
  - `*` - 模糊搜索，如 `title="*google"`

  **语法关键字：**

  **地理位置：** `country="CN"`、`subdivisions="beijing"`、`city="changsha"`

  **IP/域名：** `ip="8.8.8.8"`、`cidr="52.2.254.36/24"`、`domain="baidu.com"`、`hostname="google.com"`、`port=80`、`asn=42893`、`org="Stanford University"`、`isp="China Mobile"`

  **指纹识别：** `app="Cisco ASA SSL VPN"`、`service="ssh"`、`device="router"`、`os="RouterOS"`、`title="Cisco"`、`product="Cisco"`、`protocol="TCP"`、`industry="government"`、`is_honeypot="True"`

  **证书搜索：** `ssl="google"`、`ssl.cert.fingerprint="..."`、`ssl.jarm="..."`、`ssl.ja3s=...`、`ssl.version="TLSv1.3"`、`ssl.cert.subject.cn="example.com"`

  **HTTP相关：** `banner="FTP"`、`http.header="http"`、`http.header_hash="..."`、`http.header.server="Nginx"`、`http.header.status_code="200"`、`http.body="document"`、`http.body_hash="..."`

  **时间搜索：** `after="2020-01-01"`、`before="2020-01-01"`（需与其他条件组合使用）

  **其他：** `dig="baidu.com 220.181.38.148"`、`iconhash="f3418a443e7d841097c714d69ec4bcb8"`、`filehash="0b5ce08db7fb8fffe4e14d05588d49d9"`、`is_ipv4=true`、`is_ipv6=true`、`is_domain=true`

  **查询示例：**
  - `port=80 && service="http"` - 搜索80端口运行HTTP服务的网络设备
  - `city=nagoya && port=443 && service=ssl` - 搜索名古屋443端口运行SSL的设备
  - `country=us && os=windows` - 搜索美国运行Windows系统的设备
  - `app="Microsoft NTP"` - 搜索运行Microsoft NTP应用的设备
  - `city=tokyo && device=webcam` - 搜索东京的摄像头
  - `after="2020-01-01" && port=50050` - 搜索2020-01-01之后索引的50050端口资产
  - `os=linux && port=22 && country=PL` - 搜索波兰Linux系统22端口资产
  - `service=ftp && hostname=example` - 搜索主机名为example的FTP服务
  - `http.body="Knownsec"` - 搜索body中包含"Knownsec"的资产

  **注意事项：**
  - API调用有频率限制，请合理使用
  - 查询结果数量受账户权限限制
  - 需要有效的ZoomEye API密钥
parameters:
  - name: "query"
    type: "string"
    description: |
      ZoomEye查询语句（必需）

      搜索查询语句，支持ZoomEye查询语法。使用引号包裹字符串，使用逻辑运算符连接条件。
      示例：
      - app="Apache"
      - title="登录"
      - domain="example.com"
      - ip="1.1.1.1"
      - port=80
      - country="CN"
      - app="Apache" && country="CN"
      - service="ssh" || service="http"
      - port=80 && service="http"
    required: true
    position: 1
    format: "positional"
  - name: "page"
    type: "int"
    description: |
      页码（可选）

      要返回的页码，从1开始，默认1。
      用于分页查询大量结果。
    required: false
    position: 2
    format: "positional"
    default: 1
  - name: "pagesize"
    type: "int"
    description: |
      每页结果数量（可选）

      每页返回的结果数量，默认10。
      取值范围：1-10000，受账户权限限制。
    required: false
    position: 3
    format: "positional"
    default: 5
  - name: "fields"
    type: "string"
    description: |
      返回字段列表（可选）

      需要返回的字段，多个字段用逗号分隔。
      默认字段：ip,port,domain,update_time
      可用字段：ip,port,domain,url,hostname,os,service,title,version,device,rdns,product,header,body,banner,update_time,country,province,city,isp,organization,asn,protocol,ssl,ssl.jarm,ssl.ja3s等
    required: false
    position: 4
    format: "positional"
    default: "ip,port,domain,update_time"
  - name: "sub_type"
    type: "string"
    description: |
      数据类型（可选）

      指定搜索的数据类型。
      可选值：v4（IPv4设备，默认）、v6（IPv6设备）、web（网站资产）
    required: false
    position: 5
    format: "positional"
    default: "v4"
  - name: "facets"
    type: "string"
    description: |
      统计项（可选）

      需要统计的字段，多个字段用逗号分隔。
      支持：country、subdivisions、city、product、service、device、os、port
    required: false
    position: 6
    format: "positional"
  - name: "ignore_cache"
    type: "bool"
    description: |
      是否忽略缓存（可选）

      设置为true时忽略缓存，获取最新数据。
      默认false，支持商业版及以上用户。
    required: false
    position: 7
    format: "positional"
    default: false
