@qingflow-tech/qingflow-app-builder-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-builder-mcp@1.0.1
6
+ npm install @qingflow-tech/qingflow-app-builder-mcp@1.0.2
7
7
  ```
8
8
 
9
9
  Run:
10
10
 
11
11
  ```bash
12
- npx -y -p @qingflow-tech/qingflow-app-builder-mcp@1.0.1 qingflow-app-builder-mcp
12
+ npx -y -p @qingflow-tech/qingflow-app-builder-mcp@1.0.2 qingflow-app-builder-mcp
13
13
  ```
14
14
 
15
15
  Environment:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qingflow-tech/qingflow-app-builder-mcp",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Builder MCP for Qingflow app/package/system design and staged solution workflows.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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 = read_secret_arg(args.credential, stdin_enabled=bool(args.credential_stdin), label="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
- normalized_base_url = self._normalize_base_url(base_url)
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(credential).strip()
92
+ normalized_credential = str(resolved_credential).strip()
87
93
  if not normalized_credential:
88
- raise_tool_error(QingflowApiError.config_error("credential is required"))
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
  """执行认证与会话相关逻辑。"""