Part of #390.
Summary
Add kbagent component sync-action to execute synchronous component actions (e.g. testConnection, getTables, list endpoints/schemas), porting the run_sync_action MCP tool.
MCP source (keboola-mcp-server @ agent-v1.60.0)
- Tool:
src/keboola_mcp_server/tools/components/tools.py (run_sync_action).
- Client:
clients/sync_actions.py → POST {root}/actions on a dedicated host sync-actions.{stack-suffix} (NOT the queue / encryption / docker-runner host).
- Body:
{"configData": {"parameters": …, "storage": …}, "componentId": …, "action": …, "branchId": …}.
- Shallow-merge of root config + row (top-level only, NOT deep):
parameters = {**root_params, **row_params} and storage = {**root_storage, **row_storage} — only when configuration_row_id is given.
action_name is freeform (no allow-list in code); valid actions are component-defined and surfaced via component detail synchronous_actions.
- Auth:
X-StorageAPI-Token. Response: opaque pass-through (dict or list).
What kbagent already has
- Truly absent today — no sync-action client method, service, or command (only docstring mentions of
testConnection).
- But
client.py KeboolaClient already multiplexes sibling services (queue, query, encryption) off the stack URL via the same derivation kbagent uses (_derive_service_url(stack_url, prefix) in http_base.py) with lazy per-service sub-clients. A sync-actions prefix slots in mechanically (mirror the _encrypt_request pattern); sub-clients inherit the X-StorageApi-Token headers automatically.
get_config_detail and get_config_row already exist (both honor branch_id) for the merge.
Proposed command
kbagent component sync-action ACTION_NAME \
--component-id ID --config-id ID [--row-id ID] \
--project ALIAS [--branch ID]
ACTION_NAME positional, freeform (matches MCP). Optional --config-data JSON|@file|- override is a possible enhancement (out of MCP scope).
Implementation sketch (3-layer)
- L3
KeboolaClient.run_sync_action(component_id, action, config_data, branch_id) → new sync-actions sub-client + _sync_actions_request wrapper → POST /actions.
- L2
ComponentService.run_sync_action(...): fetch root config (+ row), do the shallow merge (replicate MCP exactly — {**root, **row} for parameters and storage independently), call the client.
- L1 thin command in
commands/component.py, dual output (JSON = raw result; human = panel with JSON/syntax block since shape is action-specific).
Effort: S–M
Client method ≈ the _encrypt_request pattern; service reuses two existing fetchers + a 4-line merge; the M part is permissions registration + doc-sync + E2E.
Acceptance criteria
Risks / open questions
- Permission category: transport is
POST of indeterminate blast radius (an action could write). Recommend registering component.sync-action as write in OPERATION_REGISTRY (consistent with tool.call="write", encrypt.values="write"), so --deny-writes blocks it. Confirm with maintainer.
- Use SHALLOW merge, not kbagent's deep-merge — semantics must match MCP, else row
storage.input.tables would merge instead of replace.
- Never log
config_data — the merged payload carries the stored configuration's parameters (possibly secrets). Rely on masking infra; don't echo the request body in verbose mode.
branchId: MCP always sends it (default 'default'); kbagent's idiom is to omit branch scoping for production. Recommend omit-when-None; verify the API treats absent branchId as default branch.
- Nice-to-have: surface
synchronous_actions in component detail so users can discover valid actions (kbagent's component detail doesn't expose it today).
Part of #390.
Summary
Add
kbagent component sync-actionto execute synchronous component actions (e.g.testConnection,getTables, list endpoints/schemas), porting therun_sync_actionMCP tool.MCP source (
keboola-mcp-server@agent-v1.60.0)src/keboola_mcp_server/tools/components/tools.py(run_sync_action).clients/sync_actions.py→POST {root}/actionson a dedicated hostsync-actions.{stack-suffix}(NOT the queue / encryption / docker-runner host).{"configData": {"parameters": …, "storage": …}, "componentId": …, "action": …, "branchId": …}.parameters = {**root_params, **row_params}andstorage = {**root_storage, **row_storage}— only whenconfiguration_row_idis given.action_nameis freeform (no allow-list in code); valid actions are component-defined and surfaced via component detailsynchronous_actions.X-StorageAPI-Token. Response: opaque pass-through (dict or list).What kbagent already has
testConnection).client.pyKeboolaClientalready multiplexes sibling services (queue,query,encryption) off the stack URL via the same derivation kbagent uses (_derive_service_url(stack_url, prefix)inhttp_base.py) with lazy per-service sub-clients. Async-actionsprefix slots in mechanically (mirror the_encrypt_requestpattern); sub-clients inherit theX-StorageApi-Tokenheaders automatically.get_config_detailandget_config_rowalready exist (both honorbranch_id) for the merge.Proposed command
ACTION_NAMEpositional, freeform (matches MCP). Optional--config-data JSON|@file|-override is a possible enhancement (out of MCP scope).Implementation sketch (3-layer)
KeboolaClient.run_sync_action(component_id, action, config_data, branch_id)→ newsync-actionssub-client +_sync_actions_requestwrapper →POST /actions.ComponentService.run_sync_action(...): fetch root config (+ row), do the shallow merge (replicate MCP exactly —{**root, **row}forparametersandstorageindependently), call the client.commands/component.py, dual output (JSON = raw result; human = panel with JSON/syntax block since shape is action-specific).Effort: S–M
Client method ≈ the
_encrypt_requestpattern; service reuses two existing fetchers + a 4-line merge; the M part is permissions registration + doc-sync + E2E.Acceptance criteria
kbagent component sync-action testConnection --component-id … --config-id …returns the action result.--row-id) via top-level shallow merge.Risks / open questions
POSTof indeterminate blast radius (an action could write). Recommend registeringcomponent.sync-actionaswriteinOPERATION_REGISTRY(consistent withtool.call="write",encrypt.values="write"), so--deny-writesblocks it. Confirm with maintainer.storage.input.tableswould merge instead of replace.config_data— the merged payload carries the stored configuration'sparameters(possibly secrets). Rely on masking infra; don't echo the request body in verbose mode.branchId: MCP always sends it (default'default'); kbagent's idiom is to omit branch scoping for production. Recommend omit-when-None; verify the API treats absentbranchIdas default branch.synchronous_actionsincomponent detailso users can discover valid actions (kbagent's component detail doesn't expose it today).