This is an automated email from the ASF dual-hosted git repository.
fanng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push:
new 7069bbd3b7 [#8093] improvement(mcp-server): Use pylint to format code
in mcp-server (#8094)
7069bbd3b7 is described below
commit 7069bbd3b71e173d0a5dddcaede33c2b312493a5
Author: Mini Yu <[email protected]>
AuthorDate: Fri Aug 15 10:09:18 2025 +0800
[#8093] improvement(mcp-server): Use pylint to format code in mcp-server
(#8094)
### What changes were proposed in this pull request?
Use pylint to format code in mcp-server.
### Why are the changes needed?
For high-quality code.
Fix: #8093
### Does this PR introduce _any_ user-facing change?
N/A.
### How was this patch tested?
Test locally and CI
---
mcp-server/build.gradle.kts | 6 ++
mcp-server/mcp_server/client/fileset_operation.py | 1 +
.../client/plain/exception.py} | 24 +-----
.../plain/plain_rest_client_fileset_operation.py | 4 +-
.../plain/plain_rest_client_model_operation.py | 9 +-
.../plain/plain_rest_client_tag_operation.py | 5 +-
mcp-server/mcp_server/client/plain/utils.py | 6 +-
mcp-server/mcp_server/client/tag_operation.py | 16 ++--
mcp-server/mcp_server/main.py | 9 +-
mcp-server/mcp_server/server.py | 3 +-
mcp-server/mcp_server/tools/catalog.py | 19 +++--
mcp-server/mcp_server/tools/fileset.py | 1 +
mcp-server/mcp_server/tools/model.py | 3 +-
mcp-server/mcp_server/tools/table.py | 38 ++++++++-
mcp-server/pylintrc | 95 ++++++++++++++++++++++
mcp-server/pyproject.toml | 1 +
mcp-server/tests/unit/tools/mock_operation.py | 14 ++--
mcp-server/tests/unit/tools/test_catalog.py | 5 ++
mcp-server/uv.lock | 85 ++++++++++++++++++-
19 files changed, 285 insertions(+), 59 deletions(-)
diff --git a/mcp-server/build.gradle.kts b/mcp-server/build.gradle.kts
index 14e416a5a6..0aa3dd8d0f 100644
--- a/mcp-server/build.gradle.kts
+++ b/mcp-server/build.gradle.kts
@@ -250,6 +250,11 @@ tasks {
}
}
+tasks.register<Exec>("pylint") {
+ mustRunAfter("buildPython")
+ commandLine(venvPython, "-m", "pylint", "./tests", "./mcp_server")
+}
+
tasks.named("test") {
val skipUTs = project.hasProperty("skipTests")
if (!skipUTs) {
@@ -259,6 +264,7 @@ tasks.named("test") {
tasks.named("build") {
dependsOn("buildPython")
+ dependsOn("pylint")
}
tasks.named("clean") {
diff --git a/mcp-server/mcp_server/client/fileset_operation.py
b/mcp-server/mcp_server/client/fileset_operation.py
index 9350bc128d..e41b3ec74f 100644
--- a/mcp-server/mcp_server/client/fileset_operation.py
+++ b/mcp-server/mcp_server/client/fileset_operation.py
@@ -56,6 +56,7 @@ class FilesetOperation(ABC):
"""
pass
+ # pylint: disable=too-many-positional-arguments
@abstractmethod
async def list_files_in_fileset(
self,
diff --git a/mcp-server/pyproject.toml
b/mcp-server/mcp_server/client/plain/exception.py
similarity index 64%
copy from mcp-server/pyproject.toml
copy to mcp-server/mcp_server/client/plain/exception.py
index 5c66261caa..25b085778a 100644
--- a/mcp-server/pyproject.toml
+++ b/mcp-server/mcp_server/client/plain/exception.py
@@ -15,26 +15,8 @@
# specific language governing permissions and limitations
# under the License.
-[project]
-name = "gravitino_mcp_server"
-version = "1.0.0"
-description = "Gravitino MCP server"
-readme = "README.md"
-requires-python = ">=3.10"
-dependencies = [
- "fastmcp>=2.10.6",
- "parameterized>=0.9.0",
- "pytest>=8.4.1",
-]
-[tool.isort]
-profile = "black"
-line_length = 80
-multi_line_output = 3
-include_trailing_comma = true
-force_grid_wrap = 0
-use_parentheses = true
+class GravitinoException(Exception):
+ """Custom exception for Gravitino-related errors."""
-[tool.black]
-line-length = 80
-target-version = ['py38']
+ pass
diff --git
a/mcp-server/mcp_server/client/plain/plain_rest_client_fileset_operation.py
b/mcp-server/mcp_server/client/plain/plain_rest_client_fileset_operation.py
index 3d53be529e..9548d7aab2 100644
--- a/mcp-server/mcp_server/client/plain/plain_rest_client_fileset_operation.py
+++ b/mcp-server/mcp_server/client/plain/plain_rest_client_fileset_operation.py
@@ -39,6 +39,7 @@ class PlainRESTClientFilesetOperation(FilesetOperation):
)
return response.json().get("fileset", {})
+ # pylint: disable=too-many-positional-arguments
async def list_files_in_fileset(
self,
catalog_name: str,
@@ -48,6 +49,7 @@ class PlainRESTClientFilesetOperation(FilesetOperation):
sub_path: str = "/",
) -> str:
response = await self.rest_client.get(
-
f"/api/metalakes/{self.metalake_name}/catalogs/{catalog_name}/schemas/{schema_name}/filesets/{fileset_name}/files?sub_path={sub_path}&location_name={location_name}"
+
f"/api/metalakes/{self.metalake_name}/catalogs/{catalog_name}/schemas/{schema_name}"
+
f"/filesets/{fileset_name}/files?sub_path={sub_path}&location_name={location_name}"
)
return response.json().get("files", [])
diff --git
a/mcp-server/mcp_server/client/plain/plain_rest_client_model_operation.py
b/mcp-server/mcp_server/client/plain/plain_rest_client_model_operation.py
index 3b63c63d75..266865973b 100644
--- a/mcp-server/mcp_server/client/plain/plain_rest_client_model_operation.py
+++ b/mcp-server/mcp_server/client/plain/plain_rest_client_model_operation.py
@@ -47,7 +47,8 @@ class PlainRESTClientModelOperation(ModelOperation):
self, catalog_name: str, schema_name: str, model_name: str
) -> str:
response = await self.rest_client.get(
-
f"/api/metalakes/{self.metalake_name}/catalogs/{catalog_name}/schemas/{schema_name}/models/{model_name}/versions?details=true"
+
f"/api/metalakes/{self.metalake_name}/catalogs/{catalog_name}/schemas/{schema_name}/models/{model_name}"
+ f"/versions?details=true"
)
return response.json().get("infos", [])
@@ -55,7 +56,8 @@ class PlainRESTClientModelOperation(ModelOperation):
self, catalog_name: str, schema_name: str, model_name: str, version:
int
) -> str:
response = await self.rest_client.get(
-
f"/api/metalakes/{self.metalake_name}/catalogs/{catalog_name}/schemas/{schema_name}/models/{model_name}/versions/{version}"
+
f"/api/metalakes/{self.metalake_name}/catalogs/{catalog_name}/schemas/{schema_name}/models/{model_name}"
+ f"/versions/{version}"
)
return response.json().get("modelVersion", {})
@@ -63,6 +65,7 @@ class PlainRESTClientModelOperation(ModelOperation):
self, catalog_name: str, schema_name: str, model_name: str, alias: str
) -> str:
response = await self.rest_client.get(
-
f"/api/metalakes/{self.metalake_name}/catalogs/{catalog_name}/schemas/{schema_name}/models/{model_name}/aliases/{alias}"
+
f"/api/metalakes/{self.metalake_name}/catalogs/{catalog_name}/schemas/{schema_name}/models/{model_name}/"
+ f"aliases/{alias}"
)
return response.json().get("modelVersion", {})
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 13493307aa..cd3a67bcb0 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
@@ -15,6 +15,7 @@
# specific language governing permissions and limitations
# under the License.
+from mcp_server.client.plain.exception import GravitinoException
from mcp_server.client.plain.utils import extract_content_from_response
from mcp_server.client.tag_operation import TagOperation
@@ -63,7 +64,9 @@ class PlainRESTClientTagOperation(TagOperation):
f"/api/metalakes/{self.metalake_name}/tags/{name}"
)
if response.status_code != 200:
- raise Exception(f"Failed to delete tag {name}: {response.text}")
+ raise GravitinoException(
+ f"Failed to delete tag {name}: {response.text}"
+ )
return None
async def associate_tag_with_metadata(
diff --git a/mcp-server/mcp_server/client/plain/utils.py
b/mcp-server/mcp_server/client/plain/utils.py
index 6d93d8813e..ab0d197229 100644
--- a/mcp-server/mcp_server/client/plain/utils.py
+++ b/mcp-server/mcp_server/client/plain/utils.py
@@ -18,6 +18,8 @@
import json
import logging
+from mcp_server.client.plain.exception import GravitinoException
+
def extract_content_from_response(response, field: str, default="") -> str:
response_json = response.json()
@@ -31,5 +33,5 @@ def _handle_gravitino_exception(response: dict):
t = response.get("type", "")
message = response.get("message", "")
error_message = f"Error code: {error_code}, Error type: {t}, Error
message: {message}"
- logging.warn(error_message)
- raise Exception(error_message)
+ logging.warning(error_message)
+ raise GravitinoException(error_message)
diff --git a/mcp-server/mcp_server/client/tag_operation.py
b/mcp-server/mcp_server/client/tag_operation.py
index 613dda7f5a..b00a06b8f4 100644
--- a/mcp-server/mcp_server/client/tag_operation.py
+++ b/mcp-server/mcp_server/client/tag_operation.py
@@ -25,15 +25,15 @@ class TagOperation(ABC):
@abstractmethod
async def create_tag(
- self, name: str, comment: str, properties: dict
+ self, tag_name: str, tag_comment: str, tag_properties: dict
) -> str:
"""
Create a new tag within the specified metalake.
Args:
- name: Name of the tag to be created
- comment: Description or comment for the tag
- properties: Dictionary of key-value pairs representing tag
properties
+ tag_name: Name of the tag to be created
+ tag_comment: Description or comment for the tag
+ tag_properties: Dictionary of key-value pairs representing tag
properties
Returns:
str: JSON-formatted string containing the created tag information
@@ -41,12 +41,12 @@ class TagOperation(ABC):
pass
@abstractmethod
- async def get_tag_by_name(self, name: str) -> str:
+ async def get_tag_by_name(self, tag_name: str) -> str:
"""
Load a tag by its name.
Args:
- name: Name of the tag to get
+ tag_name: Name of the tag to get
Returns:
str: JSON-formatted string containing the tag information
@@ -64,12 +64,12 @@ class TagOperation(ABC):
pass
@abstractmethod
- async def alter_tag(self, name: str, updates: list) -> str:
+ async def alter_tag(self, tag_name: str, updates: list) -> str:
"""
Alter an existing tag within the specified metalake.
Args:
- name: Name of the tag to be altered
+ tag_name: Name of the tag to be altered
updates: List of update operations to be applied to the tag
Returns:
diff --git a/mcp-server/mcp_server/main.py b/mcp-server/mcp_server/main.py
index 6b7a80d758..c325ea17c4 100644
--- a/mcp-server/mcp_server/main.py
+++ b/mcp-server/mcp_server/main.py
@@ -32,7 +32,7 @@ def do_main():
mcp_url=args.mcp_url,
)
_init_logging(setting)
- logging.info(f"Gravitino MCP server setting: {setting}")
+ logging.info("Gravitino MCP server setting: %s", setting)
server = GravitinoMCPServer(setting)
server.run()
@@ -47,7 +47,6 @@ def _init_logging(setting: Setting):
def _comma_separated_set(value) -> set:
- print(f"value={value}")
if not value:
return set()
return set(item.strip() for item in value.split(",") if item.strip())
@@ -76,7 +75,8 @@ def _parse_args():
"--include-tool-tags",
type=_comma_separated_set,
default=set(),
- help="The tool tags to include, separated by commas, support
tags:[catalog, schema, table]. (default: empty, all tools will be included).",
+ help="The tool tags to include, separated by commas, support
tags:[catalog, schema, table]. "
+ "(default: empty, all tools will be included).",
)
parser.add_argument(
@@ -84,7 +84,8 @@ def _parse_args():
type=str,
choices=["stdio", "http"],
default=DefaultSetting.default_transport,
- help=f"Transport protocol type: stdio (local), http (Streamable HTTP).
(default: {DefaultSetting.default_transport})",
+ help=f"Transport protocol type: stdio (local), http (Streamable HTTP).
"
+ f"(default: {DefaultSetting.default_transport})",
)
parser.add_argument(
diff --git a/mcp-server/mcp_server/server.py b/mcp-server/mcp_server/server.py
index 3020716af5..9cce5f556b 100644
--- a/mcp-server/mcp_server/server.py
+++ b/mcp-server/mcp_server/server.py
@@ -17,7 +17,6 @@
import asyncio
import logging
-import re
from contextlib import asynccontextmanager
from typing import AsyncIterator
from urllib.parse import urlparse
@@ -38,7 +37,7 @@ def _create_lifespan_manager(gravitino_context:
GravitinoContext):
@asynccontextmanager
async def app_lifespan(server: FastMCP) -> AsyncIterator[GravitinoContext]:
- logging.info(f"Add Gravitino context: {gravitino_context}")
+ logging.info("Add Gravitino context: %s", gravitino_context)
yield gravitino_context
return app_lifespan
diff --git a/mcp-server/mcp_server/tools/catalog.py
b/mcp-server/mcp_server/tools/catalog.py
index dd025f4d94..50115610e8 100644
--- a/mcp-server/mcp_server/tools/catalog.py
+++ b/mcp-server/mcp_server/tools/catalog.py
@@ -34,16 +34,16 @@ def load_catalog_tools(mcp: FastMCP):
str: A JSON string representing an array of catalog objects with
the following structure:
[
{
- "name": "catalog-name", # A unique name for
the catalog
- "type": "catalog-type", # Type of catalog
(e.g., "relational", "fileset", "message", "model")
- "provider": "provider-name", # The catalog
provider. One catalog type may have different providers, for "relational"
catalog type, the provider may be "jdbc-postgresql", "jdbc-mysql",
"lakehouse-iceberg", etc.
- "comment": "description-text", # Human-readable
description
- "properties": { # Configuration
properties, the property key may differ for different catalog types and
providers
+ "name": "catalog-name",
+ "type": "catalog-type",
+ "provider": "provider-name",
+ "comment": "description-text",
+ "properties": {
"key1": "value1",
"key2": "value2",
...
},
- "audit": { # Audit metadata
+ "audit": {
"creator": "creator-name",
"createTime": "ISO-8601-timestamp",
"lastModifier": "modifier-name",
@@ -53,6 +53,13 @@ def load_catalog_tools(mcp: FastMCP):
...
]
+ name: The unique name of the catalog.
+ type: The type of the catalog, such as "relational",
"fileset", "message", or "model".
+ provider: The specific provider of the catalog, which can vary
based on the catalog type.
+ comment: A human-readable description of the catalog.
+ properties: A dictionary of configuration properties for the
catalog,
+ which may vary based on the catalog type and
provider.
+
Example Return Value:
[
{
diff --git a/mcp-server/mcp_server/tools/fileset.py
b/mcp-server/mcp_server/tools/fileset.py
index 2bfc53734d..27400de207 100644
--- a/mcp-server/mcp_server/tools/fileset.py
+++ b/mcp-server/mcp_server/tools/fileset.py
@@ -96,6 +96,7 @@ def load_fileset_tools(mcp: FastMCP):
catalog_name, schema_name, fileset_name
)
+ # pylint:disable=too-many-positional-arguments
@mcp.tool(tags={"fileset"})
async def list_files_in_fileset(
ctx: Context,
diff --git a/mcp-server/mcp_server/tools/model.py
b/mcp-server/mcp_server/tools/model.py
index 9e3ac18dc7..08e56343da 100644
--- a/mcp-server/mcp_server/tools/model.py
+++ b/mcp-server/mcp_server/tools/model.py
@@ -121,7 +121,8 @@ def load_model_tools(mcp: FastMCP):
model_name (str): The name of the model to list versions for.
Returns:
- str: A JSON string containing model version information. The
string represents an array of version information.
+ str: A JSON string containing model version information. The string
+ represents an array of version information.
Example Return Value:
[
diff --git a/mcp-server/mcp_server/tools/table.py
b/mcp-server/mcp_server/tools/table.py
index 5b7f1836dc..af6c62da6f 100644
--- a/mcp-server/mcp_server/tools/table.py
+++ b/mcp-server/mcp_server/tools/table.py
@@ -109,7 +109,7 @@ def load_table_tools(mcp: FastMCP):
},
...
],
- "properties": { # Table-specific
properties, different catalog provider may have different properties.
+ "properties": {
"key1": "value1",
"key2": "value2",
...
@@ -148,6 +148,31 @@ def load_table_tools(mcp: FastMCP):
...
]
}
+ name: The unique name of the table.
+ comment: A human-readable description of the table.
+ columns: A list of column definitions, each containing:
+ - name: The name of the column.
+ - type: The data type of the column (e.g., string,
integer).
+ - comment: A description of the column.
+ - nullable: Indicates whether the column can contain
null values.
+ - autoIncrement: Indicates whether the column is
auto-incrementing.
+ properties: A dictionary of table-specific properties, which
may vary based on the catalog provider.
+ audit: Metadata about the table's creation, including:
+ - creator: The name of the user who created the table.
+ - createTime: The timestamp when the table was
created, in ISO-8601 format.
+ distribution: Information about how the data is distributed
across files/parts, including:
+ - strategy: The distribution strategy used (e.g.,
hash, range).
+ - number: The number of buckets or parts created.
+ sortOrders: A list of sorting specifications for the table,
each containing:
+ - sortTerm: The term used for sorting, which can be a
field or an expression.
+ - direction: The sorting direction (ascending or
descending).
+ - nullOrdering: How null values are ordered (nulls
first or last).
+
+ partitioning: A list of partitioning strategies used for the
table, each containing:
+ - strategy: The partitioning strategy used (e.g.,
identity, bucket[N], truncate[W], list,
+ range, func).
+ - fieldName: The field(s) used for partitioning,
specified as a list of strings.
+ indexes: A list of table indexes, such as primary keys or
unique keys.
Example Return Value:
{
@@ -217,8 +242,15 @@ def load_table_tools(mcp: FastMCP):
Special Considerations:
- The catalog type which containing the table must be "relational"
- The table in different catalog provider may have different
properties
- - "distribution" a.k.a (Clustering) is a technique to split the
data into more manageable files/parts, (By specifying the number of buckets to
create). The value of the distribution column will be hashed by a user-defined
number into buckets. Supporting "hash", "range", "even", etc distribution
strategies.
- - "partitioning" is a partitioning strategy that is used to split
a table into parts based on partition keys. Supporting diverse partitioning
strategies like "identity", "bucket[N]", "truncate[W]", "list", "range",
"func", etc.
+ - "distribution" a.k.a (Clustering) is a technique to split the
data
+ into more manageable files/parts, (By specifying the number of
+ buckets to create). The value of the distribution column will
be
+ hashed by a user-defined number into buckets. Supporting
"hash",
+ "range", "even", etc distribution strategies.
+ - "partitioning" is a partitioning strategy that is used to split a
+ table into parts based on partition keys. Supporting diverse
+ partitioning strategies like "identity", "bucket[N]",
"truncate[W]",
+ "list", "range", "func", etc.
- "indexes" represents the table index, such as primary key or
unique key.
"""
client = ctx.request_context.lifespan_context.rest_client()
diff --git a/mcp-server/pylintrc b/mcp-server/pylintrc
new file mode 100644
index 0000000000..21bfe53e53
--- /dev/null
+++ b/mcp-server/pylintrc
@@ -0,0 +1,95 @@
+# 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.
+
+
+[MESSAGES CONTROL]
+
+# Only show warnings with the listed confidence levels. Leave empty to show
+# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED.
+confidence=
+
+# Enable the message, report, category or checker with the given id(s).
+# enable=logging
+
+# Disable the message, report, category or checker with the given id(s).
+disable=missing-class-docstring,
+ missing-function-docstring,
+ missing-module-docstring,
+ global-variable-not-assigned,
+ too-many-public-methods,
+ too-few-public-methods,
+ unused-argument,
+ no-else-break,
+ no-member,
+ fixme,
+ unnecessary-pass,
+ duplicate-code, #TODO-fix
+ too-many-arguments, #TODO-fix
+
+[LOGGING]
+
+# The type of string formatting that logging methods do. `old` means using %
+# formatting, `new` is for `{}` formatting.
+logging-format-style=old
+
+# Logging modules to check that the string format arguments are in logging
+# function parameter format.
+logging-modules=logging
+
+[TYPECHECK]
+
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E1101 when accessed. Python regular
+# expressions are accepted.
+generated-members=gravitino.utils.http_client.Response
+
+[FORMAT]
+
+# Maximum number of characters on a single line.
+max-line-length=120
+
+# Regexp for a line that is allowed to be longer than the limit.
+ignore-long-lines=^\s*(# )?<?https?://\S+>?$
+
+# maximum number of lines in a file
+max-module-lines=1100
+
+# Number of spaces of indent required inside a hanging or continued line.
+indent-after-paren=4
+
+# String used as indentation unit.
+indent-string=' '
+
+[BASIC]
+
+# Naming Style for Const
+const-naming-style=UPPER_CASE
+
+# Naming Style for Class
+class-naming-style=PascalCase
+class-const-naming-style=UPPER_CASE
+class-attribute-naming-style=any
+attr-naming-style=snake_case
+
+# Naming Style for Function and Method
+function-naming-style=snake_case
+method-naming-style=snake_case
+argument-naming-style=snake_case
+variable-naming-style=snake_case
+
+# Naming Style for Module
+module-naming-style=snake_case
diff --git a/mcp-server/pyproject.toml b/mcp-server/pyproject.toml
index 5c66261caa..3e204d5527 100644
--- a/mcp-server/pyproject.toml
+++ b/mcp-server/pyproject.toml
@@ -25,6 +25,7 @@ dependencies = [
"fastmcp>=2.10.6",
"parameterized>=0.9.0",
"pytest>=8.4.1",
+ "pylint>=2.20.0",
]
[tool.isort]
diff --git a/mcp-server/tests/unit/tools/mock_operation.py
b/mcp-server/tests/unit/tools/mock_operation.py
index be0fa2120e..27b7bfc030 100644
--- a/mcp-server/tests/unit/tools/mock_operation.py
+++ b/mcp-server/tests/unit/tools/mock_operation.py
@@ -86,6 +86,8 @@ class MockFilesetOperation(FilesetOperation):
) -> str:
return "mock_fileset"
+ # pylint: disable=R0917
+ # This method has too many arguments, but it's a mock and we want to keep
the signature similar to the real one.
async def list_files_in_fileset(
self,
catalog_name: str,
@@ -141,15 +143,15 @@ class MockTagOperation(TagOperation):
return "mock_tags"
async def create_tag(
- self, name: str, comment: str, properties: dict
+ self, tag_name: str, tag_comment: str, tag_properties: dict
) -> str:
- return f"mock_tag_created: {name}"
+ return f"mock_tag_created: {tag_name}"
- async def get_tag_by_name(self, name: str) -> str:
- return f"mock_tag: {name}"
+ async def get_tag_by_name(self, tag_name: str) -> str:
+ return f"mock_tag: {tag_name}"
- async def alter_tag(self, name: str, updates: list) -> str:
- return f"mock_tag_altered: {name} with updates {updates}"
+ async def alter_tag(self, tag_name: str, updates: list) -> str:
+ return f"mock_tag_altered: {tag_name} with updates {updates}"
async def delete_tag(self, name: str) -> str:
return f"mock_tag_deleted: {name}"
diff --git a/mcp-server/tests/unit/tools/test_catalog.py
b/mcp-server/tests/unit/tools/test_catalog.py
index 01a8fd1ef7..ca6c03f735 100644
--- a/mcp-server/tests/unit/tools/test_catalog.py
+++ b/mcp-server/tests/unit/tools/test_catalog.py
@@ -27,13 +27,18 @@ from tests.unit.tools import MockOperation
class TestCatalogTool(unittest.TestCase):
+ """Test the catalog tool functionality."""
def setUp(self):
+ """Set up the test environment."""
+
RESTClientFactory.set_rest_client(MockOperation)
server = GravitinoMCPServer(Setting("mock_metalake"))
self.mcp = server.mcp
def test_list_catalogs(self):
+ """Test listing catalogs."""
+
async def _test_list_catalogs(mcp_server):
async with Client(mcp_server) as client:
result = await client.call_tool("get_list_of_catalogs")
diff --git a/mcp-server/uv.lock b/mcp-server/uv.lock
index b64f8b27b0..0364247693 100644
--- a/mcp-server/uv.lock
+++ b/mcp-server/uv.lock
@@ -1,6 +1,11 @@
version = 1
-revision = 2
+revision = 3
requires-python = ">=3.10"
+resolution-markers = [
+ "python_full_version >= '3.12'",
+ "python_full_version == '3.11.*'",
+ "python_full_version < '3.11'",
+]
[[package]]
name = "annotated-types"
@@ -26,6 +31,18 @@ wheels = [
{ url =
"https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl",
hash =
"sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size
= 100916, upload-time = "2025-03-17T00:02:52.713Z" },
]
+[[package]]
+name = "astroid"
+version = "3.3.11"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions", marker = "python_full_version < '3.11'" },
+]
+sdist = { url =
"https://files.pythonhosted.org/packages/18/74/dfb75f9ccd592bbedb175d4a32fc643cf569d7c218508bfbd6ea7ef9c091/astroid-3.3.11.tar.gz",
hash =
"sha256:1e5a5011af2920c7c67a53f65d536d65bfa7116feeaf2354d8b94f29573bb0ce", size
= 400439, upload-time = "2025-07-13T18:04:23.177Z" }
+wheels = [
+ { url =
"https://files.pythonhosted.org/packages/af/0f/3b8fdc946b4d9cc8cc1e8af42c4e409468c84441b933d037e101b3d72d86/astroid-3.3.11-py3-none-any.whl",
hash =
"sha256:54c760ae8322ece1abd213057c4b5bba7c49818853fc901ef09719a60dbf9dec", size
= 275612, upload-time = "2025-07-13T18:04:21.07Z" },
+]
+
[[package]]
name = "attrs"
version = "25.3.0"
@@ -197,6 +214,15 @@ wheels = [
{ url =
"https://files.pythonhosted.org/packages/16/1f/4b9f6986add9f6ff361c1bfffeb08fc2f2f6752f8adf8d4dcf0a988b6f28/cyclopts-3.22.3-py3-none-any.whl",
hash =
"sha256:771ae584868c8beeac74184a96e9fad3726c787b17e47a6f0d5f42cece1df57a", size
= 84941, upload-time = "2025-07-23T23:25:08.527Z" },
]
+[[package]]
+name = "dill"
+version = "0.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url =
"https://files.pythonhosted.org/packages/12/80/630b4b88364e9a8c8c5797f4602d0f76ef820909ee32f0bacb9f90654042/dill-0.4.0.tar.gz",
hash =
"sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0", size
= 186976, upload-time = "2025-04-16T00:41:48.867Z" }
+wheels = [
+ { url =
"https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl",
hash =
"sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049", size
= 119668, upload-time = "2025-04-16T00:41:47.671Z" },
+]
+
[[package]]
name = "dnspython"
version = "2.7.0"
@@ -277,6 +303,7 @@ source = { virtual = "." }
dependencies = [
{ name = "fastmcp" },
{ name = "parameterized" },
+ { name = "pylint" },
{ name = "pytest" },
]
@@ -284,6 +311,7 @@ dependencies = [
requires-dist = [
{ name = "fastmcp", specifier = ">=2.10.6" },
{ name = "parameterized", specifier = ">=0.9.0" },
+ { name = "pylint", specifier = ">=2.20.0" },
{ name = "pytest", specifier = ">=8.4.1" },
]
@@ -351,6 +379,15 @@ wheels = [
{ url =
"https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl",
hash =
"sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size
= 6050, upload-time = "2025-03-19T20:10:01.071Z" },
]
+[[package]]
+name = "isort"
+version = "6.0.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url =
"https://files.pythonhosted.org/packages/b8/21/1e2a441f74a653a144224d7d21afe8f4169e6c7c20bb13aec3a2dc3815e0/isort-6.0.1.tar.gz",
hash =
"sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450", size
= 821955, upload-time = "2025-02-26T21:13:16.955Z" }
+wheels = [
+ { url =
"https://files.pythonhosted.org/packages/c1/11/114d0a5f4dabbdcedc1125dee0888514c3c3b16d3e9facad87ed96fad97c/isort-6.0.1-py3-none-any.whl",
hash =
"sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615", size
= 94186, upload-time = "2025-02-26T21:13:14.911Z" },
+]
+
[[package]]
name = "jsonschema"
version = "4.25.0"
@@ -390,6 +427,15 @@ wheels = [
{ url =
"https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl",
hash =
"sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size
= 87528, upload-time = "2023-06-03T06:41:11.019Z" },
]
+[[package]]
+name = "mccabe"
+version = "0.7.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url =
"https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz",
hash =
"sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size
= 9658, upload-time = "2022-01-24T01:14:51.113Z" }
+wheels = [
+ { url =
"https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl",
hash =
"sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size
= 7350, upload-time = "2022-01-24T01:14:49.62Z" },
+]
+
[[package]]
name = "mcp"
version = "1.12.2"
@@ -451,6 +497,15 @@ wheels = [
{ url =
"https://files.pythonhosted.org/packages/00/2f/804f58f0b856ab3bf21617cccf5b39206e6c4c94c2cd227bde125ea6105f/parameterized-0.9.0-py2.py3-none-any.whl",
hash =
"sha256:4e0758e3d41bea3bbd05ec14fc2c24736723f243b28d702081aef438c9372b1b", size
= 20475, upload-time = "2023-03-27T02:01:09.31Z" },
]
+[[package]]
+name = "platformdirs"
+version = "4.3.8"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url =
"https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz",
hash =
"sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size
= 21362, upload-time = "2025-05-07T22:47:42.121Z" }
+wheels = [
+ { url =
"https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl",
hash =
"sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size
= 18567, upload-time = "2025-05-07T22:47:40.376Z" },
+]
+
[[package]]
name = "pluggy"
version = "1.6.0"
@@ -599,6 +654,25 @@ wheels = [
{ url =
"https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl",
hash =
"sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size
= 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
]
+[[package]]
+name = "pylint"
+version = "3.3.8"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "astroid" },
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+ { name = "dill" },
+ { name = "isort" },
+ { name = "mccabe" },
+ { name = "platformdirs" },
+ { name = "tomli", marker = "python_full_version < '3.11'" },
+ { name = "tomlkit" },
+]
+sdist = { url =
"https://files.pythonhosted.org/packages/9d/58/1f614a84d3295c542e9f6e2c764533eea3f318f4592dc1ea06c797114767/pylint-3.3.8.tar.gz",
hash =
"sha256:26698de19941363037e2937d3db9ed94fb3303fdadf7d98847875345a8bb6b05", size
= 1523947, upload-time = "2025-08-09T09:12:57.234Z" }
+wheels = [
+ { url =
"https://files.pythonhosted.org/packages/2d/1a/711e93a7ab6c392e349428ea56e794a3902bb4e0284c1997cff2d7efdbc1/pylint-3.3.8-py3-none-any.whl",
hash =
"sha256:7ef94aa692a600e82fabdd17102b73fc226758218c97473c7ad67bd4cb905d83", size
= 523153, upload-time = "2025-08-09T09:12:54.836Z" },
+]
+
[[package]]
name = "pyperclip"
version = "1.9.0"
@@ -902,6 +976,15 @@ wheels = [
{ url =
"https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl",
hash =
"sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size
= 14257, upload-time = "2024-11-27T22:38:35.385Z" },
]
+[[package]]
+name = "tomlkit"
+version = "0.13.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url =
"https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz",
hash =
"sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size
= 185207, upload-time = "2025-06-05T07:13:44.947Z" }
+wheels = [
+ { url =
"https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl",
hash =
"sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size
= 38901, upload-time = "2025-06-05T07:13:43.546Z" },
+]
+
[[package]]
name = "typing-extensions"
version = "4.14.1"