@josephyan/qingflow-app-builder-mcp 0.2.0-beta.7 → 0.2.0-beta.71

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.
Files changed (70) hide show
  1. package/README.md +5 -3
  2. package/docs/local-agent-install.md +21 -5
  3. package/npm/bin/qingflow-app-builder-mcp.mjs +1 -1
  4. package/npm/lib/runtime.mjs +168 -12
  5. package/package.json +1 -1
  6. package/pyproject.toml +4 -1
  7. package/skills/qingflow-app-builder/SKILL.md +155 -22
  8. package/skills/qingflow-app-builder/references/create-app.md +51 -21
  9. package/skills/qingflow-app-builder/references/environments.md +1 -1
  10. package/skills/qingflow-app-builder/references/flow-actors-and-permissions.md +123 -0
  11. package/skills/qingflow-app-builder/references/gotchas.md +28 -1
  12. package/skills/qingflow-app-builder/references/solution-playbooks.md +14 -12
  13. package/skills/qingflow-app-builder/references/tool-selection.md +47 -19
  14. package/skills/qingflow-app-builder/references/update-flow.md +112 -25
  15. package/skills/qingflow-app-builder/references/update-layout.md +11 -24
  16. package/skills/qingflow-app-builder/references/update-schema.md +1 -23
  17. package/skills/qingflow-app-builder/references/update-views.md +87 -21
  18. package/skills/qingflow-app-builder-code-integrations/SKILL.md +137 -0
  19. package/skills/qingflow-app-builder-code-integrations/agents/openai.yaml +4 -0
  20. package/skills/qingflow-app-builder-code-integrations/references/code-block.md +66 -0
  21. package/skills/qingflow-app-builder-code-integrations/references/q-linker.md +77 -0
  22. package/src/qingflow_mcp/__init__.py +1 -1
  23. package/src/qingflow_mcp/backend_client.py +210 -0
  24. package/src/qingflow_mcp/builder_facade/models.py +1252 -3
  25. package/src/qingflow_mcp/builder_facade/service.py +11367 -2389
  26. package/src/qingflow_mcp/cli/__init__.py +1 -0
  27. package/src/qingflow_mcp/cli/commands/__init__.py +15 -0
  28. package/src/qingflow_mcp/cli/commands/app.py +40 -0
  29. package/src/qingflow_mcp/cli/commands/auth.py +78 -0
  30. package/src/qingflow_mcp/cli/commands/builder.py +515 -0
  31. package/src/qingflow_mcp/cli/commands/common.py +62 -0
  32. package/src/qingflow_mcp/cli/commands/imports.py +96 -0
  33. package/src/qingflow_mcp/cli/commands/record.py +304 -0
  34. package/src/qingflow_mcp/cli/commands/task.py +89 -0
  35. package/src/qingflow_mcp/cli/commands/workspace.py +33 -0
  36. package/src/qingflow_mcp/cli/context.py +48 -0
  37. package/src/qingflow_mcp/cli/formatters.py +355 -0
  38. package/src/qingflow_mcp/cli/json_io.py +50 -0
  39. package/src/qingflow_mcp/cli/main.py +149 -0
  40. package/src/qingflow_mcp/config.py +39 -0
  41. package/src/qingflow_mcp/import_store.py +121 -0
  42. package/src/qingflow_mcp/list_type_labels.py +24 -0
  43. package/src/qingflow_mcp/response_trim.py +668 -0
  44. package/src/qingflow_mcp/server.py +160 -18
  45. package/src/qingflow_mcp/server_app_builder.py +275 -68
  46. package/src/qingflow_mcp/server_app_user.py +219 -191
  47. package/src/qingflow_mcp/session_store.py +41 -1
  48. package/src/qingflow_mcp/solution/compiler/form_compiler.py +43 -4
  49. package/src/qingflow_mcp/solution/compiler/icon_utils.py +119 -45
  50. package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +41 -2
  51. package/src/qingflow_mcp/solution/executor.py +107 -11
  52. package/src/qingflow_mcp/solution/spec_models.py +2 -0
  53. package/src/qingflow_mcp/tools/ai_builder_tools.py +2032 -127
  54. package/src/qingflow_mcp/tools/app_tools.py +419 -12
  55. package/src/qingflow_mcp/tools/approval_tools.py +571 -72
  56. package/src/qingflow_mcp/tools/auth_tools.py +398 -2
  57. package/src/qingflow_mcp/tools/code_block_tools.py +756 -0
  58. package/src/qingflow_mcp/tools/custom_button_tools.py +179 -0
  59. package/src/qingflow_mcp/tools/directory_tools.py +203 -31
  60. package/src/qingflow_mcp/tools/feedback_tools.py +230 -0
  61. package/src/qingflow_mcp/tools/file_tools.py +1 -0
  62. package/src/qingflow_mcp/tools/import_tools.py +2150 -0
  63. package/src/qingflow_mcp/tools/package_tools.py +18 -4
  64. package/src/qingflow_mcp/tools/portal_tools.py +31 -0
  65. package/src/qingflow_mcp/tools/qingbi_report_tools.py +109 -7
  66. package/src/qingflow_mcp/tools/record_tools.py +9894 -1104
  67. package/src/qingflow_mcp/tools/solution_tools.py +115 -3
  68. package/src/qingflow_mcp/tools/task_context_tools.py +2040 -0
  69. package/src/qingflow_mcp/tools/task_tools.py +376 -225
  70. package/src/qingflow_mcp/tools/workspace_tools.py +163 -19
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from copy import deepcopy
4
+ import json
4
5
  from typing import Any
