Skip to main content
Glama

Search Examiner Reports

search_examiner_reports
Read-onlyIdempotent

Search examiner reports to understand what examiners expect, common mistakes, and tips for structuring answers to gain full marks on Cambridge topics.

Instructions

Search Cambridge examiner report commentary for insights on how a topic is examined.

Returns examiner observations including:

  • What examiners expect in answers on this topic

  • Common mistakes candidates make

  • Tips for how to structure answers to gain full marks

Use AFTER searching questions to understand examiner expectations on the same topic. Examiner reports are the most authoritative source for HOW to answer, not WHAT to answer.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryYes
subjectNo
paperNo
yearNo
limitNo

Implementation Reference

  • The search_examiner_reports tool handler function. Calls /search/examiner-reports API endpoint, deduplicates results using _clean_examiner_chunk, and returns formatted examiner commentary with year/session/paper metadata.
    def search_examiner_reports(
        query: str,
        subject: Optional[str] = DEFAULT_SUBJECT,
        paper: Optional[int] = None,
        year: Optional[int] = None,
        limit: int = 5,
    ) -> ToolResult:
        """Search Cambridge examiner report commentary for insights on how a topic is examined.
    
        Returns examiner observations including:
        - What examiners expect in answers on this topic
        - Common mistakes candidates make
        - Tips for how to structure answers to gain full marks
    
        Use AFTER searching questions to understand examiner expectations on the same topic.
        Examiner reports are the most authoritative source for HOW to answer, not WHAT to answer.
        """
        params: dict[str, Any] = {"q": query, "limit": max(1, min(limit, 20))}
        if subject:
            params["subject"] = subject
        if paper is not None:
            params["paper"] = paper
        if year is not None:
            params["year"] = year
    
        try:
            data = _api_get("/search/examiner-reports", params)
        except Exception as exc:
            logger.error("search_examiner_reports failed: %s", exc)
            error_payload = _error_from_exception(exc, "/search/examiner-reports")
            raise ToolError(error_payload.get("error", {}).get("message", "ER search failed."))
    
        raw_chunks = data.get("results", []) if isinstance(data, dict) else []
        total = data.get("total", 0) if isinstance(data, dict) else 0
    
        # De-duplicate near-identical chunks and strip boilerplate
        seen_hashes: set[int] = set()
        curated_chunks: list[dict[str, Any]] = []
        for chunk in raw_chunks:
            if not isinstance(chunk, dict):
                continue
            cleaned_text = _clean_examiner_chunk(chunk.get("chunk_text", ""))
            if not cleaned_text or len(cleaned_text) < 20:
                continue
            text_hash = hash(cleaned_text[:200])
            if text_hash in seen_hashes:
                continue
            seen_hashes.add(text_hash)
            curated_chunks.append({
                "year": chunk.get("year"),
                "session": chunk.get("session_name"),
                "paper": chunk.get("paper_number"),
                "commentary": cleaned_text,
                "relevance_score": chunk.get("relevance_score"),
            })
    
        lines = [f"Examiner Report Search: {total} total for '{query}', showing {len(curated_chunks)} unique."]
        for i, chunk in enumerate(curated_chunks, 1):
            year_str = chunk.get("year", "?")
            session = chunk.get("session", "?")
            paper_num = chunk.get("paper")
            paper_label = f"Paper {paper_num}" if paper_num else "General"
            text = _clean_text(chunk.get("commentary", ""), max_len=350)
            lines.append(f"[{i}] {year_str} {session} | {paper_label}")
            lines.append(f"    {text}")
    
        if not curated_chunks:
            lines.append("No examiner report data found for this query.")
    
        payload = {
            "ok": True, "query": query, "total": total,
            "returned": len(curated_chunks),
            "results": curated_chunks,
        }
        return ToolResult(content="\n".join(lines), structured_content=payload)
  • Tool registration via @mcp.tool decorator with title 'Search Examiner Reports', tags 'search' and 'enhanced', and readOnlyHint/idempotentHint annotations.
    @mcp.tool(
        title="Search Examiner Reports",
        tags={"search", "enhanced"},
        annotations={"readOnlyHint": True, "idempotentHint": True},
    )
  • Helper function that cleans examiner report text by removing generic boilerplate preamble (e.g., 'Comments on specific questions', 'Key messages', 'General comments') and copyright lines.
    def _clean_examiner_chunk(text: str) -> str:
        """Remove generic ER boilerplate preamble, keep only topic-specific commentary."""
        if not text:
            return ""
    
        # Common boilerplate sections to skip past
        skip_markers = [
            "Comments on specific questions",
            "Comment on specific questions",
        ]
        for marker in skip_markers:
            idx = text.find(marker)
            if idx >= 0:
                # Start from after the marker line
                after = text[idx + len(marker):]
                stripped = after.lstrip("\n\r ")
                if stripped:
                    text = stripped
                    break
    
        # Strip remaining generic preamble phrases that add no value
        boilerplate_starts = [
            "Key messages\n",
            "General comments\n",
            "Candidates need to demonstrate a detailed study",
            "Candidates are advised to answer each question",
            "Candidates must always make sure",
            "Candidates are further advised to make use",
        ]
        for bp in boilerplate_starts:
            if text.startswith(bp):
                # Find next paragraph
                next_para = text.find("\n\n", len(bp))
                if next_para > 0:
                    text = text[next_para:].lstrip("\n\r ")
    
        # Clean up copyright lines
        text = re.sub(r"©\s*\d{4}\s*$", "", text, flags=re.MULTILINE).strip()
        return text
Behavior3/5

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

Annotations already declare readOnlyHint and idempotentHint. Description adds behavioral context about the type of insights returned (examiner observations, common mistakes, tips). No contradiction with annotations.

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

Conciseness5/5

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

Description is concise, front-loaded with purpose, uses a bullet list for return types, and fits in a few sentences without unnecessary words.

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?

Covers the purpose, return type, and usage guidance effectively. Does not explain pagination or exact output format, but for a search tool with no output schema, it is adequate.

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

Parameters2/5

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

Schema description coverage is 0%, and the description does not explain the individual parameters (subject, paper, year, limit) beyond their names. The general purpose of the query is clear, but filter parameters lack explanation.

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?

Clearly states it searches Cambridge examiner reports for insights on how a topic is examined. Lists specific return types (examiner expectations, common mistakes, tips). Differentiates from siblings by specifying it is about HOW to answer, not WHAT, and advises use after searching questions.

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?

Explicitly says 'Use AFTER searching questions to understand examiner expectations on the same topic.' Provides clear context for when to use, but does not give explicit alternatives for when not to use it.

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/Pixel2075/searchcaie-mcp'

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