@josephyan/qingflow-cli 0.2.0-beta.70 → 0.2.0-beta.72
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/src/qingflow_mcp/backend_client.py +1 -0
- package/src/qingflow_mcp/builder_facade/models.py +16 -8
- package/src/qingflow_mcp/builder_facade/service.py +208 -111
- package/src/qingflow_mcp/cli/commands/__init__.py +4 -1
- package/src/qingflow_mcp/cli/commands/builder.py +24 -64
- package/src/qingflow_mcp/cli/commands/chart.py +18 -0
- package/src/qingflow_mcp/cli/commands/portal.py +25 -0
- package/src/qingflow_mcp/cli/commands/view.py +18 -0
- package/src/qingflow_mcp/cli/context.py +3 -0
- package/src/qingflow_mcp/response_trim.py +211 -178
- package/src/qingflow_mcp/server_app_builder.py +18 -42
- package/src/qingflow_mcp/server_app_user.py +21 -1
- package/src/qingflow_mcp/tools/ai_builder_tools.py +165 -124
- package/src/qingflow_mcp/tools/app_tools.py +0 -4
- package/src/qingflow_mcp/tools/qingbi_report_tools.py +58 -7
- package/src/qingflow_mcp/tools/resource_read_tools.py +399 -0
|
@@ -16,6 +16,7 @@ from .tools.directory_tools import DirectoryTools
|
|
|
16
16
|
from .tools.feedback_tools import FeedbackTools
|
|
17
17
|
from .tools.file_tools import FileTools
|
|
18
18
|
from .tools.import_tools import ImportTools
|
|
19
|
+
from .tools.resource_read_tools import ResourceReadTools
|
|
19
20
|
from .tools.task_context_tools import TaskContextTools
|
|
20
21
|
from .tools.workspace_tools import WorkspaceTools
|
|
21
22
|
|
|
@@ -95,6 +96,8 @@ Analysis answers must include concrete numbers. When applicable, include percent
|
|
|
95
96
|
`record_update_schema_get -> record_update`
|
|
96
97
|
`record_list / record_get -> record_delete`
|
|
97
98
|
`record_code_block_schema_get -> record_code_block_run`
|
|
99
|
+
`portal_list -> portal_get -> chart_get / view_get`
|
|
100
|
+
`portal_get -> view_get -> record_list`
|
|
98
101
|
|
|
99
102
|
- Use `columns` as `[{{field_id}}]`
|
|
100
103
|
- Use `where` items as `{{field_id, op, value}}`
|
|
@@ -177,6 +180,7 @@ If the current MCP capability is unsupported, the workflow is awkward, or the us
|
|
|
177
180
|
workspace = wrap_trimmed_methods(WorkspaceTools(sessions, backend), USER_SERVER_METHOD_MAP)
|
|
178
181
|
file_tools = wrap_trimmed_methods(FileTools(sessions, backend), USER_SERVER_METHOD_MAP)
|
|
179
182
|
imports = wrap_trimmed_methods(ImportTools(sessions, backend), USER_SERVER_METHOD_MAP)
|
|
183
|
+
resources = wrap_trimmed_methods(ResourceReadTools(sessions, backend), USER_SERVER_METHOD_MAP)
|
|
180
184
|
feedback = FeedbackTools(backend, mcp_side="App User MCP")
|
|
181
185
|
code_block_tools = wrap_trimmed_methods(CodeBlockTools(sessions, backend), USER_SERVER_METHOD_MAP)
|
|
182
186
|
task_context_tools = wrap_trimmed_methods(TaskContextTools(sessions, backend), USER_SERVER_METHOD_MAP)
|
|
@@ -256,6 +260,22 @@ If the current MCP capability is unsupported, the workflow is awkward, or the us
|
|
|
256
260
|
def app_get(profile: str = DEFAULT_PROFILE, app_key: str = "") -> dict:
|
|
257
261
|
return apps.app_get(profile=profile, app_key=app_key)
|
|
258
262
|
|
|
263
|
+
@server.tool()
|
|
264
|
+
def portal_list(profile: str = DEFAULT_PROFILE) -> dict:
|
|
265
|
+
return resources.portal_list(profile=profile)
|
|
266
|
+
|
|
267
|
+
@server.tool()
|
|
268
|
+
def portal_get(profile: str = DEFAULT_PROFILE, dash_key: str = "") -> dict:
|
|
269
|
+
return resources.portal_get(profile=profile, dash_key=dash_key)
|
|
270
|
+
|
|
271
|
+
@server.tool()
|
|
272
|
+
def view_get(profile: str = DEFAULT_PROFILE, view_id: str = "") -> dict:
|
|
273
|
+
return resources.view_get(profile=profile, view_id=view_id)
|
|
274
|
+
|
|
275
|
+
@server.tool()
|
|
276
|
+
def chart_get(profile: str = DEFAULT_PROFILE, chart_id: str = "") -> dict:
|
|
277
|
+
return resources.chart_get(profile=profile, chart_id=chart_id)
|
|
278
|
+
|
|
259
279
|
@server.tool()
|
|
260
280
|
def file_get_upload_info(
|
|
261
281
|
profile: str = DEFAULT_PROFILE,
|
|
@@ -320,7 +340,7 @@ If the current MCP capability is unsupported, the workflow is awkward, or the us
|
|
|
320
340
|
) -> dict:
|
|
321
341
|
try:
|
|
322
342
|
return trim_public_response(
|
|
323
|
-
"feedback_submit",
|
|
343
|
+
"user:feedback_submit",
|
|
324
344
|
feedback.feedback_submit(
|
|
325
345
|
category=category,
|
|
326
346
|
title=title,
|
|
@@ -209,28 +209,28 @@ class AiBuilderTools(ToolBase):
|
|
|
209
209
|
return self.app_custom_button_delete(profile=profile, app_key=app_key, button_id=button_id)
|
|
210
210
|
|
|
211
211
|
@mcp.tool()
|
|
212
|
-
def
|
|
213
|
-
return self.
|
|
212
|
+
def app_get(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
|
|
213
|
+
return self.app_get(profile=profile, app_key=app_key)
|
|
214
214
|
|
|
215
215
|
@mcp.tool()
|
|
216
|
-
def
|
|
217
|
-
return self.
|
|
216
|
+
def app_get_fields(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
|
|
217
|
+
return self.app_get_fields(profile=profile, app_key=app_key)
|
|
218
218
|
|
|
219
219
|
@mcp.tool()
|
|
220
|
-
def
|
|
221
|
-
return self.
|
|
220
|
+
def app_get_layout(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
|
|
221
|
+
return self.app_get_layout(profile=profile, app_key=app_key)
|
|
222
222
|
|
|
223
223
|
@mcp.tool()
|
|
224
|
-
def
|
|
225
|
-
return self.
|
|
224
|
+
def app_get_views(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
|
|
225
|
+
return self.app_get_views(profile=profile, app_key=app_key)
|
|
226
226
|
|
|
227
227
|
@mcp.tool()
|
|
228
|
-
def
|
|
229
|
-
return self.
|
|
228
|
+
def app_get_flow(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
|
|
229
|
+
return self.app_get_flow(profile=profile, app_key=app_key)
|
|
230
230
|
|
|
231
231
|
@mcp.tool()
|
|
232
|
-
def
|
|
233
|
-
return self.
|
|
232
|
+
def app_get_charts(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
|
|
233
|
+
return self.app_get_charts(profile=profile, app_key=app_key)
|
|
234
234
|
|
|
235
235
|
@mcp.tool()
|
|
236
236
|
def portal_list(profile: str = DEFAULT_PROFILE) -> JSONObject:
|
|
@@ -245,36 +245,15 @@ class AiBuilderTools(ToolBase):
|
|
|
245
245
|
return self.portal_get(profile=profile, dash_key=dash_key, being_draft=being_draft)
|
|
246
246
|
|
|
247
247
|
@mcp.tool()
|
|
248
|
-
def
|
|
249
|
-
profile
|
|
250
|
-
dash_key: str = "",
|
|
251
|
-
being_draft: bool = True,
|
|
252
|
-
) -> JSONObject:
|
|
253
|
-
return self.portal_read_summary(profile=profile, dash_key=dash_key, being_draft=being_draft)
|
|
254
|
-
|
|
255
|
-
@mcp.tool()
|
|
256
|
-
def view_get(profile: str = DEFAULT_PROFILE, viewgraph_key: str = "") -> JSONObject:
|
|
257
|
-
return self.view_get(profile=profile, viewgraph_key=viewgraph_key)
|
|
248
|
+
def view_get(profile: str = DEFAULT_PROFILE, view_key: str = "") -> JSONObject:
|
|
249
|
+
return self.view_get(profile=profile, view_key=view_key)
|
|
258
250
|
|
|
259
251
|
@mcp.tool()
|
|
260
252
|
def chart_get(
|
|
261
253
|
profile: str = DEFAULT_PROFILE,
|
|
262
254
|
chart_id: str = "",
|
|
263
|
-
data_payload: JSONObject | None = None,
|
|
264
|
-
page_num: int | None = None,
|
|
265
|
-
page_size: int | None = None,
|
|
266
|
-
page_num_y: int | None = None,
|
|
267
|
-
page_size_y: int | None = None,
|
|
268
255
|
) -> JSONObject:
|
|
269
|
-
return self.chart_get(
|
|
270
|
-
profile=profile,
|
|
271
|
-
chart_id=chart_id,
|
|
272
|
-
data_payload=data_payload or {},
|
|
273
|
-
page_num=page_num,
|
|
274
|
-
page_size=page_size,
|
|
275
|
-
page_num_y=page_num_y,
|
|
276
|
-
page_size_y=page_size_y,
|
|
277
|
-
)
|
|
256
|
+
return self.chart_get(profile=profile, chart_id=chart_id)
|
|
278
257
|
|
|
279
258
|
@mcp.tool()
|
|
280
259
|
def app_schema_apply(
|
|
@@ -759,7 +738,16 @@ class AiBuilderTools(ToolBase):
|
|
|
759
738
|
lambda: self._facade.app_read_summary(profile=profile, app_key=app_key),
|
|
760
739
|
error_code="APP_READ_FAILED",
|
|
761
740
|
normalized_args=normalized_args,
|
|
762
|
-
suggested_next_call={"tool_name": "
|
|
741
|
+
suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
|
|
742
|
+
)
|
|
743
|
+
|
|
744
|
+
def app_get(self, *, profile: str, app_key: str) -> JSONObject:
|
|
745
|
+
normalized_args = {"app_key": app_key}
|
|
746
|
+
return _safe_tool_call(
|
|
747
|
+
lambda: self._facade.app_get(profile=profile, app_key=app_key),
|
|
748
|
+
error_code="APP_GET_FAILED",
|
|
749
|
+
normalized_args=normalized_args,
|
|
750
|
+
suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
|
|
763
751
|
)
|
|
764
752
|
|
|
765
753
|
def app_read_fields(self, *, profile: str, app_key: str) -> JSONObject:
|
|
@@ -768,7 +756,16 @@ class AiBuilderTools(ToolBase):
|
|
|
768
756
|
lambda: self._facade.app_read_fields(profile=profile, app_key=app_key),
|
|
769
757
|
error_code="FIELDS_READ_FAILED",
|
|
770
758
|
normalized_args=normalized_args,
|
|
771
|
-
suggested_next_call={"tool_name": "
|
|
759
|
+
suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": app_key}},
|
|
760
|
+
)
|
|
761
|
+
|
|
762
|
+
def app_get_fields(self, *, profile: str, app_key: str) -> JSONObject:
|
|
763
|
+
normalized_args = {"app_key": app_key}
|
|
764
|
+
return _safe_tool_call(
|
|
765
|
+
lambda: self._facade.app_get_fields(profile=profile, app_key=app_key),
|
|
766
|
+
error_code="APP_GET_FIELDS_FAILED",
|
|
767
|
+
normalized_args=normalized_args,
|
|
768
|
+
suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": app_key}},
|
|
772
769
|
)
|
|
773
770
|
|
|
774
771
|
def app_read_layout_summary(self, *, profile: str, app_key: str) -> JSONObject:
|
|
@@ -777,7 +774,16 @@ class AiBuilderTools(ToolBase):
|
|
|
777
774
|
lambda: self._facade.app_read_layout_summary(profile=profile, app_key=app_key),
|
|
778
775
|
error_code="LAYOUT_READ_FAILED",
|
|
779
776
|
normalized_args=normalized_args,
|
|
780
|
-
suggested_next_call={"tool_name": "
|
|
777
|
+
suggested_next_call={"tool_name": "app_get_layout", "arguments": {"profile": profile, "app_key": app_key}},
|
|
778
|
+
)
|
|
779
|
+
|
|
780
|
+
def app_get_layout(self, *, profile: str, app_key: str) -> JSONObject:
|
|
781
|
+
normalized_args = {"app_key": app_key}
|
|
782
|
+
return _safe_tool_call(
|
|
783
|
+
lambda: self._facade.app_get_layout(profile=profile, app_key=app_key),
|
|
784
|
+
error_code="APP_GET_LAYOUT_FAILED",
|
|
785
|
+
normalized_args=normalized_args,
|
|
786
|
+
suggested_next_call={"tool_name": "app_get_layout", "arguments": {"profile": profile, "app_key": app_key}},
|
|
781
787
|
)
|
|
782
788
|
|
|
783
789
|
def app_read_views_summary(self, *, profile: str, app_key: str) -> JSONObject:
|
|
@@ -786,7 +792,16 @@ class AiBuilderTools(ToolBase):
|
|
|
786
792
|
lambda: self._facade.app_read_views_summary(profile=profile, app_key=app_key),
|
|
787
793
|
error_code="VIEWS_READ_FAILED",
|
|
788
794
|
normalized_args=normalized_args,
|
|
789
|
-
suggested_next_call={"tool_name": "
|
|
795
|
+
suggested_next_call={"tool_name": "app_get_views", "arguments": {"profile": profile, "app_key": app_key}},
|
|
796
|
+
)
|
|
797
|
+
|
|
798
|
+
def app_get_views(self, *, profile: str, app_key: str) -> JSONObject:
|
|
799
|
+
normalized_args = {"app_key": app_key}
|
|
800
|
+
return _safe_tool_call(
|
|
801
|
+
lambda: self._facade.app_get_views(profile=profile, app_key=app_key),
|
|
802
|
+
error_code="APP_GET_VIEWS_FAILED",
|
|
803
|
+
normalized_args=normalized_args,
|
|
804
|
+
suggested_next_call={"tool_name": "app_get_views", "arguments": {"profile": profile, "app_key": app_key}},
|
|
790
805
|
)
|
|
791
806
|
|
|
792
807
|
def app_read_flow_summary(self, *, profile: str, app_key: str) -> JSONObject:
|
|
@@ -795,7 +810,16 @@ class AiBuilderTools(ToolBase):
|
|
|
795
810
|
lambda: self._facade.app_read_flow_summary(profile=profile, app_key=app_key),
|
|
796
811
|
error_code="FLOW_READ_FAILED",
|
|
797
812
|
normalized_args=normalized_args,
|
|
798
|
-
suggested_next_call={"tool_name": "
|
|
813
|
+
suggested_next_call={"tool_name": "app_get_flow", "arguments": {"profile": profile, "app_key": app_key}},
|
|
814
|
+
)
|
|
815
|
+
|
|
816
|
+
def app_get_flow(self, *, profile: str, app_key: str) -> JSONObject:
|
|
817
|
+
normalized_args = {"app_key": app_key}
|
|
818
|
+
return _safe_tool_call(
|
|
819
|
+
lambda: self._facade.app_get_flow(profile=profile, app_key=app_key),
|
|
820
|
+
error_code="APP_GET_FLOW_FAILED",
|
|
821
|
+
normalized_args=normalized_args,
|
|
822
|
+
suggested_next_call={"tool_name": "app_get_flow", "arguments": {"profile": profile, "app_key": app_key}},
|
|
799
823
|
)
|
|
800
824
|
|
|
801
825
|
def app_read_charts_summary(self, *, profile: str, app_key: str) -> JSONObject:
|
|
@@ -804,7 +828,16 @@ class AiBuilderTools(ToolBase):
|
|
|
804
828
|
lambda: self._facade.app_read_charts_summary(profile=profile, app_key=app_key),
|
|
805
829
|
error_code="CHARTS_READ_FAILED",
|
|
806
830
|
normalized_args=normalized_args,
|
|
807
|
-
suggested_next_call={"tool_name": "
|
|
831
|
+
suggested_next_call={"tool_name": "app_get_charts", "arguments": {"profile": profile, "app_key": app_key}},
|
|
832
|
+
)
|
|
833
|
+
|
|
834
|
+
def app_get_charts(self, *, profile: str, app_key: str) -> JSONObject:
|
|
835
|
+
normalized_args = {"app_key": app_key}
|
|
836
|
+
return _safe_tool_call(
|
|
837
|
+
lambda: self._facade.app_get_charts(profile=profile, app_key=app_key),
|
|
838
|
+
error_code="APP_GET_CHARTS_FAILED",
|
|
839
|
+
normalized_args=normalized_args,
|
|
840
|
+
suggested_next_call={"tool_name": "app_get_charts", "arguments": {"profile": profile, "app_key": app_key}},
|
|
808
841
|
)
|
|
809
842
|
|
|
810
843
|
def portal_list(self, *, profile: str) -> JSONObject:
|
|
@@ -830,13 +863,14 @@ class AiBuilderTools(ToolBase):
|
|
|
830
863
|
lambda: self._facade.portal_read_summary(profile=profile, dash_key=dash_key, being_draft=being_draft),
|
|
831
864
|
error_code="PORTAL_READ_FAILED",
|
|
832
865
|
normalized_args=normalized_args,
|
|
833
|
-
suggested_next_call={"tool_name": "
|
|
866
|
+
suggested_next_call={"tool_name": "portal_get", "arguments": {"profile": profile, **normalized_args}},
|
|
834
867
|
)
|
|
835
868
|
|
|
836
|
-
def view_get(self, *, profile: str, viewgraph_key: str) -> JSONObject:
|
|
837
|
-
|
|
869
|
+
def view_get(self, *, profile: str, view_key: str = "", viewgraph_key: str = "") -> JSONObject:
|
|
870
|
+
resolved_view_key = str(view_key or viewgraph_key or "").strip()
|
|
871
|
+
normalized_args = {"view_key": resolved_view_key}
|
|
838
872
|
return _safe_tool_call(
|
|
839
|
-
lambda: self._facade.view_get(profile=profile,
|
|
873
|
+
lambda: self._facade.view_get(profile=profile, view_key=resolved_view_key),
|
|
840
874
|
error_code="VIEW_GET_FAILED",
|
|
841
875
|
normalized_args=normalized_args,
|
|
842
876
|
suggested_next_call={"tool_name": "view_get", "arguments": {"profile": profile, **normalized_args}},
|
|
@@ -847,30 +881,10 @@ class AiBuilderTools(ToolBase):
|
|
|
847
881
|
*,
|
|
848
882
|
profile: str,
|
|
849
883
|
chart_id: str,
|
|
850
|
-
data_payload: JSONObject | None = None,
|
|
851
|
-
page_num: int | None = None,
|
|
852
|
-
page_size: int | None = None,
|
|
853
|
-
page_num_y: int | None = None,
|
|
854
|
-
page_size_y: int | None = None,
|
|
855
884
|
) -> JSONObject:
|
|
856
|
-
normalized_args = {
|
|
857
|
-
"chart_id": chart_id,
|
|
858
|
-
"data_payload": deepcopy(data_payload) if isinstance(data_payload, dict) else {},
|
|
859
|
-
"page_num": page_num,
|
|
860
|
-
"page_size": page_size,
|
|
861
|
-
"page_num_y": page_num_y,
|
|
862
|
-
"page_size_y": page_size_y,
|
|
863
|
-
}
|
|
885
|
+
normalized_args = {"chart_id": chart_id}
|
|
864
886
|
return _safe_tool_call(
|
|
865
|
-
lambda: self._facade.chart_get(
|
|
866
|
-
profile=profile,
|
|
867
|
-
chart_id=chart_id,
|
|
868
|
-
data_payload=normalized_args["data_payload"],
|
|
869
|
-
page_num=page_num,
|
|
870
|
-
page_size=page_size,
|
|
871
|
-
page_num_y=page_num_y,
|
|
872
|
-
page_size_y=page_size_y,
|
|
873
|
-
),
|
|
887
|
+
lambda: self._facade.chart_get(profile=profile, chart_id=chart_id),
|
|
874
888
|
error_code="CHART_GET_FAILED",
|
|
875
889
|
normalized_args=normalized_args,
|
|
876
890
|
suggested_next_call={"tool_name": "chart_get", "arguments": {"profile": profile, "chart_id": chart_id}},
|
|
@@ -2517,7 +2531,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
2517
2531
|
"execution_notes": [
|
|
2518
2532
|
"apply may return partial_success when some views land and others fail",
|
|
2519
2533
|
"when duplicate view names exist, supply view_key to target the exact view",
|
|
2520
|
-
"read back
|
|
2534
|
+
"read back app_get_views after any failed or partial view apply",
|
|
2521
2535
|
"view existence verification and saved-filter verification are separate; treat filters as unverified until verification.view_filters_verified is true",
|
|
2522
2536
|
"buttons omitted preserves existing button config; buttons=[] clears all buttons; buttons=[...] replaces the full button config",
|
|
2523
2537
|
],
|
|
@@ -2546,7 +2560,73 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
2546
2560
|
"remove_views": [],
|
|
2547
2561
|
},
|
|
2548
2562
|
},
|
|
2549
|
-
"
|
|
2563
|
+
"app_get": {
|
|
2564
|
+
"allowed_keys": ["app_key"],
|
|
2565
|
+
"aliases": {},
|
|
2566
|
+
"allowed_values": {},
|
|
2567
|
+
"execution_notes": [
|
|
2568
|
+
"returns builder-side app configuration summary and editability",
|
|
2569
|
+
"use this as the default builder discovery read before fields/layout/views/flow/charts detail reads",
|
|
2570
|
+
"editability reflects builder permissions, not end-user data visibility",
|
|
2571
|
+
],
|
|
2572
|
+
"minimal_example": {
|
|
2573
|
+
"profile": "default",
|
|
2574
|
+
"app_key": "APP_KEY",
|
|
2575
|
+
},
|
|
2576
|
+
},
|
|
2577
|
+
"app_get_fields": {
|
|
2578
|
+
"allowed_keys": ["app_key"],
|
|
2579
|
+
"aliases": {},
|
|
2580
|
+
"allowed_values": {},
|
|
2581
|
+
"execution_notes": [
|
|
2582
|
+
"returns compact current field configuration for one app",
|
|
2583
|
+
"use this before app_schema_apply when you need exact field definitions",
|
|
2584
|
+
],
|
|
2585
|
+
"minimal_example": {
|
|
2586
|
+
"profile": "default",
|
|
2587
|
+
"app_key": "APP_KEY",
|
|
2588
|
+
},
|
|
2589
|
+
},
|
|
2590
|
+
"app_get_layout": {
|
|
2591
|
+
"allowed_keys": ["app_key"],
|
|
2592
|
+
"aliases": {},
|
|
2593
|
+
"allowed_values": {},
|
|
2594
|
+
"execution_notes": [
|
|
2595
|
+
"returns compact current layout configuration for one app",
|
|
2596
|
+
"use this before app_layout_apply when you need paragraph and row structure",
|
|
2597
|
+
],
|
|
2598
|
+
"minimal_example": {
|
|
2599
|
+
"profile": "default",
|
|
2600
|
+
"app_key": "APP_KEY",
|
|
2601
|
+
},
|
|
2602
|
+
},
|
|
2603
|
+
"app_get_views": {
|
|
2604
|
+
"allowed_keys": ["app_key"],
|
|
2605
|
+
"aliases": {},
|
|
2606
|
+
"allowed_values": {},
|
|
2607
|
+
"execution_notes": [
|
|
2608
|
+
"returns compact current view inventory for one app",
|
|
2609
|
+
"use this before app_views_apply when you need exact current view keys",
|
|
2610
|
+
],
|
|
2611
|
+
"minimal_example": {
|
|
2612
|
+
"profile": "default",
|
|
2613
|
+
"app_key": "APP_KEY",
|
|
2614
|
+
},
|
|
2615
|
+
},
|
|
2616
|
+
"app_get_flow": {
|
|
2617
|
+
"allowed_keys": ["app_key"],
|
|
2618
|
+
"aliases": {},
|
|
2619
|
+
"allowed_values": {},
|
|
2620
|
+
"execution_notes": [
|
|
2621
|
+
"returns workflow configuration summary for one app",
|
|
2622
|
+
"use this before app_flow_apply when you need the current node structure",
|
|
2623
|
+
],
|
|
2624
|
+
"minimal_example": {
|
|
2625
|
+
"profile": "default",
|
|
2626
|
+
"app_key": "APP_KEY",
|
|
2627
|
+
},
|
|
2628
|
+
},
|
|
2629
|
+
"app_get_charts": {
|
|
2550
2630
|
"allowed_keys": ["app_key"],
|
|
2551
2631
|
"aliases": {},
|
|
2552
2632
|
"allowed_values": {},
|
|
@@ -2565,8 +2645,8 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
2565
2645
|
"aliases": {},
|
|
2566
2646
|
"allowed_values": {},
|
|
2567
2647
|
"execution_notes": [
|
|
2568
|
-
"returns
|
|
2569
|
-
"use this as the portal discovery path before portal_get",
|
|
2648
|
+
"returns builder-configurable portal list items only",
|
|
2649
|
+
"use this as the builder portal discovery path before portal_get",
|
|
2570
2650
|
"results are compact list items, not raw dash payloads",
|
|
2571
2651
|
],
|
|
2572
2652
|
"minimal_example": {
|
|
@@ -2578,8 +2658,8 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
2578
2658
|
"aliases": {"beingDraft": "being_draft"},
|
|
2579
2659
|
"allowed_values": {},
|
|
2580
2660
|
"execution_notes": [
|
|
2581
|
-
"returns
|
|
2582
|
-
"chart and view components are returned as refs only; use chart_get or view_get for
|
|
2661
|
+
"returns builder-side portal configuration detail plus a normalized component inventory",
|
|
2662
|
+
"chart and view components are returned as refs only; use builder chart_get or builder view_get for configuration detail",
|
|
2583
2663
|
"being_draft=true reads the current draft view; being_draft=false reads live",
|
|
2584
2664
|
],
|
|
2585
2665
|
"minimal_example": {
|
|
@@ -2616,69 +2696,32 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
2616
2696
|
"reorder_chart_ids": [],
|
|
2617
2697
|
},
|
|
2618
2698
|
},
|
|
2619
|
-
"chart_apply": {
|
|
2620
|
-
"allowed_keys": ["app_key", "upsert_charts", "remove_chart_ids", "reorder_chart_ids"],
|
|
2621
|
-
"aliases": {
|
|
2622
|
-
"legacy_tool_name": "app_charts_apply",
|
|
2623
|
-
},
|
|
2624
|
-
"allowed_values": {
|
|
2625
|
-
"chart.chart_type": [member.value for member in PublicChartType],
|
|
2626
|
-
"chart.filter.operator": [member.value for member in ViewFilterOperator],
|
|
2627
|
-
},
|
|
2628
|
-
"execution_notes": [
|
|
2629
|
-
"legacy compatibility alias; prefer app_charts_apply in new builder flows",
|
|
2630
|
-
"behavior matches app_charts_apply exactly",
|
|
2631
|
-
],
|
|
2632
|
-
"minimal_example": {
|
|
2633
|
-
"profile": "default",
|
|
2634
|
-
"app_key": "APP_KEY",
|
|
2635
|
-
"upsert_charts": [{"name": "数据总量", "chart_type": "target", "indicator_field_ids": []}],
|
|
2636
|
-
"remove_chart_ids": [],
|
|
2637
|
-
"reorder_chart_ids": [],
|
|
2638
|
-
},
|
|
2639
|
-
},
|
|
2640
|
-
"portal_read_summary": {
|
|
2641
|
-
"allowed_keys": ["dash_key", "being_draft"],
|
|
2642
|
-
"aliases": {"beingDraft": "being_draft"},
|
|
2643
|
-
"allowed_values": {},
|
|
2644
|
-
"execution_notes": [
|
|
2645
|
-
"returns a compact portal summary instead of the raw dash payload",
|
|
2646
|
-
"being_draft=true reads the current draft view; being_draft=false reads live",
|
|
2647
|
-
"use this before portal_apply when you need the current section inventory or target dash metadata",
|
|
2648
|
-
],
|
|
2649
|
-
"minimal_example": {
|
|
2650
|
-
"profile": "default",
|
|
2651
|
-
"dash_key": "DASH_KEY",
|
|
2652
|
-
"being_draft": True,
|
|
2653
|
-
},
|
|
2654
|
-
},
|
|
2655
2699
|
"view_get": {
|
|
2656
|
-
"allowed_keys": ["
|
|
2657
|
-
"aliases": {
|
|
2700
|
+
"allowed_keys": ["view_key"],
|
|
2701
|
+
"aliases": {},
|
|
2658
2702
|
"allowed_values": {},
|
|
2659
2703
|
"execution_notes": [
|
|
2660
|
-
"returns one view
|
|
2661
|
-
"does not return record data; use
|
|
2662
|
-
"use this after portal_get when a component references a view_ref.view_key",
|
|
2704
|
+
"returns one builder-side view definition detail",
|
|
2705
|
+
"does not return record data; use user-side view_get or record_list for runtime rows",
|
|
2706
|
+
"use this after builder portal_get when a component references a view_ref.view_key",
|
|
2663
2707
|
],
|
|
2664
2708
|
"minimal_example": {
|
|
2665
2709
|
"profile": "default",
|
|
2666
|
-
"
|
|
2710
|
+
"view_key": "VIEW_KEY",
|
|
2667
2711
|
},
|
|
2668
2712
|
},
|
|
2669
2713
|
"chart_get": {
|
|
2670
|
-
"allowed_keys": ["chart_id"
|
|
2671
|
-
"aliases": {
|
|
2714
|
+
"allowed_keys": ["chart_id"],
|
|
2715
|
+
"aliases": {},
|
|
2672
2716
|
"allowed_values": {},
|
|
2673
2717
|
"execution_notes": [
|
|
2674
|
-
"returns chart base info
|
|
2718
|
+
"returns builder-side chart base info and chart config only",
|
|
2675
2719
|
"chart_id is required; chart names are not accepted here",
|
|
2676
|
-
"
|
|
2720
|
+
"does not return chart data; use user-side chart_get for runtime data access",
|
|
2677
2721
|
],
|
|
2678
2722
|
"minimal_example": {
|
|
2679
2723
|
"profile": "default",
|
|
2680
2724
|
"chart_id": "CHART_ID",
|
|
2681
|
-
"data_payload": {},
|
|
2682
2725
|
},
|
|
2683
2726
|
},
|
|
2684
2727
|
"portal_apply": {
|
|
@@ -2745,6 +2788,4 @@ _PRIVATE_BUILDER_TOOL_CONTRACTS = {
|
|
|
2745
2788
|
"app_views_plan",
|
|
2746
2789
|
}
|
|
2747
2790
|
|
|
2748
|
-
_BUILDER_TOOL_CONTRACT_ALIASES = {
|
|
2749
|
-
"chart_apply": "app_charts_apply",
|
|
2750
|
-
}
|
|
2791
|
+
_BUILDER_TOOL_CONTRACT_ALIASES = {}
|
|
@@ -179,9 +179,6 @@ class AppTools(ToolBase):
|
|
|
179
179
|
accessible_views.extend(self._resolve_accessible_custom_views(context, app_key))
|
|
180
180
|
import_capability, import_warnings = _derive_import_capability(base_info)
|
|
181
181
|
warnings.extend(import_warnings)
|
|
182
|
-
editability, editability_warnings = _derive_editability(base_info)
|
|
183
|
-
warnings.extend(editability_warnings)
|
|
184
|
-
|
|
185
182
|
return {
|
|
186
183
|
"profile": profile,
|
|
187
184
|
"ws_id": session_profile.selected_ws_id,
|
|
@@ -193,7 +190,6 @@ class AppTools(ToolBase):
|
|
|
193
190
|
"app_name": app_name,
|
|
194
191
|
"can_create": can_create,
|
|
195
192
|
"import_capability": import_capability,
|
|
196
|
-
"editability": editability,
|
|
197
193
|
"accessible_views": accessible_views,
|
|
198
194
|
},
|
|
199
195
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
from uuid import uuid4
|
|
4
5
|
|
|
5
6
|
from mcp.server.fastmcp import FastMCP
|
|
@@ -18,6 +19,37 @@ def _qingbi_base_url(base_url: str) -> str:
|
|
|
18
19
|
return normalized[:-4] if normalized.endswith("/api") else normalized
|
|
19
20
|
|
|
20
21
|
|
|
22
|
+
def _should_retry_qflow_base(error: QingflowApiError) -> bool:
|
|
23
|
+
return int(getattr(error, "backend_code", 0) or 0) == 81007
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _should_retry_asos_data(error: QingflowApiError) -> bool:
|
|
27
|
+
backend_code = int(getattr(error, "backend_code", 0) or 0)
|
|
28
|
+
http_status = getattr(error, "http_status", None)
|
|
29
|
+
return backend_code in {44011, 81007} or http_status == 404
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _coerce_tool_error(error: RuntimeError | QingflowApiError) -> QingflowApiError | None:
|
|
33
|
+
if isinstance(error, QingflowApiError):
|
|
34
|
+
return error
|
|
35
|
+
if not isinstance(error, RuntimeError):
|
|
36
|
+
return None
|
|
37
|
+
try:
|
|
38
|
+
payload = json.loads(str(error))
|
|
39
|
+
except Exception:
|
|
40
|
+
return None
|
|
41
|
+
if not isinstance(payload, dict):
|
|
42
|
+
return None
|
|
43
|
+
return QingflowApiError(
|
|
44
|
+
category=str(payload.get("category") or "runtime"),
|
|
45
|
+
message=str(payload.get("message") or str(error)),
|
|
46
|
+
backend_code=payload.get("backend_code"),
|
|
47
|
+
request_id=payload.get("request_id"),
|
|
48
|
+
http_status=payload.get("http_status"),
|
|
49
|
+
details=payload.get("details") if isinstance(payload.get("details"), dict) else None,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
21
53
|
class QingbiReportTools(ToolBase):
|
|
22
54
|
def register(self, mcp: FastMCP) -> None:
|
|
23
55
|
@mcp.tool()
|
|
@@ -127,7 +159,13 @@ class QingbiReportTools(ToolBase):
|
|
|
127
159
|
|
|
128
160
|
def qingbi_report_get_base(self, *, profile: str, chart_id: str) -> JSONObject:
|
|
129
161
|
self._require_chart_id(chart_id)
|
|
130
|
-
|
|
162
|
+
try:
|
|
163
|
+
return self._request(profile, "GET", f"/qingbi/charts/baseinfo/{chart_id}", chart_id=chart_id)
|
|
164
|
+
except (QingflowApiError, RuntimeError) as raw_error:
|
|
165
|
+
error = _coerce_tool_error(raw_error)
|
|
166
|
+
if error is None or not _should_retry_qflow_base(error):
|
|
167
|
+
raise
|
|
168
|
+
return self._request(profile, "GET", f"/qingbi/charts/qflow/baseinfo/{chart_id}", chart_id=chart_id)
|
|
131
169
|
|
|
132
170
|
def qingbi_report_update_base(self, *, profile: str, chart_id: str, payload: JSONObject) -> JSONObject:
|
|
133
171
|
self._require_chart_id(chart_id)
|
|
@@ -166,21 +204,34 @@ class QingbiReportTools(ToolBase):
|
|
|
166
204
|
params["pageNumY"] = page_num_y
|
|
167
205
|
if page_size_y is not None:
|
|
168
206
|
params["pageSizeY"] = page_size_y
|
|
169
|
-
|
|
207
|
+
try:
|
|
208
|
+
if payload:
|
|
209
|
+
return self._request(
|
|
210
|
+
profile,
|
|
211
|
+
"POST",
|
|
212
|
+
f"/qingbi/charts/data/qflow/{chart_id}/detail",
|
|
213
|
+
chart_id=chart_id,
|
|
214
|
+
params=params,
|
|
215
|
+
json_body=payload,
|
|
216
|
+
)
|
|
170
217
|
return self._request(
|
|
171
218
|
profile,
|
|
172
|
-
"
|
|
173
|
-
f"/qingbi/charts/data/qflow/{chart_id}
|
|
219
|
+
"GET",
|
|
220
|
+
f"/qingbi/charts/data/qflow/{chart_id}",
|
|
174
221
|
chart_id=chart_id,
|
|
175
222
|
params=params,
|
|
176
|
-
json_body=payload,
|
|
177
223
|
)
|
|
224
|
+
except (QingflowApiError, RuntimeError) as raw_error:
|
|
225
|
+
error = _coerce_tool_error(raw_error)
|
|
226
|
+
if error is None or not _should_retry_asos_data(error):
|
|
227
|
+
raise
|
|
178
228
|
return self._request(
|
|
179
229
|
profile,
|
|
180
|
-
"
|
|
181
|
-
f"/qingbi/charts/data/qflow/{chart_id}",
|
|
230
|
+
"POST",
|
|
231
|
+
f"/qingbi/charts/data/qflow/{chart_id}/asos",
|
|
182
232
|
chart_id=chart_id,
|
|
183
233
|
params=params,
|
|
234
|
+
json_body=payload or {},
|
|
184
235
|
)
|
|
185
236
|
|
|
186
237
|
def qingbi_report_delete(self, *, profile: str, chart_id: str) -> JSONObject:
|