@josephyan/qingflow-app-user-mcp 0.2.0-beta.29 → 0.2.0-beta.30

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.29
6
+ npm install @josephyan/qingflow-app-user-mcp@0.2.0-beta.30
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.29 qingflow-app-user-mcp
12
+ npx -y -p @josephyan/qingflow-app-user-mcp@0.2.0-beta.30 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.29",
3
+ "version": "0.2.0-beta.30",
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.0b29"
7
+ version = "0.2.0b30"
8
8
  description = "User-authenticated MCP server for Qingflow"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: qingflow-app-user
3
- description: Route Qingflow end-user requests to the right specialized operational skill after the MCP is already connected and authenticated. Use when the task is operational but it is not yet clear whether it is record CRUD, task-center workflow work, or final analysis.
3
+ description: Route Qingflow end-user requests to the right specialized operational skill after the MCP is already connected and authenticated. Use when the task is operational but it is not yet clear whether it is record CRUD or final analysis.
4
4
  metadata:
5
5
  short-description: Router for Qingflow operational skills
6
6
  ---
@@ -19,20 +19,16 @@ Route to exactly one of these specialized paths:
19
19
  1. Record CRUD
20
20
  Switch to [$qingflow-record-crud](/Users/yanqidong/Documents/qingflow-next/.codex/skills/qingflow-record-crud/SKILL.md)
21
21
 
22
- 2. Task center / comments / workflow usage / directory
23
- Switch to [$qingflow-task-ops](/Users/yanqidong/Documents/qingflow-next/.codex/skills/qingflow-task-ops/SKILL.md)
24
-
25
- 3. Analysis
22
+ 2. Analysis
26
23
  Switch to [$qingflow-record-analysis](/Users/yanqidong/Documents/qingflow-next/.codex/skills/qingflow-record-analysis/SKILL.md)
27
24
 
28
- 4. MCP connection / auth / workspace selection
25
+ 3. MCP connection / auth / workspace selection
29
26
  Switch to [$qingflow-mcp-setup](/Users/yanqidong/.codex/skills/qingflow-mcp-setup/SKILL.md)
30
27
 
31
28
  ## Routing Rules
32
29
 
33
30
  - If the user does not know the target `app_key`, discover apps first with `app_list` or `app_search`, then route to the specialized skill
34
31
  - If the task is about browsing, reading, creating, updating, deleting, attachments, relations, subtable writes, or member/department-field candidate lookup, switch to `$qingflow-record-crud`
35
- - If the task is about inbox, todo, cc, task-center workload, comments, approval, reject, rollback, transfer, urge, or directory lookup, switch to `$qingflow-task-ops`
36
32
  - If the task is about grouped distributions, ratios, rankings, trends, insights, or any final statistical conclusion, switch to `$qingflow-record-analysis`
37
33
  - If the MCP is not connected, authenticated, or bound to the right workspace, switch to `$qingflow-mcp-setup`
38
34
 
@@ -45,5 +41,4 @@ Route to exactly one of these specialized paths:
45
41
  ## Resources
46
42
 
47
43
  - Record CRUD: [$qingflow-record-crud](/Users/yanqidong/Documents/qingflow-next/.codex/skills/qingflow-record-crud/SKILL.md)
48
- - Task center and workflow usage: [$qingflow-task-ops](/Users/yanqidong/Documents/qingflow-next/.codex/skills/qingflow-task-ops/SKILL.md)
49
44
  - Dedicated analysis workflow: [$qingflow-record-analysis](/Users/yanqidong/Documents/qingflow-next/.codex/skills/qingflow-record-analysis/SKILL.md)
@@ -2,4 +2,4 @@ from __future__ import annotations
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "0.2.0b29"
5
+ __version__ = "0.2.0b30"
@@ -11,14 +11,12 @@ from .tools.auth_tools import AuthTools
11
11
  from .tools.file_tools import FileTools
12
12
  from .tools.package_tools import PackageTools
13
13
  from .tools.navigation_tools import NavigationTools
14
- from .tools.approval_tools import ApprovalTools
15
14
  from .tools.directory_tools import DirectoryTools
16
15
  from .tools.portal_tools import PortalTools
17
16
  from .tools.qingbi_report_tools import QingbiReportTools
18
17
  from .tools.record_tools import RecordTools
19
18
  from .tools.role_tools import RoleTools
20
19
  from .tools.solution_tools import SolutionTools
21
- from .tools.task_tools import TaskTools
22
20
  from .tools.view_tools import ViewTools
23
21
  from .tools.workflow_tools import WorkflowTools
24
22
  from .tools.workspace_tools import WorkspaceTools
@@ -92,10 +90,6 @@ Analysis answers must include concrete numbers. When applicable, include percent
92
90
  - If a member or department field id is known but candidate ids are not, use `record_member_candidates` or `record_department_candidates` before `record_write`.
93
91
  - For default-all member or department fields, prefer those field candidate tools instead of starting with `directory_*`.
94
92
 
95
- ## Task Center Path
96
-
97
- `task_summary -> task_list / task_facets -> task action`
98
-
99
93
  ## Time Handling
100
94
 
101
95
  Normalize relative dates before building DSL.