5
6
  from uuid import uuid4
6
7
 
@@ -8,6 +9,7 @@ from mcp.server.fastmcp import FastMCP
8
9
  from pydantic import BaseModel, ValidationError
9
10
 
10
11
  from ..config import DEFAULT_PROFILE
12
+ from ..errors import QingflowApiError
11
13
  from ..list_type_labels import get_record_list_type_label
12
14
  from ..solution.build_assembly_store import BuildAssemblyStore, default_manifest
13
15
  from ..solution.compiler import CompiledSolution, ExecutionPlan, ExecutionStep, build_execution_plan, compile_solution
@@ -1850,7 +1852,35 @@ class SolutionTools(ToolBase):
1850
1852
  packages = PackageTools(self.sessions, self.backend)
1851
1853
  normalized_name = package_name.strip()
1852
1854
  if package_tag_id > 0:
1853
- result = packages.package_get(profile=profile, tag_id=package_tag_id, include_raw=False)
1855
+ try:
1856
+ result = packages.package_get(profile=profile, tag_id=package_tag_id, include_raw=False)
1857
+ except (QingflowApiError, RuntimeError) as exc:
1858
+ error = _coerce_solution_api_error(exc)
1859
+ if error.backend_code in {40002, 40027}:
1860
+ return {
1861
+ "status": "resolved",
1862
+ "matched_via": "tag_id",
1863
+ "tag_id": package_tag_id,
1864
+ "tag_name": None,
1865
+ "candidates": [],
1866
+ "metadata_unverified": True,
1867
+ "lookup_permission_blocked": {
1868
+ "scope": "package",
1869
+ "target": {"tag_id": package_tag_id},
1870
+ "transport_error": {
1871
+ "http_status": error.http_status,
1872
+ "backend_code": error.backend_code,
1873
+ "category": error.category,
1874
+ "request_id": error.request_id,
1875
+ },
1876
+ },
1877
+ }
1878
+ return _builder_package_resolution_failed(
1879
+ package_name=normalized_name,
1880
+ package_tag_id=package_tag_id,
1881
+ error=error,
1882
+ retried=False,
1883
+ )
1854
1884
  summary = result.get("result") if isinstance(result.get("result"), dict) else {}
1855
1885
  return {
1856
1886
  "status": "resolved",
@@ -1869,12 +1899,28 @@ class SolutionTools(ToolBase):
1869
1899
  "source_shape": None,
1870
1900
  "retried": False,
1871
1901
  }
1872
- result = packages.package_list(profile=profile, trial_status="all", include_raw=False)
1902
+ try:
1903
+ result = packages.package_list(profile=profile, trial_status="all", include_raw=False)
1904
+ except (QingflowApiError, RuntimeError) as exc:
1905
+ return _builder_package_resolution_failed(
1906
+ package_name=normalized_name,
1907
+ package_tag_id=package_tag_id,
1908
+ error=_coerce_solution_api_error(exc),
1909
+ retried=False,
1910
+ )
1873
1911
  items = result.get("items") if isinstance(result.get("items"), list) else []
