@josephyan/qingflow-app-user-mcp 0.2.0-beta.983 → 0.2.0-beta.984
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/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/session_store.py +69 -15
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.984
|
|
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.984 qingflow-app-user-mcp
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Environment:
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import os
|
|
4
5
|
from dataclasses import asdict, dataclass
|
|
5
6
|
from datetime import datetime, timezone
|
|
6
7
|
from pathlib import Path
|
|
@@ -70,6 +71,7 @@ class SessionStore:
|
|
|
70
71
|
profiles_path = get_profiles_path() if base_dir is None else Path(base_dir) / "profiles.json"
|
|
71
72
|
self._profiles_path = profiles_path
|
|
72
73
|
self._profiles_path.parent.mkdir(parents=True, exist_ok=True)
|
|
74
|
+
self._secrets_path = self._profiles_path.parent / "secrets.json"
|
|
73
75
|
self._keyring = keyring_backend if keyring_backend is not None else keyring
|
|
74
76
|
self._memory_sessions: dict[str, BackendSession] = {}
|
|
75
77
|
self._logged_out_profiles: set[str] = set()
|
|
@@ -264,26 +266,78 @@ class SessionStore:
|
|
|
264
266
|
json.dump(payload, handle, ensure_ascii=False, indent=2)
|
|
265
267
|
|
|
266
268
|
def _set_secret(self, key: str, value: str) -> bool:
|
|
267
|
-
if self._keyring is None:
|
|
268
|
-
|
|
269
|
+
if self._keyring is not None:
|
|
270
|
+
try:
|
|
271
|
+
self._keyring.set_password(KEYRING_SERVICE_NAME, key, value)
|
|
272
|
+
self._delete_file_secret(key)
|
|
273
|
+
return True
|
|
274
|
+
except Exception:
|
|
275
|
+
pass
|
|
276
|
+
return self._set_file_secret(key, value)
|
|
277
|
+
|
|
278
|
+
def _get_secret(self, key: str) -> str | None:
|
|
279
|
+
if self._keyring is not None:
|
|
280
|
+
try:
|
|
281
|
+
value = self._keyring.get_password(KEYRING_SERVICE_NAME, key)
|
|
282
|
+
except Exception:
|
|
283
|
+
value = None
|
|
284
|
+
if value:
|
|
285
|
+
return value
|
|
286
|
+
return self._get_file_secret(key)
|
|
287
|
+
|
|
288
|
+
def _delete_secret(self, key: str) -> None:
|
|
289
|
+
if self._keyring is not None:
|
|
290
|
+
try:
|
|
291
|
+
self._keyring.delete_password(KEYRING_SERVICE_NAME, key)
|
|
292
|
+
except Exception:
|
|
293
|
+
pass
|
|
294
|
+
self._delete_file_secret(key)
|
|
295
|
+
|
|
296
|
+
def _load_file_secrets(self) -> dict[str, str]:
|
|
297
|
+
if not self._secrets_path.exists():
|
|
298
|
+
return {}
|
|
299
|
+
try:
|
|
300
|
+
with self._secrets_path.open("r", encoding="utf-8") as handle:
|
|
301
|
+
payload = json.load(handle)
|
|
302
|
+
except (OSError, json.JSONDecodeError):
|
|
303
|
+
return {}
|
|
304
|
+
if not isinstance(payload, dict):
|
|
305
|
+
return {}
|
|
306
|
+
return {str(key): str(value) for key, value in payload.items() if isinstance(value, str)}
|
|
307
|
+
|
|
308
|
+
def _save_file_secrets(self, payload: dict[str, str]) -> bool:
|
|
309
|
+
self._secrets_path.parent.mkdir(parents=True, exist_ok=True)
|
|
269
310
|
try:
|
|
270
|
-
self.
|
|
311
|
+
fd = os.open(self._secrets_path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
|
|
312
|
+
with os.fdopen(fd, "w", encoding="utf-8") as handle:
|
|
313
|
+
json.dump(payload, handle, ensure_ascii=False, indent=2)
|
|
314
|
+
try:
|
|
315
|
+
os.chmod(self._secrets_path, 0o600)
|
|
316
|
+
except OSError:
|
|
317
|
+
pass
|
|
271
318
|
return True
|
|
272
|
-
except
|
|
319
|
+
except OSError:
|
|
273
320
|
return False
|
|
274
321
|
|
|
275
|
-
def
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
return self._keyring.get_password(KEYRING_SERVICE_NAME, key)
|
|
280
|
-
except Exception:
|
|
281
|
-
return None
|
|
322
|
+
def _set_file_secret(self, key: str, value: str) -> bool:
|
|
323
|
+
payload = self._load_file_secrets()
|
|
324
|
+
payload[key] = value
|
|
325
|
+
return self._save_file_secrets(payload)
|
|
282
326
|
|
|
283
|
-
def
|
|
284
|
-
|
|
327
|
+
def _get_file_secret(self, key: str) -> str | None:
|
|
328
|
+
return self._load_file_secrets().get(key)
|
|
329
|
+
|
|
330
|
+
def _delete_file_secret(self, key: str) -> None:
|
|
331
|
+
payload = self._load_file_secrets()
|
|
332
|
+
if key not in payload:
|
|
333
|
+
return
|
|
334
|
+
payload.pop(key, None)
|
|
335
|
+
if payload:
|
|
336
|
+
self._save_file_secrets(payload)
|
|
285
337
|
return
|
|
286
338
|
try:
|
|
287
|
-
self.
|
|
288
|
-
except
|
|
339
|
+
self._secrets_path.unlink()
|
|
340
|
+
except FileNotFoundError:
|
|
341
|
+
return
|
|
342
|
+
except OSError:
|
|
289
343
|
return
|