Skip to content

Commit 58819a2

Browse files
authored
Merge pull request #159 from keboola/KAB-1127-mcp-add-tracking-mechanism-to-flows
Kab 1127 add flow tracking mechanism We add metadata tag to the flow creation and update tools as it is done in components. We also test the metadata tag in integration tests.
2 parents 9b9fab0 + 88581c1 commit 58819a2

File tree

4 files changed

+50
-8
lines changed

4 files changed

+50
-8
lines changed

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,4 @@ jobs:
6666
# with:
6767
# helm-chart: "mcp-server"
6868
# image-tag: production-${{ github.sha }}
69-
# github-app-private-key: ${{ secrets.GITOPS_KBC_STACKS_TRIGGER_APP_PVK }}
69+
# github-app-private-key: ${{ secrets.GITOPS_KBC_STACKS_TRIGGER_APP_PVK }}

integtests/test_flow_tools.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
from fastmcp import Context
33

44
from integtests.conftest import ConfigDef
5-
from keboola_mcp_server.client import KeboolaClient
5+
from keboola_mcp_server.client import ORCHESTRATOR_COMPONENT_ID, KeboolaClient
6+
from keboola_mcp_server.config import MetadataField
67
from keboola_mcp_server.tools.flow import (
78
FlowToolResponse,
89
create_flow,
@@ -55,6 +56,7 @@ async def test_create_and_retrieve_flow(mcp_context: Context, configs: list[Conf
5556
tasks=tasks,
5657
)
5758
flow_id = created.flow_id
59+
client = KeboolaClient.from_state(mcp_context.session.state)
5860
try:
5961
assert isinstance(created, FlowToolResponse)
6062
assert created.description == flow_description
@@ -68,8 +70,20 @@ async def test_create_and_retrieve_flow(mcp_context: Context, configs: list[Conf
6870
assert detail.phases[0].name == 'Extract'
6971
assert detail.phases[1].name == 'Transform'
7072
assert detail.tasks[0].task['componentId'] == configs[0].component_id
73+
74+
# Verify the metadata - check that KBC.MCP.createdBy is set to 'true'
75+
metadata = await client.storage_client.configuration_metadata_get(
76+
component_id=ORCHESTRATOR_COMPONENT_ID,
77+
configuration_id=flow_id
78+
)
79+
80+
# Convert metadata list to dictionary for easier checking
81+
# metadata is a list of dicts with 'key' and 'value' keys
82+
assert isinstance(metadata, list)
83+
metadata_dict = {item['key']: item['value'] for item in metadata if isinstance(item, dict)}
84+
assert MetadataField.CREATED_BY_MCP in metadata_dict
85+
assert metadata_dict[MetadataField.CREATED_BY_MCP] == 'true'
7186
finally:
72-
client = KeboolaClient.from_state(mcp_context.session.state)
7387
await client.storage_client.flow_delete(flow_id, skip_trash=True)
7488

7589

@@ -106,6 +120,7 @@ async def test_update_flow(mcp_context: Context, configs: list[ConfigDef]) -> No
106120
tasks=tasks,
107121
)
108122
flow_id = created.flow_id
123+
client = KeboolaClient.from_state(mcp_context.session.state)
109124
try:
110125
new_name = 'Updated Flow Name'
111126
new_description = 'Updated description.'
@@ -123,8 +138,21 @@ async def test_update_flow(mcp_context: Context, configs: list[ConfigDef]) -> No
123138
assert updated.description == new_description
124139
assert updated.success is True
125140
assert len(updated.links) == 3
141+
142+
# Verify the metadata - check that KBC.MCP.updatedBy.version.{version} is set to 'true'
143+
metadata = await client.storage_client.configuration_metadata_get(
144+
component_id=ORCHESTRATOR_COMPONENT_ID,
145+
configuration_id=flow_id
146+
)
147+
148+
assert isinstance(metadata, list)
149+
metadata_dict = {item['key']: item['value'] for item in metadata if isinstance(item, dict)}
150+
sync_flow = await client.storage_client.flow_detail(flow_id)
151+
updated_by_md_key = f'{MetadataField.UPDATED_BY_MCP_PREFIX}{sync_flow["version"]}'
152+
assert updated_by_md_key in metadata_dict
153+
assert metadata_dict[updated_by_md_key] == 'true'
154+
126155
finally:
127-
client = KeboolaClient.from_state(mcp_context.session.state)
128156
await client.storage_client.flow_delete(flow_id, skip_trash=True)
129157

130158

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "keboola-mcp-server"
7-
version = "1.0.0"
7+
version = "1.0.1"
88
description = "MCP server for interacting with Keboola Connection"
99
readme = "README.md"
1010
requires-python = ">=3.10"

src/keboola_mcp_server/tools/flow.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from fastmcp import Context, FastMCP
1010
from pydantic import AliasChoices, BaseModel, Field
1111

12-
from keboola_mcp_server.client import JsonDict, KeboolaClient
12+
from keboola_mcp_server.client import ORCHESTRATOR_COMPONENT_ID, JsonDict, KeboolaClient
1313
from keboola_mcp_server.errors import tool_errors
1414
from keboola_mcp_server.links import Link, ProjectLinksManager
1515
from keboola_mcp_server.mcp import with_session_state
@@ -20,6 +20,7 @@
2020
FlowTask,
2121
ReducedFlow,
2222
)
23+
from keboola_mcp_server.tools.components.tools import _set_cfg_creation_metadata, _set_cfg_update_metadata
2324
from keboola_mcp_server.tools.validation import validate_flow_configuration_against_schema
2425

2526
LOG = logging.getLogger(__name__)
@@ -127,8 +128,14 @@ async def create_flow(
127128
name=name, description=description, flow_configuration=flow_configuration # Direct configuration
128129
)
129130

131+
await _set_cfg_creation_metadata(
132+
client,
133+
component_id=ORCHESTRATOR_COMPONENT_ID,
134+
configuration_id=str(new_raw_configuration['id']),
135+
)
136+
130137
flow_id = str(new_raw_configuration['id'])
131-
flow_name = new_raw_configuration['name']
138+
flow_name = str(new_raw_configuration['name'])
132139
flow_links = links_manager.get_flow_links(flow_id=flow_id, flow_name=flow_name)
133140
tool_response = FlowToolResponse.model_validate(new_raw_configuration | {'links': flow_links})
134141

@@ -191,8 +198,15 @@ async def update_flow(
191198
flow_configuration=flow_configuration, # Direct configuration
192199
)
193200

201+
await _set_cfg_update_metadata(
202+
client,
203+
component_id=ORCHESTRATOR_COMPONENT_ID,
204+
configuration_id=str(updated_raw_configuration['id']),
205+
configuration_version=cast(int, updated_raw_configuration['version']),
206+
)
207+
194208
flow_id = str(updated_raw_configuration['id'])
195-
flow_name = updated_raw_configuration['name']
209+
flow_name = str(updated_raw_configuration['name'])
196210
flow_links = links_manager.get_flow_links(flow_id=flow_id, flow_name=flow_name)
197211
tool_response = FlowToolResponse.model_validate(updated_raw_configuration | {'links': flow_links})
198212

0 commit comments

Comments
 (0)