This is an automated email from the ASF dual-hosted git repository.
yuqi1129 pushed a commit to branch branch-1.3
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/branch-1.3 by this push:
new ef73430589 [Cherry-pick to branch-1.3] [#13120] fix(mcp): return tag
details when listing metadata tags (#13121) (#13129)
ef73430589 is described below
commit ef734305891c32257c0b5231fcb8371bc5c7b712
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Mon Sep 14 16:54:05 2026 +0800
[Cherry-pick to branch-1.3] [#13120] fix(mcp): return tag details when
listing metadata tags (#13121) (#13129)
**Cherry-pick Information:**
- Original commit: a880b41eef8a1ffc33e1db41ce102e99b8842612
- Target branch: `branch-1.3`
- Status: ✅ Clean cherry-pick (no conflicts)
Co-authored-by: Qi Yu <[email protected]>
---
.../plain/plain_rest_client_tag_operation.py | 2 +-
mcp-server/tests/unit/client/test_tag_operation.py | 130 +++++++++++++++++++++
2 files changed, 131 insertions(+), 1 deletion(-)
diff --git
a/mcp-server/mcp_server/client/plain/plain_rest_client_tag_operation.py
b/mcp-server/mcp_server/client/plain/plain_rest_client_tag_operation.py
index b5fabf9cb1..880d65b91b 100644
--- a/mcp-server/mcp_server/client/plain/plain_rest_client_tag_operation.py
+++ b/mcp-server/mcp_server/client/plain/plain_rest_client_tag_operation.py
@@ -98,7 +98,7 @@ class PlainRESTClientTagOperation(TagOperation):
f"/objects/{encode_path_segment(metadata_type)}"
f"/{encode_path_segment(metadata_full_name)}/tags?details=true"
)
- return extract_content_from_response(response, "names", [])
+ return extract_content_from_response(response, "tags", [])
async def list_metadata_by_tag(self, tag_name: str) -> str:
response = await self.rest_client.get(
diff --git a/mcp-server/tests/unit/client/test_tag_operation.py
b/mcp-server/tests/unit/client/test_tag_operation.py
new file mode 100644
index 0000000000..0298b35f17
--- /dev/null
+++ b/mcp-server/tests/unit/client/test_tag_operation.py
@@ -0,0 +1,130 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+"""Regression tests for the metadata tag list REST response contract."""
+
+import json
+import unittest
+from unittest.mock import patch
+
+import httpx
+from fastmcp import Client
+
+from mcp_server.client.factory import RESTClientFactory
+from mcp_server.client.plain.exception import GravitinoException
+from mcp_server.client.plain.plain_rest_client_tag_operation import (
+ PlainRESTClientTagOperation,
+)
+from mcp_server.core import Setting
+from mcp_server.server import GravitinoMCPServer
+from tests.unit.tools import MockOperation
+
+
+class TestTagOperation(unittest.IsolatedAsyncioTestCase):
+ def setUp(self):
+ self.tags = [
+ {
+ "name": "firstTag",
+ "comment": "Inherited governance tag",
+ "properties": {"classification": "internal"},
+ "audit": {
+ "creator": "admin",
+ "createTime": "2026-09-13T10:00:00Z",
+ },
+ "inherited": True,
+ },
+ {
+ "name": "tableTag",
+ "comment": "Direct table tag",
+ "properties": {},
+ "audit": {
+ "creator": "admin",
+ "createTime": "2026-09-13T11:00:00Z",
+ },
+ "inherited": False,
+ },
+ ]
+
+ def _client(self, body):
+ def respond(request):
+ self.assertEqual(request.method, "GET")
+ self.assertEqual(
+ request.url.path,
+
"/api/metalakes/acme/objects/table/iceberg_s3.sales.orders/tags",
+ )
+ self.assertEqual(dict(request.url.params), {"details": "true"})
+ return httpx.Response(200, json=body)
+
+ return httpx.AsyncClient(
+ base_url="http://localhost:8090",
+ transport=httpx.MockTransport(respond),
+ )
+
+ async def test_list_tags_preserves_complete_tag_details(self):
+ async with self._client({"code": 0, "tags": self.tags}) as client:
+ operation = PlainRESTClientTagOperation("acme", client)
+ result = await operation.list_tags_for_metadata(
+ "iceberg_s3.sales.orders", "table"
+ )
+ self.assertEqual(json.loads(result), self.tags)
+
+ async def test_list_tags_without_associations(self):
+ async with self._client({"code": 0, "tags": []}) as client:
+ operation = PlainRESTClientTagOperation("acme", client)
+ result = await operation.list_tags_for_metadata(
+ "iceberg_s3.sales.orders", "table"
+ )
+ self.assertEqual(json.loads(result), [])
+
+ async def test_list_tags_propagates_server_error(self):
+ body = {
+ "code": 1003,
+ "type": "NoSuchMetadataObjectException",
+ "message": "Metadata object does not exist",
+ }
+ async with self._client(body) as client:
+ operation = PlainRESTClientTagOperation("acme", client)
+ with self.assertRaisesRegex(
+ GravitinoException, "Metadata object does not exist"
+ ):
+ await operation.list_tags_for_metadata(
+ "iceberg_s3.sales.orders", "table"
+ )
+
+ async def test_mcp_tool_returns_rest_tag_details(self):
+ async with self._client({"code": 0, "tags": self.tags}) as rest_client:
+ operation = PlainRESTClientTagOperation("acme", rest_client)
+ with (
+ patch.object(
+ RESTClientFactory, "_rest_client_class", MockOperation
+ ),
+ patch.object(
+ MockOperation, "as_tag_operation", return_value=operation
+ ),
+ ):
+ server = GravitinoMCPServer(Setting("acme"))
+ async with Client(server.mcp) as client:
+ result = await client.call_tool(
+ "list_tags_for_metadata",
+ {
+ "metadata_full_name": "iceberg_s3.sales.orders",
+ "metadata_type": "table",
+ },
+ )
+ self.assertEqual(
+ json.loads(result.content[0].text), self.tags
+ )