open-edison 0.1.44__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/single_user_mcp.py CHANGED
@@ -5,11 +5,21 @@ FastMCP instance for the single-user Open Edison setup.
5
5
  Handles MCP protocol communication with running servers using a unified composite proxy.
6
6
  """
7
7
 
8
+ import asyncio
9
+ import time
8
10
  from typing import Any, TypedDict
9
11
 
10
12
  from fastmcp import Client as FastMCPClient
11
13
  from fastmcp import Context, FastMCP
14
+ from fastmcp.server.server import add_resource_prefix, has_resource_prefix
15
+
16
+ # Low level FastMCP imports
17
+ from fastmcp.tools.tool import Tool
18
+ from fastmcp.tools.tool_transform import (
19
+ apply_transformations_to_tools,
20
+ )
12
21
  from loguru import logger as log
22
+ from mcp.server.lowlevel.server import LifespanResultT
13
23
 
14
24
  from src.config import Config, MCPServerConfig
15
25
  from src.middleware.session_tracking import (
@@ -60,6 +70,114 @@ class SingleUserMCP(FastMCP[Any]):
60
70
  self._setup_demo_resources()
61
71
  self._setup_demo_prompts()
62
72
 
73
+ async def import_server(
74
+ self,
75
+ server: FastMCP[LifespanResultT],
76
+ prefix: str | None = None,
77
+ tool_separator: str | None = None,
78
+ resource_separator: str | None = None,
79
+ prompt_separator: str | None = None,
80
+ ) -> None:
81
+ """
82
+ Import the MCP objects from another FastMCP server into this one with a given prefix.
83
+ Overloads FastMCP's import_server method to improve performance.
84
+
85
+ Args:
86
+ server: The FastMCP server to import
87
+ prefix: prefix to use for the imported server's objects. If None,
88
+ objects are imported with their original names.
89
+ tool_separator: Deprecated. Required to be None.
90
+ resource_separator: Deprecated. Required to be None.
91
+ prompt_separator: Deprecated. Required to be None.
92
+ """
93
+
94
+ if prefix is None:
95
+ raise ValueError("Prefix is required")
96
+
97
+ if tool_separator is not None:
98
+ raise ValueError("Tool separator is deprecated and not supported")
99
+
100
+ if resource_separator is not None:
101
+ raise ValueError("Resource separator is deprecated and not supported")
102
+
103
+ if prompt_separator is not None:
104
+ raise ValueError("Prompt separator is deprecated and not supported")
105
+
106
+ log.debug(f"🔧 Importing server {prefix} ({server.name}) into single user MCP'")
107
+
108
+ # Fetch all server objects in parallel
109
+ tools, resources, templates, prompts = await asyncio.gather(
110
+ server.get_tools(),
111
+ server.get_resources(),
112
+ server.get_resource_templates(),
113
+ server.get_prompts(),
114
+ return_exceptions=True,
115
+ )
116
+
117
+ # Validate and normalize all results
118
+ tools = self._validate_server_result(tools, "tools", server.name)
119
+ resources = self._validate_server_result(resources, "resources", server.name)
120
+ templates = self._validate_server_result(templates, "templates", server.name)
121
+ prompts = self._validate_server_result(prompts, "prompts", server.name)
122
+
123
+ # Import all components
124
+ self._import_tools(tools, prefix)
125
+ self._import_resources(resources, prefix)
126
+ self._import_templates(templates, prefix)
127
+ self._import_prompts(prompts, prefix)
128
+
129
+ log.debug(f"Imported server {server.name} with prefix '{prefix}'")
130
+
131
+ def _validate_server_result(
132
+ self, result: Any, result_type: str, server_name: str
133
+ ) -> dict[str, Any]:
134
+ """Validate and normalize server result from asyncio.gather with return_exceptions=True."""
135
+ if isinstance(result, Exception):
136
+ log.warning(f'Server {server_name} does not appear to contain "{result_type}"')
137
+ log.debug(f"Server {server_name} _validate_server_result exception result: {result}")
138
+ return {}
139
+ if not isinstance(result, dict):
140
+ log.warning(f"Server {server_name} returned an unexpected response")
141
+ log.debug(
142
+ f"Server {server_name} _validate_server_result unexpected type {type(result)} with value: {result}"
143
+ )
144
+ return {}
145
+ return result # type: ignore[return-value]
146
+
147
+ def _import_tools(self, tools: dict[str, Any], prefix: str) -> None:
148
+ """Import tools from server"""
149
+ for key, tool in tools.items():
150
+ if prefix:
151
+ tool = tool.model_copy(key=f"{prefix}_{key}")
152
+ self._tool_manager.add_tool(tool)
153
+
154
+ def _import_resources(self, resources: dict[str, Any], prefix: str) -> None:
155
+ """Import resources from server"""
156
+ for key, resource in resources.items():
157
+ if prefix:
158
+ resource_key = add_resource_prefix(key, prefix, self.resource_prefix_format)
159
+ resource = resource.model_copy(
160
+ update={"name": f"{prefix}_{resource.name}"}, key=resource_key
161
+ )
162
+ self._resource_manager.add_resource(resource)
163
+
164
+ def _import_templates(self, templates: dict[str, Any], prefix: str) -> None:
165
+ """Import templates from server"""
166
+ for key, template in templates.items():
167
+ if prefix:
168
+ template_key = add_resource_prefix(key, prefix, self.resource_prefix_format)
169
+ template = template.model_copy(
170
+ update={"name": f"{prefix}_{template.name}"}, key=template_key
171
+ )
172
+ self._resource_manager.add_template(template)
173
+
174
+ def _import_prompts(self, prompts: dict[str, Any], prefix: str) -> None:
175
+ """Import prompts from server"""
176
+ for key, prompt in prompts.items():
177
+ if prefix:
178
+ prompt = prompt.model_copy(key=f"{prefix}_{key}")
179
+ self._prompt_manager.add_prompt(prompt)
180
+
63
181
  def _convert_to_fastmcp_config(self, enabled_servers: list[MCPServerConfig]) -> dict[str, Any]:
64
182
  """
