@qingflow-tech/qingflow-cli 1.1.61-beta.4 → 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.
Files changed (48) hide show
  1. package/README.md +3 -3
  2. package/package.json +1 -1
  3. package/pyproject.toml +1 -2
  4. package/skills/qingflow-cli/SKILL.md +2 -104
  5. package/skills/qingflow-cli/reference/builder/20-build-complete-system.md +2 -2
  6. package/skills/qingflow-cli/reference/builder/30-schema-fields.md +5 -9
  7. package/skills/qingflow-cli/reference/builder/reference/app-delivery-sop.md +3 -3
  8. package/src/qingflow_mcp/app_form_compat.py +0 -3
  9. package/src/qingflow_mcp/app_form_validator.py +17 -69
  10. package/src/qingflow_mcp/builder_facade/app_form.py +100 -497
  11. package/src/qingflow_mcp/builder_facade/schemas/app_form/manifest.json +0 -1
  12. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/complete.json +30 -64
  13. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/address.json +8 -4
  14. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/amount.json +8 -4
  15. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/attachment.json +10 -6
  16. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/code_block.json +10 -6
  17. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/data_relation.json +18 -11
  18. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/date.json +8 -4
  19. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/datetime.json +8 -4
  20. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/department.json +10 -6
  21. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/email.json +8 -4
  22. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/link.json +8 -4
  23. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/long_text.json +8 -4
  24. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/member.json +10 -6
  25. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/multi_select.json +8 -4
  26. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/number.json +8 -4
  27. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/phone.json +8 -4
  28. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/q_linker.json +10 -6
  29. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/relation.json +15 -46
  30. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/single_choice.json +8 -4
  31. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/single_select.json +8 -4
  32. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/subtable.json +27 -61
  33. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/text.json +8 -4
  34. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/formula.json +0 -43
  35. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/summary.json +12 -7
  36. package/src/qingflow_mcp/builder_facade/service.py +1 -39
  37. package/src/qingflow_mcp/cli/main.py +1 -34
  38. package/src/qingflow_mcp/cloud_context.py +1 -16
  39. package/src/qingflow_mcp/public_surface.py +8 -67
  40. package/src/qingflow_mcp/response_trim.py +1 -2
  41. package/src/qingflow_mcp/server.py +6 -12
  42. package/src/qingflow_mcp/server_app_builder.py +5 -9
  43. package/src/qingflow_mcp/server_app_user.py +5 -9
  44. package/src/qingflow_mcp/server_cloud.py +7 -43
  45. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/rich_text.json +0 -146
  46. package/src/qingflow_mcp/tool_catalog.py +0 -387
  47. package/src/qingflow_mcp/tool_catalog_i18n.py +0 -185
  48. package/src/qingflow_mcp/tool_policy.py +0 -197
@@ -136,10 +136,10 @@
136
136
  ]
137
137
  }
138
138
  ],
139
- "description": "字段选择器;用于定位应用的数据标题、数据封面、自定义编号取值来源或其他配置引用的字段。至少提供 queId 或 name,两者同时提供时必须指向同一字段。后端允许同父级同名字段,queId 是稳定身份;name 仅在唯一时可解析,命中多个同名时报 AMBIGUOUS_FIELD_SELECTOR,此时必须改用 queId。",
139
+ "description": "字段选择器;用于定位应用的数据标题、数据封面、自定义编号取值来源或其他配置引用的字段。至少提供 queId 或 name,两者同时提供时必须指向同一字段。",
140
140
  "properties": {
141
141
  "name": {
142
- "description": "字段名称;仅在当前作用域内唯一时可用于解析,同名命中多个字段时报 AMBIGUOUS_FIELD_SELECTOR,应改用 queId。",
142
+ "description": "字段名称;仅在当前作用域内唯一时可用于解析。",
143
143
  "minLength": 1,
144
144
  "type": "string"
145
145
  },
@@ -258,9 +258,13 @@
258
258
  "type": "array"
259
259
  },
260
260
  "width": {
261
- "description": "字段在所在行的宽度百分比(1-100 整数);后端不校验总和与取值,存量数据存在 33/34 等三等分布值。",
262
- "maximum": 100,
263
- "minimum": 1,
261
+ "description": "字段在所在行的宽度百分比;只能选择 25、50、75 或 100。",
262
+ "enum": [
263
+ 25,
264
+ 50,
265
+ 75,
266
+ 100
267
+ ],
264
268
  "type": "integer"
265
269
  }
266
270
  },
@@ -330,10 +334,11 @@
330
334
  "description": "段落判别值。"
331
335
  },
332
336
  "rows": {
333
- "description": "段落内的有序字段行;可为空数组(空 = 存量空段落形态,后端容忍,round-trip 空↔空稳定)。",
337
+ "description": "段落内的非空有序字段行。",
334
338
  "items": {
335
339
  "$ref": "#/$defs/FieldRow"
336
340
  },
341
+ "minItems": 1,
337
342
  "type": "array"
338
343
  },
339
344
  "sectionId": {
@@ -378,6 +383,7 @@
378
383
  ],
379
384
  "type": "object"
380
385
  },
386
+ "minItems": 1,
381
387
  "type": "array"
382
388
  },
383
389
  "sectionId": {
@@ -689,7 +695,6 @@
689
695
  "x-qingflow-supported-field-types": [
690
696
  "text",
691
697
  "long_text",
692
- "rich_text",
693
698
  "number",
694
699
  "amount",
695
700
  "link",
@@ -163,14 +163,9 @@ FIELD_TYPE_TO_QUESTION_TYPE: dict[str, int] = {
163
163
  FieldType.relation.value: 25,
164
164
  }
165
165
 
166
- # code_block / q_linker 输出目标字段类型。后端写链无类型白名单(FormSvcImpl 原样
167
- # 落库),运行时 DataSvcImpl.getAnswerValueByString:attachment 按拆分 URL 写入、
168
- # subtable 整表组装(均有专门分支),rich_text/data_relation 走 Question.canImport
169
- # 兜底按字符串写入(不报错)——四者为存量合法配置,与 validator 侧白名单同口径。
170
166
  INTEGRATION_OUTPUT_TARGET_FIELD_TYPES: tuple[str, ...] = (
171
167
  FieldType.text.value,
172
168
  FieldType.long_text.value,
173
- "rich_text",
174
169
  FieldType.number.value,
175
170
  FieldType.amount.value,
176
171
  FieldType.date.value,
@@ -178,9 +173,6 @@ INTEGRATION_OUTPUT_TARGET_FIELD_TYPES: tuple[str, ...] = (
178
173
  FieldType.single_select.value,
179
174
  FieldType.multi_select.value,
180
175
  FieldType.boolean.value,
181
- FieldType.attachment.value,
182
- "data_relation",
183
- FieldType.subtable.value,
184
176
  )
185
177
 
186
178
  MATCH_TYPE_ACCURACY = 1
@@ -23245,23 +23237,6 @@ def _apply_field_mutation(field: dict[str, Any], mutation: Any) -> None:
23245
23237
  field.pop("_question_rebuild_required", None)
23246
23238
 
23247
23239
 
23248
- class AmbiguousFieldSelector(ValueError):
23249
- """name-only selector 命中多个同名字段。
23250
-
23251
- 方案 A(duplicate-field-name):后端以 queId 为字段身份、允许同父级同名;
23252
- name 命中多个字段时禁止静默取第一个,必须显式失败并给出候选 queId。
23253
- compiler 包装层(app_form)把它转写为 AMBIGUOUS_FIELD_SELECTOR 错误码。
23254
- """
23255
-
23256
- def __init__(self, location: str, candidates: list[int]) -> None:
23257
- self.location = location
23258
- self.candidates = candidates
23259
- super().__init__(
23260
- f"{location} matches multiple fields with the same name; "
23261
- f"use queId to disambiguate (candidate queIds: {sorted(candidates)})"
23262
- )
23263
-
23264
-
23265
23240
  def _resolve_field_selector_with_uniqueness(
23266
23241
  *,
23267
23242
  fields: list[dict[str, Any]],
@@ -23277,12 +23252,7 @@ def _resolve_field_selector_with_uniqueness(
23277
23252
  name = str(selector.name or "").strip()
23278
23253
  matched = [field for field in fields if str(field.get("name") or "").strip() == name]
23279
23254
  if len(matched) > 1:
23280
- candidate_ids = [
23281
- ref
23282
- for candidate in matched
23283
- if (ref := _coerce_any_int(candidate.get("que_id"))) is not None
23284
- ]
23285
- raise AmbiguousFieldSelector(location, candidate_ids)
23255
+ raise ValueError(f"{location} matched multiple fields with the same name; use field_id or que_id")
23286
23256
  else:
23287
23257
  matched = []
23288
23258
  if len(matched) != 1:
@@ -29962,14 +29932,6 @@ def _build_view_update_payload(
29962
29932
  payload["viewgraphType"] = "calendarView"
29963
29933
  payload["beingShowTitleQue"] = True
29964
29934
  payload.pop("groupQueId", None)
29965
- elif normalized_type == "hierarchy":
29966
- payload["viewgraphType"] = "hierarchyView"
29967
- payload["beingShowTitleQue"] = True
29968
- payload.pop("groupQueId", None)
29969
- elif normalized_type == "calendar":
29970
- payload["viewgraphType"] = "calendarView"
29971
- payload["beingShowTitleQue"] = True
29972
- payload.pop("groupQueId", None)
29973
29935
  return _hydrate_view_backend_payload(
29974
29936
  payload=payload,
29975
29937
  view_type=normalized_type,
@@ -7,22 +7,11 @@ import sys
7
7
  from typing import Any, Callable, TextIO
8
8
 
9
9
  from ..errors import QingflowApiError, backend_code_value_int, message_looks_like_invalid_token
10
- from ..public_surface import (
11
- NON_TOOL_CLI_HANDLER_ROUTES,
12
- cli_public_tool_spec_from_namespace,
13
- cli_route_from_namespace,
14
- cli_trim_key_from_namespace,
15
- )
10
+ from ..public_surface import cli_public_tool_spec_from_namespace
16
11
  from ..resource_uri import cli_resource_base_url
17
12
  from ..response_trim import resolve_cli_tool_name, trim_error_response, trim_public_response
18
13
  from ..tools.ai_builder_tools import _attach_builder_apply_envelope
19
14
  from ..version import get_cli_version, get_cli_version_info
20
- from ..tool_policy import (
21
- TOOL_POLICY_ENV,
22
- TOOL_POLICY_INVALID,
23
- ToolPolicyViolation,
24
- parse_tool_policy,
25
- )
26
15
  from .context import CliContext, build_cli_context
27
16
  from .formatters import emit_json_result, emit_text_result
28
17
  from .commands import register_all_commands
@@ -101,14 +90,6 @@ def run(
101
90
  if handler is None:
102
91
  parser.print_help(out)
103
92
  return 2
104
- violation = _cli_tool_policy_violation(args)
105
- if violation is not None:
106
- return _emit_error(
107
- violation.payload(),
108
- json_mode=bool(args.json),
109
- stdout=out,
110
- stderr=err,
111
- )
112
93
  try:
113
94
  if _should_force_json_output(args):
114
95
  setattr(args, "json", True)
@@ -141,20 +122,6 @@ def run(
141
122
  return exit_code
142
123
 
143
124
 
144
- def _cli_tool_policy_violation(args: argparse.Namespace) -> ToolPolicyViolation | None:
145
- stable_id = cli_trim_key_from_namespace(args)
146
- if stable_id is None:
147
- # 仅显式登记的非业务分发器可跳过闸门;无子命令的新增 handler 也必须默认拒绝。
148
- if cli_route_from_namespace(args) in NON_TOOL_CLI_HANDLER_ROUTES:
149
- return None
150
- return ToolPolicyViolation(
151
- TOOL_POLICY_INVALID,
152
- "user:unregistered",
153
- "CLI 业务命令未登记稳定工具 ID",
154
- )
155
- return parse_tool_policy(os.getenv(TOOL_POLICY_ENV)).violation_for(stable_id)
156
-
157
-
158
125
  def _handle_version(_args: argparse.Namespace, _context: CliContext) -> dict[str, Any]:
159
126
  info = get_cli_version_info()
160
127
  return {
@@ -2,14 +2,11 @@ from __future__ import annotations
2
2
 
3
3
  from contextvars import ContextVar
4
4
  from dataclasses import dataclass, field
5
- from typing import TYPE_CHECKING, Callable
5
+ from typing import Callable
6
6
 
7
7
  from .backend_client import BackendRequestContext
8
8
  from .session_store import BackendSession, SessionProfile
9
9
 
10
- if TYPE_CHECKING:
11
- from .tool_policy import ToolPolicySnapshot
12
-
13
10
 
14
11
  CLOUD_PROFILE_NAME = "__cloud_mcp__"
15
12
 
@@ -76,15 +73,3 @@ current_call_platform: ContextVar[str] = ContextVar(
76
73
  "qingflow_mcp_current_call_platform",
77
74
  default="",
78
75
  )
79
-
80
- # 远端 MCP 每个 HTTP 请求独立绑定策略原文;缺头用 None 表示,不回退进程环境变量。
81
- current_tool_policy_header: ContextVar[str | None] = ContextVar(
82
- "qingflow_mcp_current_tool_policy_header",
83
- default=None,
84
- )
85
-
86
- # 在 HTTP 入口合成一次快照,列表与调用共用,避免将 URL 选择保存到会话或共享环境。
87
- current_http_tool_policy: ContextVar[ToolPolicySnapshot | None] = ContextVar(
88
- "qingflow_mcp_current_http_tool_policy",
89
- default=None,
90
- )
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import argparse
4
3
  from argparse import Namespace
5
4
  from dataclasses import dataclass
6
5
 
@@ -110,7 +109,6 @@ USER_PUBLIC_TOOL_SPECS: tuple[PublicToolSpec, ...] = (
110
109
  PublicToolSpec(USER_DOMAIN, "record_export_direct", ("record_export_direct",), ("export", "direct"), cli_show_effective_context=True),
111
110
  PublicToolSpec(USER_DOMAIN, "record_code_block_run", ("record_code_block_run",), ("record", "code-block-run"), cli_show_effective_context=True, cli_context_write=True),
112
111
  PublicToolSpec(USER_DOMAIN, "task_list", ("task_list",), ("task", "list"), cli_show_effective_context=True),
113
- PublicToolSpec(USER_DOMAIN, "task_workbench", cli_route=("task",), mcp_public=False),
114
112
  PublicToolSpec(USER_DOMAIN, "task_get", ("task_get",), ("task", "get"), cli_show_effective_context=True),
115
113
  PublicToolSpec(USER_DOMAIN, "task_action_execute", ("task_action_execute",), ("task", "action"), cli_show_effective_context=True, cli_context_write=True),
116
114
  PublicToolSpec(
@@ -132,12 +130,12 @@ USER_PUBLIC_TOOL_SPECS: tuple[PublicToolSpec, ...] = (
132
130
 
133
131
 
134
132
  BUILDER_PUBLIC_TOOL_SPECS: tuple[PublicToolSpec, ...] = (
135
- PublicToolSpec(BUILDER_DOMAIN, "auth_use_credential", ("auth_use_credential",), cli_public=False),
136
- PublicToolSpec(BUILDER_DOMAIN, "auth_whoami", ("auth_whoami",), cli_public=False),
137
- PublicToolSpec(BUILDER_DOMAIN, "auth_logout", ("auth_logout",), cli_public=False),
133
+ PublicToolSpec(BUILDER_DOMAIN, "auth_use_credential", ("auth_use_credential",), ("builder", "auth", "use-credential"), cli_public=False),
134
+ PublicToolSpec(BUILDER_DOMAIN, "auth_whoami", ("auth_whoami",), ("builder", "auth", "whoami"), cli_public=False),
135
+ PublicToolSpec(BUILDER_DOMAIN, "auth_logout", ("auth_logout",), ("builder", "auth", "logout"), cli_public=False),
138
136
  PublicToolSpec(BUILDER_DOMAIN, "get_login_link", ("get_login_link",), mcp_public=True, cli_public=False),
139
- PublicToolSpec(BUILDER_DOMAIN, "workspace_list", ("workspace_list",), cli_public=False),
140
- PublicToolSpec(BUILDER_DOMAIN, "workspace_get", ("workspace_get",), cli_public=False),
137
+ PublicToolSpec(BUILDER_DOMAIN, "workspace_list", ("workspace_list",), ("builder", "workspace", "list"), cli_public=False),
138
+ PublicToolSpec(BUILDER_DOMAIN, "workspace_get", ("workspace_get",), ("builder", "workspace", "get"), cli_public=False),
141
139
  PublicToolSpec(BUILDER_DOMAIN, "file_upload_local", ("file_upload_local",), ("builder", "file", "upload-local"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
142
140
  PublicToolSpec(BUILDER_DOMAIN, "feedback_submit", ("feedback_submit",), ("builder", "feedback", "submit"), has_contract=True),
143
141
  PublicToolSpec(BUILDER_DOMAIN, "builder_tool_contract", ("builder_tool_contract",), ("builder", "contract"), has_contract=False),
@@ -196,8 +194,7 @@ PUBLIC_TOOL_BY_KEY: dict[str, PublicToolSpec] = {spec.trim_key: spec for spec in
196
194
  PUBLIC_TOOL_BY_CLI_ROUTE: dict[tuple[str, ...], PublicToolSpec] = {
197
195
  spec.cli_route: spec
198
196
  for spec in ALL_PUBLIC_TOOL_SPECS
199
- # 隐藏命令仍有可达 handler,闸门绑定不能把“帮助不可见”误当成“不可执行”。
200
- if spec.cli_route is not None
197
+ if spec.cli_public and spec.cli_route is not None
201
198
  }
202
199
  PUBLIC_TOOL_BY_CLI_ROUTE.update(
203
200
  {
@@ -208,9 +205,6 @@ PUBLIC_TOOL_BY_CLI_ROUTE.update(
208
205
  }
209
206
  )
210
207
 
211
- # 这些 handler 只负责版本输出或参数迁移提示,不会发起轻流业务请求。
212
- NON_TOOL_CLI_HANDLER_ROUTES = frozenset({("version",), ("record", "schema")})
213
-
214
208
 
215
209
  def public_tool_specs(domain: str | None = None) -> tuple[PublicToolSpec, ...]:
216
210
  if domain is None:
@@ -264,13 +258,11 @@ def cli_route_from_namespace(args: Namespace) -> tuple[str, ...] | None:
264
258
  record_command = getattr(args, "record_command", None)
265
259
  if record_command == "schema":
266
260
  schema_command = getattr(args, "record_schema_command", None)
267
- if isinstance(schema_command, str) and schema_command:
268
- return (command, "schema", schema_command)
269
- return (command, "schema")
261
+ return (command, "schema", schema_command) if isinstance(schema_command, str) and schema_command else None
270
262
  return (command, record_command) if isinstance(record_command, str) and record_command else None
271
263
  if command == "task":
272
264
  task_command = getattr(args, "task_command", None)
273
- return (command, task_command) if isinstance(task_command, str) and task_command else (command,)
265
+ return (command, task_command) if isinstance(task_command, str) and task_command else None
274
266
  if command != "builder":
275
267
  child_attr = f"{command}_command"
276
268
  child = getattr(args, child_attr, None)
@@ -291,54 +283,3 @@ def cli_route_from_namespace(args: Namespace) -> tuple[str, ...] | None:
291
283
  child_attr = f"builder_{builder_command.replace('-', '_')}_command"
292
284
  child = getattr(args, child_attr, None)
293
285
  return ("builder", builder_command, child) if isinstance(child, str) and child else None
294
-
295
-
296
- def discover_cli_handler_routes(
297
- parser: argparse.ArgumentParser,
298
- ) -> dict[tuple[str, ...], tuple[str, ...]]:
299
- """从 argparse 实际 handler 树枚举规范化路由及其实现,包含中间交互入口。"""
300
- discovered: dict[tuple[str, ...], set[str]] = {}
301
-
302
- def walk(current: argparse.ArgumentParser, path: tuple[str, ...], inherited: dict[str, object]) -> None:
303
- attrs = dict(inherited)
304
- for action in current._actions:
305
- if isinstance(action, argparse._SubParsersAction):
306
- continue
307
- if action.dest != argparse.SUPPRESS and action.default != argparse.SUPPRESS:
308
- attrs.setdefault(action.dest, action.default)
309
- attrs.update(current._defaults)
310
-
311
- handler = current._defaults.get("handler")
312
- if callable(handler):
313
- variants = [attrs]
314
- for action in current._actions:
315
- if isinstance(action, argparse._SubParsersAction) or action.choices is None:
316
- continue
317
- variants.extend({**attrs, action.dest: choice} for choice in action.choices)
318
- for variant in variants:
319
- route = cli_route_from_namespace(Namespace(**variant))
320
- if route is None:
321
- route = _normalize_cli_parser_path(path)
322
- if route:
323
- identity = f"{handler.__module__}:{handler.__qualname__}"
324
- discovered.setdefault(route, set()).add(identity)
325
-
326
- for action in current._actions:
327
- if not isinstance(action, argparse._SubParsersAction):
328
- continue
329
- for name, child in action.choices.items():
330
- next_attrs = dict(attrs)
331
- next_attrs[action.dest] = name
332
- walk(child, path + (name,), next_attrs)
333
-
334
- walk(parser, (), {})
335
- return {route: tuple(sorted(handlers)) for route, handlers in discovered.items()}
336
-
337
-
338
- def _normalize_cli_parser_path(path: tuple[str, ...]) -> tuple[str, ...]:
339
- normalized = list(path)
340
- if normalized and normalized[0] == "build":
341
- normalized[0] = "builder"
342
- if len(normalized) > 1 and normalized[0] == "builder" and normalized[1] == "associated-resources":
343
- normalized[1] = "associated-resource"
344
- return tuple(normalized)
@@ -51,9 +51,8 @@ COMMON_ERROR_DROP_TOP = {
51
51
 
52
52
  SUCCESS_POLICY_BY_TOOL: dict[str, TransformFn] = {}
53
53
 
54
- #: 这些校验命令的产出契约明确包含 ok 字段(供阶段 2 SKILL.md 引用),
54
+ #: 这两个校验命令的产出契约明确包含 ok 字段(供阶段 2 SKILL.md 引用),
55
55
  #: 需在 success 裁剪时保留 ok(默认 COMMON_SUCCESS_DROP_TOP 会丢弃 ok)。
56
- #: `builder:app_views_validate`(views 声明式校验)同属校验类工具,ok 同样进产出契约。
57
56
  _KEEP_OK_TOOLS = {
58
57
  "builder:app_form_validate_formula",
59
58
  "builder:app_form_validate_code_block",
@@ -2,6 +2,8 @@ from __future__ import annotations
2
2
 
3
3
  from datetime import date
4
4
 
5
+ from mcp.server.fastmcp import FastMCP
6
+
5
7
  from .backend_client import BackendClient
6
8
  from .session_store import SessionStore
7
9
  from .tools.app_tools import AppTools
@@ -24,11 +26,9 @@ from .tools.task_context_tools import TaskContextTools
24
26
  from .tools.view_tools import ViewTools
25
27
  from .tools.workflow_tools import WorkflowTools
26
28
  from .tools.workspace_tools import WorkspaceTools
27
- from .tool_catalog import stdio_server_bindings
28
- from .tool_policy import PolicyAwareFastMCP, configure_tool_policy
29
29
 
30
30
 
31
- def _register_missing_builder_tools(server: PolicyAwareFastMCP) -> None:
31
+ def _register_missing_builder_tools(server: FastMCP) -> None:
32
32
  """Add current high-level builder tools to the unified MCP without duplicate names."""
33
33
  from .server_app_builder import build_builder_server
34
34
 
@@ -48,10 +48,10 @@ def _register_missing_builder_tools(server: PolicyAwareFastMCP) -> None:
48
48
  )
49
49
 
50
50
 
51
- def build_server() -> PolicyAwareFastMCP:
51
+ def build_server() -> FastMCP:
52
52
  today = date.today()
53
53
  current_year = today.year
54
- server = PolicyAwareFastMCP(
54
+ server = FastMCP(
55
55
  "Qingflow MCP",
56
56
  instructions=f"""Use this unified server for Qingflow operational and builder workflows. Current date: `{today.isoformat()}`.
57
57
 
@@ -259,14 +259,8 @@ If the current MCP capability is unsupported, the workflow is awkward, or the us
259
259
  WorkflowTools(sessions, backend).register(server)
260
260
  ViewTools(sessions, backend).register(server)
261
261
  SolutionTools(sessions, backend).register(server)
262
- # 保存补齐 Builder 前的真实注册来源,裸名冲突只能绑定最终保留 callable 的来源域。
263
- legacy_tool_names = set(server._tool_manager._tools)
264
262
  _register_missing_builder_tools(server)
265
- return configure_tool_policy(
266
- server,
267
- stdio_server_bindings(legacy_tool_names, server._tool_manager._tools),
268
- source="environment",
269
- )
263
+ return server
270
264
 
271
265
 
272
266
  mcp = build_server()
@@ -2,6 +2,8 @@ from __future__ import annotations
2
2
 
3
3
  import json
4
4
 
5
+ from mcp.server.fastmcp import FastMCP
6
+
5
7
  from .backend_client import BackendClient
6
8
  from .config import DEFAULT_PROFILE
7
9
  from .response_trim import BUILDER_SERVER_METHOD_MAP, trim_error_response, trim_public_response, wrap_trimmed_methods
@@ -12,8 +14,6 @@ from .tools.auth_tools import AuthTools
12
14
  from .tools.feedback_tools import FeedbackTools
13
15
  from .tools.file_tools import FileTools
14
16
  from .tools.workspace_tools import WorkspaceTools
15
- from .tool_catalog import builder_server_bindings
16
- from .tool_policy import PolicyAwareFastMCP, configure_tool_policy
17
17
 
18
18
 
19
19
  def _config_failure(message: str, *, fix_hint: str) -> dict:
@@ -27,8 +27,8 @@ def _config_failure(message: str, *, fix_hint: str) -> dict:
27
27
  }
28
28
 
29
29
 
30
- def build_builder_server() -> PolicyAwareFastMCP:
31
- server = PolicyAwareFastMCP(
30
+ def build_builder_server() -> FastMCP:
31
+ server = FastMCP(
32
32
  "Qingflow App Builder MCP",
33
33
  instructions=(
34
34
  "Use this server for AI-native Qingflow builder workflows. "
@@ -689,11 +689,7 @@ def build_builder_server() -> PolicyAwareFastMCP:
689
689
  expected_package_id=expected_package_id,
690
690
  )
691
691
 
692
- return configure_tool_policy(
693
- server,
694
- builder_server_bindings(server._tool_manager._tools),
695
- source="environment",
696
- )
692
+ return server
697
693
 
698
694
 
699
695
  def main() -> None:
@@ -3,6 +3,8 @@ from __future__ import annotations
3
3
  import json
4
4
  from datetime import date
5
5
 
6
+ from mcp.server.fastmcp import FastMCP
7
+
6
8
  from .backend_client import BackendClient
7
9
  from .config import DEFAULT_PROFILE
8
10
  from .response_trim import USER_SERVER_METHOD_MAP, trim_error_response, trim_public_response, wrap_trimmed_methods
@@ -20,14 +22,12 @@ from .tools.login_link_tools import LOGIN_LINK_DESCRIPTION, LoginLinkTools
20
22
  from .tools.resource_read_tools import ResourceReadTools
21
23
  from .tools.task_context_tools import TaskContextTools
22
24
  from .tools.workspace_tools import WorkspaceTools
23
- from .tool_catalog import user_server_bindings
24
- from .tool_policy import PolicyAwareFastMCP, configure_tool_policy
25
25
 
26
26
 
27
- def build_user_server(*, stateless_http: bool = False) -> PolicyAwareFastMCP:
27
+ def build_user_server(*, stateless_http: bool = False) -> FastMCP:
28
28
  today = date.today()
29
29
  current_year = today.year
30
- server = PolicyAwareFastMCP(
30
+ server = FastMCP(
31
31
  "Qingflow App User MCP",
32
32
  stateless_http=stateless_http,
33
33
  instructions=f"""Use this server for Qingflow operational workflows. Current date: `{today.isoformat()}`.
@@ -507,11 +507,7 @@ If the current MCP capability is unsupported, the workflow is awkward, or the us
507
507
  task_context_tools.register(server)
508
508
  directory_tools.register_frontend_search(server)
509
509
 
510
- return configure_tool_policy(
511
- server,
512
- user_server_bindings(server._tool_manager._tools),
513
- source="request" if stateless_http else "environment",
514
- )
510
+ return server
515
511
 
516
512
 
517
513
  mcp = build_user_server()
@@ -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() -> PolicyAwareFastMCP:
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 configure_tool_policy(
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,