From d043b9baf13a7961edbe77860c7cb0d8bba1c390 Mon Sep 17 00:00:00 2001 From: mariankrotil Date: Wed, 18 Feb 2026 15:53:05 +0100 Subject: [PATCH 1/3] AI-2573 test: set integtest mcp user-agent defaults --- integtests/conftest.py | 62 +++++++++++++++++++++++++++++++++++---- integtests/test_errors.py | 7 +++-- 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/integtests/conftest.py b/integtests/conftest.py index 10b1ab88..fb8e3f2f 100644 --- a/integtests/conftest.py +++ b/integtests/conftest.py @@ -1,4 +1,5 @@ import dataclasses +import importlib.metadata import json import logging import os @@ -14,12 +15,13 @@ from dotenv import load_dotenv from fastmcp import Client, Context, FastMCP from kbcstorage.client import Client as SyncStorageClient +from mcp.types import ClientCapabilities, Implementation, InitializeRequestParams from mcp.server.session import ServerSession from mcp.shared.context import RequestContext from keboola_mcp_server.clients.client import KeboolaClient from keboola_mcp_server.config import Config, ServerRuntimeInfo -from keboola_mcp_server.mcp import ServerState +from keboola_mcp_server.mcp import ServerState, SessionStateMiddleware from keboola_mcp_server.server import create_server from keboola_mcp_server.workspace import WorkspaceManager @@ -35,6 +37,10 @@ DEV_STORAGE_API_URL_ENV_VAR = 'STORAGE_API_URL' DEV_STORAGE_TOKEN_ENV_VAR = 'KBC_STORAGE_TOKEN' DEV_WORKSPACE_SCHEMA_ENV_VAR = 'KBC_WORKSPACE_SCHEMA' +INTEGTEST_CLIENT_INFO = Implementation( + name='integtest/mcp', version=importlib.metadata.version('keboola_mcp_server') +) +INTEGTEST_USER_AGENT = f'{INTEGTEST_CLIENT_INFO.name}/{INTEGTEST_CLIENT_INFO.version}' @dataclass(frozen=True) @@ -84,6 +90,44 @@ def env_file_loaded() -> bool: return load_dotenv() +@pytest.fixture(scope='session', autouse=True) +def _patch_fastmcp_client_default_info() -> Generator[None, None, None]: + # Ensure all fastmcp.Client instances in integration tests use a distinct identity + # unless a test intentionally provides a different client_info. + monkeypatch = pytest.MonkeyPatch() + original_init = Client.__init__ + + def _init_with_integtest_client_info(self, *args: Any, **kwargs: Any) -> None: + kwargs.setdefault('client_info', INTEGTEST_CLIENT_INFO) + original_init(self, *args, **kwargs) + + monkeypatch.setattr(Client, '__init__', _init_with_integtest_client_info) + try: + yield + finally: + monkeypatch.undo() + + +@pytest.fixture(scope='session', autouse=True) +def _patch_session_middleware_user_agent() -> Generator[None, None, None]: + # Force a distinct User-Agent for outbound Keboola API requests during integration tests. + monkeypatch = pytest.MonkeyPatch() + original_get_headers = SessionStateMiddleware._get_headers.__func__ + + def _get_headers_with_integtest_ua( + cls: type[SessionStateMiddleware], runtime_info: ServerRuntimeInfo + ) -> dict[str, Any]: + headers = original_get_headers(cls, runtime_info) + headers['User-Agent'] = INTEGTEST_USER_AGENT + return headers + + monkeypatch.setattr(SessionStateMiddleware, '_get_headers', classmethod(_get_headers_with_integtest_ua)) + try: + yield + finally: + monkeypatch.undo() + + @pytest.fixture(scope='session') def env_init(env_file_loaded: bool, storage_api_token: str, storage_api_url: str, workspace_schema: str) -> bool: # We reset the development environment variables to the values of the integtest environment variables. @@ -300,7 +344,11 @@ def sync_storage_client(storage_api_token: str, storage_api_url: str) -> SyncSto @pytest.fixture def keboola_client(sync_storage_client: SyncStorageClient) -> KeboolaClient: - return KeboolaClient(storage_api_token=sync_storage_client.token, storage_api_url=sync_storage_client.root_url) + return KeboolaClient( + storage_api_token=sync_storage_client.token, + storage_api_url=sync_storage_client.root_url, + headers={'User-Agent': INTEGTEST_USER_AGENT}, + ) @pytest.fixture @@ -332,8 +380,12 @@ def mcp_context( KeboolaClient.STATE_KEY: keboola_client, WorkspaceManager.STATE_KEY: workspace_manager, } - client_context.session.client_params = None - client_context.client_id = None + client_context.session.client_params = InitializeRequestParams( + protocolVersion='1', + capabilities=ClientCapabilities(), + clientInfo=INTEGTEST_CLIENT_INFO, + ) + client_context.client_id = INTEGTEST_USER_AGENT client_context.session_id = None client_context.request_context = mocker.MagicMock(RequestContext) client_context.request_context.lifespan_context = ServerState(mcp_config, ServerRuntimeInfo(transport='stdio')) @@ -351,5 +403,5 @@ def mcp_server(storage_api_url: str, storage_api_token: str, workspace_schema: s @pytest_asyncio.fixture async def mcp_client(mcp_server: FastMCP) -> AsyncGenerator[Client, None]: - async with Client(mcp_server) as client: + async with Client(mcp_server, client_info=INTEGTEST_CLIENT_INFO) as client: yield client diff --git a/integtests/test_errors.py b/integtests/test_errors.py index b22fa19c..af58e34f 100644 --- a/integtests/test_errors.py +++ b/integtests/test_errors.py @@ -9,8 +9,9 @@ import httpx import pytest from fastmcp import Context -from mcp.types import ClientCapabilities, Implementation, InitializeRequestParams +from mcp.types import ClientCapabilities, InitializeRequestParams +from integtests.conftest import INTEGTEST_CLIENT_INFO, INTEGTEST_USER_AGENT from keboola_mcp_server.clients.client import KeboolaClient from keboola_mcp_server.errors import tool_errors from keboola_mcp_server.mcp import CONVERSATION_ID, AggregateError @@ -116,7 +117,7 @@ async def test_event_emitted(self, tool_name: str, event_message: str, event_typ mcp_context.session.client_params = InitializeRequestParams( protocolVersion='1', capabilities=ClientCapabilities(), - clientInfo=Implementation(name='integtest', version='1.2.3'), + clientInfo=INTEGTEST_CLIENT_INFO, ) mcp_context.session.state[CONVERSATION_ID] = '#987654321' unique = uuid.uuid4().hex @@ -145,7 +146,7 @@ async def test_event_emitted(self, tool_name: str, event_message: str, event_typ assert emitted_event['params']['mcpServerContext'] == { 'appEnv': 'DEV', 'version': distribution('keboola_mcp_server').version, - 'userAgent': 'integtest/1.2.3', + 'userAgent': INTEGTEST_USER_AGENT, 'sessionId': 'deadbee', 'serverTransport': 'stdio', 'conversationId': '#987654321', From ff2e5c5e2c89c1fa96307ff2f2d6d9efef9d2815 Mon Sep 17 00:00:00 2001 From: mariankrotil Date: Wed, 18 Feb 2026 15:56:43 +0100 Subject: [PATCH 2/3] AI-2573 test: bump version to 1.44.7 --- pyproject.toml | 2 +- uv.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8929fa03..e3c7f14b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "keboola-mcp-server" -version = "1.44.6" +version = "1.44.7" description = "MCP server for interacting with Keboola Connection" readme = "README.md" requires-python = ">=3.10" diff --git a/uv.lock b/uv.lock index 7a0aad35..240c20f4 100644 --- a/uv.lock +++ b/uv.lock @@ -1223,7 +1223,7 @@ wheels = [ [[package]] name = "keboola-mcp-server" -version = "1.44.6" +version = "1.44.7" source = { editable = "." } dependencies = [ { name = "cryptography" }, From 480ba07a59d065e66f426515d72c33444369af43 Mon Sep 17 00:00:00 2001 From: mariankrotil Date: Wed, 18 Feb 2026 16:10:31 +0100 Subject: [PATCH 3/3] AI-2573 style: apply isort --- integtests/conftest.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/integtests/conftest.py b/integtests/conftest.py index fb8e3f2f..9161a65c 100644 --- a/integtests/conftest.py +++ b/integtests/conftest.py @@ -15,9 +15,9 @@ from dotenv import load_dotenv from fastmcp import Client, Context, FastMCP from kbcstorage.client import Client as SyncStorageClient -from mcp.types import ClientCapabilities, Implementation, InitializeRequestParams from mcp.server.session import ServerSession from mcp.shared.context import RequestContext +from mcp.types import ClientCapabilities, Implementation, InitializeRequestParams from keboola_mcp_server.clients.client import KeboolaClient from keboola_mcp_server.config import Config, ServerRuntimeInfo @@ -37,9 +37,7 @@ DEV_STORAGE_API_URL_ENV_VAR = 'STORAGE_API_URL' DEV_STORAGE_TOKEN_ENV_VAR = 'KBC_STORAGE_TOKEN' DEV_WORKSPACE_SCHEMA_ENV_VAR = 'KBC_WORKSPACE_SCHEMA' -INTEGTEST_CLIENT_INFO = Implementation( - name='integtest/mcp', version=importlib.metadata.version('keboola_mcp_server') -) +INTEGTEST_CLIENT_INFO = Implementation(name='integtest/mcp', version=importlib.metadata.version('keboola_mcp_server')) INTEGTEST_USER_AGENT = f'{INTEGTEST_CLIENT_INFO.name}/{INTEGTEST_CLIENT_INFO.version}'