@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 +2 -2
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/skills/qingflow-app-user/SKILL.md +3 -8
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/server.py +0 -8
- package/src/qingflow_mcp/server_app_user.py +0 -10
- package/src/qingflow_mcp/tools/file_tools.py +1 -0
- package/src/qingflow_mcp/tools/record_tools.py +25 -2
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.
|
|
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.
|
|
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
package/pyproject.toml
CHANGED
|
@@ -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
|
|
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.
|
|
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
|
-
|
|
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)
|
|
@@ -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
|
|
|
@@ -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
|
-
|
|
6001
|
-
|
|
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
|
|