65
183
  Convert Open Edison config format to FastMCP MCPConfig format.
@@ -87,46 +205,6 @@ class SingleUserMCP(FastMCP[Any]):
87
205
 
88
206
  return {"mcpServers": mcp_servers}
89
207
 
90
- async def create_composite_proxy(self, enabled_servers: list[MCPServerConfig]) -> bool:
91
- """
92
- Create a unified composite proxy for all enabled MCP servers.
93
-
94
- This replaces individual server mounting with a single FastMCP composite proxy
95
- that handles all configured servers with automatic namespacing.
96
-
97
- Args:
98
- enabled_servers: List of enabled MCP server configurations
99
-
100
- Returns:
101
- True if composite proxy was created successfully, False otherwise
102
- """
103
- if not enabled_servers:
104
- log.info("No real servers to mount in composite proxy")
105
- return True
106
-
107
- oauth_manager = get_oauth_manager()
108
-
109
- for server_config in enabled_servers:
110
- server_name = server_config.name
111
-
112
- # Skip if this server would produce an empty config (e.g., misconfigured)
113
- fastmcp_config = self._convert_to_fastmcp_config([server_config])
114
- if not fastmcp_config.get("mcpServers"):
115
- log.warning(f"Skipping server '{server_name}' due to empty MCP config")
116
- continue
117
-
118
- try:
119
- await self._mount_single_server(server_config, fastmcp_config, oauth_manager)
120
- except Exception as e:
121
- log.error(f"❌ Failed to mount server {server_name}: {e}")
122
- # Continue with other servers even if one fails
123
- continue
124
-
125
- log.info(
126
- f"✅ Created composite proxy with {len(enabled_servers)} servers ({mounted_servers.keys()})"
127
- )
128
- return True
129
-
130
208
  async def _mount_single_server(
131
209
  self,
132
210
  server_config: MCPServerConfig,
@@ -140,6 +218,7 @@ class SingleUserMCP(FastMCP[Any]):
140
218
  remote_url = server_config.get_remote_url()
141
219
  oauth_info = await oauth_manager.check_oauth_requirement(server_name, remote_url)
142
220
 
221
+ client_timeout = 10
143
222
  # Create proxy based on server type to avoid union type issues
144
223
  if server_config.is_remote_server():
145
224
  # Handle remote servers (with or without OAuth)
@@ -156,18 +235,22 @@ class SingleUserMCP(FastMCP[Any]):
156
235
  server_config.oauth_client_name,
157
236
  )