1874
1912
  source_shape = result.get("source_shape")
1875
1913
  retried = False
1876
1914
  if not items:
1877
- retry_result = packages.package_list(profile=profile, trial_status="all", include_raw=False)
1915
+ try:
1916
+ retry_result = packages.package_list(profile=profile, trial_status="all", include_raw=False)
1917
+ except (QingflowApiError, RuntimeError) as exc:
1918
+ return _builder_package_resolution_failed(
1919
+ package_name=normalized_name,
1920
+ package_tag_id=package_tag_id,
1921
+ error=_coerce_solution_api_error(exc),
1922
+ retried=True,
1923
+ )
1878
1924
  retry_items = retry_result.get("items") if isinstance(retry_result.get("items"), list) else []
1879
1925
  if retry_items:
1880
1926
  items = retry_items
@@ -2982,6 +3028,72 @@ def _solution_error_fields(
2982
3028
  }
2983
3029
 
2984
3030
 
3031
+ def _builder_package_resolution_failed(
3032
+ *,
3033
+ package_name: str,
3034
+ package_tag_id: int,
3035
+ error: QingflowApiError,
3036
+ retried: bool,
3037
+ ) -> dict[str, Any]:
3038
+ if package_tag_id > 0:
3039
+ detail = f"failed to resolve package_tag_id '{package_tag_id}': {error.message}"
3040
+ elif error.backend_code in {40002, 40027}:
3041
+ detail = (
3042
+ f"failed to resolve package '{package_name}' because package listing is permission-restricted; "
3043
+ "provide package_tag_id explicitly or use an account that can list packages"
3044
+ )
3045
+ else:
3046
+ detail = f"failed to resolve package '{package_name}': {error.message}"
3047
+ error_fields = _solution_error_fields(category="config", detail=detail, suggested_next_call=None, stage="app")
3048
+ error_fields["error_code"] = "PACKAGE_RESOLVE_FAILED"
3049
+ return {
3050
+ "status": "failed",
3051
+ "response": {
3052
+ "status": "failed",
3053
+ "mode": "plan",
3054
+ "stage": "app",
3055
+ "errors": [{"category": "config", "detail": detail}],
3056
+ "package_resolution": {
3057
+ "status": "failed",
3058
+ "requested_name": package_name or None,
3059
+ "requested_tag_id": package_tag_id or None,
3060
+ "resolution_policy": "exact_name_only" if package_name else "tag_id_only",
3061
+ "candidates": [],
3062
+ "source_shape": None,
3063
+ "retried": retried,
3064
+ "transport_error": {
3065
+ "http_status": error.http_status,
3066
+ "backend_code": error.backend_code,
3067
+ "category": error.category,
3068
+ "request_id": error.request_id,
3069
+ },
3070
+ },
3071
+ **error_fields,
3072
+ },
3073
+ }
3074
+
3075
+
3076
+ def _coerce_solution_api_error(error: Exception) -> QingflowApiError:
3077
+ if isinstance(error, QingflowApiError):
3078
+ return error
3079
+ if isinstance(error, RuntimeError):
3080
+ try:
3081
+ payload = json.loads(str(error))
3082
+ except json.JSONDecodeError:
3083
+ payload = None
3084
+ if isinstance(payload, dict) and payload.get("category") and payload.get("message"):
3085
+ details = payload.get("details")
3086
+ return QingflowApiError(
3087
+ category=str(payload.get("category")),
3088
+ message=str(payload.get("message")),
3089
+ backend_code=payload.get("backend_code"),
3090
+ request_id=payload.get("request_id"),
3091
+ http_status=payload.get("http_status"),
3092
+ details=details if isinstance(details, dict) else None,
3093
+ )
3094
+ return QingflowApiError(category="runtime", message=str(error))
3095
+
3096
+
2985
3097
  def _coerce_count(value: Any) -> int | None:
2986
3098
  if isinstance(value, bool) or value is None:
2987
3099
  return None