Skill: marcosnahuel/mercadolibre-mcp (version 1.0.0)

--- name: mercadolibre-mcp description: Complete MCP server for Mercado Libre seller operations — products, orders, pricing, stock, questions, ads, reputation, competitor analysis version: 1.0.0 metadata: openclaw: requires: env: - ML_CLIENT_ID - ML_CLIENT_SECRET - ML_REFRESH_TOKEN bins: - node primaryEnv: ML_REFRESH_TOKEN emoji: "🛒" homepage: https://github.com/MarcosNahuel/mercadolibre-mcp --- # Mercado Libre MCP Server The first complete MCP server for Mercado Libre. 11 tools for seller operations — manage products, orders, pricing, stock, customer questions, advertising, reputation, and competitor intelligence. ## Setup Two authentication modes: ### Option A: Direct token (recommended if you have n8n/cron managing refresh) ``` ML_ACCESS_TOKEN=APP_USR-... ML_SITE_ID=MLA ``` ### Option B: Auto-refresh (standalone, no external dependencies) 1. Create an app at [developers.mercadolibre.com](https://developers.mercadolibre.com) 2. Authorize via OAuth to get your refresh token 3. Set environment variables: ``` ML_CLIENT_ID=your_client_id ML_CLIENT_SECRET=your_client_secret ML_REFRESH_TOKEN=your_refresh_token ML_SITE_ID=MLA ``` ## Tools ### Read operations - **list_products** — List all products/listings for a seller with price, stock, status, and thumbnails - **get_orders** — Get orders/sales with buyer info, items, shipping, and payment details - **list_questions** — List questions received on listings, filterable by status (unanswered/answered) - **get_item_metrics** — Get visits, health score, conversion rate, and catalog status for a listing - **get_reputation** — Get seller reputation: level, completed sales, claims, cancellations, handling time - **search_competitors** — Search competitor products by keyword with price, seller, sales volume - **get_categories** — Browse ML categories and required attributes for publishing ### Write operations - **update_price** — Update listing price (returns before/after for confirmation) - **update_stock** — Update available quantity (returns before/after for confirmation) - **answer_question** — Answer a buyer question (public, visible to all buyers) - **manage_ads** — Activate, pause, or check status of Product Ads ## Supported countries Works with all Mercado Libre sites: MLA (Argentina), MLU (Uruguay), MLB (Brazil), MLC (Chile), MLM (Mexico), MCO (Colombia), and more. ## Features - **OAuth2 auto-refresh** — Token renews automatically every 6 hours - **Rate limiting** — Automatic retry with backoff when hitting API limits - **Multi-get batching** — Fetches multiple items in batches of 20 - **Zod validation** — All inputs validated before calling the API - **Clear error messages** — Human-readable errors in Spanish, not raw JSON ## Example prompts - "List my active products" - "Show me today's orders" - "Update the price of MLA123456 to $5000" - "What unanswered questions do I have?" - "Search competitors for brake pads Toyota" - "What category should I use for motorcycle parts?" - "Activate ads on MLA123456 with $500/day budget" - "How is my seller reputation?" ## License MIT — by [TRAID](https://traid.ai)