158
237
  if oauth_auth:
159
- client = FastMCPClient(remote_url, auth=oauth_auth)
238
+ client = FastMCPClient(
239
+ remote_url,
240
+ auth=oauth_auth,
241
+ timeout=client_timeout,
242
+ )
160
243
  log.info(
161
244
  f"🔐 Created remote client with OAuth authentication for {server_name}"
162
245
  )
163
246
  else:
164
- client = FastMCPClient(remote_url)
247
+ client = FastMCPClient(remote_url, timeout=client_timeout)
165
248
  log.warning(
166
249
  f"⚠️ OAuth auth creation failed, using unauthenticated client for {server_name}"
167
250
  )
168
251
  else:
169
252
  # Remote server without OAuth or needs auth
170
- client = FastMCPClient(remote_url)
253
+ client = FastMCPClient(remote_url, timeout=client_timeout)
171
254
  log.info(f"🌐 Created remote client for {server_name}")
172
255
 
173
256
  # Log OAuth status warnings
@@ -184,10 +267,12 @@ class SingleUserMCP(FastMCP[Any]):
184
267
 
185
268
  else:
186
269
  # Local server - create proxy directly from config (avoids union type issue)
187
- log.info(f"🔧 Creating local process proxy for {server_name}")
270
+ log.debug(f"🔧 Creating local process proxy for {server_name}")
188
271
  proxy = FastMCP.as_proxy(fastmcp_config)
189
272
 
190
- super().mount(proxy, prefix=server_name)
273
+ log.debug(f"🔧 Importing server {server_name} into single user MCP")
274
+ await self.import_server(proxy, prefix=server_name)
275
+ # await super().import_server(proxy, prefix=server_name)
191
276
  mounted_servers[server_name] = MountedServerInfo(config=server_config, proxy=proxy)
192
277
 
193
278
  server_type = "remote" if server_config.is_remote_server() else "local"
@@ -230,10 +315,7 @@ class SingleUserMCP(FastMCP[Any]):
230
315
  try:
231
316
  oauth_manager = get_oauth_manager()
232
317
  await self._mount_single_server(server_config, fastmcp_config, oauth_manager)
233
- # Warm lists after mount
234
- _ = await self._tool_manager.list_tools()
235
- _ = await self._resource_manager.list_resources()
236
- _ = await self._prompt_manager.list_prompts()
318
+
237
319
  return True
238
320
  except Exception as e: # noqa: BLE001
239
321
  log.error(f"❌ Failed to mount server {server_name}: {e}")
@@ -242,7 +324,6 @@ class SingleUserMCP(FastMCP[Any]):
242
324
  async def unmount(self, server_name: str) -> bool:
243
325
  """
244
326
  Unmount a previously mounted server by name.
245
-
246
327
  Returns True if it was unmounted, False if it wasn't mounted.
247
328
  """
248
329
  info = mounted_servers.pop(server_name, None)
@@ -250,85 +331,145 @@ class SingleUserMCP(FastMCP[Any]):
250
331
  log.info(f"ℹ️ Server {server_name} was not mounted")
251
332
  return False
252
333
 
253
- proxy = info.get("proxy")
254
-
255
- # Manually remove from FastMCP managers' mounted lists
334
+ # Remove the server from mounted_servers lists in all managers
256
335
  for manager_name in ("_tool_manager", "_resource_manager", "_prompt_manager"):
257
336
  manager = getattr(self, manager_name, None)
337
+ if manager is None:
338
+ continue
258
339
  mounted_list = getattr(manager, "_mounted_servers", None)
259
340
  if mounted_list is None:
260
341
  continue
261
342
 
262
- # Prefer removing by both prefix and object identity; fallback to prefix-only
263
- new_list = [
264
- m
265
- for m in mounted_list
266
- if not (m.prefix == server_name and (proxy is None or m.server is proxy))
267
- ]
268
- if len(new_list) == len(mounted_list):
269
- new_list = [m for m in mounted_list if m.prefix != server_name]
343
+ # Remove servers with matching prefix
344
+ mounted_list[:] = [m for m in mounted_list if m.prefix != server_name]
270
345
 
