Skip to main content
Glama
saidsurucu

Mevzuat MCP

by saidsurucu

get_teblig_content

Retrieve the full Markdown content of a Turkish communiqué (Tebliğ) using its number and series for analysis, summarization, or question answering.

Instructions

Retrieve the full content of a Turkish communiqué (Tebliğ) in Markdown format.

This tool fetches the complete text of a communiqué identified by its number. Use 'search_teblig' first to find the communiqué number and series.

Returns:

  • Full communiqué content formatted as Markdown

  • Ready for analysis, summarization, or question answering

Example usage:

  1. Search for communiqués: search_teblig(aranacak_ifade="katma değer vergisi")

  2. Get full content: get_teblig_content(mevzuat_no="42331", mevzuat_tertip="5")

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
mevzuat_noYesThe communiqué number from search results (e.g., '42331')
mevzuat_tertipNoCommuniqué series from search results (e.g., '5')5

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
madde_idYes
mevzuat_idYes
error_messageNo
markdown_contentYes

Implementation Reference

  • The handler function for 'get_teblig_content' tool. Decorated with @app.tool() for MCP registration. Calls MevzuatApiClientNew.get_content with mevzuat_tur=9 specifically for Tebliğ documents.
    @app.tool()
    async def get_teblig_content(
        mevzuat_no: str = Field(
            ...,
            description="The communiqué number from search results (e.g., '42331')"
        ),
        mevzuat_tertip: str = Field(
            "5",
            description="Communiqué series from search results (e.g., '5')"
        )
    ) -> MevzuatArticleContent:
        """
        Retrieve the full content of a Turkish communiqué (Tebliğ) in Markdown format.
    
        This tool fetches the complete text of a communiqué identified by its number.
        Use 'search_teblig' first to find the communiqué number and series.
    
        Returns:
        - Full communiqué content formatted as Markdown
        - Ready for analysis, summarization, or question answering
    
        Example usage:
        1. Search for communiqués: search_teblig(aranacak_ifade="katma değer vergisi")
        2. Get full content: get_teblig_content(mevzuat_no="42331", mevzuat_tertip="5")
        """
        logger.info(f"Tool 'get_teblig_content' called: {mevzuat_no}, tertip: {mevzuat_tertip}")
    
        try:
            result = await mevzuat_client.get_content(
                mevzuat_no=mevzuat_no,
                mevzuat_tur=9,  # Tebliğ
                mevzuat_tertip=mevzuat_tertip
            )
    
            if result.error_message:
                logger.warning(f"Error fetching communiqué content: {result.error_message}")
    
            return result
    
        except Exception as e:
            logger.exception(f"Error in tool 'get_teblig_content' for {mevzuat_no}")
            return MevzuatArticleContent(
                madde_id=mevzuat_no,
                mevzuat_id=mevzuat_no,
                markdown_content="",
                error_message=f"An unexpected error occurred: {str(e)}"
            )
  • Pydantic model defining the output schema (MevzuatArticleContent) returned by the tool.
    class MevzuatArticleContent(BaseModel):
        """Model for the content of legislation (reused from old models)."""
        madde_id: str
        mevzuat_id: str
        markdown_content: str
        error_message: Optional[str] = None
  • The supporting method in MevzuatApiClientNew that performs the actual content fetching, conversion to markdown, and caching. Called by the tool with mevzuat_tur=9 for Tebliğ.
    async def get_content(
        self,
        mevzuat_no: str,
        mevzuat_tur: int = 1,
        mevzuat_tertip: str = "3",
        resmi_gazete_tarihi: Optional[str] = None
    ) -> MevzuatArticleContent:
        """
        Download and extract content from legislation.
        Tries HTML scraping first (most reliable), then falls back to file downloads.
        For Presidential Decisions (tur=20) and Circulars (tur=22), skip HTML and go directly to PDF.
    
        Args:
            mevzuat_no: Legislation number
            mevzuat_tur: Legislation type code (1=Kanun, 20=CB Kararı, 22=CB Genelgesi, etc.)
            mevzuat_tertip: Series number
            resmi_gazete_tarihi: Official Gazette date (DD/MM/YYYY) - required for CB Genelgesi (tur=22)
        """
        # CB Kararları (tur=20) and CB Genelgesi (tur=22) are PDF-only, skip HTML scraping
        if mevzuat_tur not in [20, 22]:
            # Try HTML scraping first (most reliable method for other types)
            result = await self.get_content_from_html(mevzuat_no, mevzuat_tur, mevzuat_tertip)
            if result.markdown_content:
                return result
    
            logger.info(f"HTML scraping returned no content for {mevzuat_no}, trying file downloads")
        else:
            if mevzuat_tur == 20:
                logger.info("CB Kararı detected (tur=20), skipping HTML scraping, going directly to PDF")
            elif mevzuat_tur == 22:
                logger.info("CB Genelgesi detected (tur=22), skipping HTML scraping, going directly to PDF")
    
        cache_key = f"doc:{mevzuat_tur}.{mevzuat_tertip}.{mevzuat_no}" if self._cache_enabled else None
    
        if cache_key and self._cache:
            cached_content = self._cache.get(cache_key)
            if cached_content:
                logger.debug(f"Cache hit: {mevzuat_no}")
                return MevzuatArticleContent(
                    madde_id=mevzuat_no,
                    mevzuat_id=mevzuat_no,
                    markdown_content=cached_content
                )
    
        # Construct URLs based on mevzuat type
        if mevzuat_tur == 22:  # CB Genelgesi - special PDF URL format
            if not resmi_gazete_tarihi:
                return MevzuatArticleContent(
                    madde_id=mevzuat_no,
                    mevzuat_id=mevzuat_no,
                    markdown_content="",
                    error_message="resmi_gazete_tarihi is required for CB Genelgesi (tur=22)"
                )
            # Convert DD/MM/YYYY to YYYYMMDD
            parts = resmi_gazete_tarihi.split('/')
            if len(parts) == 3:
                date_str = f"{parts[2]}{parts[1].zfill(2)}{parts[0].zfill(2)}"
            else:
                return MevzuatArticleContent(
                    madde_id=mevzuat_no,
                    mevzuat_id=mevzuat_no,
                    markdown_content="",
                    error_message=f"Invalid date format: {resmi_gazete_tarihi}. Expected DD/MM/YYYY"
                )
            pdf_url = self.GENELGE_PDF_URL_TEMPLATE.format(date=date_str, no=mevzuat_no)
            doc_url = None  # Genelge has no DOC version
        elif mevzuat_tur == 20:  # CB Kararı - PDF only, no DOC
            doc_url = None  # CB Kararları have no DOC version
            pdf_url = self.PDF_URL_TEMPLATE.format(tur=mevzuat_tur, tertip=mevzuat_tertip, no=mevzuat_no)
        else:
            doc_url = self.DOC_URL_TEMPLATE.format(tur=mevzuat_tur, tertip=mevzuat_tertip, no=mevzuat_no)
            pdf_url = self.PDF_URL_TEMPLATE.format(tur=mevzuat_tur, tertip=mevzuat_tertip, no=mevzuat_no)
    
        # Try DOC first (skip for CB Genelgesi and CB Kararı which have no DOC version)
        if doc_url:
            try:
                logger.info(f"Trying DOC: {doc_url}")
                # Use separate headers for document download
                doc_headers = {
                    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
                    'Accept': 'application/msword, */*',
                }
                response = await self._http_client.get(doc_url, headers=doc_headers)
                response.raise_for_status()
    
                doc_bytes = response.content
                logger.info(f"Downloaded DOC: {len(doc_bytes)} bytes")
    
                # DOC files from mevzuat.gov.tr are actually HTML
                if len(doc_bytes) < 100:
                    logger.warning(f"DOC file too small ({len(doc_bytes)} bytes), likely empty")
                    raise Exception("DOC file is empty or too small")
    
                doc_stream = io.BytesIO(doc_bytes)
                result = self._md_converter.convert_stream(doc_stream, file_extension=".doc")
                markdown_content = result.text_content.strip() if result and result.text_content else ""
    
                if markdown_content:
                    logger.info(f"DOC conversion successful for {mevzuat_no}")
                    if cache_key and self._cache:
                        self._cache.put(cache_key, markdown_content)
                    return MevzuatArticleContent(
                        madde_id=mevzuat_no,
                        mevzuat_id=mevzuat_no,
                        markdown_content=markdown_content
                    )
            except Exception as e:
                logger.info(f"DOC failed, trying PDF: {e}")
    
        # Try PDF fallback
        try:
            logger.info(f"Trying PDF: {pdf_url}")
    
            # For CB Kararı (tur=20) and CB Genelgesi (tur=22), ensure we have session cookies
            if mevzuat_tur in [20, 22]:
                await self._ensure_session()
                # Create temporary client with cookies to avoid deprecation warning
                async with httpx.AsyncClient(
                    headers=self.HEADERS,
                    cookies=self._cookies,
                    timeout=self._http_client.timeout,
                    follow_redirects=True
                ) as temp_client:
                    response = await temp_client.get(pdf_url)
            else:
                response = await self._http_client.get(pdf_url)
    
            response.raise_for_status()
    
            pdf_bytes = response.content
            markdown_content = ""
    
            # For CB Kararı (tur=20) and CB Genelgesi (tur=22), use Mistral OCR (handles images + text)
            if mevzuat_tur in [20, 22] and self._mistral_client:
                doc_type = "CB Kararı" if mevzuat_tur == 20 else "CB Genelgesi"
                logger.info(f"Using Mistral OCR for {doc_type} PDF")
                markdown_content = await self._ocr_pdf_with_mistral(pdf_bytes, pdf_url)
    
                # Fallback to markitdown if OCR fails
                if not markdown_content:
                    logger.warning("Mistral OCR failed, falling back to markitdown")
                    pdf_stream = io.BytesIO(pdf_bytes)
                    result = self._md_converter.convert_stream(pdf_stream, file_extension=".pdf")
                    markdown_content = result.text_content.strip() if result and result.text_content else ""
            else:
                # Use markitdown for other types
                pdf_stream = io.BytesIO(pdf_bytes)
                result = self._md_converter.convert_stream(pdf_stream, file_extension=".pdf")
                markdown_content = result.text_content.strip() if result and result.text_content else ""
    
            if markdown_content:
                logger.info(f"PDF conversion successful for {mevzuat_no}")
                if cache_key and self._cache:
                    self._cache.put(cache_key, markdown_content)
                return MevzuatArticleContent(
                    madde_id=mevzuat_no,
                    mevzuat_id=mevzuat_no,
                    markdown_content=markdown_content
                )
        except Exception as e:
            logger.error(f"PDF also failed: {e}")
    
        return MevzuatArticleContent(
            madde_id=mevzuat_no,
            mevzuat_id=mevzuat_no,
            markdown_content="",
            error_message=f"Both DOC and PDF download/conversion failed for {mevzuat_tur}.{mevzuat_tertip}.{mevzuat_no}"
        )
  • The @app.tool() decorator registers the get_teblig_content function as an MCP tool.
    @app.tool()
  • API mapping that normalizes 'Tebliğ' to 'Teblig' for search API requests.
    MEVZUAT_TUR_API_MAPPING = {
        "Kurum Yönetmeliği": "KurumVeKurulusYonetmeligi",
        "Cumhurbaşkanlığı Kararnamesi": "CumhurbaskaniKararnameleri",
        "Cumhurbaşkanı Kararı": "CumhurbaskaniKararlari",
        "CB Yönetmeliği": "CumhurbaskanligiVeBakanlarKuruluYonetmelik",
        "CB Genelgesi": "CumhurbaskanligiGenelgeleri",
        "Tebliğ": "Teblig",
Behavior3/5

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

No annotations are provided, so the description carries full burden. It discloses the return format ('Markdown') and readiness for analysis, but lacks details on error handling, rate limits, authentication needs, or whether it's read-only/destructive. The description doesn't contradict annotations (none exist).

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?

Well-structured with clear sections: purpose, usage guidance, returns, and example. Every sentence adds value, though the example usage section could be slightly more concise. The description is appropriately sized and front-loaded with key information.

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 tool has an output schema (which handles return values), 100% schema coverage, and clear purpose/guidelines, the description is mostly complete. However, as a tool with no annotations, it could benefit from more behavioral context about error cases or limitations to achieve full completeness.

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

Parameters3/5

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

Schema description coverage is 100%, so the schema already documents both parameters thoroughly. The description adds minimal value beyond the schema by mentioning these parameters come 'from search results' in the example, but doesn't provide additional semantic context. Baseline 3 is appropriate when schema does the heavy lifting.

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 clearly states the specific action ('Retrieve the full content'), resource ('Turkish communiqué (Tebliğ)'), and format ('Markdown format'). It distinguishes from sibling tools by focusing on content retrieval rather than searching, with 'search_teblig' explicitly mentioned as a different tool.

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

Usage Guidelines5/5

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

Explicit guidance is provided: 'Use search_teblig first to find the communiqué number and series.' This clearly indicates when to use this tool versus alternatives, with a named alternative and prerequisite workflow.

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/saidsurucu/mevzuat-mcp'

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