path: package-lock.json
content: {
  "name": "@traid/mercadolibre-mcp",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "@traid/mercadolibre-mcp",
      "version": "1.0.0",
      "license": "MIT",
      "dependencies": {
        "@modelcontextprotocol/sdk": "^1.27.1",
        "zod": "^3.24.0"
      },
      "bin": {
        "mercadolibre-mcp": "dist/index.js"
      },
      "devDependencies": {
        "tsx": "^4.19.0",
        "typescript": "^5.7.0"
      },
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/aix-ppc64": {
      "version": "0.27.4",
      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "ppc64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "aix"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/android-arm": {
      "version": "0.27.4",
      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "android"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/android-arm64": {
      "version": "0.27.4",
      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "android"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/android-x64": {
      "version": "0.27.4",
      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "android"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/darwin-arm64": {
      "version": "0.27.4",
      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "darwin"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/darwin-x64": {
      "version": "0.27.4",
      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "darwin"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/freebsd-arm64": {
      "version": "0.27.4",
      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "freebsd"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/freebsd-x64": {
      "version": "0.27.4",
      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "freebsd"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-arm": {
      "version": "0.27.4",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-arm64": {
      "version": "0.27.4",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-ia32": {
      "version": "0.27.4",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "ia32"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-loong64": {
      "version": "0.27.4",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "loong64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-mips64el": {
      "version": "0.27.4",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "mips64el"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-ppc64": {
      "version": "0.27.4",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "ppc64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-riscv64": {
      "version": "0.27.4",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "riscv64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-s390x": {
      "version": "0.27.4",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "s390x"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-x64": {
      "version": "0.27.4",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/netbsd-arm64": {
      "version": "0.27.4",
      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "netbsd"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/netbsd-x64": {
      "version": "0.27.4",
      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "netbsd"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/openbsd-arm64": {
      "version": "0.27.4",
      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "openbsd"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/openbsd-x64": {
      "version": "0.27.4",
      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "openbsd"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/openharmony-arm64": {
      "version": "0.27.4",
      "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "openharmony"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/sunos-x64": {
      "version": "0.27.4",
      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "sunos"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/win32-arm64": {
      "version": "0.27.4",
      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "win32"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/win32-ia32": {
      "version": "0.27.4",
      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "ia32"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "win32"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/win32-x64": {
      "version": "0.27.4",
      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "win32"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@hono/node-server": {
      "version": "1.19.11",
      "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=18.14.1"
      },
      "peerDependencies": {
        "hono": "^4"
      }
    },
    "node_modules/@modelcontextprotocol/sdk": {
      "version": "1.27.1",
      "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "@hono/node-server": "^1.19.9",
        "ajv": "^8.17.1",
        "ajv-formats": "^3.0.1",
        "content-type": "^1.0.5",
        "cors": "^2.8.5",
        "cross-spawn": "^7.0.5",
        "eventsource": "^3.0.2",
        "eventsource-parser": "^3.0.0",
        "express": "^5.2.1",
        "express-rate-limit": "^8.2.1",
        "hono": "^4.11.4",
        "jose": "^6.1.3",
        "json-schema-typed": "^8.0.2",
        "pkce-challenge": "^5.0.0",
        "raw-body": "^3.0.0",
        "zod": "^3.25 || ^4.0",
        "zod-to-json-schema": "^3.25.1"
      },
      "engines": {
        "node": ">=18"
      },
      "peerDependencies": {
        "@cfworker/json-schema": "^4.1.1",
        "zod": "^3.25 || ^4.0"
      },
      "peerDependenciesMeta": {
        "@cfworker/json-schema": {
          "optional": true
        },
        "zod": {
          "optional": false
        }
      }
    },
    "node_modules/accepts": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "mime-types": "^3.0.0",
        "negotiator": "^1.0.0"
      },
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/ajv": {
      "version": "8.18.0",
      "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "fast-deep-equal": "^3.1.3",
        "fast-uri": "^3.0.1",
        "json-schema-traverse": "^1.0.0",
        "require-from-string": "^2.0.2"
      },
      "funding": {
        "type": "github",
        "url": "https://github.com/sponsors/epoberezkin"
      }
    },
    "node_modules/ajv-formats": {
      "version": "3.0.1",
      "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "ajv": "^8.0.0"
      },
      "peerDependencies": {
        "ajv": "^8.0.0"
      },
      "peerDependenciesMeta": {
        "ajv": {
          "optional": true
        }
      }
    },
    "node_modules/body-parser": {
      "version": "2.2.2",
      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "bytes": "^3.1.2",
        "content-type": "^1.0.5",
        "debug": "^4.4.3",
        "http-errors": "^2.0.0",
        "iconv-lite": "^0.7.0",
        "on-finished": "^2.4.1",
        "qs": "^6.14.1",
        "raw-body": "^3.0.1",
        "type-is": "^2.0.1"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/bytes": {
      "version": "3.1.2",
      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/call-bind-apply-helpers": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "es-errors": "^1.3.0",
        "function-bind": "^1.1.2"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/call-bound": {
      "version": "1.0.4",
      "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "call-bind-apply-helpers": "^1.0.2",
        "get-intrinsic": "^1.3.0"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/content-disposition": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/content-type": {
      "version": "1.0.5",
      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/cookie": {
      "version": "0.7.2",
      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/cookie-signature": {
      "version": "1.2.2",
      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=6.6.0"
      }
    },
    "node_modules/cors": {
      "version": "2.8.6",
      "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "object-assign": "^4",
        "vary": "^1"
      },
      "engines": {
        "node": ">= 0.10"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/cross-spawn": {
      "version": "7.0.6",
      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "path-key": "^3.1.0",
        "shebang-command": "^2.0.0",
        "which": "^2.0.1"
      },
      "engines": {
        "node": ">= 8"
      }
    },
    "node_modules/debug": {
      "version": "4.4.3",
      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "ms": "^2.1.3"
      },
      "engines": {
        "node": ">=6.0"
      },
      "peerDependenciesMeta": {
        "supports-color": {
          "optional": true
        }
      }
    },
    "node_modules/depd": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/dunder-proto": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "call-bind-apply-helpers": "^1.0.1",
        "es-errors": "^1.3.0",
        "gopd": "^1.2.0"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/ee-first": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/encodeurl": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/es-define-property": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/es-errors": {
      "version": "1.3.0",
      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/es-object-atoms": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "es-errors": "^1.3.0"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/esbuild": {
      "version": "0.27.4",
      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "hasInstallScript": true,
      "license": "MIT",
      "bin": {
        "esbuild": "bin/esbuild"
      },
      "engines": {
        "node": ">=18"
      },
      "optionalDependencies": {
        "@esbuild/aix-ppc64": "0.27.4",
        "@esbuild/android-arm": "0.27.4",
        "@esbuild/android-arm64": "0.27.4",
        "@esbuild/android-x64": "0.27.4",
        "@esbuild/darwin-arm64": "0.27.4",
        "@esbuild/darwin-x64": "0.27.4",
        "@esbuild/freebsd-arm64": "0.27.4",
        "@esbuild/freebsd-x64": "0.27.4",
        "@esbuild/linux-arm": "0.27.4",
        "@esbuild/linux-arm64": "0.27.4",
        "@esbuild/linux-ia32": "0.27.4",
        "@esbuild/linux-loong64": "0.27.4",
        "@esbuild/linux-mips64el": "0.27.4",
        "@esbuild/linux-ppc64": "0.27.4",
        "@esbuild/linux-riscv64": "0.27.4",
        "@esbuild/linux-s390x": "0.27.4",
        "@esbuild/linux-x64": "0.27.4",
        "@esbuild/netbsd-arm64": "0.27.4",
        "@esbuild/netbsd-x64": "0.27.4",
        "@esbuild/openbsd-arm64": "0.27.4",
        "@esbuild/openbsd-x64": "0.27.4",
        "@esbuild/openharmony-arm64": "0.27.4",
        "@esbuild/sunos-x64": "0.27.4",
        "@esbuild/win32-arm64": "0.27.4",
        "@esbuild/win32-ia32": "0.27.4",
        "@esbuild/win32-x64": "0.27.4"
      }
    },
    "node_modules/escape-html": {
      "version": "1.0.3",
      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/etag": {
      "version": "1.8.1",
      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/eventsource": {
      "version": "3.0.7",
      "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "eventsource-parser": "^3.0.1"
      },
      "engines": {
        "node": ">=18.0.0"
      }
    },
    "node_modules/eventsource-parser": {
      "version": "3.0.6",
      "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=18.0.0"
      }
    },
    "node_modules/express": {
      "version": "5.2.1",
      "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "accepts": "^2.0.0",
        "body-parser": "^2.2.1",
        "content-disposition": "^1.0.0",
        "content-type": "^1.0.5",
        "cookie": "^0.7.1",
        "cookie-signature": "^1.2.1",
        "debug": "^4.4.0",
        "depd": "^2.0.0",
        "encodeurl": "^2.0.0",
        "escape-html": "^1.0.3",
        "etag": "^1.8.1",
        "finalhandler": "^2.1.0",
        "fresh": "^2.0.0",
        "http-errors": "^2.0.0",
        "merge-descriptors": "^2.0.0",
        "mime-types": "^3.0.0",
        "on-finished": "^2.4.1",
        "once": "^1.4.0",
        "parseurl": "^1.3.3",
        "proxy-addr": "^2.0.7",
        "qs": "^6.14.0",
        "range-parser": "^1.2.1",
        "router": "^2.2.0",
        "send": "^1.1.0",
        "serve-static": "^2.2.0",
        "statuses": "^2.0.1",
        "type-is": "^2.0.1",
        "vary": "^1.1.2"
      },
      "engines": {
        "node": ">= 18"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/express-rate-limit": {
      "version": "8.3.1",
      "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "ip-address": "10.1.0"
      },
      "engines": {
        "node": ">= 16"
      },
      "funding": {
        "url": "https://github.com/sponsors/express-rate-limit"
      },
      "peerDependencies": {
        "express": ">= 4.11"
      }
    },
    "node_modules/fast-deep-equal": {
      "version": "3.1.3",
      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/fast-uri": {
      "version": "3.1.0",
      "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/fastify"
        },
        {
          "type": "opencollective",
          "url": "https://opencollective.com/fastify"
        }
      ],
      "license": "BSD-3-Clause"
    },
    "node_modules/finalhandler": {
      "version": "2.1.1",
      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "debug": "^4.4.0",
        "encodeurl": "^2.0.0",
        "escape-html": "^1.0.3",
        "on-finished": "^2.4.1",
        "parseurl": "^1.3.3",
        "statuses": "^2.0.1"
      },
      "engines": {
        "node": ">= 18.0.0"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/forwarded": {
      "version": "0.2.0",
      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/fresh": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/fsevents": {
      "version": "2.3.3",
      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "hasInstallScript": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "darwin"
      ],
      "engines": {
        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
      }
    },
    "node_modules/function-bind": {
      "version": "1.1.2",
      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/get-intrinsic": {
      "version": "1.3.0",
      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "call-bind-apply-helpers": "^1.0.2",
        "es-define-property": "^1.0.1",
        "es-errors": "^1.3.0",
        "es-object-atoms": "^1.1.1",
        "function-bind": "^1.1.2",
        "get-proto": "^1.0.1",
        "gopd": "^1.2.0",
        "has-symbols": "^1.1.0",
        "hasown": "^2.0.2",
        "math-intrinsics": "^1.1.0"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/get-proto": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "dunder-proto": "^1.0.1",
        "es-object-atoms": "^1.0.0"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/get-tsconfig": {
      "version": "4.13.7",
      "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "resolve-pkg-maps": "^1.0.0"
      },
      "funding": {
        "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
      }
    },
    "node_modules/gopd": {
      "version": "1.2.0",
      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/has-symbols": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/hasown": {
      "version": "2.0.2",
      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "function-bind": "^1.1.2"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/hono": {
      "version": "4.12.9",
      "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.9.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=16.9.0"
      }
    },
    "node_modules/http-errors": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "depd": "~2.0.0",
        "inherits": "~2.0.4",
        "setprototypeof": "~1.2.0",
        "statuses": "~2.0.2",
        "toidentifier": "~1.0.1"
      },
      "engines": {
        "node": ">= 0.8"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/iconv-lite": {
      "version": "0.7.2",
      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "safer-buffer": ">= 2.1.2 < 3.0.0"
      },
      "engines": {
        "node": ">=0.10.0"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/inherits": {
      "version": "2.0.4",
      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC"
    },
    "node_modules/ip-address": {
      "version": "10.1.0",
      "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 12"
      }
    },
    "node_modules/ipaddr.js": {
      "version": "1.9.1",
      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.10"
      }
    },
    "node_modules/is-promise": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/isexe": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC"
    },
    "node_modules/jose": {
      "version": "6.2.2",
      "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "funding": {
        "url": "https://github.com/sponsors/panva"
      }
    },
    "node_modules/json-schema-traverse": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/json-schema-typed": {
      "version": "8.0.2",
      "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BSD-2-Clause"
    },
    "node_modules/math-intrinsics": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/media-typer": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/merge-descriptors": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/mime-db": {
      "version": "1.54.0",
      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/mime-types": {
      "version": "3.0.2",
      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "mime-db": "^1.54.0"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/ms": {
      "version": "2.1.3",
      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/negotiator": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/object-assign": {
      "version": "4.1.1",
      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/object-inspect": {
      "version": "1.13.4",
      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/on-finished": {
      "version": "2.4.1",
      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "ee-first": "1.1.1"
      },
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/once": {
      "version": "1.4.0",
      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC",
      "dependencies": {
        "wrappy": "1"
      }
    },
    "node_modules/parseurl": {
      "version": "1.3.3",
      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/path-key": {
      "version": "3.1.1",
      "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/path-to-regexp": {
      "version": "8.3.0",
      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/pkce-challenge": {
      "version": "5.0.1",
      "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=16.20.0"
      }
    },
    "node_modules/proxy-addr": {
      "version": "2.0.7",
      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "forwarded": "0.2.0",
        "ipaddr.js": "1.9.1"
      },
      "engines": {
        "node": ">= 0.10"
      }
    },
    "node_modules/qs": {
      "version": "6.15.0",
      "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BSD-3-Clause",
      "dependencies": {
        "side-channel": "^1.1.0"
      },
      "engines": {
        "node": ">=0.6"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/range-parser": {
      "version": "1.2.1",
      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/raw-body": {
      "version": "3.0.2",
      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "bytes": "~3.1.2",
        "http-errors": "~2.0.1",
        "iconv-lite": "~0.7.0",
        "unpipe": "~1.0.0"
      },
      "engines": {
        "node": ">= 0.10"
      }
    },
    "node_modules/require-from-string": {
      "version": "2.0.2",
      "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/resolve-pkg-maps": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT",
      "funding": {
        "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
      }
    },
    "node_modules/router": {
      "version": "2.2.0",
      "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "debug": "^4.4.0",
        "depd": "^2.0.0",
        "is-promise": "^4.0.0",
        "parseurl": "^1.3.3",
        "path-to-regexp": "^8.0.0"
      },
      "engines": {
        "node": ">= 18"
      }
    },
    "node_modules/safer-buffer": {
      "version": "2.1.2",
      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/send": {
      "version": "1.2.1",
      "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "debug": "^4.4.3",
        "encodeurl": "^2.0.0",
        "escape-html": "^1.0.3",
        "etag": "^1.8.1",
        "fresh": "^2.0.0",
        "http-errors": "^2.0.1",
        "mime-types": "^3.0.2",
        "ms": "^2.1.3",
        "on-finished": "^2.4.1",
        "range-parser": "^1.2.1",
        "statuses": "^2.0.2"
      },
      "engines": {
        "node": ">= 18"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/serve-static": {
      "version": "2.2.1",
      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "encodeurl": "^2.0.0",
        "escape-html": "^1.0.3",
        "parseurl": "^1.3.3",
        "send": "^1.2.0"
      },
      "engines": {
        "node": ">= 18"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/setprototypeof": {
      "version": "1.2.0",
      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC"
    },
    "node_modules/shebang-command": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "shebang-regex": "^3.0.0"
      },
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/shebang-regex": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/side-channel": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "es-errors": "^1.3.0",
        "object-inspect": "^1.13.3",
        "side-channel-list": "^1.0.0",
        "side-channel-map": "^1.0.1",
        "side-channel-weakmap": "^1.0.2"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/side-channel-list": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "es-errors": "^1.3.0",
        "object-inspect": "^1.13.3"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/side-channel-map": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "call-bound": "^1.0.2",
        "es-errors": "^1.3.0",
        "get-intrinsic": "^1.2.5",
        "object-inspect": "^1.13.3"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/side-channel-weakmap": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "call-bound": "^1.0.2",
        "es-errors": "^1.3.0",
        "get-intrinsic": "^1.2.5",
        "object-inspect": "^1.13.3",
        "side-channel-map": "^1.0.1"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/statuses": {
      "version": "2.0.2",
      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/toidentifier": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=0.6"
      }
    },
    "node_modules/tsx": {
      "version": "4.21.0",
      "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "esbuild": "~0.27.0",
        "get-tsconfig": "^4.7.5"
      },
      "bin": {
        "tsx": "dist/cli.mjs"
      },
      "engines": {
        "node": ">=18.0.0"
      },
      "optionalDependencies": {
        "fsevents": "~2.3.3"
      }
    },
    "node_modules/type-is": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "content-type": "^1.0.5",
        "media-typer": "^1.1.0",
        "mime-types": "^3.0.0"
      },
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/typescript": {
      "version": "5.9.3",
      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "Apache-2.0",
      "bin": {
        "tsc": "bin/tsc",
        "tsserver": "bin/tsserver"
      },
      "engines": {
        "node": ">=14.17"
      }
    },
    "node_modules/unpipe": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/vary": {
      "version": "1.1.2",
      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/which": {
      "version": "2.0.2",
      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC",
      "dependencies": {
        "isexe": "^2.0.0"
      },
      "bin": {
        "node-which": "bin/node-which"
      },
      "engines": {
        "node": ">= 8"
      }
    },
    "node_modules/wrappy": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC"
    },
    "node_modules/zod": {
      "version": "3.25.76",
      "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "funding": {
        "url": "https://github.com/sponsors/colinhacks"
      }
    },
    "node_modules/zod-to-json-schema": {
      "version": "3.25.1",
      "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC",
      "peerDependencies": {
        "zod": "^3.25 || ^4"
      }
    }
  }
}


