embed-client-dev 4.0.78__py3-none-any.whl

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.
Files changed (48) hide show
  1. embed_client/__init__.py +36 -0
  2. embed_client/__main__.py +15 -0
  3. embed_client/_version.py +3 -0
  4. embed_client/adapter_config_factory.py +213 -0
  5. embed_client/adapter_config_normalizer.py +135 -0
  6. embed_client/adapter_generator_bridge.py +166 -0
  7. embed_client/auth.py +189 -0
  8. embed_client/cli.py +483 -0
  9. embed_client/cli_core.py +427 -0
  10. embed_client/client_factory.py +515 -0
  11. embed_client/client_factory_examples.py +375 -0
  12. embed_client/client_factory_helpers.py +59 -0
  13. embed_client/config.py +408 -0
  14. embed_client/config_defaults.py +77 -0
  15. embed_client/config_examples.py +228 -0
  16. embed_client/config_generator.py +652 -0
  17. embed_client/config_generator_cli.py +151 -0
  18. embed_client/config_presets.py +245 -0
  19. embed_client/config_validator.py +246 -0
  20. embed_client/config_validator_cli.py +141 -0
  21. embed_client/constants.py +52 -0
  22. embed_client/demo_cli.py +78 -0
  23. embed_client/demo_security_cli.py +206 -0
  24. embed_client/embed_file_output_validate.py +348 -0
  25. embed_client/embed_file_status_store.py +80 -0
  26. embed_client/embedding_client.py +1929 -0
  27. embed_client/embedding_client_helpers.py +105 -0
  28. embed_client/example_async_usage.py +357 -0
  29. embed_client/example_async_usage_demo.py +282 -0
  30. embed_client/example_async_usage_ru.py +340 -0
  31. embed_client/example_async_usage_ru_demo.py +313 -0
  32. embed_client/exceptions.py +122 -0
  33. embed_client/identifier_types.py +56 -0
  34. embed_client/json_embed_file_preflight.py +128 -0
  35. embed_client/local_json_embed_file.py +73 -0
  36. embed_client/md_embed_file_prep.py +197 -0
  37. embed_client/response_normalizer.py +160 -0
  38. embed_client/response_parsers.py +219 -0
  39. embed_client/security_cli.py +200 -0
  40. embed_client/security_cli_core.py +227 -0
  41. embed_client/ssl_examples.py +278 -0
  42. embed_client/ssl_manager.py +141 -0
  43. embed_client_dev-4.0.78.dist-info/METADATA +155 -0
  44. embed_client_dev-4.0.78.dist-info/RECORD +48 -0
  45. embed_client_dev-4.0.78.dist-info/WHEEL +5 -0
  46. embed_client_dev-4.0.78.dist-info/entry_points.txt +4 -0
  47. embed_client_dev-4.0.78.dist-info/licenses/LICENSE +21 -0
  48. embed_client_dev-4.0.78.dist-info/top_level.txt +1 -0
