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
+                    )

Reply via email to