@@ -123,13 +117,11 @@ Avoid builder-side app or schema changes here.""",
123
117
  QingbiReportTools(sessions, backend).register(server)
124
118
  PackageTools(sessions, backend).register(server)
125
119
  NavigationTools(sessions, backend).register(server)
126
- ApprovalTools(sessions, backend).register(server)
127
120
  PortalTools(sessions, backend).register(server)
128
121
  DirectoryTools(sessions, backend).register(server)
129
122
  WorkflowTools(sessions, backend).register(server)
130
123
  ViewTools(sessions, backend).register(server)
131
124
  SolutionTools(sessions, backend).register(server)
132
- TaskTools(sessions, backend).register(server)
133
125
  return server
134
126
 
135
127
 
@@ -7,13 +7,11 @@ from mcp.server.fastmcp import FastMCP
7
7
  from .backend_client import BackendClient
8
8
  from .config import DEFAULT_PROFILE
9
9
  from .session_store import SessionStore
10
- from .tools.approval_tools import ApprovalTools
11
10
  from .tools.app_tools import AppTools
12
11
  from .tools.auth_tools import AuthTools
13
12
  from .tools.directory_tools import DirectoryTools
14
13
  from .tools.file_tools import FileTools
15
14
  from .tools.record_tools import RecordTools
16
- from .tools.task_tools import TaskTools
17
15
  from .tools.workspace_tools import WorkspaceTools
18
16
 
19
17
 
@@ -80,10 +78,6 @@ Analysis answers must include concrete numbers. When applicable, include percent
80
78
  - If a member or department field id is known but candidate ids are not, use `record_member_candidates` or `record_department_candidates` before `record_write`.
81
79
  - For default-all member or department fields, prefer those field candidate tools instead of starting with `directory_*`.
82
80
 
83
- ## Task Center Path
84
-
85
- `task_summary -> task_list / task_facets -> task action`
86
-
87
81
  ## Time Handling
88
82
 
89
83
  Normalize relative dates before building DSL.
@@ -106,8 +100,6 @@ Avoid builder-side app or schema changes here.""",
106
100
  apps = AppTools(sessions, backend)
107
101
  workspace = WorkspaceTools(sessions, backend)
108
102
  files = FileTools(sessions, backend)
109
- approvals = ApprovalTools(sessions, backend)
110
- tasks = TaskTools(sessions, backend)
111
103
 
112
104
  @server.tool()
113
105
  def auth_login(
@@ -227,8 +219,6 @@ Avoid builder-side app or schema changes here.""",
227
219
 
228
220
  RecordTools(sessions, backend).register(server)
229
221
  DirectoryTools(sessions, backend).register(server)
230
- approvals.register(server)
231
- tasks.register(server)
232
222
 
233
223
  return server
234
224
 
@@ -178,6 +178,7 @@ class FileTools(ToolBase):
178
178
  "download_url": download_url,
179
179
  "attachment_value": {
180
180
  "value": download_url,
181
+ "otherInfo": file_name,
181
182
  "name": file_name,
182
183
  },
183
184
  "comment_file_info": {
@@ -4971,6 +4971,12 @@ def _extract_field_value(answer_list: list[JSONValue], field: FormField | None)
4971
4971
  values = answer.get("values")
4972
4972
  if not isinstance(values, list) or not values:
4973
4973
  return None
4974
+ if field.que_type in ATTACHMENT_QUE_TYPES:
4975
+ extracted_attachments = [_extract_attachment_item(item) for item in values]
4976
+ extracted_attachments = [item for item in extracted_attachments if item is not None]
4977
+ if not extracted_attachments:
4978
+ return None
4979
+ return extracted_attachments[0] if len(extracted_attachments) == 1 else extracted_attachments
4974
4980
  extracted = [_extract_value_item(item) for item in values]
4975
4981
  return extracted[0] if len(extracted) == 1 else extracted
4976
4982
  return None
@@ -5997,12 +6003,29 @@ def _attachment_value(value: JSONValue) -> JSONObject:
5997
6003
  details={"received_value": value},
5998
6004
  )
5999
6005
  payload: JSONObject = {"value": value.get("value", value.get("url"))}
6000
- if value.get("name") is not None:
6001
- payload["name"] = value["name"]
6006
+ file_name = value.get("otherInfo", value.get("name", value.get("fileName")))
6007
+ if file_name is not None:
6008
+ payload["otherInfo"] = file_name
6002
6009
  return payload
6003
6010
  return {"value": _stringify_json(value)}
6004
6011
 
6005
6012
 
6013
+ def _extract_attachment_item(value: JSONValue) -> JSONObject | None:
6014
+ if not isinstance(value, dict):
6015
+ text = _normalize_optional_text(value)
6016
+ return {"value": text, "name": None} if text else None
6017
+ file_url = value.get("value", value.get("url"))
6018
+ normalized_url = _normalize_optional_text(file_url) or (_stringify_json(file_url) if file_url is not None else None)
6019
+ if not normalized_url:
6020
+ return None
6021
+ file_name = value.get("otherInfo", value.get("name", value.get("fileName")))
6022
+ normalized_name = _normalize_optional_text(file_name) or (_stringify_json(file_name) if file_name is not None else None)
6023
+ payload: JSONObject = {"value": normalized_url}
6024
+ if normalized_name:
6025
+ payload["name"] = normalized_name
6026
+ return payload
6027
+
6028
+
6006
6029
  def _relation_value(value: JSONValue) -> JSONObject:
6007
6030
  return _relation_value_payload(None, value)[0]
6008
6031