@@ -0,0 +1,36 @@
1
+ """
2
+ embed-client: client for the Embedding Service API with authentication,
3
+ SSL/TLS, and mTLS configuration.
4
+
5
+ Canonical runtime client is :class:`EmbeddingClient` (direct JSON-RPC via
6
+ ``mcp_proxy_adapter.client.jsonrpc_client.JsonRpcClient``).
7
+
8
+ Author: Vasiliy Zdanovskiy
9
+ email: vasilyvz@gmail.com
10
+ """
11
+
12
+ from embed_client._version import __version__
13
+
14
+ from embed_client.auth import ClientAuthManager
15
+ from embed_client.config import ClientConfig
16
+ from embed_client.config_generator import ClientConfigGenerator
17
+ from embed_client.config_validator import ConfigValidator
18
+ from embed_client.embedding_client import EmbeddingClient
19
+ from embed_client.embedding_client_helpers import (
20
+ list_commands_via_client,
21
+ wait_for_job_poll,
22
+ )
23
+ from embed_client.ssl_manager import ClientSSLManager
24
+ from embed_client.client_factory import ClientFactory
25
+
26
+ __all__ = [
27
+ "EmbeddingClient",
28
+ "wait_for_job_poll",
29
+ "list_commands_via_client",
30
+ "ClientConfig",
31
+ "ClientAuthManager",
32
+ "ClientSSLManager",
33
+ "ClientFactory",
34
+ "ClientConfigGenerator",
35
+ "ConfigValidator",
36
+ ]
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Main entry point for embed-client CLI.
4
+
5
+ Author: Vasiliy Zdanovskiy
6
+ email: vasilyvz@gmail.com
7
+ """
8
+
9
+ import asyncio
10
+ import sys
11
+
12
+ from embed_client.cli import main
13
+
14
+ if __name__ == "__main__":
15
+ sys.exit(asyncio.run(main()))
@@ -0,0 +1,3 @@
1
+ """Authoritative release version for the bundled server and client."""
2
+
3
+ __version__ = "4.0.78"
@@ -0,0 +1,213 @@
1
+ """
2
+ Adapter Configuration Factory
3
+
4
+ This module provides translation from ClientConfig to mcp_proxy_adapter JsonRpcClient parameters.
5
+
6
+ Author: Vasiliy Zdanovskiy
7
+ email: vasilyvz@gmail.com
8
+ """
9
+
10
+ from typing import TYPE_CHECKING, Any, Dict, Optional
11
+ from urllib.parse import urlparse
12
+ import os
13
+
14
+ if TYPE_CHECKING:
15
+ from embed_client.config import ClientConfig
16
+
17
+
18
+ class AdapterConfigFactory:
19
+ """
20
+ Factory for converting ClientConfig/config_dict to adapter JsonRpcClient parameters.
21
+
22
+ Translates embed-client configuration format to mcp_proxy_adapter JsonRpcClient
23
+ initialization parameters, handling all security modes and authentication methods.
24
+ """
25
+
26
+ @staticmethod
27
+ def from_config_dict(config_dict: Dict[str, Any]) -> Dict[str, Any]:
28
+ """
29
+ Convert config_dict to adapter parameters.
30
+
31
+ Args:
32
+ config_dict: Configuration dictionary with server, auth, ssl sections
33
+
34
+ Returns:
35
+ Dictionary with adapter parameters: protocol, host, port, token_header,
36
+ token, cert, key, ca, check_hostname, timeout
37
+ """
38
+ server_config = config_dict.get("server", {})
39
+ auth_config = config_dict.get("auth", {})
40
+ ssl_config = config_dict.get("ssl", {})
41
+ client_config = config_dict.get("client", {})
42
+
43
+ # Extract base URL and parse it
44
+ base_url = server_config.get("base_url") or server_config.get(
45
+ "host", "http://localhost"
46
+ )
47
+ port = server_config.get("port", 8001)
48
+
49
+ # A bare host/IP (as the config generator writes it) has no scheme;
50
+ # give urlparse a netloc so the host is kept instead of dropped.
51
+ if "://" not in base_url:
52
+ base_url = f"http://{base_url}"
53
+ parsed = urlparse(base_url)
54
+ protocol = parsed.scheme or "http"
55
+ host = parsed.hostname or "localhost"
56
+
57
+ # If port is in URL, use it; otherwise use config port
58
+ if parsed.port:
59
+ port = parsed.port
60
+ elif ":" in base_url and not base_url.startswith("http"):
61
+ # Handle edge case where port might be in hostname
62
+ pass
63
+
64
+ # Determine protocol for adapter (http, https, mtls)
65
+ adapter_protocol = "http"
66
+ if protocol == "https" or ssl_config.get("enabled", False):
67
+ if ssl_config.get("cert_file") and ssl_config.get("key_file"):
68
+ adapter_protocol = "mtls"
69
+ else:
70
+ adapter_protocol = "https"
71
+
72
+ # Extract authentication parameters
73
+ token_header: Optional[str] = None
74
+ token: Optional[str] = None
75
+ auth_method = auth_config.get("method", "none")
76
+
77
+ if auth_method == "api_key":
78
+ api_keys = auth_config.get("api_keys", {})
79
+ if api_keys:
80
+ # Use first available API key
81
+ token = list(api_keys.values())[0]
82
+ token_header = auth_config.get("api_key_header", "X-API-Key")
83
+ elif auth_method == "jwt":
84
+ # JWT tokens are created dynamically, but we can set header
85
+ token_header = "Authorization"
86
+ # Token will be generated by auth manager
87
+ jwt_config = auth_config.get("jwt", {})
88
+ if jwt_config.get("username") and jwt_config.get("password"):
89
+ # Store credentials for later token generation
90
+ token = None # Will be generated on demand
91
+ elif auth_method == "basic":
92
+ token_header = "Authorization"
93
+ # Basic auth will be handled via adapter's token mechanism
94
+ basic_config = auth_config.get("basic", {})
95
+ if basic_config.get("username") and basic_config.get("password"):
96
+ token = None # Will be generated on demand
97
+
98
+ # Extract SSL/TLS parameters
99
+ cert: Optional[str] = None
100
+ key: Optional[str] = None
101
+ ca: Optional[str] = None
102
+ check_hostname = ssl_config.get("check_hostname", True)
103
+
104
+ if adapter_protocol in ("https", "mtls"):
105
+ cert = ssl_config.get("cert_file")
106
+ key = ssl_config.get("key_file")
107
+ ca = ssl_config.get("ca_cert_file")
108
+
109
+ # If mTLS, ensure cert and key are set
110
+ if adapter_protocol == "mtls" and not (cert and key):
111
+ # Fallback to auth certificate config
112
+ cert = auth_config.get("certificate", {}).get("cert_file")
113
+ key = auth_config.get("certificate", {}).get("key_file")
114
+
115
+ # Extract timeout
116
+ timeout = client_config.get("timeout", 30.0)
117
+
118
+ return {
119
+ "protocol": adapter_protocol,
120
+ "host": host,
121
+ "port": port,
122
+ "token_header": token_header,
123
+ "token": token,
124
+ "cert": cert,
125
+ "key": key,
126
+ "ca": ca,
127
+ "check_hostname": check_hostname,
128
+ "timeout": timeout,
129
+ # Store original config for auth manager integration
130
+ "_original_config": config_dict,
131
+ }
132
+
133
+ @staticmethod
134
+ def from_client_config(config: "ClientConfig") -> Dict[str, Any]:
135
+ """
136
+ Convert ClientConfig object to adapter parameters.
137
+
138
+ Args:
139
+ config: ClientConfig instance
140
+
141
+ Returns:
142
+ Dictionary with adapter parameters
143
+ """
144
+ return AdapterConfigFactory.from_config_dict(config.get_all())
145
+
146
+ @staticmethod
147
+ def from_legacy_params(
148
+ base_url: str,
149
+ port: Optional[int] = None,
150
+ auth_method: Optional[str] = None,
151
+ ssl_enabled: Optional[bool] = None,
152
+ **kwargs,
153
+ ) -> Dict[str, Any]:
154
+ """
155
+ Convert legacy factory parameters to adapter parameters.
156
+
157
+ Args:
158
+ base_url: Server base URL
159
+ port: Server port
160
+ auth_method: Authentication method
161
+ ssl_enabled: Whether SSL is enabled
162
+ **kwargs: Additional parameters (cert_file, key_file, api_key, etc.)
163
+
164
+ Returns:
165
+ Dictionary with adapter parameters
166
+ """
167
+ parsed = urlparse(base_url)
168
+ protocol = parsed.scheme or "http"
169
+ host = parsed.hostname or "localhost"
170
+ actual_port = port or parsed.port or (443 if protocol == "https" else 80)
171
+
172
+ # Determine protocol
173
+ adapter_protocol = "http"
174
+ if ssl_enabled or protocol == "https":
175
+ if kwargs.get("cert_file") and kwargs.get("key_file"):
176
+ adapter_protocol = "mtls"
177
+ else:
178
+ adapter_protocol = "https"
179
+
180
+ # Extract auth
181
+ token_header: Optional[str] = None
182
+ token: Optional[str] = None
183
+
184
+ if auth_method == "api_key":
185
+ token = kwargs.get("api_key") or os.environ.get("EMBED_CLIENT_API_KEY")
186
+ token_header = kwargs.get("api_key_header", "X-API-Key")
187
+ elif auth_method == "jwt":
188
+ token_header = "Authorization"
189
+ # JWT will be generated on demand
190
+ elif auth_method == "basic":
191
+ token_header = "Authorization"
192
+ # Basic auth will be generated on demand
193
+
194
+ # Extract SSL
195
+ cert = kwargs.get("cert_file")
196
+ key = kwargs.get("key_file")
197
+ ca = kwargs.get("ca_cert_file")
198
+ check_hostname = kwargs.get("check_hostname", True)
199
+
200
+ timeout = kwargs.get("timeout", 30.0)
201
+
202
+ return {
203
+ "protocol": adapter_protocol,
204
+ "host": host,
205
+ "port": actual_port,
206
+ "token_header": token_header,
207
+ "token": token,
208
+ "cert": cert,
209
+ "key": key,
210
+ "ca": ca,
211
+ "check_hostname": check_hostname,
212
+ "timeout": timeout,
213
+ }
@@ -0,0 +1,135 @@
1
+ """
2
+ Normalize embed-client config to adapter config format for validation.
3
+
4
+ Converts client config (server.host, server.port, protocol, ssl, auth)
5
+ to the format expected by mcp_proxy_adapter.core.validation.config_validator,
6
+ and maps validation results back to embed-client section/key names.
7
+
8
+ Author: Vasiliy Zdanovskiy
9
+ email: vasilyvz@gmail.com
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from typing import Any, Dict, List, Optional
15
+
16
+ # Section/key mapping: adapter -> embed-client (for user-facing messages)
17
+ _ADAPTER_TO_CLIENT_KEY: Dict[str, str] = {
18
+ "server.ssl.cert": "ssl.cert_file",
19
+ "server.ssl.key": "ssl.key_file",
20
+ "server.ssl.ca": "ssl.ca_cert_file",
21
+ "server.ssl.crl": "ssl.crl_file",
22
+ }
23
+
24
+
25
+ def client_to_adapter_format(
26
+ config_data: Dict[str, Any],
27
+ config_path: Optional[str] = None,
28
+ ) -> Dict[str, Any]:
29
+ """
30
+ Convert embed-client config to adapter ConfigValidator input format.
31
+
32
+ Adapter expects: server.host, server.port, server.protocol, server.debug,
33
+ server.log_level, server.ssl.{cert,key,ca}, auth, optional ssl, etc.
34
+ """
35
+ server = config_data.get("server", {})
36
+ protocol = config_data.get("protocol", server.get("protocol", "http"))
37
+ ssl_config = config_data.get("ssl", {})
38
+ auth_config = config_data.get("auth", {})
39
+
40
+ # Cert paths: embed-client uses ssl.cert_file / auth.certificate (keep as-is; adapter uses cwd for abspath)
41
+ cert_file = ssl_config.get("cert_file") or (
42
+ auth_config.get("certificate") or {}
43
+ ).get("cert_file")
44
+ key_file = ssl_config.get("key_file") or (auth_config.get("certificate") or {}).get(
45
+ "key_file"
46
+ )
47
+ ca_cert_file = ssl_config.get("ca_cert_file") or (
48
+ auth_config.get("certificate") or {}
49
+ ).get("ca_cert_file")
50
+ crl_file = ssl_config.get("crl_file")
51
+
52
+ server_ssl: Optional[Dict[str, Any]] = None
53
+ if protocol in ("https", "mtls") and (cert_file or key_file or ca_cert_file):
54
+ server_ssl = {
55
+ "cert": cert_file,
56
+ "key": key_file,
57
+ "ca": ca_cert_file,
58
+ "crl": crl_file,
59
+ }
60
+
61
+ adapter_server: Dict[str, Any] = {
62
+ "host": server.get("host", "localhost"),
63
+ "port": server.get("port", 8001),
64
+ "protocol": protocol,
65
+ "debug": False,
66
+ "log_level": "INFO",
67
+ "log_dir": server.get("log_dir", "./logs"),
68
+ }
69
+ if server_ssl:
70
+ adapter_server["ssl"] = server_ssl
71
+
72
+ # Adapter auth: use_token, tokens, roles (for validation consistency)
73
+ security = config_data.get("security", {})
74
+ adapter_auth = {
75
+ "use_token": auth_config.get("method") == "api_key",
76
+ "use_roles": bool(security.get("roles")),
77
+ "tokens": {},
78
+ "roles": (
79
+ security.get("roles") if isinstance(security.get("roles"), dict) else {}
80
+ ),
81
+ }
82
+ api_key = auth_config.get("api_key") or {}
83
+ if api_key.get("key"):
84
+ adapter_auth["tokens"] = {api_key["key"]: ["read", "write"]}
85
+ if security.get("tokens") and isinstance(security["tokens"], dict):
86
+ adapter_auth["tokens"] = {
87
+ (v if isinstance(v, str) else str(v)): ["read", "write"]
88
+ for v in security["tokens"].values()
89
+ }
90
+
91
+ normalized: Dict[str, Any] = {
92
+ "server": adapter_server,
93
+ "auth": adapter_auth,
94
+ }
95
+ if ssl_config:
96
+ normalized["ssl"] = {
97
+ "enabled": ssl_config.get("enabled", protocol in ("https", "mtls")),
98
+ "cert_file": cert_file,
99
+ "key_file": key_file,
100
+ "ca_cert_file": ca_cert_file,
101
+ "crl_file": crl_file,
102
+ }
103
+ # Minimal sections to avoid "unknown section" warnings
104
+ normalized["client"] = {"enabled": False}
105
+ normalized["registration"] = {"enabled": False}
106
+ return normalized
107
+
108
+
109
+ def map_adapter_errors_to_client(
110
+ adapter_errors: List[Any],
111
+ ) -> List[str]:
112
+ """
113
+ Map adapter ValidationResult list to embed-client error strings.
114
+
115
+ Translates section/key (e.g. server.ssl.cert) to client names (ssl.cert_file).
116
+ """
117
+ from mcp_proxy_adapter.core.validation.validation_result import ValidationResult # type: ignore[import-untyped]
118
+
119
+ result: List[str] = []
120
+ for r in adapter_errors:
121
+ if not isinstance(r, ValidationResult):
122
+ result.append(str(r))
123
+ continue
124
+ if getattr(r, "level", "error") != "error":
125
+ continue
126
+ msg = getattr(r, "message", str(r))
127
+ section = getattr(r, "section", None)
128
+ key = getattr(r, "key", None)
129
+ if section and key:
130
+ full_key = f"{section}.{key}" if key else section
131
+ client_key = _ADAPTER_TO_CLIENT_KEY.get(full_key, full_key)
132
+ result.append(f"{client_key}: {msg}")
133
+ else:
134
+ result.append(msg)
135
+ return result
@@ -0,0 +1,166 @@
1
+ """
2
+ Bridge to mcp_proxy_adapter config generator.
3
+
4
+ Uses adapter SimpleConfigGenerator as base: generates adapter config then
5
+ converts to embed-client client config format (server.host/port, protocol,
6
+ ssl.cert_file/key_file/ca_cert_file, auth, client.timeout).
7
+
8
+ Author: Vasiliy Zdanovskiy
9
+ email: vasilyvz@gmail.com
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ import tempfile
16
+ from pathlib import Path
17
+ from typing import Any, Dict, Optional
18
+
19
+ try:
20
+ from mcp_proxy_adapter.core.config.simple_config_generator import ( # type: ignore[import-untyped]
21
+ SimpleConfigGenerator,
22
+ )
23
+
24
+ _ADAPTER_AVAILABLE = True
25
+ except ImportError:
26
+ _ADAPTER_AVAILABLE = False
27
+ SimpleConfigGenerator = None # type: ignore[misc, assignment]
28
+
29
+
30
+ def adapter_output_to_client_config(adapter_dict: Dict[str, Any]) -> Dict[str, Any]:
31
+ """
32
+ Convert adapter generator output to embed-client config format.
33
+
34
+ Adapter has: server.{host, port, protocol, ssl.{cert, key, ca, crl}},
35
+ auth.{use_token, tokens, roles}. We output: server.{host, port},
36
+ protocol, ssl.{enabled, cert_file, key_file, ca_cert_file, crl_file},
37
+ auth.{method, api_key, certificate}, client.timeout, security.
38
+ """
39
+ server = adapter_dict.get("server", {})
40
+ ssl = server.get("ssl") or {}
41
+ auth = adapter_dict.get("auth", {})
42
+
43
+ protocol = server.get("protocol", "http")
44
+ ssl_enabled = protocol in ("https", "mtls")
45
+ verify_mode = "CERT_REQUIRED" if protocol == "mtls" else "CERT_NONE"
46
+
47
+ # Build embed-client auth: certificate for mTLS, api_key when use_token
48
+ if protocol == "mtls":
49
+ auth_method = "certificate"
50
+ elif auth.get("use_token"):
51
+ auth_method = "api_key"
52
+ else:
53
+ auth_method = "none"
54
+ tokens = auth.get("tokens") or {}
55
+ first_token: Optional[str] = None
56
+ if isinstance(tokens, dict) and tokens:
57
+ first_token = next(iter(tokens.keys()), None)
58
+
59
+ config: Dict[str, Any] = {
60
+ "server": {
61
+ "host": server.get("host", "localhost"),
62
+ "port": server.get("port", 8001),
63
+ },
64
+ "client": {"timeout": 30.0},
65
+ "protocol": protocol,
66
+ "auth": {
67
+ "method": auth_method,
68
+ "api_key": {"key": first_token, "header": "X-API-Key"},
69
+ "certificate": {
70
+ "enabled": protocol == "mtls",
71
+ "cert_file": ssl.get("cert") if protocol == "mtls" else None,
72
+ "key_file": ssl.get("key") if protocol == "mtls" else None,
73
+ "ca_cert_file": ssl.get("ca") if protocol == "mtls" else None,
74
+ },
75
+ },
76
+ "ssl": {
77
+ "enabled": ssl_enabled,
78
+ "verify": protocol != "http",
79
+ "verify_mode": verify_mode,
80
+ "check_hostname": False,
81
+ "check_expiry": False,
82
+ "cert_file": ssl.get("cert"),
83
+ "key_file": ssl.get("key"),
84
+ "ca_cert_file": ssl.get("ca"),
85
+ "crl_file": ssl.get("crl"),
86
+ "client_cert_required": protocol == "mtls",
87
+ },
88
+ "security": {"enabled": bool(auth.get("use_token")), "tokens": {}, "roles": {}},
89
+ }
90
+ if auth.get("use_token") and isinstance(tokens, dict) and tokens:
91
+ # embed-client security.tokens: role_name -> token_value
92
+ config["security"]["tokens"] = (
93
+ {"user": first_token} if first_token else dict(tokens)
94
+ )
95
+ if auth.get("use_roles") and isinstance(auth.get("roles"), dict):
96
+ config["security"]["roles"] = auth["roles"]
97
+ return config
98
+
99
+
100
+ def generate_client_config_via_adapter(
101
+ protocol: str,
102
+ host: str = "localhost",
103
+ port: int = 8001,
104
+ cert_file: Optional[str] = None,
105
+ key_file: Optional[str] = None,
106
+ ca_cert_file: Optional[str] = None,
107
+ crl_file: Optional[str] = None,
108
+ token: Optional[str] = None,
109
+ use_token: bool = False,
110
+ use_roles: bool = False,
111
+ tokens: Optional[Dict[str, Any]] = None,
112
+ roles: Optional[Dict[str, Any]] = None,
113
+ output_path: Optional[Path] = None,
114
+ ) -> Dict[str, Any]:
115
+ """
116
+ Generate embed-client config using adapter SimpleConfigGenerator as base.
117
+
118
+ Calls adapter generator, loads output, converts to embed-client format.
119
+ Returns config dict; if output_path given, also saves JSON.
120
+ """
121
+ if not _ADAPTER_AVAILABLE or SimpleConfigGenerator is None:
122
+ raise RuntimeError(
123
+ "mcp_proxy_adapter is not available; install it to use adapter-based generator"
124
+ )
125
+
126
+ if protocol == "mtls" and not ca_cert_file:
127
+ raise ValueError("CA certificate is required for mTLS. Provide ca_cert_file.")
128
+
129
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as tmp:
130
+ temp_path = tmp.name
131
+ try:
132
+ gen = SimpleConfigGenerator()
133
+ gen.generate(
134
+ protocol=protocol,
135
+ with_proxy=False,
136
+ out_path=temp_path,
137
+ server_host=host,
138
+ server_port=port,
139
+ server_cert_file=cert_file,
140
+ server_key_file=key_file,
141
+ server_ca_cert_file=ca_cert_file,
142
+ server_crl_file=crl_file,
143
+ )
144
+ with open(temp_path, "r", encoding="utf-8") as f:
145
+ adapter_dict = json.load(f)
146
+
147
+ # Optionally set auth from adapter CLI-style (tokens/roles)
148
+ if use_token and (token or tokens):
149
+ adapter_dict.setdefault("auth", {})["use_token"] = True
150
+ if tokens:
151
+ adapter_dict["auth"]["tokens"] = tokens
152
+ elif token:
153
+ adapter_dict["auth"]["tokens"] = {token: ["read", "write"]}
154
+ if use_roles and roles:
155
+ adapter_dict.setdefault("auth", {})["use_roles"] = True
156
+ adapter_dict["auth"]["roles"] = roles
157
+
158
+ client_config = adapter_output_to_client_config(adapter_dict)
159
+ if output_path:
160
+ output_path = Path(output_path)
161
+ output_path.parent.mkdir(parents=True, exist_ok=True)
162
+ with open(output_path, "w", encoding="utf-8") as f:
163
+ json.dump(client_config, f, indent=2, ensure_ascii=False)
164
+ return client_config
165
+ finally:
166
+ Path(temp_path).unlink(missing_ok=True)