Skip to main content
Glama

analyze_reviews

Analyze previously fetched Amazon reviews to generate a VOC report with sentiment breakdown, pain points, selling points, and listing tips without additional API costs.

Instructions

Run AI analysis on reviews you already have.

Useful when you fetched reviews via fetch_reviews (or your own scraper) and want the VOC report — sentiment breakdown, pain points, selling points, listing tips — without re-paying the Shulex API.

Args: reviews_json: Either fetch.sh's {reviews, meta} envelope, or a bare list of review objects. asin: 10-character ASIN that the reviews belong to (for the report header).

Returns: {asin, market, report_markdown, sentiment, pain_points, selling_points, tips, summary_zh, summary_en}

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
reviews_jsonYes
asinYes

Implementation Reference

  • Actual implementation of the analyze_reviews tool. Writes reviews to a temp JSON file, runs analyze.sh shell script, parses the markdown output into structured dict.
    def analyze_reviews(reviews_json: dict[str, Any] | list[dict], asin: str) -> dict[str, Any]:
        """Run AI analysis on a reviews JSON object (or list) that was previously
        fetched. Useful when the caller already has reviews from `fetch_reviews`
        and wants to re-analyze without paying the Shulex API a second time.
        """
        asin = _validate_asin(asin)
    
        # Accept both fetch.sh's `{reviews, meta}` shape and a bare list.
        if isinstance(reviews_json, list):
            wrapped = {"reviews": reviews_json, "meta": {"asin": asin, "market": "US"}}
        else:
            wrapped = reviews_json
            wrapped.setdefault("meta", {}).setdefault("asin", asin)
    
        market = wrapped.get("meta", {}).get("market", "US")
    
        with tempfile.NamedTemporaryFile(
            prefix="mcp_reviews_", suffix=".json", delete=False, mode="w", encoding="utf-8"
        ) as tmp:
            json.dump(wrapped, tmp, ensure_ascii=False)
            reviews_path = tmp.name
        try:
            report_md = _run_script("analyze.sh", [reviews_path, asin])
        finally:
            try:
                os.unlink(reviews_path)
            except OSError:
                pass
    
        return _parse_analyze_markdown(asin, market, report_md)
  • MCP tool registration via @mcp.tool() decorator. Thin wrapper that delegates to tools.analyze_reviews().
    @mcp.tool()
    def analyze_reviews(reviews_json: dict | list, asin: str) -> dict:
        """Run AI analysis on reviews you already have.
    
        Useful when you fetched reviews via `fetch_reviews` (or your own scraper)
        and want the VOC report — sentiment breakdown, pain points, selling
        points, listing tips — without re-paying the Shulex API.
    
        Args:
            reviews_json: Either fetch.sh's `{reviews, meta}` envelope, or a
                bare list of review objects.
            asin: 10-character ASIN that the reviews belong to (for the report
                header).
    
        Returns:
            {asin, market, report_markdown, sentiment, pain_points,
             selling_points, tips, summary_zh, summary_en}
        """
        return tools.analyze_reviews(reviews_json=reviews_json, asin=asin)
  • Parses the markdown output from analyze.sh into structured fields: sentiment counts, pain points, selling points, tips, and summaries in both ZH and EN.
    def _parse_analyze_markdown(asin: str, market: str, report_md: str) -> dict[str, Any]:
        """Pull the structured fields analyze.sh embedded as `KEY: value` lines.
    
        The renderer in analyze.sh emits markers like `SENTIMENT_POSITIVE: 37`,
        `PAIN_POINT_1_ZH: ...`, `TIP_3_EN: ...` interleaved with the prose
        markdown. We grep them out into a flat dict, then assemble structured
        fields. Anything we can't parse stays accessible via `report_markdown`.
        """
        flat: dict[str, str] = {}
        for line in report_md.splitlines():
            m = _LINE_RE.match(line.strip())
            if m:
                flat.setdefault(m.group(1), m.group(2).strip())
    
        def grouped(prefix: str, suffixes: tuple[str, ...]) -> list[dict[str, str]]:
            items = []
            for i in range(1, 11):  # support up to 10; reports typically have 5
                row = {}
                for suf in suffixes:
                    key = f"{prefix}_{i}_{suf}"
                    if key in flat:
                        row[suf.lower()] = flat[key]
                if row:
                    items.append(row)
            return items
    
        sentiment: Optional[dict[str, int]] = None
        if {"SENTIMENT_POSITIVE", "SENTIMENT_NEUTRAL", "SENTIMENT_NEGATIVE"} <= flat.keys():
            try:
                sentiment = {
                    "positive": int(flat["SENTIMENT_POSITIVE"]),
                    "neutral": int(flat["SENTIMENT_NEUTRAL"]),
                    "negative": int(flat["SENTIMENT_NEGATIVE"]),
                }
            except ValueError:
                sentiment = None
    
        return {
            "asin": asin,
            "market": market,
            "report_markdown": report_md,
            "sentiment": sentiment,
            "pain_points": grouped("PAIN_POINT", ("ZH", "EN", "COUNT")),
            "selling_points": grouped("SELLING_POINT", ("ZH", "EN", "COUNT")),
            "tips": grouped("TIP", ("ZH", "EN")),
            "summary_zh": flat.get("SUMMARY_ZH", ""),
            "summary_en": flat.get("SUMMARY_EN", ""),
        }
  • Helper used by analyze_reviews to validate and normalize the ASIN parameter.
    def _validate_asin(asin: str) -> str:
        """Validate ASIN shape. We accept lowercase from MCP clients but normalize
        to upper before passing to the shell scripts (which warn but proceed on
        lowercase) — this keeps our error messages clearer.
        """
        asin = asin.strip().upper()
        if not VALID_ASIN_RE.match(asin):
            raise ValueError(
                f"invalid ASIN {asin!r}: must be 10 alphanumeric characters (e.g. B08N5WRWNW)"
            )
        return asin
  • Pydantic model defining the return schema for analyze_reviews output.
    class AnalyzeReport(BaseModel):
        """Report returned by `analyze_reviews` / `voc_full`.
    
        Always includes the raw markdown (`report_markdown`). When the report can
        be parsed cleanly, structured fields are populated; on parse failure the
        structured fields are None and the markdown is still returned verbatim.
        """
        asin: str
        market: str = "US"
        report_markdown: str
        sentiment: Optional[dict] = None
        pain_points: list[dict] = Field(default_factory=list)
        selling_points: list[dict] = Field(default_factory=list)
        tips: list[dict] = Field(default_factory=list)
        summary_zh: str = ""
        summary_en: str = ""
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description must disclose behavioral traits. It mentions that using this tool avoids re-paying the Shulex API, which is a cost-related behavior. However, it does not mention whether the tool is read-only, requires authentication, or any other side effects. Given it is an analysis tool, the description is moderately transparent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is concise and well-structured, with separate paragraphs for purpose, usage, arguments, and returns. It is front-loaded with the action. Could be slightly shorter, but no unnecessary filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity (custom input format) and lack of output schema, the description covers all return fields and provides usage context. It explains how reviews_json can be two different formats, which is crucial. It also mentions the relationship to fetch_reviews. Missing details on error handling or limitations, but overall adequate.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema coverage is 0%, so the description must compensate. It does so by specifying that reviews_json accepts either a specific envelope format or a bare list, and explains asin as a 10-character identifier for the report header. This adds significant meaning beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description explicitly states the tool runs AI analysis on reviews to produce a VOC report with specific components (sentiment, pain points, etc.). It distinguishes from sibling tools like fetch_reviews (which fetches reviews) by focusing on analysis of already-obtained reviews.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description clearly indicates when this tool is useful: after fetching reviews via fetch_reviews or a custom scraper, to get analysis without extra API cost. It does not explicitly state when not to use, but the context is implied.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mguozhen/voc-amazon-reviews'

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