diff --git a/.serena/.gitignore b/.serena/.gitignore new file mode 100644 index 0000000..14d86ad --- /dev/null +++ b/.serena/.gitignore @@ -0,0 +1 @@ +/cache diff --git a/.serena/project.yml b/.serena/project.yml new file mode 100644 index 0000000..db1e992 --- /dev/null +++ b/.serena/project.yml @@ -0,0 +1,84 @@ +# list of languages for which language servers are started; choose from: +# al bash clojure cpp csharp csharp_omnisharp +# dart elixir elm erlang fortran go +# haskell java julia kotlin lua markdown +# nix perl php python python_jedi r +# rego ruby ruby_solargraph rust scala swift +# terraform typescript typescript_vts yaml zig +# Note: +# - For C, use cpp +# - For JavaScript, use typescript +# Special requirements: +# - csharp: Requires the presence of a .sln file in the project folder. +# When using multiple languages, the first language server that supports a given file will be used for that file. +# The first language is the default language and the respective language server will be used as a fallback. +# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored. +languages: +- python + +# the encoding used by text files in the project +# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings +encoding: "utf-8" + +# whether to use the project's gitignore file to ignore files +# Added on 2025-04-07 +ignore_all_files_in_gitignore: true + +# list of additional paths to ignore +# same syntax as gitignore, so you can use * and ** +# Was previously called `ignored_dirs`, please update your config if you are using that. +# Added (renamed) on 2025-04-07 +ignored_paths: [] + +# whether the project is in read-only mode +# If set to true, all editing tools will be disabled and attempts to use them will result in an error +# Added on 2025-04-18 +read_only: false + +# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details. +# Below is the complete list of tools for convenience. +# To make sure you have the latest list of tools, and to view their descriptions, +# execute `uv run scripts/print_tool_overview.py`. +# +# * `activate_project`: Activates a project by name. +# * `check_onboarding_performed`: Checks whether project onboarding was already performed. +# * `create_text_file`: Creates/overwrites a file in the project directory. +# * `delete_lines`: Deletes a range of lines within a file. +# * `delete_memory`: Deletes a memory from Serena's project-specific memory store. +# * `execute_shell_command`: Executes a shell command. +# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced. +# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type). +# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type). +# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes. +# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file. +# * `initial_instructions`: Gets the initial instructions for the current project. +# Should only be used in settings where the system prompt cannot be set, +# e.g. in clients you have no control over, like Claude Desktop. +# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol. +# * `insert_at_line`: Inserts content at a given line in a file. +# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol. +# * `list_dir`: Lists files and directories in the given directory (optionally with recursion). +# * `list_memories`: Lists memories in Serena's project-specific memory store. +# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building). +# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context). +# * `read_file`: Reads a file within the project directory. +# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store. +# * `remove_project`: Removes a project from the Serena configuration. +# * `replace_lines`: Replaces a range of lines within a file with new content. +# * `replace_symbol_body`: Replaces the full definition of a symbol. +# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen. +# * `search_for_pattern`: Performs a search for a pattern in the project. +# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase. +# * `switch_modes`: Activates modes by providing a list of their names +# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information. +# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task. +# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed. +# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store. +excluded_tools: [] + +# initial prompt for the project. It will always be given to the LLM upon activating the project +# (contrary to the memories, which are loaded on demand). +initial_prompt: "" + +project_name: "yargi-mcp" +included_optional_tools: [] diff --git a/mcp_server_main.py b/mcp_server_main.py index 69b6847..69c2443 100644 --- a/mcp_server_main.py +++ b/mcp_server_main.py @@ -619,7 +619,7 @@ async def search_emsal_detailed_decisions( sort_direction: str = Field("desc", description="Sorting direction ('asc' or 'desc')."), page_number: int = Field(1, ge=1, description="Page number (accepts int)."), # page_size: int = Field(10, ge=1, le=10, description="Results per page.") -) -> CompactEmsalSearchResult: +) -> Dict[str, Any]: """Search Emsal precedent decisions with detailed criteria.""" page_size = 10 # Default value @@ -652,9 +652,9 @@ async def search_emsal_detailed_decisions( total_records=api_response.data.recordsTotal if api_response.data.recordsTotal is not None else 0, requested_page=search_query.page_number, page_size=search_query.page_size - ) + ).model_dump() logger.warning("API response for Emsal search did not contain expected data structure.") - return CompactEmsalSearchResult(decisions=[], total_records=0, requested_page=search_query.page_number, page_size=search_query.page_size) + return CompactEmsalSearchResult(decisions=[], total_records=0, requested_page=search_query.page_number, page_size=search_query.page_size).model_dump() except Exception as e: logger.exception(f"Error in tool 'search_emsal_detailed_decisions'.") raise @@ -666,12 +666,13 @@ async def search_emsal_detailed_decisions( "idempotentHint": True } ) -async def get_emsal_document_markdown(id: str) -> EmsalDocumentMarkdown: +async def get_emsal_document_markdown(id: str) -> Dict[str, Any]: """Get document as Markdown.""" logger.info(f"Tool 'get_emsal_document_markdown' called for ID: {id}") if not id or not id.strip(): raise ValueError("Document ID required for Emsal.") try: - return await emsal_client_instance.get_decision_document_as_markdown(id) + result = await emsal_client_instance.get_decision_document_as_markdown(id) + return result.model_dump() except Exception as e: logger.exception(f"Error in tool 'get_emsal_document_markdown'.") raise @@ -704,7 +705,7 @@ async def search_uyusmazlik_decisions( hepsi: str = Field("", description="Search for texts containing all specified words."), herhangi_birisi: str = Field("", description="Search for texts containing any of the specified words."), not_hepsi: str = Field("", description="Exclude texts containing these specified words.") -) -> UyusmazlikSearchResponse: +) -> Dict[str, Any]: """Search Court of Jurisdictional Disputes decisions.""" # Convert string literals to enums @@ -743,7 +744,8 @@ async def search_uyusmazlik_decisions( logger.info(f"Tool 'search_uyusmazlik_decisions' called.") try: - return await uyusmazlik_client_instance.search_decisions(search_params) + result = await uyusmazlik_client_instance.search_decisions(search_params) + return result.model_dump() except Exception as e: logger.exception(f"Error in tool 'search_uyusmazlik_decisions'.") raise @@ -757,13 +759,14 @@ async def search_uyusmazlik_decisions( ) async def get_uyusmazlik_document_markdown_from_url( document_url: str = Field(..., description="Full URL to the Uyuşmazlık Mahkemesi decision document from search results") -) -> UyusmazlikDocumentMarkdown: +) -> Dict[str, Any]: """Get Uyuşmazlık Mahkemesi decision as Markdown.""" logger.info(f"Tool 'get_uyusmazlik_document_markdown_from_url' called for URL: {str(document_url)}") if not document_url: raise ValueError("Document URL (document_url) is required for Uyuşmazlık document retrieval.") try: - return await uyusmazlik_client_instance.get_decision_document_as_markdown(str(document_url)) + result = await uyusmazlik_client_instance.get_decision_document_as_markdown(str(document_url)) + return result.model_dump() except Exception as e: logger.exception(f"Error in tool 'get_uyusmazlik_document_markdown_from_url'.") raise @@ -1027,7 +1030,7 @@ async def search_rekabet_kurumu_decisions( KararSayisi: str = Field("", description="Decision number (Karar Sayısı)."), KararTarihi: str = Field("", description="Decision date (Karar Tarihi), e.g., DD.MM.YYYY."), page: int = Field(1, ge=1, description="Page number to fetch for the results list.") -) -> RekabetSearchResult: +) -> Dict[str, Any]: """Search Competition Authority decisions.""" karar_turu_guid_enum = KARAR_TURU_ADI_TO_GUID_ENUM_MAP.get(KararTuru) @@ -1052,10 +1055,11 @@ async def search_rekabet_kurumu_decisions( logger.info(f"Tool 'search_rekabet_kurumu_decisions' called. Query: {search_query.model_dump_json(exclude_none=True, indent=2)}") try: - return await rekabet_client_instance.search_decisions(search_query) + result = await rekabet_client_instance.search_decisions(search_query) + return result.model_dump() except Exception as e: logger.exception("Error in tool 'search_rekabet_kurumu_decisions'.") - return RekabetSearchResult(decisions=[], retrieved_page_number=page, total_records_found=0, total_pages=0) + return RekabetSearchResult(decisions=[], retrieved_page_number=page, total_records_found=0, total_pages=0).model_dump() @app.tool( description="Get Competition Authority decision text in paginated Markdown format", @@ -1067,15 +1071,15 @@ async def search_rekabet_kurumu_decisions( async def get_rekabet_kurumu_document( karar_id: str = Field(..., description="GUID (kararId) of the Rekabet Kurumu decision. This ID is obtained from search results."), page_number: int = Field(1, ge=1, description="Requested page number for the Markdown content converted from PDF (1-indexed, accepts int). Default is 1.") -) -> RekabetDocument: +) -> Dict[str, Any]: """Get Competition Authority decision as paginated Markdown.""" logger.info(f"Tool 'get_rekabet_kurumu_document' called. Karar ID: {karar_id}, Markdown Page: {page_number}") current_page_to_fetch = page_number if page_number >= 1 else 1 try: - - return await rekabet_client_instance.get_decision_document(karar_id, page_number=current_page_to_fetch) + result = await rekabet_client_instance.get_decision_document(karar_id, page_number=current_page_to_fetch) + return result.model_dump() except Exception as e: logger.exception(f"Error in tool 'get_rekabet_kurumu_document'. Karar ID: {karar_id}") raise @@ -1371,10 +1375,10 @@ async def search_sayistay_unified( yargilama_dairesi: Literal["ALL", "1", "2", "3", "4", "5", "6", "7", "8"] = Field("ALL", description="Chamber selection (daire only)"), hesap_yili: str = Field("", description="Account year (daire only)"), web_karar_metni: str = Field("", description="Decision text search (daire only)") -) -> SayistayUnifiedSearchResult: +) -> Dict[str, Any]: """Search Sayıştay decisions across all three decision types with unified interface.""" logger.info(f"Tool 'search_sayistay_unified' called with decision_type={decision_type}") - + try: search_request = SayistayUnifiedSearchRequest( decision_type=decision_type, @@ -1397,7 +1401,8 @@ async def search_sayistay_unified( hesap_yili=hesap_yili, web_karar_metni=web_karar_metni ) - return await sayistay_unified_client_instance.search_unified(search_request) + result = await sayistay_unified_client_instance.search_unified(search_request) + return result.model_dump() except Exception as e: logger.exception("Error in tool 'search_sayistay_unified'") raise @@ -1413,15 +1418,16 @@ async def search_sayistay_unified( async def get_sayistay_document_unified( decision_id: str = Field(..., description="Decision ID from search_sayistay_unified results"), decision_type: Literal["genel_kurul", "temyiz_kurulu", "daire"] = Field(..., description="Decision type: genel_kurul, temyiz_kurulu, or daire") -) -> SayistayUnifiedDocumentMarkdown: +) -> Dict[str, Any]: """Get Sayıştay decision document as Markdown for any decision type.""" logger.info(f"Tool 'get_sayistay_document_unified' called for ID: {decision_id}, type: {decision_type}") - + if not decision_id or not decision_id.strip(): raise ValueError("Decision ID must be a non-empty string.") - + try: - return await sayistay_unified_client_instance.get_document_unified(decision_id, decision_type) + result = await sayistay_unified_client_instance.get_document_unified(decision_id, decision_type) + return result.model_dump() except Exception as e: logger.exception("Error in tool 'get_sayistay_document_unified'") raise @@ -1646,22 +1652,22 @@ async def search_kvkk_decisions( keywords: str = Field(..., description="Turkish keywords. Supports +required -excluded \"exact phrase\" operators"), page: int = Field(1, ge=1, le=50, description="Page number for results (1-50)."), # pageSize: int = Field(10, ge=1, le=20, description="Number of results per page (1-20).") -) -> KvkkSearchResult: +) -> Dict[str, Any]: """Search function for legal decisions.""" logger.info(f"KVKK search tool called with keywords: {keywords}") - + pageSize = 10 # Default value - + search_request = KvkkSearchRequest( keywords=keywords, page=page, pageSize=pageSize ) - + try: result = await kvkk_client_instance.search_decisions(search_request) logger.info(f"KVKK search completed. Found {len(result.decisions)} decisions on page {page}") - return result + return result.model_dump() except Exception as e: logger.exception(f"Error in KVKK search: {e}") # Return empty result on error @@ -1671,7 +1677,7 @@ async def search_kvkk_decisions( page=page, pageSize=pageSize, query=keywords - ) + ).model_dump() @app.tool( description="Get KVKK decision document in Markdown format with metadata extraction", @@ -1684,10 +1690,10 @@ async def search_kvkk_decisions( async def get_kvkk_document_markdown( decision_url: str = Field(..., description="KVKK decision URL from search results"), page_number: int = Field(1, ge=1, description="Page number for paginated Markdown content (1-indexed, accepts int). Default is 1 (first 5,000 characters).") -) -> KvkkDocumentMarkdown: +) -> Dict[str, Any]: """Get KVKK decision as paginated Markdown.""" logger.info(f"KVKK document retrieval tool called for URL: {decision_url}") - + if not decision_url or not decision_url.strip(): return KvkkDocumentMarkdown( source_url=HttpUrl("https://www.kvkk.gov.tr"), @@ -1700,7 +1706,7 @@ async def get_kvkk_document_markdown( total_pages=0, is_paginated=False, error_message="Decision URL is required and cannot be empty." - ) + ).model_dump() try: # Validate URL format @@ -1716,11 +1722,11 @@ async def get_kvkk_document_markdown( total_pages=0, is_paginated=False, error_message="Invalid KVKK decision URL format. URL must start with https://www.kvkk.gov.tr/" - ) - + ).model_dump() + result = await kvkk_client_instance.get_decision_document(decision_url, page_number or 1) logger.info(f"KVKK document retrieved successfully. Page {result.current_page}/{result.total_pages}, Content length: {len(result.markdown_chunk) if result.markdown_chunk else 0}") - return result + return result.model_dump() except Exception as e: logger.exception(f"Error retrieving KVKK document: {e}") @@ -1735,7 +1741,7 @@ async def get_kvkk_document_markdown( total_pages=0, is_paginated=False, error_message=f"Error retrieving KVKK document: {str(e)}" - ) + ).model_dump() # --- MCP Tools for BDDK (Banking Regulation Authority) --- @app.tool(