@qingflow-tech/qingflow-cli 1.1.47-beta.1 → 1.1.47-beta.2
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 -1
- package/skills/qingflow-cli/SKILL.md +10 -0
- package/skills/qingflow-common/SKILL.md +7 -3
- package/src/qingflow_mcp/builder_facade/service.py +119 -1
- package/src/qingflow_mcp/cli/commands/auth.py +1 -1
- package/src/qingflow_mcp/cli/context.py +1 -1
- package/src/qingflow_mcp/cli/formatters.py +62 -3
- package/src/qingflow_mcp/public_surface.py +1 -0
- package/src/qingflow_mcp/resource_uri.py +184 -0
- package/src/qingflow_mcp/response_trim.py +3 -3
- package/src/qingflow_mcp/server_app_builder.py +7 -0
- package/src/qingflow_mcp/server_app_user.py +3 -2
- package/src/qingflow_mcp/tools/ai_builder_tools.py +208 -39
- package/src/qingflow_mcp/tools/app_tools.py +52 -13
- package/src/qingflow_mcp/tools/login_link_tools.py +21 -1
- package/src/qingflow_mcp/tools/resource_read_tools.py +13 -0
|
@@ -10,6 +10,7 @@ from ..config import DEFAULT_PROFILE
|
|
|
10
10
|
from ..errors import QingflowApiError, backend_code_int, is_auth_like_error, raise_tool_error
|
|
11
11
|
from ..json_types import JSONObject
|
|
12
12
|
from ..list_type_labels import SYSTEM_VIEW_DEFINITIONS, get_app_publish_status_label
|
|
13
|
+
from ..resource_uri import try_build_resource_uri
|
|
13
14
|
from ..solution.compiler.icon_utils import workspace_icon_config
|
|
14
15
|
from .base import ToolBase
|
|
15
16
|
|
|
@@ -46,6 +47,15 @@ class AppTools(ToolBase):
|
|
|
46
47
|
unfiltered_count = len(items)
|
|
47
48
|
if normalized_query:
|
|
48
49
|
items = self._filter_visible_apps(items, normalized_query)
|
|
50
|
+
for item in items:
|
|
51
|
+
uri, warning = try_build_resource_uri(
|
|
52
|
+
ws_id=session_profile.selected_ws_id,
|
|
53
|
+
resource_type="app",
|
|
54
|
+
app_key=item.get("app_key"),
|
|
55
|
+
)
|
|
56
|
+
if uri:
|
|
57
|
+
item["uri"] = uri
|
|
58
|
+
_append_unique_warning(warnings, warning)
|
|
49
59
|
response = {
|
|
50
60
|
"profile": profile,
|
|
51
61
|
"ws_id": session_profile.selected_ws_id,
|
|
@@ -200,9 +210,10 @@ class AppTools(ToolBase):
|
|
|
200
210
|
if not _is_optional_app_metadata_error(exc):
|
|
201
211
|
raise
|
|
202
212
|
visible_app = self._resolve_visible_app(context, app_key)
|
|
203
|
-
if visible_app is
|
|
204
|
-
|
|
205
|
-
|
|
213
|
+
if visible_app is None:
|
|
214
|
+
raise
|
|
215
|
+
app_name = str(visible_app.get("app_name") or visible_app.get("title") or app_key).strip() or app_key
|
|
216
|
+
app_icon = str(visible_app.get("app_icon") or "").strip() or None
|
|
206
217
|
warnings.append(
|
|
207
218
|
_with_api_error_fields(
|
|
208
219
|
{
|
|
@@ -224,22 +235,45 @@ class AppTools(ToolBase):
|
|
|
224
235
|
accessible_views.extend(custom_views)
|
|
225
236
|
import_capability, import_warnings = _derive_import_capability(base_info)
|
|
226
237
|
warnings.extend(import_warnings)
|
|
238
|
+
app_uri, uri_warning = try_build_resource_uri(
|
|
239
|
+
ws_id=session_profile.selected_ws_id,
|
|
240
|
+
resource_type="app",
|
|
241
|
+
app_key=app_key,
|
|
242
|
+
)
|
|
243
|
+
_append_unique_warning(warnings, uri_warning)
|
|
244
|
+
for view in accessible_views:
|
|
245
|
+
view_id = str(view.get("view_id") or "")
|
|
246
|
+
view_kind, separator, view_key = view_id.partition(":")
|
|
247
|
+
if not separator:
|
|
248
|
+
continue
|
|
249
|
+
uri, warning = try_build_resource_uri(
|
|
250
|
+
ws_id=session_profile.selected_ws_id,
|
|
251
|
+
resource_type="view",
|
|
252
|
+
app_key=app_key,
|
|
253
|
+
view_kind=view_kind,
|
|
254
|
+
view_key=view_key,
|
|
255
|
+
)
|
|
256
|
+
if uri:
|
|
257
|
+
view["uri"] = uri
|
|
258
|
+
_append_unique_warning(warnings, warning)
|
|
259
|
+
data: JSONObject = {
|
|
260
|
+
"app_key": app_key,
|
|
261
|
+
"app_name": app_name,
|
|
262
|
+
"app_icon": app_icon,
|
|
263
|
+
"icon_config": workspace_icon_config(app_icon),
|
|
264
|
+
**({"uri": app_uri} if app_uri else {}),
|
|
265
|
+
**({"visible_app": visible_app} if base_info is None and visible_app is not None else {}),
|
|
266
|
+
"can_create": can_create,
|
|
267
|
+
"import_capability": import_capability,
|
|
268
|
+
"accessible_views": accessible_views,
|
|
269
|
+
}
|
|
227
270
|
return {
|
|
228
271
|
"profile": profile,
|
|
229
272
|
"ws_id": session_profile.selected_ws_id,
|
|
230
273
|
"ok": True,
|
|
231
274
|
"request_route": {"base_url": context.base_url, "qf_version": context.qf_version},
|
|
232
275
|
"warnings": warnings,
|
|
233
|
-
"data":
|
|
234
|
-
"app_key": app_key,
|
|
235
|
-
"app_name": app_name,
|
|
236
|
-
"app_icon": app_icon,
|
|
237
|
-
"icon_config": workspace_icon_config(app_icon),
|
|
238
|
-
**({"visible_app": visible_app} if base_info is None and visible_app is not None else {}),
|
|
239
|
-
"can_create": can_create,
|
|
240
|
-
"import_capability": import_capability,
|
|
241
|
-
"accessible_views": accessible_views,
|
|
242
|
-
},
|
|
276
|
+
"data": data,
|
|
243
277
|
}
|
|
244
278
|
|
|
245
279
|
return self._run(profile, runner, tool_name='应用详情')
|
|
@@ -1048,6 +1082,11 @@ def _derive_editability(base_info: Any) -> tuple[JSONObject, list[JSONObject]]:
|
|
|
1048
1082
|
}, warnings
|
|
1049
1083
|
|
|
1050
1084
|
|
|
1085
|
+
def _append_unique_warning(warnings: list[JSONObject], warning: JSONObject | None) -> None:
|
|
1086
|
+
if warning and not any(item.get("code") == warning.get("code") for item in warnings):
|
|
1087
|
+
warnings.append(warning)
|
|
1088
|
+
|
|
1089
|
+
|
|
1051
1090
|
def _coerce_positive_int(value: Any) -> int | None:
|
|
1052
1091
|
try:
|
|
1053
1092
|
number = int(value)
|
|
@@ -9,6 +9,7 @@ from mcp.server.fastmcp import FastMCP
|
|
|
9
9
|
from ..config import DEFAULT_PROFILE, get_web_base_url
|
|
10
10
|
from ..errors import QingflowApiError
|
|
11
11
|
from ..json_types import JSONObject
|
|
12
|
+
from ..resource_uri import parse_resource_path
|
|
12
13
|
from .base import ToolBase
|
|
13
14
|
|
|
14
15
|
LOGIN_LINK_DESCRIPTION = (
|
|
@@ -17,6 +18,9 @@ LOGIN_LINK_DESCRIPTION = (
|
|
|
17
18
|
"redirect_path 可选:传已确认的站内业务路径,可带 query/hash;省略进入首页,不接受外部地址或认证入口。"
|
|
18
19
|
"纯数据查询无需调用;用户指定页面但路径未知时先定位,不猜路径或静默改为首页。"
|
|
19
20
|
"返回 url 和毫秒整数 expires_at;必要安全验证及原有页面权限仍然生效。"
|
|
21
|
+
"交付应用包、应用、视图或安装结果时,目标浏览器登录态未知则默认用资源 uri 的同源 pathname 独立签发;"
|
|
22
|
+
"只有用户确认同一浏览器、网页域名、账号和工作区已登录时才直接交付稳定 uri。"
|
|
23
|
+
"调用过本工具或 Agent 自动化浏览器已登录不等于用户浏览器已登录;纯中间查询不签发。"
|
|
20
24
|
"用户要链接时仅交付,不提前消费;要求打开时才用浏览器打开返回的完整链接。"
|
|
21
25
|
)
|
|
22
26
|
|
|
@@ -70,7 +74,23 @@ class LoginLinkTools(ToolBase):
|
|
|
70
74
|
"""执行工具方法逻辑。"""
|
|
71
75
|
|
|
72
76
|
def runner(session_profile, context):
|
|
73
|
-
|
|
77
|
+
requested_target = redirect_path or ""
|
|
78
|
+
if requested_target.startswith("/resources"):
|
|
79
|
+
target = requested_target
|
|
80
|
+
try:
|
|
81
|
+
resource = parse_resource_path(target)
|
|
82
|
+
except ValueError:
|
|
83
|
+
raise QingflowApiError(
|
|
84
|
+
category="validation",
|
|
85
|
+
message="redirect_path 必须是合法的稳定资源路径",
|
|
86
|
+
) from None
|
|
87
|
+
if resource.ws_id != session_profile.selected_ws_id:
|
|
88
|
+
raise QingflowApiError(
|
|
89
|
+
category="validation",
|
|
90
|
+
message="redirect_path 的资源工作区必须与当前选中工作区一致",
|
|
91
|
+
)
|
|
92
|
+
else:
|
|
93
|
+
target = validate_redirect_path(requested_target)
|
|
74
94
|
web_base_url = get_web_base_url()
|
|
75
95
|
result = self.backend.request(
|
|
76
96
|
"POST",
|
|
@@ -6,6 +6,7 @@ from typing import Any
|
|
|
6
6
|
from ..errors import QingflowApiError, backend_code_int, is_auth_like_error, raise_tool_error
|
|
7
7
|
from ..json_types import JSONObject
|
|
8
8
|
from ..list_type_labels import SYSTEM_VIEW_DEFINITIONS
|
|
9
|
+
from ..resource_uri import try_build_resource_uri
|
|
9
10
|
from ..solution.compiler.icon_utils import workspace_icon_config
|
|
10
11
|
from .app_tools import _analysis_supported_for_view_type
|
|
11
12
|
from .base import ToolBase
|
|
@@ -227,6 +228,17 @@ class ResourceReadTools(ToolBase):
|
|
|
227
228
|
"message": f"view_get supports exporting `{view_id}`, but the export call still needs an explicit `app_key` from the parent app context.",
|
|
228
229
|
}
|
|
229
230
|
)
|
|
231
|
+
uri = None
|
|
232
|
+
if resolved_app_key:
|
|
233
|
+
uri, uri_warning = try_build_resource_uri(
|
|
234
|
+
ws_id=session_profile.selected_ws_id,
|
|
235
|
+
resource_type="view",
|
|
236
|
+
app_key=resolved_app_key,
|
|
237
|
+
view_kind="custom",
|
|
238
|
+
view_key=view_key,
|
|
239
|
+
)
|
|
240
|
+
if uri_warning:
|
|
241
|
+
warnings.append(uri_warning)
|
|
230
242
|
return {
|
|
231
243
|
"profile": profile,
|
|
232
244
|
"ws_id": session_profile.selected_ws_id,
|
|
@@ -249,6 +261,7 @@ class ResourceReadTools(ToolBase):
|
|
|
249
261
|
],
|
|
250
262
|
"analysis_supported": _analysis_supported_for_view_type(raw_view_type or None),
|
|
251
263
|
"export_capability": export_capability,
|
|
264
|
+
**({"uri": uri} if uri else {}),
|
|
252
265
|
},
|
|
253
266
|
}
|
|
254
267
|
|