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/single_user_mcp.py CHANGED
@@ -5,12 +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
12
- from fastmcp.server.dependencies import get_context
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
+ )
13
21
  from loguru import logger as log
22
+ from mcp.server.lowlevel.server import LifespanResultT
14
23
 
15
24
  from src.config import Config, MCPServerConfig
16
25
  from src.middleware.session_tracking import (
@@ -61,6 +70,114 @@ class SingleUserMCP(FastMCP[Any]):
61
70
  self._setup_demo_resources()
62
71
  self._setup_demo_prompts()
63
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
+
64
181
  def _convert_to_fastmcp_config(self, enabled_servers: list[MCPServerConfig]) -> dict[str, Any]:
65
182
  """
66
183
  Convert Open Edison config format to FastMCP MCPConfig format.
@@ -88,46 +205,6 @@ class SingleUserMCP(FastMCP[Any]):
88
205
 
89
206
  return {"mcpServers": mcp_servers}
90
207
 
91
- async def create_composite_proxy(self, enabled_servers: list[MCPServerConfig]) -> bool:
92
- """
93
- Create a unified composite proxy for all enabled MCP servers.
94
-
95
- This replaces individual server mounting with a single FastMCP composite proxy
96
- that handles all configured servers with automatic namespacing.
97
-
98
- Args:
99
- enabled_servers: List of enabled MCP server configurations
100
-
101
- Returns:
102
- True if composite proxy was created successfully, False otherwise
103
- """
104
- if not enabled_servers:
105
- log.info("No real servers to mount in composite proxy")
106
- return True
107
-
108
- oauth_manager = get_oauth_manager()
109
-
110
- for server_config in enabled_servers:
111
- server_name = server_config.name
112
-
113
- # Skip if this server would produce an empty config (e.g., misconfigured)
114
- fastmcp_config = self._convert_to_fastmcp_config([server_config])
115
- if not fastmcp_config.get("mcpServers"):
116
- log.warning(f"Skipping server '{server_name}' due to empty MCP config")
117
- continue
118
-
119
- try:
120
- await self._mount_single_server(server_config, fastmcp_config, oauth_manager)
121
- except Exception as e:
122
- log.error(f"❌ Failed to mount server {server_name}: {e}")
123
- # Continue with other servers even if one fails
124
- continue
125
-
126
- log.info(
127
- f"✅ Created composite proxy with {len(enabled_servers)} servers ({mounted_servers.keys()})"
128
- )
129
- return True
130
-
131
208
  async def _mount_single_server(
132
209
  self,
133
210
  server_config: MCPServerConfig,
@@ -141,6 +218,7 @@ class SingleUserMCP(FastMCP[Any]):
141
218
  remote_url = server_config.get_remote_url()
142
219
  oauth_info = await oauth_manager.check_oauth_requirement(server_name, remote_url)
143
220
 
221
+ client_timeout = 10
144
222
  # Create proxy based on server type to avoid union type issues
145
223
  if server_config.is_remote_server():
146
224
  # Handle remote servers (with or without OAuth)
@@ -157,18 +235,22 @@ class SingleUserMCP(FastMCP[Any]):
157
235
  server_config.oauth_client_name,
158
236
  )
159
237
  if oauth_auth:
160
- client = FastMCPClient(remote_url, auth=oauth_auth)
238
+ client = FastMCPClient(
239
+ remote_url,
240
+ auth=oauth_auth,
241
+ timeout=client_timeout,
242
+ )
161
243
  log.info(
162
244
  f"🔐 Created remote client with OAuth authentication for {server_name}"
163
245
  )
164
246
  else:
165
- client = FastMCPClient(remote_url)
247
+ client = FastMCPClient(remote_url, timeout=client_timeout)
166
248
  log.warning(
167
249
  f"⚠️ OAuth auth creation failed, using unauthenticated client for {server_name}"
168
250
  )
169
251
  else:
170
252
  # Remote server without OAuth or needs auth
171
- client = FastMCPClient(remote_url)
253
+ client = FastMCPClient(remote_url, timeout=client_timeout)
172
254
  log.info(f"🌐 Created remote client for {server_name}")
173
255
 
174
256
  # Log OAuth status warnings
@@ -185,10 +267,12 @@ class SingleUserMCP(FastMCP[Any]):
185
267
 
186
268
  else:
187
269
  # Local server - create proxy directly from config (avoids union type issue)
188
- log.info(f"🔧 Creating local process proxy for {server_name}")
270
+ log.debug(f"🔧 Creating local process proxy for {server_name}")
189
271
  proxy = FastMCP.as_proxy(fastmcp_config)
190
272
 
191
- 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)
192
276
  mounted_servers[server_name] = MountedServerInfo(config=server_config, proxy=proxy)
193
277
 
194
278
  server_type = "remote" if server_config.is_remote_server() else "local"
@@ -231,10 +315,7 @@ class SingleUserMCP(FastMCP[Any]):
231
315
  try:
232
316
  oauth_manager = get_oauth_manager()
233
317
  await self._mount_single_server(server_config, fastmcp_config, oauth_manager)
234
- # Warm lists after mount
235
- _ = await self._tool_manager.list_tools()
236
- _ = await self._resource_manager.list_resources()
237
- _ = await self._prompt_manager.list_prompts()
318
+
238
319
  return True
239
320
  except Exception as e: # noqa: BLE001
240
321
  log.error(f"❌ Failed to mount server {server_name}: {e}")
@@ -243,7 +324,6 @@ class SingleUserMCP(FastMCP[Any]):
243
324
  async def unmount(self, server_name: str) -> bool:
244
325
  """
245
326
  Unmount a previously mounted server by name.
246
-
247
327
  Returns True if it was unmounted, False if it wasn't mounted.
248
328
  """
249
329
  info = mounted_servers.pop(server_name, None)
@@ -251,88 +331,145 @@ class SingleUserMCP(FastMCP[Any]):
251
331
  log.info(f"ℹ️ Server {server_name} was not mounted")
252
332
  return False
253
333
 
254
- proxy = info.get("proxy")
255
-
256
- # Manually remove from FastMCP managers' mounted lists
334
+ # Remove the server from mounted_servers lists in all managers
257
335
  for manager_name in ("_tool_manager", "_resource_manager", "_prompt_manager"):
258
336
  manager = getattr(self, manager_name, None)
337
+ if manager is None:
338
+ continue
259
339
  mounted_list = getattr(manager, "_mounted_servers", None)
260
340
  if mounted_list is None:
261
341
  continue
262
342
 
263
- # Prefer removing by both prefix and object identity; fallback to prefix-only
264
- new_list = [
265
- m
266
- for m in mounted_list
267
- if not (m.prefix == server_name and (proxy is None or m.server is proxy))
268
- ]
269
- if len(new_list) == len(mounted_list):
270
- 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]
271
345
 
272
- 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
+ }
273
352
 
274
- # Invalidate and warm lists to ensure reload
275
- _ = await self._tool_manager.list_tools()
276
- _ = await self._resource_manager.list_resources()
277
- _ = 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
+ }
278
380
 
279
381
  log.info(f"🧹 Unmounted server {server_name} and cleared references")
280
382
  return True
281
383
 
282
- async def _send_list_changed_notifications(self) -> None:
283
- """Send notifications to clients about changed component lists."""
284
- try:
285
- try:
286
- context = get_context()
287
- # Queue notifications for all component types since we don't know
288
- # what types of components the unmounted server provided
289
- context._queue_tool_list_changed() # type: ignore
290
- context._queue_resource_list_changed() # type: ignore
291
- context._queue_prompt_list_changed() # type: ignore
292
- log.debug("Queued component list change notifications")
293
- except RuntimeError:
294
- # No active context - notifications will be sent when context becomes available
295
- log.debug("No active context for notifications")
296
-
297
- except Exception as e:
298
- 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
299
442
 
300
443
  async def initialize(self) -> None:
301
444
  """Initialize the FastMCP server using unified composite proxy approach."""
302
445
  log.info("Initializing Single User MCP server with composite proxy")
303
446
  log.debug(f"Available MCP servers in config: {[s.name for s in Config().mcp_servers]}")
304
-
447
+ start_time = time.perf_counter()
305
448
  # Get all enabled servers
306
449
  enabled_servers = [s for s in Config().mcp_servers if s.enabled]
307
450
  log.info(
308
451
  f"Found {len(enabled_servers)} enabled servers: {[s.name for s in enabled_servers]}"
309
452
  )
310
453
 
311
- # Unmount all servers
312
- for server_name in list(mounted_servers.keys()):
313
- 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]
314
457
 
315
- # Create composite proxy for all real servers
316
- success = await self.create_composite_proxy(enabled_servers)
317
- if not success:
318
- log.error("Failed to create composite proxy")
319
- 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]
320
460
 
321
- 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)
322
464
 
323
- # Invalidate and warm lists to ensure reload
324
- log.debug("Reloading tool list...")
325
- _ = await self._tool_manager.list_tools()
326
- log.debug("Reloading resource list...")
327
- _ = await self._resource_manager.list_resources()
328
- log.debug("Reloading prompt list...")
329
- _ = await self._prompt_manager.list_prompts()
330
- log.debug("Reloading complete")
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)
331
468
 
332
- # Send notifications to clients about changed component lists
333
- log.debug("Sending list changed notifications...")
334
- await self._send_list_changed_notifications()
335
- log.debug("List changed notifications sent")
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
+ )
336
473
 
337
474
  def _calculate_risk_level(self, trifecta: dict[str, bool]) -> str:
338
475
  """
@@ -422,7 +559,7 @@ class SingleUserMCP(FastMCP[Any]):
422
559
  """
423
560
  Get a list of all available tools. Use this tool to get an updated list of available tools.
424
561
  """
425
- tool_list = await self._tool_manager.list_tools()
562
+ tool_list = await self.list_all_servers_tools_parallel()
426
563
  available_tools: list[str] = []
427
564
  log.trace(f"Raw tool list: {tool_list}")
428
565
  perms = Permissions()
@@ -458,7 +595,7 @@ class SingleUserMCP(FastMCP[Any]):
458
595
  def _setup_demo_resources(self) -> None:
459
596
  """Set up built-in demo resources for testing."""
460
597
 
461
- @self.resource("config://app") # noqa
598
+ @self.resource("info://builtin/app") # noqa
462
599
  def builtin_get_app_config() -> dict[str, Any]:
463
600
  """Get application configuration."""
464
601
  return {
@@ -467,7 +604,7 @@ class SingleUserMCP(FastMCP[Any]):
467
604
  "total_mounted": len(mounted_servers),
468
605
  }
469
606
 
470
- log.info("✅ Added built-in demo resources: config://app")
607
+ log.info("✅ Added built-in demo resources: info://builtin/app")
471
608
 
472
609
  def _setup_demo_prompts(self) -> None:
473
610
  """Set up built-in demo prompts for testing."""
@@ -1,40 +0,0 @@
1
- src/__init__.py,sha256=bEYMwBiuW9jzF07iWhas4Vb30EcpnqfpNfz_Q6yO1jU,209
2
- src/__main__.py,sha256=kQsaVyzRa_ESC57JpKDSQJAHExuXme0rM5beJsYxFeA,161
3
- src/cli.py,sha256=PH2qPLma0PO1L75OSK06IdPy8RB5gTBp2R1HkacCQ0Q,4736
4
- src/config.py,sha256=oO89omLCLoPfEGH6j4WSHQgZbhJZnYou-qdxY54VfDo,11037
5
- src/config.pyi,sha256=FgehEGli8ZXSjGlANBgMGv5497q4XskQciOc1fUcxqM,2033
6
- src/events.py,sha256=aFQrVXDIZwt55Dz6OtyoXu2yi9evqo-8jZzo3CR2Tto,4965
7
- src/oauth_manager.py,sha256=MJ1gHVKiu-pMbskSCxRlZ6xP4wJOr-ELydOdgdUBKKw,9969
8
- src/oauth_override.py,sha256=C7QS8sPA6JqJDiNZA0FGeXcB7jU-yYu-k8V56QVpsqU,393
9
- src/permissions.py,sha256=dERB8s40gDInsbXtu0pJYDDuZ3_kD8rxXyYYTfrG3qs,11621
10
- src/server.py,sha256=WseZks-r07tq7UGX0XFc0OUEzUrB9MGfDiHeYHa3NEE,46593
11
- src/single_user_mcp.py,sha256=2xQgrxqulTD_1HySoMNZD5AoADJS5pz41Gn-tEFgBHI,18760
12
- src/telemetry.py,sha256=-RZPIjpI53zbsKmp-63REeZ1JirWHV5WvpSRa2nqZEk,11321
13
- src/vulture_whitelist.py,sha256=CjBOSsarbzbQt_9ATWc8MbruBsYX3hJVa_ysbRD9ZYM,135
14
- src/frontend_dist/index.html,sha256=s95FMkH8VLisvawLH7bZxbLzRUFvMhHkH6ZMzpVBngs,673
15
- src/frontend_dist/sw.js,sha256=rihX1es-vWwjmtnXyaksJjs2dio6MVAOTAWwQPeJUYw,2164
16
- src/frontend_dist/assets/index-BUUcUfTt.js,sha256=awoyPI6u0v6ao2iarZdSkrSDUvyU8aNkMLqHMvgVgyY,257666
17
- src/frontend_dist/assets/index-o6_8mdM8.css,sha256=nwmX_6q55mB9463XN2JM8BdeihjkALpQK83Fc3_iGvE,15936
18
- src/mcp_importer/__init__.py,sha256=Mk59pVr7OMGfYGWeSYk8-URfhIcrs3SPLYS7fmJbMII,275
19
- src/mcp_importer/__main__.py,sha256=mFcxXFqJMC0SFEqIP-9WVEqLJSYqShC0x1Ht7PQZPm8,479
20
- src/mcp_importer/api.py,sha256=N5oVaTj3OMIROLx__UOSr60VMqXXX20JsOHmeHIGP48,17431
21
- src/mcp_importer/cli.py,sha256=Pe0GLWm1nMd1VuNXOSkxIrFZuGNFc9dNvfBsvf-bdBI,3487
22
- src/mcp_importer/export_cli.py,sha256=Fw0jDQCI8gGW4BDrJLzWjLUtV4q6v0h2QZ7HF1V2Jcg,6279
23
- src/mcp_importer/exporters.py,sha256=fSgl6seduoXFp7YnKH26UEaC1sFBnd4whSut7CJLBQs,11348
24
- src/mcp_importer/import_api.py,sha256=wD5yqxWwFfn1MQNKE79rEeyZODdmPgUDhsRYdCJYh4Q,59
25
- src/mcp_importer/importers.py,sha256=zGN8lT7qQJ95jDTd-ck09j_w5PSvH-uj33TILoHfHbs,2191
26
- src/mcp_importer/merge.py,sha256=KIGT7UgbAm07-LdyoUXEJ7ABSIiPTFlj_qjz669yFxg,1569
27
- src/mcp_importer/parsers.py,sha256=MDhzODsvX5t1U_CI8byBxCpx6rA4WkpqX4bJMiNE74s,6298
28
- src/mcp_importer/paths.py,sha256=4L-cPr7KCM9X9gAUP7Da6ictLNrPWuQ_IM419zqY-2I,2700
29
- src/mcp_importer/quick_cli.py,sha256=Vv2vjNzpSOaic0YHFbPAuX0nZByawS2kDw6KiCtEX3A,1798
30
- src/mcp_importer/types.py,sha256=nSaOLGqpCmA3R14QCO6wrpgX75VaLz9HfslUWzw_GPQ,102
31
- src/middleware/data_access_tracker.py,sha256=bArBffWgYmvxOx9z_pgXQhogvnWQcc1m6WvEblDD4gw,15039
32
- src/middleware/session_tracking.py,sha256=5W1VH9HNqIZeX0HNxDEm41U4GY6SqKSXtApDEeZK2qo,23084
33
- src/setup_tui/__init__.py,sha256=mDFrQoiOtQOHc0sFfGKrNXVLEDeB1S0O5aISBVzfxYo,184
34
- src/setup_tui/main.py,sha256=BM6t8YzWOSn2hDzVhp_nOol93NoHf6fiP4uFLOpx-BQ,11082
35
- src/tools/io.py,sha256=hhc4pv3eUzYWSZ7BbThclxSMwWBQaGMoGsItIPf_pco,1047
36
- open_edison-0.1.64.dist-info/METADATA,sha256=1X57SDfaooRExNvcCN-vHr1HH1QzbrVLWhJaNIaIEDI,11993
37
- open_edison-0.1.64.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
38
- open_edison-0.1.64.dist-info/entry_points.txt,sha256=YiGNm9x2I00hgT10HDyB4gxC1LcaV_mu8bXFjolu0Yw,171
39
- open_edison-0.1.64.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
40
- open_edison-0.1.64.dist-info/RECORD,,