@qingflow-tech/qingflow-cli 1.1.61-beta.5 → 1.1.61
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 +3 -3
- package/package.json +1 -1
- package/pyproject.toml +1 -2
- package/skills/qingflow-cli/SKILL.md +2 -104
- package/skills/qingflow-cli/reference/builder/20-build-complete-system.md +2 -2
- package/skills/qingflow-cli/reference/builder/30-schema-fields.md +5 -22
- package/skills/qingflow-cli/reference/builder/70-portal.md +16 -37
- package/skills/qingflow-cli/reference/builder/reference/app-delivery-sop.md +3 -3
- package/skills/qingflow-cli/reference/examples/portal/portal_sections_all_types.example.json +7 -6
- package/src/qingflow_mcp/app_form_compat.py +0 -3
- package/src/qingflow_mcp/app_form_validator.py +20 -89
- package/src/qingflow_mcp/builder_facade/app_form.py +112 -629
- package/src/qingflow_mcp/builder_facade/models.py +13 -164
- package/src/qingflow_mcp/builder_facade/schemas/app_form/manifest.json +1 -15
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/complete.json +38 -112
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/address.json +8 -31
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/amount.json +10 -6
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/attachment.json +10 -33
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/code_block.json +10 -6
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/data_relation.json +18 -11
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/date.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/datetime.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/department.json +10 -6
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/email.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/link.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/long_text.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/member.json +10 -6
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/multi_select.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/number.json +10 -6
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/phone.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/q_linker.json +12 -8
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/relation.json +15 -73
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/single_choice.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/single_select.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/subtable.json +29 -90
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/text.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/formula.json +0 -43
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/summary.json +19 -27
- package/src/qingflow_mcp/builder_facade/service.py +72 -1143
- package/src/qingflow_mcp/cli/commands/builder.py +5 -114
- package/src/qingflow_mcp/cli/main.py +1 -34
- package/src/qingflow_mcp/cloud_context.py +1 -16
- package/src/qingflow_mcp/public_surface.py +8 -69
- package/src/qingflow_mcp/response_trim.py +1 -2
- package/src/qingflow_mcp/server.py +6 -12
- package/src/qingflow_mcp/server_app_builder.py +6 -41
- package/src/qingflow_mcp/server_app_user.py +5 -9
- package/src/qingflow_mcp/server_cloud.py +7 -43
- package/src/qingflow_mcp/solution/compiler/icon_utils.py +0 -73
- package/src/qingflow_mcp/solution/executor.py +2 -5
- package/src/qingflow_mcp/tools/ai_builder_tools.py +12 -110
- package/src/qingflow_mcp/tools/portal_tools.py +54 -4
- package/src/qingflow_mcp/tools/solution_tools.py +0 -2
- package/skills/qingflow-cli/reference/examples/portal/portal_apply_v1alpha1.example.json +0 -100
- package/src/qingflow_mcp/builder_facade/portals_schema.py +0 -397
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/ai_content_classification.json +0 -146
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/ai_content_extraction.json +0 -146
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/ai_content_generation.json +0 -146
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/ai_document_parsing.json +0 -146
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/ai_image_generation.json +0 -146
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/ai_image_recognition.json +0 -146
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/ai_machine_translation.json +0 -146
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/ai_text_summary.json +0 -146
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/date_duration.json +0 -146
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/embed_field.json +0 -146
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/image_select.json +0 -146
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/location.json +0 -146
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/ocr.json +0 -146
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/rich_text.json +0 -146
- package/src/qingflow_mcp/builder_facade/schemas/portals/manifest.json +0 -59
- package/src/qingflow_mcp/builder_facade/schemas/portals/v1alpha1/complete.json +0 -1397
- package/src/qingflow_mcp/builder_facade/schemas/portals/v1alpha1/components/carousel.json +0 -193
- package/src/qingflow_mcp/builder_facade/schemas/portals/v1alpha1/components/chart.json +0 -643
- package/src/qingflow_mcp/builder_facade/schemas/portals/v1alpha1/components/favourite.json +0 -203
- package/src/qingflow_mcp/builder_facade/schemas/portals/v1alpha1/components/filter.json +0 -268
- package/src/qingflow_mcp/builder_facade/schemas/portals/v1alpha1/components/grid.json +0 -265
- package/src/qingflow_mcp/builder_facade/schemas/portals/v1alpha1/components/link.json +0 -131
- package/src/qingflow_mcp/builder_facade/schemas/portals/v1alpha1/components/task.json +0 -199
- package/src/qingflow_mcp/builder_facade/schemas/portals/v1alpha1/components/text.json +0 -123
- package/src/qingflow_mcp/builder_facade/schemas/portals/v1alpha1/components/view.json +0 -196
- package/src/qingflow_mcp/builder_facade/schemas/portals/v1alpha1/summary.json +0 -800
- package/src/qingflow_mcp/tool_catalog.py +0 -387
- package/src/qingflow_mcp/tool_catalog_i18n.py +0 -185
- package/src/qingflow_mcp/tool_policy.py +0 -197
- package/src/qingflow_mcp/tools/portal_writer.py +0 -59
|
@@ -6,15 +6,11 @@ from typing import Any
|
|
|
6
6
|
|
|
7
7
|
import anyio
|
|
8
8
|
import uvicorn
|
|
9
|
+
from mcp.server.fastmcp import FastMCP
|
|
10
|
+
|
|
9
11
|
from .backend_client import BackendClient
|
|
10
12
|
from .cloud_auth import CloudAuthBroker
|
|
11
|
-
from .cloud_context import
|
|
12
|
-
CloudMcpContext,
|
|
13
|
-
current_call_platform,
|
|
14
|
-
current_cloud_context,
|
|
15
|
-
current_http_tool_policy,
|
|
16
|
-
current_tool_policy_header,
|
|
17
|
-
)
|
|
13
|
+
from .cloud_context import CloudMcpContext, current_call_platform, current_cloud_context
|
|
18
14
|
from .cloud_oauth_proxy import (
|
|
19
15
|
CloudOAuthProxyConfig,
|
|
20
16
|
OAuthAccessTokenResolver,
|
|
@@ -25,8 +21,6 @@ from .config import get_cloud_http_allowed_hosts, get_cloud_http_host, get_cloud
|
|
|
25
21
|
from .errors import QingflowApiError, is_auth_like_error
|
|
26
22
|
from .server_app_builder import build_builder_server
|
|
27
23
|
from .server_app_user import build_user_server
|
|
28
|
-
from .tool_catalog import combined_server_bindings
|
|
29
|
-
from .tool_policy import PolicyAwareFastMCP, configure_tool_policy, parse_http_tool_policy
|
|
30
24
|
|
|
31
25
|
|
|
32
26
|
BUILDER_TOOL_PREFIX = "builder_"
|
|
@@ -170,18 +164,12 @@ class CloudMcpAuthApp:
|
|
|
170
164
|
cloud_context.token,
|
|
171
165
|
)
|
|
172
166
|
|
|
173
|
-
tool_policy_header = _extract_header(scope, "x-qingflow-tool-policy")
|
|
174
|
-
http_tool_policy = parse_http_tool_policy(tool_policy_header, bytes(scope.get("query_string") or b""))
|
|
175
167
|
context_token = current_cloud_context.set(cloud_context)
|
|
176
168
|
call_platform_token = current_call_platform.set(call_platform)
|
|
177
|
-
tool_policy_token = current_tool_policy_header.set(tool_policy_header)
|
|
178
|
-
http_tool_policy_token = current_http_tool_policy.set(http_tool_policy)
|
|
179
169
|
try:
|
|
180
170
|
scope = _ensure_streamable_http_accept(scope)
|
|
181
171
|
await self._app(scope, receive, send)
|
|
182
172
|
finally:
|
|
183
|
-
current_http_tool_policy.reset(http_tool_policy_token)
|
|
184
|
-
current_tool_policy_header.reset(tool_policy_token)
|
|
185
173
|
current_call_platform.reset(call_platform_token)
|
|
186
174
|
current_cloud_context.reset(context_token)
|
|
187
175
|
|
|
@@ -409,29 +397,16 @@ def build_cloud_app() -> CloudMcpAuthApp:
|
|
|
409
397
|
)
|
|
410
398
|
|
|
411
399
|
|
|
412
|
-
def build_combined_server() ->
|
|
400
|
+
def build_combined_server() -> FastMCP:
|
|
413
401
|
server = build_user_server(stateless_http=True)
|
|
414
402
|
builder_server = build_builder_server()
|
|
415
|
-
user_tool_names = set(server._tool_manager._tools)
|
|
416
|
-
builder_tool_names = set(builder_server._tool_manager._tools)
|
|
417
403
|
server._mcp_server.name = "Qingflow Combined MCP"
|
|
418
404
|
_register_prefixed_builder_tools(server, builder_server)
|
|
419
405
|
_set_combined_instructions(server, builder_server)
|
|
420
|
-
return
|
|
421
|
-
server,
|
|
422
|
-
combined_server_bindings(
|
|
423
|
-
user_tool_names,
|
|
424
|
-
builder_tool_names,
|
|
425
|
-
builder_prefix=BUILDER_TOOL_PREFIX,
|
|
426
|
-
),
|
|
427
|
-
source="request",
|
|
428
|
-
)
|
|
406
|
+
return server
|
|
429
407
|
|
|
430
408
|
|
|
431
|
-
def _register_prefixed_builder_tools(
|
|
432
|
-
target_server: PolicyAwareFastMCP,
|
|
433
|
-
builder_server: PolicyAwareFastMCP,
|
|
434
|
-
) -> None:
|
|
409
|
+
def _register_prefixed_builder_tools(target_server: FastMCP, builder_server: FastMCP) -> None:
|
|
435
410
|
for original_name, tool in builder_server._tool_manager._tools.items():
|
|
436
411
|
target_server._tool_manager.add_tool(
|
|
437
412
|
tool.fn,
|
|
@@ -444,10 +419,7 @@ def _register_prefixed_builder_tools(
|
|
|
444
419
|
)
|
|
445
420
|
|
|
446
421
|
|
|
447
|
-
def _set_combined_instructions(
|
|
448
|
-
user_server: PolicyAwareFastMCP,
|
|
449
|
-
builder_server: PolicyAwareFastMCP,
|
|
450
|
-
) -> None:
|
|
422
|
+
def _set_combined_instructions(user_server: FastMCP, builder_server: FastMCP) -> None:
|
|
451
423
|
user_instructions = user_server.instructions or ""
|
|
452
424
|
builder_instructions = builder_server.instructions or ""
|
|
453
425
|
user_server._mcp_server.instructions = f"""Use this combined Qingflow MCP server for both user operational workflows and builder workflows.
|
|
@@ -482,14 +454,6 @@ def _extract_host(scope: dict[str, Any]) -> str:
|
|
|
482
454
|
return ""
|
|
483
455
|
|
|
484
456
|
|
|
485
|
-
def _extract_header(scope: dict[str, Any], name: str) -> str | None:
|
|
486
|
-
expected = name.lower().encode("latin1")
|
|
487
|
-
for key, value in scope.get("headers", []):
|
|
488
|
-
if key.lower() == expected:
|
|
489
|
-
return value.decode("utf-8", errors="replace")
|
|
490
|
-
return None
|
|
491
|
-
|
|
492
|
-
|
|
493
457
|
async def _read_request_body(
|
|
494
458
|
receive: Callable[[], Awaitable[dict[str, Any]]],
|
|
495
459
|
limit: int,
|
|
@@ -312,13 +312,6 @@ def parse_workspace_icon(value: str | None) -> tuple[str | None, str | None, str
|
|
|
312
312
|
stripped = str(value).strip()
|
|
313
313
|
if not stripped:
|
|
314
314
|
return None, None, None
|
|
315
|
-
if _looks_like_icon_url(stripped):
|
|
316
|
-
# URL 形态旧图标(如 https://…/dashboard_security.png)不是目录图标:目录名与
|
|
317
|
-
# 色均无法表达。返回 (None, None, None) 而不是按关键字把 URL 归一成假目录名
|
|
318
|
-
# ('dashboard' 命中 → 'view-grid')——后者经编译重编码会销毁存量 URL 图标,
|
|
319
|
-
# 并派生出不存在的默认色(r5 实测 50 门户 color diff 根因)。声明面按「未表达」
|
|
320
|
-
# 处理(读面 gap 记账);更新语义 apply 不触碰未指定图标,URL 原样存活。
|
|
321
|
-
return None, None, None
|
|
322
315
|
if _looks_like_icon_json(stripped):
|
|
323
316
|
try:
|
|
324
317
|
payload = json.loads(stripped)
|
|
@@ -331,11 +324,6 @@ def parse_workspace_icon(value: str | None) -> tuple[str | None, str | None, str
|
|
|
331
324
|
return normalize_workspace_icon_name(stripped), None, None
|
|
332
325
|
|
|
333
326
|
|
|
334
|
-
def _looks_like_icon_url(value: str) -> bool:
|
|
335
|
-
lowered = value.strip().lower()
|
|
336
|
-
return lowered.startswith(("http://", "https://", "//"))
|
|
337
|
-
|
|
338
|
-
|
|
339
327
|
def workspace_icon_config(value: str | None) -> dict[str, str | None]:
|
|
340
328
|
icon_name, icon_color, icon_text = parse_workspace_icon(value)
|
|
341
329
|
raw = str(value).strip() if value not in (None, "") else None
|
|
@@ -454,67 +442,6 @@ def encode_workspace_icon_with_defaults(
|
|
|
454
442
|
)
|
|
455
443
|
|
|
456
444
|
|
|
457
|
-
def update_workspace_icon_from_existing(
|
|
458
|
-
*,
|
|
459
|
-
icon: str | None,
|
|
460
|
-
color: str | None,
|
|
461
|
-
title: str,
|
|
462
|
-
existing_icon: str | None,
|
|
463
|
-
fallback_icon_name: str | None = None,
|
|
464
|
-
) -> str:
|
|
465
|
-
"""更新语义的 dashIcon 重编码(round-trip 保真)。
|
|
466
|
-
|
|
467
|
-
后端 updateDashBaseInfo 对 dashIcon 无校验直接透传落库;生产存量存在三类非目录形态:
|
|
468
|
-
文字图标(iconName 空 + iconText=名字首字)、旧版 URL 图标(icon 键携带 URL)、
|
|
469
|
-
空(iconName/iconText 均空)。encode_workspace_icon_with_defaults 的 fallback 语义
|
|
470
|
-
(为新建资源合成目录图标)会把「声明 icon 未指定」的存量形态重写为 fallback 图标。
|
|
471
|
-
|
|
472
|
-
更新语义规则:
|
|
473
|
-
- 声明 icon 显式指定且与存量 iconName 归一后一致 → JSON 级最小 patch:仅当声明
|
|
474
|
-
显式指定了不同 color 才改写 iconColor,否则存量 JSON 原串透传(byte 级保真);
|
|
475
|
-
iconText 等非目录形态键始终保留——生产存量存在 iconName+iconText 同设形态
|
|
476
|
-
(r7 全量实测 48 门),全量重编码会把 iconText 清成 null。
|
|
477
|
-
- 声明 icon 显式指定且为其他图标 → 走 encode(用户意图覆写);
|
|
478
|
-
- 声明 icon 未指定且无存量 → 走 encode_with_defaults(创建/首次合成);
|
|
479
|
-
- 声明 icon 未指定且有存量:
|
|
480
|
-
- color 也未指定 → 原样保留存量 JSON;
|
|
481
|
-
- color 指定 → JSON 级最小 patch(仅替换 iconColor,iconName/iconText/icon URL
|
|
482
|
-
等存量形态键原样保留)。
|
|
483
|
-
"""
|
|
484
|
-
existing_payload = str(existing_icon or "").strip()
|
|
485
|
-
if existing_payload and _looks_like_icon_json(existing_payload):
|
|
486
|
-
try:
|
|
487
|
-
parsed = json.loads(existing_payload)
|
|
488
|
-
except ValueError:
|
|
489
|
-
parsed = None
|
|
490
|
-
if isinstance(parsed, dict):
|
|
491
|
-
existing_name, existing_color, _ = parse_workspace_icon(existing_payload)
|
|
492
|
-
if icon is None:
|
|
493
|
-
if color is None:
|
|
494
|
-
return existing_payload
|
|
495
|
-
patched = dict(parsed)
|
|
496
|
-
patched["iconColor"] = color
|
|
497
|
-
return json.dumps(patched, ensure_ascii=False, separators=(",", ":"))
|
|
498
|
-
if (
|
|
499
|
-
existing_name is not None
|
|
500
|
-
and not _looks_like_icon_json(icon)
|
|
501
|
-
and not _looks_like_icon_url(icon)
|
|
502
|
-
and normalize_workspace_icon_name(icon) == existing_name
|
|
503
|
-
):
|
|
504
|
-
if color is None or color == existing_color:
|
|
505
|
-
return existing_payload
|
|
506
|
-
patched = dict(parsed)
|
|
507
|
-
patched["iconColor"] = color
|
|
508
|
-
return json.dumps(patched, ensure_ascii=False, separators=(",", ":"))
|
|
509
|
-
return encode_workspace_icon_with_defaults(
|
|
510
|
-
icon=icon,
|
|
511
|
-
color=color,
|
|
512
|
-
title=title,
|
|
513
|
-
fallback_icon_name=fallback_icon_name,
|
|
514
|
-
existing_icon=existing_icon,
|
|
515
|
-
)
|
|
516
|
-
|
|
517
|
-
|
|
518
445
|
def normalize_workspace_icon_name(icon: str | None) -> str | None:
|
|
519
446
|
if not icon:
|
|
520
447
|
return None
|
|
@@ -10,7 +10,6 @@ from ..tools.app_tools import AppTools
|
|
|
10
10
|
from ..tools.navigation_tools import NavigationTools
|
|
11
11
|
from ..tools.package_tools import PackageTools
|
|
12
12
|
from ..tools.portal_tools import PortalTools
|
|
13
|
-
from ..tools.portal_writer import PortalWriter
|
|
14
13
|
from ..tools.qingbi_report_tools import QingbiReportTools
|
|
15
14
|
from ..tools.record_tools import RecordTools
|
|
16
15
|
from ..tools.role_tools import RoleTools
|
|
@@ -40,7 +39,6 @@ class SolutionExecutor:
|
|
|
40
39
|
view_writer: "ViewWriter | None" = None,
|
|
41
40
|
chart_tools: QingbiReportTools,
|
|
42
41
|
portal_tools: PortalTools,
|
|
43
|
-
portal_writer: "PortalWriter | None" = None,
|
|
44
42
|
navigation_tools: NavigationTools,
|
|
45
43
|
workflow_tools: Any | None = None,
|
|
46
44
|
) -> None:
|
|
@@ -53,7 +51,6 @@ class SolutionExecutor:
|
|
|
53
51
|
self.view_writer = view_writer
|
|
54
52
|
self.chart_tools = chart_tools
|
|
55
53
|
self.portal_tools = portal_tools
|
|
56
|
-
self.portal_writer = portal_writer or PortalWriter(portal_tools.sessions, portal_tools.backend)
|
|
57
54
|
self.navigation_tools = navigation_tools
|
|
58
55
|
self.workflow_tools = workflow_tools
|
|
59
56
|
|
|
@@ -627,7 +624,7 @@ class SolutionExecutor:
|
|
|
627
624
|
result = portal_artifact
|
|
628
625
|
if not dash_key:
|
|
629
626
|
create_payload = self._resolve_portal_payload(compiled.portal_plan["create_payload"], store, base_payload=None)
|
|
630
|
-
result = self.
|
|
627
|
+
result = self.portal_tools.portal_create(profile=profile, payload=create_payload)
|
|
631
628
|
portal_result = result.get("result") or {}
|
|
632
629
|
dash_key = portal_result.get("dashKey")
|
|
633
630
|
store.set_artifact("portal", "result", result)
|
|
@@ -658,7 +655,7 @@ class SolutionExecutor:
|
|
|
658
655
|
),
|
|
659
656
|
)
|
|
660
657
|
update_payload = self._resolve_portal_payload(compiled.portal_plan["update_payload"], store, base_payload=base_payload)
|
|
661
|
-
self.
|
|
658
|
+
self.portal_tools.portal_update(profile=profile, dash_key=dash_key, payload=update_payload)
|
|
662
659
|
self._refresh_portal_artifact(profile=profile, store=store, being_draft=True, artifact_key="draft_result")
|
|
663
660
|
|
|
664
661
|
def _build_navigation(self, profile: str, compiled: CompiledSolution, store: RunArtifactStore) -> None:
|
|
@@ -699,36 +699,13 @@ class AiBuilderTools(ToolBase):
|
|
|
699
699
|
def portal_list(profile: str = DEFAULT_PROFILE) -> JSONObject:
|
|
700
700
|
return self.portal_list(profile=profile)
|
|
701
701
|
|
|
702
|
-
@mcp.tool()
|
|
703
|
-
def portal_schema(
|
|
704
|
-
schema_version: str | None = None,
|
|
705
|
-
schema_kind: str | None = None,
|
|
706
|
-
) -> JSONObject:
|
|
707
|
-
"""Get a complete portals schema or a focused component schema such as chart, view, grid, or carousel."""
|
|
708
|
-
return self.portal_schema(schema_version=schema_version, schema_kind=schema_kind)
|
|
709
|
-
|
|
710
|
-
@mcp.tool()
|
|
711
|
-
def portal_validate(
|
|
712
|
-
declaration: JSONObject | None = None,
|
|
713
|
-
schema_version: str | None = None,
|
|
714
|
-
) -> JSONObject:
|
|
715
|
-
return self.portal_validate(declaration=declaration or {}, schema_version=schema_version)
|
|
716
|
-
|
|
717
702
|
@mcp.tool()
|
|
718
703
|
def portal_get(
|
|
719
704
|
profile: str = DEFAULT_PROFILE,
|
|
720
705
|
dash_key: str = "",
|
|
721
706
|
being_draft: bool = True,
|
|
722
|
-
schema_kind: str = "summary",
|
|
723
|
-
schema_version: str | None = None,
|
|
724
707
|
) -> JSONObject:
|
|
725
|
-
return self.portal_get(
|
|
726
|
-
profile=profile,
|
|
727
|
-
dash_key=dash_key,
|
|
728
|
-
being_draft=being_draft,
|
|
729
|
-
schema_kind=schema_kind,
|
|
730
|
-
schema_version=schema_version,
|
|
731
|
-
)
|
|
708
|
+
return self.portal_get(profile=profile, dash_key=dash_key, being_draft=being_draft)
|
|
732
709
|
|
|
733
710
|
@mcp.tool()
|
|
734
711
|
def view_get(profile: str = DEFAULT_PROFILE, view_key: str = "") -> JSONObject:
|
|
@@ -868,10 +845,6 @@ class AiBuilderTools(ToolBase):
|
|
|
868
845
|
color: str | None = None,
|
|
869
846
|
hide_copyright: bool | None = None,
|
|
870
847
|
dash_global_config: JSONObject | None = None,
|
|
871
|
-
global_config: JSONObject | None = None,
|
|
872
|
-
style_template_id: int | None = None,
|
|
873
|
-
overall_style: JSONObject | None = None,
|
|
874
|
-
schema_version: str | None = None,
|
|
875
848
|
config: JSONObject | None = None,
|
|
876
849
|
payload: JSONObject | None = None,
|
|
877
850
|
patch_sections: list[JSONObject] | None = None,
|
|
@@ -910,10 +883,6 @@ class AiBuilderTools(ToolBase):
|
|
|
910
883
|
color=color,
|
|
911
884
|
hide_copyright=hide_copyright,
|
|
912
885
|
dash_global_config=dash_global_config,
|
|
913
|
-
global_config=global_config,
|
|
914
|
-
style_template_id=style_template_id,
|
|
915
|
-
overall_style=overall_style,
|
|
916
|
-
schema_version=schema_version,
|
|
917
886
|
config=config or {},
|
|
918
887
|
payload=payload,
|
|
919
888
|
patch_sections=patch_sections,
|
|
@@ -2083,58 +2052,16 @@ class AiBuilderTools(ToolBase):
|
|
|
2083
2052
|
suggested_next_call={"tool_name": "portal_list", "arguments": {"profile": profile}},
|
|
2084
2053
|
))
|
|
2085
2054
|
|
|
2086
|
-
def portal_get(
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
profile: str,
|
|
2090
|
-
dash_key: str,
|
|
2091
|
-
being_draft: bool = True,
|
|
2092
|
-
schema_kind: str = "summary",
|
|
2093
|
-
schema_version: str | None = None,
|
|
2094
|
-
) -> JSONObject:
|
|
2095
|
-
"""读取门户详情;schema_kind=summary|complete 返回 canonical 声明,raw 为原始归一响应。"""
|
|
2096
|
-
normalized_args = {"dash_key": dash_key, "being_draft": being_draft, "schema_kind": schema_kind, "schema_version": schema_version}
|
|
2055
|
+
def portal_get(self, *, profile: str, dash_key: str, being_draft: bool = True) -> JSONObject:
|
|
2056
|
+
"""执行门户相关逻辑。"""
|
|
2057
|
+
normalized_args = {"dash_key": dash_key, "being_draft": being_draft}
|
|
2097
2058
|
return _publicize_package_fields(_safe_tool_call(
|
|
2098
|
-
lambda: self._facade.portal_get(
|
|
2099
|
-
profile=profile,
|
|
2100
|
-
dash_key=dash_key,
|
|
2101
|
-
being_draft=being_draft,
|
|
2102
|
-
schema_kind=schema_kind,
|
|
2103
|
-
schema_version=schema_version,
|
|
2104
|
-
),
|
|
2059
|
+
lambda: self._facade.portal_get(profile=profile, dash_key=dash_key, being_draft=being_draft),
|
|
2105
2060
|
error_code="PORTAL_GET_FAILED",
|
|
2106
2061
|
normalized_args=normalized_args,
|
|
2107
2062
|
suggested_next_call={"tool_name": "portal_get", "arguments": {"profile": profile, **normalized_args}},
|
|
2108
2063
|
))
|
|
2109
2064
|
|
|
2110
|
-
def portal_schema(
|
|
2111
|
-
self,
|
|
2112
|
-
*,
|
|
2113
|
-
schema_version: str | None = None,
|
|
2114
|
-
schema_kind: str | None = None,
|
|
2115
|
-
) -> JSONObject:
|
|
2116
|
-
"""读取版本化门户声明 schema(纯离线)。"""
|
|
2117
|
-
return _publicize_package_fields(_safe_tool_call(
|
|
2118
|
-
lambda: self._facade.portal_schema(schema_version=schema_version, schema_kind=schema_kind),
|
|
2119
|
-
error_code="PORTAL_SCHEMA_FAILED",
|
|
2120
|
-
normalized_args={"schema_version": schema_version, "schema_kind": schema_kind},
|
|
2121
|
-
suggested_next_call={"tool_name": "portal_schema", "arguments": {"schema_kind": "summary"}},
|
|
2122
|
-
))
|
|
2123
|
-
|
|
2124
|
-
def portal_validate(
|
|
2125
|
-
self,
|
|
2126
|
-
*,
|
|
2127
|
-
declaration: JSONObject,
|
|
2128
|
-
schema_version: str | None = None,
|
|
2129
|
-
) -> JSONObject:
|
|
2130
|
-
"""纯离线校验一条门户声明(不访问后端)。"""
|
|
2131
|
-
return _publicize_package_fields(_safe_tool_call(
|
|
2132
|
-
lambda: self._facade.portal_validate(declaration=declaration, schema_version=schema_version),
|
|
2133
|
-
error_code="PORTAL_VALIDATE_FAILED",
|
|
2134
|
-
normalized_args={"schema_version": schema_version},
|
|
2135
|
-
suggested_next_call={"tool_name": "portal_schema", "arguments": {"schema_kind": "complete"}},
|
|
2136
|
-
))
|
|
2137
|
-
|
|
2138
2065
|
def portal_read_summary(self, *, profile: str, dash_key: str, being_draft: bool = True) -> JSONObject:
|
|
2139
2066
|
"""执行门户相关逻辑。"""
|
|
2140
2067
|
normalized_args = {"dash_key": dash_key, "being_draft": being_draft}
|
|
@@ -4306,10 +4233,6 @@ class AiBuilderTools(ToolBase):
|
|
|
4306
4233
|
color: str | None = None,
|
|
4307
4234
|
hide_copyright: bool | None = None,
|
|
4308
4235
|
dash_global_config: JSONObject | None = None,
|
|
4309
|
-
global_config: JSONObject | None = None,
|
|
4310
|
-
style_template_id: int | None = None,
|
|
4311
|
-
overall_style: JSONObject | None = None,
|
|
4312
|
-
schema_version: str | None = None,
|
|
4313
4236
|
config: JSONObject | None = None,
|
|
4314
4237
|
payload: JSONObject | None = None,
|
|
4315
4238
|
patch_sections: list[JSONObject] | None = None,
|
|
@@ -4361,14 +4284,6 @@ class AiBuilderTools(ToolBase):
|
|
|
4361
4284
|
request_payload["hide_copyright"] = hide_copyright
|
|
4362
4285
|
if dash_global_config is not None:
|
|
4363
4286
|
request_payload["dash_global_config"] = dash_global_config
|
|
4364
|
-
if global_config is not None:
|
|
4365
|
-
request_payload["global_config"] = global_config
|
|
4366
|
-
if style_template_id is not None:
|
|
4367
|
-
request_payload["style_template_id"] = style_template_id
|
|
4368
|
-
if overall_style is not None:
|
|
4369
|
-
request_payload["overall_style"] = overall_style
|
|
4370
|
-
if schema_version is not None:
|
|
4371
|
-
request_payload["schema_version"] = schema_version
|
|
4372
4287
|
if config:
|
|
4373
4288
|
merged_config = dict(request_payload.get("config") or {}) if isinstance(request_payload.get("config"), dict) else {}
|
|
4374
4289
|
merged_config.update(config)
|
|
@@ -4400,24 +4315,16 @@ class AiBuilderTools(ToolBase):
|
|
|
4400
4315
|
))
|
|
4401
4316
|
normalized_args = request.model_dump(mode="json")
|
|
4402
4317
|
normalized_args["package_id"] = normalized_args.pop("package_tag_id", package_id)
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
icon_failure = _validate_workspace_icon_for_builder(
|
|
4410
|
-
tool_name="portal_apply",
|
|
4411
|
-
icon=str(request.icon or ""),
|
|
4412
|
-
color=str(request.color or ""),
|
|
4413
|
-
creating=True,
|
|
4414
|
-
)
|
|
4415
|
-
else:
|
|
4416
|
-
icon_failure = None
|
|
4318
|
+
icon_failure = _validate_workspace_icon_for_builder(
|
|
4319
|
+
tool_name="portal_apply",
|
|
4320
|
+
icon=str(request.icon or ""),
|
|
4321
|
+
color=str(request.color or ""),
|
|
4322
|
+
creating=not bool(str(request.dash_key or "").strip()),
|
|
4323
|
+
)
|
|
4417
4324
|
if icon_failure is not None:
|
|
4418
4325
|
return self._attach_apply_envelope(profile, "portal_apply", icon_failure)
|
|
4419
4326
|
result = _publicize_package_fields(_safe_tool_call(
|
|
4420
|
-
lambda: self._facade.portal_apply(profile=profile, request=request
|
|
4327
|
+
lambda: self._facade.portal_apply(profile=profile, request=request),
|
|
4421
4328
|
error_code="PORTAL_APPLY_FAILED",
|
|
4422
4329
|
normalized_args=normalized_args,
|
|
4423
4330
|
suggested_next_call={"tool_name": "portal_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
@@ -5831,11 +5738,6 @@ def _publicize_package_fields(value):
|
|
|
5831
5738
|
}
|
|
5832
5739
|
public: JSONObject = {}
|
|
5833
5740
|
for key, item in value.items():
|
|
5834
|
-
if key == "declaration":
|
|
5835
|
-
# canonical 声明保持 schema 契约命名(package_tag_id 等),不做公开名重命名,
|
|
5836
|
-
# 否则 get 投影的 declaration 无法直接回喂 validate/apply(round-trip 破坏)。
|
|
5837
|
-
public[key] = item
|
|
5838
|
-
continue
|
|
5839
5741
|
public_key = key_map.get(key, key)
|
|
5840
5742
|
public[public_key] = _publicize_package_fields(item)
|
|
5841
5743
|
return public
|
|
@@ -14,10 +14,8 @@ class PortalTools(ToolBase):
|
|
|
14
14
|
类型:门户配置工具。
|
|
15
15
|
主要职责:
|
|
16
16
|
1. 查询门户列表与详情;
|
|
17
|
-
2.
|
|
18
|
-
|
|
19
|
-
创建/更新写入已下沉 ``PortalWriter``(tools/portal_writer.py),
|
|
20
|
-
公开层唯一入口是 ``builder portal apply`` / ``portal_apply``。
|
|
17
|
+
2. 维护门户基础信息与配置;
|
|
18
|
+
3. 执行门户发布与删除操作。
|
|
21
19
|
"""
|
|
22
20
|
|
|
23
21
|
def register(self, mcp: FastMCP) -> None:
|
|
@@ -34,6 +32,18 @@ class PortalTools(ToolBase):
|
|
|
34
32
|
def portal_get_base_info(profile: str = DEFAULT_PROFILE, dash_key: str = "") -> JSONObject:
|
|
35
33
|
return self.portal_get_base_info(profile=profile, dash_key=dash_key)
|
|
36
34
|
|
|
35
|
+
@mcp.tool()
|
|
36
|
+
def portal_create(profile: str = DEFAULT_PROFILE, payload: JSONObject | None = None) -> JSONObject:
|
|
37
|
+
return self.portal_create(profile=profile, payload=payload or {})
|
|
38
|
+
|
|
39
|
+
@mcp.tool(description=self._high_risk_tool_description(operation="update", target="portal base settings"))
|
|
40
|
+
def portal_update_base_info(profile: str = DEFAULT_PROFILE, dash_key: str = "", payload: JSONObject | None = None) -> JSONObject:
|
|
41
|
+
return self.portal_update_base_info(profile=profile, dash_key=dash_key, payload=payload or {})
|
|
42
|
+
|
|
43
|
+
@mcp.tool(description=self._high_risk_tool_description(operation="update", target="portal configuration"))
|
|
44
|
+
def portal_update(profile: str = DEFAULT_PROFILE, dash_key: str = "", payload: JSONObject | None = None) -> JSONObject:
|
|
45
|
+
return self.portal_update(profile=profile, dash_key=dash_key, payload=payload or {})
|
|
46
|
+
|
|
37
47
|
@mcp.tool(description=self._high_risk_tool_description(operation="delete", target="portal configuration"))
|
|
38
48
|
def portal_delete(profile: str = DEFAULT_PROFILE, dash_key: str = "") -> JSONObject:
|
|
39
49
|
return self.portal_delete(profile=profile, dash_key=dash_key)
|
|
@@ -60,6 +70,16 @@ class PortalTools(ToolBase):
|
|
|
60
70
|
|
|
61
71
|
return self._run(profile, runner, tool_name='门户详情')
|
|
62
72
|
|
|
73
|
+
def portal_create(self, *, profile: str, payload: JSONObject) -> JSONObject:
|
|
74
|
+
"""执行门户相关逻辑。"""
|
|
75
|
+
body = self._require_dict(payload)
|
|
76
|
+
|
|
77
|
+
def runner(session_profile, context):
|
|
78
|
+
result = self.backend.request("POST", context, "/dash", json_body=body)
|
|
79
|
+
return {"profile": profile, "ws_id": session_profile.selected_ws_id, "result": result}
|
|
80
|
+
|
|
81
|
+
return self._run(profile, runner, tool_name='创建门户')
|
|
82
|
+
|
|
63
83
|
def portal_get_base_info(self, *, profile: str, dash_key: str) -> JSONObject:
|
|
64
84
|
"""执行门户相关逻辑。"""
|
|
65
85
|
self._require_dash_key(dash_key)
|
|
@@ -99,6 +119,36 @@ class PortalTools(ToolBase):
|
|
|
99
119
|
|
|
100
120
|
return self._run(profile, runner, tool_name='门户基础信息')
|
|
101
121
|
|
|
122
|
+
def portal_update_base_info(self, *, profile: str, dash_key: str, payload: JSONObject) -> JSONObject:
|
|
123
|
+
"""执行门户相关逻辑。"""
|
|
124
|
+
self._require_dash_key(dash_key)
|
|
125
|
+
body = self._require_dict(payload)
|
|
126
|
+
|
|
127
|
+
def runner(session_profile, context):
|
|
128
|
+
result = self.backend.request("POST", context, f"/dash/{dash_key}/baseInfo", json_body=body)
|
|
129
|
+
return self._attach_human_review_notice(
|
|
130
|
+
{"profile": profile, "ws_id": session_profile.selected_ws_id, "dash_key": dash_key, "result": result},
|
|
131
|
+
operation="update",
|
|
132
|
+
target="portal base settings",
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
return self._run(profile, runner, tool_name='更新门户基础信息')
|
|
136
|
+
|
|
137
|
+
def portal_update(self, *, profile: str, dash_key: str, payload: JSONObject) -> JSONObject:
|
|
138
|
+
"""执行门户相关逻辑。"""
|
|
139
|
+
self._require_dash_key(dash_key)
|
|
140
|
+
body = self._require_dict(payload)
|
|
141
|
+
|
|
142
|
+
def runner(session_profile, context):
|
|
143
|
+
result = self.backend.request("POST", context, f"/dash/{dash_key}", json_body=body)
|
|
144
|
+
return self._attach_human_review_notice(
|
|
145
|
+
{"profile": profile, "ws_id": session_profile.selected_ws_id, "dash_key": dash_key, "result": result},
|
|
146
|
+
operation="update",
|
|
147
|
+
target="portal configuration",
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
return self._run(profile, runner, tool_name='更新门户配置')
|
|
151
|
+
|
|
102
152
|
def portal_delete(self, *, profile: str, dash_key: str) -> JSONObject:
|
|
103
153
|
"""执行门户相关逻辑。"""
|
|
104
154
|
self._require_dash_key(dash_key)
|
|
@@ -39,7 +39,6 @@ from .record_tools import RecordTools
|
|
|
39
39
|
from .role_tools import RoleTools
|
|
40
40
|
from .view_tools import ViewTools
|
|
41
41
|
from .view_writer import ViewWriter
|
|
42
|
-
from .portal_writer import PortalWriter
|
|
43
42
|
from .workspace_tools import WorkspaceTools
|
|
44
43
|
|
|
45
44
|
STAGED_BUILD_MODES = {"preflight", "plan", "apply", "repair"}
|
|
@@ -1432,7 +1431,6 @@ class SolutionTools(ToolBase):
|
|
|
1432
1431
|
view_writer=ViewWriter(self.sessions, self.backend),
|
|
1433
1432
|
chart_tools=QingbiReportTools(self.sessions, self.backend),
|
|
1434
1433
|
portal_tools=PortalTools(self.sessions, self.backend),
|
|
1435
|
-
portal_writer=PortalWriter(self.sessions, self.backend),
|
|
1436
1434
|
navigation_tools=NavigationTools(self.sessions, self.backend),
|
|
1437
1435
|
)
|
|
1438
1436
|
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"schema_version": "v1alpha1",
|
|
3
|
-
"name": "业务工作台",
|
|
4
|
-
"package_tag_id": 2030703,
|
|
5
|
-
"icon": "view-grid",
|
|
6
|
-
"color": "#2468F2",
|
|
7
|
-
"hide_copyright": false,
|
|
8
|
-
"global_config": {
|
|
9
|
-
"auto_refresh": false,
|
|
10
|
-
"interval": 60,
|
|
11
|
-
"export": true
|
|
12
|
-
},
|
|
13
|
-
"visibility": {
|
|
14
|
-
"mode": "workspace",
|
|
15
|
-
"external_mode": "not"
|
|
16
|
-
},
|
|
17
|
-
"layout_preset": "auto",
|
|
18
|
-
"sections": [
|
|
19
|
-
{
|
|
20
|
-
"title": "业务入口",
|
|
21
|
-
"source_type": "grid",
|
|
22
|
-
"position": {
|
|
23
|
-
"pc": { "x": 0, "y": 0, "cols": 24, "rows": 4 }
|
|
24
|
-
},
|
|
25
|
-
"config": {
|
|
26
|
-
"gridTitle": "业务入口",
|
|
27
|
-
"beingShowTitle": true,
|
|
28
|
-
"items": [
|
|
29
|
-
{ "type": 1, "jumpMode": 1, "linkAppKey": "APP_KEY_DEVICE", "linkFormType": 1, "title": "设备台账" },
|
|
30
|
-
{ "type": 1, "jumpMode": 1, "linkAppKey": "APP_KEY_WORKORDER", "linkFormType": 1, "title": "工单管理" }
|
|
31
|
-
]
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
"title": "设备总数",
|
|
36
|
-
"source_type": "chart",
|
|
37
|
-
"role": "metric",
|
|
38
|
-
"position": {
|
|
39
|
-
"pc": { "x": 0, "y": 4, "cols": 6, "rows": 5 }
|
|
40
|
-
},
|
|
41
|
-
"chart": {
|
|
42
|
-
"app_key": "APP_KEY_DEVICE",
|
|
43
|
-
"name": "设备总数",
|
|
44
|
-
"chart_type": "target",
|
|
45
|
-
"metric": "count(*)"
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
"title": "异常设备数",
|
|
50
|
-
"source_type": "chart",
|
|
51
|
-
"role": "metric",
|
|
52
|
-
"position": {
|
|
53
|
-
"pc": { "x": 6, "y": 4, "cols": 6, "rows": 5 }
|
|
54
|
-
},
|
|
55
|
-
"chart": {
|
|
56
|
-
"app_key": "APP_KEY_DEVICE",
|
|
57
|
-
"name": "异常设备数",
|
|
58
|
-
"chart_type": "target",
|
|
59
|
-
"metric": "count(*)",
|
|
60
|
-
"filters": [
|
|
61
|
-
{ "field_name": "状态", "operator": "eq", "values": ["异常"] }
|
|
62
|
-
]
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
"title": "工单趋势",
|
|
67
|
-
"source_type": "chart",
|
|
68
|
-
"position": {
|
|
69
|
-
"pc": { "x": 0, "y": 9, "cols": 12, "rows": 7 }
|
|
70
|
-
},
|
|
71
|
-
"chart": {
|
|
72
|
-
"app_key": "APP_KEY_WORKORDER",
|
|
73
|
-
"name": "工单趋势",
|
|
74
|
-
"chart_type": "line",
|
|
75
|
-
"metric": "count(*)",
|
|
76
|
-
"group_by": ["创建日期"]
|
|
77
|
-
}
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
"title": "工单类型分布",
|
|
81
|
-
"source_type": "chart",
|
|
82
|
-
"position": {
|
|
83
|
-
"pc": { "x": 12, "y": 9, "cols": 12, "rows": 7 }
|
|
84
|
-
},
|
|
85
|
-
"chart_ref": { "app_key": "APP_KEY_WORKORDER", "chart_name": "工单类型分布" }
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
"title": "异常设备明细",
|
|
89
|
-
"source_type": "view",
|
|
90
|
-
"position": {
|
|
91
|
-
"pc": { "x": 0, "y": 16, "cols": 24, "rows": 11 }
|
|
92
|
-
},
|
|
93
|
-
"view_ref": {
|
|
94
|
-
"app_key": "APP_KEY_DEVICE",
|
|
95
|
-
"view_key": "VIEW_KEY_ABNORMAL_DEVICES"
|
|
96
|
-
},
|
|
97
|
-
"view_display": { "show_title": true }
|
|
98
|
-
}
|
|
99
|
-
]
|
|
100
|
-
}
|