271
- mounted_list[:] = new_list
346
+ # Remove tools with matching prefix (server name)
347
+ self._tool_manager._tools = { # type: ignore
348
+ key: value
349
+ for key, value in self._tool_manager._tools.items() # type: ignore
350
+ if not key.startswith(f"{server_name}_")
351
+ }
272
352
 
273
- # Invalidate and warm lists to ensure reload
274
- _ = await self._tool_manager.list_tools()
275
- _ = await self._resource_manager.list_resources()
276
- _ = await self._prompt_manager.list_prompts()
353
+ # Remove transformations with matching prefix (server name)
354
+ self._tool_manager.transformations = { # type: ignore
355
+ key: value
356
+ for key, value in self._tool_manager.transformations.items() # type: ignore
357
+ if not key.startswith(f"{server_name}_")
358
+ }
359
+
360
+ # Remove resources with matching prefix (server name)
361
+ self._resource_manager._resources = { # type: ignore
362
+ key: value
363
+ for key, value in self._resource_manager._resources.items() # type: ignore
364
+ if not has_resource_prefix(key, server_name, self.resource_prefix_format) # type: ignore
365
+ }
366
+
367
+ # Remove templates with matching prefix (server name)
368
+ self._resource_manager._templates = { # type: ignore
369
+ key: value
370
+ for key, value in self._resource_manager._templates.items() # type: ignore
371
+ if not has_resource_prefix(key, server_name, self.resource_prefix_format) # type: ignore
372
+ }
373
+
374
+ # Remove prompts with matching prefix (server name)
375
+ self._prompt_manager._prompts = { # type: ignore
376
+ key: value
377
+ for key, value in self._prompt_manager._prompts.items() # type: ignore
378
+ if not key.startswith(f"{server_name}_")
379
+ }
277
380
 
278
381
  log.info(f"🧹 Unmounted server {server_name} and cleared references")
279
382
  return True
280
383
 
281
- async def _send_list_changed_notifications(self) -> None:
282
- """Send notifications to clients about changed component lists."""
283
- try:
284
- # Import here to avoid circular imports
285
- from fastmcp.server.dependencies import get_context
286
-
287
- try:
288
- context = get_context()
289
- # Queue notifications for all component types since we don't know
290
- # what types of components the unmounted server provided
291
- context._queue_tool_list_changed() # type: ignore
292
- context._queue_resource_list_changed() # type: ignore
293
- context._queue_prompt_list_changed() # type: ignore
294
- log.debug("Queued component list change notifications")
295
- except RuntimeError:
296
- # No active context - notifications will be sent when context becomes available
297
- log.debug("No active context for notifications")
298
-
299
- except Exception as e:
300
- log.warning(f"Error sending unmount notifications: {e}")
384
+ async def list_all_servers_tools_parallel(self) -> list[Tool]:
385
+ """Reload all servers' tools in parallel.
386
+ Reimplements FastMCP's ToolManager._list_tools method with parallel execution.
387
+ """
388
+
389
+ # Execute all server reloads in parallel
390
+ list_tasks = [
391
+ server.server._list_tools()
392
+ for server in self._tool_manager._mounted_servers # type: ignore
393
+ ]
394
+
395
+ log.debug(f"Starting reload for {len(list_tasks)} servers' tools in parallel")
396
+ start_time = time.perf_counter()
397
+ all_tools: dict[str, Tool] = {}
398
+ if list_tasks:
399
+ # Use return_exceptions=True to prevent one failing server from breaking everything
400
+ tools_lists = await asyncio.gather(*list_tasks, return_exceptions=True)
401
+ for server, tools_result in zip(
402
+ self._tool_manager._mounted_servers, # type: ignore
403
+ tools_lists,
404
+ strict=False,
405
+ ):
406
+ if isinstance(tools_result, Exception):
407
+ log.warning(f"Failed to get tools from server {server.prefix}: {tools_result}")
408
+ continue
409
+
410
+ tools_list = tools_result
411
+ if not tools_list or not isinstance(tools_list, list):
412
+ continue
413
+
414
+ tools_dict = {t.key: t for t in tools_list} # type: ignore
415
+ if server.prefix:
416
+ for tool in tools_dict.values():
417
+ prefixed_tool = tool.model_copy( # type: ignore
418
+ key=f"{server.prefix}_{tool.key}" # type: ignore
419
+ )
420
+ all_tools[prefixed_tool.key] = prefixed_tool # type: ignore
421
+ else:
422
+ all_tools.update(tools_dict) # type: ignore
423
+ log.debug(
424
+ f"Saved {len(all_tools)} tools from {len([r for r in tools_lists if not isinstance(r, Exception)])} servers"
425
+ )
426
+ else:
427
+ all_tools = {}
428
+
429
+ # Add local tools
430
+ all_tools.update(self._tool_manager._tools) # type: ignore
431
+
432
+ transformed_tools = apply_transformations_to_tools(
433
+ tools=all_tools,
434
+ transformations=self._tool_manager.transformations,
435
+ )
436
+
437
+ final_tools_list = list(transformed_tools.values())
438
+
439
+ end_time = time.perf_counter()
440
+ log.debug(f"Time taken to reload all servers' tools: {end_time - start_time:.1f} seconds")
441
+ return final_tools_list
301
442
 
