You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@superset.apache.org by "aminghadersohi (via GitHub)" <gi...@apache.org> on 2026/05/22 03:39:21 UTC
[PR] feat(mcp): add create_tag tool [superset]
aminghadersohi opened a new pull request, #40358:
URL: https://github.com/apache/superset/pull/40358
### SUMMARY
Adds a new MCP mutation tool `create_tag` that allows AI agents to create custom tags in Superset and optionally apply them to objects (charts, dashboards, datasets, queries) in a single call.
**New files:**
- `superset/mcp_service/tag/schemas.py` — Pydantic request/response schemas (`CreateTagRequest`, `CreateTagResponse`)
- `superset/mcp_service/tag/tool/create_tag.py` — the `create_tag` MCP tool
- `superset/mcp_service/tag/tool/__init__.py` — module exports
- `superset/mcp_service/tag/__init__.py` — package init
The tool follows the established mutation pattern (same as `create_virtual_dataset`):
- `@tool(tags=["mutate"], class_permission_name="Tag", method_permission_name="write")`
- Uses `CreateCustomTagWithRelationshipsCommand` (same as the REST API `POST /api/v1/tag/`)
- Returns structured `CreateTagResponse` with tag ID, objects tagged/skipped, and error field
- Wraps operation with `event_logger.log_context`
### BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
N/A — backend MCP tool only.
### TESTING INSTRUCTIONS
1. Start the MCP server
2. Call `create_tag` with `{"name": "my-tag", "description": "test tag"}`
3. Verify the tag is created and the response includes the tag ID
4. Optionally provide `objects_to_tag: [["chart", 1]]` to tag an object at creation time
### ADDITIONAL INFORMATION
- [ ] Has associated issue:
- [ ] Required feature flags:
- [ ] Changes UI
- [ ] Includes DB Migration (follow approval process in [SIP-59](https://github.com/apache/superset/issues/13351))
- [ ] Migration is atomic, supports rollback & is backwards-compatible
- [ ] Confirm DB migration upgrade and downgrade tested
- [ ] Runtime estimates and downtime expectations provided
- [x] Introduces new feature or API
- [ ] Removes existing feature or API
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org
Re: [PR] feat(mcp): add create_tag and update_tag tools [superset]
Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on code in PR #40358:
URL: https://github.com/apache/superset/pull/40358#discussion_r3327360343
##########
superset/commands/tag/update.py:
##########
@@ -31,10 +31,11 @@
class UpdateTagCommand(UpdateMixin, BaseCommand):
- def __init__(self, model_id: int, data: dict[str, Any]):
+ def __init__(self, model_id: int, data: dict[str, Any], bulk_create: bool = False):
self._model_id = model_id
self._properties = data.copy()
self._model: Optional[Tag] = None
+ self._bulk_create = bulk_create
Review Comment:
<div>
<div id="suggestion">
<div id="issue"><b>Missing Test Coverage</b></div>
<div id="fix">
The `bulk_create` parameter is added but existing unit tests in `tests/unit_tests/tags/commands/update_test.py` do not test this parameter directly. Tests only verify basic update behavior without testing concurrency-safe vs destructive modes.
</div>
</div>
<small><i>Code Review Run #dbe4b3</i></small>
</div>
---
Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
- [ ] Yes, avoid them
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org
Re: [PR] feat(mcp): add create_tag and update_tag tools [superset]
Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #40358:
URL: https://github.com/apache/superset/pull/40358#discussion_r3326665261
##########
superset/mcp_service/tag/tool/update_tag.py:
##########
@@ -0,0 +1,121 @@
+# 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.
+
+import logging
+
+from fastmcp import Context
+from superset_core.mcp.decorators import tool, ToolAnnotations
+
+from superset.extensions import event_logger
+from superset.mcp_service.tag.schemas import UpdateTagRequest, UpdateTagResponse
+
+logger = logging.getLogger(__name__)
+
+
+@tool(
+ tags=["mutate"],
+ class_permission_name="Tag",
+ method_permission_name="write",
+ annotations=ToolAnnotations(
+ title="Update a tag",
+ readOnlyHint=False,
+ destructiveHint=True,
+ ),
+)
+async def update_tag(request: UpdateTagRequest, ctx: Context) -> UpdateTagResponse:
+ """Update an existing custom tag in Superset.
+
+ Use this tool to rename a tag or update its description.
+ Existing object associations are preserved when only name or description change.
+
+ Workflow:
+ 1. Call this tool with the tag ``id`` and the fields to update
+ 2. Omit fields you do not want to change — they keep their current values
+ 3. Use the returned ``id`` to confirm the update succeeded
+ """
+ await ctx.info("Updating tag: id=%d" % (request.id,))
+
+ try:
+ from superset.commands.tag.exceptions import (
+ TagInvalidError,
+ TagNotFoundError,
+ )
+ from superset.commands.tag.update import UpdateTagCommand
+ from superset.daos.tag import TagDAO
+
+ existing = TagDAO.find_by_id(request.id)
+ if existing is None:
+ await ctx.warning("Tag not found: id=%d" % (request.id,))
+ return UpdateTagResponse(
+ id=None,
+ name=None,
+ description=None,
+ error=f"Tag with id={request.id} not found",
+ )
+
+ name = request.name if request.name is not None else existing.name
+ description = (
+ request.description
+ if request.description is not None
+ else existing.description
+ )
+
+ # Preserve existing object associations. UpdateTagCommand calls
+ # create_tag_relationship with bulk_create=False (destructive default),
+ # so we must pass the current objects to avoid clearing them.
+ existing_objects = [
+ (obj.object_type.name, obj.object_id) for obj in existing.objects
+ ]
+
+ with event_logger.log_context(action="mcp.update_tag.update"):
+ properties = {
+ "name": name,
+ "description": description,
+ "objects_to_tag": existing_objects,
+ }
+ tag = UpdateTagCommand(request.id, properties).run()
Review Comment:
**Suggestion:** Relationship preservation is based on a pre-fetched snapshot of `existing.objects`, but `UpdateTagCommand` applies destructive relationship sync (`bulk_create=False`), so concurrent tag-association changes made after this read can be deleted unintentionally. Fetch and lock the tag/relationships in the same transaction used for update, or add a non-destructive update mode in the command layer to avoid stale-snapshot deletions. [race condition]
<details>
<summary><b>Severity Level:</b> Critical 🚨</summary>
```mdx
- ❌ Concurrent tagging operations can delete newly added tag associations.
- ⚠️ Dashboards/charts may unexpectedly lose custom tag labels.
```
</details>
<details>
<summary><b>Steps of Reproduction ✅ </b></summary>
```mdx
1. Create a custom tag with at least one tagged object using the REST endpoint `POST
/api/v1/tag/` implemented in `superset/tags/api.py:93-141`. This uses
`CreateCustomTagWithRelationshipsCommand(...).run()`
(`superset/commands/tag/create.py:15-27`), which calls
`TagDAO.create_tag_relationship(..., bulk_create=self._bulk_create)` at lines 19-24 to
populate initial `TaggedObject` associations.
2. In client A, invoke the MCP `update_tag` tool to change only metadata (e.g.,
`{"request": {"id": <tag_id>, "name": "tagged"}}`). The tool implementation in
`superset/mcp_service/tag/tool/update_tag.py:39-82` loads the tag via
`TagDAO.find_by_id(request.id)` at line 60 and builds `existing_objects =
[(obj.object_type.name, obj.object_id) for obj in existing.objects]` at lines 80-82,
capturing a snapshot of current relationships.
3. Before client A reaches `UpdateTagCommand(request.id, properties).run()` at line 90 in
`superset/mcp_service/tag/tool/update_tag.py`, have client B add an additional association
to the same tag via `POST /api/v1/tag/<object_type>/<object_id>/` handled by
`TagRestApi.add_objects` in `superset/tags/api.py:68-123`. That path calls
`CreateCustomTagCommand(object_type, object_id, tags).run()` at line 122, which ultimately
uses `TagDAO.create_custom_tagged_objects` (`superset/daos/tag.py:45-70`) to add a new
`TaggedObject` row for this tag.
4. When client A resumes, it calls `UpdateTagCommand(request.id, properties).run()` at
`superset/mcp_service/tag/tool/update_tag.py:90` with `properties["objects_to_tag"]` set
to the stale `existing_objects` snapshot. Inside `UpdateTagCommand.run()`
(`superset/commands/tag/update.py:39-47`),
`TagDAO.create_tag_relationship(objects_to_tag=self._properties.get("objects_to_tag", []),
tag=self._model)` is invoked at line 44. `create_tag_relationship` in
`superset/daos/tag.py:31-89` computes `current_tagged_objects` from `tag.objects`
(including the new association) and `updated_tagged_objects` from the stale list. Because
`bulk_create` defaults to `False`, it calculates `tagged_objects_to_delete =
current_tagged_objects - updated_tagged_objects` at lines 60-64 and deletes those
relationships at lines 73-81, removing the concurrently added `TaggedObject` despite
client A never requesting its deletion.
```
</details>
[Fix in Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=d2eae8addd7d4f4b9550d3295a9ed3ae&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) | [Fix in VSCode Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=d2eae8addd7d4f4b9550d3295a9ed3ae&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
*(Use Cmd/Ctrl + Click for best experience)*
<details>
<summary><b>Prompt for AI Agent 🤖 </b></summary>
```mdx
This is a comment left during a code review.
**Path:** superset/mcp_service/tag/tool/update_tag.py
**Line:** 80:90
**Comment:**
*Race Condition: Relationship preservation is based on a pre-fetched snapshot of `existing.objects`, but `UpdateTagCommand` applies destructive relationship sync (`bulk_create=False`), so concurrent tag-association changes made after this read can be deleted unintentionally. Fetch and lock the tag/relationships in the same transaction used for update, or add a non-destructive update mode in the command layer to avoid stale-snapshot deletions.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
```
</details>
<a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40358&comment_hash=c47aa320b58ad07f26a9c1a555196727fc5b2dc1ec961c746c40b3daf18a37cb&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40358&comment_hash=c47aa320b58ad07f26a9c1a555196727fc5b2dc1ec961c746c40b3daf18a37cb&reaction=dislike'>👎</a>
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org
Re: [PR] feat(mcp): add create_tag and update_tag tools [superset]
Posted by "aminghadersohi (via GitHub)" <gi...@apache.org>.
aminghadersohi commented on code in PR #40358:
URL: https://github.com/apache/superset/pull/40358#discussion_r3328070233
##########
superset/commands/tag/update.py:
##########
@@ -31,10 +31,11 @@
class UpdateTagCommand(UpdateMixin, BaseCommand):
- def __init__(self, model_id: int, data: dict[str, Any]):
+ def __init__(self, model_id: int, data: dict[str, Any], bulk_create: bool = False):
self._model_id = model_id
self._properties = data.copy()
self._model: Optional[Tag] = None
+ self._bulk_create = bulk_create
Review Comment:
Fixed: added to . The new test verifies that passing to preserves pre-existing relationships that are not in the list — directly covering the non-destructive mode added to the command.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org
Re: [PR] feat(mcp): add create_tag and update_tag tools [superset]
Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on code in PR #40358:
URL: https://github.com/apache/superset/pull/40358#discussion_r3328071315
##########
superset/commands/tag/update.py:
##########
@@ -31,10 +31,11 @@
class UpdateTagCommand(UpdateMixin, BaseCommand):
- def __init__(self, model_id: int, data: dict[str, Any]):
+ def __init__(self, model_id: int, data: dict[str, Any], bulk_create: bool = False):
self._model_id = model_id
self._properties = data.copy()
self._model: Optional[Tag] = None
+ self._bulk_create = bulk_create
Review Comment:
<!-- Bito Reply -->
The suggestion in the comment is valid and should be applied. It correctly identifies a missing test case for the `bulk_create` parameter in the `UpdateTagCommand` class. The new test should verify that passing `bulk_create=True` preserves pre-existing relationships not in the `objects_to_tag` list, ensuring the non-destructive mode works as intended.
**superset/commands/tag/update.py**
```
class UpdateTagCommand(UpdateMixin, BaseCommand):
- def __init__(self, model_id: int, data: dict[str, Any]):
+ def __init__(self, model_id: int, data: dict[str, Any], bulk_create: bool = False):
self._model_id = model_id
self._properties = data.copy()
self._model: Optional[Tag] = None
+ self._bulk_create = bulk_create
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org
Re: [PR] feat(mcp): add create_tag and update_tag tools [superset]
Posted by "aminghadersohi (via GitHub)" <gi...@apache.org>.
aminghadersohi commented on PR #40358:
URL: https://github.com/apache/superset/pull/40358#issuecomment-4606870396
Closing: not needed at this time. See SC-107899 for policy context.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org
Re: [PR] feat(mcp): add create_tag and update_tag tools [superset]
Posted by "aminghadersohi (via GitHub)" <gi...@apache.org>.
aminghadersohi closed pull request #40358: feat(mcp): add create_tag and update_tag tools
URL: https://github.com/apache/superset/pull/40358
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org
Re: [PR] feat(mcp): add create_tag and update_tag tools [superset]
Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on code in PR #40358:
URL: https://github.com/apache/superset/pull/40358#discussion_r3330879456
##########
tests/unit_tests/mcp_service/tag/tool/test_create_tag.py:
##########
@@ -0,0 +1,243 @@
+# 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.
+
+from unittest.mock import MagicMock, patch
+
+import pytest
+from fastmcp import Client
+
+from superset.mcp_service.app import mcp
+from superset.mcp_service.tag.schemas import CreateTagRequest
+from superset.utils import json
+
+
+@pytest.fixture
+def mcp_server():
+ return mcp
+
+
+@pytest.fixture(autouse=True)
+def mock_auth():
+ """Mock authentication for all tests in this module."""
+ from unittest.mock import Mock
+
+ with patch("superset.mcp_service.auth.get_user_from_request") as mock_get_user:
+ mock_user = Mock()
+ mock_user.id = 1
+ mock_user.username = "admin"
+ mock_get_user.return_value = mock_user
+ yield mock_get_user
+
+
+def _make_tag(
+ tag_id: int = 42, name: str = "my-tag", description: str = ""
+) -> MagicMock:
+ tag = MagicMock()
+ tag.id = tag_id
+ tag.name = name
+ tag.description = description
+ return tag
+
+
+@pytest.mark.asyncio
+async def test_create_tag_success(mcp_server) -> None:
+ """Happy path: tag is created and ID is returned."""
+ mock_tag = _make_tag(42, "my-tag", "A test tag")
+
+ with (
+ patch(
+ "superset.commands.tag.create.CreateCustomTagWithRelationshipsCommand.run",
+ return_value=(set(), set()),
+ ),
+ patch(
+ "superset.daos.tag.TagDAO.find_by_name",
+ return_value=mock_tag,
+ ),
+ ):
+ async with Client(mcp_server) as client:
+ result = await client.call_tool(
+ "create_tag",
+ {"request": {"name": "my-tag", "description": "A test tag"}},
+ )
+
+ data = json.loads(result.content[0].text)
+ assert data["id"] == 42
+ assert data["name"] == "my-tag"
+ assert data["description"] == "A test tag"
+ assert data["objects_tagged"] == []
+ assert data["objects_skipped"] == []
+ assert data["error"] is None
+
+
+@pytest.mark.asyncio
+async def test_create_tag_with_objects(mcp_server) -> None:
+ """Tag is created and objects_tagged is populated and sorted."""
+ mock_tag = _make_tag(10, "org-tag", "")
+
+ with (
+ patch(
+ "superset.commands.tag.create.CreateCustomTagWithRelationshipsCommand.run",
+ return_value=({("chart", 2), ("dashboard", 1)}, set()),
+ ),
+ patch(
+ "superset.daos.tag.TagDAO.find_by_name",
+ return_value=mock_tag,
+ ),
+ ):
+ async with Client(mcp_server) as client:
+ result = await client.call_tool(
+ "create_tag",
+ {
+ "request": {
+ "name": "org-tag",
+ "objects_to_tag": [["chart", 2], ["dashboard", 1]],
+ }
+ },
+ )
+
+ data = json.loads(result.content[0].text)
+ assert data["id"] == 10
+ assert data["error"] is None
+ # Response lists must be deterministically sorted
+ assert data["objects_tagged"] == sorted([["chart", 2], ["dashboard", 1]])
+
+
+@pytest.mark.asyncio
+async def test_create_tag_objects_skipped(mcp_server) -> None:
+ """Skipped objects (insufficient ownership) are reported separately."""
+ mock_tag = _make_tag(7, "skip-tag", "")
+
+ with (
+ patch(
+ "superset.commands.tag.create.CreateCustomTagWithRelationshipsCommand.run",
+ return_value=(set(), {("chart", 5)}),
+ ),
+ patch(
+ "superset.daos.tag.TagDAO.find_by_name",
+ return_value=mock_tag,
+ ),
+ ):
+ async with Client(mcp_server) as client:
+ result = await client.call_tool(
+ "create_tag",
+ {
+ "request": {
+ "name": "skip-tag",
+ "objects_to_tag": [["chart", 5]],
+ }
+ },
+ )
+
+ data = json.loads(result.content[0].text)
+ assert data["id"] == 7
+ assert data["objects_tagged"] == []
+ assert data["objects_skipped"] == [["chart", 5]]
+ assert data["error"] is None
+
+
+@pytest.mark.asyncio
+async def test_create_tag_strips_whitespace_from_name(mcp_server) -> None:
+ """Leading/trailing whitespace is stripped from the tag name."""
+ mock_tag = _make_tag(1, "trimmed", "")
+
+ with (
+ patch(
+ "superset.commands.tag.create.CreateCustomTagWithRelationshipsCommand.run",
+ return_value=(set(), set()),
+ ),
+ patch(
+ "superset.daos.tag.TagDAO.find_by_name",
+ return_value=mock_tag,
+ ),
+ ):
+ async with Client(mcp_server) as client:
+ result = await client.call_tool(
+ "create_tag",
+ {"request": {"name": " trimmed "}},
+ )
+
+ data = json.loads(result.content[0].text)
+ assert data["id"] == 1
+ assert data["name"] == "trimmed"
+
+
+@pytest.mark.asyncio
+async def test_create_tag_invalid_error_returns_structured_response(
+ mcp_server,
+) -> None:
+ """TagInvalidError is caught and returned as a structured error."""
+ from superset.commands.tag.exceptions import TagInvalidError
+
+ with patch(
+ "superset.commands.tag.create.CreateCustomTagWithRelationshipsCommand.run",
+ side_effect=TagInvalidError(),
+ ):
+ async with Client(mcp_server) as client:
+ result = await client.call_tool(
+ "create_tag",
+ {"request": {"name": "bad-tag"}},
+ )
+
+ data = json.loads(result.content[0].text)
+ assert data["id"] is None
+ assert data["error"] is not None
Review Comment:
<div>
<div id="suggestion">
<div id="issue"><b>Incomplete error assertion in test</b></div>
<div id="fix">
The test `test_create_tag_invalid_error_returns_structured_response` at lines 179-197 only asserts `data['error'] is not None` but does not validate the error message content. Per BITO rule [6262], tests should verify actual behavior — `TagInvalidError.normalized_messages()` is called at `create_tag.py:96` but the test never inspects its output. Add assertions like `assert 'Tag parameters are invalid' in data['error'] or validate the dict structure returned by normalized_messages()`.
</div>
<details>
<summary>
<b>Code suggestion</b>
</summary>
<blockquote>Check the AI-generated fix before applying</blockquote>
<div id="code">
```
--- a/tests/unit_tests/mcp_service/tag/tool/test_create_tag.py
+++ b/tests/unit_tests/mcp_service/tag/tool/test_create_tag.py
@@ -194,6 +194,8 @@ async def test_create_tag_invalid_error_returns_structured_response(
data = json.loads(result.content[0].text)
assert data["id"] is None
assert data["error"] is not None
+ # Verify error message content is present and non-empty
+ assert len(data["error"]) > 0
```
</div>
</details>
</div>
<small><i>Code Review Run #f44f48</i></small>
</div>
---
Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
- [ ] Yes, avoid them
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org
Re: [PR] feat(mcp): add create_tag tool [superset]
Posted by "Copilot (via GitHub)" <gi...@apache.org>.
Copilot commented on code in PR #40358:
URL: https://github.com/apache/superset/pull/40358#discussion_r3285753121
##########
superset/mcp_service/tag/tool/create_tag.py:
##########
@@ -0,0 +1,111 @@
+# 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.
+
+import logging
+
+from fastmcp import Context
+from superset_core.mcp.decorators import tool, ToolAnnotations
+
+from superset.extensions import event_logger
+from superset.mcp_service.tag.schemas import CreateTagRequest, CreateTagResponse
+
+logger = logging.getLogger(__name__)
+
+
+@tool(
+ tags=["mutate"],
+ class_permission_name="Tag",
+ method_permission_name="write",
+ annotations=ToolAnnotations(
+ title="Create a tag",
+ readOnlyHint=False,
+ destructiveHint=False,
+ ),
+)
+async def create_tag(request: CreateTagRequest, ctx: Context) -> CreateTagResponse:
+ """Create a new custom tag in Superset, optionally applying it to objects.
+
+ Use this tool to create tags for organizing charts, dashboards, datasets,
+ and queries. Tags can be applied to multiple objects at creation time.
+
+ Workflow:
+ 1. Call this tool with a tag name and optional description
+ 2. Optionally provide objects_to_tag to apply the tag immediately
+ 3. Use the returned ``id`` to reference the tag in future operations
+ """
+ await ctx.info("Creating tag: name=%r" % (request.name,))
+
+ try:
+ from superset.commands.tag.create import CreateCustomTagWithRelationshipsCommand
+ from superset.commands.tag.exceptions import (
+ TagCreateFailedError,
+ TagInvalidError,
+ )
+ from superset.daos.tag import TagDAO
+
+ with event_logger.log_context(action="mcp.create_tag.create"):
+ properties = {
+ "name": request.name,
+ "description": request.description or "",
+ "objects_to_tag": request.objects_to_tag,
+ }
+ objects_tagged, objects_skipped = CreateCustomTagWithRelationshipsCommand(
Review Comment:
The tool forces `description` to `""` when sending properties (`request.description or ""`), but the response echoes `request.description` (possibly `None`). This can make the MCP response disagree with what was persisted. Consider normalizing `description` consistently (e.g., strip/None handling) and returning the saved value (`tag.description`) so clients get an accurate result.
##########
superset/mcp_service/tag/tool/create_tag.py:
##########
@@ -0,0 +1,111 @@
+# 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.
+
+import logging
+
+from fastmcp import Context
+from superset_core.mcp.decorators import tool, ToolAnnotations
+
+from superset.extensions import event_logger
+from superset.mcp_service.tag.schemas import CreateTagRequest, CreateTagResponse
+
+logger = logging.getLogger(__name__)
+
+
+@tool(
+ tags=["mutate"],
+ class_permission_name="Tag",
+ method_permission_name="write",
+ annotations=ToolAnnotations(
+ title="Create a tag",
+ readOnlyHint=False,
+ destructiveHint=False,
+ ),
+)
+async def create_tag(request: CreateTagRequest, ctx: Context) -> CreateTagResponse:
+ """Create a new custom tag in Superset, optionally applying it to objects.
+
+ Use this tool to create tags for organizing charts, dashboards, datasets,
+ and queries. Tags can be applied to multiple objects at creation time.
+
+ Workflow:
+ 1. Call this tool with a tag name and optional description
+ 2. Optionally provide objects_to_tag to apply the tag immediately
+ 3. Use the returned ``id`` to reference the tag in future operations
+ """
+ await ctx.info("Creating tag: name=%r" % (request.name,))
+
+ try:
+ from superset.commands.tag.create import CreateCustomTagWithRelationshipsCommand
+ from superset.commands.tag.exceptions import (
+ TagCreateFailedError,
+ TagInvalidError,
+ )
+ from superset.daos.tag import TagDAO
+
+ with event_logger.log_context(action="mcp.create_tag.create"):
+ properties = {
+ "name": request.name,
+ "description": request.description or "",
+ "objects_to_tag": request.objects_to_tag,
+ }
+ objects_tagged, objects_skipped = CreateCustomTagWithRelationshipsCommand(
+ properties
+ ).run()
+
+ tag = TagDAO.find_by_name(request.name)
Review Comment:
`CreateCustomTagWithRelationshipsCommand` strips the tag name before creating/looking it up (`tag_name.strip()`), but this tool re-queries with `TagDAO.find_by_name(request.name)` (no strip). If the request includes leading/trailing whitespace, `tag` can be `None` even though creation succeeded, and the response `id` will be null. Normalize `request.name` (strip + reject whitespace-only) and use the normalized name consistently for both command properties and DAO lookup/response.
##########
superset/mcp_service/app.py:
##########
@@ -670,6 +670,9 @@ def create_mcp_app(
get_schema,
health_check,
)
+from superset.mcp_service.tag.tool import ( # noqa: F401, E402
+ create_tag,
+)
Review Comment:
The default MCP instructions in this file include a curated “Available tools” list used to guide LLM behavior, but `create_tag` isn’t listed there. Add an entry (e.g., under a Tag Management section) so agents know when/how to use the new tool.
##########
superset/mcp_service/tag/schemas.py:
##########
@@ -0,0 +1,68 @@
+# 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.
+
+"""Pydantic schemas for tag-related MCP tools."""
+
+from pydantic import BaseModel, ConfigDict, Field
+
+
+class CreateTagRequest(BaseModel):
+ model_config = ConfigDict(populate_by_name=True)
+
+ name: str = Field(
+ ...,
+ min_length=1,
+ description=(
+ "Name for the new tag. Must be unique. "
+ "Used to identify and reference the tag across Superset."
+ ),
+ )
+ description: str | None = Field(
+ None,
+ description="Optional human-readable description for the tag.",
+ )
+ objects_to_tag: list[tuple[str, int]] = Field(
+ default_factory=list,
+ description=(
+ "Optional list of objects to apply this tag to immediately after creation. "
+ "Each item is a [object_type, object_id] pair where object_type is one of: "
+ "'chart', 'dashboard', 'dataset', 'query'."
+ ),
+ )
Review Comment:
`CreateTagRequest.name` doesn’t enforce the Tag model’s max length (Tag.name is `String(250)`) and doesn’t strip/reject whitespace-only names. Also, `objects_to_tag` IDs aren’t constrained to `>=1` even though the REST schema validates `Range(min=1)`. Adding Pydantic constraints/validators here will prevent DB errors and match existing API behavior.
##########
superset/mcp_service/tag/tool/create_tag.py:
##########
@@ -0,0 +1,111 @@
+# 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.
+
+import logging
+
+from fastmcp import Context
+from superset_core.mcp.decorators import tool, ToolAnnotations
+
+from superset.extensions import event_logger
+from superset.mcp_service.tag.schemas import CreateTagRequest, CreateTagResponse
+
+logger = logging.getLogger(__name__)
+
+
+@tool(
+ tags=["mutate"],
+ class_permission_name="Tag",
+ method_permission_name="write",
+ annotations=ToolAnnotations(
+ title="Create a tag",
+ readOnlyHint=False,
+ destructiveHint=False,
+ ),
+)
+async def create_tag(request: CreateTagRequest, ctx: Context) -> CreateTagResponse:
+ """Create a new custom tag in Superset, optionally applying it to objects.
+
+ Use this tool to create tags for organizing charts, dashboards, datasets,
+ and queries. Tags can be applied to multiple objects at creation time.
+
+ Workflow:
+ 1. Call this tool with a tag name and optional description
+ 2. Optionally provide objects_to_tag to apply the tag immediately
+ 3. Use the returned ``id`` to reference the tag in future operations
+ """
+ await ctx.info("Creating tag: name=%r" % (request.name,))
+
+ try:
+ from superset.commands.tag.create import CreateCustomTagWithRelationshipsCommand
+ from superset.commands.tag.exceptions import (
+ TagCreateFailedError,
+ TagInvalidError,
+ )
+ from superset.daos.tag import TagDAO
+
+ with event_logger.log_context(action="mcp.create_tag.create"):
+ properties = {
+ "name": request.name,
+ "description": request.description or "",
+ "objects_to_tag": request.objects_to_tag,
+ }
+ objects_tagged, objects_skipped = CreateCustomTagWithRelationshipsCommand(
+ properties
+ ).run()
+
+ tag = TagDAO.find_by_name(request.name)
+
+ await ctx.info(
+ "Tag created: id=%s, name=%r, objects_tagged=%d, objects_skipped=%d"
+ % (
+ tag.id if tag else None,
+ request.name,
+ len(objects_tagged),
+ len(objects_skipped),
+ )
+ )
+
+ return CreateTagResponse(
+ id=tag.id if tag else None,
+ name=request.name,
+ description=request.description,
+ objects_tagged=list(objects_tagged),
+ objects_skipped=list(objects_skipped),
+ )
Review Comment:
`objects_tagged` / `objects_skipped` come back as sets from the command and are converted with `list(...)`. Set iteration order is non-deterministic, which can make responses (and future tests) flaky. Consider returning a stable ordering (e.g., sort by `(object_type, object_id)`).
##########
superset/mcp_service/tag/tool/create_tag.py:
##########
@@ -0,0 +1,111 @@
+# 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.
+
+import logging
+
+from fastmcp import Context
+from superset_core.mcp.decorators import tool, ToolAnnotations
+
+from superset.extensions import event_logger
+from superset.mcp_service.tag.schemas import CreateTagRequest, CreateTagResponse
+
+logger = logging.getLogger(__name__)
+
+
+@tool(
+ tags=["mutate"],
+ class_permission_name="Tag",
+ method_permission_name="write",
+ annotations=ToolAnnotations(
+ title="Create a tag",
+ readOnlyHint=False,
+ destructiveHint=False,
+ ),
Review Comment:
`create_tag` can be destructive when the tag name already exists: `CreateCustomTagWithRelationshipsCommand(...).run()` calls `TagDAO.create_tag_relationship(...)` which deletes existing tag/object relationships when `bulk_create=False` and `objects_to_tag` is empty (or doesn’t include previously tagged objects). Either enforce “create-only” semantics (fail if the tag already exists), or invoke the command with `bulk_create=True` / adjust the tool so it never removes existing relationships, and update `destructiveHint` accordingly.
##########
superset/mcp_service/tag/tool/create_tag.py:
##########
@@ -0,0 +1,111 @@
+# 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.
+
+import logging
+
+from fastmcp import Context
+from superset_core.mcp.decorators import tool, ToolAnnotations
+
+from superset.extensions import event_logger
+from superset.mcp_service.tag.schemas import CreateTagRequest, CreateTagResponse
+
+logger = logging.getLogger(__name__)
+
+
+@tool(
+ tags=["mutate"],
+ class_permission_name="Tag",
+ method_permission_name="write",
+ annotations=ToolAnnotations(
+ title="Create a tag",
+ readOnlyHint=False,
+ destructiveHint=False,
+ ),
+)
+async def create_tag(request: CreateTagRequest, ctx: Context) -> CreateTagResponse:
+ """Create a new custom tag in Superset, optionally applying it to objects.
+
+ Use this tool to create tags for organizing charts, dashboards, datasets,
+ and queries. Tags can be applied to multiple objects at creation time.
+
+ Workflow:
+ 1. Call this tool with a tag name and optional description
+ 2. Optionally provide objects_to_tag to apply the tag immediately
+ 3. Use the returned ``id`` to reference the tag in future operations
+ """
+ await ctx.info("Creating tag: name=%r" % (request.name,))
Review Comment:
This PR introduces a new MCP mutation tool, but there are no unit tests added for its request schema and tool behavior. Existing MCP tools have dedicated unit tests (including happy path + permission/validation error cases); adding similar coverage for `create_tag` will help prevent regressions and ensure stable MCP responses.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org
Re: [PR] feat(mcp): add create_tag and update_tag tools [superset]
Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #40358:
URL: https://github.com/apache/superset/pull/40358#issuecomment-4580514384
AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
<details>
<summary>
Bito Usage Guide
</summary>
**Commands**
Type the following command in the pull request comment and save the comment.
- `/review` - Manually triggers a full AI review.
- `/pause` - Pauses automatic reviews on this pull request.
- `/resume` - Resumes automatic reviews.
- `/resolve` - Marks all Bito-posted review comments as resolved.
- `/abort` - Cancels all in-progress reviews.
Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
**Configuration**
This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
**Documentation & Help**
- <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
- <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
- <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
- <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
</details>
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org
Re: [PR] feat(mcp): add create_tag tool [superset]
Posted by "codecov[bot] (via GitHub)" <gi...@apache.org>.
codecov[bot] commented on PR #40358:
URL: https://github.com/apache/superset/pull/40358#issuecomment-4514877638
## [Codecov](https://app.codecov.io/gh/apache/superset/pull/40358?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) Report
:x: Patch coverage is `53.33333%` with `21 lines` in your changes missing coverage. Please review.
:white_check_mark: Project coverage is 55.38%. Comparing base ([`5966bb1`](https://app.codecov.io/gh/apache/superset/commit/5966bb1c1efdbc63888325116304f449548e0f5f?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)) to head ([`5aa240f`](https://app.codecov.io/gh/apache/superset/commit/5aa240fdf4f0352abe5e7d8317ea3ca68f7d1fdb?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)).
| [Files with missing lines](https://app.codecov.io/gh/apache/superset/pull/40358?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | Patch % | Lines |
|---|---|---|
| [superset/mcp\_service/tag/tool/create\_tag.py](https://app.codecov.io/gh/apache/superset/pull/40358?src=pr&el=tree&filepath=superset%2Fmcp_service%2Ftag%2Ftool%2Fcreate_tag.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-c3VwZXJzZXQvbWNwX3NlcnZpY2UvdGFnL3Rvb2wvY3JlYXRlX3RhZy5weQ==) | 27.58% | [21 Missing :warning: ](https://app.codecov.io/gh/apache/superset/pull/40358?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) |
> :exclamation: There is a different number of reports uploaded between BASE (5966bb1) and HEAD (5aa240f). Click for more details.
>
> <details><summary>HEAD has 80 uploads less than BASE</summary>
>
>| Flag | BASE (5966bb1) | HEAD (5aa240f) |
>|------|------|------|
>|python|70|2|
>|presto|7|1|
>|hive|7|1|
></details>
<details><summary>Additional details and impacted files</summary>
```diff
@@ Coverage Diff @@
## master #40358 +/- ##
==========================================
- Coverage 64.20% 55.38% -8.82%
==========================================
Files 2592 2595 +3
Lines 139004 139049 +45
Branches 32273 32273
==========================================
- Hits 89241 77009 -12232
- Misses 48231 61376 +13145
+ Partials 1532 664 -868
```
| [Flag](https://app.codecov.io/gh/apache/superset/pull/40358/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | Coverage Δ | |
|---|---|---|
| [hive](https://app.codecov.io/gh/apache/superset/pull/40358/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `39.31% <53.33%> (+<0.01%)` | :arrow_up: |
| [mysql](https://app.codecov.io/gh/apache/superset/pull/40358/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `?` | |
| [postgres](https://app.codecov.io/gh/apache/superset/pull/40358/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `?` | |
| [presto](https://app.codecov.io/gh/apache/superset/pull/40358/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `40.98% <53.33%> (+<0.01%)` | :arrow_up: |
| [python](https://app.codecov.io/gh/apache/superset/pull/40358/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `41.05% <53.33%> (-19.41%)` | :arrow_down: |
| [sqlite](https://app.codecov.io/gh/apache/superset/pull/40358/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `?` | |
| [unit](https://app.codecov.io/gh/apache/superset/pull/40358/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `?` | |
Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#carryforward-flags-in-the-pull-request-comment) to find out more.
</details>
[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/apache/superset/pull/40358?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache).
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache).
<details><summary> :rocket: New features to boost your workflow: </summary>
- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org
Re: [PR] feat(mcp): add create_tag tool [superset]
Posted by "aminghadersohi (via GitHub)" <gi...@apache.org>.
aminghadersohi commented on code in PR #40358:
URL: https://github.com/apache/superset/pull/40358#discussion_r3305903556
##########
superset/mcp_service/tag/tool/create_tag.py:
##########
@@ -0,0 +1,111 @@
+# 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.
+
+import logging
+
+from fastmcp import Context
+from superset_core.mcp.decorators import tool, ToolAnnotations
+
+from superset.extensions import event_logger
+from superset.mcp_service.tag.schemas import CreateTagRequest, CreateTagResponse
+
+logger = logging.getLogger(__name__)
+
+
+@tool(
+ tags=["mutate"],
+ class_permission_name="Tag",
+ method_permission_name="write",
+ annotations=ToolAnnotations(
+ title="Create a tag",
+ readOnlyHint=False,
+ destructiveHint=False,
+ ),
Review Comment:
Fixed: now passing `bulk_create=True` to `CreateCustomTagWithRelationshipsCommand`, which preserves existing tag/object relationships not included in the call. The `destructiveHint=False` annotation is accurate — the tool will not remove any existing relationships.
##########
superset/mcp_service/tag/tool/create_tag.py:
##########
@@ -0,0 +1,111 @@
+# 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.
+
+import logging
+
+from fastmcp import Context
+from superset_core.mcp.decorators import tool, ToolAnnotations
+
+from superset.extensions import event_logger
+from superset.mcp_service.tag.schemas import CreateTagRequest, CreateTagResponse
+
+logger = logging.getLogger(__name__)
+
+
+@tool(
+ tags=["mutate"],
+ class_permission_name="Tag",
+ method_permission_name="write",
+ annotations=ToolAnnotations(
+ title="Create a tag",
+ readOnlyHint=False,
+ destructiveHint=False,
+ ),
+)
+async def create_tag(request: CreateTagRequest, ctx: Context) -> CreateTagResponse:
+ """Create a new custom tag in Superset, optionally applying it to objects.
+
+ Use this tool to create tags for organizing charts, dashboards, datasets,
+ and queries. Tags can be applied to multiple objects at creation time.
+
+ Workflow:
+ 1. Call this tool with a tag name and optional description
+ 2. Optionally provide objects_to_tag to apply the tag immediately
+ 3. Use the returned ``id`` to reference the tag in future operations
+ """
+ await ctx.info("Creating tag: name=%r" % (request.name,))
+
+ try:
+ from superset.commands.tag.create import CreateCustomTagWithRelationshipsCommand
+ from superset.commands.tag.exceptions import (
+ TagCreateFailedError,
+ TagInvalidError,
+ )
+ from superset.daos.tag import TagDAO
+
+ with event_logger.log_context(action="mcp.create_tag.create"):
+ properties = {
+ "name": request.name,
+ "description": request.description or "",
+ "objects_to_tag": request.objects_to_tag,
+ }
+ objects_tagged, objects_skipped = CreateCustomTagWithRelationshipsCommand(
+ properties
+ ).run()
+
+ tag = TagDAO.find_by_name(request.name)
Review Comment:
Fixed: added a `@field_validator('name')` in `CreateTagRequest` that strips whitespace and rejects whitespace-only names. The stripped name is then used consistently for both the command properties dict and the `TagDAO.find_by_name` lookup.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org
Re: [PR] feat(mcp): add create_tag and update_tag tools [superset]
Posted by "aminghadersohi (via GitHub)" <gi...@apache.org>.
aminghadersohi commented on code in PR #40358:
URL: https://github.com/apache/superset/pull/40358#discussion_r3326691529
##########
superset/mcp_service/tag/tool/update_tag.py:
##########
@@ -0,0 +1,121 @@
+# 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.
+
+import logging
+
+from fastmcp import Context
+from superset_core.mcp.decorators import tool, ToolAnnotations
+
+from superset.extensions import event_logger
+from superset.mcp_service.tag.schemas import UpdateTagRequest, UpdateTagResponse
+
+logger = logging.getLogger(__name__)
+
+
+@tool(
+ tags=["mutate"],
+ class_permission_name="Tag",
+ method_permission_name="write",
+ annotations=ToolAnnotations(
+ title="Update a tag",
+ readOnlyHint=False,
+ destructiveHint=True,
+ ),
+)
+async def update_tag(request: UpdateTagRequest, ctx: Context) -> UpdateTagResponse:
+ """Update an existing custom tag in Superset.
+
+ Use this tool to rename a tag or update its description.
+ Existing object associations are preserved when only name or description change.
+
+ Workflow:
+ 1. Call this tool with the tag ``id`` and the fields to update
+ 2. Omit fields you do not want to change — they keep their current values
+ 3. Use the returned ``id`` to confirm the update succeeded
+ """
+ await ctx.info("Updating tag: id=%d" % (request.id,))
+
+ try:
+ from superset.commands.tag.exceptions import (
+ TagInvalidError,
+ TagNotFoundError,
+ )
+ from superset.commands.tag.update import UpdateTagCommand
+ from superset.daos.tag import TagDAO
+
+ existing = TagDAO.find_by_id(request.id)
+ if existing is None:
+ await ctx.warning("Tag not found: id=%d" % (request.id,))
+ return UpdateTagResponse(
+ id=None,
+ name=None,
+ description=None,
+ error=f"Tag with id={request.id} not found",
+ )
+
+ name = request.name if request.name is not None else existing.name
+ description = (
+ request.description
+ if request.description is not None
+ else existing.description
+ )
+
+ # Preserve existing object associations. UpdateTagCommand calls
+ # create_tag_relationship with bulk_create=False (destructive default),
+ # so we must pass the current objects to avoid clearing them.
+ existing_objects = [
+ (obj.object_type.name, obj.object_id) for obj in existing.objects
+ ]
+
+ with event_logger.log_context(action="mcp.update_tag.update"):
+ properties = {
+ "name": name,
+ "description": description,
+ "objects_to_tag": existing_objects,
+ }
+ tag = UpdateTagCommand(request.id, properties).run()
Review Comment:
Fixed: added a `bulk_create` parameter to `UpdateTagCommand` (default `False` to preserve existing REST API behaviour). The MCP `update_tag` tool now passes `bulk_create=True`, making the relationship sync non-destructive without a pre-fetch snapshot. This eliminates the TOCTOU race — no concurrent tag-association changes can be deleted because the deletion step in `create_tag_relationship` is skipped entirely when `bulk_create=True`.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org
Re: [PR] feat(mcp): add create_tag and update_tag tools [superset]
Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #40358:
URL: https://github.com/apache/superset/pull/40358#issuecomment-4587936938
AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
<details>
<summary>
Bito Usage Guide
</summary>
**Commands**
Type the following command in the pull request comment and save the comment.
- `/review` - Manually triggers a full AI review.
- `/pause` - Pauses automatic reviews on this pull request.
- `/resume` - Resumes automatic reviews.
- `/resolve` - Marks all Bito-posted review comments as resolved.
- `/abort` - Cancels all in-progress reviews.
Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
**Configuration**
This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
**Documentation & Help**
- <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
- <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
- <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
- <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
</details>
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org
Re: [PR] feat(mcp): add create_tag and update_tag tools [superset]
Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on code in PR #40358:
URL: https://github.com/apache/superset/pull/40358#discussion_r3331103752
##########
superset/mcp_service/tag/schemas.py:
##########
@@ -249,3 +249,114 @@ def serialize_tag_object(tag: Any) -> TagInfo | None:
created_on_humanized=humanize_timestamp(getattr(tag, "created_on", None)),
)
)
+
+
+class CreateTagRequest(BaseModel):
+ model_config = ConfigDict(populate_by_name=True)
+
+ name: str = Field(
+ ...,
+ min_length=1,
+ max_length=250,
+ description=(
+ "Name for the new tag. Must be unique. "
+ "Used to identify and reference the tag across Superset."
+ ),
+ )
+ description: str | None = Field(
+ None,
+ description="Optional human-readable description for the tag.",
+ )
+ objects_to_tag: list[tuple[str, int]] = Field(
+ default_factory=list,
+ description=(
+ "Optional list of objects to apply this tag to immediately after creation. "
+ "Each item is a [object_type, object_id] pair where object_type is one of: "
+ "'chart', 'dashboard', 'dataset', 'query'."
+ ),
+ )
+
+ @field_validator("name")
+ @classmethod
+ def name_must_not_be_blank(cls, v: str) -> str:
+ stripped = v.strip()
+ if not stripped:
+ raise ValueError("name must not be blank or whitespace-only")
+ return stripped
+
+ @field_validator("objects_to_tag")
+ @classmethod
+ def validate_object_ids(cls, v: list[tuple[str, int]]) -> list[tuple[str, int]]:
+ for obj_type, obj_id in v:
+ if obj_id < 1:
+ raise ValueError(
+ f"object_id must be >= 1, got {obj_id} for type '{obj_type}'"
+ )
+ return v
+
+
+class CreateTagResponse(BaseModel):
+ id: int | None = Field(
+ None,
+ description="ID of the created tag. None if creation failed.",
+ )
+ name: str = Field(..., description="Tag name.")
+ description: str | None = Field(None, description="Tag description.")
+ objects_tagged: list[tuple[str, int]] = Field(
+ default_factory=list,
+ description="Objects that were successfully tagged.",
+ )
+ objects_skipped: list[tuple[str, int]] = Field(
+ default_factory=list,
+ description=(
+ "Objects that were skipped because the current user lacks ownership."
+ ),
+ )
+ error: str | None = Field(
+ None,
+ description="Error message if creation failed, otherwise null.",
+ )
+
+
+class UpdateTagRequest(BaseModel):
+ model_config = ConfigDict(populate_by_name=True)
+
+ id: int = Field(..., description="ID of the tag to update.")
+ name: str | None = Field(
+ None,
+ min_length=1,
+ max_length=250,
+ description=(
+ "New name for the tag. Omit to keep the existing name. "
+ "Must be unique across all tags."
+ ),
+ )
+ description: str | None = Field(
+ None,
+ description=(
+ "New description for the tag. Omit to keep the existing description."
+ ),
+ )
+
+ @field_validator("name")
+ @classmethod
+ def name_must_not_be_blank(cls, v: str | None) -> str | None:
+ if v is not None:
+ stripped = v.strip()
+ if not stripped:
+ raise ValueError("name must not be blank or whitespace-only")
+ return stripped
+ return v
Review Comment:
<div>
<div id="suggestion">
<div id="issue"><b>Incomplete object type validation</b></div>
<div id="fix">
The `validate_object_ids` validator only checks `obj_id >= 1` but does not validate `obj_type` against the documented allowed values. Invalid `obj_type` strings silently pass schema validation and cause errors at the command layer that result in skipped objects with no clear error returned to the caller.
</div>
</div>
<div id="suggestion">
<div id="issue"><b>Semantic duplication of validator</b></div>
<div id="fix">
The `name_must_not_be_blank` validator body is semantically duplicated across `CreateTagRequest` (lines 279-285) and `UpdateTagRequest` (lines 341-349). Duplication creates divergence risk if the validation logic is updated for one class but not the other.
</div>
</div>
<small><i>Code Review Run #67db6f</i></small>
</div>
---
Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
- [ ] Yes, avoid them
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org
Re: [PR] feat(mcp): add create_tag tool [superset]
Posted by "aminghadersohi (via GitHub)" <gi...@apache.org>.
aminghadersohi commented on code in PR #40358:
URL: https://github.com/apache/superset/pull/40358#discussion_r3305904999
##########
superset/mcp_service/tag/tool/create_tag.py:
##########
@@ -0,0 +1,111 @@
+# 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.
+
+import logging
+
+from fastmcp import Context
+from superset_core.mcp.decorators import tool, ToolAnnotations
+
+from superset.extensions import event_logger
+from superset.mcp_service.tag.schemas import CreateTagRequest, CreateTagResponse
+
+logger = logging.getLogger(__name__)
+
+
+@tool(
+ tags=["mutate"],
+ class_permission_name="Tag",
+ method_permission_name="write",
+ annotations=ToolAnnotations(
+ title="Create a tag",
+ readOnlyHint=False,
+ destructiveHint=False,
+ ),
+)
+async def create_tag(request: CreateTagRequest, ctx: Context) -> CreateTagResponse:
+ """Create a new custom tag in Superset, optionally applying it to objects.
+
+ Use this tool to create tags for organizing charts, dashboards, datasets,
+ and queries. Tags can be applied to multiple objects at creation time.
+
+ Workflow:
+ 1. Call this tool with a tag name and optional description
+ 2. Optionally provide objects_to_tag to apply the tag immediately
+ 3. Use the returned ``id`` to reference the tag in future operations
+ """
+ await ctx.info("Creating tag: name=%r" % (request.name,))
+
+ try:
+ from superset.commands.tag.create import CreateCustomTagWithRelationshipsCommand
+ from superset.commands.tag.exceptions import (
+ TagCreateFailedError,
+ TagInvalidError,
+ )
+ from superset.daos.tag import TagDAO
+
+ with event_logger.log_context(action="mcp.create_tag.create"):
+ properties = {
+ "name": request.name,
+ "description": request.description or "",
+ "objects_to_tag": request.objects_to_tag,
+ }
+ objects_tagged, objects_skipped = CreateCustomTagWithRelationshipsCommand(
Review Comment:
Fixed: the response now returns `tag.description` (the persisted value from the DB) rather than `request.description`. This ensures the client always sees what was actually saved.
##########
superset/mcp_service/tag/schemas.py:
##########
@@ -0,0 +1,68 @@
+# 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.
+
+"""Pydantic schemas for tag-related MCP tools."""
+
+from pydantic import BaseModel, ConfigDict, Field
+
+
+class CreateTagRequest(BaseModel):
+ model_config = ConfigDict(populate_by_name=True)
+
+ name: str = Field(
+ ...,
+ min_length=1,
+ description=(
+ "Name for the new tag. Must be unique. "
+ "Used to identify and reference the tag across Superset."
+ ),
+ )
+ description: str | None = Field(
+ None,
+ description="Optional human-readable description for the tag.",
+ )
+ objects_to_tag: list[tuple[str, int]] = Field(
+ default_factory=list,
+ description=(
+ "Optional list of objects to apply this tag to immediately after creation. "
+ "Each item is a [object_type, object_id] pair where object_type is one of: "
+ "'chart', 'dashboard', 'dataset', 'query'."
+ ),
+ )
Review Comment:
Fixed: added `max_length=250` to the `name` field (matching `Tag.name = String(250)`), a `field_validator` that strips whitespace and rejects whitespace-only names, and a `field_validator` on `objects_to_tag` that enforces `object_id >= 1` (matching the REST API's `Range(min=1)`).
##########
superset/mcp_service/app.py:
##########
@@ -670,6 +670,9 @@ def create_mcp_app(
get_schema,
health_check,
)
+from superset.mcp_service.tag.tool import ( # noqa: F401, E402
+ create_tag,
+)
Review Comment:
Fixed: added a 'Tag Management' section to the Available tools list and included `create_tag` in the write tools permission-awareness bullet.
##########
superset/mcp_service/tag/tool/create_tag.py:
##########
@@ -0,0 +1,111 @@
+# 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.
+
+import logging
+
+from fastmcp import Context
+from superset_core.mcp.decorators import tool, ToolAnnotations
+
+from superset.extensions import event_logger
+from superset.mcp_service.tag.schemas import CreateTagRequest, CreateTagResponse
+
+logger = logging.getLogger(__name__)
+
+
+@tool(
+ tags=["mutate"],
+ class_permission_name="Tag",
+ method_permission_name="write",
+ annotations=ToolAnnotations(
+ title="Create a tag",
+ readOnlyHint=False,
+ destructiveHint=False,
+ ),
+)
+async def create_tag(request: CreateTagRequest, ctx: Context) -> CreateTagResponse:
+ """Create a new custom tag in Superset, optionally applying it to objects.
+
+ Use this tool to create tags for organizing charts, dashboards, datasets,
+ and queries. Tags can be applied to multiple objects at creation time.
+
+ Workflow:
+ 1. Call this tool with a tag name and optional description
+ 2. Optionally provide objects_to_tag to apply the tag immediately
+ 3. Use the returned ``id`` to reference the tag in future operations
+ """
+ await ctx.info("Creating tag: name=%r" % (request.name,))
Review Comment:
Fixed: added `tests/unit_tests/mcp_service/tag/tool/test_create_tag.py` covering the happy path (with and without objects), skipped objects, whitespace stripping, `TagInvalidError`, `TagCreateFailedError`, and schema validation rejection of blank names and invalid object IDs.
##########
superset/mcp_service/tag/tool/create_tag.py:
##########
@@ -0,0 +1,111 @@
+# 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.
+
+import logging
+
+from fastmcp import Context
+from superset_core.mcp.decorators import tool, ToolAnnotations
+
+from superset.extensions import event_logger
+from superset.mcp_service.tag.schemas import CreateTagRequest, CreateTagResponse
+
+logger = logging.getLogger(__name__)
+
+
+@tool(
+ tags=["mutate"],
+ class_permission_name="Tag",
+ method_permission_name="write",
+ annotations=ToolAnnotations(
+ title="Create a tag",
+ readOnlyHint=False,
+ destructiveHint=False,
+ ),
+)
+async def create_tag(request: CreateTagRequest, ctx: Context) -> CreateTagResponse:
+ """Create a new custom tag in Superset, optionally applying it to objects.
+
+ Use this tool to create tags for organizing charts, dashboards, datasets,
+ and queries. Tags can be applied to multiple objects at creation time.
+
+ Workflow:
+ 1. Call this tool with a tag name and optional description
+ 2. Optionally provide objects_to_tag to apply the tag immediately
+ 3. Use the returned ``id`` to reference the tag in future operations
+ """
+ await ctx.info("Creating tag: name=%r" % (request.name,))
+
+ try:
+ from superset.commands.tag.create import CreateCustomTagWithRelationshipsCommand
+ from superset.commands.tag.exceptions import (
+ TagCreateFailedError,
+ TagInvalidError,
+ )
+ from superset.daos.tag import TagDAO
+
+ with event_logger.log_context(action="mcp.create_tag.create"):
+ properties = {
+ "name": request.name,
+ "description": request.description or "",
+ "objects_to_tag": request.objects_to_tag,
+ }
+ objects_tagged, objects_skipped = CreateCustomTagWithRelationshipsCommand(
+ properties
+ ).run()
+
+ tag = TagDAO.find_by_name(request.name)
+
+ await ctx.info(
+ "Tag created: id=%s, name=%r, objects_tagged=%d, objects_skipped=%d"
+ % (
+ tag.id if tag else None,
+ request.name,
+ len(objects_tagged),
+ len(objects_skipped),
+ )
+ )
+
+ return CreateTagResponse(
+ id=tag.id if tag else None,
+ name=request.name,
+ description=request.description,
+ objects_tagged=list(objects_tagged),
+ objects_skipped=list(objects_skipped),
+ )
Review Comment:
Fixed: `sorted(objects_tagged)` and `sorted(objects_skipped)` are used before building the response, giving deterministic ordering by `(object_type, object_id)`.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org
Re: [PR] feat(mcp): add create_tag tool [superset]
Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #40358:
URL: https://github.com/apache/superset/pull/40358#issuecomment-4514781887
AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org
Re: [PR] feat(mcp): add create_tag and update_tag tools [superset]
Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #40358:
URL: https://github.com/apache/superset/pull/40358#issuecomment-4588516969
AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
<details>
<summary>
Bito Usage Guide
</summary>
**Commands**
Type the following command in the pull request comment and save the comment.
- `/review` - Manually triggers a full AI review.
- `/pause` - Pauses automatic reviews on this pull request.
- `/resume` - Resumes automatic reviews.
- `/resolve` - Marks all Bito-posted review comments as resolved.
- `/abort` - Cancels all in-progress reviews.
Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
**Configuration**
This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
**Documentation & Help**
- <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
- <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
- <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
- <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
</details>
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org
Re: [PR] feat(mcp): add create_tag and update_tag tools [superset]
Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #40358:
URL: https://github.com/apache/superset/pull/40358#issuecomment-4582121378
AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
<details>
<summary>
Bito Usage Guide
</summary>
**Commands**
Type the following command in the pull request comment and save the comment.
- `/review` - Manually triggers a full AI review.
- `/pause` - Pauses automatic reviews on this pull request.
- `/resume` - Resumes automatic reviews.
- `/resolve` - Marks all Bito-posted review comments as resolved.
- `/abort` - Cancels all in-progress reviews.
Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
**Configuration**
This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
**Documentation & Help**
- <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
- <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
- <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
- <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
</details>
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org