@qingflow-tech/qingflow-cli 1.1.41 → 1.1.43
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 +3 -3
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_TASK_CONTEXT_WORKFLOW.md +1 -1
- package/skills/qingflow-cli/reference/task/QINGFLOW_CLI_TASK_CONTEXT_WORKFLOW.md +1 -1
- package/src/qingflow_mcp/auth_provider.py +522 -0
- package/src/qingflow_mcp/cli/commands/auth.py +125 -23
- package/src/qingflow_mcp/cli/context.py +13 -2
- package/src/qingflow_mcp/cli/main.py +9 -1
- package/src/qingflow_mcp/cli/oauth_callback.py +140 -0
- package/src/qingflow_mcp/cli/oauth_client.py +276 -0
- package/src/qingflow_mcp/cloud_auth.py +31 -0
- package/src/qingflow_mcp/cloud_context.py +5 -0
- package/src/qingflow_mcp/cloud_oauth_proxy.py +454 -0
- package/src/qingflow_mcp/cloud_redis.py +25 -0
- package/src/qingflow_mcp/config.py +56 -0
- package/src/qingflow_mcp/oauth_profile_store.py +264 -0
- package/src/qingflow_mcp/oauth_token_store.py +210 -0
- package/src/qingflow_mcp/server_app_user.py +2 -1
- package/src/qingflow_mcp/server_cloud.py +265 -22
- package/src/qingflow_mcp/tools/auth_tools.py +163 -1
- package/src/qingflow_mcp/tools/base.py +43 -3
- package/src/qingflow_mcp/tools/task_context_tools.py +6 -0
|
@@ -5,7 +5,8 @@ from contextvars import ContextVar
|
|
|
5
5
|
from typing import Any, Callable, TypeVar
|
|
6
6
|
|
|
7
7
|
from ..backend_client import BackendRequestContext, BackendClient
|
|
8
|
-
from ..
|
|
8
|
+
from ..auth_provider import CliSessionFacade
|
|
9
|
+
from ..cloud_context import current_call_platform, current_cloud_context
|
|
9
10
|
from ..config import (
|
|
10
11
|
get_credit_meter_enabled,
|
|
11
12
|
get_credit_usage_base_url,
|
|
@@ -21,7 +22,7 @@ _RUN_DEPTH: ContextVar[int] = ContextVar("qingflow_mcp_tool_run_depth", default=
|
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
class ToolBase:
|
|
24
|
-
def __init__(self, sessions: SessionStore, backend: BackendClient) -> None:
|
|
25
|
+
def __init__(self, sessions: SessionStore | CliSessionFacade, backend: BackendClient) -> None:
|
|
25
26
|
self.sessions = sessions
|
|
26
27
|
self.backend = backend
|
|
27
28
|
|
|
@@ -30,7 +31,8 @@ class ToolBase:
|
|
|
30
31
|
if cloud_context is not None:
|
|
31
32
|
return cloud_context.as_tool_context(profile)
|
|
32
33
|
|
|
33
|
-
self.
|
|
34
|
+
if not self._uses_oauth(profile):
|
|
35
|
+
self._ensure_environment_credential_matches(profile)
|
|
34
36
|
session_profile = self.sessions.get_profile(profile)
|
|
35
37
|
if session_profile is None:
|
|
36
38
|
raise QingflowApiError.auth_required(profile)
|
|
@@ -83,8 +85,18 @@ class ToolBase:
|
|
|
83
85
|
return func(refreshed_profile, refreshed_context)
|
|
84
86
|
except QingflowApiError as refreshed_error:
|
|
85
87
|
self._handle_error(profile, refreshed_error)
|
|
88
|
+
if error.looks_like_invalid_token() and self._refresh_oauth_session(profile):
|
|
89
|
+
try:
|
|
90
|
+
refreshed_profile, _, refreshed_context = self._require_context(
|
|
91
|
+
profile,
|
|
92
|
+
require_workspace=require_workspace,
|
|
93
|
+
)
|
|
94
|
+
return func(refreshed_profile, refreshed_context)
|
|
95
|
+
except QingflowApiError as refreshed_error:
|
|
96
|
+
self._handle_error(profile, refreshed_error)
|
|
86
97
|
if (
|
|
87
98
|
error.looks_like_invalid_token()
|
|
99
|
+
and not self._uses_oauth(profile)
|
|
88
100
|
and session_profile is not None
|
|
89
101
|
and backend_session is not None
|
|
90
102
|
and self._refresh_session_from_credential(
|
|
@@ -121,6 +133,19 @@ class ToolBase:
|
|
|
121
133
|
http_status=error.http_status,
|
|
122
134
|
)
|
|
123
135
|
raise_tool_error(refreshed_error)
|
|
136
|
+
if self._uses_oauth(profile):
|
|
137
|
+
oauth_error = QingflowApiError(
|
|
138
|
+
category="auth",
|
|
139
|
+
message=(
|
|
140
|
+
f"OAuth session for profile '{profile}' has expired and could not be refreshed. "
|
|
141
|
+
"Run auth login --method oauth again."
|
|
142
|
+
),
|
|
143
|
+
backend_code=error.backend_code,
|
|
144
|
+
request_id=error.request_id,
|
|
145
|
+
http_status=error.http_status,
|
|
146
|
+
details={"reason": "oauth_relogin_required"},
|
|
147
|
+
)
|
|
148
|
+
raise_tool_error(oauth_error)
|
|
124
149
|
self.sessions.invalidate(profile)
|
|
125
150
|
error = QingflowApiError(
|
|
126
151
|
category="auth",
|
|
@@ -142,6 +167,20 @@ class ToolBase:
|
|
|
142
167
|
current_cloud_context.set(refreshed_context)
|
|
143
168
|
return True
|
|
144
169
|
|
|
170
|
+
def _refresh_oauth_session(self, profile: str) -> bool:
|
|
171
|
+
if not self._uses_oauth(profile):
|
|
172
|
+
return False
|
|
173
|
+
assert isinstance(self.sessions, CliSessionFacade)
|
|
174
|
+
try:
|
|
175
|
+
return self.sessions.refresh_oauth_session(profile, force=True)
|
|
176
|
+
except QingflowApiError as error:
|
|
177
|
+
if (error.details or {}).get("reason") == "oauth_relogin_required":
|
|
178
|
+
raise
|
|
179
|
+
return False
|
|
180
|
+
|
|
181
|
+
def _uses_oauth(self, profile: str) -> bool:
|
|
182
|
+
return isinstance(self.sessions, CliSessionFacade) and self.sessions.uses_oauth(profile)
|
|
183
|
+
|
|
145
184
|
def _refresh_session_from_credential(
|
|
146
185
|
self,
|
|
147
186
|
profile: str,
|
|
@@ -323,6 +362,7 @@ class ToolBase:
|
|
|
323
362
|
"scene": "MCP",
|
|
324
363
|
"aiBiz": "MCP",
|
|
325
364
|
"extraInfo": tool_name,
|
|
365
|
+
"callPlatform": current_call_platform.get(),
|
|
326
366
|
},
|
|
327
367
|
)
|
|
328
368
|
|
|
@@ -535,6 +535,10 @@ class TaskContextTools(ToolBase):
|
|
|
535
535
|
task_context=task_context,
|
|
536
536
|
fields=field_updates,
|
|
537
537
|
)
|
|
538
|
+
elif normalized_action == "transfer" and body.get("answers") is None:
|
|
539
|
+
record = task_context.get("record") if isinstance(task_context.get("record"), dict) else {}
|
|
540
|
+
current_answers = record.get("answers") if isinstance(record.get("answers"), list) else []
|
|
541
|
+
prepared_fields = {"merged_answers": current_answers}
|
|
538
542
|
before_apply_status = ((task_context.get("record") or {}).get("apply_status"))
|
|
539
543
|
runtime_baseline = None
|
|
540
544
|
if normalized_action != "save_only":
|
|
@@ -704,6 +708,8 @@ class TaskContextTools(ToolBase):
|
|
|
704
708
|
action_payload["auditFeedback"] = audit_feedback
|
|
705
709
|
if merged_answers is not None:
|
|
706
710
|
action_payload["answers"] = merged_answers
|
|
711
|
+
elif payload.get("answers") is not None:
|
|
712
|
+
action_payload["answers"] = payload["answers"]
|
|
707
713
|
return self._approval_tools.record_transfer(
|
|
708
714
|
profile=profile,
|
|
709
715
|
app_key=app_key,
|