Skip to main content
Glama
saidsurucu

Mevzuat MCP

by saidsurucu

search_within_tuzuk

Search specific Turkish statutes' articles using keyword queries with Boolean operators or semantic AI search for natural language questions.

Instructions

Search within a specific Statute's (Tüzük) articles using keyword or semantic search.

Modes:

  • semantic=False (default): Keyword search with Boolean operators (AND/OR/NOT, uppercase required)

  • semantic=True: Natural language semantic search using AI embeddings (requires OPENROUTER_API_KEY)

Keyword examples: "tapu AND sicil", '"sicil kayıt"', "tescil OR ilan" Semantic examples: "tapu sicil kayıt işlemleri", "vakıf tescil süreci"

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
mevzuat_noYesThe statute number to search within (e.g., '20135150', '20134513', '200814001')
keywordYesSearch query. For keyword mode: supports AND/OR/NOT operators (uppercase). For semantic mode: use natural language.
mevzuat_tertipNoStatute series from search results (e.g., '5')5
case_sensitiveNoWhether to match case when searching (default: False). Only used in keyword mode.
max_resultsNoMaximum number of matching articles to return (1-50, default: 25)
semanticNoTrue: semantic search (natural language query, requires OPENROUTER_API_KEY). False: keyword search (Boolean operators AND/OR/NOT).

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • The @app.tool() decorator registers 'search_within_tuzuk' as an MCP tool on the FastMCP server.
    @app.tool()
    async def search_within_tuzuk(
        mevzuat_no: str = Field(
            ...,
            description="The statute number to search within (e.g., '20135150', '20134513', '200814001')"
        ),
        keyword: str = Field(
            ...,
            description='Search query. For keyword mode: supports AND/OR/NOT operators (uppercase). For semantic mode: use natural language.'
        ),
        mevzuat_tertip: str = Field(
            "5",
            description="Statute series from search results (e.g., '5')"
        ),
        case_sensitive: bool = Field(
            False,
            description="Whether to match case when searching (default: False). Only used in keyword mode."
        ),
        max_results: int = Field(
            25,
            ge=1,
            le=50,
            description="Maximum number of matching articles to return (1-50, default: 25)"
        ),
        semantic: bool = Field(
            False,
            description="True: semantic search (natural language query, requires OPENROUTER_API_KEY). False: keyword search (Boolean operators AND/OR/NOT)."
        )
    ) -> str:
        """
        Search within a specific Statute's (Tüzük) articles using keyword or semantic search.
    
        Modes:
        - semantic=False (default): Keyword search with Boolean operators (AND/OR/NOT, uppercase required)
        - semantic=True: Natural language semantic search using AI embeddings (requires OPENROUTER_API_KEY)
    
        Keyword examples: "tapu AND sicil", '"sicil kayıt"', "tescil OR ilan"
        Semantic examples: "tapu sicil kayıt işlemleri", "vakıf tescil süreci"
        """
  • Handler function for 'search_within_tuzuk'. For semantic=True, delegates to _semantic_search_within with mevzuat_tur=2 (Tüzük). For keyword mode, fetches content via mevzuat_client.get_content with tur=2, then searches articles using search_articles_by_keyword, and formats results with format_search_results.
    async def search_within_tuzuk(
        mevzuat_no: str = Field(
            ...,
            description="The statute number to search within (e.g., '20135150', '20134513', '200814001')"
        ),
        keyword: str = Field(
            ...,
            description='Search query. For keyword mode: supports AND/OR/NOT operators (uppercase). For semantic mode: use natural language.'
        ),
        mevzuat_tertip: str = Field(
            "5",
            description="Statute series from search results (e.g., '5')"
        ),
        case_sensitive: bool = Field(
            False,
            description="Whether to match case when searching (default: False). Only used in keyword mode."
        ),
        max_results: int = Field(
            25,
            ge=1,
            le=50,
            description="Maximum number of matching articles to return (1-50, default: 25)"
        ),
        semantic: bool = Field(
            False,
            description="True: semantic search (natural language query, requires OPENROUTER_API_KEY). False: keyword search (Boolean operators AND/OR/NOT)."
        )
    ) -> str:
        """
        Search within a specific Statute's (Tüzük) articles using keyword or semantic search.
    
        Modes:
        - semantic=False (default): Keyword search with Boolean operators (AND/OR/NOT, uppercase required)
        - semantic=True: Natural language semantic search using AI embeddings (requires OPENROUTER_API_KEY)
    
        Keyword examples: "tapu AND sicil", '"sicil kayıt"', "tescil OR ilan"
        Semantic examples: "tapu sicil kayıt işlemleri", "vakıf tescil süreci"
        """
        logger.info(f"Tool 'search_within_tuzuk' called: {mevzuat_no}, keyword: '{keyword}', semantic: {semantic}")
    
        try:
            if semantic:
                if not SEMANTIC_SEARCH_AVAILABLE:
                    return "Error: Semantic search requires OPENROUTER_API_KEY environment variable."
                return await _semantic_search_within(
                    mevzuat_no=mevzuat_no, query=keyword, mevzuat_tur=2,
                    mevzuat_tertip=mevzuat_tertip, max_results=max_results
                )
    
            content_result = await mevzuat_client.get_content(
                mevzuat_no=mevzuat_no, mevzuat_tur=2, mevzuat_tertip=mevzuat_tertip
            )
            if content_result.error_message:
                return f"Error fetching statute content: {content_result.error_message}"
    
            matches = search_articles_by_keyword(
                markdown_content=content_result.markdown_content,
                keyword=keyword, case_sensitive=case_sensitive, max_results=max_results
            )
            result = ArticleSearchResult(
                mevzuat_no=mevzuat_no, mevzuat_tur=2, keyword=keyword,
                total_matches=len(matches), matching_articles=matches
            )
            if len(matches) == 0:
                return f"No articles found containing '{keyword}' in Tüzük {mevzuat_no}"
            return format_search_results(result)
    
        except Exception as e:
            logger.exception(f"Error in tool 'search_within_tuzuk' for {mevzuat_no}")
            return f"An unexpected error occurred while searching Tüzük {mevzuat_no}: {str(e)}"
  • Schema model used for search operations (MevzuatTurLiteral includes 'Tuzuk').
    class MevzuatSearchRequestNew(BaseModel):
        """Request model for searching legislation on mevzuat.gov.tr"""
    
        mevzuat_tur: MevzuatTurLiteral = Field(
            "Kanun",
            description="Type of legislation. Currently only 'Kanun' (laws) are fully supported for content extraction."
        )
    
        aranacak_ifade: Optional[str] = Field(
            None,
            description="Search term or phrase to look for in legislation"
        )
    
        aranacak_yer: int = Field(
            3,
            ge=1,
            le=3,
            description="Where to search: 1=Title only, 2=Article titles, 3=Full text (default)"
        )
    
        tam_cumle: bool = Field(
            False,
            description="Exact phrase match (true) or any word match (false, default)"
        )
    
        mevzuat_no: Optional[str] = Field(
            None,
            description="Specific legislation number to search for"
        )
    
        baslangic_tarihi: Optional[str] = Field(
            None,
            description="Start date for filtering (format: DD.MM.YYYY)"
        )
    
        bitis_tarihi: Optional[str] = Field(
            None,
            description="End date for filtering (format: DD.MM.YYYY)"
        )
    
        page_number: int = Field(
            1,
            ge=1,
            description="Page number of results"
        )
    
        page_size: int = Field(
            10,
            ge=1,
            le=100,
            description="Number of results per page"
        )
  • Shared helper function used for semantic search. The search_within_tuzuk handler calls this with mevzuat_tur=2 (Tuzuk type code). It fetches content, processes into chunks, builds a vector store, queries with the user's query, and returns formatted semantic results.
    async def _semantic_search_within(
        mevzuat_no: str,
        query: str,
        mevzuat_tur: int,
        mevzuat_tertip: str = "5",
        max_results: int = 10,
        threshold: float = 0.3,
        resmi_gazete_tarihi: Optional[str] = None,
    ) -> str:
        """Shared helper for semantic search within any legislation type."""
        # 1. Get content with tertip fallback (already cached by mevzuat_client)
        content_result = await _get_content_with_tertip_fallback(
            mevzuat_no=mevzuat_no,
            mevzuat_tur=mevzuat_tur,
            mevzuat_tertip=mevzuat_tertip,
            resmi_gazete_tarihi=resmi_gazete_tarihi,
        )
    
        if content_result.error_message:
            return f"Error fetching content: {content_result.error_message}"
    
        if not content_result.markdown_content:
            return f"Error: No content found for mevzuat {mevzuat_no}"
    
        content = content_result.markdown_content
    
        # 2. Check embedding cache
        cached = _embedding_cache.get(mevzuat_tur, mevzuat_tertip, mevzuat_no, content)
        if cached:
            vector_store, chunks = cached
        else:
            # 3. Process into chunks
            chunks = _processor.process_legislation(content, mevzuat_no, mevzuat_tur)
            if not chunks:
                return f"Error: Could not split content into searchable segments for mevzuat {mevzuat_no}"
    
            # 4. Encode documents
            texts = [c.text for c in chunks]
            titles = [c.title for c in chunks]
            embeddings = _embedder.encode_documents(texts, titles)
    
            # 5. Build vector store
            vector_store = VectorStore(dimension=_embedder.dimension)
            vector_store.add_documents(
                ids=[c.chunk_id for c in chunks],
                texts=texts,
                embeddings=embeddings,
                metadata=[c.metadata for c in chunks],
            )
    
            # 6. Cache
            _embedding_cache.put(mevzuat_tur, mevzuat_tertip, mevzuat_no, content, vector_store, chunks)
    
        # 7. Search
        query_embedding = _embedder.encode_query(query)
        results = vector_store.search(query_embedding, top_k=max_results, threshold=threshold)
    
        if not results:
            return f"No semantically similar content found for '{query}' in mevzuat {mevzuat_no}"
    
        # 8. Format results
        # Determine method description
        chunk_type = chunks[0].metadata.get('type', 'chunk') if chunks else 'chunk'
        method = "Article-based semantic search" if chunk_type == 'article' else "Chunk-based semantic search"
    
        output = []
        output.append("Semantic Search Results")
        output.append(f"Query: \"{query}\"")
        output.append(f"Legislation: {mevzuat_no} (type: {mevzuat_tur})")
        output.append(f"Method: {method} | Results: {len(results)}")
        output.append("")
    
        for doc, score in results:
            if chunk_type == 'article':
                madde_no = doc.metadata.get('madde_no', '?')
                madde_title = doc.metadata.get('madde_title', '')
                output.append(f"=== MADDE {madde_no} === (Similarity: {score:.2f})")
                if madde_title:
                    output.append(f"Title: {madde_title}")
            else:
                chunk_idx = doc.metadata.get('chunk_index', 0)
                total = doc.metadata.get('total_chunks', 0)
                output.append(f"=== Chunk {chunk_idx + 1}/{total} === (Similarity: {score:.2f})")
    
            output.append("")
            output.append(doc.text)
            output.append("")
    
        return "\n".join(output)
Behavior5/5

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

No annotations provided, so description fully covers behavior: two search modes, Boolean operators, case sensitivity, max results, API key requirement. Examples clarify usage.

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?

Concise, front-loaded, uses headings for modes. Every sentence adds value. Examples are well-structured.

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

Completeness5/5

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

Covers all aspects: required inputs, mode selection, parameter details, examples. With 6 parameters and no annotations, description is complete.

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?

Schema already describes all parameters (100% coverage). Description adds value with usage examples, operator uppercase requirement, and API key condition for semantic mode.

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 the tool searches within a specific statute's articles using keyword or semantic search. Distinguishes from siblings by specifying 'Tüzük' and requiring a statute number.

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?

Explains two modes and gives examples. Implicitly requires statute number (mevzuat_no). Does not explicitly compare to other tools like search_tuzuk, but provides context on mode selection.

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