code-analysis-client 1.0.9__tar.gz → 1.6.39__tar.gz

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 (23) hide show
  1. {code_analysis_client-1.0.9 → code_analysis_client-1.6.39}/PKG-INFO +4 -4
  2. {code_analysis_client-1.0.9 → code_analysis_client-1.6.39}/README.md +2 -2
  3. {code_analysis_client-1.0.9 → code_analysis_client-1.6.39}/code_analysis_client/__init__.py +1 -0
  4. {code_analysis_client-1.0.9 → code_analysis_client-1.6.39}/code_analysis_client/client.py +3 -0
  5. {code_analysis_client-1.0.9 → code_analysis_client-1.6.39}/code_analysis_client/commands_proxy.py +3 -0
  6. {code_analysis_client-1.0.9 → code_analysis_client-1.6.39}/code_analysis_client/config.py +3 -0
  7. {code_analysis_client-1.0.9 → code_analysis_client-1.6.39}/code_analysis_client/exceptions.py +1 -0
  8. {code_analysis_client-1.0.9 → code_analysis_client-1.6.39}/code_analysis_client/file_session.py +33 -24
  9. {code_analysis_client-1.0.9 → code_analysis_client-1.6.39}/code_analysis_client/server_api.py +111 -10
  10. {code_analysis_client-1.0.9 → code_analysis_client-1.6.39}/code_analysis_client/universal_file.py +17 -4
  11. {code_analysis_client-1.0.9 → code_analysis_client-1.6.39}/code_analysis_client/validation.py +3 -6
  12. code_analysis_client-1.6.39/code_analysis_client/version.txt +1 -0
  13. {code_analysis_client-1.0.9 → code_analysis_client-1.6.39}/code_analysis_client.egg-info/PKG-INFO +4 -4
  14. {code_analysis_client-1.0.9 → code_analysis_client-1.6.39}/code_analysis_client.egg-info/requires.txt +1 -1
  15. {code_analysis_client-1.0.9 → code_analysis_client-1.6.39}/pyproject.toml +1 -1
  16. code_analysis_client-1.0.9/code_analysis_client/version.txt +0 -1
  17. {code_analysis_client-1.0.9 → code_analysis_client-1.6.39}/code_analysis_client/py.typed +0 -0
  18. {code_analysis_client-1.0.9 → code_analysis_client-1.6.39}/code_analysis_client/responses.py +0 -0
  19. {code_analysis_client-1.0.9 → code_analysis_client-1.6.39}/code_analysis_client/server_schema.py +0 -0
  20. {code_analysis_client-1.0.9 → code_analysis_client-1.6.39}/code_analysis_client.egg-info/SOURCES.txt +0 -0
  21. {code_analysis_client-1.0.9 → code_analysis_client-1.6.39}/code_analysis_client.egg-info/dependency_links.txt +0 -0
  22. {code_analysis_client-1.0.9 → code_analysis_client-1.6.39}/code_analysis_client.egg-info/top_level.txt +0 -0
  23. {code_analysis_client-1.0.9 → code_analysis_client-1.6.39}/setup.cfg +0 -0
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-analysis-client
3
- Version: 1.0.9
3
+ Version: 1.6.39
4
4
  Summary: Async JSON-RPC client for the code-analysis MCP server (mcp-proxy-adapter JsonRpcClient)
5
5
  Author-email: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
6
6
  Requires-Python: >=3.10
7
7
  Description-Content-Type: text/markdown
8
- Requires-Dist: mcp-proxy-adapter>=8.10.13
8
+ Requires-Dist: mcp-proxy-adapter>=8.10.15
9
9
  Provides-Extra: dev
10
10
  Requires-Dist: pytest>=8.0; extra == "dev"
11
11
  Requires-Dist: pytest-asyncio>=0.25.0; extra == "dev"
@@ -97,8 +97,8 @@ Sync checks (in-process registry):
97
97
  pytest tests/test_client_server_api_sync.py tests/test_code_analysis_client.py -k session