302
443
  async def initialize(self) -> None:
303
444
  """Initialize the FastMCP server using unified composite proxy approach."""
304
445
  log.info("Initializing Single User MCP server with composite proxy")
305
446
  log.debug(f"Available MCP servers in config: {[s.name for s in Config().mcp_servers]}")
306
-
447
+ start_time = time.perf_counter()
307
448
  # Get all enabled servers
308
449
  enabled_servers = [s for s in Config().mcp_servers if s.enabled]
309
450
  log.info(
310
451
  f"Found {len(enabled_servers)} enabled servers: {[s.name for s in enabled_servers]}"
311
452
  )
312
453
 
313
- # Unmount all servers
314
- for server_name in list(mounted_servers.keys()):
315
- await self.unmount(server_name)
454
+ # Figure out which servers are to be unmounted
455
+ enabled_server_names = {s.name for s in enabled_servers}
456
+ servers_to_unmount = [s for s in mounted_servers if s not in enabled_server_names]
316
457
 
317
- # Create composite proxy for all real servers
318
- success = await self.create_composite_proxy(enabled_servers)
319
- if not success:
320
- log.error("Failed to create composite proxy")
321
- return
458
+ # Figure out which servers are to be mounted
459
+ servers_to_mount = [s.name for s in enabled_servers if s.name not in mounted_servers]
322
460
 
323
- log.info("✅ Single User MCP server initialized with composite proxy")
461
+ # Unmount those servers (quick)
462
+ for server_name in servers_to_unmount:
463
+ await self.unmount(server_name)
324
464
 
325
- # Invalidate and warm lists to ensure reload
326
- _ = await self._tool_manager.list_tools()
327
- _ = await self._resource_manager.list_resources()
328
- _ = await self._prompt_manager.list_prompts()
465
+ # Mount those servers (async gathered bc import does network roundtrip)
466
+ mount_tasks = [self.mount_server(server_name) for server_name in servers_to_mount]
467
+ await asyncio.gather(*mount_tasks)
329
468
 
330
- # Send notifications to clients about changed component lists
331
- await self._send_list_changed_notifications()
469
+ log.info("✅ Single User MCP server initialized with composite proxy")
470
+ log.debug(
471
+ f"Time taken to initialize Single User MCP server: {time.perf_counter() - start_time:.1f} seconds"
472
+ )
332
473
 
333
474
  def _calculate_risk_level(self, trifecta: dict[str, bool]) -> str:
334
475
  """
@@ -418,7 +559,7 @@ class SingleUserMCP(FastMCP[Any]):
418
559
  """
419
560
  Get a list of all available tools. Use this tool to get an updated list of available tools.
