open-edison 0.1.64__py3-none-any.whl → 0.1.72rc1__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.
src/permissions.py CHANGED
@@ -53,6 +53,9 @@ class ResourcePermission:
53
53
  write_operation: bool = False
54
54
  read_private_data: bool = False
55
55
  read_untrusted_public_data: bool = False
56
+ acl: str = "PUBLIC"
57
+ # Optional metadata fields (ignored by enforcement but accepted from JSON)
58
+ description: str | None = None
56
59
 
57
60
 
58
61
  @dataclass
@@ -226,43 +229,117 @@ class Permissions:
226
229
  def get_tool_permission(self, tool_name: str) -> ToolPermission:
227
230
  """Get permission for a specific tool"""
228
231
  if tool_name not in self.tool_permissions:
229
- raise PermissionsError(f"Tool '{tool_name}' not found in permissions")
232
+ if tool_name.startswith("builtin_"):
233
+ log.info(
234
+ f"Tool '{tool_name}' not found; returning builtin safe default (enabled, 0 risk)"
235
+ )
236
+ return ToolPermission(
237
+ enabled=True,
238
+ write_operation=False,
239
+ read_private_data=False,
240
+ read_untrusted_public_data=False,
241
+ acl="PUBLIC",
242
+ )
243
+ log.warning(
244
+ f"Tool '{tool_name}' not found in permissions; returning enabled full-trifecta default"
245
+ )
246
+ return ToolPermission(
247
+ enabled=True,
248
+ write_operation=True,
249
+ read_private_data=True,
250
+ read_untrusted_public_data=True,
251
+ acl="SECRET",
252
+ )
230
253
  return self.tool_permissions[tool_name]
231
254
 
232
255
  def get_resource_permission(self, resource_name: str) -> ResourcePermission:
233
256
  """Get permission for a specific resource"""
234
257
  if resource_name not in self.resource_permissions:
235
- raise PermissionsError(f"Resource '{resource_name}' not found in permissions")
258
+ if resource_name.startswith("builtin_"):
259
+ log.info(
260
+ f"Resource '{resource_name}' not found; returning builtin safe default (enabled, 0 risk)"
261
+ )
262
+ return ResourcePermission(
263
+ enabled=True,
264
+ write_operation=False,
265
+ read_private_data=False,
266
+ read_untrusted_public_data=False,
267
+ )
268
+ log.warning(
269
+ f"Resource '{resource_name}' not found in permissions; returning enabled full-trifecta default"
270
+ )
271
+ return ResourcePermission(
272
+ enabled=True,
273
+ write_operation=True,
274
+ read_private_data=True,
275
+ read_untrusted_public_data=True,
276
+ )
236
277
  return self.resource_permissions[resource_name]
237
278
 
238
279
  def get_prompt_permission(self, prompt_name: str) -> PromptPermission:
239
280
  """Get permission for a specific prompt"""
240
281
  if prompt_name not in self.prompt_permissions:
241
- raise PermissionsError(f"Prompt '{prompt_name}' not found in permissions")
282
+ if prompt_name.startswith("builtin_"):
283
+ log.info(
284
+ f"Prompt '{prompt_name}' not found; returning builtin safe default (enabled, 0 risk)"
285
+ )
286
+ return PromptPermission(
287
+ enabled=True,
288
+ write_operation=False,
289
+ read_private_data=False,
290
+ read_untrusted_public_data=False,
291
+ acl="PUBLIC",
292
+ )
293
+ log.warning(
294
+ f"Prompt '{prompt_name}' not found in permissions; returning enabled full-trifecta default"
295
+ )
296
+ return PromptPermission(
297
+ enabled=True,
298
+ write_operation=True,
299
+ read_private_data=True,
300
+ read_untrusted_public_data=True,
301
+ acl="SECRET",
302
+ )
242
303
  return self.prompt_permissions[prompt_name]
243
304
 
244
305
  def is_tool_enabled(self, tool_name: str) -> bool:
245
306
  """Check if a tool is enabled
246
307
  Also checks if the server is enabled"""
247
308
  permission = self.get_tool_permission(tool_name)
248
- server_name = self.server_name_from_tool_name(tool_name)
249
- server_enabled = self.is_server_enabled(server_name)
309
+ try:
310
+ server_name = self.server_name_from_tool_name(tool_name)
311
+ server_enabled = self.is_server_enabled(server_name)
312
+ except PermissionsError:
313
+ log.warning(f"Server resolution failed for tool '{tool_name}'; treating as disabled")
314
+ server_enabled = False
250
315
  return permission.enabled and server_enabled
251
316
 
252
317
  def is_resource_enabled(self, resource_name: str) -> bool:
253
318
  """Check if a resource is enabled
254
319
  Also checks if the server is enabled"""
255
320
  permission = self.get_resource_permission(resource_name)
256
- server_name = self.server_name_from_tool_name(resource_name)
257
- server_enabled = self.is_server_enabled(server_name)
321
+ try:
322
+ server_name = self.server_name_from_tool_name(resource_name)
323
+ server_enabled = self.is_server_enabled(server_name)
324
+ except PermissionsError:
325
+ log.warning(
326
+ f"Server resolution failed for resource '{resource_name}'; treating as disabled"
327
+ )
328
+ server_enabled = False
258
329
  return permission.enabled and server_enabled
259
330
 
260
331
  def is_prompt_enabled(self, prompt_name: str) -> bool:
261
332
  """Check if a prompt is enabled
262
333
  Also checks if the server is enabled"""
263
334
  permission = self.get_prompt_permission(prompt_name)
