magg 0.3.1__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.
- magg/__init__.py +10 -0
- magg/__main__.py +7 -0
- magg/cli.py +373 -0
- magg/discovery/__init__.py +1 -0
- magg/discovery/catalog.py +90 -0
- magg/discovery/metadata.py +744 -0
- magg/discovery/search.py +411 -0
- magg/logs/__init__.py +48 -0
- magg/logs/adapter.py +18 -0
- magg/logs/config.py +48 -0
- magg/logs/defaults.py +76 -0
- magg/logs/filter.py +17 -0
- magg/logs/formatter.py +16 -0
- magg/logs/handler.py +35 -0
- magg/logs/listener.py +58 -0
- magg/logs/queue.py +25 -0
- magg/mbro/__init__.py +5 -0
- magg/mbro/__main__.py +7 -0
- magg/mbro/acon.py +289 -0
- magg/mbro/cli.py +679 -0
- magg/mbro/client.py +344 -0
- magg/mbro/formatter.py +752 -0
- magg/mbro/test/__init__.py +1 -0
- magg/mbro/test/test_basic_functionality.py +95 -0
- magg/mbro/test/test_cli.py +296 -0
- magg/mbro/test/test_client.py +54 -0
- magg/mbro/test/test_integration.py +128 -0
- magg/mbro/test/test_search_functionality.py +171 -0
- magg/mbro/test/test_tool_calling.py +132 -0
- magg/process.py +48 -0
- magg/server/__init__.py +4 -0
- magg/server/__main__.py +7 -0
- magg/server/cli.py +38 -0
- magg/server/defaults.py +14 -0
- magg/server/manager.py +181 -0
- magg/server/proxy.py +223 -0
- magg/server/response.py +201 -0
- magg/server/runner.py +171 -0
- magg/server/server.py +692 -0
- magg/settings.py +247 -0
- magg/test/__init__.py +1 -0
- magg/test/test_basic.py +102 -0
- magg/test/test_client_api.py +135 -0
- magg/test/test_client_mounting.py +79 -0
- magg/test/test_config.py +213 -0
- magg/test/test_config_migration.py +172 -0
- magg/test/test_e2e_mounting.py +189 -0
- magg/test/test_e2e_simple.py +129 -0
- magg/test/test_error_handling.py +179 -0
- magg/test/test_integration.py +186 -0
- magg/test/test_mounting.py +155 -0
- magg/test/test_mounting_debug.py +99 -0
- magg/test/test_mounting_real.py +94 -0
- magg/test/test_server_add.py +238 -0
- magg/test/test_server_removal.py +240 -0
- magg/test/test_tool_delegation.py +151 -0
- magg/test/test_transport.py +190 -0
- magg/util/__init__.py +13 -0
- magg/util/system.py +56 -0
- magg/util/terminal.py +120 -0
- magg/util/transform.py +345 -0
- magg/util/transport.py +184 -0
- magg/util/transports.py +82 -0
- magg/util/uri.py +121 -0
- magg-0.3.1.dist-info/METADATA +19 -0
- magg-0.3.1.dist-info/RECORD +68 -0
- magg-0.3.1.dist-info/WHEEL +4 -0
- magg-0.3.1.dist-info/entry_points.txt +4 -0
magg/__init__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""MAGG - MCP Aggregator
|
|
2
|
+
|
|
3
|
+
A self-aware MCP server that manages and aggregates other MCP tools and servers.
|
|
4
|
+
"""
|
|
5
|
+
from importlib import metadata
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
__version__ = metadata.version("magg")
|
|
9
|
+
except metadata.PackageNotFoundError:
|
|
10
|
+
__version__ = "unknown"
|
magg/__main__.py
ADDED
magg/cli.py
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Main CLI interface for MAGG - Simplified implementation."""
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
from . import __version__, process
|
|
12
|
+
from .settings import ConfigManager, ServerConfig
|
|
13
|
+
from .util.terminal import (
|
|
14
|
+
print_success, print_error, print_warning,
|
|
15
|
+
print_info, print_server_list, print_status_summary, confirm_action
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
process.setup(source=__name__)
|
|
20
|
+
|
|
21
|
+
logger: logging.Logger | None = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
async def cmd_serve(args) -> None:
|
|
25
|
+
"""Start MAGG server."""
|
|
26
|
+
from magg.server.runner import print_startup_banner
|
|
27
|
+
from magg.server.runner import ServerRunner
|
|
28
|
+
|
|
29
|
+
logger.info("Starting MAGG server (mode: %s)", 'http' if args.http else 'stdio')
|
|
30
|
+
|
|
31
|
+
if args.http:
|
|
32
|
+
print_startup_banner()
|
|
33
|
+
|
|
34
|
+
runner = ServerRunner(args.config)
|
|
35
|
+
|
|
36
|
+
if args.http:
|
|
37
|
+
logger.info("Starting HTTP server on %s:%s", args.host, args.port)
|
|
38
|
+
await runner.run_http(host=args.host, port=args.port)
|
|
39
|
+
else:
|
|
40
|
+
logger.info("Starting stdio server")
|
|
41
|
+
await runner.run_stdio()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def cmd_serve_args(parser: argparse.ArgumentParser) -> None:
|
|
45
|
+
parser.add_argument(
|
|
46
|
+
'--http',
|
|
47
|
+
action='store_true',
|
|
48
|
+
help='Run as HTTP server instead of stdio mode'
|
|
49
|
+
)
|
|
50
|
+
parser.add_argument(
|
|
51
|
+
'--host',
|
|
52
|
+
type=str,
|
|
53
|
+
default='localhost',
|
|
54
|
+
help='HTTP server host address (default: localhost)'
|
|
55
|
+
)
|
|
56
|
+
parser.add_argument(
|
|
57
|
+
'--port',
|
|
58
|
+
type=int,
|
|
59
|
+
default=8000,
|
|
60
|
+
help='HTTP server port (default: 8000)'
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
async def cmd_add_server(args) -> None:
|
|
65
|
+
"""Add a new MCP server."""
|
|
66
|
+
config_manager = ConfigManager(args.config)
|
|
67
|
+
config = config_manager.load_config()
|
|
68
|
+
|
|
69
|
+
if args.name in config.servers:
|
|
70
|
+
logger.debug("Attempt to add duplicate server: %s", args.name)
|
|
71
|
+
print_error(f"Server '{args.name}' already exists")
|
|
72
|
+
sys.exit(1)
|
|
73
|
+
|
|
74
|
+
# Parse environment variables
|
|
75
|
+
env = None
|
|
76
|
+
if args.env:
|
|
77
|
+
try:
|
|
78
|
+
env = dict(arg.split('=', 1) for arg in args.env)
|
|
79
|
+
except ValueError:
|
|
80
|
+
print_error("Invalid environment variable format. Use KEY=VALUE")
|
|
81
|
+
sys.exit(1)
|
|
82
|
+
|
|
83
|
+
# Parse command and args
|
|
84
|
+
command = None
|
|
85
|
+
command_args = None
|
|
86
|
+
if args.command:
|
|
87
|
+
parts = args.command.split()
|
|
88
|
+
if parts:
|
|
89
|
+
command = parts[0]
|
|
90
|
+
command_args = parts[1:] if len(parts) > 1 else None
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
server = ServerConfig(
|
|
94
|
+
name=args.name,
|
|
95
|
+
source=args.source,
|
|
96
|
+
prefix=args.prefix, # Will be auto-generated if not provided
|
|
97
|
+
command=command,
|
|
98
|
+
args=command_args,
|
|
99
|
+
uri=args.uri,
|
|
100
|
+
env=env,
|
|
101
|
+
working_dir=args.working_dir,
|
|
102
|
+
notes=args.notes
|
|
103
|
+
)
|
|
104
|
+
except ValueError as e:
|
|
105
|
+
print_error(f"Invalid server configuration: {e}")
|
|
106
|
+
sys.exit(1)
|
|
107
|
+
|
|
108
|
+
config.add_server(server)
|
|
109
|
+
|
|
110
|
+
if config_manager.save_config(config):
|
|
111
|
+
print_success(f"Added server '{args.name}'")
|
|
112
|
+
print(f" Source: {args.source}")
|
|
113
|
+
print(f" Prefix: {server.prefix}")
|
|
114
|
+
if server.command:
|
|
115
|
+
full_command = server.command
|
|
116
|
+
if server.args:
|
|
117
|
+
full_command += ' ' + ' '.join(server.args)
|
|
118
|
+
print(f" Command: {full_command}")
|
|
119
|
+
if server.notes:
|
|
120
|
+
print(f" Notes: {server.notes}")
|
|
121
|
+
else:
|
|
122
|
+
print_error("Failed to save configuration")
|
|
123
|
+
sys.exit(1)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
async def cmd_list_servers(args) -> None:
|
|
127
|
+
"""List configured servers."""
|
|
128
|
+
config_manager = ConfigManager(args.config)
|
|
129
|
+
config = config_manager.load_config()
|
|
130
|
+
|
|
131
|
+
# logger.debug("Listing %d configured servers", len(config.servers))
|
|
132
|
+
print_server_list(config.servers)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
async def cmd_remove_server(args) -> None:
|
|
136
|
+
"""Remove a server."""
|
|
137
|
+
config_manager = ConfigManager(args.config)
|
|
138
|
+
config = config_manager.load_config()
|
|
139
|
+
|
|
140
|
+
if args.name not in config.servers:
|
|
141
|
+
logger.warning("Attempt to remove non-existent server: %s", args.name)
|
|
142
|
+
print_error(f"Server '{args.name}' not found")
|
|
143
|
+
sys.exit(1)
|
|
144
|
+
|
|
145
|
+
# Show server details before removal
|
|
146
|
+
server = config.servers[args.name]
|
|
147
|
+
print_info(f"Server to remove: {args.name}")
|
|
148
|
+
print(f" Source: {server.source}")
|
|
149
|
+
print(f" Prefix: {server.prefix}")
|
|
150
|
+
|
|
151
|
+
if not args.force and not confirm_action("Are you sure you want to remove this server?"):
|
|
152
|
+
logger.debug("User cancelled removal of server '%s'", args.name)
|
|
153
|
+
print_info("Removal cancelled")
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
config.remove_server(args.name)
|
|
157
|
+
|
|
158
|
+
if config_manager.save_config(config):
|
|
159
|
+
logger.info("Successfully removed server '%s'", args.name)
|
|
160
|
+
print_success(f"Removed server '{args.name}'")
|
|
161
|
+
else:
|
|
162
|
+
print_error("Failed to save configuration")
|
|
163
|
+
sys.exit(1)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
async def cmd_enable_server(args) -> None:
|
|
167
|
+
"""Enable a server."""
|
|
168
|
+
config_manager = ConfigManager(args.config)
|
|
169
|
+
config = config_manager.load_config()
|
|
170
|
+
|
|
171
|
+
if args.name not in config.servers:
|
|
172
|
+
print_error(f"Server '{args.name}' not found")
|
|
173
|
+
sys.exit(1)
|
|
174
|
+
|
|
175
|
+
server = config.servers[args.name]
|
|
176
|
+
if server.enabled:
|
|
177
|
+
print_info(f"Server '{args.name}' is already enabled")
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
server.enabled = True
|
|
181
|
+
|
|
182
|
+
if config_manager.save_config(config):
|
|
183
|
+
print_success(f"Enabled server '{args.name}'")
|
|
184
|
+
print_info("The server will be mounted on next startup")
|
|
185
|
+
else:
|
|
186
|
+
print_error("Failed to save configuration")
|
|
187
|
+
sys.exit(1)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
async def cmd_disable_server(args) -> None:
|
|
191
|
+
"""Disable a server."""
|
|
192
|
+
config_manager = ConfigManager(args.config)
|
|
193
|
+
config = config_manager.load_config()
|
|
194
|
+
|
|
195
|
+
if args.name not in config.servers:
|
|
196
|
+
print_error(f"Server '{args.name}' not found")
|
|
197
|
+
sys.exit(1)
|
|
198
|
+
|
|
199
|
+
server = config.servers[args.name]
|
|
200
|
+
if not server.enabled:
|
|
201
|
+
print_info(f"Server '{args.name}' is already disabled")
|
|
202
|
+
return
|
|
203
|
+
|
|
204
|
+
server.enabled = False
|
|
205
|
+
|
|
206
|
+
if config_manager.save_config(config):
|
|
207
|
+
print_success(f"Disabled server '{args.name}'")
|
|
208
|
+
print_warning("The server will remain mounted until MAGG is restarted")
|
|
209
|
+
else:
|
|
210
|
+
print_error("Failed to save configuration")
|
|
211
|
+
sys.exit(1)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
async def cmd_status(args) -> None:
|
|
215
|
+
"""Show MAGG status."""
|
|
216
|
+
config_manager = ConfigManager(args.config)
|
|
217
|
+
config = config_manager.load_config()
|
|
218
|
+
|
|
219
|
+
enabled = [s for s in config.servers.values() if s.enabled]
|
|
220
|
+
disabled = [s for s in config.servers.values() if not s.enabled]
|
|
221
|
+
|
|
222
|
+
print_status_summary(
|
|
223
|
+
str(config_manager.config_path),
|
|
224
|
+
len(config.servers),
|
|
225
|
+
len(enabled),
|
|
226
|
+
len(disabled)
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
async def cmd_export(args) -> None:
|
|
231
|
+
"""Export configuration."""
|
|
232
|
+
config_manager = ConfigManager(args.config)
|
|
233
|
+
config = config_manager.load_config()
|
|
234
|
+
|
|
235
|
+
export_data = {
|
|
236
|
+
'servers': {
|
|
237
|
+
name: server.model_dump(
|
|
238
|
+
exclude_none=True, exclude_unset=True, exclude_defaults=True, by_alias=True
|
|
239
|
+
)
|
|
240
|
+
for name, server in config.servers.items()
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if args.output:
|
|
245
|
+
try:
|
|
246
|
+
with open(args.output, 'w') as f:
|
|
247
|
+
json.dump(export_data, f, indent=2)
|
|
248
|
+
print_success(f"Exported configuration to {args.output}")
|
|
249
|
+
except IOError as e:
|
|
250
|
+
print_error(f"Failed to write to {args.output}: {e}")
|
|
251
|
+
sys.exit(1)
|
|
252
|
+
else:
|
|
253
|
+
print(json.dumps(export_data, indent=2))
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def create_parser() -> argparse.ArgumentParser:
|
|
257
|
+
"""Create the command line parser."""
|
|
258
|
+
parser = argparse.ArgumentParser(
|
|
259
|
+
prog='magg',
|
|
260
|
+
description='MAGG - MCP Aggregator: Manage and aggregate MCP servers',
|
|
261
|
+
epilog='Use "magg <command> --help" for more information about a command.'
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
parser.add_argument(
|
|
265
|
+
'--version', '-V',
|
|
266
|
+
action='version',
|
|
267
|
+
version=f'%(prog)s {__version__}',
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
parser.add_argument(
|
|
271
|
+
'--config',
|
|
272
|
+
type=str,
|
|
273
|
+
help='Path to config file (default: .magg/config.json in current directory)'
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
subparsers = parser.add_subparsers(dest='subcommand', help='Commands')
|
|
277
|
+
|
|
278
|
+
# Serve command
|
|
279
|
+
serve_parser = subparsers.add_parser(
|
|
280
|
+
'serve',
|
|
281
|
+
help='Start MAGG server',
|
|
282
|
+
description='Start the MAGG server in either stdio mode (default) or HTTP mode'
|
|
283
|
+
)
|
|
284
|
+
cmd_serve_args(serve_parser)
|
|
285
|
+
|
|
286
|
+
# Add server command
|
|
287
|
+
add_parser = subparsers.add_parser('add-server', help='Add a new server')
|
|
288
|
+
add_parser.add_argument('name', help='Server name')
|
|
289
|
+
add_parser.add_argument('source', help='URL of the server package/repository')
|
|
290
|
+
add_parser.add_argument('--prefix', help='Tool prefix (defaults to server name)')
|
|
291
|
+
add_parser.add_argument('--command', help='Command to run the server')
|
|
292
|
+
add_parser.add_argument('--uri', help='URI for HTTP servers')
|
|
293
|
+
add_parser.add_argument('--env', nargs='*', help='Environment variables (KEY=VALUE)')
|
|
294
|
+
add_parser.add_argument('--working-dir', help='Working directory')
|
|
295
|
+
add_parser.add_argument('--notes', help='Setup notes')
|
|
296
|
+
|
|
297
|
+
# List servers
|
|
298
|
+
subparsers.add_parser('list-servers', help='List configured servers')
|
|
299
|
+
|
|
300
|
+
# Remove server
|
|
301
|
+
remove_parser = subparsers.add_parser('remove-server', help='Remove a server')
|
|
302
|
+
remove_parser.add_argument('name', help='Server name')
|
|
303
|
+
remove_parser.add_argument('--force', '-f', action='store_true', help='Remove without confirmation')
|
|
304
|
+
|
|
305
|
+
# Enable/disable server
|
|
306
|
+
enable_parser = subparsers.add_parser('enable-server', help='Enable a server')
|
|
307
|
+
enable_parser.add_argument('name', help='Server name')
|
|
308
|
+
|
|
309
|
+
disable_parser = subparsers.add_parser('disable-server', help='Disable a server')
|
|
310
|
+
disable_parser.add_argument('name', help='Server name')
|
|
311
|
+
|
|
312
|
+
# Status command
|
|
313
|
+
subparsers.add_parser('status', help='Show MAGG status')
|
|
314
|
+
|
|
315
|
+
# Export command
|
|
316
|
+
export_parser = subparsers.add_parser('export', help='Export configuration')
|
|
317
|
+
export_parser.add_argument('--output', '-o', help='Output file (default: stdout)')
|
|
318
|
+
|
|
319
|
+
return parser
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
async def run():
|
|
323
|
+
"""Main entry point (async)."""
|
|
324
|
+
parser = create_parser()
|
|
325
|
+
args = parser.parse_args()
|
|
326
|
+
|
|
327
|
+
if not args.subcommand:
|
|
328
|
+
parser.print_help()
|
|
329
|
+
sys.exit(1)
|
|
330
|
+
|
|
331
|
+
# Map commands to functions
|
|
332
|
+
commands = {
|
|
333
|
+
'serve': cmd_serve,
|
|
334
|
+
'add-server': cmd_add_server,
|
|
335
|
+
'list-servers': cmd_list_servers,
|
|
336
|
+
'remove-server': cmd_remove_server,
|
|
337
|
+
'enable-server': cmd_enable_server,
|
|
338
|
+
'disable-server': cmd_disable_server,
|
|
339
|
+
'status': cmd_status,
|
|
340
|
+
'export': cmd_export,
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
cmd_func = commands.get(args.subcommand)
|
|
344
|
+
if cmd_func:
|
|
345
|
+
await cmd_func(args)
|
|
346
|
+
else:
|
|
347
|
+
parser.print_help()
|
|
348
|
+
sys.exit(1)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def main():
|
|
352
|
+
"""Run the CLI."""
|
|
353
|
+
global logger
|
|
354
|
+
|
|
355
|
+
process.setup()
|
|
356
|
+
|
|
357
|
+
logger = logging.getLogger(__name__)
|
|
358
|
+
|
|
359
|
+
try:
|
|
360
|
+
asyncio.run(run())
|
|
361
|
+
except KeyboardInterrupt:
|
|
362
|
+
print_warning("\nOperation cancelled by user")
|
|
363
|
+
sys.exit(130) # Standard exit code for SIGINT
|
|
364
|
+
except Exception as e:
|
|
365
|
+
print_error(f"Unexpected error: {e}")
|
|
366
|
+
if os.getenv('MAGG_DEBUG').lower() in {'1', 'true', 'yes'}:
|
|
367
|
+
import traceback
|
|
368
|
+
traceback.print_exc()
|
|
369
|
+
sys.exit(1)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
if __name__ == '__main__':
|
|
373
|
+
main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Tool discovery and search capabilities."""
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Simplified tool catalog for search functionality only."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from .search import ToolSearchEngine, ToolSearchResult, ToolCatalog
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CatalogManager:
|
|
12
|
+
"""Manages tool search catalog - search functionality only."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, catalog_path: Path | None = None):
|
|
15
|
+
self.catalog_path = catalog_path or Path.cwd() / ".magg" / "search_cache.json"
|
|
16
|
+
self.catalog_path.parent.mkdir(parents=True, exist_ok=True)
|
|
17
|
+
|
|
18
|
+
self.search_catalog = ToolCatalog()
|
|
19
|
+
self.logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
# Load existing search cache
|
|
22
|
+
self.load_search_cache()
|
|
23
|
+
|
|
24
|
+
def load_search_cache(self) -> None:
|
|
25
|
+
"""Load search cache from disk."""
|
|
26
|
+
if not self.catalog_path.exists():
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
with open(self.catalog_path, 'r') as f:
|
|
31
|
+
data = json.load(f)
|
|
32
|
+
|
|
33
|
+
# Load search catalog cache
|
|
34
|
+
if "search_catalog" in data:
|
|
35
|
+
self.search_catalog.import_catalog(data["search_catalog"])
|
|
36
|
+
|
|
37
|
+
except Exception as e:
|
|
38
|
+
self.logger.error(f"Error loading search cache: {e}")
|
|
39
|
+
|
|
40
|
+
def save_search_cache(self) -> None:
|
|
41
|
+
"""Save search cache to disk."""
|
|
42
|
+
try:
|
|
43
|
+
data = {
|
|
44
|
+
"search_catalog": self.search_catalog.export_catalog()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
with open(self.catalog_path, 'w') as f:
|
|
48
|
+
json.dump(data, f, indent=2)
|
|
49
|
+
|
|
50
|
+
except Exception as e:
|
|
51
|
+
self.logger.error(f"Error saving search cache: {e}")
|
|
52
|
+
|
|
53
|
+
async def search_only(self, query: str, limit_per_source: int = 5) -> dict[str, list[ToolSearchResult]]:
|
|
54
|
+
"""Search for tools without auto-adding to cache."""
|
|
55
|
+
async with ToolSearchEngine() as search_engine:
|
|
56
|
+
results = await search_engine.search_all(query, limit_per_source)
|
|
57
|
+
return results
|
|
58
|
+
|
|
59
|
+
async def search_and_cache(self, query: str, limit_per_source: int = 5) -> dict[str, list[ToolSearchResult]]:
|
|
60
|
+
"""Search for tools and update the cache."""
|
|
61
|
+
async with ToolSearchEngine() as search_engine:
|
|
62
|
+
results = await search_engine.search_all(query, limit_per_source)
|
|
63
|
+
|
|
64
|
+
# Add all results to cache
|
|
65
|
+
for source_results in results.values():
|
|
66
|
+
self.search_catalog.add_results(source_results)
|
|
67
|
+
|
|
68
|
+
# Save updated cache
|
|
69
|
+
self.save_search_cache()
|
|
70
|
+
|
|
71
|
+
return results
|
|
72
|
+
|
|
73
|
+
def search_local_cache(self, query: str) -> list[ToolSearchResult]:
|
|
74
|
+
"""Search the local cache."""
|
|
75
|
+
return self.search_catalog.search_catalog(query)
|
|
76
|
+
|
|
77
|
+
def get_search_stats(self) -> dict[str, Any]:
|
|
78
|
+
"""Get statistics about the search cache."""
|
|
79
|
+
total_cached = len(self.search_catalog.catalog)
|
|
80
|
+
|
|
81
|
+
# Count by source
|
|
82
|
+
source_counts = {}
|
|
83
|
+
for result in self.search_catalog.catalog.values():
|
|
84
|
+
source_counts[result.source] = source_counts.get(result.source, 0) + 1
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
"total_cached": total_cached,
|
|
88
|
+
"source_breakdown": source_counts,
|
|
89
|
+
"cache_path": str(self.catalog_path)
|
|
90
|
+
}
|