path: package.json
content: {
  "name": "@traid/mercadolibre-mcp",
  "version": "1.0.0",
  "description": "MCP server completo para Mercado Libre — 11 tools de operaciones de seller",
  "license": "MIT",
  "type": "module",
  "main": "dist/index.js",
  "bin": {
    "mercadolibre-mcp": "dist/index.js"
  },
  "scripts": {
    "build": "tsc",
    "dev": "tsx src/index.ts",
    "start": "node dist/index.js"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.27.1",
    "zod": "^3.24.0"
  },
  "devDependencies": {
    "tsx": "^4.19.0",
    "typescript": "^5.7.0"
  },
  "engines": {
    "node": ">=18"
  },
  "keywords": [
    "mcp",
    "mercadolibre",
    "mercado-libre",
    "ecommerce",
    "seller",
    "claude",
    "ai"
  ],
  "repository": {
    "type": "git",
    "url": "https://github.com/MarcosNahuel/mercadolibre-mcp"
  }
}


path: README.md
content: # @traid/mercadolibre-mcp

MCP server completo para Mercado Libre. 11 tools de operaciones de seller para usar desde Claude Code, Cursor, o cualquier cliente MCP.

## Tools disponibles

| Tool | Descripción | Tipo |
|------|-------------|------|
| `list_products` | Lista productos/publicaciones de un vendedor | Lectura |
| `get_orders` | Obtiene órdenes/ventas con detalle | Lectura |
| `update_price` | Actualiza precio de una publicación | Escritura |
| `update_stock` | Actualiza stock de una publicación | Escritura |
| `list_questions` | Lista preguntas recibidas | Lectura |
| `answer_question` | Responde una pregunta | Escritura |
| `get_item_metrics` | Métricas: visitas, conversión, salud | Lectura |
| `manage_ads` | Gestiona Product Ads (activar/pausar/status) | Escritura |
| `get_reputation` | Reputación del vendedor | Lectura |
| `search_competitors` | Busca productos de la competencia | Lectura |
| `get_categories` | Categorías y atributos para publicar | Lectura |

## Setup

Dos modos de autenticación:

### Opción A: Token directo (recomendado si tenés n8n/cron renovando el token)

```json
{
  "mcpServers": {
    "mercadolibre": {
      "command": "node",
      "args": ["path/to/mercadolibre-mcp/dist/index.js"],
      "env": {
        "ML_ACCESS_TOKEN": "APP_USR-...",
        "ML_SITE_ID": "MLA"
      }
    }
  }
}
```

