@josephyan/qingflow-app-user-mcp 0.2.0-beta.992 → 0.2.0-beta.994

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 CHANGED
@@ -3,13 +3,13 @@
3
3
  Install:
4
4
 
5
5
  ```bash
6
- npm install @josephyan/qingflow-app-user-mcp@0.2.0-beta.992
6
+ npm install @josephyan/qingflow-app-user-mcp@0.2.0-beta.994
7
7
  ```
8
8
 
9
9
  Run:
10
10
 
11
11
  ```bash
12
- npx -y -p @josephyan/qingflow-app-user-mcp@0.2.0-beta.992 qingflow-app-user-mcp
12
+ npx -y -p @josephyan/qingflow-app-user-mcp@0.2.0-beta.994 qingflow-app-user-mcp
13
13
  ```
14
14
 
15
15
  Environment:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@josephyan/qingflow-app-user-mcp",
3
- "version": "0.2.0-beta.992",
3
+ "version": "0.2.0-beta.994",
4
4
  "description": "Operational end-user MCP for Qingflow records, tasks, comments, and directory workflows.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "qingflow-mcp"
7
- version = "0.2.0b992"
7
+ version = "0.2.0b994"
8
8
  description = "User-authenticated MCP server for Qingflow"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -5,7 +5,7 @@ from pathlib import Path
5
5
 
6
6
  __all__ = ["__version__"]
7
7
 
8
- _FALLBACK_VERSION = "0.2.0b992"
8
+ _FALLBACK_VERSION = "0.2.0b994"
9
9
 
10
10
 
11
11
  def _resolve_local_pyproject_version() -> str | None:
@@ -173,20 +173,26 @@ def _format_record_list(result: dict[str, Any]) -> str:
173
173
  def _format_task_list(result: dict[str, Any]) -> str:
174
174
  data = result.get("data") if isinstance(result.get("data"), dict) else {}
175
175
  items = data.get("items") if isinstance(data.get("items"), list) else []
176
- rows = []
176
+ lines = ["Tasks"]
177
177
  for item in items:
178
178
  if not isinstance(item, dict):
179
179
  continue
180
- rows.append(
181
- [
182
- str(item.get("app_key") or ""),
183
- str(item.get("record_id") or ""),
184
- str(item.get("workflow_node_id") or ""),
185
- str(item.get("workflow_node_name") or ""),
186
- ]
180
+ lines.append(
181
+ "- "
182
+ + " / ".join(
183
+ [
184
+ str(item.get("app_key") or "-"),
185
+ str(item.get("record_id") or "-"),
186
+ str(item.get("workflow_node_id") or "-"),
187
+ str(item.get("workflow_node_name") or "-"),
188
+ ]
189
+ )
187
190
  )
188
- output = _render_titled_table("Tasks", ["app_key", "record_id", "node_id", "node_name"], rows)
189
- lines = output.rstrip("\n").split("\n")
191
+ summary_fields = item.get("summary_fields") if isinstance(item.get("summary_fields"), list) else []
192
+ for summary in summary_fields:
193
+ if not isinstance(summary, dict):
194
+ continue
195
+ lines.append(f" {summary.get('title') or '-'}: {summary.get('answer') or '-'}")
190
196
  _append_warnings(lines, result.get("warnings"))
191
197
  return "\n".join(lines) + "\n"
192
198
 
@@ -269,6 +269,15 @@ class AuthTools(ToolBase):
269
269
  )
270
270
  if selected_ws_id is not None:
271
271
  session_profile = self.sessions.select_workspace(profile, ws_id=selected_ws_id, ws_name=selected_ws_name)
272
+ backend_session = self.sessions.get_backend_session(profile)
273
+ permission_level = (
274
+ self._workspace_permission_level(
275
+ session_profile=session_profile,
276
+ backend_session=backend_session,
277
+ )
278
+ if backend_session is not None
279
+ else None
280
+ )
272
281
 
