Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
2 changes: 2 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -59,6 +60,7 @@
stateless_http=True,
)
register_tools(mcp)
register_resources(mcp)


def with_monitoring(
Expand Down
8 changes: 8 additions & 0 deletions mcp_resources/__init__.py
Original file line number Diff line number Diff line change
@@ -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)
63 changes: 63 additions & 0 deletions mcp_resources/catalog_overview.py
Original file line number Diff line number Diff line change
@@ -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()
37 changes: 37 additions & 0 deletions tests/test_catalog_overview_resource.py
Original file line number Diff line number Diff line change
@@ -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