98
98
  ```
99
99
 
100
- Package version is in ``client/code_analysis_client/version.txt`` (synced with the
101
- root ``code-analysis`` project via ``scripts/sync_code_analysis_client_version.py``).
100
+ Package version is in the root ``pyproject.toml``; before a client wheel build run
101
+ ``python scripts/sync_code_analysis_client_version.py`` (also done by ``release_build.sh``).
102
102
 
103
103
  ## Examples (this repository)
104
104
 
@@ -85,8 +85,8 @@ Sync checks (in-process registry):
85
85
  pytest tests/test_client_server_api_sync.py tests/test_code_analysis_client.py -k session
86
86
  ```
87
87
 
88
- Package version is in ``client/code_analysis_client/version.txt`` (synced with the
89
- root ``code-analysis`` project via ``scripts/sync_code_analysis_client_version.py``).
88
+ Package version is in the root ``pyproject.toml``; before a client wheel build run
89
+ ``python scripts/sync_code_analysis_client_version.py`` (also done by ``release_build.sh``).
90
90
 
91
91
  ## Examples (this repository)
92
92
 
@@ -64,6 +64,7 @@ __all__ = [
64
64
 
65
65
 
66
66
  def _read_package_version() -> str:
67
+ """Read installed client version from version.txt or package metadata."""
67
68
  vf = Path(__file__).resolve().parent / "version.txt"
68
69
  if vf.is_file():
69
70
  return vf.read_text(encoding="utf-8").strip()
@@ -228,10 +228,13 @@ class CodeAnalysisAsyncClient:
228
228
  )
229
229
 
230
230
  async def close(self) -> None:
231
+ """Close the underlying JSON-RPC transport."""
231
232
  await self._rpc.close()
232
233
 
233
234
  async def __aenter__(self) -> CodeAnalysisAsyncClient:
235
+ """Enter async context manager and return this client."""
234
236
  return self
235
237
 
236
238
  async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
239
+ """Close the client when leaving an async context manager block."""
237
240
  await self.close()
@@ -19,6 +19,7 @@ class ValidatedCommandsProxy:
19
19
  __slots__ = ("_client",)
20
20
 
21
21
  def __init__(self, client: CodeAnalysisAsyncClient) -> None:
22
+ """Store the client used for schema lookup and validated command calls."""
22
23
  object.__setattr__(self, "_client", client)
23
24
 
24
25
  def clear_schema_cache(self) -> None:
@@ -47,10 +48,12 @@ class ValidatedCommandsProxy:
47
48
  return cast(Dict[str, Any], await self._client.call_validated(command, merged))
48
49
 
49
50
  def __getattr__(self, name: str) -> Any:
51
+ """Return an async validated-call wrapper for an arbitrary command name."""
50
52
  if name.startswith("_"):
51
53
  raise AttributeError(name)
52
54
 
53
55
  async def _bound(**kw: Any) -> Dict[str, Any]:
56
+ """Execute the dynamically selected command with validated keyword params."""
54
57
  return cast(Dict[str, Any], await self._client.call_validated(name, kw))
55
58
 
56
59
  _bound.__name__ = name
@@ -13,12 +13,14 @@ from typing import Any, Mapping
13
13
 
14
14
 
15
15
  def _expand_path(value: Any) -> str | None:
16
+ """Expand a non-empty filesystem path value to an absolute string."""
16
17
  if value is None or value == "":
17
18
  return None
18
19
  return str(Path(str(value)).expanduser().resolve())
19
20
 
20
21
 
21
22
  def _ssl_paths_from_section(ssl_section: Any) -> dict[str, str]:
23
+ """Extract and normalize TLS path fields from a config SSL section."""
22
24
  if not isinstance(ssl_section, dict):
23
25
  return {}
24
26
  out: dict[str, str] = {}
@@ -38,6 +40,7 @@ def _ssl_paths_from_section(ssl_section: Any) -> dict[str, str]:
38
40
 
39
41
 
40
42
  def _network_from_server_config(config: Mapping[str, Any]) -> dict[str, Any]:
43
+ """Return host, port, and protocol settings from a server config dict."""
41
44
  server = config.get("server", {})
42
45
  if not isinstance(server, dict):
43
46
  server = {}