273
282
  return {
274
283
  "profile": session_profile.profile,
@@ -282,6 +291,7 @@ class AuthTools(ToolBase):
282
291
  "selected_ws_name": session_profile.selected_ws_name,
283
292
  "suggested_ws_id": session_profile.selected_ws_id,
284
293
  "suggested_ws_name": session_profile.selected_ws_name,
294
+ "permission_level": permission_level,
285
295
  "persisted": session_profile.persisted,
286
296
  "request_route": self._request_route_payload(
287
297
  BackendRequestContext(
@@ -781,6 +791,13 @@ class AuthTools(ToolBase):
781
791
  if ws_id is None:
782
792
  return default_payload, []
783
793
 
794
+ permission_level = self._workspace_permission_level(
795
+ session_profile=session_profile,
796
+ backend_session=backend_session,
797
+ )
798
+ payload = dict(default_payload)
799
+ payload["permission_level"] = permission_level
800
+
784
801
  context = BackendRequestContext(
785
802
  base_url=backend_session.base_url,
786
803
  token=backend_session.token,
@@ -788,12 +805,6 @@ class AuthTools(ToolBase):
788
805
  qf_version=backend_session.qf_version,
789
806
  qf_version_source=backend_session.qf_version_source,
790
807
  )
791
- permission_level = self._resolve_permission_level(
792
- self._workspace_auth(context, ws_id=ws_id)
793
- )
794
- payload = dict(default_payload)
795
- payload["permission_level"] = permission_level
796
-
797
808
  member = self._lookup_current_member(
798
809
  context=context,
799
810
  uid=session_profile.uid,
@@ -815,6 +826,25 @@ class AuthTools(ToolBase):
815
826
  payload["roles"] = self._compact_roles(member)
816
827
  return payload, []
817
828
 
829
+ def _workspace_permission_level(
830
+ self,
831
+ *,
832
+ session_profile, # type: ignore[no-untyped-def]
833
+ backend_session, # type: ignore[no-untyped-def]
834
+ ) -> str | None:
835
+ """Resolve the selected workspace permission label without requiring member lookup."""
836
+ ws_id = session_profile.selected_ws_id
837
+ if ws_id is None:
838
+ return None
839
+ context = BackendRequestContext(
840
+ base_url=backend_session.base_url,
841
+ token=backend_session.token,
842
+ ws_id=ws_id,
843
+ qf_version=backend_session.qf_version,
844
+ qf_version_source=backend_session.qf_version_source,
845
+ )
846
+ return self._resolve_permission_level(self._workspace_auth(context, ws_id=ws_id))
847
+
818
848
  def _workspace_auth(self, context: BackendRequestContext, *, ws_id: int) -> int | None:
819
849
  """执行内部辅助逻辑。"""
820
850
  workspace = self._fetch_workspace_auth_from_detail(context, ws_id=ws_id)
@@ -185,11 +185,7 @@ class TaskContextTools(ToolBase):
185
185
  )
186
186
  task_page = raw.get("page", {})
187
187
  warnings: list[dict[str, Any]] = []
188
- items = [
189
- self._normalize_task_item(item, task_box=task_box, flow_status=flow_status)
190
- for item in _task_page_items(task_page)
191
- if isinstance(item, dict)
192
- ]
188
+ items = [self._normalize_task_item(item) for item in _task_page_items(task_page) if isinstance(item, dict)]
193
189
  returned_items = len(items)
194
190
  page_amount = _task_page_amount(task_page)
195
191
  reported_total = _task_page_total(task_page)
@@ -235,8 +231,6 @@ class TaskContextTools(ToolBase):
235
231
  "reported_total": reported_total,
236
232
  },
237
233
  "selection": {
238
- "task_box": task_box,
239
- "flow_status": flow_status,
240
234
  "app_key": app_key,
241
235
  "workflow_node_id": workflow_node_id,
242
236
  "query": query,
@@ -1050,11 +1044,7 @@ class TaskContextTools(ToolBase):
1050
1044
  )
1051
1045
  task_page = raw.get("page", {})
1052
1046
  raw_items = _task_page_items(task_page)
1053
- normalized_items = [
1054
- self._normalize_task_item(item, task_box=task_box, flow_status=flow_status)
1055
- for item in raw_items
1056
- if isinstance(item, dict)
1057
- ]
1047
+ normalized_items = [self._normalize_task_item(item) for item in raw_items if isinstance(item, dict)]
1058
1048
  matched_items.extend(item for item in normalized_items if self._task_item_matches_query(item, query))
1059
1049
  if page_amount is None:
1060
1050
  coerced_page_amount = _coerce_count(_task_page_amount(task_page))
@@ -1726,7 +1716,7 @@ class TaskContextTools(ToolBase):
1726
1716
  if value not in (None, "", [])
1727
1717
  }
1728
1718
 
1729
- def _normalize_task_item(self, raw: dict[str, Any], *, task_box: str, flow_status: str) -> dict[str, Any]:
1719
+ def _normalize_task_item(self, raw: dict[str, Any]) -> dict[str, Any]:
1730
1720
  """执行内部辅助逻辑。"""
1731
1721
  app_key = raw.get("appKey") or raw.get("app_key")
1732
1722
  record_id = raw.get("rowRecordId") or raw.get("recordId") or raw.get("applyId")
@@ -1739,11 +1729,30 @@ class TaskContextTools(ToolBase):
1739
1729
  "workflow_node_id": workflow_node_id,
1740
1730
  "workflow_node_name": raw.get("nodeName") or raw.get("auditNodeName"),
1741
1731
  "apply_time": raw.get("applyTime") or raw.get("receiveTime"),
1742
- "task_box": task_box,
1743
- "flow_status": flow_status,
1744
- "actionable": task_box == "todo" and bool(record_id) and bool(workflow_node_id),
1732
+ "summary_fields": self._normalize_task_summary_fields(raw.get("dataSnapshot")),
1745
1733
  }
1746
1734
 
1735
+ def _normalize_task_summary_fields(self, raw: Any) -> list[dict[str, Any]]:
1736
+ """执行内部辅助逻辑。"""
1737
+ if not isinstance(raw, list):
1738
+ return []
1739
+ summary_fields: list[dict[str, Any]] = []
1740
+ for item in raw:
1741
+ if not isinstance(item, dict):
1742
+ continue
1743
+ summary_field: dict[str, Any] = {
1744
+ "field_id": item.get("fieldId"),
1745
+ "title": item.get("fieldTitle"),
1746
+ "type": item.get("fieldType"),
1747
+ "answer": item.get("fieldAnswer"),
1748
+ "desensitized": self._coerce_bool(item.get("beingDesensitized")),
1749
+ }
1750
+ associated_field_type = item.get("associatedQueType")
1751
+ if associated_field_type is not None:
1752
+ summary_field["associated_field_type"] = associated_field_type
1753
+ summary_fields.append(summary_field)
1754
+ return summary_fields
1755
+
1747
1756
  def _select_task_node(self, infos: Any, workflow_node_id: int, *, app_key: str, record_id: int) -> dict[str, Any]:
1748
1757
  """执行内部辅助逻辑。"""
1749
1758
  if not isinstance(infos, list) or not infos: