diff --git a/src/mcp_server_appwrite/server.py b/src/mcp_server_appwrite/server.py index 8413705..2e82579 100644 --- a/src/mcp_server_appwrite/server.py +++ b/src/mcp_server_appwrite/server.py @@ -169,14 +169,23 @@ def load_appwrite_config() -> AppwriteConfig: return AppwriteConfig(project_id=project_id, api_key=api_key, endpoint=endpoint) +def _configure_mcp_client_headers(client: Client) -> Client: + current_user_agent = client._global_headers.get("user-agent", "") + suffix_start = current_user_agent.find(" ") + suffix = current_user_agent[suffix_start:] if suffix_start != -1 else "" + + client.add_header("x-sdk-name", "mcp") + client.add_header("user-agent", f"AppwriteMCP/{SERVER_VERSION}{suffix}") + return client + + def build_client(config: AppwriteConfig | None = None) -> Client: config = config or load_appwrite_config() client = Client() client.set_endpoint(config.endpoint) client.set_project(config.project_id) client.set_key(config.api_key) - client.add_header("x-sdk-name", "mcp") - return client + return _configure_mcp_client_headers(client) def build_introspection_client() -> Client: @@ -184,8 +193,7 @@ def build_introspection_client() -> Client: generation. It never makes API calls, so no project/key is required.""" client = Client() client.set_endpoint(os.getenv("APPWRITE_ENDPOINT", DEFAULT_ENDPOINT)) - client.add_header("x-sdk-name", "mcp") - return client + return _configure_mcp_client_headers(client) def build_client_for_request( @@ -215,7 +223,7 @@ def build_client_for_request( client.set_endpoint(endpoint or os.getenv("APPWRITE_ENDPOINT", DEFAULT_ENDPOINT)) client.set_project(target_project or project_id) client.add_header("Authorization", f"Bearer {bearer_token}") - client.add_header("x-sdk-name", "mcp") + client = _configure_mcp_client_headers(client) if target_project: client.add_header("x-appwrite-project", target_project) # Admin mode lets the console-issued token be recognized on another project diff --git a/tests/unit/test_server.py b/tests/unit/test_server.py index 0b646a5..c40c34b 100644 --- a/tests/unit/test_server.py +++ b/tests/unit/test_server.py @@ -22,7 +22,9 @@ _prepare_arguments, _validate_service, build_client, + build_client_for_request, build_instructions, + build_introspection_client, build_operator, parse_args, register_services, @@ -177,6 +179,39 @@ def test_build_client_loads_dotenv_from_current_working_directory(self): self.assertEqual(client._endpoint, "https://example.test/v1") self.assertEqual(client.get_config("project"), "test-project") self.assertEqual(client._global_headers["x-appwrite-key"], "test-key") + self.assert_mcp_client_headers(client) + + def assert_mcp_client_headers(self, client): + user_agent = client._global_headers["user-agent"] + + self.assertEqual(client._global_headers["x-sdk-name"], "mcp") + self.assertTrue( + user_agent.startswith(f"AppwriteMCP/{server_module.SERVER_VERSION}"), + user_agent, + ) + self.assertNotIn("AppwritePythonSDK", user_agent) + + def test_build_introspection_client_sets_mcp_headers(self): + client = build_introspection_client() + + self.assert_mcp_client_headers(client) + + def test_build_client_for_request_sets_mcp_headers_and_auth_context(self): + client = build_client_for_request( + "console", + "test-token", + endpoint="https://example.test/v1", + target_project="target-project", + organization_id="org-id", + ) + + self.assertEqual(client._endpoint, "https://example.test/v1") + self.assertEqual(client.get_config("project"), "target-project") + self.assertEqual(client._global_headers["authorization"], "Bearer test-token") + self.assertEqual(client._global_headers["x-appwrite-project"], "target-project") + self.assertEqual(client._global_headers["x-appwrite-mode"], "admin") + self.assertEqual(client._global_headers["x-appwrite-organization"], "org-id") + self.assert_mcp_client_headers(client) def test_coerce_enum_returns_raw_value_string(self): self.assertEqual(_coerce_argument("code", "ch", Browser), "ch")