Skip to content
Merged
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
18 changes: 13 additions & 5 deletions src/mcp_server_appwrite/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,23 +169,31 @@ 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:
"""A credential-less client used only to introspect SDK methods for schema
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(
Expand Down Expand Up @@ -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
Expand Down
35 changes: 35 additions & 0 deletions tests/unit/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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")
Expand Down
Loading