### Opción B: Auto-refresh (standalone, sin dependencias externas)

1. Ir a [developers.mercadolibre.com](https://developers.mercadolibre.com)
2. Crear aplicación → obtener `CLIENT_ID` y `CLIENT_SECRET`
3. Autorizar vía OAuth → obtener `REFRESH_TOKEN`

```json
{
  "mcpServers": {
    "mercadolibre": {
      "command": "node",
      "args": ["path/to/mercadolibre-mcp/dist/index.js"],
      "env": {
        "ML_CLIENT_ID": "tu_client_id",
        "ML_CLIENT_SECRET": "tu_client_secret",
        "ML_REFRESH_TOKEN": "tu_refresh_token",
        "ML_SITE_ID": "MLA"
      }
    }
  }
}
```

### 3. Sites soportados

| Site ID | País |
|---------|------|
| MLA | Argentina |
| MLU | Uruguay |
| MLB | Brasil |
| MLC | Chile |
| MLM | México |
| MCO | Colombia |

## Uso

Una vez configurado, Claude Code puede ejecutar directamente:

- "Listame los productos activos"
- "Mostrá las órdenes de hoy"
- "Actualizá el precio de MLA123456 a $5000"
- "Qué preguntas sin responder tengo?"
- "Buscá competencia para repuestos de freno Toyota"

## Características

- **OAuth2 auto-refresh**: El token se renueva automáticamente cada 6h
- **Rate limiting**: Retry automático con backoff ante límites de la API
- **Multi-get batching**: Consultas de múltiples items en lotes de 20
- **Errores en español**: Mensajes de error claros, no JSON crudo
- **Sin cache**: Datos siempre en tiempo real
- **Validación Zod**: Cada parámetro validado antes de llamar a la API

## Desarrollo

```bash
cd mcp-servers/mercadolibre
npm install
npm run build    # Compilar TypeScript
npm run dev      # Desarrollo con tsx
```

## Licencia

MIT


path: smithery.json
content: {
  "qualifiedName": "@traid/mercadolibre-mcp",
  "displayName": "Mercado Libre MCP Server",
  "description": "Complete MCP server for Mercado Libre seller operations — 11 tools for products, orders, pricing, stock, questions, ads, reputation, and competitor analysis",
  "homepage": "https://github.com/MarcosNahuel/mercadolibre-mcp",
  "iconUrl": "https://http2.mlstatic.com/frontend-assets/ml-web-navigation/ui-navigation/6.6.73/mercadolibre/[REDACTED_SECRET]",
  "connections": [
    {
      "type": "stdio",
      "configSchema": {
        "type": "object",
        "properties": {
          "ML_CLIENT_ID": {
            "type": "string",
            "description": "App Client ID from developers.mercadolibre.com"
          },
          "ML_CLIENT_SECRET": {
            "type": "string",
            "description": "App Client Secret"
          },
          "ML_REFRESH_TOKEN": {
            "type": "string",
            "description": "OAuth refresh token for the seller account"
          },
          "ML_SITE_ID": {
            "type": "string",
            "description": "ML site ID (MLA, MLU, MLB, MLC, MLM, MCO)",
            "default": "MLA"
          }
        },
        "required": ["ML_CLIENT_ID", "ML_CLIENT_SECRET", "ML_REFRESH_TOKEN"]
      }
    }
  ],
  "tools": [
    { "name": "list_products", "description": "List seller products with price, stock, status" },
    { "name": "get_orders", "description": "Get orders with buyer, items, shipping, payments" },
    { "name": "update_price", "description": "Update listing price (returns before/after)" },
    { "name": "update_stock", "description": "Update available quantity (returns before/after)" },
    { "name": "list_questions", "description": "List buyer questions, filter by status" },
    { "name": "answer_question", "description": "Answer a buyer question (public)" },
    { "name": "get_item_metrics", "description": "Visits, conversion, health, catalog status" },
    { "name": "manage_ads", "description": "Activate, pause, or check Product Ads status" },
    { "name": "get_reputation", "description": "Seller reputation, level, metrics" },
    { "name": "search_competitors", "description": "Search competitor products by keyword" },
    { "name": "get_categories", "description": "Browse categories and required attributes" }
  ]
}


path: src/auth.ts
content: // OAuth2 para Mercado Libre — soporta 2 modos:
// 1. Token directo (ML_ACCESS_TOKEN) — refresheado externamente por n8n/cron
// 2. Auto-refresh (ML_CLIENT_ID + ML_CLIENT_SECRET + ML_REFRESH_TOKEN) — refresh interno cada 6h

import type { MLConfig, TokenData } from './types.js'

const ML_AUTH_URL = 'https://api.mercadolibre.com/oauth/token'

let cachedToken: TokenData | null = null

export function getConfig(): MLConfig {
  const clientId = process.env.ML_CLIENT_ID || ''
  const clientSecret = process.env.ML_CLIENT_SECRET || ''
  const refreshToken = process.env.ML_REFRESH_TOKEN || ''
  const siteId = process.env.ML_SITE_ID || 'MLA'

  return { clientId, clientSecret, refreshToken, siteId }
}

export async function getAccessToken(): Promise<string> {
  // Modo 1: Token directo (gestionado externamente por n8n, cron, etc.)
  const directToken = process.env.ML_ACCESS_TOKEN
  if (directToken) {
    return directToken
  }

  // Modo 2: Auto-refresh con OAuth2
  // Si el token está cacheado y no expiró (con 5 min de margen), reusar
  if (cachedToken && cachedToken.expiresAt > Date.now() + 5 * 60 * 1000) {
    return cachedToken.accessToken
  }

  const config = getConfig()

  if (!config.clientId || !config.clientSecret || !config.refreshToken) {
    throw new Error(
      'Configurá ML_ACCESS_TOKEN (token directo) o ML_CLIENT_ID + ML_CLIENT_SECRET + ML_REFRESH_TOKEN (auto-refresh). ' +
      'Si usás n8n para renovar el token, configurá ML_ACCESS_TOKEN.'
    )
  }

  const response = await fetch(ML_AUTH_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'refresh_token',
      client_id: config.clientId,
      client_secret: config.clientSecret,
      refresh_token: config.refreshToken,
    }),
  })

  if (!response.ok) {
    const errorText = await response.text()
    throw new Error(
      `Error renovando token ML (${response.status}): ${errorText}. ` +
      'Verificá que ML_CLIENT_ID, ML_CLIENT_SECRET y ML_REFRESH_TOKEN sean correctos.'
    )
  }

  const data = await response.json() as {
    access_token: string
    expires_in: number
    refresh_token: string
  }

  cachedToken = {
    accessToken: data.access_token,
    expiresAt: Date.now() + data.expires_in * 1000,
  }

  // Actualizar el refresh_token si ML devuelve uno nuevo
  if (data.refresh_token && data.refresh_token !== config.refreshToken) {
    process.env.ML_REFRESH_TOKEN = data.refresh_token
    console.error(
      `[ml-mcp] Refresh token actualizado. Nuevo: ${data.refresh_token.substring(0, 20)}...`
    )
  }

  return cachedToken.accessToken
}

// Para tests o reset manual
export function clearTokenCache(): void {
  cachedToken = null
}


path: src/client.ts
content: // HTTP client con rate limiting y retry para la API de Mercado Libre

import { getAccessToken } from './auth.js'
import type { MLError } from './types.js'

const ML_API_BASE = 'https://api.mercadolibre.com'
const MAX_RETRIES = 2
const RETRY_DELAY_MS = 1000

interface RequestOptions {
  method?: 'GET' | 'POST' | 'PUT' | 'DELETE'
  body?: Record<string, unknown>
  params?: Record<string, string | number | undefined>
}

