@qingflow-tech/qingflow-app-builder-mcp 1.0.6 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/skills/qingflow-app-builder/SKILL.md +68 -6
- package/skills/qingflow-app-builder/references/create-app.md +4 -1
- package/skills/qingflow-app-builder/references/gotchas.md +13 -1
- package/skills/qingflow-app-builder/references/tool-selection.md +3 -1
- package/skills/qingflow-app-builder/references/update-schema.md +6 -2
- package/skills/qingflow-app-builder/references/update-views.md +126 -5
- package/src/qingflow_mcp/builder_facade/models.py +269 -2
- package/src/qingflow_mcp/builder_facade/service.py +3549 -172
- package/src/qingflow_mcp/cli/commands/builder.py +39 -26
- package/src/qingflow_mcp/cli/commands/record.py +3 -3
- package/src/qingflow_mcp/public_surface.py +2 -5
- package/src/qingflow_mcp/response_trim.py +3 -6
- package/src/qingflow_mcp/server_app_builder.py +33 -20
- package/src/qingflow_mcp/tools/ai_builder_tools.py +395 -117
- package/src/qingflow_mcp/tools/record_tools.py +9 -2
|
@@ -123,30 +123,22 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
123
123
|
button_catalog = button_subparsers.add_parser("catalog", help="读取按钮样式目录")
|
|
124
124
|
button_catalog.set_defaults(handler=_handle_button_catalog, format_hint="builder_summary")
|
|
125
125
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
button_update.add_argument("--button-id", type=int, required=True)
|
|
143
|
-
button_update.add_argument("--payload-file", required=True)
|
|
144
|
-
button_update.set_defaults(handler=_handle_button_update, format_hint="builder_summary")
|
|
145
|
-
|
|
146
|
-
button_delete = button_subparsers.add_parser("delete", help="删除自定义按钮")
|
|
147
|
-
button_delete.add_argument("--app-key", required=True)
|
|
148
|
-
button_delete.add_argument("--button-id", type=int, required=True)
|
|
149
|
-
button_delete.set_defaults(handler=_handle_button_delete, format_hint="builder_summary")
|
|
126
|
+
button_apply = button_subparsers.add_parser("apply", help="声明式创建、更新或删除自定义按钮")
|
|
127
|
+
button_apply.add_argument("--app-key", required=True)
|
|
128
|
+
button_apply.add_argument("--upsert-buttons-file")
|
|
129
|
+
button_apply.add_argument("--remove-buttons-file")
|
|
130
|
+
button_apply.add_argument("--view-configs-file")
|
|
131
|
+
button_apply.set_defaults(handler=_handle_button_apply, format_hint="builder_summary")
|
|
132
|
+
|
|
133
|
+
associated_resource = builder_subparsers.add_parser("associated-resource", aliases=["associated-resources"], help="关联视图/报表")
|
|
134
|
+
associated_resource_subparsers = associated_resource.add_subparsers(dest="builder_associated_resource_command", required=True)
|
|
135
|
+
associated_resource_apply = associated_resource_subparsers.add_parser("apply", help="声明式管理应用关联资源池和视图展示配置")
|
|
136
|
+
associated_resource_apply.add_argument("--app-key", required=True)
|
|
137
|
+
associated_resource_apply.add_argument("--upsert-resources-file")
|
|
138
|
+
associated_resource_apply.add_argument("--remove-associated-item-ids-file")
|
|
139
|
+
associated_resource_apply.add_argument("--reorder-associated-item-ids-file")
|
|
140
|
+
associated_resource_apply.add_argument("--view-configs-file")
|
|
141
|
+
associated_resource_apply.set_defaults(handler=_handle_associated_resource_apply, format_hint="builder_summary")
|
|
150
142
|
|
|
151
143
|
portal = builder_subparsers.add_parser("portal", help="门户")
|
|
152
144
|
portal_subparsers = portal.add_subparsers(dest="builder_portal_command", required=True)
|
|
@@ -185,8 +177,8 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
185
177
|
schema_apply_apply.add_argument("--visibility-file")
|
|
186
178
|
schema_apply_apply.add_argument("--create-if-missing", action="store_true")
|
|
187
179
|
schema_apply_apply.add_argument("--publish", action=argparse.BooleanOptionalAction, default=True)
|
|
188
|
-
schema_apply_apply.add_argument("--add-fields-file")
|
|
189
|
-
schema_apply_apply.add_argument("--update-fields-file")
|
|
180
|
+
schema_apply_apply.add_argument("--add-fields-file", help="字段 JSON 数组;字段可用 as_data_title/as_data_cover 标记数据标题/封面")
|
|
181
|
+
schema_apply_apply.add_argument("--update-fields-file", help="字段更新 JSON 数组;set 内可用 as_data_title/as_data_cover 标记数据标题/封面")
|
|
190
182
|
schema_apply_apply.add_argument("--remove-fields-file")
|
|
191
183
|
schema_apply_apply.set_defaults(handler=_handle_schema_apply, format_hint="builder_summary")
|
|
192
184
|
|
|
@@ -369,6 +361,27 @@ def _handle_button_catalog(args: argparse.Namespace, context: CliContext) -> dic
|
|
|
369
361
|
return context.builder.button_style_catalog_get(profile=args.profile)
|
|
370
362
|
|
|
371
363
|
|
|
364
|
+
def _handle_button_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
365
|
+
return context.builder.app_custom_buttons_apply(
|
|
366
|
+
profile=args.profile,
|
|
367
|
+
app_key=args.app_key,
|
|
368
|
+
upsert_buttons=load_list_arg(args.upsert_buttons_file, option_name="--upsert-buttons-file"),
|
|
369
|
+
remove_buttons=load_list_arg(args.remove_buttons_file, option_name="--remove-buttons-file"),
|
|
370
|
+
view_configs=load_list_arg(args.view_configs_file, option_name="--view-configs-file"),
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def _handle_associated_resource_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
375
|
+
return context.builder.app_associated_resources_apply(
|
|
376
|
+
profile=args.profile,
|
|
377
|
+
app_key=args.app_key,
|
|
378
|
+
upsert_resources=load_list_arg(args.upsert_resources_file, option_name="--upsert-resources-file"),
|
|
379
|
+
remove_associated_item_ids=load_list_arg(args.remove_associated_item_ids_file, option_name="--remove-associated-item-ids-file"),
|
|
380
|
+
reorder_associated_item_ids=load_list_arg(args.reorder_associated_item_ids_file, option_name="--reorder-associated-item-ids-file"),
|
|
381
|
+
view_configs=load_list_arg(args.view_configs_file, option_name="--view-configs-file"),
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
|
|
372
385
|
def _handle_button_get(args: argparse.Namespace, context: CliContext) -> dict:
|
|
373
386
|
return context.builder.app_custom_button_get(profile=args.profile, app_key=args.app_key, button_id=args.button_id)
|
|
374
387
|
|
|
@@ -82,7 +82,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
82
82
|
|
|
83
83
|
insert = record_subparsers.add_parser("insert", help="新增记录")
|
|
84
84
|
insert.add_argument("--app-key", required=True)
|
|
85
|
-
insert.add_argument("--fields-file")
|
|
85
|
+
insert.add_argument("--fields-file", help=argparse.SUPPRESS)
|
|
86
86
|
insert.add_argument("--items-file")
|
|
87
87
|
insert.add_argument("--verify-write", action=argparse.BooleanOptionalAction, default=True)
|
|
88
88
|
insert.set_defaults(handler=_handle_insert, format_hint="")
|
|
@@ -289,8 +289,8 @@ def _handle_insert(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
289
289
|
)
|
|
290
290
|
if not args.fields_file:
|
|
291
291
|
raise_config_error(
|
|
292
|
-
"record insert requires --items-file
|
|
293
|
-
fix_hint="
|
|
292
|
+
"record insert requires --items-file.",
|
|
293
|
+
fix_hint="Use `record insert --app-key APP_KEY --items-file ITEMS.json`; a single insert is one item in the JSON array.",
|
|
294
294
|
)
|
|
295
295
|
return context.record.record_insert_public(
|
|
296
296
|
profile=args.profile,
|
|
@@ -136,11 +136,8 @@ BUILDER_PUBLIC_TOOL_SPECS: tuple[PublicToolSpec, ...] = (
|
|
|
136
136
|
PublicToolSpec(BUILDER_DOMAIN, "app_release_edit_lock_if_mine", ("app_release_edit_lock_if_mine",), ("builder", "app", "release-edit-lock-if-mine"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
|
|
137
137
|
PublicToolSpec(BUILDER_DOMAIN, "app_resolve", ("app_resolve",), ("builder", "app", "resolve"), has_contract=True, cli_show_effective_context=True),
|
|
138
138
|
PublicToolSpec(BUILDER_DOMAIN, "button_style_catalog_get", ("button_style_catalog_get",), ("builder", "button", "catalog"), has_contract=True, cli_show_effective_context=True),
|
|
139
|
-
PublicToolSpec(BUILDER_DOMAIN, "
|
|
140
|
-
PublicToolSpec(BUILDER_DOMAIN, "
|
|
141
|
-
PublicToolSpec(BUILDER_DOMAIN, "app_custom_button_create", ("app_custom_button_create",), ("builder", "button", "create"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
|
|
142
|
-
PublicToolSpec(BUILDER_DOMAIN, "app_custom_button_update", ("app_custom_button_update",), ("builder", "button", "update"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
|
|
143
|
-
PublicToolSpec(BUILDER_DOMAIN, "app_custom_button_delete", ("app_custom_button_delete",), ("builder", "button", "delete"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
|
|
139
|
+
PublicToolSpec(BUILDER_DOMAIN, "app_custom_buttons_apply", ("app_custom_buttons_apply",), ("builder", "button", "apply"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
|
|
140
|
+
PublicToolSpec(BUILDER_DOMAIN, "app_associated_resources_apply", ("app_associated_resources_apply",), ("builder", "associated-resource", "apply"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
|
|
144
141
|
PublicToolSpec(BUILDER_DOMAIN, "app_get", ("app_get",), ("builder", "app", "get", "summary"), has_contract=True, cli_show_effective_context=True),
|
|
145
142
|
PublicToolSpec(BUILDER_DOMAIN, "app_get_fields", ("app_get_fields",), ("builder", "app", "get", "fields"), has_contract=True, cli_show_effective_context=True),
|
|
146
143
|
PublicToolSpec(BUILDER_DOMAIN, "app_repair_code_blocks", ("app_repair_code_blocks",), ("builder", "app", "repair-code-blocks"), has_contract=True, cli_show_effective_context=True),
|
|
@@ -586,7 +586,7 @@ def _trim_detail_context_record_get(payload: JSONObject) -> None:
|
|
|
586
586
|
associated_resources = payload.get("associated_resources")
|
|
587
587
|
if isinstance(associated_resources, list):
|
|
588
588
|
payload["associated_resources"] = [
|
|
589
|
-
_pick(item, ("type", "name", "app_key", "app_name", "view_key", "chart_key", "view_type", "data_access"))
|
|
589
|
+
_pick(item, ("type", "resource_type", "name", "app_key", "app_name", "view_key", "chart_key", "view_type", "report_source", "data_access"))
|
|
590
590
|
for item in associated_resources
|
|
591
591
|
if isinstance(item, dict)
|
|
592
592
|
]
|
|
@@ -1057,11 +1057,8 @@ _register_policy(
|
|
|
1057
1057
|
"app_release_edit_lock_if_mine",
|
|
1058
1058
|
"app_resolve",
|
|
1059
1059
|
"button_style_catalog_get",
|
|
1060
|
-
"
|
|
1061
|
-
"
|
|
1062
|
-
"app_custom_button_create",
|
|
1063
|
-
"app_custom_button_update",
|
|
1064
|
-
"app_custom_button_delete",
|
|
1060
|
+
"app_custom_buttons_apply",
|
|
1061
|
+
"app_associated_resources_apply",
|
|
1065
1062
|
"app_get_fields",
|
|
1066
1063
|
"app_repair_code_blocks",
|
|
1067
1064
|
"app_get_layout",
|
|
@@ -36,9 +36,13 @@ def build_builder_server() -> FastMCP:
|
|
|
36
36
|
"Use builder_tool_contract when you need a machine-readable contract, aliases, allowed enums, or a minimal valid example for a public builder tool. "
|
|
37
37
|
"Use solution_install when the user explicitly wants to install a packaged solution/template by solution_key, optionally copying bundled demo data. "
|
|
38
38
|
"If creating or updating an app package may be appropriate, use package_apply with explicit user intent; otherwise use package_get and app_resolve to locate resources, "
|
|
39
|
-
"app_get
|
|
39
|
+
"app_get as the default app map read, then app_get_fields/app_repair_code_blocks/app_get_layout/app_get_views/app_get_flow/app_get_charts/portal_list/portal_get/view_get/chart_get for focused configuration reads, "
|
|
40
40
|
"member_search/role_search/role_create when workflow assignees must come from the directory or role catalog, preferring roles over explicit members unless the user explicitly names members, "
|
|
41
|
-
"then app_schema_apply/app_layout_apply/app_flow_apply/app_views_apply/app_charts_apply/portal_apply to execute normalized patches; these apply tools perform planning, normalization, and dependency checks internally where applicable. Schema/layout/views noop requests skip publish, charts are immediate-live without publish and resolve targets by chart_id first then exact unique chart name, portal updates use replace semantics only when sections are supplied and edit-mode base-info-only updates may omit sections, publish=false only guarantees draft/base-info updates, and flow should use publish=false whenever you only want draft/precheck behavior. "
|
|
41
|
+
"then app_schema_apply/app_layout_apply/app_flow_apply/app_views_apply/app_custom_buttons_apply/app_associated_resources_apply/app_charts_apply/portal_apply to execute normalized patches; these apply tools perform planning, normalization, and dependency checks internally where applicable. Schema/layout/views noop requests skip publish, app_custom_buttons_apply and app_associated_resources_apply publish after at least one write succeeds and expose no draft-only parameter, charts are immediate-live without publish and resolve targets by chart_id first then exact unique chart name, portal updates use replace semantics only when sections are supplied and edit-mode base-info-only updates may omit sections, publish=false only guarantees draft/base-info updates for tools that still expose that parameter, and flow should use publish=false whenever you only want draft/precheck behavior. "
|
|
42
|
+
"For app_schema_apply, configure data title and data cover directly in field JSON with as_data_title=true and as_data_cover=true; data title is required and exactly one field may be marked, while data cover is optional and must be a top-level attachment field. "
|
|
43
|
+
"For app_views_apply, keep fixed saved filters in filters and configure the frontend query panel separately with query_conditions; query_conditions.rows is a matrix of field names compiled to backend queryCondition queIds. "
|
|
44
|
+
"For custom button body create/update/delete and view placement, use app_custom_buttons_apply. For addData buttons, prefer trigger_add_data_config.target_app_key + field_mappings/default_values; do not ask agents to write raw que_relation unless maintaining a legacy config. View button bindings merge by default and merge-mode view_configs must include buttons; use view_configs[].mode=replace or explicit buttons=[] only when clearing/replacing existing bindings is intended. "
|
|
45
|
+
"For associated reports/views, use app_associated_resources_apply for both the app-level associated_resources pool and per-view display config; associated_item_id is the app-level form_asos_chart.id, not chart_id/chart_key. Do not ask agents to pass backend raw sourceType: views infer the internal Qingflow view source, reports default to BI app reports, and dataset reports use report_source=dataset. "
|
|
42
46
|
"For code_block fields with output bindings, always use qf_output assignment rather than const/let qf_output, and use app_repair_code_blocks when an existing form hangs because output-bound fields stay loading. "
|
|
43
47
|
"Use package_apply to manage package metadata, visibility, grouping, and ordering, and app_publish_verify for explicit final publish verification. "
|
|
44
48
|
"For workflow edits, keep the public builder surface on stable linear flows only: start/approve/fill/copy/webhook/end. Branch and condition nodes are intentionally disabled because the backend workflow route is not front-end stable for those node types. Declare node assignees and editable fields explicitly. "
|
|
@@ -295,29 +299,38 @@ def build_builder_server() -> FastMCP:
|
|
|
295
299
|
return ai_builder.button_style_catalog_get(profile=profile)
|
|
296
300
|
|
|
297
301
|
@server.tool()
|
|
298
|
-
def
|
|
299
|
-
return ai_builder.app_custom_button_list(profile=profile, app_key=app_key)
|
|
300
|
-
|
|
301
|
-
@server.tool()
|
|
302
|
-
def app_custom_button_get(profile: str = DEFAULT_PROFILE, app_key: str = "", button_id: int = 0) -> dict:
|
|
303
|
-
return ai_builder.app_custom_button_get(profile=profile, app_key=app_key, button_id=button_id)
|
|
304
|
-
|
|
305
|
-
@server.tool()
|
|
306
|
-
def app_custom_button_create(profile: str = DEFAULT_PROFILE, app_key: str = "", payload: dict | None = None) -> dict:
|
|
307
|
-
return ai_builder.app_custom_button_create(profile=profile, app_key=app_key, payload=payload or {})
|
|
308
|
-
|
|
309
|
-
@server.tool()
|
|
310
|
-
def app_custom_button_update(
|
|
302
|
+
def app_custom_buttons_apply(
|
|
311
303
|
profile: str = DEFAULT_PROFILE,
|
|
312
304
|
app_key: str = "",
|
|
313
|
-
|
|
314
|
-
|
|
305
|
+
upsert_buttons: list[dict] | None = None,
|
|
306
|
+
remove_buttons: list[dict] | None = None,
|
|
307
|
+
view_configs: list[dict] | None = None,
|
|
315
308
|
) -> dict:
|
|
316
|
-
return ai_builder.
|
|
309
|
+
return ai_builder.app_custom_buttons_apply(
|
|
310
|
+
profile=profile,
|
|
311
|
+
app_key=app_key,
|
|
312
|
+
upsert_buttons=upsert_buttons or [],
|
|
313
|
+
remove_buttons=remove_buttons or [],
|
|
314
|
+
view_configs=view_configs or [],
|
|
315
|
+
)
|
|
317
316
|
|
|
318
317
|
@server.tool()
|
|
319
|
-
def
|
|
320
|
-
|
|
318
|
+
def app_associated_resources_apply(
|
|
319
|
+
profile: str = DEFAULT_PROFILE,
|
|
320
|
+
app_key: str = "",
|
|
321
|
+
upsert_resources: list[dict] | None = None,
|
|
322
|
+
remove_associated_item_ids: list[int] | None = None,
|
|
323
|
+
reorder_associated_item_ids: list[int] | None = None,
|
|
324
|
+
view_configs: list[dict] | None = None,
|
|
325
|
+
) -> dict:
|
|
326
|
+
return ai_builder.app_associated_resources_apply(
|
|
327
|
+
profile=profile,
|
|
328
|
+
app_key=app_key,
|
|
329
|
+
upsert_resources=upsert_resources or [],
|
|
330
|
+
remove_associated_item_ids=remove_associated_item_ids or [],
|
|
331
|
+
reorder_associated_item_ids=reorder_associated_item_ids or [],
|
|
332
|
+
view_configs=view_configs or [],
|
|
333
|
+
)
|
|
321
334
|
|
|
322
335
|
@server.tool()
|
|
323
336
|
def app_get(profile: str = DEFAULT_PROFILE, app_key: str = "") -> dict:
|