@@ -15,6 +15,7 @@ class ClientValidationError(ValueError):
15
15
  field: Optional[str] = None,
16
16
  details: Optional[Dict[str, Any]] = None,
17
17
  ) -> None:
18
+ """Initialize validation error with optional field and details payload."""
18
19
  super().__init__(message)
19
20
  self.field = field
20
21
  self.details = details or {}
@@ -40,6 +40,7 @@ class SessionNotFoundError(ClientValidationError):
40
40
 
41
41
 
42
42
  def _unwrap(data: Dict[str, Any]) -> Dict[str, Any]:
43
+ """Unwrap command result payloads and map missing sessions to SessionNotFoundError."""
43
44
  return unwrap_command_result(
44
45
  data,
45
46
  session_not_found_type=SessionNotFoundError,
@@ -47,6 +48,7 @@ def _unwrap(data: Dict[str, Any]) -> Dict[str, Any]:
47
48
 
48
49
 
49
50
  def _require_non_empty(value: str, *, field: str) -> str:
51
+ """Return stripped text or raise ClientValidationError for an empty field."""
50
52
  text = str(value or "").strip()
51
53
  if not text:
52
54
  raise ClientValidationError(f"{field} is required", field=field)
@@ -90,6 +92,7 @@ def _validate_upload_selector(
90
92
 
91
93
 
92
94
  def _lock_mode_from_flag(lock: bool) -> str:
95
+ """Convert a boolean lock flag into the server lock_mode value."""
93
96
  return "full" if lock else "none"
94
97
 
95
98
 
@@ -111,6 +114,7 @@ class FileSessionClient:
111
114
  __slots__ = ("_client",)
112
115
 
113
116
  def __init__(self, client: CodeAnalysisAsyncClient) -> None:
117
+ """Store the underlying async client used to call session commands."""
114
118
  self._client = client
115
119
 
116
120
  async def create_session(
@@ -133,17 +137,19 @@ class FileSessionClient:
133
137
  )
134
138
  return sid
135
139
 
140
+ async def validate_session(
141
+ self, session_id: str, *, touch: bool = False
142
+ ) -> Dict[str, Any]:
143
+ """Confirm ``session_id`` exists (``session_validate``)."""
144
+ sid = _require_non_empty(session_id, field="session_id")
145
+ params: Dict[str, Any] = {"session_id": sid}
146
+ if touch:
147
+ params["touch"] = True
148
+ return _unwrap(await self._client.call_validated("session_validate", params))
149
+
136
150
  async def assert_session_exists(self, session_id: str) -> None:
137
- """Verify ``session_id`` is registered (touch via ``session_list_file_locks``)."""
138
- sid = str(session_id).strip()
139
- if not sid:
140
- raise ClientValidationError("session_id is required", field="session_id")
141
- _unwrap(
142
- await self._client.call_validated(
143
- "session_list_file_locks",
144
- {"session_id": sid},
145
- )
146
- )
151
+ """Verify ``session_id`` is registered (``session_validate``)."""
152
+ await self.validate_session(session_id, touch=False)
147
153
 
148
154
  async def delete_session(
149
155
  self, session_id: str, *, force: bool = False
@@ -151,13 +157,14 @@ class FileSessionClient:
151
157
  """Delete a client session (``session_delete``).
152
158
 
153
159
  ``force`` defaults to false (omit on the wire when false). Safe delete
154
- requires no ``session_file_locks`` and no ``subordinate_sessions`` rows
155
- where this session is ``parent_session_id``. When ``force`` is true,
156
- subordinate server link rows and file locks for this session are removed
157
- before the session row is deleted.
158
-
159
- Returns ``session_id``, ``deleted``, ``released_lock_count``, and
160
- ``released_subordinate_count``.
160
+ requires no ``session_file_locks``, no ``subordinate_sessions`` rows
161
+ where this session is ``parent_session_id``, and no advisory file-lock
162
+ leases. When ``force`` is true, subordinate link rows, DB file locks, and
163
+ advisory leases for this session are released before the session row is
164
+ deleted.
165
+
166
+ Returns ``session_id``, ``deleted``, ``released_lock_count``,
167
+ ``released_subordinate_count``, and ``released_advisory_lease_count``.
161
168
  """
162
169
  params: Dict[str, Any] = {"session_id": session_id}
163
170
  if force:
@@ -174,7 +181,8 @@ class FileSessionClient:
174
181
 
175
182
  Returns ``locked_files_by_project`` (file_id, file_path,
176
183
  project_presentation per project) and ``subordinate_sessions``
177
- (server_uuid, session_presentation, server_presentation, link_comment).
184
+ (session_id, server_uuid, session_presentation, server_presentation,
185
+ link_comment).
178
186
  """
179
187
  sid = _require_non_empty(session_id, field="session_id")
180
188
  return _unwrap(
@@ -193,8 +201,9 @@ class FileSessionClient:
193
201
  ) -> Dict[str, Any]:
194
202
  """Register leading session on a subordinate server (``subordinate_session_create``).
195
203
 
196
- The same ``parent_session_id`` is used on the remote server. ``server_uuid``
197
- defaults to the server's ``registration.instance_uuid`` when omitted.
204
+ Subordinate servers use ``parent_session_id`` as ``session_id``.
205
+ ``server_uuid`` defaults to the server's ``registration.instance_uuid``
206
+ when omitted.
198
207
  """
199
208
  params: Dict[str, Any] = {
200
209
  "parent_session_id": _require_non_empty(
@@ -213,7 +222,7 @@ class FileSessionClient:
213
222
  parent_session_id: str,
214
223
  server_uuid: str,
215
224
  ) -> Dict[str, Any]:
216
- """Fetch one subordinate link (``subordinate_session_get``)."""
225
+ """Fetch one subordinate server link (``subordinate_session_get``)."""
217
226
  return _unwrap(
218
227
  await self._client.call_validated(
219
228
  "subordinate_session_get",
@@ -251,9 +260,9 @@ class FileSessionClient:
251
260
  parent_session_id: str,
252
261
  server_uuid: str,
253
262
  ) -> Dict[str, Any]:
254
- """Remove subordinate link only (``subordinate_session_delete``).
263
+ """Remove subordinate server link (``subordinate_session_delete``).
255
264
 
256
- Does not delete the ``client_sessions`` row for ``parent_session_id``.
265
+ Does not delete the leading ``client_sessions`` row; use :meth:`delete_session`.
257
266
  """
258
267
  return _unwrap(
259
268
  await self._client.call_validated(
@@ -273,7 +282,7 @@ class FileSessionClient:
273
282
  parent_session_id: Optional[str] = None,
274
283
  server_uuid: Optional[str] = None,
275
284
  ) -> Dict[str, Any]:
276
- """List subordinate links (``subordinate_session_list``).
285
+ """List subordinate server links (``subordinate_session_list``).
277
286
 
278
287
  Returns ``links`` and ``count``. All filter parameters are optional.
279
288
  """
@@ -1,10 +1,20 @@
1
1
  """
2
2
  Canonical command names for the high-level client API vs the live server registry.
3
3
 
4
- The generic client (:class:`CodeAnalysisAsyncClient.call`) can invoke any command
5
- returned by server ``help``. Facade modules (:mod:`file_session`, :mod:`universal_file`)
6
- must only reference commands listed here — and every name in
7
- :data:`CLIENT_FACADE_COMMANDS` must be registered on the server.
4
+ **code-analysis-server** owns project trees, the index database, sessions, locks,
5
+ and editor ingress (chunked transfer). It does **not** expose in-server content
6
+ editing (``universal_file_open/edit/write/close``, CST/JSON tree modify/save,
7
+ ``format_code``, legacy line writers).
8
+
9
+ **File content on CA** — read-only surface only:
10
+
11
+ - :data:`FILE_CONTENT_READ_COMMANDS` — preview and project search (``search`` /
12
+ ``search_get_page``); on-disk grep is internal to ``search`` when enabled.
13
+ - :data:`FS_COMMANDS` — filesystem operations (copy/move/remove/list/grep); not
14
+ the structured edit workflow.
15
+
16
+ Editor clients use :data:`CLIENT_FACADE_COMMANDS` (sessions, transfer, locks,
17
+ ``universal_file_preview``).
8
18
 
9
19
  Author: Vasiliy Zdanovskiy
10
20
  email: vasilyvz@gmail.com
@@ -25,10 +35,11 @@ LEGACY_REMOVED_COMMANDS: FrozenSet[str] = frozenset(
25
35
  "write_project_text_lines",
26
36
  "create_text_file",
27
37
  "replace_file_lines",
38
+ "universal_file_search",
28
39
  }
29
40
  )
30
41
 
31
- # CST tree MCP workflow — modules may exist in the repo but commands are not registered.
42
+ # CST / JSON tree MCP workflow — modules may exist in the repo but are not registered.
32
43
  CST_REMOVED_COMMANDS: FrozenSet[str] = frozenset(
33
44
  {
34
45
  "cst_load_file",
@@ -41,17 +52,85 @@ CST_REMOVED_COMMANDS: FrozenSet[str] = frozenset(
41
52
  "cst_get_node_at_line",
42
53
  "cst_reload_tree",
43
54
  "cst_convert_and_save",
55
+ "cst_apply_buffer",
56
+ "cst_list_trees",
57
+ "cst_unload_tree",
44
58
  "list_cst_blocks",
45
59
  "query_cst",
60
+ "json_load_file",
61
+ "json_find_node",
62
+ "json_get_node_info",
63
+ "json_modify_tree",
64
+ "json_save_tree",
65
+ "json_reload_tree",
66
+ }
67
+ )
68
+
69
+ # Content editing on ai-editor-server only (not on code-analysis-server).
70
+ EDITING_REMOVED_COMMANDS: FrozenSet[str] = frozenset(
71
+ {
72
+ "universal_file_open",
73
+ "universal_file_edit",
74
+ "universal_file_write",
75
+ "universal_file_close",
76
+ "universal_file_move_nodes",
77
+ "session_git_log",
78
+ "session_git_diff",
79
+ "session_git_show",
80
+ "session_git_status",
81
+ "session_git_revert",
82
+ "session_undo",
83
+ "session_redo",
84
+ "session_write",
85
+ "delete_file",
86
+ "delete_files_by_mask",
87
+ "restore_deleted_files",
88
+ "unmark_deleted_file",
89
+ "cleanup_deleted_files",
90
+ "collapse_versions",
91
+ "restore_backup_file",
92
+ "delete_backup",
93
+ "clear_all_backups",
94
+ "split_class",
95
+ "extract_superclass",
96
+ "split_file_to_package",
97
+ "format_code",
98
+ }
99
+ )
100
+
101
+ REMOVED_COMMANDS: FrozenSet[str] = (
102
+ LEGACY_REMOVED_COMMANDS | CST_REMOVED_COMMANDS | EDITING_REMOVED_COMMANDS
103
+ )
104
+
105
+ # Read-only file **content** on CA (no mutate/edit workflow). Includes on-disk search
106
+ # (``fs_grep``, paginated ``search_*``) for paths not yet in the index.
107
+ FILE_CONTENT_READ_COMMANDS: FrozenSet[str] = frozenset(
108
+ {
109
+ "universal_file_preview",
46
110
  "get_file_lines",
111
+ "search",
112
+ "search_get_page",
113
+ "search_get_status",
114
+ "search_cancel",
115
+ "search_close",
47
116
  }
48
117
  )
49
118
 
50
- REMOVED_COMMANDS: FrozenSet[str] = LEGACY_REMOVED_COMMANDS | CST_REMOVED_COMMANDS
119
+ # Filesystem helpers separate from structured content editing; may touch paths/bytes
120
+ # via copy/move/remove but are not universal_file_edit / CST / format_code.
121
+ FS_COMMANDS: FrozenSet[str] = frozenset(
122
+ {
123
+ "fs_copy",
124
+ "fs_move",
125
+ "fs_remove",
126
+ }
127
+ )
51
128
 
129
+ # Client sessions + DB file locks — persisted on this server for editor/terminal clients.
52
130
  FILE_SESSION_COMMANDS: FrozenSet[str] = frozenset(
53
131
  {
54
132
  "session_create",
133
+ "session_validate",
55
134
  "session_delete",
56
135
  "session_list",
57
136
  "session_view",
@@ -76,10 +155,6 @@ TRANSFER_AND_LOCK_COMMANDS: FrozenSet[str] = frozenset(
76
155
 
77
156
  UNIVERSAL_FILE_COMMANDS: FrozenSet[str] = frozenset(
78
157
  {
79
- "universal_file_open",
80
- "universal_file_edit",
81
- "universal_file_write",
82
- "universal_file_close",
83
158
  "universal_file_preview",
84
159
  }
85
160
  )
@@ -92,6 +167,7 @@ CLIENT_FACADE_COMMANDS: FrozenSet[str] = (
92
167
  # ``session_list_file_locks`` is also used by ``assert_session_exists``.
93
168
  FILE_SESSION_FACADE_METHODS: Dict[str, str] = {
94
169
  "session_create": "create_session",
170
+ "session_validate": "validate_session",
95
171
  "session_delete": "delete_session",
96
172
  "session_list": "list_sessions",
97
173
  "session_view": "view_session",
@@ -155,6 +231,7 @@ def assert_transfer_facade_complete() -> None:
155
231
 
156
232
 
157
233
  def _command_registered(get_command: Callable[[str], object], name: str) -> bool:
234
+ """Return True when ``get_command`` can resolve a command name."""
158
235
  try:
159
236
  get_command(name)
160
237
  return True
@@ -182,3 +259,27 @@ def assert_removed_commands_absent(get_command: Callable[[str], object]) -> None
182
259
  ]
183
260
  if present:
184
261
  raise AssertionError(f"Removed commands still registered: {present}")
262
+
263
+
264
+ def assert_file_content_read_commands_registered(
265
+ get_command: Callable[[str], object],
266
+ ) -> None:
267
+ """Raise ``KeyError`` if a read-only content command is missing from the registry."""
268
+ missing = [
269
+ name
270
+ for name in sorted(FILE_CONTENT_READ_COMMANDS)
271
+ if not _command_registered(get_command, name)
272
+ ]
273
+ if missing:
274
+ raise KeyError(f"File content read commands not registered: {missing}")
275
+
276
+
277
+ def assert_fs_commands_registered(get_command: Callable[[str], object]) -> None:
278
+ """Raise ``KeyError`` if a filesystem helper command is missing from the registry."""
279
+ missing = [
280
+ name
281
+ for name in sorted(FS_COMMANDS)
282
+ if not _command_registered(get_command, name)
283
+ ]
284
+ if missing:
285
+ raise KeyError(f"Filesystem commands not registered: {missing}")
@@ -20,11 +20,16 @@ if TYPE_CHECKING:
20
20
 
21
21
 
22
22
  class UniversalFileClient:
23
- """Edit-session workflow for project files (open → edit → write → close)."""
23
+ """Edit-session workflow for project files (open → edit → write → close).
24
+
25
+ Uses ``session_id`` from ``universal_file_open`` only. Not related to
26
+ client ``session_*`` commands (``session_create``, ``session_open_file``, …).
27
+ """
24
28
 
25
29
  __slots__ = ("_client",)
26
30
 
27
31
  def __init__(self, client: CodeAnalysisAsyncClient) -> None:
32
+ """Store the async client used for universal file workflow commands."""
28
33
  self._client = client
29
34
 
30
35
  async def open(
@@ -35,7 +40,10 @@ class UniversalFileClient:
35
40
  create: bool = False,
36
41
  initial_content: Optional[str] = None,
37
42
  ) -> Dict[str, Any]:
38
- """Start an edit session (``universal_file_open``)."""
43
+ """Start an edit session (``universal_file_open``).
44
+
45
+ Identifies the file by ``project_id`` + ``file_path`` (not ``file_id``).
46
+ """
39
47
  params: Dict[str, Any] = {
40
48
  "project_id": project_id,
41
49
  "file_path": file_path,
@@ -72,7 +80,12 @@ class UniversalFileClient:
72
80
  *,
73
81
  write_mode: str = "commit",
74
82
  ) -> Dict[str, Any]:
75
- """Persist or preview draft (``universal_file_write``)."""
83
+ """Persist or preview draft (``universal_file_write``).
84
+
85
+ Default ``write_mode`` is ``commit`` in this client wrapper. The server
86
+ default when the parameter is omitted is ``preview``. Pass
87
+ ``write_mode='preview'`` first to inspect the diff, then ``commit``.
88
+ """
76
89
  return unwrap_command_result(
77
90
  await self._client.call_validated(
78
91
  "universal_file_write",
@@ -89,7 +102,7 @@ class UniversalFileClient:
89
102
  project_id: str,
90
103
  session_id: str,
91
104
  ) -> Dict[str, Any]:
92
- """End edit session (``universal_file_close``)."""
105
+ """End edit session (``universal_file_open`` session_id, not client ``session_*``)."""
93
106
  return unwrap_command_result(
94
107
  await self._client.call_validated(
95
108
  "universal_file_close",
@@ -13,12 +13,9 @@ from code_analysis_client.exceptions import ClientValidationError
13
13
 
14
14
 
15
15
  def prepare_params_for_schema(
16
- params: Dict[str, Any], schema: Dict[str, Any]
16
+ params: Dict[str, Any], schema: Dict[str, Any] # noqa: ARG001
17
17
  ) -> Dict[str, Any]:
18
- """Drop unknown keys when ``additionalProperties`` is false (same as server base)."""
19
- props = schema.get("properties") or {}
20
- if not schema.get("additionalProperties", True):
21
- return {k: v for k, v in params.items() if k in props}
18
+ """Return a shallow copy of params for validation and dispatch."""
22
19
  return dict(params)
23
20
 
24
21
 
@@ -34,7 +31,7 @@ def validate_params_against_schema(
34
31
  field="params",
35
32
  )
36
33
  props = schema.get("properties") or {}
37
- additional_ok = schema.get("additionalProperties", True)
34
+ additional_ok = schema.get("additionalProperties", False)
38
35
  required_set = set(schema.get("required") or [])
39
36
  for key, value in params.items():
40
37
  if key not in props:
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-analysis-client
3
- Version: 1.0.9
3
+ Version: 1.6.39
4
4
  Summary: Async JSON-RPC client for the code-analysis MCP server (mcp-proxy-adapter JsonRpcClient)
5
5
  Author-email: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
6
6
  Requires-Python: >=3.10
7
7
  Description-Content-Type: text/markdown
8
- Requires-Dist: mcp-proxy-adapter>=8.10.13
8
+ Requires-Dist: mcp-proxy-adapter>=8.10.15
9
9
  Provides-Extra: dev
10
10
  Requires-Dist: pytest>=8.0; extra == "dev"
11
11
  Requires-Dist: pytest-asyncio>=0.25.0; extra == "dev"
@@ -97,8 +97,8 @@ Sync checks (in-process registry):
97
97
  pytest tests/test_client_server_api_sync.py tests/test_code_analysis_client.py -k session
98
98
  ```
99
99
 
100
- Package version is in ``client/code_analysis_client/version.txt`` (synced with the
101
- root ``code-analysis`` project via ``scripts/sync_code_analysis_client_version.py``).
100
+ Package version is in the root ``pyproject.toml``; before a client wheel build run
101
+ ``python scripts/sync_code_analysis_client_version.py`` (also done by ``release_build.sh``).
102
102
 
103
103
  ## Examples (this repository)
104
104
 
@@ -1,4 +1,4 @@
1
- mcp-proxy-adapter>=8.10.13
1
+ mcp-proxy-adapter>=8.10.15
2
2
 
3
3
  [dev]
4
4
  pytest>=8.0
@@ -12,7 +12,7 @@ authors = [
12
12
  { name = "Vasiliy Zdanovskiy", email = "vasilyvz@gmail.com" },
13
13
  ]
14
14
  dependencies = [
15
- "mcp-proxy-adapter>=8.10.13",
15
+ "mcp-proxy-adapter>=8.10.15",
16
16
  ]
17
17
 
18
18
  [project.optional-dependencies]