264
- server_name = self.server_name_from_tool_name(prompt_name)
265
- server_enabled = self.is_server_enabled(server_name)
335
+ try:
336
+ server_name = self.server_name_from_tool_name(prompt_name)
337
+ server_enabled = self.is_server_enabled(server_name)
338
+ except PermissionsError:
339
+ log.warning(
340
+ f"Server resolution failed for prompt '{prompt_name}'; treating as disabled"
341
+ )
342
+ server_enabled = False
266
343
  return permission.enabled and server_enabled
267
344
 
268
345
  @staticmethod
src/server.py CHANGED
@@ -31,8 +31,11 @@ from loguru import logger as log
31
31
  from pydantic import BaseModel, Field
32
32
 
33
33
  from src import events
34
- from src.config import Config, MCPServerConfig, clear_json_file_cache
34
+ from src.config import Config, MCPServerConfig, clear_json_file_cache, get_config_json_path
35
35
  from src.config import get_config_dir as _get_cfg_dir # type: ignore[attr-defined]
36
+ from src.mcp_stdio_capture import (
37
+ install_stdio_client_stderr_capture as _install_stdio_capture,
38
+ )
36
39
  from src.middleware.session_tracking import (
37
40
  MCPSessionModel,
38
41
  create_db_session,
@@ -48,6 +51,9 @@ _security = HTTPBearer()
48
51
  _auth_dependency = Depends(_security)
49
52
 
50
53
 
54
+ _install_stdio_capture()
55
+
56
+
51
57
  class OpenEdisonProxy:
52
58
  """
53
59
  Open Edison Single-User MCP Proxy Server
@@ -372,6 +378,9 @@ class OpenEdisonProxy:
372
378
  log.info(f"FastAPI management API on {self.host}:{self.port + 1}")
373
379
  log.info(f"FastMCP protocol server on {self.host}:{self.port}")
374
380
 
381
+ # Print location of config
382
+ log.info(f"Config file location: {get_config_json_path()}")
383
+
375
384
  initialize_telemetry()
376
385
 
377
386
  # Ensure the sessions database exists and has the required schema
@@ -543,11 +552,8 @@ class OpenEdisonProxy:
543
552
  warms the lists to ensure subsequent list calls reflect current state.
544
553
  """
545
554
  try:
546
- mcp = self.single_user_mcp
547
- # Warm managers so any internal caches are refreshed
548
- await mcp._tool_manager.list_tools() # type: ignore[attr-defined]
549
- await mcp._resource_manager.list_resources() # type: ignore[attr-defined]
550
- await mcp._prompt_manager.list_prompts() # type: ignore[attr-defined]
555
+ clear_json_file_cache()
556
+ Permissions.clear_permissions_file_cache()
551
557
  return {"status": "ok"}
552
558
  except Exception as e: # noqa: BLE001
553
559
  log.error(f"Failed to process permissions-changed: {e}")
@@ -662,14 +668,29 @@ class OpenEdisonProxy:
662
668
  )
663
669
 
664
670
  sessions: list[dict[str, Any]] = []
671
+ has_warned_about_missing_created_at = False
665
672
  for row_model in results:
666
673
  row = cast(Any, row_model)
667
674
  tool_calls_val = row.tool_calls
668
675
  data_access_summary_val = row.data_access_summary
676
+ created_at_val = None
677
+ if isinstance(data_access_summary_val, dict):
678
+ created_at_val = data_access_summary_val.get("created_at") # type: ignore[assignment]
679
+ if (
680
+ created_at_val is None
681
+ and isinstance(tool_calls_val, list)
682
+ and tool_calls_val
683
+ and not has_warned_about_missing_created_at
684
+ ):
685
+ has_warned_about_missing_created_at = True
686
+ log.warning(
687
+ "created_at is missing, will have sessions with unknown timestamps"
688
+ )
669
689
  sessions.append(
670
690
  {
671
691
  "session_id": row.session_id,
672
692
  "correlation_id": row.correlation_id,
693
+ "created_at": created_at_val,
673
694
  "tool_calls": tool_calls_val
674
695
  if isinstance(tool_calls_val, list)
675
696
  else [],
src/setup_tui/main.py CHANGED
@@ -132,16 +132,18 @@ def confirm_configs(configs: list[MCPServerConfig], *, dry_run: bool = False) ->
132
132
 
133
133
  def confirm_apply_configs(client: CLIENT, *, dry_run: bool = False) -> None:
134
134
  if not questionary.confirm(
135
- f"We have detected that you have {client.name} installed. Would you like to connect it to open-edison?",
135
+ f"Would you like to set up Open Edison for {client.name}? (This will modify your MCP configuration. We will make a back up of your current one if you would like to revert.)",
136
136
  default=True,
137
137
  ).ask():
138
138
  return
139
139
 
140
- export_edison_to(client, dry_run=dry_run)
140
+ result = export_edison_to(client, dry_run=dry_run)
141
141
  if dry_run:
142
142
  print(f"[dry-run] Export prepared for {client.name}; no changes written.")
143
143
  else:
144
- print(f"Successfully set up Open Edison for {client.name}!")
144
+ print(
145
+ f"Successfully set up Open Edison for {client.name}! Your previous MCP configuration has been backed up at {result.backup_path}"
146
+ )
145
147
 
146
148
 
147
149
  def show_manual_setup_screen() -> None:
@@ -235,7 +237,7 @@ def run(*, dry_run: bool = False, skip_oauth: bool = False) -> bool: # noqa: C9
235
237
 
236
238
  if len(configs) == 0:
237
239
  if not questionary.confirm(
238
- "No MCP servers found. Would you like to continue without them?", default=False
240
+ "No MCP servers found. Would you like to continue without them?", default=True
239
241
  ).ask():
240
242
  print("Setup aborted. Please configure an MCP client and try again.")
241
243
  return False