export async function mlFetch<T>(
  path: string,
  options: RequestOptions = {}
): Promise<T> {
  const { method = 'GET', body, params } = options

  // Construir URL con query params
  let url = `${ML_API_BASE}${path}`
  if (params) {
    const searchParams = new URLSearchParams()
    for (const [key, value] of Object.entries(params)) {
      if (value !== undefined) {
        searchParams.set(key, String(value))
      }
    }
    const qs = searchParams.toString()
    if (qs) url += `?${qs}`
  }

  let lastError: Error | null = null

  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
    try {
      const token = await getAccessToken()

      const response = await fetch(url, {
        method,
        headers: {
          Authorization: `Bearer ${token}`,
          'Content-Type': 'application/json',
        },
        ...(body ? { body: JSON.stringify(body) } : {}),
      })

      // Rate limit: esperar y reintentar
      if (response.status === 429) {
        const retryAfter = response.headers.get('retry-after')
        const waitMs = retryAfter ? parseInt(retryAfter) * 1000 : RETRY_DELAY_MS * (attempt + 1)
        console.error(`[ml-mcp] Rate limited. Esperando ${waitMs}ms...`)
        await sleep(waitMs)
        continue
      }

      if (!response.ok) {
        const errorData = await response.json().catch(() => null) as MLError | null
        const errorMsg = errorData?.message || errorData?.error || `HTTP ${response.status}`
        const cause = errorData?.cause?.join(', ') || ''

        // Token expirado: limpiar cache y reintentar
        if (response.status === 401 && attempt < MAX_RETRIES) {
          const { clearTokenCache } = await import('./auth.js')
          clearTokenCache()
          continue
        }

        throw new Error(
          `Error API ML: ${errorMsg}${cause ? ` (${cause})` : ''} [${method} ${path}]`
        )
      }

      return await response.json() as T
    } catch (error) {
      lastError = error as Error
      if (attempt < MAX_RETRIES && isRetryable(error as Error)) {
        await sleep(RETRY_DELAY_MS * (attempt + 1))
        continue
      }
      throw error
    }
  }

  throw lastError || new Error('Error inesperado en mlFetch')
}

// Fetch múltiple con batching (ML soporta multi-get en /items)
export async function mlMultiGet<T>(ids: string[], path: string): Promise<T[]> {
  if (ids.length === 0) return []

  // ML permite hasta 20 items por multi-get
  const BATCH_SIZE = 20
  const results: T[] = []

  for (let i = 0; i < ids.length; i += BATCH_SIZE) {
    const batch = ids.slice(i, i + BATCH_SIZE)
    const data = await mlFetch<T[]>(path, {
      params: { ids: batch.join(',') },
    })
    results.push(...data)
  }

  return results
}

function isRetryable(error: Error): boolean {
  const msg = error.message.toLowerCase()
  return (
    msg.includes('fetch failed') ||
    msg.includes('network') ||
    msg.includes('econnreset') ||
    msg.includes('timeout')
  )
}

function sleep(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms))
}


path: src/index.ts
content: #!/usr/bin/env node

// MCP Server Mercado Libre — 11 tools de operaciones de seller
// Primer MCP completo de ML en el mercado

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'

// Tools
import { registerListProducts } from './tools/products.js'
import { registerGetOrders } from './tools/orders.js'
import { registerUpdatePrice } from './tools/pricing.js'
import { registerUpdateStock } from './tools/stock.js'
import { registerListQuestions, registerAnswerQuestion } from './tools/questions.js'
import { registerGetItemMetrics } from './tools/metrics.js'
import { registerManageAds } from './tools/ads.js'
import { registerGetReputation } from './tools/reputation.js'
import { registerSearchCompetitors } from './tools/competitors.js'
import { registerGetCategories } from './tools/categories.js'

const server = new McpServer({
  name: 'mercadolibre',
  version: '1.0.0',
})

// Registrar las 11 tools
registerListProducts(server)
registerGetOrders(server)
registerUpdatePrice(server)
registerUpdateStock(server)
registerListQuestions(server)
registerAnswerQuestion(server)
registerGetItemMetrics(server)
registerManageAds(server)
registerGetReputation(server)
registerSearchCompetitors(server)
registerGetCategories(server)

// Arrancar con transporte stdio
async function main() {
  const transport = new StdioServerTransport()
  await server.connect(transport)
  console.error('[ml-mcp] Server iniciado — 11 tools disponibles')
}

main().catch((error) => {
  console.error('[ml-mcp] Error fatal:', error)
  process.exit(1)
})


path: src/tools/ads.ts
content: import { z } from 'zod'
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { mlFetch } from '../client.js'

export function registerManageAds(server: McpServer) {
  server.tool(
    'manage_ads',
    'Gestiona Product Ads de Mercado Libre: activar, pausar o consultar estado de publicidad de un item.',
    {
      seller_id: z.string().describe('ID numérico del vendedor'),
      item_id: z.string().describe('ID de la publicación (ej: MLA1234567890)'),
      action: z.enum(['status', 'activate', 'pause']).describe(
        'Acción: "status" consulta el estado actual, "activate" activa publicidad, "pause" la pausa'
      ),
      daily_budget: z.number().positive().optional()
        .describe('Presupuesto diario en moneda local (solo para activate)'),
    },
    async ({ seller_id, item_id, action, daily_budget }) => {
      if (action === 'status') {
        // Consultar estado actual del ad
        try {
          const adData = await mlFetch<{
            id: string
            status: string
            daily_budget: number
            campaign_id: string
          }>(`/advertising/product_ads/${item_id}`)

          return {
            content: [{
              type: 'text' as const,
              text: `Publicidad de ${item_id}:\n` +
                `  Estado: ${adData.status}\n` +
                `  Presupuesto diario: ${adData.daily_budget}\n` +
                `  Campaña: ${adData.campaign_id}`,
            }],
          }
        } catch {
          return {
            content: [{
              type: 'text' as const,
              text: `El item ${item_id} no tiene publicidad activa o no es elegible para Product Ads.`,
            }],
          }
        }
      }

      if (action === 'activate') {
        const body: Record<string, unknown> = {
          item_id,
          status: 'active',
        }
        if (daily_budget) body.daily_budget = daily_budget

        const result = await mlFetch<{ id: string; status: string; daily_budget: number }>(
          `/advertising/product_ads`,
          { method: 'POST', body }
        )

        return {
          content: [{
            type: 'text' as const,
            text: `Publicidad activada para ${item_id}:\n` +
              `  Estado: ${result.status}\n` +
              `  Presupuesto diario: ${result.daily_budget}`,
          }],
        }
      }

      // pause
      await mlFetch(`/advertising/product_ads/${item_id}`, {
        method: 'PUT',
        body: { status: 'paused' },
      })

      return {
        content: [{
          type: 'text' as const,
          text: `Publicidad pausada para ${item_id}.`,
        }],
      }
    }
  )
}


path: src/tools/categories.ts
content: import { z } from 'zod'
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { mlFetch } from '../client.js'
import { getConfig } from '../auth.js'
import type { MLCategory } from '../types.js'