420
561
  """
421
- tool_list = await self._tool_manager.list_tools()
562
+ tool_list = await self.list_all_servers_tools_parallel()
422
563
  available_tools: list[str] = []
423
564
  log.trace(f"Raw tool list: {tool_list}")
424
565
  perms = Permissions()
@@ -454,7 +595,7 @@ class SingleUserMCP(FastMCP[Any]):
454
595
  def _setup_demo_resources(self) -> None:
455
596
  """Set up built-in demo resources for testing."""
456
597
 
457
- @self.resource("config://app") # noqa
598
+ @self.resource("info://builtin/app") # noqa
458
599
  def builtin_get_app_config() -> dict[str, Any]:
459
600
  """Get application configuration."""
460
601
  return {
@@ -463,7 +604,7 @@ class SingleUserMCP(FastMCP[Any]):
463
604
  "total_mounted": len(mounted_servers),
464
605
  }
465
606
 
466
- log.info("✅ Added built-in demo resources: config://app")
607
+ log.info("✅ Added built-in demo resources: info://builtin/app")
467
608
 
468
609
  def _setup_demo_prompts(self) -> None:
469
610
  """Set up built-in demo prompts for testing."""
src/tools/io.py ADDED
@@ -0,0 +1,35 @@
1
+ import os
2
+ from collections.abc import Iterator
3
+ from contextlib import contextmanager
4
+
5
+
6
+ @contextmanager
7
+ def suppress_fds(*, suppress_stdout: bool = False, suppress_stderr: bool = True) -> Iterator[None]:
8
+ """Temporarily redirect process-level stdout/stderr to os.devnull.
9
+
10
+ Args:
11
+ suppress_stdout: If True, redirect fd 1 to devnull
12
+ suppress_stderr: If True, redirect fd 2 to devnull
13
+
14
+ Yields:
15
+ None
16
+ """
17
+ saved: list[tuple[int, int]] = []
18
+ try:
19
+ if suppress_stdout:
20
+ saved.append((1, os.dup(1)))
21
+ devnull_out = os.open(os.devnull, os.O_WRONLY)
22
+ os.dup2(devnull_out, 1)
23
+ os.close(devnull_out)
24
+ if suppress_stderr:
25
+ saved.append((2, os.dup(2)))
26
+ devnull_err = os.open(os.devnull, os.O_WRONLY)
27
+ os.dup2(devnull_err, 2)
28
+ os.close(devnull_err)
29
+ yield
30
+ finally:
31
+ for fd, backup in saved:
32
+ try:
33
+ os.dup2(backup, fd)
34
+ finally:
35
+ os.close(backup)
@@ -0,0 +1,3 @@
1
+ from src.oauth_override import OpenEdisonOAuth
2
+
3
+ OpenEdisonOAuth.redirect_handler # noqa: B018 unused method (src/oauth_override.py:7)
@@ -1,37 +0,0 @@
1
- src/__init__.py,sha256=bEYMwBiuW9jzF07iWhas4Vb30EcpnqfpNfz_Q6yO1jU,209
2
- src/__main__.py,sha256=kQsaVyzRa_ESC57JpKDSQJAHExuXme0rM5beJsYxFeA,161
3
- src/cli.py,sha256=fqX-HuRDePRasexpnURQ_pVYeycJuWxllMcwfqDxMQw,8490
4
- src/config.py,sha256=RSsAYzl8cj6eaDN1RORMcfKKWBcp4bKTQp2BdhAL9mg,10258
5
- src/config.pyi,sha256=FgehEGli8ZXSjGlANBgMGv5497q4XskQciOc1fUcxqM,2033
6
- src/events.py,sha256=aFQrVXDIZwt55Dz6OtyoXu2yi9evqo-8jZzo3CR2Tto,4965
7
- src/oauth_manager.py,sha256=W9QSo0vfGDQ_i-QWCngkv7YLSL3Rk5jfPmqjU1J2rnU,9911
8
- src/permissions.py,sha256=NGAnlG_z59HEiVA-k3cYvwmmiuHzxuNb5Tbd5umbL00,10483
9
- src/server.py,sha256=cnO5bgxT-lrfuwk9AIvB_HBV8SWOtFClfGUn5_zFWyo,45652
10
- src/single_user_mcp.py,sha256=rJrlqHcIubGkos_24ux5rb3OoKYDzvagCHghhfDeXTI,18535
11
- src/telemetry.py,sha256=-RZPIjpI53zbsKmp-63REeZ1JirWHV5WvpSRa2nqZEk,11321
12
- src/frontend_dist/index.html,sha256=s95FMkH8VLisvawLH7bZxbLzRUFvMhHkH6ZMzpVBngs,673
13
- src/frontend_dist/sw.js,sha256=rihX1es-vWwjmtnXyaksJjs2dio6MVAOTAWwQPeJUYw,2164
14
- src/frontend_dist/assets/index-BUUcUfTt.js,sha256=awoyPI6u0v6ao2iarZdSkrSDUvyU8aNkMLqHMvgVgyY,257666
15
- src/frontend_dist/assets/index-o6_8mdM8.css,sha256=nwmX_6q55mB9463XN2JM8BdeihjkALpQK83Fc3_iGvE,15936
16
- src/mcp_importer/__init__.py,sha256=Mk59pVr7OMGfYGWeSYk8-URfhIcrs3SPLYS7fmJbMII,275
17
- src/mcp_importer/__main__.py,sha256=0jVfxKzyr6koVu1ghhWseah5ilKIoGovE6zkEZ-u-Og,515
18
- src/mcp_importer/api.py,sha256=47tur0xgl1NBI1Vnh3cpScEmDS64bKMYcWjZDuqx7HQ,6644
19
- src/mcp_importer/cli.py,sha256=Pe0GLWm1nMd1VuNXOSkxIrFZuGNFc9dNvfBsvf-bdBI,3487
20
- src/mcp_importer/export_cli.py,sha256=daEadB6nL8P4OpEGFx0GshuN1a091L7BhiitpV1bPqA,6294
21
- src/mcp_importer/exporters.py,sha256=fSgl6seduoXFp7YnKH26UEaC1sFBnd4whSut7CJLBQs,11348
22
- src/mcp_importer/import_api.py,sha256=xWaKoE3vibSWpA5roVL7qEMS73vcmAC0tcHP6CsZw6E,95
23
- src/mcp_importer/importers.py,sha256=zGN8lT7qQJ95jDTd-ck09j_w5PSvH-uj33TILoHfHbs,2191
24
- src/mcp_importer/merge.py,sha256=KIGT7UgbAm07-LdyoUXEJ7ABSIiPTFlj_qjz669yFxg,1569
25
- src/mcp_importer/parsers.py,sha256=JRE7y_Gg-QmlAARvZdrI9CmUyy-ODvDPbS695pb3Aw8,4856
26
- src/mcp_importer/paths.py,sha256=4L-cPr7KCM9X9gAUP7Da6ictLNrPWuQ_IM419zqY-2I,2700
27
- src/mcp_importer/quick_cli.py,sha256=4mJe10q_lZCYLm75QBt1rYy2j8mGEsRZoAqA0agjfSM,1834
28
- src/mcp_importer/types.py,sha256=h03TbAnJbap6OWWd0dT0QcFWNvSaiVFWH9V9PD6x4s0,138
29
- src/middleware/data_access_tracker.py,sha256=bArBffWgYmvxOx9z_pgXQhogvnWQcc1m6WvEblDD4gw,15039
30
- src/middleware/session_tracking.py,sha256=5W1VH9HNqIZeX0HNxDEm41U4GY6SqKSXtApDEeZK2qo,23084
31
- src/setup_tui/__init__.py,sha256=mDFrQoiOtQOHc0sFfGKrNXVLEDeB1S0O5aISBVzfxYo,184
32
- src/setup_tui/main.py,sha256=892X2KVKOYmKzUu1Ok8SnApNYxpcFHFHmFLvpPZP4qY,5501
33
- open_edison-0.1.44.dist-info/METADATA,sha256=hY2fd8IeT-YeBAUjg_FBtpGf50VGOpgRp4xdBhx7ED4,12375
34
- open_edison-0.1.44.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
35
- open_edison-0.1.44.dist-info/entry_points.txt,sha256=YiGNm9x2I00hgT10HDyB4gxC1LcaV_mu8bXFjolu0Yw,171
36
- open_edison-0.1.44.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
37
- open_edison-0.1.44.dist-info/RECORD,,