@qingflow-tech/qingflow-app-user-mcp 1.0.1 → 1.0.2
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 @qingflow-tech/qingflow-app-user-mcp@1.0.
|
|
6
|
+
npm install @qingflow-tech/qingflow-app-user-mcp@1.0.2
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
Run:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npx -y -p @qingflow-tech/qingflow-app-user-mcp@1.0.
|
|
12
|
+
npx -y -p @qingflow-tech/qingflow-app-user-mcp@1.0.2 qingflow-app-user-mcp
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Environment:
|
package/package.json
CHANGED
|
@@ -25,8 +25,13 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
25
25
|
logout.add_argument("--forget-persisted", action="store_true")
|
|
26
26
|
logout.set_defaults(handler=_handle_logout, format_hint="")
|
|
27
27
|
|
|
28
|
+
|
|
28
29
|
def _handle_use_credential(args: argparse.Namespace, context: CliContext) -> dict:
|
|
29
|
-
credential =
|
|
30
|
+
credential = (
|
|
31
|
+
read_secret_arg(args.credential, stdin_enabled=bool(args.credential_stdin), label="credential")
|
|
32
|
+
if args.credential or bool(args.credential_stdin)
|
|
33
|
+
else ""
|
|
34
|
+
)
|
|
30
35
|
return context.auth.auth_use_credential(
|
|
31
36
|
profile=args.profile,
|
|
32
37
|
base_url=args.base_url,
|
|
@@ -22,6 +22,7 @@ DEFAULT_REPOSITORY_AUTHOR_NAME = "qingflow-mcp"
|
|
|
22
22
|
DEFAULT_REPOSITORY_AUTHOR_EMAIL = "qingflow-mcp@local.invalid"
|
|
23
23
|
DEFAULT_REPOSITORY_INTERNAL_SHARE_TOKEN_KEY = "tokenKey"
|
|
24
24
|
DEFAULT_CREDIT_USAGE_RECORD_PATH = "/user/credit/usage"
|
|
25
|
+
DEFAULT_MCPORTER_CONFIG_PATH = "/root/.openclaw/workspace/config/mcporter.json"
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
def get_mcp_home() -> Path:
|
|
@@ -33,6 +34,13 @@ def get_profiles_path() -> Path:
|
|
|
33
34
|
return get_mcp_home() / "profiles.json"
|
|
34
35
|
|
|
35
36
|
|
|
37
|
+
def get_mcporter_config_path() -> Path:
|
|
38
|
+
custom_path = os.getenv("QINGFLOW_MCP_MCPORTER_CONFIG_PATH") or os.getenv(
|
|
39
|
+
"QINGFLOW_MCP_AUTH_CONFIG_PATH"
|
|
40
|
+
)
|
|
41
|
+
return Path(custom_path).expanduser() if custom_path else Path(DEFAULT_MCPORTER_CONFIG_PATH)
|
|
42
|
+
|
|
43
|
+
|
|
36
44
|
def get_repository_metadata_dir() -> Path:
|
|
37
45
|
return get_mcp_home() / "repository-metadata"
|
|
38
46
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
from typing import Any
|
|
4
5
|
|
|
5
6
|
from mcp.server.fastmcp import FastMCP
|
|
@@ -9,6 +10,7 @@ from ..config import (
|
|
|
9
10
|
DEFAULT_PROFILE,
|
|
10
11
|
get_default_base_url,
|
|
11
12
|
get_default_qf_version,
|
|
13
|
+
get_mcporter_config_path,
|
|
12
14
|
normalize_base_url,
|
|
13
15
|
)
|
|
14
16
|
from ..errors import QingflowApiError, raise_tool_error
|
|
@@ -77,15 +79,24 @@ class AuthTools(ToolBase):
|
|
|
77
79
|
profile: str = DEFAULT_PROFILE,
|
|
78
80
|
base_url: str | None = None,
|
|
79
81
|
qf_version: str | None = None,
|
|
80
|
-
credential: str,
|
|
82
|
+
credential: str | None = None,
|
|
81
83
|
persist: bool = False,
|
|
82
84
|
) -> dict[str, Any]:
|
|
83
85
|
"""执行认证与会话相关逻辑。"""
|
|
84
|
-
|
|
86
|
+
resolved_base_url, resolved_credential = self._resolve_mcporter_auth_inputs(
|
|
87
|
+
base_url=base_url,
|
|
88
|
+
credential=credential,
|
|
89
|
+
)
|
|
90
|
+
normalized_base_url = self._normalize_base_url(resolved_base_url)
|
|
85
91
|
normalized_qf_version, qf_version_source = self._resolve_qf_version_input(qf_version)
|
|
86
|
-
normalized_credential = str(
|
|
92
|
+
normalized_credential = str(resolved_credential).strip()
|
|
87
93
|
if not normalized_credential:
|
|
88
|
-
raise_tool_error(
|
|
94
|
+
raise_tool_error(
|
|
95
|
+
QingflowApiError.config_error(
|
|
96
|
+
"credential is required or configure /root/.openclaw/workspace/config/mcporter.json "
|
|
97
|
+
"with mcpServers.qingflow.headers.x-qingflow-client-id"
|
|
98
|
+
)
|
|
99
|
+
)
|
|
89
100
|
|
|
90
101
|
context_payload, detected_qf_version = self._fetch_auth_context(
|
|
91
102
|
normalized_base_url,
|
|
@@ -153,6 +164,46 @@ class AuthTools(ToolBase):
|
|
|
153
164
|
),
|
|
154
165
|
}
|
|
155
166
|
|
|
167
|
+
def _resolve_mcporter_auth_inputs(self, *, base_url: str | None, credential: str | None) -> tuple[str | None, str]:
|
|
168
|
+
"""从参数或 mcporter 配置解析登录所需 base_url 与 credential。"""
|
|
169
|
+
normalized_base_url = self._normalize_text(base_url)
|
|
170
|
+
normalized_credential = self._normalize_text(credential)
|
|
171
|
+
if normalized_base_url and normalized_credential:
|
|
172
|
+
return normalized_base_url, normalized_credential
|
|
173
|
+
|
|
174
|
+
mcporter_context = self._read_mcporter_qingflow_context()
|
|
175
|
+
if not normalized_base_url:
|
|
176
|
+
normalized_base_url = self._normalize_text(mcporter_context.get("base_url"))
|
|
177
|
+
if not normalized_credential:
|
|
178
|
+
normalized_credential = self._normalize_text(mcporter_context.get("credential"))
|
|
179
|
+
return normalized_base_url, normalized_credential or ""
|
|
180
|
+
|
|
181
|
+
def _read_mcporter_qingflow_context(self) -> dict[str, str]:
|
|
182
|
+
"""读取 OpenClaw mcporter 中的 Qingflow MCP 上下文。"""
|
|
183
|
+
path = get_mcporter_config_path()
|
|
184
|
+
if not path.exists():
|
|
185
|
+
return {}
|
|
186
|
+
try:
|
|
187
|
+
with path.open("r", encoding="utf-8") as handle:
|
|
188
|
+
payload = json.load(handle)
|
|
189
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
190
|
+
raise_tool_error(QingflowApiError.config_error(f"failed to read mcporter config '{path}': {exc}"))
|
|
191
|
+
|
|
192
|
+
if not isinstance(payload, dict):
|
|
193
|
+
raise_tool_error(QingflowApiError.config_error(f"mcporter config '{path}' must be a JSON object"))
|
|
194
|
+
mcp_servers = payload.get("mcpServers")
|
|
195
|
+
qingflow = mcp_servers.get("qingflow") if isinstance(mcp_servers, dict) else None
|
|
196
|
+
if not isinstance(qingflow, dict):
|
|
197
|
+
return {}
|
|
198
|
+
headers = qingflow.get("headers")
|
|
199
|
+
credential = None
|
|
200
|
+
if isinstance(headers, dict):
|
|
201
|
+
credential = headers.get("x-qingflow-client-id")
|
|
202
|
+
return {
|
|
203
|
+
"base_url": str(qingflow.get("url") or "").strip(),
|
|
204
|
+
"credential": str(credential or "").strip(),
|
|
205
|
+
}
|
|
206
|
+
|
|
156
207
|
@tool_cn_name("我的身份")
|
|
157
208
|
def auth_whoami(self, *, profile: str = DEFAULT_PROFILE) -> dict[str, Any]:
|
|
158
209
|
"""执行认证与会话相关逻辑。"""
|