export function registerGetCategories(server: McpServer) {
  server.tool(
    'get_categories',
    'Obtiene categorías de Mercado Libre y sus atributos. Útil para saber qué categoría usar al publicar y qué atributos son requeridos.',
    {
      category_id: z.string().optional()
        .describe('ID de categoría específica (ej: MLA1234). Si se omite, lista las categorías raíz del sitio.'),
      query: z.string().optional()
        .describe('Buscar categoría por nombre (ej: "repuestos autos"). Usa el category predictor de ML.'),
    },
    async ({ category_id, query }) => {
      const config = getConfig()

      // Si se pasa un category_id, obtener detalle + atributos
      if (category_id) {
        const [category, attributes] = await Promise.all([
          mlFetch<MLCategory>(`/categories/${category_id}`),
          mlFetch<Array<{
            id: string
            name: string
            value_type: string
            tags: { required?: boolean }
            values?: Array<{ id: string; name: string }>
          }>>(`/categories/${category_id}/attributes`).catch(() => []),
        ])

        const path = category.path_from_root.map(p => p.name).join(' > ')
        const requiredAttrs = attributes.filter(a => a.tags?.required)
        const optionalAttrs = attributes.filter(a => !a.tags?.required)

        return {
          content: [{
            type: 'text' as const,
            text: `Categoría: ${category.name} (${category_id})\n` +
              `Ruta: ${path}\n\n` +
              `Subcategorías: ${category.children_categories.length > 0
                ? category.children_categories.map(c => `${c.name} (${c.id})`).join(', ')
                : 'Ninguna (categoría hoja)'}\n\n` +
              `Atributos requeridos (${requiredAttrs.length}):\n` +
              requiredAttrs.map(a => `  - ${a.name} (${a.id}) [${a.value_type}]` +
                (a.values?.length ? ` — opciones: ${a.values.slice(0, 5).map(v => v.name).join(', ')}${a.values.length > 5 ? '...' : ''}` : '')
              ).join('\n') +
              `\n\nAtributos opcionales (${optionalAttrs.length}):\n` +
              optionalAttrs.slice(0, 10).map(a => `  - ${a.name} (${a.id}) [${a.value_type}]`).join('\n') +
              (optionalAttrs.length > 10 ? `\n  ... y ${optionalAttrs.length - 10} más` : ''),
          }],
        }
      }

      // Si se pasa query, usar el predictor de categorías
      if (query) {
        const predictions = await mlFetch<Array<{
          id: string
          name: string
          path_from_root: Array<{ id: string; name: string }>
        }>>(`/sites/${config.siteId}/domain_discovery/search`, {
          params: { q: query },
        }).catch(() =>
          // Fallback al category predictor clásico
          mlFetch<Array<{
            id: string
            name: string
            path_from_root: Array<{ id: string; name: string }>
          }>>(`/sites/${config.siteId}/category_predictor/predict`, {
            params: { title: query },
          }).catch(() => [])
        )

        if (!predictions || predictions.length === 0) {
          return {
            content: [{
              type: 'text' as const,
              text: `No se encontraron categorías para "${query}". Probá con otros términos.`,
            }],
          }
        }

        const cats = (Array.isArray(predictions) ? predictions : [predictions]).map(p => ({
          id: p.id,
          nombre: p.name,
          ruta: p.path_from_root?.map(r => r.name).join(' > ') || '',
        }))

        return {
          content: [{
            type: 'text' as const,
            text: `Categorías sugeridas para "${query}":\n\n` +
              JSON.stringify(cats, null, 2),
          }],
        }
      }

      // Sin parámetros: categorías raíz del sitio
      const rootCategories = await mlFetch<Array<{ id: string; name: string }>>(
        `/sites/${config.siteId}/categories`
      )

      return {
        content: [{
          type: 'text' as const,
          text: `Categorías raíz de ${config.siteId} (${rootCategories.length}):\n\n` +
            rootCategories.map(c => `  ${c.id}: ${c.name}`).join('\n'),
        }],
      }
    }
  )
}


path: src/tools/competitors.ts
content: import { z } from 'zod'
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { mlFetch } from '../client.js'
import { getConfig } from '../auth.js'
import type { MLSearchResult } from '../types.js'

export function registerSearchCompetitors(server: McpServer) {
  server.tool(
    'search_competitors',
    'Busca productos de la competencia en Mercado Libre. Retorna top resultados con precio, vendedor, ventas y envío gratis.',
    {
      query: z.string().describe('Término de búsqueda (ej: "repuesto freno toyota")'),
      category_id: z.string().optional()
        .describe('ID de categoría para filtrar (ej: MLA1234)'),
      price_min: z.number().optional().describe('Precio mínimo'),
      price_max: z.number().optional().describe('Precio máximo'),
      limit: z.number().min(1).max(50).optional()
        .describe('Cantidad de resultados (default 10)'),
      sort: z.enum(['relevance', 'price_asc', 'price_desc']).optional()
        .describe('Ordenar por relevancia o precio'),
    },
    async ({ query, category_id, price_min, price_max, limit = 10, sort = 'relevance' }) => {
      const config = getConfig()

      const sortMap: Record<string, string> = {
        relevance: 'relevance',
        price_asc: 'price_asc',
        price_desc: 'price_desc',
      }

      const params: Record<string, string | number | undefined> = {
        q: query,
        category: category_id,
        price: price_min || price_max
          ? `${price_min || '*'}-${price_max || '*'}`
          : undefined,
        limit,
        sort: sortMap[sort],
      }

      const result = await mlFetch<{
        results: MLSearchResult[]
        paging: { total: number }
      }>(`/sites/${config.siteId}/search`, { params })

      if (result.results.length === 0) {
        return {
          content: [{
            type: 'text' as const,
            text: `No se encontraron resultados para "${query}".`,
          }],
        }
      }

      const competitors = result.results.map((r, i) => ({
        posicion: i + 1,
        id: r.id,
        titulo: r.title,
        precio: `${r.currency_id} ${r.price}`,
        vendidos: r.sold_quantity,
        stock: r.available_quantity,
        vendedor: r.seller.nickname,
        mercadolider: r.seller.power_seller_status || 'No',
        envio_gratis: r.shipping.free_shipping,
        condicion: r.condition,
        link: r.permalink,
      }))

      return {
        content: [{
          type: 'text' as const,
          text: `${result.results.length} resultados para "${query}" (${result.paging.total} total):\n\n` +
            JSON.stringify(competitors, null, 2),
        }],
      }
    }
  )
}


path: src/tools/metrics.ts
content: import { z } from 'zod'
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { mlFetch } from '../client.js'

export function registerGetItemMetrics(server: McpServer) {
  server.tool(
    'get_item_metrics',
    'Obtiene métricas de una publicación: visitas, salud de publicación y datos de catálogo.',
    {
      item_id: z.string().describe('ID de la publicación (ej: MLA1234567890)'),
      date_from: z.string().optional()
        .describe('Fecha desde para visitas (YYYY-MM-DD, default: últimos 30 días)'),
      date_to: z.string().optional()
        .describe('Fecha hasta para visitas (YYYY-MM-DD, default: hoy)'),
    },
    async ({ item_id, date_from, date_to }) => {
      // Calcular fechas por defecto (últimos 30 días)
      const now = new Date()
      const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000)
      const from = date_from || thirtyDaysAgo.toISOString().split('T')[0]
      const to = date_to || now.toISOString().split('T')[0]

      // Obtener datos del item y visitas en paralelo
      const [itemData, visitsData] = await Promise.all([
        mlFetch<{
          id: string
          title: string
          price: number
          currency_id: string
          available_quantity: number
          sold_quantity: number
          status: string
          health: number
          listing_type_id: string
          catalog_listing: boolean
          category_id: string
          permalink: string
        }>(`/items/${item_id}`, {
          params: {
            attributes: 'id,title,price,currency_id,available_quantity,sold_quantity,status,health,listing_type_id,catalog_listing,category_id,permalink',
          },
        }),
        mlFetch<Array<{ date: string; total: number }>>(
          `/items/${item_id}/visits/time_window`,
          { params: { last: 30, unit: 'day' } }
        ).catch(() => [] as Array<{ date: string; total: number }>),
      ])

      const totalVisits = visitsData.reduce((sum, v) => sum + v.total, 0)
      const avgDailyVisits = totalVisits > 0 ? Math.round(totalVisits / visitsData.length) : 0
      const conversionRate = totalVisits > 0
        ? ((itemData.sold_quantity / totalVisits) * 100).toFixed(2)
        : '0.00'

      return {
        content: [{
          type: 'text' as const,
          text: `Métricas de ${item_id} ("${itemData.title}"):\n\n` +
            `  Estado: ${itemData.status}\n` +
            `  Precio: ${itemData.currency_id} ${itemData.price}\n` +
            `  Stock: ${itemData.available_quantity}\n` +
            `  Vendidos: ${itemData.sold_quantity}\n` +
            `  Tipo listado: ${itemData.listing_type_id}\n` +
            `  En catálogo: ${itemData.catalog_listing ? 'Sí' : 'No'}\n` +
            `  Salud: ${itemData.health != null ? `${(itemData.health * 100).toFixed(0)}%` : 'N/D'}\n` +
            `\nVisitas (últimos 30 días):\n` +
            `  Total: ${totalVisits}\n` +
            `  Promedio diario: ${avgDailyVisits}\n` +
            `  Tasa de conversión estimada: ${conversionRate}%\n` +
            `\n  Categoría: ${itemData.category_id}\n` +
            `  Link: ${itemData.permalink}`,
        }],
      }
    }
  )
}


