From 9c71af691814fbcaac6fd39372c1ad402579a965 Mon Sep 17 00:00:00 2001 From: AbdelStark Date: Wed, 11 Mar 2026 13:37:40 +0100 Subject: [PATCH] feat: add catalog overview MCP resource --- README.md | 3 ++ main.py | 2 + mcp_resources/__init__.py | 8 ++++ mcp_resources/catalog_overview.py | 63 +++++++++++++++++++++++++ tests/test_catalog_overview_resource.py | 37 +++++++++++++++ 5 files changed, 113 insertions(+) create mode 100644 mcp_resources/__init__.py create mode 100644 mcp_resources/catalog_overview.py create mode 100644 tests/test_catalog_overview_resource.py diff --git a/README.md b/README.md index e89b3bb..75d4445 100644 --- a/README.md +++ b/README.md @@ -321,6 +321,9 @@ The MCP server is built using the [official Python SDK for MCP servers and clien ## 🛠️ Available Tools The MCP server provides tools to interact with data.gouv.fr datasets and dataservices. +It also exposes a `Catalog Scope and Discovery Guide` MCP resource so clients can +understand that the server has live access to the current catalog and should search +before claiming a dataset is unavailable. **Note:** "Dataservices" are external third-party APIs (e.g., Adresse API, Sirene API) registered in the data.gouv.fr catalog. They are distinct from data.gouv.fr's own internal APIs (Main/Tabular/Metrics) which power this MCP server. diff --git a/main.py b/main.py index d9977bd..522bd4c 100644 --- a/main.py +++ b/main.py @@ -13,6 +13,7 @@ from helpers.matomo import track_matomo from helpers.sentry import init_sentry +from mcp_resources import register_resources from tools import register_tools init_sentry() @@ -59,6 +60,7 @@ stateless_http=True, ) register_tools(mcp) +register_resources(mcp) def with_monitoring( diff --git a/mcp_resources/__init__.py b/mcp_resources/__init__.py new file mode 100644 index 0000000..53e16b3 --- /dev/null +++ b/mcp_resources/__init__.py @@ -0,0 +1,8 @@ +from mcp.server.fastmcp import FastMCP + +from mcp_resources.catalog_overview import register_catalog_overview_resource + + +def register_resources(mcp: FastMCP) -> None: + """Register all MCP resources with the provided FastMCP instance.""" + register_catalog_overview_resource(mcp) diff --git a/mcp_resources/catalog_overview.py b/mcp_resources/catalog_overview.py new file mode 100644 index 0000000..f9b5afc --- /dev/null +++ b/mcp_resources/catalog_overview.py @@ -0,0 +1,63 @@ +from mcp.server.fastmcp import FastMCP + +from helpers import env_config + +CATALOG_OVERVIEW_URI = "datagouv://catalog-overview" + + +def _build_catalog_overview() -> str: + site_url = env_config.get_base_url("site") + data_env = env_config.get_base_url("datagouv_api") + + return "\n".join( + [ + "# data.gouv.fr Catalog Overview", + "", + "This MCP server does not ship a fixed, preloaded list of datasets.", + "It provides live read-only access to the current public catalog exposed by data.gouv.fr.", + "", + f"Current catalog site: {site_url}", + f"Current API base: {data_env}", + "", + "When a user asks whether data is available for a topic, search first before concluding that the data is unavailable.", + "", + "Recommended discovery workflow:", + "1. Use search_datasets for datasets and search_dataservices for APIs.", + "2. Use get_dataset_info or get_dataservice_info to inspect the result.", + "3. Use list_dataset_resources to enumerate files in a dataset.", + "4. Use get_resource_info to inspect file metadata and raw URLs.", + "5. Use query_resource_data to preview, filter, and sort tabular resources.", + "6. Use get_metrics for usage metrics when you need visits or downloads.", + "", + "Available tools:", + "- search_datasets: keyword search across the catalog.", + "- get_dataset_info: detailed metadata for one dataset.", + "- list_dataset_resources: list all resources for a dataset.", + "- get_resource_info: inspect one resource and its access options.", + "- query_resource_data: query CSV/XLSX-like tabular resources through the Tabular API.", + "- search_dataservices: search third-party APIs listed on data.gouv.fr.", + "- get_dataservice_info: inspect one dataservice.", + "- get_dataservice_openapi_spec: fetch and summarize a dataservice OpenAPI spec.", + "- get_metrics: retrieve dataset or resource usage metrics.", + "", + "Example questions this server should answer by searching first:", + '- "Do you have datasets about housing prices in Paris?"', + '- "Are there resources about EV charging stations?"', + '- "What data is available for the Assemblee nationale?"', + ] + ) + + +def register_catalog_overview_resource(mcp: FastMCP) -> None: + @mcp.resource( + CATALOG_OVERVIEW_URI, + name="catalog_overview", + title="Catalog Scope and Discovery Guide", + description=( + "Explains what this server can access on data.gouv.fr and how to " + "discover datasets before claiming data is unavailable." + ), + mime_type="text/markdown", + ) + def catalog_overview() -> str: + return _build_catalog_overview() diff --git a/tests/test_catalog_overview_resource.py b/tests/test_catalog_overview_resource.py new file mode 100644 index 0000000..90bac7f --- /dev/null +++ b/tests/test_catalog_overview_resource.py @@ -0,0 +1,37 @@ +import pytest + +from main import mcp +from mcp_resources.catalog_overview import CATALOG_OVERVIEW_URI + + +@pytest.mark.asyncio +async def test_catalog_overview_resource_is_listed(): + resources = await mcp.list_resources() + + resource = next( + (item for item in resources if str(item.uri) == CATALOG_OVERVIEW_URI), None + ) + + assert resource is not None + assert resource.name == "catalog_overview" + assert resource.title == "Catalog Scope and Discovery Guide" + assert resource.mimeType == "text/markdown" + assert resource.description is not None + + +@pytest.mark.asyncio +async def test_catalog_overview_resource_content_uses_current_environment(monkeypatch): + monkeypatch.setenv("DATAGOUV_API_ENV", "demo") + + contents = list(await mcp.read_resource(CATALOG_OVERVIEW_URI)) + + assert len(contents) == 1 + content = contents[0].content + assert isinstance(content, str) + assert ( + "This MCP server does not ship a fixed, preloaded list of datasets." in content + ) + assert "Current catalog site: https://demo.data.gouv.fr/" in content + assert "search_datasets" in content + assert "search_dataservices" in content + assert "query_resource_data" in content