path: src/tools/orders.ts
content: import { z } from 'zod'
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { mlFetch } from '../client.js'
import type { MLOrder } from '../types.js'

export function registerGetOrders(server: McpServer) {
  server.tool(
    'get_orders',
    'Obtiene las órdenes/ventas de un vendedor. Incluye comprador, items, envío y pagos.',
    {
      seller_id: z.string().describe('ID numérico del vendedor en ML'),
      status: z.enum(['paid', 'shipped', 'delivered', 'cancelled']).optional()
        .describe('Filtrar por estado de la orden'),
      date_from: z.string().optional()
        .describe('Fecha desde (ISO 8601, ej: 2026-03-01T00:00:00.000-03:00)'),
      date_to: z.string().optional()
        .describe('Fecha hasta (ISO 8601)'),
      limit: z.number().min(1).max(50).optional()
        .describe('Cantidad máxima de órdenes (default 20)'),
      offset: z.number().min(0).optional()
        .describe('Offset para paginación'),
    },
    async ({ seller_id, status, date_from, date_to, limit = 20, offset = 0 }) => {
      const params: Record<string, string | number | undefined> = {
        seller: seller_id,
        'order.status': status,
        'order.date_created.from': date_from,
        'order.date_created.to': date_to,
        limit,
        offset,
        sort: 'date_desc',
      }

      const result = await mlFetch<{
        results: MLOrder[]
        paging: { total: number; offset: number; limit: number }
      }>('/orders/search', { params })

      if (result.results.length === 0) {
        return {
          content: [{
            type: 'text' as const,
            text: 'No se encontraron órdenes con los filtros indicados.',
          }],
        }
      }

      const orders = result.results.map(o => ({
        id: o.id,
        estado: o.status,
        fecha: o.date_created,
        total: `${o.currency_id} ${o.total_amount}`,
        comprador: o.buyer.nickname,
        items: o.order_items.map(oi => ({
          id: oi.item.id,
          titulo: oi.item.title,
          cantidad: oi.quantity,
          precio_unitario: oi.unit_price,
        })),
        envio_estado: o.shipping?.status || 'sin envío',
        pago_estado: o.payments?.[0]?.status || 'sin pago',
      }))

      return {
        content: [{
          type: 'text' as const,
          text: `${result.results.length} órdenes (de ${result.paging.total} total):\n\n` +
            JSON.stringify(orders, null, 2),
        }],
      }
    }
  )
}


path: src/tools/pricing.ts
content: import { z } from 'zod'
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { mlFetch } from '../client.js'
import type { MLProduct } from '../types.js'

export function registerUpdatePrice(server: McpServer) {
  server.tool(
    'update_price',
    'Actualiza el precio de una publicación en Mercado Libre. Retorna precio anterior y nuevo para confirmación.',
    {
      item_id: z.string().describe('ID de la publicación (ej: MLA1234567890)'),
      price: z.number().positive().describe('Nuevo precio en la moneda de la publicación'),
    },
    async ({ item_id, price }) => {
      // Obtener precio actual antes de cambiar
      const current = await mlFetch<MLProduct>(`/items/${item_id}`, {
        params: { attributes: 'id,title,price,currency_id' },
      })

      const previousPrice = current.price

      // Actualizar precio
      await mlFetch<MLProduct>(`/items/${item_id}`, {
        method: 'PUT',
        body: { price },
      })

      return {
        content: [{
          type: 'text' as const,
          text: `Precio actualizado para ${item_id} ("${current.title}"):\n` +
            `  Anterior: ${current.currency_id} ${previousPrice}\n` +
            `  Nuevo:    ${current.currency_id} ${price}\n` +
            `  Cambio:   ${price > previousPrice ? '+' : ''}${((price - previousPrice) / previousPrice * 100).toFixed(1)}%`,
        }],
      }
    }
  )
}


path: src/tools/products.ts
content: import { z } from 'zod'
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { mlFetch } from '../client.js'
import type { MLProduct } from '../types.js'

export function registerListProducts(server: McpServer) {
  server.tool(
    'list_products',
    'Lista los productos/publicaciones de un vendedor en Mercado Libre. Retorna título, precio, stock, estado, link y thumbnail.',
    {
      seller_id: z.string().describe('ID numérico del vendedor en ML'),
      status: z.enum(['active', 'paused', 'closed', 'under_review']).optional()
        .describe('Filtrar por estado de la publicación'),
      limit: z.number().min(1).max(100).optional()
        .describe('Cantidad máxima de productos (default 50)'),
      offset: z.number().min(0).optional()
        .describe('Offset para paginación'),
    },
    async ({ seller_id, status, limit = 50, offset = 0 }) => {
      // Paso 1: buscar IDs del vendedor
      const searchResult = await mlFetch<{
        results: string[]
        paging: { total: number; offset: number; limit: number }
      }>(`/users/${seller_id}/items/search`, {
        params: {
          status: status,
          limit: Math.min(limit, 50),
          offset,
        },
      })

      if (searchResult.results.length === 0) {
        return {
          content: [{
            type: 'text' as const,
            text: `No se encontraron productos para el vendedor ${seller_id}` +
              (status ? ` con estado "${status}"` : '') + '.',
          }],
        }
      }

      // Paso 2: obtener detalle de cada item (multi-get de a 20)
      const itemIds = searchResult.results
      const products: MLProduct[] = []

      for (let i = 0; i < itemIds.length; i += 20) {
        const batch = itemIds.slice(i, i + 20)
        const items = await mlFetch<Array<{ code: number; body: MLProduct }>>(
          '/items',
          { params: { ids: batch.join(',') } }
        )
        for (const item of items) {
          if (item.code === 200) products.push(item.body)
        }
      }

      const summary = products.map(p => ({
        id: p.id,
        titulo: p.title,
        precio: `${p.currency_id} ${p.price}`,
        stock: p.available_quantity,
        vendidos: p.sold_quantity,
        estado: p.status,
        link: p.permalink,
        thumbnail: p.thumbnail,
        tipo: p.listing_type_id,
        catalogo: p.catalog_listing ?? false,
      }))

      return {
        content: [{
          type: 'text' as const,
          text: `${products.length} productos encontrados (de ${searchResult.paging.total} total):\n\n` +
            JSON.stringify(summary, null, 2),
        }],
      }
    }
  )
}


path: src/tools/questions.ts
content: import { z } from 'zod'
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { mlFetch } from '../client.js'
import type { MLQuestion } from '../types.js'

export function registerListQuestions(server: McpServer) {
  server.tool(
    'list_questions',
    'Lista las preguntas recibidas en publicaciones de Mercado Libre. Puede filtrar por item o estado (sin responder, respondidas).',
    {
      seller_id: z.string().describe('ID numérico del vendedor'),
      item_id: z.string().optional().describe('ID de la publicación para filtrar'),
      status: z.enum(['UNANSWERED', 'ANSWERED', 'CLOSED_UNANSWERED']).optional()
        .describe('Filtrar por estado de la pregunta'),
      limit: z.number().min(1).max(50).optional()
        .describe('Cantidad máxima (default 20)'),
      offset: z.number().min(0).optional()
        .describe('Offset para paginación'),
    },
    async ({ seller_id, item_id, status, limit = 20, offset = 0 }) => {
      const params: Record<string, string | number | undefined> = {
        seller_id,
        item: item_id,
        status,
        limit,
        offset,
        sort_fields: 'date_created',
        sort_types: 'DESC',
      }

      const result = await mlFetch<{
        questions: MLQuestion[]
        total: number
      }>('/questions/search', { params })

      if (result.questions.length === 0) {
        return {
          content: [{
            type: 'text' as const,
            text: 'No se encontraron preguntas con los filtros indicados.',
          }],
        }
      }

      const questions = result.questions.map(q => ({
        id: q.id,
        item_id: q.item_id,
        pregunta: q.text,
        de: q.from.nickname,
        fecha: q.date_created,
        estado: q.status,
        respuesta: q.answer ? q.answer.text : null,
        fecha_respuesta: q.answer?.date_created || null,
      }))

      return {
        content: [{
          type: 'text' as const,
          text: `${result.questions.length} preguntas (de ${result.total} total):\n\n` +
            JSON.stringify(questions, null, 2),
        }],
      }
    }
  )
}

export function registerAnswerQuestion(server: McpServer) {
  server.tool(
    'answer_question',
    'Responde una pregunta en Mercado Libre. La respuesta es pública y visible por todos los compradores.',
    {
      question_id: z.number().describe('ID numérico de la pregunta a responder'),
      text: z.string().min(1).max(2000).describe('Texto de la respuesta'),
    },
    async ({ question_id, text }) => {
      await mlFetch<{ id: number; status: string }>(
        `/answers`,
        {
          method: 'POST',
          body: { question_id, text },
        }
      )

      return {
        content: [{
          type: 'text' as const,
          text: `Pregunta #${question_id} respondida exitosamente.\n` +
            `Respuesta: "${text.substring(0, 100)}${text.length > 100 ? '...' : ''}"`,
        }],
      }
    }
  )
}


path: src/tools/reputation.ts
content: import { z } from 'zod'
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { mlFetch } from '../client.js'

export function registerGetReputation(server: McpServer) {
  server.tool(
    'get_reputation',
    'Obtiene la reputación de un vendedor: nivel, ventas completadas, reclamos, cancelaciones y tiempo de despacho.',
    {
      seller_id: z.string().describe('ID numérico del vendedor en ML'),
    },
    async ({ seller_id }) => {
      const user = await mlFetch<{
        id: number
        nickname: string
        seller_reputation: {
          level_id: string
          power_seller_status: string | null
          transactions: {
            completed: number
            canceled: number
            total: number
            ratings: {
              positive: number
              neutral: number
              negative: number
            }
            period: string
          }
          metrics: {
            claims: { rate: number; value: number }
            delayed_handling_time: { rate: number; value: number }
            cancellations: { rate: number; value: number }
            sales: { completed: number; period: string }
          }
        }
        status: { site_status: string }
        points: number
        registration_date: string
      }>(`/users/${seller_id}`)

      const rep = user.seller_reputation
      const metrics = rep.metrics
      const tx = rep.transactions

      return {
        content: [{
          type: 'text' as const,
          text: `Reputación de ${user.nickname} (ID: ${user.id}):\n\n` +
            `  Nivel: ${rep.level_id}\n` +
            `  MercadoLíder: ${rep.power_seller_status || 'No'}\n` +
            `  Estado: ${user.status.site_status}\n` +
            `  Registrado: ${user.registration_date}\n` +
            `\nTransacciones (${tx.period}):\n` +
            `  Completadas: ${tx.completed}\n` +
            `  Canceladas: ${tx.canceled}\n` +
            `  Total: ${tx.total}\n` +
            `  Calificación positiva: ${(tx.ratings.positive * 100).toFixed(1)}%\n` +
            `  Calificación neutral: ${(tx.ratings.neutral * 100).toFixed(1)}%\n` +
            `  Calificación negativa: ${(tx.ratings.negative * 100).toFixed(1)}%\n` +
            `\nMétricas:\n` +
            `  Reclamos: ${(metrics.claims.rate * 100).toFixed(2)}% (${metrics.claims.value} casos)\n` +
            `  Despacho demorado: ${(metrics.delayed_handling_time.rate * 100).toFixed(2)}% (${metrics.delayed_handling_time.value} casos)\n` +
            `  Cancelaciones: ${(metrics.cancellations.rate * 100).toFixed(2)}% (${metrics.cancellations.value} casos)\n` +
            `  Ventas completadas: ${metrics.sales.completed} (${metrics.sales.period})`,
        }],
      }
    }
  )
}


path: src/tools/stock.ts
content: import { z } from 'zod'
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { mlFetch } from '../client.js'
import type { MLProduct } from '../types.js'

export function registerUpdateStock(server: McpServer) {
  server.tool(
    'update_stock',
    'Actualiza el stock/cantidad disponible de una publicación en Mercado Libre. Retorna stock anterior y nuevo.',
    {
      item_id: z.string().describe('ID de la publicación (ej: MLA1234567890)'),
      quantity: z.number().int().min(0).describe('Nueva cantidad disponible'),
    },
    async ({ item_id, quantity }) => {
      // Obtener stock actual
      const current = await mlFetch<MLProduct>(`/items/${item_id}`, {
        params: { attributes: 'id,title,available_quantity' },
      })

      const previousStock = current.available_quantity

      // Actualizar stock
      await mlFetch<MLProduct>(`/items/${item_id}`, {
        method: 'PUT',
        body: { available_quantity: quantity },
      })

      return {
        content: [{
          type: 'text' as const,
          text: `Stock actualizado para ${item_id} ("${current.title}"):\n` +
            `  Anterior: ${previousStock} unidades\n` +
            `  Nuevo:    ${quantity} unidades\n` +
            `  Diferencia: ${quantity >= previousStock ? '+' : ''}${quantity - previousStock}`,
        }],
      }
    }
  )
}


path: src/types.ts
content: // Tipos compartidos del MCP server Mercado Libre

export interface MLConfig {
  clientId: string
  clientSecret: string
  refreshToken: string
  siteId: string // MLA, MLU, MLB, etc.
}

export interface TokenData {
  accessToken: string
  expiresAt: number // Unix timestamp en ms
}

export interface MLProduct {
  id: string
  title: string
  price: number
  currency_id: string
  available_quantity: number
  sold_quantity: number
  status: string
  permalink: string
  thumbnail: string
  category_id: string
  condition: string
  listing_type_id: string
  date_created: string
  last_updated: string
  health?: number
  catalog_listing?: boolean
}

export interface MLOrder {
  id: number
  status: string
  date_created: string
  date_closed: string
  total_amount: number
  currency_id: string
  buyer: {
    id: number
    nickname: string
    first_name: string
    last_name: string
  }
  order_items: Array<{
    item: { id: string; title: string }
    quantity: number
    unit_price: number
  }>
  shipping: {
    id: number
    status: string
  }
  payments: Array<{
    id: number
    status: string
    total_paid_amount: number
  }>
}

export interface MLQuestion {
  id: number
  item_id: string
  seller_id: number
  status: string
  text: string
  date_created: string
  from: { id: number; nickname: string }
  answer?: {
    text: string
    date_created: string
    status: string
  }
}

export interface MLReputation {
  seller_id: number
  nickname: string
  level_id: string
  power_seller_status: string | null
  transactions: {
    completed: number
    canceled: number
    total: number
    ratings: {
      positive: number
      neutral: number
      negative: number
    }
    period: string
  }
  metrics: {
    claims: { rate: number; value: number }
    delayed_handling_time: { rate: number; value: number }
    cancellations: { rate: number; value: number }
    sales: { completed: number; period: string }
  }
}

export interface MLVisits {
  item_id: string
  total_visits: number
  date_from: string
  date_to: string
}

export interface MLAdCampaign {
  item_id: string
  status: string
  daily_budget: number
  ads_cost: number
}

export interface MLCategory {
  id: string
  name: string
  path_from_root: Array<{ id: string; name: string }>
  children_categories: Array<{ id: string; name: string }>
  attributes?: Array<{
    id: string
    name: string
    value_type: string
    tags: Record<string, unknown>
  }>
}

export interface MLSearchResult {
  id: string
  title: string
  price: number
  currency_id: string
  sold_quantity: number
  available_quantity: number
  permalink: string
  thumbnail: string
  seller: {
    id: number
    nickname: string
    power_seller_status: string | null
  }
  shipping: { free_shipping: boolean }
  condition: string
}

export interface MLError {
  message: string
  error: string
  status: number
  cause: string[]
}


path: tsconfig.json
content: {
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "dist",
    "rootDir": "src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "declaration": true,
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}


Security scan verdicts — ClawScan: suspicious | static analysis: suspicious | VirusTotal: clean | SkillSpector: suspicious (HIGH)